fractal/session/view/content/room_details/permissions/
members_subpage.rs1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{
3 glib,
4 glib::{clone, closure},
5 CompositeTemplate,
6};
7use tracing::error;
8
9use super::{MemberPowerLevel, PermissionsMemberRow, PrivilegedMembers};
10use crate::{session::model::User, utils::expression};
11
12mod imp {
13 use std::{cell::Cell, marker::PhantomData};
14
15 use glib::subclass::InitializingObject;
16
17 use super::*;
18
19 #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
20 #[template(
21 resource = "/org/gnome/Fractal/ui/session/view/content/room_details/permissions/members_subpage.ui"
22 )]
23 #[properties(wrapper_type = super::PermissionsMembersSubpage)]
24 pub struct PermissionsMembersSubpage {
25 #[template_child]
26 search_bar: TemplateChild<gtk::SearchBar>,
27 #[template_child]
28 search_entry: TemplateChild<gtk::SearchEntry>,
29 #[template_child]
30 list_view: TemplateChild<gtk::ListView>,
31 filtered_model: gtk::FilterListModel,
32 #[property(get = Self::list, set = Self::set_list, explicit_notify, nullable)]
34 list: PhantomData<Option<PrivilegedMembers>>,
35 #[property(get, set = Self::set_editable, explicit_notify)]
37 editable: Cell<bool>,
38 }
39
40 #[glib::object_subclass]
41 impl ObjectSubclass for PermissionsMembersSubpage {
42 const NAME: &'static str = "RoomDetailsPermissionsMembersSubpage";
43 type Type = super::PermissionsMembersSubpage;
44 type ParentType = adw::NavigationPage;
45
46 fn class_init(klass: &mut Self::Class) {
47 Self::bind_template(klass);
48 }
49
50 fn instance_init(obj: &InitializingObject<Self>) {
51 obj.init_template();
52 }
53 }
54
55 #[glib::derived_properties]
56 impl ObjectImpl for PermissionsMembersSubpage {
57 fn constructed(&self) {
58 self.parent_constructed();
59
60 self.search_bar.connect_entry(&*self.search_entry);
63
64 let user_expr = gtk::ClosureExpression::new::<String>(
65 &[] as &[gtk::Expression],
66 closure!(|item: Option<glib::Object>| {
67 item.and_downcast_ref()
68 .map(MemberPowerLevel::search_string)
69 .unwrap_or_default()
70 }),
71 );
72 let search_filter = gtk::StringFilter::builder()
73 .match_mode(gtk::StringFilterMatchMode::Substring)
74 .expression(expression::normalize_string(user_expr))
75 .ignore_case(true)
76 .build();
77
78 expression::normalize_string(self.search_entry.property_expression("text")).bind(
79 &search_filter,
80 "search",
81 None::<&glib::Object>,
82 );
83
84 self.filtered_model.set_filter(Some(&search_filter));
85
86 let power_level_expr = MemberPowerLevel::this_expression("power-level");
88 let power_level_sorter = gtk::NumericSorter::builder()
89 .expression(power_level_expr)
90 .sort_order(gtk::SortType::Descending)
91 .build();
92
93 let display_name_expr =
94 MemberPowerLevel::this_expression("user").chain_property::<User>("display-name");
95 let display_name_sorter = gtk::StringSorter::new(Some(display_name_expr));
96
97 let user_id_expr =
98 MemberPowerLevel::this_expression("user").chain_property::<User>("user-id-string");
99 let user_id_sorter = gtk::StringSorter::new(Some(user_id_expr));
100
101 let sorter = gtk::MultiSorter::new();
102 sorter.append(power_level_sorter);
103 sorter.append(display_name_sorter);
104 sorter.append(user_id_sorter);
105
106 let sorted_model =
107 gtk::SortListModel::new(Some(self.filtered_model.clone()), Some(sorter));
108
109 self.list_view
110 .set_model(Some(>k::NoSelection::new(Some(sorted_model))));
111
112 let factory = gtk::SignalListItemFactory::new();
113 factory.connect_setup(clone!(
114 #[weak(rename_to = imp)]
115 self,
116 move |_, item| {
117 let Some(item) = item.downcast_ref::<gtk::ListItem>() else {
118 error!("List item factory did not receive a list item: {item:?}");
119 return;
120 };
121 let Some(permissions) = imp.list().and_then(|l| l.permissions()) else {
122 return;
123 };
124 let row = PermissionsMemberRow::new(&permissions);
125 item.set_child(Some(&row));
126 item.bind_property("item", &row, "member")
127 .sync_create()
128 .build();
129 item.set_activatable(false);
130 item.set_selectable(false);
131 }
132 ));
133 self.list_view.set_factory(Some(&factory));
134 }
135 }
136
137 impl WidgetImpl for PermissionsMembersSubpage {}
138 impl NavigationPageImpl for PermissionsMembersSubpage {}
139
140 impl PermissionsMembersSubpage {
141 fn list(&self) -> Option<PrivilegedMembers> {
143 self.filtered_model.model().and_downcast()
144 }
145
146 fn set_list(&self, list: Option<&PrivilegedMembers>) {
148 if self.list().as_ref() == list {
149 return;
150 }
151
152 self.filtered_model.set_model(list);
153 self.obj().notify_list();
154 }
155
156 fn set_editable(&self, editable: bool) {
158 if self.editable.get() == editable {
159 return;
160 }
161
162 self.editable.set(editable);
163 self.obj().notify_editable();
164 }
165 }
166}
167
168glib::wrapper! {
169 pub struct PermissionsMembersSubpage(ObjectSubclass<imp::PermissionsMembersSubpage>)
171 @extends gtk::Widget, adw::NavigationPage;
172}
173
174impl PermissionsMembersSubpage {
175 pub fn new() -> Self {
176 glib::Object::new()
177 }
178}