fractal/session/model/sidebar_data/
list_model.rs1use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
2
3use super::{Selection, SidebarItemList};
4use crate::{
5 session::model::{IdentityVerification, Room},
6 utils::{expression, BoundObjectWeakRef},
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 pub item_list: OnceCell<SidebarItemList>,
20 #[property(get)]
22 pub string_filter: gtk::StringFilter,
23 #[property(get)]
25 pub is_filtered: Cell<bool>,
26 #[property(get)]
28 pub selection_model: Selection,
29 pub 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 if let Some(verification) = item.downcast_ref::<IdentityVerification>() {
54 let verification_handler = verification.connect_replaced(clone!(
55 #[weak]
56 selection_model,
57 move |_, new_verification| {
58 selection_model
59 .set_selected_item(Some(new_verification.clone()));
60 }
61 ));
62 imp.selected_item.set(item, vec![verification_handler]);
63 }
64 }
65 }
66 ));
67
68 self.string_filter.connect_search_notify(clone!(
70 #[weak(rename_to = imp)]
71 self,
72 move |string_filter| {
73 imp.set_is_filtered(string_filter.search().is_some_and(|s| !s.is_empty()));
74 }
75 ));
76 }
77 }
78
79 impl SidebarListModel {
80 fn item_list(&self) -> &SidebarItemList {
82 self.item_list.get().unwrap()
83 }
84
85 fn set_item_list(&self, item_list: SidebarItemList) {
87 let item_list = self.item_list.get_or_init(|| item_list);
88
89 let flattened_model = gtk::FlattenListModel::new(Some(item_list.clone()));
90
91 self.item_type_filter.set_filter_func(clone!(
93 #[weak(rename_to = imp)]
94 self,
95 #[upgrade_or]
96 false,
97 move |item| !imp.is_filtered.get() || item.is::<Room>()
98 ));
99
100 let room_name_expression = Room::this_expression("display-name");
102 self.string_filter
103 .set_match_mode(gtk::StringFilterMatchMode::Substring);
104 self.string_filter
105 .set_expression(Some(expression::normalize_string(room_name_expression)));
106 self.string_filter.set_ignore_case(true);
107 self.string_filter.set_search(Some(""));
109
110 let multi_filter = gtk::EveryFilter::new();
111 multi_filter.append(self.item_type_filter.clone());
112 multi_filter.append(self.string_filter.clone());
113
114 let filter_model = gtk::FilterListModel::new(Some(flattened_model), Some(multi_filter));
115
116 self.selection_model.set_model(Some(filter_model));
117 }
118
119 fn set_is_filtered(&self, is_filtered: bool) {
121 if self.is_filtered.get() == is_filtered {
122 return;
123 }
124
125 self.is_filtered.set(is_filtered);
126
127 self.obj().notify_is_filtered();
128 self.item_list().inhibit_expanded(is_filtered);
129 self.item_type_filter.changed(gtk::FilterChange::Different);
130 }
131 }
132}
133
134glib::wrapper! {
135 pub struct SidebarListModel(ObjectSubclass<imp::SidebarListModel>);
139}
140
141impl SidebarListModel {
142 pub fn new(item_list: &SidebarItemList) -> Self {
144 glib::Object::builder()
145 .property("item-list", item_list)
146 .build()
147 }
148}