From ce3bcd78218af025480fa9bf4164170fb174b4e5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 6 Dec 2021 10:56:45 -0500 Subject: [PATCH] save/restore custom app groups --- Cargo.toml | 6 +- examples/app_library/app_group/imp.rs | 2 +- examples/app_library/app_group/mod.rs | 8 +- examples/app_library/main.rs | 1 + examples/app_library/utils.rs | 11 +++ examples/app_library/window/imp.rs | 37 ++++++++- examples/app_library/window/mod.rs | 112 ++++++++++++++++++++++---- examples/launcher/window/window.ui | 4 +- 8 files changed, 156 insertions(+), 25 deletions(-) create mode 100644 examples/app_library/utils.rs diff --git a/Cargo.toml b/Cargo.toml index ff8c5ca9..b528aacd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,9 @@ x11 = { version = "2", features = ["xlib"] } # examples/launcher #pop-launcher = "1.0.3" -pop-launcher = { git = "https://github.com/pop-os/launcher", branch = "client" } -serde_json = "1.0.70" -pop-launcher-service = { git = "https://github.com/pop-os/launcher", branch = "client" } +pop-launcher = { git = "https://github.com/pop-os/launcher", branch = "master" } +serde_json = "1.0.72" +pop-launcher-service = { git = "https://github.com/pop-os/launcher", branch = "master" } postage = "0.4.1" futures = "0.3.18" glib = "0.14.8" diff --git a/examples/app_library/app_group/imp.rs b/examples/app_library/app_group/imp.rs index 73b1ca3c..07dcc776 100644 --- a/examples/app_library/app_group/imp.rs +++ b/examples/app_library/app_group/imp.rs @@ -12,7 +12,7 @@ use super::AppGroupData; // Object holding the state #[derive(Default)] pub struct AppGroup { - data: Rc>, + pub data: Rc>, } // The central trait for subclassing a GObject diff --git a/examples/app_library/app_group/mod.rs b/examples/app_library/app_group/mod.rs index a8f2cc77..832128e9 100644 --- a/examples/app_library/app_group/mod.rs +++ b/examples/app_library/app_group/mod.rs @@ -1,9 +1,10 @@ mod imp; -use gdk4::glib::Object; +use glib::Object; use glib::ObjectExt; use glib::ToVariant; use gtk4::glib; +use gtk4::subclass::prelude::*; use serde::{Deserialize, Serialize}; glib::wrapper! { @@ -26,6 +27,11 @@ impl AppGroup { }; self_ } + + pub fn group_data(&self) -> AppGroupData { + let imp = imp::AppGroup::from_instance(self); + imp.data.borrow().clone() + } } // Object holding the state diff --git a/examples/app_library/main.rs b/examples/app_library/main.rs index 63bc2faf..340d4d1c 100644 --- a/examples/app_library/main.rs +++ b/examples/app_library/main.rs @@ -1,5 +1,6 @@ mod app_group; mod grid_item; +mod utils; mod window; use gtk::gdk::Display; diff --git a/examples/app_library/utils.rs b/examples/app_library/utils.rs new file mode 100644 index 00000000..7b463e9d --- /dev/null +++ b/examples/app_library/utils.rs @@ -0,0 +1,11 @@ +use std::path::PathBuf; + +use gtk4::glib; + +pub fn data_path() -> PathBuf { + let mut path = glib::user_data_dir(); + path.push("com.cosmic.app_library"); + std::fs::create_dir_all(&path).expect("Could not create directory."); + path.push("data.json"); + path +} diff --git a/examples/app_library/window/imp.rs b/examples/app_library/window/imp.rs index 889249d9..2545a5cf 100644 --- a/examples/app_library/window/imp.rs +++ b/examples/app_library/window/imp.rs @@ -1,7 +1,7 @@ use gtk4 as gtk; -use std::cell::RefCell; -use std::rc::Rc; +use std::fs::File; +use glib::signal::Inhibit; use glib::subclass::InitializingObject; use gtk::prelude::*; use gtk::subclass::prelude::*; @@ -9,6 +9,9 @@ use gtk::{gio, glib}; use gtk::{CompositeTemplate, GridView, SearchEntry}; use once_cell::sync::OnceCell; +use crate::app_group::AppGroup; +use crate::utils::data_path; + // Object holding the state #[derive(CompositeTemplate, Default)] #[template(file = "window.ui")] @@ -47,6 +50,7 @@ impl ObjectImpl for Window { // Setup obj.setup_model(); + obj.restore_data(); obj.setup_callbacks(); obj.setup_factory(); } @@ -55,7 +59,34 @@ impl ObjectImpl for Window { impl WidgetImpl for Window {} // Trait shared by all windows -impl WindowImpl for Window {} +impl WindowImpl for Window { + fn close_request(&self, window: &Self::Type) -> Inhibit { + // Store todo data in vector + let mut backup_data = Vec::new(); + let mut position = 3; + while let Some(item) = window.group_model().item(position) { + if position == window.group_model().n_items() - 1 { + break; + } + // Get `AppGroup` from `glib::Object` + let group_data = item + .downcast_ref::() + .expect("The object needs to be of type `AppGroupData`.") + .group_data(); + // Add todo data to vector and increase position + backup_data.push(group_data); + position += 1; + } + + // Save state in file + let file = File::create(data_path()).expect("Could not create json file."); + serde_json::to_writer_pretty(file, &backup_data) + .expect("Could not write data to json file"); + + // Pass close request on to the parent + self.parent_close_request(window) + } +} // Trait shared by all application impl ApplicationWindowImpl for Window {} diff --git a/examples/app_library/window/mod.rs b/examples/app_library/window/mod.rs index 6558342e..8933f936 100644 --- a/examples/app_library/window/mod.rs +++ b/examples/app_library/window/mod.rs @@ -1,10 +1,15 @@ mod imp; use crate::app_group::AppGroup; use crate::app_group::AppGroupData; +use crate::utils::data_path; use glib::FromVariant; use glib::Variant; use gtk4 as gtk; use gtk4::Button; +use gtk4::Dialog; +use gtk4::Entry; +use gtk4::Label; +use std::fs::File; use std::path::Path; use crate::grid_item::GridItem; @@ -135,14 +140,14 @@ impl Window { app_names: Vec::new(), category: "Utility".to_string(), }), - AppGroup::new(AppGroupData { - id: 0, - name: "Custom Web".to_string(), - icon: "folder".to_string(), - mutable: true, - app_names: vec!["Firefox Web Browser".to_string()], - category: "".to_string(), - }), + // AppGroup::new(AppGroupData { + // id: 0, + // name: "Custom Web".to_string(), + // icon: "folder".to_string(), + // mutable: true, + // app_names: vec!["Firefox Web Browser".to_string()], + // category: "".to_string(), + // }), AppGroup::new(AppGroupData { id: 0, name: "New Group".to_string(), @@ -212,10 +217,60 @@ impl Window { }); // on activation change the group filter model to use the app names, and category - group_grid_view.connect_activate(glib::clone!(@weak app_filter_model => move |grid_view, position| { + group_grid_view.connect_activate(glib::clone!(@weak app_filter_model, @weak window => move |grid_view, position| { + let group_model = grid_view.model().unwrap().downcast::() + .expect("could not downcast app group view model to single selection model") + .model() + .downcast::() + .expect("could not downcast app group view selection model to list store model"); // if last item in the model, don't change filter, instead show dialog for adding new group! - if position == grid_view.model().unwrap().n_items() - 1 { - println!("TODO: launch action to show the Add/Edit Group Overlay"); + if position == group_model.n_items() - 1 { + let entry = Entry::new(); + let label = Label::new(Some("Name")); + label.set_justify(gtk4::Justification::Left); + label.set_xalign(0.0); + let vbox = gtk4::Box::builder() + .spacing(12) + .hexpand(true) + .orientation(gtk4::Orientation::Vertical) + .margin_top(12) + .margin_bottom(12) + .margin_end(12) + .margin_start(12) + + .build(); + vbox.append(&label); + vbox.append(&entry); + + let dialog = Dialog::builder() + .modal(true) + .resizable(false) + .use_header_bar(true.into()) + .destroy_with_parent(true) + .transient_for(&window) + .title("New App Group") + .child(&vbox) + .build(); + dialog.add_buttons(&[("Apply", gtk4::ResponseType::Apply), ("Cancel", gtk4::ResponseType::Cancel)]); + // dialog.add_action_widget(>k4::Button::new(), gtk4::ResponseType::Apply); + dialog.connect_response(glib::clone!(@weak entry, @weak group_model => move |dialog, response_type| { + println!("dialog should be closing..."); + if response_type == gtk4::ResponseType::Apply { + let new_app_group = AppGroup::new(AppGroupData { + id: 0, + name: entry.text().to_string(), + icon: "folder".to_string(), + mutable: true, + app_names: vec![], + category: "".to_string(), + }); + group_model.insert(group_model.n_items() - 1, &new_app_group); + } + dialog.emit_close(); + })); + // let flags = gtk4::DialogFlags::MODAL; + // let dialog = Dialog::with_buttons(Some("New App Group"), Some(&window), flags, &[("Apply", gtk4::ResponseType::Apply), ("Cancel", gtk4::ResponseType::Cancel)]); + dialog.present(); return; }; // update the application filter @@ -344,11 +399,18 @@ impl Window { })); self.add_action(&action_quit); - window.connect_is_active_notify(|win| { - if !win.is_active() { - win.close(); - } - }); + // window.connect_is_active_notify(|win| { + // let app = win + // .application() + // .expect("could not get application from window"); + // let active_window = app + // .active_window() + // .expect("no active window available, closing app library."); + // dbg!(&active_window); + // if !active_window.is_active() { + // win.close(); + // } + // }); } fn setup_factory(&self) { @@ -392,4 +454,22 @@ impl Window { // Set the factory of the list view imp.group_grid_view.set_factory(Some(&group_factory)); } + + fn restore_data(&self) { + if let Ok(file) = File::open(data_path()) { + // Deserialize data from file to vector + let backup_data: Vec = + serde_json::from_reader(file).expect("Could not get backup data from json file."); + + let app_group_objects: Vec = backup_data + .into_iter() + .map(|data| AppGroup::new(data).upcast::()) + .collect(); + + // Insert restored objects into model + self.group_model().splice(3, 0, &app_group_objects); + } else { + println!("Backup file does not exist yet {:?}", data_path()); + } + } } diff --git a/examples/launcher/window/window.ui b/examples/launcher/window/window.ui index 516e1f09..9b8e96b8 100644 --- a/examples/launcher/window/window.ui +++ b/examples/launcher/window/window.ui @@ -23,7 +23,9 @@ 500 true - + + true +