From 385f2dda32f47db53ce213f147e88bf730a29eb0 Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Sun, 24 Oct 2021 14:20:53 +0700 Subject: [PATCH] refactor: move all traits to EchidnaWindow struct Before, all the traits are implemented for EchidnaApplication. This is added to better support using the app with many windows open. EchidnaApplication may be obsolete now, but I'm not removing it for now. --- src/app/imp.rs | 198 -------------------- src/components/app/imp.rs | 60 ++++++ src/{ => components}/app/mod.rs | 4 +- src/components/mod.rs | 1 + src/components/window/file.rs | 134 +++++++++++++ src/components/window/imp.rs | 37 ++++ src/{app => components/window}/menubar.rs | 48 ++--- src/components/window/mod.rs | 36 ++++ src/components/window/sidebar.rs | 9 + src/components/window/window.ui | 55 ++++++ src/{app => components/window}/workspace.rs | 0 src/main.rs | 11 +- 12 files changed, 359 insertions(+), 234 deletions(-) delete mode 100644 src/app/imp.rs create mode 100644 src/components/app/imp.rs rename src/{ => components}/app/mod.rs (96%) create mode 100644 src/components/window/file.rs create mode 100644 src/components/window/imp.rs rename src/{app => components/window}/menubar.rs (75%) create mode 100644 src/components/window/mod.rs create mode 100644 src/components/window/sidebar.rs create mode 100644 src/components/window/window.ui rename src/{app => components/window}/workspace.rs (100%) diff --git a/src/app/imp.rs b/src/app/imp.rs deleted file mode 100644 index d4643f8..0000000 --- a/src/app/imp.rs +++ /dev/null @@ -1,198 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use std::str::from_utf8; -use sourceview::{View, Buffer, LanguageManager, }; -use sourceview::prelude::*; -use gtk::subclass::prelude::*; -use gtk::prelude::*; -use super::menubar::MenubarImplementedEditor; -use gtk::{ - ApplicationWindow, - Application, - FileChooserDialog, - FileChooserAction, - ResponseType, - Label - -}; - -use gio::{ - MenuModel, - SimpleAction, - Cancellable -}; -//use super::workspace; -use glib::clone; - -#[derive(Debug, Default)] -pub struct EchidnaEditor { - pub name: &'static str, - pub app_id: &'static str -} - -pub trait EchidnaEditorExt { - fn action_open_file(window: ApplicationWindow, app: super::EchidnaEditor, action: &SimpleAction, variant: Option<&glib::Variant>, notebook: gtk::Notebook); - fn open_file(notebook: >k::Notebook, file: gio::File); -} - - - -#[glib::object_subclass] -impl ObjectSubclass for EchidnaEditor { - const NAME: &'static str = "EchidnaEditorApplication"; - type Type = super::EchidnaEditor; - type ParentType = Application; - - fn new() -> Self { - Self { - name: "Echidna Code Editor", - app_id: "land.echidna.editor" - } - } -} - - - -impl EchidnaEditorExt for EchidnaEditor { - - /* - Open a file and put it in an editor and the opened files bar. - - - Open a file chooser dialog. - - Connect a signal to it and get the file choosen. - - Read th file's information and - - TODO: if it's a plain text, - - TODO: Load the file's content - - TODO: Create an editor - - TODO: Set the editor's content to the file's content. - - TODO: Somehow keep track of what file belongs to what editor/opened file bar widget. - - TODO: If the user enables Autosave, listen to the editor's changes and automatically save the editor's content. - - TODO: Close the editor and the tab widget when the file is closed (triggered by the X button on them). - - Perhaps some of the last points should not be implemented in this function but rather in another function that keeps track of every files. - */ - fn action_open_file(window: ApplicationWindow, app: super::EchidnaEditor, _action: &SimpleAction, variant: Option<&glib::Variant>, notebook: gtk::Notebook){ - - let dialog: FileChooserDialog = FileChooserDialog::new(Some("Open a file"), - Some(&window), - FileChooserAction::Open, - &[("Cancel", ResponseType::Cancel), - ("Open", ResponseType::Accept) ]); - - - dialog.set_visible(true); - - - - // TODO: Somehow inserts self to this function. - // This function sets the callback function as 'static, which for some reasons ban cloning self into it. Idk why. - dialog.connect_response(clone!( @weak app, @weak window, => - move |dialog, response| { - - if response == ResponseType::Accept { - - let file_option = dialog.file(); - - match file_option { - Some(file) => { - dialog.destroy(); - - Self::open_file(¬ebook, file); - - }, - None => { - - }, - } - } else if response == ResponseType::Cancel { - dialog.destroy(); - - } })); - - } - - fn open_file(notebook: >k::Notebook, file: gio::File){ - let cancellable = Cancellable::new(); - let filepath = file.path().expect("No filepath"); - let file_info_result = file.query_info( - "*", - gio::FileQueryInfoFlags::NONE, - Some(&cancellable)); - match file_info_result { - Ok(info) => { - match info.content_type() { - Some(content_type) => { - println!("Opened {} and found its content type is {}.", "file", content_type.to_string()); - let content_cancellable = Cancellable::new(); - let file_content = file.load_contents(Some(&content_cancellable)); - match file_content { - Ok(content) => { - let (int_vec, _text_option) = content; - let language_manager = LanguageManager::new(); - let language = language_manager.guess_language(Some(&info.name().to_str().expect("Could not open the file because its name is not supported by Unicode.")), None); - - let buffer = Buffer::new(None); - buffer.set_text(from_utf8(&int_vec).expect(format!("Could not parse the contents of {:?} as it's unsupported by UTF-8", filepath).as_str())); - match language { - Some(lang) => buffer.set_language(Some(&lang)), - None => {} - } - - let sourceview = View::with_buffer(&buffer); - - notebook.prepend_page(&sourceview, - Some(&Label::new(Some(&info.name().to_str() - .expect("Could not parse file's name"))))); - - } - Err(e) => println!("Could not open {:?} because:\n{:#?}", filepath, e), - } - }, - None => println!("It does not seem like {:?} has a type", filepath), - - } - - }, - Err(e) => println!("Could not retrieve file information for {:?} because:\n{}", filepath, e), - } - - - } - - -} - -impl ObjectImpl for EchidnaEditor { - - - -} - -impl ApplicationImpl for EchidnaEditor { - - fn activate(&self, app: &Self::Type){ - - let builder = gtk::Builder::from_string(include_str!("../../ui/window.ui")); - let window: ApplicationWindow = builder - .object("window") - .expect("Could not get object 'window' from builder."); - - let menubuilder = gtk::Builder::from_string(include_str!("../../ui/menu.ui")); - let menubar: MenuModel = menubuilder - .object("menu") - .expect("Could not get object 'menu' from builder."); - - self.setup_menubar(app, &window, &builder.clone()); - - window.set_application(Some(app)); - - window.present(); - - } - -} - -impl GtkApplicationImpl for EchidnaEditor {} - diff --git a/src/components/app/imp.rs b/src/components/app/imp.rs new file mode 100644 index 0000000..9dd1c98 --- /dev/null +++ b/src/components/app/imp.rs @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use super::super::window::EchidnaWindow; +use super::super::window::menubar::MenubarImplementedEditor; + +use gtk::subclass::prelude::*; +use gtk::prelude::*; + +use gtk::{ + Application, +}; + +#[derive(Debug, Default)] +pub struct EchidnaEditor { + pub name: &'static str, + pub app_id: &'static str +} + + + + +#[glib::object_subclass] +impl ObjectSubclass for EchidnaEditor { + const NAME: &'static str = "EchidnaEditorApplication"; + type Type = super::EchidnaEditor; + type ParentType = Application; + + fn new() -> Self { + Self { + name: "Echidna Code Editor", + app_id: "land.echidna.editor" + } + } +} + + +impl ObjectImpl for EchidnaEditor { + + + +} + +impl ApplicationImpl for EchidnaEditor { + + fn activate(&self, app: &Self::Type){ + + let window = EchidnaWindow::new(app); + + window.setup_menubar(); + window.set_application(Some(app)); + + window.present(); + + } + +} + +impl GtkApplicationImpl for EchidnaEditor {} + diff --git a/src/app/mod.rs b/src/components/app/mod.rs similarity index 96% rename from src/app/mod.rs rename to src/components/app/mod.rs index b98bcfb..8b3c9c1 100644 --- a/src/app/mod.rs +++ b/src/components/app/mod.rs @@ -3,12 +3,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ pub mod imp; -pub mod menubar; -pub mod workspace; + use glib::wrapper; wrapper! { pub struct EchidnaEditor(ObjectSubclass) @extends gio::Application, gtk::Application, @implements gio::ActionGroup, gio::ActionMap; + } impl Default for EchidnaEditor { diff --git a/src/components/mod.rs b/src/components/mod.rs index 7a10ed2..24e9074 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -3,3 +3,4 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ pub mod app; +pub mod window; diff --git a/src/components/window/file.rs b/src/components/window/file.rs new file mode 100644 index 0000000..b043344 --- /dev/null +++ b/src/components/window/file.rs @@ -0,0 +1,134 @@ + +use glib::{ + clone +}; +use sourceview::{ + View, + Buffer, + LanguageManager, + prelude::* +}; +use gio::{ + Cancellable + +}; +use gtk::{ + FileChooserDialog, + FileChooserAction, + ResponseType, + Label, + prelude::*, + subclass::prelude::* +}; + +pub trait FileImplementedEditor { + fn action_open_file/*>*/(window: Self); + fn open_file(notebook: >k::Notebook, file: gio::File); +} + +impl FileImplementedEditor for super::EchidnaWindow { + /* + Open a file and put it in an editor and the opened files bar. + + - Open a file chooser dialog. + - Connect a signal to it and get the file choosen. + - Read th file's information and + - TODO: if it's a plain text, + - TODO: Load the file's content + - TODO: Create an editor + - TODO: Set the editor's content to the file's content. + - TODO: Somehow keep track of what file belongs to what editor/opened file bar widget. + - TODO: If the user enables Autosave, listen to the editor's changes and automatically save the editor's content. + - TODO: Close the editor and the tab widget when the file is closed (triggered by the X button on them). + + Perhaps some of the last points should not be implemented in this function but rather in another function that keeps track of every files. + */ + fn action_open_file/*>*/(window: Self){ + + let dialog: FileChooserDialog = FileChooserDialog::new(Some("Open a file"), + Some(&window), + FileChooserAction::Open, + &[("Cancel", ResponseType::Cancel), + ("Open", ResponseType::Accept) ]); + + + dialog.set_visible(true); + + + + // TODO: Somehow inserts self to this function. + // This function sets the callback function as 'static, which for some reasons ban cloning self into it. Idk why. + dialog.connect_response(clone!( @weak window, => + move |dialog, response| { + + if response == ResponseType::Accept { + + let file_option = dialog.file(); + + match file_option { + Some(file) => { + dialog.destroy(); + + Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file); + + }, + None => { + + }, + } + } else if response == ResponseType::Cancel { + dialog.destroy(); + + } })); + + } + + fn open_file(notebook: >k::Notebook, file: gio::File){ + let cancellable = Cancellable::new(); + let filepath = file.path().expect("No filepath"); + let file_info_result = file.query_info( + "*", + gio::FileQueryInfoFlags::NONE, + Some(&cancellable)); + match file_info_result { + Ok(info) => { + match info.content_type() { + Some(content_type) => { + println!("Opened {} and found its content type is {}.", "file", content_type.to_string()); + let content_cancellable = Cancellable::new(); + let file_content = file.load_contents(Some(&content_cancellable)); + match file_content { + Ok(content) => { + let (int_vec, _text_option) = content; + let language_manager = LanguageManager::new(); + let language = language_manager.guess_language(Some(&info.name().to_str().expect("Could not open the file because its name is not supported by Unicode.")), None); + + let buffer = Buffer::new(None); + buffer.set_text(std::str::from_utf8(&int_vec).expect(format!("Could not parse the contents of {:?} as it's unsupported by UTF-8", filepath).as_str())); + match language { + Some(lang) => buffer.set_language(Some(&lang)), + None => {} + } + + let sourceview = View::with_buffer(&buffer); + + notebook.prepend_page(&sourceview, + Some(&Label::new(Some(&info.name().to_str() + .expect("Could not parse file's name"))))); + + } + Err(e) => println!("Could not open {:?} because:\n{:#?}", filepath, e), + } + }, + None => println!("It does not seem like {:?} has a type", filepath), + + } + + }, + Err(e) => println!("Could not retrieve file information for {:?} because:\n{}", filepath, e), + } + + + } + +} \ No newline at end of file diff --git a/src/components/window/imp.rs b/src/components/window/imp.rs new file mode 100644 index 0000000..38344b2 --- /dev/null +++ b/src/components/window/imp.rs @@ -0,0 +1,37 @@ + +use gtk::prelude::*; +use gtk::subclass::prelude::*; +use gtk::CompositeTemplate; + +#[derive(Debug, Default, CompositeTemplate)] +#[template(file = "./window.ui")] +pub struct EchidnaWindow { + #[template_child] + pub notebook: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for EchidnaWindow { + const NAME: &'static str = "EchidnaWindow"; + type Type = super::EchidnaWindow; + type ParentType = gtk::ApplicationWindow; + + + fn class_init(class: &mut Self::Class) { + Self::bind_template(class); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +impl ObjectImpl for EchidnaWindow {} + +impl WidgetImpl for EchidnaWindow {} + +impl WindowImpl for EchidnaWindow {} + +impl ApplicationWindowImpl for EchidnaWindow {} + +impl BuildableImpl for EchidnaWindow {} diff --git a/src/app/menubar.rs b/src/components/window/menubar.rs similarity index 75% rename from src/app/menubar.rs rename to src/components/window/menubar.rs index e8da0fb..7cb1924 100644 --- a/src/app/menubar.rs +++ b/src/components/window/menubar.rs @@ -1,32 +1,25 @@ -use super::imp::EchidnaEditor; -use super::imp::EchidnaEditorExt; +use super::file::FileImplementedEditor; +use super::EchidnaWindow; use gio::{MenuModel, SimpleAction}; use glib::clone; use gtk::prelude::*; use gtk::AboutDialog; pub trait MenubarImplementedEditor { - fn setup_menubar( - &self, - app: &super::EchidnaEditor, - window: >k::ApplicationWindow, - builder: >k::Builder, - ); + fn setup_menubar(&self); } -impl MenubarImplementedEditor for EchidnaEditor { - fn setup_menubar( - &self, - app: &super::EchidnaEditor, - window: >k::ApplicationWindow, - builder: >k::Builder, - ) { - let menubuilder = gtk::Builder::from_string(include_str!("../../ui/menu.ui")); +impl MenubarImplementedEditor for EchidnaWindow { + fn setup_menubar(&self) { + let app = &self + .application() + .expect("&self does not have an application set."); + let menubuilder = gtk::Builder::from_string(include_str!("../../../ui/menu.ui")); let menubar: MenuModel = menubuilder .object("menu") .expect("Could not get object 'menu' from builder."); app.set_menubar(Some(&menubar)); - window.set_show_menubar(true); + &self.set_show_menubar(true); let act_exit: SimpleAction = SimpleAction::new("exit", None); app.add_action(&act_exit); @@ -49,9 +42,6 @@ impl MenubarImplementedEditor for EchidnaEditor { about_dialog.set_visible(true); }); - let notebook: gtk::Notebook = builder - .object("echidna-notebook") - .expect("Could not get 'echidna-notebook' from builder."); //app.notebook = Some(Rc::new(RefCell::new(notebook))); let act_exit: SimpleAction = SimpleAction::new("exit", None); app.add_action(&act_exit); @@ -92,20 +82,22 @@ impl MenubarImplementedEditor for EchidnaEditor { let act_window_close = SimpleAction::new("close", None); - window.add_action(&act_window_close); + &self.add_action(&act_window_close); + + { + let window = self.clone(); - act_window_close.connect_activate(clone!(@weak window => - move | _action, _variant | { + act_window_close.connect_activate(move |_action, _variant| { window.close(); - } - )); + }); + } let action_open_file: SimpleAction = SimpleAction::new("open-file", None); - window.add_action(&action_open_file); - action_open_file.connect_activate(clone!(@weak window, @weak app, @weak notebook => + &self.add_action(&action_open_file); + action_open_file.connect_activate(clone!(@weak self as window => move |action, variant| { - Self::action_open_file(window, app, action, variant, notebook); + Self::action_open_file(window); })); } } diff --git a/src/components/window/mod.rs b/src/components/window/mod.rs new file mode 100644 index 0000000..bff0d6f --- /dev/null +++ b/src/components/window/mod.rs @@ -0,0 +1,36 @@ +mod file; + +mod imp; +pub mod menubar; + +use glib::{ + + + object::IsA, + +}; + +glib::wrapper! { + pub struct EchidnaWindow(ObjectSubclass) + @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, + @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; +} + +impl EchidnaWindow { + pub fn new>(application: &P) -> Self { + + let object = glib::Object::new(&[ + ("application", &application) + ]); + + match object { + Ok(o) => o, + Err(e) => panic!("Error in making EchidnaApplication {}", e), + } + } + + + + +} + diff --git a/src/components/window/sidebar.rs b/src/components/window/sidebar.rs new file mode 100644 index 0000000..4014aed --- /dev/null +++ b/src/components/window/sidebar.rs @@ -0,0 +1,9 @@ +trait SidebarImplementedEditor { + fn setup_sidebar(); +} + +impl SidebarImplementedEditor for super::imp::EchidnaEditor { + fn setup_sidebar(){ + + } +} \ No newline at end of file diff --git a/src/components/window/window.ui b/src/components/window/window.ui new file mode 100644 index 0000000..f8ec6b6 --- /dev/null +++ b/src/components/window/window.ui @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/src/app/workspace.rs b/src/components/window/workspace.rs similarity index 100% rename from src/app/workspace.rs rename to src/components/window/workspace.rs diff --git a/src/main.rs b/src/main.rs index 5bb4936..01d237c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -mod app; +mod components; -use gtk::prelude::ApplicationExtManual; use app::EchidnaEditor; +use components::app; +use gtk::prelude::ApplicationExtManual; fn main() { - let app = EchidnaEditor::new( - "land.echidna.editor", - ); + let app = EchidnaEditor::new("land.echidna.editor"); std::process::exit(app.run()); -} \ No newline at end of file +}