Created
November 25, 2017 03:47
-
-
Save koji-m/5fb31be2cae0e482d048341e12c6f5a5 to your computer and use it in GitHub Desktop.
Vanilla Text version 0.0.2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//win.rs ver 0.0.2 | |
extern crate gtk; | |
extern crate gio; | |
extern crate gdk; | |
use gtk::{ | |
ResponseType, TextViewExt, | |
TextBufferExt, | |
WidgetExt, FileChooserExt, DialogExt, | |
ContainerExt, | |
Cast, WindowExt, | |
}; | |
use gio::{ | |
FileExt, SimpleActionExt, ActionMapExt | |
}; | |
use std::cell::RefCell; | |
use std::rc::Rc; | |
use std::path::Path; | |
use std::ops::Deref; | |
pub struct WindowCore { | |
window: gtk::ApplicationWindow, | |
file: Option<gio::File>, | |
text_view: gtk::TextView, | |
changed: bool, | |
} | |
pub type Window = Rc<RefCell<WindowCore>>; | |
pub trait WindowExtend { | |
fn create(app: >k::Application, wins: Windows) -> Window; | |
fn init_actions(&self, wins: Windows); | |
fn init(&self); | |
fn register(&self, wins: Windows); | |
fn load_file(&self, file: &gio::File); | |
fn save_file(&self, wins: Windows) -> bool; | |
fn save_buffer(&self); | |
fn save_as(&self, wins: Windows) -> bool; | |
fn save_file_chooser_run(&self) -> Option<gio::File>; | |
fn save_confirm(&self, wins: Windows) -> bool; | |
fn is_empty(&self) -> bool; | |
fn window(&self) -> gtk::ApplicationWindow; | |
fn file(&self) -> Option<gio::File>; | |
fn set_file(&self, file: Option<gio::File>); | |
fn text_view(&self) -> gtk::TextView; | |
fn changed(&self) -> bool; | |
fn set_changed(&self, changed: bool); | |
fn set_title(&self, title: &str); | |
} | |
impl WindowExtend for Window { | |
fn create(app: >k::Application, wins: Windows) -> Window { | |
let window = gtk::ApplicationWindow::new(app); | |
window.set_default_size(800, 600); | |
window.set_title("Vanilla Text"); | |
let scr_win = gtk::ScrolledWindow::new(None, None); | |
let txt_view = gtk::TextView::new(); | |
scr_win.add(&txt_view); | |
window.add(&scr_win); | |
let win = Rc::new(RefCell::new( | |
WindowCore {window: window.clone(), | |
file: None, | |
text_view: txt_view.clone(), | |
changed: false})); | |
win.init_actions(wins.clone()); | |
{ | |
let win = win.clone(); | |
win.text_view().get_buffer().unwrap().connect_changed(move |_| { | |
if win.changed() { | |
return; | |
} | |
win.set_changed(true); | |
}); | |
} | |
{ | |
let win = win.clone(); | |
let wins = wins.clone(); | |
window.connect_delete_event(move |_, _| { | |
gtk::Inhibit(win.save_confirm(wins.clone())) | |
}); | |
} | |
win.register(wins); | |
win | |
} | |
fn init_actions(&self, wins: Windows) { | |
let save_action = gio::SimpleAction::new("save", None); | |
{ | |
let win = self.clone(); | |
let wins = wins.clone(); | |
save_action.connect_activate(move |_, _| { | |
win.save_file(wins.clone()); | |
}); | |
} | |
let saveas_action = gio::SimpleAction::new("saveas", None); | |
{ | |
let win = self.clone(); | |
let wins = wins.clone(); | |
saveas_action.connect_activate(move |_, _| { | |
win.save_as(wins.clone()); | |
}); | |
} | |
let close_action = gio::SimpleAction::new("close", None); | |
{ | |
let win = self.clone(); | |
let wins = wins.clone(); | |
close_action.connect_activate(move |_, _| { | |
if !win.save_confirm(wins.clone()) { | |
wins.destroy(win.clone()); | |
} | |
}); | |
} | |
let selectall_action = gio::SimpleAction::new("selectall", None); | |
{ | |
let win = self.clone(); | |
selectall_action.connect_activate(move |_, _| { | |
let buf = win.text_view().get_buffer().unwrap(); | |
let (start, end) = buf.get_bounds(); | |
buf.select_range(&start, &end); | |
}); | |
} | |
let copy_action = gio::SimpleAction::new("copy", None); | |
{ | |
let win = self.clone(); | |
copy_action.connect_activate(move |_, _| { | |
let text_view = win.text_view(); | |
let clipboard = text_view.get_clipboard(&gdk::SELECTION_CLIPBOARD); | |
text_view.get_buffer().unwrap().copy_clipboard(&clipboard); | |
}); | |
} | |
let paste_action = gio::SimpleAction::new("paste", None); | |
{ | |
let win = self.clone(); | |
paste_action.connect_activate(move |_, _| { | |
let clipboard; | |
let buf; | |
let editable; | |
{ | |
let text_view = win.text_view(); | |
clipboard = text_view.get_clipboard(&gdk::SELECTION_CLIPBOARD); | |
buf = text_view.get_buffer().unwrap(); | |
editable = text_view.get_editable(); | |
} | |
buf.paste_clipboard(&clipboard, None, editable); | |
}); | |
} | |
let cut_action = gio::SimpleAction::new("cut", None); | |
{ | |
let win = self.clone(); | |
cut_action.connect_activate(move |_, _| { | |
let clipboard; | |
let buf; | |
let editable; | |
{ | |
let text_view = win.text_view(); | |
clipboard = text_view.get_clipboard(&gdk::SELECTION_CLIPBOARD); | |
buf = text_view.get_buffer().unwrap(); | |
editable = text_view.get_editable(); | |
} | |
buf.cut_clipboard(&clipboard, editable); | |
let text_view = win.text_view(); | |
text_view.scroll_mark_onscreen(&buf.get_insert().unwrap()); | |
}); | |
} | |
let w = self.window(); | |
w.add_action(&save_action); | |
w.add_action(&saveas_action); | |
w.add_action(&close_action); | |
w.add_action(&selectall_action); | |
w.add_action(©_action); | |
w.add_action(&paste_action); | |
w.add_action(&cut_action); | |
} | |
fn init(&self) { | |
self.borrow().window.show_all(); | |
} | |
fn register(&self, wins: Windows) { | |
wins.borrow_mut().push(self.clone()); | |
} | |
fn load_file(&self, file: &gio::File) { | |
if let Ok((v, _)) = file.load_contents(None) { | |
let text = String::from_utf8(v).unwrap(); | |
let buf = self.text_view().get_buffer().unwrap(); | |
buf.set_text(&text); | |
self.window().set_title(file.get_basename().unwrap().to_str().unwrap()); | |
self.set_file(Some(file.clone())); | |
self.set_changed(false); | |
} | |
} | |
fn save_file(&self, wins: Windows) -> bool { | |
if self.file().is_some() { | |
self.save_buffer(); | |
return false; | |
} else { | |
self.save_as(wins.clone()) | |
} | |
} | |
fn save_buffer(&self) { | |
if let Some(buf) = self.text_view().get_buffer() { | |
let (start, end) = buf.get_bounds(); | |
if let Some(text) = buf.get_text(&start, &end, true) { | |
if self.file().as_ref().unwrap().replace_contents(text.as_bytes(), | |
None, | |
true, | |
gio::FILE_CREATE_NONE, | |
None).is_ok() { | |
self.set_changed(false); | |
} else { | |
let dialog = gtk::MessageDialog::new(Some(&self.window()), | |
gtk::DIALOG_MODAL, | |
gtk::MessageType::Error, | |
gtk::ButtonsType::Close, | |
"Error: Cannot save file"); | |
dialog.run(); | |
dialog.destroy(); | |
} | |
} | |
} | |
} | |
fn save_as(&self, wins: Windows) -> bool { | |
if let Some(file) = self.save_file_chooser_run() { | |
if let Some(w) = wins.get_window(&file) { | |
if w.window() != self.window() { | |
let dialog = gtk::MessageDialog::new(Some(&self.window()), | |
gtk::DIALOG_MODAL, | |
gtk::MessageType::Warning, | |
gtk::ButtonsType::Close, | |
"Error: Cannot save file beacause another window has been editing the file"); | |
dialog.run(); | |
dialog.destroy(); | |
return true; | |
} | |
} | |
self.set_file(Some(file)); | |
self.save_buffer(); | |
return false; | |
} | |
true | |
} | |
fn save_file_chooser_run(&self) -> Option<gio::File> { | |
let dialog = gtk::FileChooserDialog::new::<gtk::Window>(Some("Save File"), | |
Some(&self.window().upcast()), | |
gtk::FileChooserAction::Save); | |
dialog.add_button("Cancel", ResponseType::Cancel.into()); | |
dialog.add_button("Save", ResponseType::Accept.into()); | |
dialog.set_do_overwrite_confirmation(true); | |
if let Some(ref f) = self.file() { | |
dialog.set_filename(f.get_path().unwrap().as_path()); | |
} else { | |
dialog.set_current_name(Path::new("untitled")); | |
} | |
let file; | |
if dialog.run() == ResponseType::Accept.into() { | |
if let Some(path) = dialog.get_filename() { | |
file = Some(gio::File::new_for_path(path.as_path())) | |
} else { | |
file = None | |
} | |
} else { | |
file = None | |
} | |
dialog.destroy(); | |
file | |
} | |
fn save_confirm(&self, wins: Windows) -> bool { | |
if !self.changed() { | |
return false; | |
} | |
let dialog = gtk::MessageDialog::new(Some(&self.window()), | |
gtk::DIALOG_MODAL, | |
gtk::MessageType::Warning, | |
gtk::ButtonsType::None, | |
"Save change before closing?"); | |
dialog.add_button("Close without saving", ResponseType::Reject.into()); | |
dialog.add_button("Cancel", ResponseType::Cancel.into()); | |
dialog.add_button("Save", ResponseType::Accept.into()); | |
let r = dialog.run(); | |
dialog.destroy(); | |
if r == ResponseType::Accept.into() { | |
if self.file().is_some() { | |
self.save_file(wins.clone()) | |
} else { | |
self.save_as(wins.clone()) | |
} | |
} else if r == ResponseType::Cancel.into() { | |
true | |
} else if r == ResponseType::Reject.into() { | |
false | |
} else { | |
true | |
} | |
} | |
fn is_empty(&self) -> bool { | |
if self.file().is_some() { | |
return false; | |
} | |
let (start, end) = self.text_view().get_buffer().unwrap().get_bounds(); | |
start == end | |
} | |
fn window(&self) -> gtk::ApplicationWindow { | |
self.borrow().window.clone() | |
} | |
fn file(&self) -> Option<gio::File> { | |
self.borrow().file.clone() | |
} | |
fn set_file(&self, file: Option<gio::File>) { | |
self.borrow_mut().file = file; | |
} | |
fn changed(&self) -> bool { | |
self.borrow().changed | |
} | |
fn set_changed(&self, changed: bool) { | |
self.borrow_mut().changed = changed; | |
} | |
fn text_view(&self) -> gtk::TextView { | |
self.borrow().text_view.clone() | |
} | |
fn set_title(&self, title: &str) { | |
self.borrow().window.set_title(title); | |
} | |
} | |
pub type Windows = Rc<RefCell<Vec<Window>>>; | |
pub trait WindowsExtend { | |
fn get_window(&self, file: &gio::File) -> Option<Window>; | |
fn get_empty_window(&self) -> Option<Window>; | |
fn open(&self, file: &gio::File, app: >k::Application) -> Window; | |
fn destroy(&self, win: Window); | |
} | |
impl WindowsExtend for Windows { | |
fn get_window(&self, file: &gio::File) -> Option<Window> { | |
for w in self.borrow().deref() { | |
if let Some(ref f) = w.file() { | |
if f.equal(file) { | |
return Some(w.clone()); | |
} | |
} | |
} | |
return None; | |
} | |
fn get_empty_window(&self) -> Option<Window> { | |
for w in self.borrow().deref() { | |
if w.is_empty() { | |
return Some(w.clone()); | |
} | |
} | |
return None; | |
} | |
fn open(&self, file: &gio::File, app: >k::Application) -> Window { | |
let win = Window::create(app, self.clone()); | |
win.init(); | |
win.load_file(file); | |
win | |
} | |
fn destroy(&self, win: Window) { | |
let win = win.clone(); | |
let i = self.borrow().iter().position(move |w| { | |
w.window() == win.window() | |
}).unwrap(); | |
{ | |
let v = self.borrow(); | |
v[i].window().destroy(); | |
} | |
self.borrow_mut().remove(i); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment