fractal/session/sidebar_data/
list_model.rs1use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
2
3use super::SidebarItemList;
4use crate::{
5 session::{IdentityVerification, Room},
6 utils::{BoundObjectWeakRef, FixedSelection, expression},
7};
8
9mod imp {
10 use std::cell::{Cell, OnceCell};
11
12 use super::*;
13
14 #[derive(Debug, Default, glib::Properties)]
15 #[properties(wrapper_type = super::SidebarListModel)]
16 pub struct SidebarListModel {
17 #[property(get, set = Self::set_item_list, construct_only)]
19 item_list: OnceCell<SidebarItemList>,
20 #[property(get)]
22 string_filter: gtk::StringFilter,
23 #[property(get)]
25 is_filtered: Cell<bool>,
26 #[property(get)]
28 selection_model: FixedSelection,
29 selected_item: BoundObjectWeakRef<glib::Object>,
31 item_type_filter: gtk::CustomFilter,
32 }
33
34 #[glib::object_subclass]
35 impl ObjectSubclass for SidebarListModel {
36 const NAME: &'static str = "SidebarListModel";
37 type Type = super::SidebarListModel;
38 }
39
40 #[glib::derived_properties]
41 impl ObjectImpl for SidebarListModel {
42 fn constructed(&self) {
43 self.parent_constructed();
44
45 self.selection_model.connect_selected_item_notify(clone!(
47 #[weak(rename_to = imp)]
48 self,
49 move |selection_model| {
50 imp.selected_item.disconnect_signals();
51
52 if let Some(item) = &selection_model.selected_item()
53 && let Some(verification) = item.downcast_ref::<IdentityVerification>()
54 {
55 let verification_handler = verification.connect_replaced(clone!(
56 #[weak]
57 selection_model,
58 move |_, new_verification| {
59 selection_model.set_selected_item(Some(new_verification.clone()));
60 }
61 ));
62 imp.selected_item.set(item, vec![verification_handler]);
63 }
64 }
65 ));
66
67 self.string_filter.connect_search_notify(clone!(
69 #[weak(rename_to = imp)]
70 self,
71 move |string_filter| {
72 imp.set_is_filtered(string_filter.search().is_some_and(|s| !s.is_empty()));
73 }
74 ));
75 }
76 }
77
78 impl SidebarListModel {
79 fn item_list(&self) -> &SidebarItemList {
81 self.item_list.get().unwrap()
82 }
83
84 fn set_item_list(&self, item_list: SidebarItemList) {
86 let item_list = self.item_list.get_or_init(|| item_list);
87
88 let flattened_model = gtk::FlattenListModel::new(Some(item_list.clone()));
89
90 self.item_type_filter.set_filter_func(clone!(
92 #[weak(rename_to = imp)]
93 self,
94 #[upgrade_or]
95 false,
96 move |item| !imp.is_filtered.get() || item.is::<Room>()
97 ));
98
99 let room_name_expression = Room::this_expression("display-name");
101 self.string_filter
102 .set_match_mode(gtk::StringFilterMatchMode::Substring);
103 self.string_filter
104 .set_expression(Some(expression::normalize_string(room_name_expression)));
105 self.string_filter.set_ignore_case(true);
106 self.string_filter.set_search(Some(""));
108
109 let multi_filter = gtk::EveryFilter::new();
110 multi_filter.append(self.item_type_filter.clone());
111 multi_filter.append(self.string_filter.clone());
112
113 let filter_model = gtk::FilterListModel::new(Some(flattened_model), Some(multi_filter));
114
115 self.selection_model.set_model(Some(filter_model));
116 }
117
118 fn set_is_filtered(&self, is_filtered: bool) {
120 if self.is_filtered.get() == is_filtered {
121 return;
122 }
123
124 self.is_filtered.set(is_filtered);
125
126 self.obj().notify_is_filtered();
127 self.item_list().inhibit_expanded(is_filtered);
128 self.item_type_filter.changed(gtk::FilterChange::Different);
129 }
130 }
131}
132
133glib::wrapper! {
134 pub struct SidebarListModel(ObjectSubclass<imp::SidebarListModel>);
138}
139
140impl SidebarListModel {
141 pub fn new(item_list: &SidebarItemList) -> Self {
143 glib::Object::builder()
144 .property("item-list", item_list)
145 .build()
146 }
147}