From 4584c0b5ec32a7dba0e9c0607694ba13d777a665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sun, 20 Jul 2025 12:34:40 +0200 Subject: [PATCH] Move sidebar_data::Selection to utils as FixedSelection To be able to reuse it. --- src/session/model/mod.rs | 4 +- src/session/model/sidebar_data/list_model.rs | 6 +- src/session/model/sidebar_data/mod.rs | 2 - src/session/view/sidebar/mod.rs | 8 +- .../selection.rs => utils/fixed_selection.rs} | 200 ++++++++++-------- src/utils/mod.rs | 2 + 6 files changed, 121 insertions(+), 101 deletions(-) rename src/{session/model/sidebar_data/selection.rs => utils/fixed_selection.rs} (51%) diff --git a/src/session/model/mod.rs b/src/session/model/mod.rs index 14078752..50b11ca6 100644 --- a/src/session/model/mod.rs +++ b/src/session/model/mod.rs @@ -25,8 +25,8 @@ pub(crate) use self::{ session::*, session_settings::*, sidebar_data::{ - Selection, SidebarIconItem, SidebarIconItemType, SidebarItemList, SidebarListModel, - SidebarSection, SidebarSectionName, + SidebarIconItem, SidebarIconItemType, SidebarItemList, SidebarListModel, SidebarSection, + SidebarSectionName, }, user::{User, UserExt}, user_sessions_list::{UserSession, UserSessionsList}, diff --git a/src/session/model/sidebar_data/list_model.rs b/src/session/model/sidebar_data/list_model.rs index c8be3532..2cd838de 100644 --- a/src/session/model/sidebar_data/list_model.rs +++ b/src/session/model/sidebar_data/list_model.rs @@ -1,9 +1,9 @@ use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*}; -use super::{Selection, SidebarItemList}; +use super::SidebarItemList; use crate::{ session::model::{IdentityVerification, Room}, - utils::{BoundObjectWeakRef, expression}, + utils::{BoundObjectWeakRef, FixedSelection, expression}, }; mod imp { @@ -25,7 +25,7 @@ mod imp { pub is_filtered: Cell, /// The selection model. #[property(get)] - pub selection_model: Selection, + pub selection_model: FixedSelection, /// The selected item, if it has signal handlers. pub selected_item: BoundObjectWeakRef, item_type_filter: gtk::CustomFilter, diff --git a/src/session/model/sidebar_data/mod.rs b/src/session/model/sidebar_data/mod.rs index e2fab1db..9be7188b 100644 --- a/src/session/model/sidebar_data/mod.rs +++ b/src/session/model/sidebar_data/mod.rs @@ -3,7 +3,6 @@ mod item; mod item_list; mod list_model; mod section; -mod selection; pub use self::{ icon_item::{SidebarIconItem, SidebarIconItemType}, @@ -11,5 +10,4 @@ pub use self::{ item_list::SidebarItemList, list_model::SidebarListModel, section::{SidebarSection, SidebarSectionName}, - selection::Selection, }; diff --git a/src/session/view/sidebar/mod.rs b/src/session/view/sidebar/mod.rs index c1458be7..bc716d86 100644 --- a/src/session/view/sidebar/mod.rs +++ b/src/session/view/sidebar/mod.rs @@ -21,10 +21,10 @@ use crate::{ account_switcher::AccountSwitcherButton, components::OfflineBanner, session::model::{ - CryptoIdentityState, RecoveryState, RoomCategory, Selection, Session, - SessionVerificationState, SidebarListModel, SidebarSection, TargetRoomCategory, User, + CryptoIdentityState, RecoveryState, RoomCategory, Session, SessionVerificationState, + SidebarListModel, SidebarSection, TargetRoomCategory, User, }, - utils::expression, + utils::{FixedSelection, expression}, }; mod imp { @@ -126,7 +126,7 @@ mod imp { self.listview.set_factory(Some(&factory)); self.listview.connect_activate(move |listview, pos| { - let Some(model) = listview.model().and_downcast::() else { + let Some(model) = listview.model().and_downcast::() else { return; }; let Some(item) = model.item(pos) else { diff --git a/src/session/model/sidebar_data/selection.rs b/src/utils/fixed_selection.rs similarity index 51% rename from src/session/model/sidebar_data/selection.rs rename to src/utils/fixed_selection.rs index 26b45917..7b079d14 100644 --- a/src/session/model/sidebar_data/selection.rs +++ b/src/utils/fixed_selection.rs @@ -8,20 +8,20 @@ mod imp { use super::*; #[derive(Debug, glib::Properties)] - #[properties(wrapper_type = super::Selection)] - pub struct Selection { + #[properties(wrapper_type = super::FixedSelection)] + pub struct FixedSelection { /// The underlying model. #[property(get, set = Self::set_model, explicit_notify, nullable)] - pub model: BoundObject, + model: BoundObject, /// The position of the selected item. #[property(get, set = Self::set_selected, explicit_notify, default = gtk::INVALID_LIST_POSITION)] - pub selected: Cell, + selected: Cell, /// The selected item. #[property(get, set = Self::set_selected_item, explicit_notify, nullable)] - pub selected_item: RefCell>, + selected_item: RefCell>, } - impl Default for Selection { + impl Default for FixedSelection { fn default() -> Self { Self { model: Default::default(), @@ -32,16 +32,16 @@ mod imp { } #[glib::object_subclass] - impl ObjectSubclass for Selection { - const NAME: &'static str = "SidebarSelection"; - type Type = super::Selection; + impl ObjectSubclass for FixedSelection { + const NAME: &'static str = "FixedSelection"; + type Type = super::FixedSelection; type Interfaces = (gio::ListModel, gtk::SelectionModel); } #[glib::derived_properties] - impl ObjectImpl for Selection {} + impl ObjectImpl for FixedSelection {} - impl ListModelImpl for Selection { + impl ListModelImpl for FixedSelection { fn item_type(&self) -> glib::Type { glib::Object::static_type() } @@ -55,7 +55,7 @@ mod imp { } } - impl SelectionModelImpl for Selection { + impl SelectionModelImpl for FixedSelection { fn selection_in_range(&self, _position: u32, _n_items: u32) -> gtk::Bitset { let bitset = gtk::Bitset::new_empty(); let selected = self.selected.get(); @@ -72,44 +72,52 @@ mod imp { } } - impl Selection { + impl FixedSelection { /// Set the underlying model. fn set_model(&self, model: Option) { - let obj = self.obj(); - let _guard = obj.freeze_notify(); - - let model = model.map(|m| m.clone().upcast()); + let prev_model = self.model.obj(); - let old_model = self.model.obj(); - if old_model == model { + if prev_model == model { return; } - let n_items_before = old_model.map_or(0, |model| model.n_items()); + let prev_n_items = prev_model + .as_ref() + .map(ListModelExt::n_items) + .unwrap_or_default(); + let n_items = model + .as_ref() + .map(ListModelExt::n_items) + .unwrap_or_default(); + self.model.disconnect_signals(); + let obj = self.obj(); + let _guard = obj.freeze_notify(); + if let Some(model) = model { let items_changed_handler = model.connect_items_changed(clone!( - #[weak] - obj, + #[weak(rename_to = imp)] + self, move |m, p, r, a| { - obj.items_changed_cb(m, p, r, a); + imp.items_changed_cb(m, p, r, a); } )); - self.model.set(model.clone(), vec![items_changed_handler]); - obj.items_changed_cb(&model, 0, n_items_before, model.n_items()); - } else { - if self.selected.get() != gtk::INVALID_LIST_POSITION { - self.selected.replace(gtk::INVALID_LIST_POSITION); - obj.notify_selected(); - } - if self.selected_item.borrow().is_some() { - self.selected_item.replace(None); - obj.notify_selected_item(); - } + self.model.set(model, vec![items_changed_handler]); + } + + if self.selected.get() != gtk::INVALID_LIST_POSITION { + self.selected.replace(gtk::INVALID_LIST_POSITION); + obj.notify_selected(); + } + if self.selected_item.borrow().is_some() { + self.selected_item.replace(None); + obj.notify_selected_item(); + } - obj.items_changed(0, n_items_before, 0); + if prev_n_items > 0 || n_items > 0 { + obj.items_changed(0, prev_n_items, n_items); } obj.notify_model(); @@ -117,8 +125,8 @@ mod imp { /// Set the selected item by its position. fn set_selected(&self, position: u32) { - let old_selected = self.selected.get(); - if old_selected == position { + let prev_selected = self.selected.get(); + if prev_selected == position { return; } @@ -130,7 +138,7 @@ mod imp { position }; - if old_selected == selected { + if prev_selected == selected { return; } let obj = self.obj(); @@ -138,14 +146,14 @@ mod imp { self.selected.replace(selected); self.selected_item.replace(selected_item); - if old_selected == gtk::INVALID_LIST_POSITION { + if prev_selected == gtk::INVALID_LIST_POSITION { obj.selection_changed(selected, 1); } else if selected == gtk::INVALID_LIST_POSITION { - obj.selection_changed(old_selected, 1); - } else if selected < old_selected { - obj.selection_changed(selected, old_selected - selected + 1); + obj.selection_changed(prev_selected, 1); + } else if selected < prev_selected { + obj.selection_changed(selected, prev_selected - selected + 1); } else { - obj.selection_changed(old_selected, selected - old_selected + 1); + obj.selection_changed(prev_selected, selected - prev_selected + 1); } obj.notify_selected(); @@ -159,7 +167,7 @@ mod imp { } let obj = self.obj(); - let old_selected = self.selected.get(); + let prev_selected = self.selected.get(); let mut selected = gtk::INVALID_LIST_POSITION; if item.is_some() { @@ -176,77 +184,89 @@ mod imp { self.selected_item.replace(item); - if old_selected != selected { + if prev_selected != selected { self.selected.replace(selected); - if old_selected == gtk::INVALID_LIST_POSITION { + if prev_selected == gtk::INVALID_LIST_POSITION { obj.selection_changed(selected, 1); } else if selected == gtk::INVALID_LIST_POSITION { - obj.selection_changed(old_selected, 1); - } else if selected < old_selected { - obj.selection_changed(selected, old_selected - selected + 1); + obj.selection_changed(prev_selected, 1); + } else if selected < prev_selected { + obj.selection_changed(selected, prev_selected - selected + 1); } else { - obj.selection_changed(old_selected, selected - old_selected + 1); + obj.selection_changed(prev_selected, selected - prev_selected + 1); } obj.notify_selected(); } obj.notify_selected_item(); } - } -} -glib::wrapper! { - /// A `GtkSelectionModel` that keeps track of the selected item even if its position changes or it is removed from the list. - pub struct Selection(ObjectSubclass) - @implements gio::ListModel, gtk::SelectionModel; -} + /// Handle when items changed in the underlying model. + fn items_changed_cb( + &self, + model: &gio::ListModel, + position: u32, + removed: u32, + added: u32, + ) { + let obj = self.obj(); + let _guard = obj.freeze_notify(); -impl Selection { - pub fn new>(model: Option<&P>) -> Selection { - let model = model.map(|m| m.clone().upcast()); - glib::Object::builder().property("model", &model).build() - } + let selected = self.selected.get(); + let selected_item = self.selected_item.borrow().clone(); + + if selected_item.is_none() || selected < position { + // unchanged + } else if selected != gtk::INVALID_LIST_POSITION && selected >= position + removed { + self.selected.set(selected + added - removed); + obj.notify_selected(); + } else { + let mut found = false; + + for i in position..(position + added) { + let item = model.item(i); - fn items_changed_cb(&self, model: &gio::ListModel, position: u32, removed: u32, added: u32) { - let imp = self.imp(); - - let _guard = self.freeze_notify(); - - let selected = self.selected(); - let selected_item = self.selected_item(); - - if selected_item.is_none() || selected < position { - // unchanged - } else if selected != gtk::INVALID_LIST_POSITION && selected >= position + removed { - imp.selected.replace(selected + added - removed); - self.notify_selected(); - } else { - for i in 0..=added { - if i == added { - // the item really was deleted - imp.selected.replace(gtk::INVALID_LIST_POSITION); - self.notify_selected(); - } else { - let item = model.item(position + i); if item == selected_item { - // the item moved - if selected != position + i { - imp.selected.replace(position + i); - self.notify_selected(); + if selected != i { + // The position of the item changed. + self.selected.set(i); + obj.notify_selected(); } + + found = true; break; } } + + if !found { + // The item is no longer in the model. + self.selected.set(gtk::INVALID_LIST_POSITION); + obj.notify_selected(); + } } + + obj.items_changed(position, removed, added); } + } +} + +glib::wrapper! { + /// A `GtkSelectionModel` that keeps track of the selected item even if its + /// position changes or it is removed from the list. + pub struct FixedSelection(ObjectSubclass) + @implements gio::ListModel, gtk::SelectionModel; +} - self.items_changed(position, removed, added); +impl FixedSelection { + /// Construct a new `FixedSelection` with the given model. + pub fn new(model: Option<&impl IsA>) -> Self { + glib::Object::builder().property("model", model).build() } } -impl Default for Selection { +impl Default for FixedSelection { fn default() -> Self { - Self::new(gio::ListModel::NONE) + Self::new(None::<&gio::ListModel>) } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d1f01b78..48b0f486 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -18,6 +18,7 @@ use tokio::task::{AbortHandle, JoinHandle}; pub(crate) mod expression; mod expression_list_model; +mod fixed_selection; mod grouping_list_model; pub(crate) mod key_bindings; mod location; @@ -34,6 +35,7 @@ pub(crate) mod toast; pub(crate) use self::{ expression_list_model::ExpressionListModel, + fixed_selection::FixedSelection, grouping_list_model::*, location::{Location, LocationError, LocationExt}, placeholder_object::PlaceholderObject,