Skip to content

Instantly share code, notes, and snippets.

@koji-m
Created November 25, 2017 03:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koji-m/5fb31be2cae0e482d048341e12c6f5a5 to your computer and use it in GitHub Desktop.
Save koji-m/5fb31be2cae0e482d048341e12c6f5a5 to your computer and use it in GitHub Desktop.
Vanilla Text version 0.0.2
//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: &gtk::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: &gtk::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(&copy_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: &gtk::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: &gtk::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