From 17f000702849bea027d68b284852a5f9fa9469aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20Rodr=C3=ADguez?= Date: Fri, 14 Jun 2024 20:27:53 +0200 Subject: [PATCH 1/9] Add collections --- data/cartero.gresource.xml | 4 + data/es.danirod.Cartero.gschema.xml | 8 + data/meson.build | 4 + data/ui/collection_pane.blp | 56 +++++++ data/ui/main_window.blp | 86 ++++------ data/ui/new_collection_window.blp | 121 ++++++++++++++ data/ui/sidebar.blp | 43 +++++ data/ui/sidebar_row.blp | 45 ++++++ src/app.rs | 7 +- src/file.rs | 4 + src/fs/collection.rs | 42 +++++ src/fs/metadata.rs | 98 +++++++++++ src/fs/mod.rs | 19 +++ src/main.rs | 1 + src/objects/collection.rs | 142 ++++++++++++++++ src/objects/mod.rs | 5 + src/objects/tree_node.rs | 88 ++++++++++ src/widgets/collection_pane.rs | 135 ++++++++++++++++ src/widgets/file_dialogs.rs | 62 ------- src/widgets/mod.rs | 9 +- src/widgets/new_collection_window.rs | 205 +++++++++++++++++++++++ src/widgets/sidebar.rs | 189 ++++++++++++++++++++++ src/widgets/sidebar_row.rs | 127 +++++++++++++++ src/win.rs | 233 +++++++++++++++++---------- 24 files changed, 1523 insertions(+), 210 deletions(-) create mode 100644 data/ui/collection_pane.blp create mode 100644 data/ui/new_collection_window.blp create mode 100644 data/ui/sidebar.blp create mode 100644 data/ui/sidebar_row.blp create mode 100644 src/fs/collection.rs create mode 100644 src/fs/metadata.rs create mode 100644 src/fs/mod.rs create mode 100644 src/objects/collection.rs create mode 100644 src/objects/tree_node.rs create mode 100644 src/widgets/collection_pane.rs delete mode 100644 src/widgets/file_dialogs.rs create mode 100644 src/widgets/new_collection_window.rs create mode 100644 src/widgets/sidebar.rs create mode 100644 src/widgets/sidebar_row.rs diff --git a/data/cartero.gresource.xml b/data/cartero.gresource.xml index 63b5c85b..e304f324 100644 --- a/data/cartero.gresource.xml +++ b/data/cartero.gresource.xml @@ -1,9 +1,13 @@ + ui/collection_pane.ui + ui/sidebar.ui + ui/sidebar_row.ui ui/endpoint_pane.ui ui/key_value_row.ui ui/key_value_pane.ui + ui/new_collection_window.ui ui/main_window.ui ui/response_headers.ui ui/response_panel.ui diff --git a/data/es.danirod.Cartero.gschema.xml b/data/es.danirod.Cartero.gschema.xml index e9322185..57183c19 100644 --- a/data/es.danirod.Cartero.gschema.xml +++ b/data/es.danirod.Cartero.gschema.xml @@ -34,5 +34,13 @@ 500 The position of the split between two windows + + [] + The collections that will be visible in the sidebar + + + nothing + The default directory to present when creating a new collection + diff --git a/data/meson.build b/data/meson.build index 4aa5ddd9..28e260fb 100644 --- a/data/meson.build +++ b/data/meson.build @@ -18,12 +18,16 @@ subdir('icons') blueprint_files = [ + 'ui/collection_pane.blp', 'ui/endpoint_pane.blp', 'ui/main_window.blp', 'ui/key_value_pane.blp', 'ui/key_value_row.blp', 'ui/response_headers.blp', 'ui/response_panel.blp', + 'ui/new_collection_window.blp', + 'ui/sidebar.blp', + 'ui/sidebar_row.blp', ] blueprint_targets = [] diff --git a/data/ui/collection_pane.blp b/data/ui/collection_pane.blp new file mode 100644 index 00000000..9935721c --- /dev/null +++ b/data/ui/collection_pane.blp @@ -0,0 +1,56 @@ +/* + * Copyright 2024 the Cartero authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +// SPDX-License-Identifier: GPL-3.0-or-later +using Gtk 4.0; + +template $CarteroCollectionPane: Gtk.Box { + orientation: vertical; + + Box { + orientation: horizontal; + spacing: 10; + + Entry collection_name { + placeholder-text: _("Collection name"); + hexpand: true; + } + + Button { + label: _("Save"); + clicked => $on_save() swapped; + + styles [ + 'suggested-action' + ] + } + } + + Notebook { + NotebookPage { + tab: Label { + label: _("Variables"); + }; + + child: ScrolledWindow { + hexpand: true; + vexpand: true; + + $CarteroKeyValuePane variables {} + }; + } + } +} diff --git a/data/ui/main_window.blp b/data/ui/main_window.blp index 4c1e0c9a..93d8bbe3 100644 --- a/data/ui/main_window.blp +++ b/data/ui/main_window.blp @@ -22,70 +22,48 @@ template $CarteroWindow: Adw.ApplicationWindow { title: "Cartero"; [content] - Box { - orientation: vertical; - - Adw.HeaderBar { - title-widget: Adw.WindowTitle window_title { - title: 'Cartero'; - }; - - [start] - Box { - spacing: 5; - + Adw.OverlaySplitView { + sidebar: Adw.ToolbarView { + [top] + Adw.HeaderBar { + title-widget: Adw.WindowTitle window_title { + title: 'Cartero'; + }; + + [start] Button { - action-name: "win.new"; - icon-name: 'tab-new-symbolic'; - tooltip-text: _("New"); + action-name: "win.new-collection"; + icon-name: "tab-new-symbolic"; + tooltip-text: _("New collection"); } - Separator {} - - Button open_dialog { - styles [ - "flat" - ] - - action-name: "win.open"; - tooltip-text: _("Open"); - - [child] - Box { - spacing: 5; + [end] + MenuButton { + icon-name: "open-menu-symbolic"; + primary: true; + menu-model: main_menu; + } + } - Image { - icon-name: 'document-open-symbolic'; - } + $CarteroSidebar collections {} + }; - Label { - label: _("Open"); - } - } - } + content: Gtk.Box { + orientation: vertical; - Button save_dialog { - action-name: "win.save"; - icon-name: 'document-save-symbolic'; - tooltip-text: _("Save"); - } + Adw.HeaderBar { + [title] + Label main_label {} } - [end] - MenuButton { - icon-name: "open-menu-symbolic"; - primary: true; - menu-model: main_menu; + Adw.TabBar tabs { + view: tabview; } - } - Adw.TabBar tabs { - view: tabview; - } - - Adw.ToastOverlay toaster { - Adw.TabView tabview {} - } + Adw.ToastOverlay toaster { + Adw.TabView tabview {} + } + }; } } diff --git a/data/ui/new_collection_window.blp b/data/ui/new_collection_window.blp new file mode 100644 index 00000000..6f1c6f0f --- /dev/null +++ b/data/ui/new_collection_window.blp @@ -0,0 +1,121 @@ +/* + * Copyright 2024 the Cartero authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +// SPDX-License-Identifier: GPL-3.0-or-later +using Gtk 4.0; +using Adw 1; + +template $CarteroNewCollectionWindow: Adw.Dialog { + width-request: 640; + title: _("Create Collection"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar { } + + Adw.Clamp { + margin-top: 30; + margin-bottom: 30; + maximum-size: 500; + + Box { + orientation: vertical; + spacing: 20; + + ListBox { + selection-mode: none; + + styles [ + "boxed-list", + ] + + Adw.EntryRow collection_location { + title: _("Location"); + changed => $on_collection_location_changed() swapped; + + [suffix] + Button { + styles [ + "flat" + ] + + valign: center; + icon-name: "folder-symbolic"; + + clicked => $on_location_clicked() swapped; + } + } + + Adw.EntryRow collection_name { + title: _("Collection name"); + + changed => $on_collection_name_changed() swapped; + + [suffix] + Image parent_directory_does_not_exist { + visible: false; + margin-start: 9; + margin-end: 9; + icon-name: "dialog-error-symbolic"; + tooltip-text: "The collection location does not exist"; + } + + [suffix] + Image file_taken { + visible: false; + margin-start: 9; + margin-end: 9; + icon-name: "dialog-error-symbolic"; + tooltip-text: "The given collection is not valid"; + } + } + } + + Label { + styles [ + "dim-label" + ] + + wrap: true; + label: _("A folder will be created in the given location with the requested name to store the requests and subfolders you add to this collection."); + } + + Box { + orientation: horizontal; + halign: end; + + Button create_button { + styles [ + "suggested-action" + ] + + label: _("Create"); + halign: end; + hexpand: false; + + clicked => $on_create_collection() swapped; + } + } + } + } + } +} + +FileDialog file_dialog { + accept-label: _("Select"); + title: _("Collection location"); + modal: true; +} diff --git a/data/ui/sidebar.blp b/data/ui/sidebar.blp new file mode 100644 index 00000000..eefb7736 --- /dev/null +++ b/data/ui/sidebar.blp @@ -0,0 +1,43 @@ +/* + * Copyright 2024 the Cartero authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +// SPDX-License-Identifier: GPL-3.0-or-later +using Gtk 4.0; +using Adw 1; + +template $CarteroSidebar: Adw.Bin { + Gtk.ListView list_view { + styles [ + 'navigation-sidebar' + ] + + model: selection_model; + + factory: Gtk.SignalListItemFactory { + setup => $on_factory_setup(); + bind => $on_factory_bind(); + unbind => $on_factory_unbind(); + teardown => $on_factory_teardown(); + }; + + activate => $on_activate(); + } +} + +Gtk.SingleSelection selection_model { + can-unselect: false; + autoselect: false; +} diff --git a/data/ui/sidebar_row.blp b/data/ui/sidebar_row.blp new file mode 100644 index 00000000..66ce270c --- /dev/null +++ b/data/ui/sidebar_row.blp @@ -0,0 +1,45 @@ +/* + * Copyright 2024 the Cartero authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +// SPDX-License-Identifier: GPL-3.0-or-later +using Gtk 4.0; +using Adw 1; + +template $CarteroSidebarRow: Adw.Bin { + Gtk.Inscription inscription { + hexpand: true; + text: bind template.title; + tooltip-text: bind template.path; + } + + Gtk.GestureClick { + button: 3; + released => $on_right_click() swapped; + } +} + +PopoverMenu context_menu { + menu-model: context_menu_model; + has-arrow: false; +} + +menu context_menu_model { + item { + label: _("Close collection"); + action: "row.close-collection"; + hidden-when: 'action-disabled'; + } +} diff --git a/src/app.rs b/src/app.rs index 7bf8628d..028aeb54 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,14 +15,14 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -use adw::prelude::*; use adw::AboutWindow; -use glib::subclass::types::ObjectSubclassIsExt; use glib::Object; use gtk::gio::{self, ActionEntryBuilder, Settings}; -use gtk::prelude::ActionMapExtManual; +use gtk::prelude::*; +use gtk::subclass::prelude::*; use crate::config::{self, APP_ID, BASE_ID}; + use crate::win::CarteroWindow; mod imp { @@ -30,6 +30,7 @@ mod imp { use adw::prelude::*; use adw::subclass::application::AdwApplicationImpl; + use glib::subclass::{object::ObjectImpl, types::ObjectSubclass}; use gtk::gio::Settings; use gtk::subclass::prelude::*; diff --git a/src/file.rs b/src/file.rs index ad52b85b..a00a09b6 100644 --- a/src/file.rs +++ b/src/file.rs @@ -64,6 +64,7 @@ impl From for RequestFile { } } +#[allow(dead_code)] pub fn parse_toml(file: &str) -> Result { let contents = toml::from_str::(file)?; let variables = contents.variables.clone().unwrap_or(HashMap::new()); @@ -71,15 +72,18 @@ pub fn parse_toml(file: &str) -> Result { Ok(Endpoint(request, variables)) } +#[allow(dead_code)] pub fn store_toml(endpoint: Endpoint) -> Result { let file = RequestFile::from(endpoint); toml::to_string(&file).map_err(|e| e.into()) } +#[allow(dead_code)] pub fn read_file(path: &PathBuf) -> std::io::Result { std::fs::read_to_string(path) } +#[allow(dead_code)] pub fn write_file(path: &PathBuf, contents: &str) -> std::io::Result<()> { let mut file = File::create(path)?; write!(file, "{}", contents) diff --git a/src/fs/collection.rs b/src/fs/collection.rs new file mode 100644 index 00000000..6c276eee --- /dev/null +++ b/src/fs/collection.rs @@ -0,0 +1,42 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::path::Path; +use std::{fs::File, io::Write}; + +use crate::{error::CarteroError, objects::Collection}; + +use super::metadata::Metadata; + +pub fn save_collection(path: &Path, collection: &Collection) -> Result<(), CarteroError> { + std::fs::create_dir(path)?; + + let metadata: Metadata = collection.into(); + let metadata_toml = toml::to_string(&metadata)?; + let metadata_file = path.join("cartero-metadata"); + + let mut file = File::create(metadata_file)?; + write!(file, "{}", metadata_toml)?; + Ok(()) +} + +pub fn open_collection(path: &Path) -> Result { + let metadata_file = path.join("cartero-metadata"); + let metadata_content = std::fs::read_to_string(metadata_file)?; + let metadata: Metadata = toml::from_str(&metadata_content)?; + Ok(metadata.into()) +} diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs new file mode 100644 index 00000000..df7aa03c --- /dev/null +++ b/src/fs/metadata.rs @@ -0,0 +1,98 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::collections::HashMap; + +use glib::Object; +use serde::{Deserialize, Serialize}; + +use crate::objects::{Collection, KeyValueItem}; + +#[derive(Serialize, Deserialize)] +struct Information { + title: String, + description: String, + version: String, +} + +#[derive(Serialize, Deserialize)] +struct Variable { + active: bool, + value: String, + secret: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct Metadata { + info: Information, + variables: Option>, +} + +impl From for Collection { + fn from(val: Metadata) -> Self { + let col: Collection = Object::builder() + .property("title", val.info.title) + .property("description", val.info.description) + .property("version", val.info.version) + .build(); + + if let Some(variables) = val.variables { + for (variable_name, variable_info) in variables { + let kv: KeyValueItem = Object::builder() + .property("header-name", variable_name) + .property("header-value", variable_info.value) + .property("active", variable_info.active) + .property("secret", variable_info.secret) + .build(); + col.add_variable(&kv); + } + } + + col + } +} + +impl From<&Collection> for Metadata { + fn from(val: &Collection) -> Self { + let title = val.title(); + let description = val.description(); + let version = val.version(); + + let variables = val + .variables_list() + .iter() + .map(|var| { + let variable_name = var.header_name(); + let variable = Variable { + active: var.active(), + value: var.header_value(), + secret: var.secret(), + }; + (variable_name.to_string(), variable) + }) + .collect(); + + Metadata { + info: Information { + title, + description, + version, + }, + variables: Some(variables), + } + } +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 00000000..9cf7e94b --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +pub mod collection; +pub mod metadata; diff --git a/src/main.rs b/src/main.rs index a1660d7c..076b7c37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ mod file; mod widgets; #[rustfmt::skip] mod config; +mod fs; mod objects; mod win; diff --git a/src/objects/collection.rs b/src/objects/collection.rs new file mode 100644 index 00000000..6f859f85 --- /dev/null +++ b/src/objects/collection.rs @@ -0,0 +1,142 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use glib::{object::Cast, types::StaticType, Object}; +use gtk::{gio::ListStore, prelude::ListModelExt}; + +use super::KeyValueItem; + +mod imp { + use std::cell::{OnceCell, RefCell}; + + use glib::Properties; + use gtk::gio::ListStore; + use gtk::glib::prelude::*; + use gtk::glib::subclass::prelude::*; + + #[derive(Default, Debug, Properties)] + #[properties(wrapper_type = super::Collection)] + pub struct Collection { + #[property(get, set, default = "")] + title: RefCell, + + #[property(get, set, default = "")] + description: RefCell, + + #[property(get, set, default = "")] + version: RefCell, + + #[property(get, set)] + pub(super) variables: OnceCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for Collection { + const NAME: &'static str = "CarteroCollection"; + type Type = super::Collection; + } + + #[glib::derived_properties] + impl ObjectImpl for Collection {} +} + +glib::wrapper! { + pub struct Collection(ObjectSubclass); +} + +impl Default for Collection { + fn default() -> Self { + Self::new() + } +} + +impl Collection { + pub fn new() -> Self { + let empty_collection = ListStore::with_type(KeyValueItem::static_type()); + Object::builder() + .property("variables", empty_collection) + .build() + } + + pub fn add_variable(&self, var: &KeyValueItem) { + self.variables().append(var); + } + + pub fn variable_count(&self) -> u32 { + self.variables().n_items() + } + + pub fn variable_get(&self, pos: u32) -> Option { + self.variables() + .item(pos) + .and_then(|obj| obj.downcast::().ok()) + } + + pub fn variable_del(&self, pos: u32) -> Option { + if let Some(obj) = self.variable_get(pos) { + self.variables().remove(pos); + Some(obj) + } else { + None + } + } + + pub fn variables_list(&self) -> Vec { + (0..self.variable_count()) + .map(|i| self.variable_get(i)) + .map(Option::unwrap) + .collect() + } +} + +#[cfg(test)] +mod tests { + use crate::objects::KeyValueItem; + + use super::Collection; + + #[test] + pub fn test_collections_can_have_variables() { + let collection = Collection::new(); + + let variable = { + let v = KeyValueItem::default(); + v.set_header_name("token"); + v.set_header_value("12341234"); + v.set_active(true); + v.set_secret(true); + v + }; + + assert_eq!(0, collection.variable_count()); + assert!(collection.variable_get(0).is_none()); + + collection.add_variable(&variable); + assert_eq!(1, collection.variable_count()); + + let variable = collection.variable_get(0).unwrap(); + assert_eq!("token", variable.header_name()); + assert_eq!("12341234", variable.header_value()); + assert!(variable.active()); + assert!(variable.secret()); + + let var = collection.variable_del(0); + assert!(var.is_some()); + assert_eq!(collection.variable_count(), 0); + assert!(collection.variable_get(0).is_none()); + } +} diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 7234d099..00a81bb7 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -15,8 +15,13 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +mod collection; mod endpoint; mod key_value_item; +mod tree_node; +pub use collection::Collection; pub use endpoint::Endpoint; pub use key_value_item::KeyValueItem; +pub use tree_node::TreeNode; +pub use tree_node::TreeNodeKind; diff --git a/src/objects/tree_node.rs b/src/objects/tree_node.rs new file mode 100644 index 00000000..c04bdcdd --- /dev/null +++ b/src/objects/tree_node.rs @@ -0,0 +1,88 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use glib::Object; +use std::path::PathBuf; + +mod imp { + use std::cell::RefCell; + + use glib::Properties; + use gtk::glib; + use gtk::glib::subclass::prelude::*; + use gtk::prelude::*; + + #[derive(Default, Properties)] + #[properties(wrapper_type = super::TreeNode)] + pub struct TreeNode { + #[property(get, set)] + path: RefCell, + + #[property(get, set)] + title: RefCell, + + #[property(get, set, builder(super::TreeNodeKind::default()))] + node_type: RefCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for TreeNode { + const NAME: &'static str = "CarteroTreeNode"; + type Type = super::TreeNode; + } + + #[glib::derived_properties] + impl ObjectImpl for TreeNode {} +} + +glib::wrapper! { + pub struct TreeNode(ObjectSubclass); +} + +impl Default for TreeNode { + fn default() -> Self { + Self::new() + } +} + +impl TreeNode { + pub fn new() -> Self { + Object::builder().build() + } + + pub fn pretty_name(&self) -> String { + let title = self.title(); + if title.is_empty() { + let path = PathBuf::from(self.path()); + match path.file_name() { + Some(name) => name.to_os_string().into_string().unwrap(), + None => self.path(), + } + } else { + title + } + } +} + +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, glib::Enum)] +#[enum_type(name = "CarteroTreeNodeKind")] +pub enum TreeNodeKind { + #[default] + Collection, + Folder, + Endpoint, +} diff --git a/src/widgets/collection_pane.rs b/src/widgets/collection_pane.rs new file mode 100644 index 00000000..012bf9b4 --- /dev/null +++ b/src/widgets/collection_pane.rs @@ -0,0 +1,135 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use glib::{subclass::types::ObjectSubclassIsExt, Object}; +use gtk::glib; + +use crate::objects::Collection; + +mod imp { + use std::cell::RefCell; + use std::sync::OnceLock; + + use glib::subclass::{InitializingObject, Signal}; + use glib::Properties; + use gtk::subclass::prelude::*; + use gtk::{prelude::*, CompositeTemplate}; + + use crate::objects::Collection; + use crate::widgets::KeyValuePane; + use crate::objects::KeyValueItem; + + #[derive(CompositeTemplate, Default, Properties)] + #[template(resource = "/es/danirod/Cartero/collection_pane.ui")] + #[properties(wrapper_type = super::CollectionPane)] + pub struct CollectionPane { + #[template_child] + collection_name: TemplateChild, + + #[template_child] + variables: TemplateChild, + + #[property(get, set)] + dirty: RefCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for CollectionPane { + const NAME: &'static str = "CarteroCollectionPane"; + type Type = super::CollectionPane; + type ParentType = gtk::Box; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_callbacks(); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for CollectionPane { + fn constructed(&self) { + self.parent_constructed(); + } + + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| vec![Signal::builder("save-requested").build()]) + } + } + + impl WidgetImpl for CollectionPane {} + + impl BoxImpl for CollectionPane {} + + #[gtk::template_callbacks] + impl CollectionPane { + #[template_callback] + fn on_save(&self) { + let obj = self.obj(); + + obj.emit_by_name::<()>("save-requested", &[]); + } + + pub(super) fn load_collection(&self, col: &Collection) { + self.collection_name.set_text(&col.title()); + let variables: Vec = col + .variables_list() + .iter() + .map(|v| KeyValueItem::new_with_value(&v.header_name(), &v.header_value())) + .collect(); + self.variables.set_entries(&variables); + } + + pub(super) fn save_collection(&self, col: &Collection) { + let name = self.collection_name.text(); + let variables = self.variables.get_entries(); + + col.set_title(name); + col.variables().remove_all(); + for variable in variables { + col.add_variable(&variable); + } + } + } +} + +glib::wrapper! { + pub struct CollectionPane(ObjectSubclass) + @extends gtk::Widget, gtk::Box; +} + +impl Default for CollectionPane { + fn default() -> Self { + Object::builder().build() + } +} + +impl CollectionPane { + pub fn load_collection(&self, col: &Collection) { + let imp = self.imp(); + imp.load_collection(col); + } + + pub fn save_collection(&self, col: &Collection) { + let imp = self.imp(); + imp.save_collection(col); + } +} diff --git a/src/widgets/file_dialogs.rs b/src/widgets/file_dialogs.rs deleted file mode 100644 index aa0ac759..00000000 --- a/src/widgets/file_dialogs.rs +++ /dev/null @@ -1,62 +0,0 @@ -#[allow(deprecated)] -use gtk::{ - prelude::FileChooserExt, - prelude::{DialogExt, FileExt, GtkWindowExt}, - FileChooserAction, FileChooserDialog, ResponseType, -}; -use std::path::PathBuf; -use tokio::sync::mpsc; - -use crate::{error::CarteroError, win::CarteroWindow}; - -#[allow(deprecated)] -async fn handle_dialog_path(dialog: FileChooserDialog) -> Result, CarteroError> { - let (tx, mut rx) = mpsc::channel::>(1); - dialog.present(); - dialog.connect_response(move |dialog, res| { - glib::spawn_future_local(glib::clone!(@strong dialog, @strong tx => async move { - let path = if res == ResponseType::Accept { - dialog.file().and_then(|f| f.path()) - } else { - None - }; - let _ = tx.send(path).await; - dialog.destroy(); - })); - }); - - let path = rx.recv().await; - if let Some(p) = path { - Ok(p) - } else { - Err(CarteroError::FileDialogError) - } -} - -#[allow(deprecated)] -pub async fn open_file(win: &CarteroWindow) -> Result, CarteroError> { - let dialog = FileChooserDialog::new( - Some("Open request"), - Some(win), - FileChooserAction::Open, - &[ - ("Accept", ResponseType::Accept), - ("Cancel", ResponseType::Cancel), - ], - ); - handle_dialog_path(dialog).await -} - -#[allow(deprecated)] -pub async fn save_file(win: &CarteroWindow) -> Result, CarteroError> { - let dialog = FileChooserDialog::new( - Some("Save request"), - Some(win), - FileChooserAction::Save, - &[ - ("Save", ResponseType::Accept), - ("Cancel", ResponseType::Cancel), - ], - ); - handle_dialog_path(dialog).await -} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index ac00d069..aa713e87 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -15,18 +15,23 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +mod collection_pane; mod endpoint_pane; -mod file_dialogs; mod item_pane; mod key_value_pane; mod key_value_row; mod response_headers; +mod new_collection_window; mod response_panel; +mod sidebar; +mod sidebar_row; +pub use collection_pane::CollectionPane; pub use endpoint_pane::EndpointPane; -pub use file_dialogs::*; pub use item_pane::ItemPane; pub use key_value_pane::KeyValuePane; pub use key_value_row::KeyValueRow; pub use response_headers::ResponseHeaders; +pub use new_collection_window::NewCollectionWindow; pub use response_panel::ResponsePanel; +pub use sidebar::Sidebar; diff --git a/src/widgets/new_collection_window.rs b/src/widgets/new_collection_window.rs new file mode 100644 index 00000000..e5827013 --- /dev/null +++ b/src/widgets/new_collection_window.rs @@ -0,0 +1,205 @@ +// Copyright 2024 the Cartero authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use glib::Object; +use gtk::subclass::prelude::*; +use gtk::{gio, prelude::*}; + +mod imp { + use adw::prelude::*; + use adw::subclass::prelude::*; + use glib::subclass::InitializingObject; + use gtk::Button; + use gtk::CompositeTemplate; + use gtk::FileDialog; + use gtk::Image; + + use crate::win::CarteroWindow; + + #[derive(CompositeTemplate, Default)] + #[template(resource = "/es/danirod/Cartero/new_collection_window.ui")] + pub struct NewCollectionWindow { + #[template_child] + collection_name: TemplateChild, + + #[template_child] + pub(super) collection_location: TemplateChild, + + #[template_child] + file_dialog: TemplateChild, + + #[template_child] + file_taken: TemplateChild, + + #[template_child] + parent_directory_does_not_exist: TemplateChild, + + #[template_child] + create_button: TemplateChild