1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{CompositeTemplate, glib, glib::clone};
4use ruma::{
5 Int,
6 events::{
7 StateEventType, TimelineEventType,
8 room::power_levels::{PowerLevelAction, RoomPowerLevels},
9 },
10};
11
12use super::{PermissionsAddMembersSubpage, PermissionsMembersSubpage, PrivilegedMembers};
13use crate::{
14 components::{ButtonCountRow, LoadingButton, PowerLevelSelectionRow},
15 session::model::{Permissions, PowerLevel},
16 toast,
17 utils::BoundObjectWeakRef,
18};
19
20mod imp {
21 use std::cell::{Cell, OnceCell};
22
23 use glib::subclass::InitializingObject;
24
25 use super::*;
26
27 #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
28 #[template(
29 resource = "/org/gnome/Fractal/ui/session/view/content/room_details/permissions/permissions_subpage.ui"
30 )]
31 #[properties(wrapper_type = super::PermissionsSubpage)]
32 pub struct PermissionsSubpage {
33 #[template_child]
34 save_button: TemplateChild<LoadingButton>,
35 #[template_child]
36 messages_row: TemplateChild<PowerLevelSelectionRow>,
37 #[template_child]
38 redact_own_row: TemplateChild<PowerLevelSelectionRow>,
39 #[template_child]
40 redact_others_row: TemplateChild<PowerLevelSelectionRow>,
41 #[template_child]
42 notify_room_row: TemplateChild<PowerLevelSelectionRow>,
43 #[template_child]
44 state_row: TemplateChild<PowerLevelSelectionRow>,
45 #[template_child]
46 name_row: TemplateChild<PowerLevelSelectionRow>,
47 #[template_child]
48 topic_row: TemplateChild<PowerLevelSelectionRow>,
49 #[template_child]
50 avatar_row: TemplateChild<PowerLevelSelectionRow>,
51 #[template_child]
52 aliases_row: TemplateChild<PowerLevelSelectionRow>,
53 #[template_child]
54 history_visibility_row: TemplateChild<PowerLevelSelectionRow>,
55 #[template_child]
56 encryption_row: TemplateChild<PowerLevelSelectionRow>,
57 #[template_child]
58 power_levels_row: TemplateChild<PowerLevelSelectionRow>,
59 #[template_child]
60 server_acl_row: TemplateChild<PowerLevelSelectionRow>,
61 #[template_child]
62 upgrade_row: TemplateChild<PowerLevelSelectionRow>,
63 #[template_child]
64 invite_row: TemplateChild<PowerLevelSelectionRow>,
65 #[template_child]
66 kick_row: TemplateChild<PowerLevelSelectionRow>,
67 #[template_child]
68 ban_row: TemplateChild<PowerLevelSelectionRow>,
69 #[template_child]
70 members_default_spin_row: TemplateChild<adw::SpinRow>,
71 #[template_child]
72 members_default_adjustment: TemplateChild<gtk::Adjustment>,
73 #[template_child]
74 members_default_text_row: TemplateChild<adw::ActionRow>,
75 #[template_child]
76 members_default_label: TemplateChild<gtk::Label>,
77 #[template_child]
78 members_privileged_button: TemplateChild<ButtonCountRow>,
79 #[template_child]
81 members_subpage: TemplateChild<PermissionsMembersSubpage>,
82 #[template_child]
84 add_members_subpage: TemplateChild<PermissionsAddMembersSubpage>,
85 #[property(get, set = Self::set_permissions, construct_only)]
87 permissions: BoundObjectWeakRef<Permissions>,
88 #[property(get)]
90 editable: Cell<bool>,
91 #[property(get)]
93 changed: Cell<bool>,
94 #[property(get)]
96 privileged_members: OnceCell<PrivilegedMembers>,
97 update_in_progress: Cell<bool>,
102 }
103
104 #[glib::object_subclass]
105 impl ObjectSubclass for PermissionsSubpage {
106 const NAME: &'static str = "RoomDetailsPermissionsSubpage";
107 type Type = super::PermissionsSubpage;
108 type ParentType = adw::NavigationPage;
109
110 fn class_init(klass: &mut Self::Class) {
111 Self::bind_template(klass);
112 Self::bind_template_callbacks(klass);
113 }
114
115 fn instance_init(obj: &InitializingObject<Self>) {
116 obj.init_template();
117 }
118 }
119
120 #[glib::derived_properties]
121 impl ObjectImpl for PermissionsSubpage {}
122
123 impl WidgetImpl for PermissionsSubpage {}
124 impl NavigationPageImpl for PermissionsSubpage {}
125
126 #[gtk::template_callbacks]
127 impl PermissionsSubpage {
128 fn set_permissions(&self, permissions: &Permissions) {
130 let changed_handler = permissions.connect_changed(clone!(
131 #[weak(rename_to = imp)]
132 self,
133 move |_| {
134 imp.update();
135 }
136 ));
137
138 self.permissions.set(permissions, vec![changed_handler]);
139
140 let privileged_members = PrivilegedMembers::new(permissions);
141 self.privileged_members
142 .set(privileged_members.clone())
143 .unwrap();
144
145 privileged_members.connect_changed_notify(clone!(
146 #[weak(rename_to = imp)]
147 self,
148 move |_| {
149 imp.update_changed();
150 }
151 ));
152
153 self.members_subpage
154 .set_list(Some(privileged_members.clone()));
155
156 self.add_members_subpage.set_permissions(Some(permissions));
157 self.add_members_subpage
158 .set_privileged_members(Some(privileged_members));
159
160 self.update();
161 }
162
163 fn privileged_members(&self) -> &PrivilegedMembers {
165 self.privileged_members
166 .get()
167 .expect("privileged members should be initialized")
168 }
169
170 fn update(&self) {
172 let Some(permissions) = self.permissions.obj() else {
173 return;
174 };
175
176 self.update_in_progress.set(true);
177
178 let can_change = permissions
179 .is_allowed_to(PowerLevelAction::SendState(StateEventType::RoomPowerLevels));
180 self.set_editable(can_change);
181
182 self.update_room_actions();
183 self.update_member_actions();
184 self.update_members_power_levels();
185
186 self.save_button.set_is_loading(false);
187
188 self.update_in_progress.set(false);
189 self.update_changed();
190 }
191
192 fn set_editable(&self, editable: bool) {
194 if self.editable.get() == editable {
195 return;
196 }
197
198 self.editable.set(editable);
199 self.obj().notify_editable();
200 }
201
202 fn update_changed(&self) {
204 if self.update_in_progress.get() {
205 return;
207 }
208
209 let changed = self.compute_changed();
210
211 if self.changed.get() == changed {
212 return;
213 }
214
215 self.changed.set(changed);
216 self.obj().notify_changed();
217 }
218
219 #[allow(clippy::too_many_lines)]
221 fn compute_changed(&self) -> bool {
222 let Some(privileged_members) = self.privileged_members.get() else {
223 return false;
224 };
225
226 if privileged_members.changed() {
227 return true;
228 }
229
230 let Some(permissions) = self.permissions.obj() else {
231 return false;
232 };
233 let power_levels = permissions.power_levels();
234
235 let events_default = PowerLevel::from(power_levels.events_default);
236 if self.messages_row.selected_power_level() != events_default {
237 return true;
238 }
239
240 let redact_own = event_power_level(
241 &power_levels,
242 &TimelineEventType::RoomRedaction,
243 events_default,
244 );
245 if self.redact_own_row.selected_power_level() != redact_own {
246 return true;
247 }
248
249 let redact_others = redact_own.max(power_levels.redact.into());
250 if self.redact_others_row.selected_power_level() != redact_others {
251 return true;
252 }
253
254 let notify_room = PowerLevel::from(power_levels.notifications.room);
255 if self.notify_room_row.selected_power_level() != notify_room {
256 return true;
257 }
258
259 let state_default = PowerLevel::from(power_levels.state_default);
260 if self.state_row.selected_power_level() != state_default {
261 return true;
262 }
263
264 let name =
265 event_power_level(&power_levels, &TimelineEventType::RoomName, state_default);
266 if self.name_row.selected_power_level() != name {
267 return true;
268 }
269
270 let topic =
271 event_power_level(&power_levels, &TimelineEventType::RoomTopic, state_default);
272 if self.topic_row.selected_power_level() != topic {
273 return true;
274 }
275
276 let avatar =
277 event_power_level(&power_levels, &TimelineEventType::RoomAvatar, state_default);
278 if self.avatar_row.selected_power_level() != avatar {
279 return true;
280 }
281
282 let aliases = event_power_level(
283 &power_levels,
284 &TimelineEventType::RoomCanonicalAlias,
285 state_default,
286 );
287 if self.aliases_row.selected_power_level() != aliases {
288 return true;
289 }
290
291 let history_visibility = event_power_level(
292 &power_levels,
293 &TimelineEventType::RoomHistoryVisibility,
294 state_default,
295 );
296 if self.history_visibility_row.selected_power_level() != history_visibility {
297 return true;
298 }
299
300 let encryption = event_power_level(
301 &power_levels,
302 &TimelineEventType::RoomEncryption,
303 state_default,
304 );
305 if self.encryption_row.selected_power_level() != encryption {
306 return true;
307 }
308
309 let pl = event_power_level(
310 &power_levels,
311 &TimelineEventType::RoomPowerLevels,
312 state_default,
313 );
314 if self.power_levels_row.selected_power_level() != pl {
315 return true;
316 }
317
318 let server_acl = event_power_level(
319 &power_levels,
320 &TimelineEventType::RoomServerAcl,
321 state_default,
322 );
323 if self.server_acl_row.selected_power_level() != server_acl {
324 return true;
325 }
326
327 let upgrade = event_power_level(
328 &power_levels,
329 &TimelineEventType::RoomTombstone,
330 state_default,
331 );
332 if self.upgrade_row.selected_power_level() != upgrade {
333 return true;
334 }
335
336 let invite = PowerLevel::from(power_levels.invite);
337 if self.invite_row.selected_power_level() != invite {
338 return true;
339 }
340
341 let kick = PowerLevel::from(power_levels.kick);
342 if self.kick_row.selected_power_level() != kick {
343 return true;
344 }
345
346 let ban = PowerLevel::from(power_levels.ban);
347 if self.ban_row.selected_power_level() != ban {
348 return true;
349 }
350
351 let default_pl = PowerLevel::from(power_levels.users_default);
352 self.members_default_adjustment.value() as PowerLevel != default_pl
353 }
354
355 fn update_room_actions(&self) {
357 let Some(permissions) = self.permissions.obj() else {
358 return;
359 };
360
361 let editable = self.editable.get();
362 let power_levels = permissions.power_levels();
363 let own_pl = permissions.own_power_level();
364
365 let events_default = PowerLevel::from(power_levels.events_default);
366 self.messages_row.set_selected_power_level(events_default);
367 self.messages_row
368 .set_read_only(!editable || own_pl < events_default);
369
370 let redact_own = event_power_level(
371 &power_levels,
372 &TimelineEventType::RoomRedaction,
373 events_default,
374 );
375 self.redact_own_row.set_selected_power_level(redact_own);
376 self.redact_own_row
377 .set_read_only(!editable || own_pl < redact_own);
378
379 let redact_others = redact_own.max(power_levels.redact.into());
380 self.redact_others_row
381 .set_selected_power_level(redact_others);
382 self.redact_others_row
383 .set_read_only(!editable || own_pl < redact_others);
384
385 let notify_room = PowerLevel::from(power_levels.notifications.room);
386 self.notify_room_row.set_selected_power_level(notify_room);
387 self.notify_room_row
388 .set_read_only(!editable || own_pl < notify_room);
389
390 let state_default = PowerLevel::from(power_levels.state_default);
391 self.state_row.set_selected_power_level(state_default);
392 self.state_row
393 .set_read_only(!editable || own_pl < state_default);
394
395 self.update_state_rows();
396 }
397
398 fn update_state_rows(&self) {
400 let Some(permissions) = self.permissions.obj() else {
401 return;
402 };
403
404 let editable = self.editable.get();
405 let power_levels = permissions.power_levels();
406 let own_pl = permissions.own_power_level();
407 let state_default = self.state_row.selected_power_level();
408
409 let name =
410 event_power_level(&power_levels, &TimelineEventType::RoomName, state_default);
411 self.name_row.set_selected_power_level(name);
412 self.name_row.set_read_only(!editable || own_pl < name);
413
414 let topic =
415 event_power_level(&power_levels, &TimelineEventType::RoomTopic, state_default);
416 self.topic_row.set_selected_power_level(topic);
417 self.topic_row.set_read_only(!editable || own_pl < topic);
418
419 let avatar =
420 event_power_level(&power_levels, &TimelineEventType::RoomAvatar, state_default);
421 self.avatar_row.set_selected_power_level(avatar);
422 self.avatar_row.set_read_only(!editable || own_pl < avatar);
423
424 let aliases = event_power_level(
425 &power_levels,
426 &TimelineEventType::RoomCanonicalAlias,
427 state_default,
428 );
429 self.aliases_row.set_selected_power_level(aliases);
430 self.aliases_row
431 .set_read_only(!editable || own_pl < aliases);
432
433 let history_visibility = event_power_level(
434 &power_levels,
435 &TimelineEventType::RoomHistoryVisibility,
436 state_default,
437 );
438 self.history_visibility_row
439 .set_selected_power_level(history_visibility);
440 self.history_visibility_row
441 .set_read_only(!editable || own_pl < history_visibility);
442
443 let encryption = event_power_level(
444 &power_levels,
445 &TimelineEventType::RoomEncryption,
446 state_default,
447 );
448 self.encryption_row.set_selected_power_level(encryption);
449 self.encryption_row
450 .set_read_only(!editable || own_pl < encryption);
451
452 let pl = event_power_level(
453 &power_levels,
454 &TimelineEventType::RoomPowerLevels,
455 state_default,
456 );
457 self.power_levels_row.set_selected_power_level(pl);
458 self.power_levels_row
459 .set_read_only(!editable || own_pl < pl);
460
461 let server_acl = event_power_level(
462 &power_levels,
463 &TimelineEventType::RoomServerAcl,
464 state_default,
465 );
466 self.server_acl_row.set_selected_power_level(server_acl);
467 self.server_acl_row
468 .set_read_only(!editable || own_pl < server_acl);
469
470 let upgrade = event_power_level(
471 &power_levels,
472 &TimelineEventType::RoomTombstone,
473 state_default,
474 );
475 self.upgrade_row.set_selected_power_level(upgrade);
476 self.upgrade_row
477 .set_read_only(!editable || own_pl < upgrade);
478 }
479
480 fn update_member_actions(&self) {
482 let Some(permissions) = self.permissions.obj() else {
483 return;
484 };
485
486 let editable = self.editable.get();
487 let power_levels = permissions.power_levels();
488 let own_pl = permissions.own_power_level();
489
490 let invite = PowerLevel::from(power_levels.invite);
491 self.invite_row.set_selected_power_level(invite);
492 self.invite_row.set_read_only(!editable || own_pl < invite);
493
494 let kick = PowerLevel::from(power_levels.kick);
495 self.kick_row.set_selected_power_level(kick);
496 self.kick_row.set_read_only(!editable || own_pl < kick);
497
498 let ban = PowerLevel::from(power_levels.ban);
499 self.ban_row.set_selected_power_level(ban);
500 self.ban_row.set_read_only(!editable || own_pl < ban);
501 }
502
503 fn update_members_power_levels(&self) {
505 let Some(permissions) = self.permissions.obj() else {
506 return;
507 };
508 let power_levels = permissions.power_levels();
509
510 let default_pl = PowerLevel::from(power_levels.users_default);
511 self.members_default_adjustment.set_value(default_pl as f64);
512 self.members_default_label
513 .set_label(&default_pl.to_string());
514
515 let own_pl = permissions.own_power_level();
517 let max = default_pl.max(own_pl);
518 self.members_default_adjustment.set_upper(max as f64);
519
520 let editable = self.editable.get();
521 let can_change_default = editable && own_pl >= default_pl;
522 self.members_default_spin_row
523 .set_visible(can_change_default);
524 self.members_default_text_row
525 .set_visible(!can_change_default);
526
527 self.members_privileged_button
528 .set_count(power_levels.users.len().to_string());
529 }
530
531 #[template_callback]
535 async fn go_back(&self) {
536 let obj = self.obj();
537 let mut reset_after = false;
538
539 if self.changed.get() {
540 let title = gettext("Save Changes?");
541 let description = gettext(
542 "This page contains unsaved changes. Changes which are not saved will be lost.",
543 );
544 let dialog = adw::AlertDialog::builder()
545 .title(title)
546 .body(description)
547 .default_response("cancel")
548 .build();
549
550 dialog.add_responses(&[
551 ("cancel", &gettext("Cancel")),
552 ("discard", &gettext("Discard")),
553 ("save", &gettext("Save")),
554 ]);
555 dialog.set_response_appearance("discard", adw::ResponseAppearance::Destructive);
556 dialog.set_response_appearance("save", adw::ResponseAppearance::Suggested);
557
558 match dialog.choose_future(&*obj).await.as_str() {
559 "discard" => {
560 reset_after = true;
561 }
562 "save" => {
563 self.save().await;
564 }
565 _ => {
566 return;
567 }
568 }
569 }
570
571 let _ = obj.activate_action("navigation.pop", None);
572
573 if reset_after {
574 self.update();
575 }
576 }
577
578 #[template_callback]
580 async fn save(&self) {
581 if !self.compute_changed() {
582 return;
583 }
584
585 let Some(permissions) = self.permissions.obj() else {
586 return;
587 };
588
589 self.save_button.set_is_loading(true);
590
591 let Some(power_levels) = self.collect_power_levels() else {
592 return;
593 };
594
595 if permissions.set_power_levels(power_levels).await.is_err() {
596 toast!(self.obj(), gettext("Could not save permissions"));
597 self.save_button.set_is_loading(false);
598 }
599 }
600
601 fn collect_power_levels(&self) -> Option<RoomPowerLevels> {
605 let permissions = self.permissions.obj()?;
606
607 let mut power_levels = permissions.power_levels();
608
609 let events_default = self.messages_row.selected_power_level();
610 power_levels.events_default = Int::new_saturating(events_default);
611
612 let mut redact_own = self.redact_own_row.selected_power_level();
613 let redact_others = self.redact_others_row.selected_power_level();
614
615 redact_own = redact_own.min(redact_others);
618 set_event_power_level(
619 &mut power_levels,
620 TimelineEventType::RoomRedaction,
621 redact_own,
622 events_default,
623 );
624
625 power_levels.redact = Int::new_saturating(redact_others);
626
627 let notify_room = self.notify_room_row.selected_power_level();
628 power_levels.notifications.room = Int::new_saturating(notify_room);
629
630 let state_default = self.state_row.selected_power_level();
631 power_levels.state_default = Int::new_saturating(state_default);
632
633 let name = self.name_row.selected_power_level();
634 set_event_power_level(
635 &mut power_levels,
636 TimelineEventType::RoomName,
637 name,
638 state_default,
639 );
640
641 let topic = self.topic_row.selected_power_level();
642 set_event_power_level(
643 &mut power_levels,
644 TimelineEventType::RoomTopic,
645 topic,
646 state_default,
647 );
648
649 let avatar = self.avatar_row.selected_power_level();
650 set_event_power_level(
651 &mut power_levels,
652 TimelineEventType::RoomAvatar,
653 avatar,
654 state_default,
655 );
656
657 let aliases = self.aliases_row.selected_power_level();
658 set_event_power_level(
659 &mut power_levels,
660 TimelineEventType::RoomCanonicalAlias,
661 aliases,
662 state_default,
663 );
664
665 let history_visibility = self.history_visibility_row.selected_power_level();
666 set_event_power_level(
667 &mut power_levels,
668 TimelineEventType::RoomHistoryVisibility,
669 history_visibility,
670 state_default,
671 );
672
673 let encryption = self.encryption_row.selected_power_level();
674 set_event_power_level(
675 &mut power_levels,
676 TimelineEventType::RoomEncryption,
677 encryption,
678 state_default,
679 );
680
681 let pl = self.power_levels_row.selected_power_level();
682 set_event_power_level(
683 &mut power_levels,
684 TimelineEventType::RoomPowerLevels,
685 pl,
686 state_default,
687 );
688
689 let server_acl = self.server_acl_row.selected_power_level();
690 set_event_power_level(
691 &mut power_levels,
692 TimelineEventType::RoomServerAcl,
693 server_acl,
694 state_default,
695 );
696
697 let upgrade = self.upgrade_row.selected_power_level();
698 set_event_power_level(
699 &mut power_levels,
700 TimelineEventType::RoomTombstone,
701 upgrade,
702 state_default,
703 );
704
705 let invite = self.invite_row.selected_power_level();
706 power_levels.invite = Int::new_saturating(invite);
707
708 let kick = self.kick_row.selected_power_level();
709 power_levels.kick = Int::new_saturating(kick);
710
711 let ban = self.ban_row.selected_power_level();
712 power_levels.ban = Int::new_saturating(ban);
713
714 let default_pl = self.members_default_adjustment.value() as PowerLevel;
715 power_levels.users_default = Int::new_saturating(default_pl);
716
717 let privileged_members = self.privileged_members();
718 power_levels.users = privileged_members.collect();
719
720 Some(power_levels)
721 }
722
723 #[template_callback]
725 fn value_changed(&self) {
726 if self.update_in_progress.get() {
727 return;
729 }
730
731 self.update_changed();
732 }
733
734 #[template_callback]
736 fn redact_own_changed(&self) {
737 if self.update_in_progress.get() {
738 return;
740 }
741
742 let redact_own = self.redact_own_row.selected_power_level();
743 let redact_others = self.redact_others_row.selected_power_level();
744
745 if redact_others < redact_own {
748 self.update_in_progress.set(true);
749
750 self.redact_others_row.set_selected_power_level(redact_own);
751
752 self.update_in_progress.set(false);
753 }
754
755 self.update_changed();
756 }
757
758 #[template_callback]
760 fn redact_others_changed(&self) {
761 if self.update_in_progress.get() {
762 return;
764 }
765
766 let redact_own = self.redact_own_row.selected_power_level();
767 let redact_others = self.redact_others_row.selected_power_level();
768
769 if redact_others < redact_own {
772 self.update_in_progress.set(true);
773
774 self.redact_own_row.set_selected_power_level(redact_others);
775
776 self.update_in_progress.set(false);
777 }
778
779 self.update_changed();
780 }
781
782 #[template_callback]
784 fn state_default_changed(&self) {
785 if self.update_in_progress.get() {
786 return;
788 }
789
790 self.update_in_progress.set(true);
791
792 self.update_state_rows();
793
794 self.update_in_progress.set(false);
795 self.update_changed();
796 }
797 }
798}
799
800glib::wrapper! {
801 pub struct PermissionsSubpage(ObjectSubclass<imp::PermissionsSubpage>)
803 @extends gtk::Widget, gtk::Window, adw::NavigationPage, @implements gtk::Accessible;
804}
805
806impl PermissionsSubpage {
807 pub fn new(permissions: &Permissions) -> Self {
808 glib::Object::builder()
809 .property("permissions", permissions)
810 .build()
811 }
812}
813
814fn set_event_power_level(
816 power_levels: &mut RoomPowerLevels,
817 event_type: TimelineEventType,
818 value: PowerLevel,
819 default: PowerLevel,
820) {
821 if value == default {
822 power_levels.events.remove(&event_type);
823 } else {
824 power_levels
825 .events
826 .insert(event_type, Int::new_saturating(value));
827 }
828}
829
830fn event_power_level(
833 power_levels: &RoomPowerLevels,
834 event_type: &TimelineEventType,
835 default: i64,
836) -> i64 {
837 power_levels
838 .events
839 .get(event_type)
840 .copied()
841 .map_or(default, Into::into)
842}