From af347c89621e6dc01ce316478ad2e1d804bcd80a Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Wed, 3 Nov 2021 17:06:51 +0700 Subject: [PATCH 1/7] feat: make tabs closeable Not yet implemented for the placeholder Getting Started page. --- src/components/window/file.rs | 4 +- src/lib/closeable_tab.rs | 55 +++++++++++ src/lib/mod.rs | 5 + src/lib/workspace.rs | 175 ++++++++++++++++++++++++++++++++++ src/main.rs | 2 +- 5 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/lib/closeable_tab.rs create mode 100644 src/lib/mod.rs create mode 100644 src/lib/workspace.rs diff --git a/src/components/window/file.rs b/src/components/window/file.rs index e5a0ada..3a51ce0 100644 --- a/src/components/window/file.rs +++ b/src/components/window/file.rs @@ -2,6 +2,8 @@ * 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 crate::lib::prelude::*; + use crate::components::editor::EchidnaCoreEditor; use gio::Cancellable; use glib::{clone, Priority}; @@ -64,7 +66,7 @@ impl FileImplementedEditor for super::EchidnaWindow { fn open_file(notebook: >k::Notebook, file_location: gio::File) { let file = File::builder().location(&file_location).build(); let editor_page = EchidnaCoreEditor::new(Some(file)); - notebook.prepend_page( + notebook.prepend_closable_page( &editor_page, Some(&Label::new(Some( &file_location diff --git a/src/lib/closeable_tab.rs b/src/lib/closeable_tab.rs new file mode 100644 index 0000000..43b1b0b --- /dev/null +++ b/src/lib/closeable_tab.rs @@ -0,0 +1,55 @@ +use glib::IsA; +use gtk::{prelude::*, Box, Widget, Button}; + +pub trait ClosableTabImplementedNotebook { + fn prepend_closable_page< + T: IsA, + U: IsA + >( + &self, + child: &T, + tab_label: Option<&U>, + ) -> u32; + fn create_closable_tab< + U: IsA, + + >(tab_label: Option<&U>) -> (Box, Button); +} + +impl ClosableTabImplementedNotebook for gtk::Notebook { + fn create_closable_tab< + U: IsA, + +>(tab_label: Option<&U>)-> (Box, Button) { + let tab = Box::new(gtk::Orientation::Horizontal, 5); + if tab_label.is_some() { + tab.append(tab_label.unwrap()); + } + + let button = gtk::Button::new(); + + button.set_icon_name("window-close-symbolic"); + button.set_has_frame(false); + + tab.append(&button); + + (tab, button) + } + + fn prepend_closable_page, U: IsA>( + &self, + child: &T, + tab_label: Option<&U>, + ) -> u32 { + let (tab, button) = &Self::create_closable_tab(tab_label); + let page = self.append_page(child, Some(tab)); + + button.connect_clicked(glib::clone!(@weak self as notebook => + move |_| { + notebook.remove_page(Some(page)); + })); + + page + + } +} diff --git a/src/lib/mod.rs b/src/lib/mod.rs new file mode 100644 index 0000000..39d654d --- /dev/null +++ b/src/lib/mod.rs @@ -0,0 +1,5 @@ +pub mod closeable_tab; + +pub mod prelude { + pub use super::closeable_tab::ClosableTabImplementedNotebook; +} \ No newline at end of file diff --git a/src/lib/workspace.rs b/src/lib/workspace.rs new file mode 100644 index 0000000..9cc5c6c --- /dev/null +++ b/src/lib/workspace.rs @@ -0,0 +1,175 @@ +/* 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::imp::EchidnaEditor; +use gio::Cancellable; +use gio::{File, FileQueryInfoFlags, FileType, SimpleAction}; +use glib::clone; +use glib::subclass::types::ObjectSubclassExt; +use glib::types::Type; +use gtk::prelude::*; +use gtk::{ApplicationWindow, FileChooserAction, FileChooserDialog, ResponseType, TreeStore}; +use relative_path::RelativePath; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Deserialize, Serialize)] +struct MonacoFolder { + path: String, +} + +#[derive(Deserialize, Serialize)] +struct MonacoWorkspace { + folders: Vec, +} + +trait WorkspaceImplementedEditor { + fn action_open_workspace( + &self, + window: ApplicationWindow, + app: super::EchidnaEditor, + _action: &SimpleAction, + _variant: Option<&glib::Variant>, + ); + + fn open_workspace(&self, file: File); + fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore); + fn open_folder(&self, file: File); +} + +impl WorkspaceImplementedEditor for EchidnaEditor { + fn action_open_workspace( + &self, + window: ApplicationWindow, + app: super::EchidnaEditor, + _action: &SimpleAction, + _variant: Option<&glib::Variant>, + ) { + let dialog: FileChooserDialog = FileChooserDialog::new( + Some("Open a file"), + Some(&window), + FileChooserAction::Open, + &[ + ("Cancel", ResponseType::Cancel), + ("Open", ResponseType::Accept), + ], + ); + dialog.set_visible(true); + dialog.connect_response(clone!(@weak window, @weak app => + move |dialog, response| { + if response == ResponseType::Accept { + let file_option = dialog.file(); + match file_option { + Some(file) => { + dialog.destroy(); + Self::from_instance(&app).open_workspace(file); + }, + None => {} + } + } else if response == ResponseType::Cancel { + dialog.destroy(); + } + } + )); + } + /** + * __Open Workspace__ + * + * Basically, this is just the same as Open Folder, but it's many folders. + * + * - Open a FileChooserDialog, set to only view .code-workspace files. + * - If the user pressed cancel, destroy the dialog. If the user opened a .code-workspace file: + * - Get the workspace file, load and parse its content, .code-workspace files are in JSON with comments. But JSON only should be fine, for now. + * - Iterate over folders listed in the workspace file. + * - Find the absolute path of the folder: Relative to the workspace file. + * - Create a GFile instance of that path, and call open_folder(file: File) and pass the GFile instance to it. + * + */ + fn open_workspace(&self, file: File) { + let cancellable = Cancellable::new(); + let filepath_raw = &file + .path() + .expect("Could not get the file path of the file."); + let filepath = Path::new(&filepath_raw); + let info = file + .query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable)) + .expect(format!( + "Could not retrieve file information for {:?}", + filepath + )); + let content_type = info + .content_type() + .expect(format!("Found no content type for {:?}", filepath)); + println!( + "Opened {} and found its content type is {}.", + "file", + content_type.to_string() + ); + let content_cancellable = Cancellable::new(); + let content = file + .load_contents(Some(&content_cancellable)) + .expect("Could not load the file contents for {:?}", filepath); + + let (int_vec, _byte_string) = content; + let workspace = serde_json::from_slice::(&int_vec).expect(format!( + "Could not parse the workspace file of {:?}", + filepath + )); + + for folder in workspace.folders { + let path = RelativePath::new(&folder.path); + let folder = File::for_path(path.to_path(filepath)); + + // Do something with the folder, perhaps lists its child and . + self.open_folder(folder); + } + } +} + +/** + * + * + */ +fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore) { + let child_enumerate_cancellable = Cancellable::new(); + let child_files = parent_file + .enumerate_children( + "*", + FileQueryInfoFlags::NONE, + Some(&child_enumerate_cancellable), + ) + .expect( + format!( + "Could not look up the children files of {:?} because:\n{:#?}", + filepath + ) + .as_str(), + ); + let filepath = &parent_file + .path() + .expect("Could not get the file path of the file."); + + for file_iter in files { + let file_info = file_iter.expect(); + let file = parent_file.child(file_info.name()); + let tree_iter = tree.append(None); + tree.set_value(&tree_iter, 2, &file_info.name().to_str().to_value()); + + if file_info.file_type() == FileType::Directory { + self.recursive_add_files_into_tree_store(file, tree); + } + } + } + + /* + Loads a folder into the tree view. + + - Create a new tree + - Enumerate over child files of 'file'. (PS: In the Unix family of OS-es, directories are files too) + */ + fn open_folder(&self, file: File) { + let tree = TreeStore::new(&[gdk::Texture::static_type(), Type::STRING]); + self.recursive_add_files_into_tree_store(file, &tree); + } +} diff --git a/src/main.rs b/src/main.rs index 01d237c..7e257d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ mod components; - +pub mod lib; use app::EchidnaEditor; use components::app; use gtk::prelude::ApplicationExtManual; From 8eb2e9c66a5b0bb575eaf4192993d3f1af9d172d Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Wed, 3 Nov 2021 18:53:51 +0700 Subject: [PATCH 2/7] refactor: move back workspace.rs to src/components/window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I accidentally commited its move to src/lib. 🤦 --- src/lib/workspace.rs | 175 ------------------------------------------- 1 file changed, 175 deletions(-) delete mode 100644 src/lib/workspace.rs diff --git a/src/lib/workspace.rs b/src/lib/workspace.rs deleted file mode 100644 index 9cc5c6c..0000000 --- a/src/lib/workspace.rs +++ /dev/null @@ -1,175 +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 super::imp::EchidnaEditor; -use gio::Cancellable; -use gio::{File, FileQueryInfoFlags, FileType, SimpleAction}; -use glib::clone; -use glib::subclass::types::ObjectSubclassExt; -use glib::types::Type; -use gtk::prelude::*; -use gtk::{ApplicationWindow, FileChooserAction, FileChooserDialog, ResponseType, TreeStore}; -use relative_path::RelativePath; -use serde::{Deserialize, Serialize}; -use std::path::Path; - -#[derive(Deserialize, Serialize)] -struct MonacoFolder { - path: String, -} - -#[derive(Deserialize, Serialize)] -struct MonacoWorkspace { - folders: Vec, -} - -trait WorkspaceImplementedEditor { - fn action_open_workspace( - &self, - window: ApplicationWindow, - app: super::EchidnaEditor, - _action: &SimpleAction, - _variant: Option<&glib::Variant>, - ); - - fn open_workspace(&self, file: File); - fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore); - fn open_folder(&self, file: File); -} - -impl WorkspaceImplementedEditor for EchidnaEditor { - fn action_open_workspace( - &self, - window: ApplicationWindow, - app: super::EchidnaEditor, - _action: &SimpleAction, - _variant: Option<&glib::Variant>, - ) { - let dialog: FileChooserDialog = FileChooserDialog::new( - Some("Open a file"), - Some(&window), - FileChooserAction::Open, - &[ - ("Cancel", ResponseType::Cancel), - ("Open", ResponseType::Accept), - ], - ); - dialog.set_visible(true); - dialog.connect_response(clone!(@weak window, @weak app => - move |dialog, response| { - if response == ResponseType::Accept { - let file_option = dialog.file(); - match file_option { - Some(file) => { - dialog.destroy(); - Self::from_instance(&app).open_workspace(file); - }, - None => {} - } - } else if response == ResponseType::Cancel { - dialog.destroy(); - } - } - )); - } - /** - * __Open Workspace__ - * - * Basically, this is just the same as Open Folder, but it's many folders. - * - * - Open a FileChooserDialog, set to only view .code-workspace files. - * - If the user pressed cancel, destroy the dialog. If the user opened a .code-workspace file: - * - Get the workspace file, load and parse its content, .code-workspace files are in JSON with comments. But JSON only should be fine, for now. - * - Iterate over folders listed in the workspace file. - * - Find the absolute path of the folder: Relative to the workspace file. - * - Create a GFile instance of that path, and call open_folder(file: File) and pass the GFile instance to it. - * - */ - fn open_workspace(&self, file: File) { - let cancellable = Cancellable::new(); - let filepath_raw = &file - .path() - .expect("Could not get the file path of the file."); - let filepath = Path::new(&filepath_raw); - let info = file - .query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable)) - .expect(format!( - "Could not retrieve file information for {:?}", - filepath - )); - let content_type = info - .content_type() - .expect(format!("Found no content type for {:?}", filepath)); - println!( - "Opened {} and found its content type is {}.", - "file", - content_type.to_string() - ); - let content_cancellable = Cancellable::new(); - let content = file - .load_contents(Some(&content_cancellable)) - .expect("Could not load the file contents for {:?}", filepath); - - let (int_vec, _byte_string) = content; - let workspace = serde_json::from_slice::(&int_vec).expect(format!( - "Could not parse the workspace file of {:?}", - filepath - )); - - for folder in workspace.folders { - let path = RelativePath::new(&folder.path); - let folder = File::for_path(path.to_path(filepath)); - - // Do something with the folder, perhaps lists its child and . - self.open_folder(folder); - } - } -} - -/** - * - * - */ -fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore) { - let child_enumerate_cancellable = Cancellable::new(); - let child_files = parent_file - .enumerate_children( - "*", - FileQueryInfoFlags::NONE, - Some(&child_enumerate_cancellable), - ) - .expect( - format!( - "Could not look up the children files of {:?} because:\n{:#?}", - filepath - ) - .as_str(), - ); - let filepath = &parent_file - .path() - .expect("Could not get the file path of the file."); - - for file_iter in files { - let file_info = file_iter.expect(); - let file = parent_file.child(file_info.name()); - let tree_iter = tree.append(None); - tree.set_value(&tree_iter, 2, &file_info.name().to_str().to_value()); - - if file_info.file_type() == FileType::Directory { - self.recursive_add_files_into_tree_store(file, tree); - } - } - } - - /* - Loads a folder into the tree view. - - - Create a new tree - - Enumerate over child files of 'file'. (PS: In the Unix family of OS-es, directories are files too) - */ - fn open_folder(&self, file: File) { - let tree = TreeStore::new(&[gdk::Texture::static_type(), Type::STRING]); - self.recursive_add_files_into_tree_store(file, &tree); - } -} From d54f470a76727584660ae0c79d48a2ef4570f7da Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Thu, 4 Nov 2021 17:06:19 +0700 Subject: [PATCH 3/7] refactor: remove placeholder Getting Started page Will be replaced in a future MR. --- src/components/window/window.ui | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/window/window.ui b/src/components/window/window.ui index 41b64b2..41f8876 100644 --- a/src/components/window/window.ui +++ b/src/components/window/window.ui @@ -27,16 +27,6 @@ --> 1 - - - Hello from Echidna! - - - - - Welcome - - From 2ec23a35d5ede0e12dcdcce3cdf9f95f1e099854 Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Sat, 6 Nov 2021 17:00:11 +0700 Subject: [PATCH 4/7] fix: prepend_closable_tab appending tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It should prepend the tabs instead of appending it. 🤦 --- src/lib/closeable_tab.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/closeable_tab.rs b/src/lib/closeable_tab.rs index 43b1b0b..98b2535 100644 --- a/src/lib/closeable_tab.rs +++ b/src/lib/closeable_tab.rs @@ -42,7 +42,7 @@ impl ClosableTabImplementedNotebook for gtk::Notebook { tab_label: Option<&U>, ) -> u32 { let (tab, button) = &Self::create_closable_tab(tab_label); - let page = self.append_page(child, Some(tab)); + let page = self.prepend_page(child, Some(tab)); button.connect_clicked(glib::clone!(@weak self as notebook => move |_| { From 2b2e63d809b9315d0763b94e066ef2b7e74d5d15 Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Sat, 6 Nov 2021 17:01:39 +0700 Subject: [PATCH 5/7] feat: ClosableTabImplNotebook::append_closable_tab() --- src/lib/closeable_tab.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/lib/closeable_tab.rs b/src/lib/closeable_tab.rs index 98b2535..10099bd 100644 --- a/src/lib/closeable_tab.rs +++ b/src/lib/closeable_tab.rs @@ -10,6 +10,15 @@ pub trait ClosableTabImplementedNotebook { child: &T, tab_label: Option<&U>, ) -> u32; + fn append_closable_page< + T: IsA, + U: IsA + >( + &self, + child: &T, + tab_label: Option<&U>, + ) -> u32; + fn create_closable_tab< U: IsA, @@ -52,4 +61,23 @@ impl ClosableTabImplementedNotebook for gtk::Notebook { page } + + fn append_closable_page< + T: IsA, + U: IsA + >( + &self, + child: &T, + tab_label: Option<&U>, + ) -> u32 { + let (tab, button) = &Self::create_closable_tab(tab_label); + let page = self.append_page(child, Some(tab)); + + button.connect_clicked(glib::clone!(@weak self as notebook => + move |_| { + notebook.remove_page(Some(page)); + })); + + page + } } From 3a4792cbfe2673d8e0933e6834fdff60bf3a31fc Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Mon, 8 Nov 2021 17:43:08 +0700 Subject: [PATCH 6/7] style: add license headers to source files --- src/components/sidebar/mod.rs | 4 ++++ src/lib/closeable_tab.rs | 4 ++++ src/lib/mod.rs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/components/sidebar/mod.rs b/src/components/sidebar/mod.rs index 25b3ef2..7aa464a 100644 --- a/src/components/sidebar/mod.rs +++ b/src/components/sidebar/mod.rs @@ -1,3 +1,7 @@ +/* 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/. */ + pub mod imp; glib::wrapper! { diff --git a/src/lib/closeable_tab.rs b/src/lib/closeable_tab.rs index 10099bd..ee522de 100644 --- a/src/lib/closeable_tab.rs +++ b/src/lib/closeable_tab.rs @@ -1,3 +1,7 @@ +/* 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 glib::IsA; use gtk::{prelude::*, Box, Widget, Button}; diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 39d654d..dcd49ea 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -1,3 +1,7 @@ +/* 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/. */ + pub mod closeable_tab; pub mod prelude { From 02e437b76cabbfb0aeea2f26d021e375aa14752d Mon Sep 17 00:00:00 2001 From: Nefomemes Date: Tue, 9 Nov 2021 05:18:23 +0700 Subject: [PATCH 7/7] style: format source files with cargo fmt --- src/lib/closeable_tab.rs | 34 +++++++++------------------------- src/lib/mod.rs | 2 +- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/src/lib/closeable_tab.rs b/src/lib/closeable_tab.rs index ee522de..85900df 100644 --- a/src/lib/closeable_tab.rs +++ b/src/lib/closeable_tab.rs @@ -3,44 +3,32 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use glib::IsA; -use gtk::{prelude::*, Box, Widget, Button}; +use gtk::{prelude::*, Box, Button, Widget}; pub trait ClosableTabImplementedNotebook { - fn prepend_closable_page< - T: IsA, - U: IsA - >( + fn prepend_closable_page, U: IsA>( &self, child: &T, tab_label: Option<&U>, ) -> u32; - fn append_closable_page< - T: IsA, - U: IsA - >( + fn append_closable_page, U: IsA>( &self, child: &T, tab_label: Option<&U>, ) -> u32; - fn create_closable_tab< - U: IsA, - - >(tab_label: Option<&U>) -> (Box, Button); + fn create_closable_tab>(tab_label: Option<&U>) -> (Box, Button); } impl ClosableTabImplementedNotebook for gtk::Notebook { - fn create_closable_tab< - U: IsA, - ->(tab_label: Option<&U>)-> (Box, Button) { + fn create_closable_tab>(tab_label: Option<&U>) -> (Box, Button) { let tab = Box::new(gtk::Orientation::Horizontal, 5); if tab_label.is_some() { tab.append(tab_label.unwrap()); } let button = gtk::Button::new(); - + button.set_icon_name("window-close-symbolic"); button.set_has_frame(false); @@ -57,19 +45,15 @@ impl ClosableTabImplementedNotebook for gtk::Notebook { let (tab, button) = &Self::create_closable_tab(tab_label); let page = self.prepend_page(child, Some(tab)); - button.connect_clicked(glib::clone!(@weak self as notebook => + button.connect_clicked(glib::clone!(@weak self as notebook => move |_| { notebook.remove_page(Some(page)); })); page - } - fn append_closable_page< - T: IsA, - U: IsA - >( + fn append_closable_page, U: IsA>( &self, child: &T, tab_label: Option<&U>, @@ -77,7 +61,7 @@ impl ClosableTabImplementedNotebook for gtk::Notebook { let (tab, button) = &Self::create_closable_tab(tab_label); let page = self.append_page(child, Some(tab)); - button.connect_clicked(glib::clone!(@weak self as notebook => + button.connect_clicked(glib::clone!(@weak self as notebook => move |_| { notebook.remove_page(Some(page)); })); diff --git a/src/lib/mod.rs b/src/lib/mod.rs index dcd49ea..233c324 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -6,4 +6,4 @@ pub mod closeable_tab; pub mod prelude { pub use super::closeable_tab::ClosableTabImplementedNotebook; -} \ No newline at end of file +}