diff --git a/src/model/tree.rs b/src/model/tree.rs index 8ade9dc..f968ee3 100644 --- a/src/model/tree.rs +++ b/src/model/tree.rs @@ -65,3 +65,19 @@ impl RecordNode { pub struct RecordTree { pub records: TypedListStore, } + +impl RecordTree { + pub fn remove(&self, record_node: &RecordNode) { + fn traverse(list: &TypedListStore, record_node: &RecordNode) { + if let Some(position) = list.find(record_node) { + list.remove(position); + } + for item in list { + if let Some(children) = item.children() { + traverse(children, record_node); + } + } + } + traverse(&self.records, record_node); + } +} diff --git a/src/ui/file_pane.rs b/src/ui/file_pane.rs index b186991..2456a53 100644 --- a/src/ui/file_pane.rs +++ b/src/ui/file_pane.rs @@ -27,7 +27,6 @@ mod imp { use crate::ui::record_type_popover::RecordTypePopoverBuilder; use crate::ui::record_view::has_record::{PSRecordViewOptions, RECORD_NODE_HAS_RECORD}; use crate::ui::record_view::item::DropOption; - use crate::utils::typed_list_store::TypedListStore; use crate::utils::ui::{action_button, action_popover_button, orphan_all_children}; use std::cell::RefCell; use std::sync::OnceLock; @@ -36,8 +35,6 @@ mod imp { pub nav_bar: PSNavBar, pub view: PSRecordView, pub file: RefCell, - // pub current_path: TypedListStore, - // pub current_records: RefCell>, } #[glib::object_subclass] @@ -75,8 +72,6 @@ mod imp { drag_and_drop: true, }), file: Default::default(), - // current_path: Default::default(), - // current_records: Default::default(), } } } @@ -130,53 +125,21 @@ mod imp { }), ); - self.view - .connect_record_activated(glib::clone!(@weak self as imp => move |position| { + self.view.connect_record_activated( + glib::clone!(@weak self as imp => move |position, record_node| { glib::MainContext::default().spawn_local(async move { - imp.row_activated(position).await; + imp.row_activated(position, record_node).await; }); - })); + }), + ); - self.nav_bar - .connect_go_home(glib::clone!(@weak self as imp => move || { - glib::MainContext::default().spawn_local(async move { - imp.go_home().await - }); - })); - self.nav_bar - .connect_go_path(glib::clone!(@weak self as imp => move |position| { - glib::MainContext::default().spawn_local(async move { - imp.go_path(position).await - }); - })); - self.nav_bar - .connect_go_up(glib::clone!(@weak self as imp => move || { - glib::MainContext::default().spawn_local(async move { - imp.go_up().await - }); - })); - self.view - .connect_go_home(glib::clone!(@weak self as imp => move || { - glib::MainContext::default().spawn_local(async move { - imp.go_home().await - }); - })); - self.view - .connect_go_up(glib::clone!(@weak self as imp => move || { - glib::MainContext::default().spawn_local(async move { - imp.go_up().await - }); - })); self.view.connect_move_record( glib::clone!(@weak self as imp => move |_, src, dst, opt| { imp.move_record(src.downcast_ref().unwrap(), dst.downcast_ref().unwrap(), opt); }), ); - // self.set_view_model(&self.file.borrow()); - - // self.nav_bar - // .set_model(self.current_path.untyped().upcast_ref()); + self.update_view_model(); let shortcuts = gtk::ShortcutController::new(); shortcuts.add_shortcut( @@ -219,60 +182,22 @@ mod imp { impl WidgetImpl for FilePane {} impl FilePane { - pub fn set_view_model(&self, model: RecordTree) { - let tree_model = gtk::TreeListModel::new(model.records.clone().untyped().clone(), false, false, |node| { - let r = node.downcast_ref::()?; - let children = r.children()?; - Some(children.untyped().clone().upcast()) - }); + pub fn update_view_model(&self) { + let tree_model = gtk::TreeListModel::new( + self.file.borrow().records.untyped().clone(), + false, + false, + |node| { + let r = node.downcast_ref::()?; + let children = r.children()?; + Some(children.untyped().clone().upcast()) + }, + ); self.view.set_model(&tree_model); } - async fn go_home(&self) { - // self.current_path.remove_all(); - // self.set_view_model(&self.file.borrow().records); - // self.view.select_position_async(0).await; - } - - async fn go_path(&self, position: u32) { - // if position + 1 >= self.current_path.len() { - // return; - // } - // let prev = self.current_path.get(position + 1); - // self.current_path.truncate(position + 1); - // let records = match self.current_path.last() { - // Some(parent) => parent.children().unwrap().clone(), - // None => self.file.borrow().records.clone(), - // }; - // self.set_view_model(&records); - // if let Some(prev) = prev { - // self.view.select_object(prev.upcast_ref()).await; - // } - } - - async fn go_up(&self) { - // let prev = self.current_path.pop_back(); - // let records = match self.current_path.last() { - // Some(parent) => parent.children().unwrap().clone(), - // None => self.file.borrow().records.clone(), - // }; - // self.set_view_model(&records); - // if let Some(prev) = prev { - // self.view.select_object(prev.upcast_ref()).await; - // } - } - - async fn row_activated(&self, position: u32) { - // let Some(record) = self.current_records.borrow().get(position) else { - // return; - // }; - // if let Some(children) = record.children() { - // self.current_path.append(&record); - // self.set_view_model(children); - // self.view.select_position_async(0).await; - // } else { - // self.obj().emit_edit_record(position, &record); - // } + async fn row_activated(&self, position: u32, record: RecordNode) { + self.obj().emit_edit_record(position, &record); } fn move_record(&self, src: &RecordNode, dst: &RecordNode, option: DropOption) { @@ -324,20 +249,19 @@ impl FilePane { pub async fn set_file(&self, file: RecordTree) { *self.imp().file.borrow_mut() = file; - self.imp().set_view_model(self.imp().file.borrow().clone()); + self.imp().update_view_model(); self.imp().view.select_position_async(0).await; self.selection_changed(gtk::Bitset::new_empty()); self.grab_focus_to_view(); } - // pub async fn reset(&self) { - // self.set_model(Default::default()).await; - // } + pub async fn reset(&self) { + self.set_file(Default::default()).await; + } - fn get_record(&self, position: u32) -> Option { - // self.imp().current_records.borrow().get(position) - None + fn record_at(&self, position: u32) -> Option { + self.imp().view.record_at(position) } pub fn append_record(&self, record_node: &RecordNode) { @@ -357,15 +281,18 @@ impl FilePane { } fn remove_record(&self, position: u32) { - // self.imp().current_records.borrow().remove(position); - // self.selection_changed(gtk::Bitset::new_empty()); + if let Some(record) = self.record_at(position) { + self.imp().file.borrow().remove(&record); + self.selection_changed(gtk::Bitset::new_range(position, 1)); + } } - fn remove_records(&self, mut positions: Vec) { - // positions.sort(); - // for position in positions.into_iter().rev() { - // self.imp().current_records.borrow().remove(position); - // } + fn remove_records(&self, positions: Vec) { + let record_nodes = positions.into_iter().flat_map(|p| self.record_at(p)).collect::>(); + for record_node in record_nodes { + self.imp().file.borrow().remove(&record_node); + } + self.selection_changed(gtk::Bitset::new_empty()); } fn append_records_to(&self, parent: Option<&RecordNode>, records: &[RecordNode]) { @@ -389,7 +316,7 @@ impl FilePane { fn selection_changed(&self, selected: gtk::Bitset) { let selected_record = if selected.size() == 1 { - self.get_record(selected.nth(0)) + self.imp().view.record_at(selected.nth(0)) } else { None }; @@ -412,7 +339,7 @@ impl FilePane { let Some(position) = self.imp().view.get_selected_position() else { return; }; - let Some(record_node) = self.get_record(position) else { + let Some(record_node) = self.record_at(position) else { return; }; let Some(username) = record_node.record().username() else { @@ -426,7 +353,7 @@ impl FilePane { let Some(position) = self.imp().view.get_selected_position() else { return; }; - let Some(record_node) = self.get_record(position) else { + let Some(record_node) = self.record_at(position) else { return; }; let Some(password) = record_node.record().password() else { @@ -452,7 +379,7 @@ impl FilePane { let (positions, records): (Vec, Vec) = positions .iter_asc() .filter_map(|position| { - let record = self.get_record(position)?; + let record = self.record_at(position)?; if dest.iter().any(|p| p == record) { None } else { @@ -470,7 +397,7 @@ impl FilePane { let Some(position) = self.imp().view.get_selected_position() else { return; }; - let Some(record_node) = self.get_record(position) else { + let Some(record_node) = self.record_at(position) else { return; }; self.emit_edit_record(position, &record_node); @@ -505,7 +432,7 @@ impl FilePane { let (positions, records): (Vec, Vec) = positions .iter_asc() .filter_map(|position| { - let record = self.get_record(position)?; + let record = self.record_at(position)?; if record.is_group() { None } else { diff --git a/src/ui/record_view/view.rs b/src/ui/record_view/view.rs index 29e12d9..48e474a 100644 --- a/src/ui/record_view/view.rs +++ b/src/ui/record_view/view.rs @@ -1,10 +1,11 @@ use super::has_record::PSRecordViewOptions; use super::item::DropOption; -use crate::utils::ui::pending; +use crate::{model::tree::RecordNode, utils::ui::pending}; use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*}; mod imp { use super::*; + use crate::model::tree::RecordNode; use crate::ui::record_view::item::PSRecordViewItem; use crate::utils::ui::{orphan_all_children, scrolled}; use crate::weak_map::WeakMap; @@ -12,8 +13,6 @@ mod imp { use std::rc::Rc; use std::sync::OnceLock; - pub const SIGNAL_GO_HOME: &str = "ps-go-home"; - pub const SIGNAL_GO_UP: &str = "ps-go-up"; pub const SIGNAL_SELECTION_CHANGED: &str = "ps-selection-changed"; pub const SIGNAL_RECORD_ACTIVATED: &str = "ps-record-activated"; pub const SIGNAL_MOVE_RECORD: &str = "ps-record-move"; @@ -68,8 +67,18 @@ mod imp { self.list_view.set_model(Some(&self.selection)); self.list_view.connect_activate( - glib::clone!(@weak obj => move |_list_view, position| { - obj.emit_record_activated(position); + glib::clone!(@weak obj => move |list_view, position| { + if let Some(item) = list_view.model().and_then(|m| m.item(position)).and_downcast::() { + if item.is_expandable() { + item.set_expanded(!item.is_expanded()); + } else if let Some(record_node) = item.item().and_downcast::() { + obj.emit_record_activated(position, &record_node); + } else { + // TODO: warn unreachable + } + } else { + // TODO: warn unreachable + } }), ); @@ -94,10 +103,8 @@ mod imp { static SIGNALS: OnceLock> = OnceLock::new(); SIGNALS.get_or_init(|| { vec![ - glib::subclass::Signal::builder(SIGNAL_GO_HOME).build(), - glib::subclass::Signal::builder(SIGNAL_GO_UP).build(), glib::subclass::Signal::builder(SIGNAL_RECORD_ACTIVATED) - .param_types([u32::static_type()]) + .param_types([u32::static_type(), RecordNode::static_type()]) .build(), glib::subclass::Signal::builder(SIGNAL_SELECTION_CHANGED) .param_types([gtk::Bitset::static_type()]) @@ -174,6 +181,7 @@ mod imp { let Some(expander) = list_item.child().and_downcast::() else { return; }; + expander.set_list_row(None); let Some(child) = expander.child().and_downcast::() else { return; }; @@ -227,6 +235,13 @@ impl PSRecordView { Some(pos) } + pub fn record_at(&self, position: u32) -> Option { + let model = self.imp().selection.model()?; + let tree_list_row = model.item(position).and_downcast::()?; + let record = tree_list_row.item().and_downcast::()?; + Some(record) + } + pub fn set_popup(&self, popup_model: &gio::MenuModel) { *self.imp().popup_model.borrow_mut() = Some(popup_model.clone()); } @@ -269,58 +284,20 @@ impl PSRecordView { fn on_key_press(&self, key: gdk::Key, modifier: gdk::ModifierType) -> glib::Propagation { match (modifier, key) { - (gdk::ModifierType::ALT_MASK, gdk::Key::Home) => { - self.emit_go_home(); - glib::Propagation::Stop - } - (gdk::ModifierType::ALT_MASK, gdk::Key::Up) => { - self.emit_go_up(); - glib::Propagation::Stop - } - (gdk::ModifierType::ALT_MASK, gdk::Key::Down) => { - if let Some(position) = self.get_selected_position() { - self.emit_record_activated(position); - glib::Propagation::Stop - } else { - glib::Propagation::Proceed - } - } + (gdk::ModifierType::ALT_MASK, gdk::Key::Down) => self + .get_selected_position() + .and_then(|position| { + let record = self.record_at(position)?; + self.emit_record_activated(position, &record); + Some(glib::Propagation::Stop) + }) + .unwrap_or(glib::Propagation::Proceed), _ => glib::Propagation::Proceed, } } } impl PSRecordView { - fn emit_go_home(&self) { - self.emit_by_name::<()>(imp::SIGNAL_GO_HOME, &[]); - } - - pub fn connect_go_home(&self, f: F) -> glib::signal::SignalHandlerId - where - F: Fn() + 'static, - { - self.connect_closure( - imp::SIGNAL_GO_HOME, - false, - glib::closure_local!(move |_self: &Self| (f)()), - ) - } - - fn emit_go_up(&self) { - self.emit_by_name::<()>(imp::SIGNAL_GO_UP, &[]); - } - - pub fn connect_go_up(&self, f: F) -> glib::signal::SignalHandlerId - where - F: Fn() + 'static, - { - self.connect_closure( - imp::SIGNAL_GO_UP, - false, - glib::closure_local!(move |_self: &Self| (f)()), - ) - } - fn emit_selection_changed(&self, selection: >k::Bitset) { self.emit_by_name::<()>(imp::SIGNAL_SELECTION_CHANGED, &[selection]); } @@ -336,18 +313,21 @@ impl PSRecordView { ) } - fn emit_record_activated(&self, position: u32) { - self.emit_by_name::<()>(imp::SIGNAL_RECORD_ACTIVATED, &[&position]); + fn emit_record_activated(&self, position: u32, record_node: &RecordNode) { + self.emit_by_name::<()>(imp::SIGNAL_RECORD_ACTIVATED, &[&position, record_node]); } pub fn connect_record_activated(&self, f: F) -> glib::signal::SignalHandlerId where - F: Fn(u32) + 'static, + F: Fn(u32, RecordNode) + 'static, { self.connect_closure( imp::SIGNAL_RECORD_ACTIVATED, false, - glib::closure_local!(move |_self: &Self, position| (f)(position)), + glib::closure_local!(move |_self: &Self, position, record_node| (f)( + position, + record_node + )), ) } diff --git a/src/ui/search_pane.rs b/src/ui/search_pane.rs index 04c4193..1bc828c 100644 --- a/src/ui/search_pane.rs +++ b/src/ui/search_pane.rs @@ -91,15 +91,13 @@ mod imp { }), ); - self.view - .connect_record_activated(glib::clone!(@weak self as imp => move |position| { + self.view.connect_record_activated( + glib::clone!(@weak self as imp => move |position, _| { glib::MainContext::default().spawn_local(async move { imp.row_activated(position).await; }); - })); - - self.view - .connect_go_home(glib::clone!(@weak obj => move || obj.emit_go_home())); + }), + ); let shortcuts = gtk::ShortcutController::new(); shortcuts.add_shortcut(