fractal/session/view/content/explore/
servers_popover.rs1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{CompositeTemplate, glib};
3use ruma::ServerName;
4use tracing::error;
5
6use super::{ExploreServer, ExploreServerList, ExploreServerRow};
7use crate::session::model::Session;
8
9mod imp {
10 use std::marker::PhantomData;
11
12 use glib::subclass::InitializingObject;
13
14 use super::*;
15
16 #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
17 #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/servers_popover.ui")]
18 #[properties(wrapper_type = super::ExploreServersPopover)]
19 pub struct ExploreServersPopover {
20 #[template_child]
21 pub(super) listbox: TemplateChild<gtk::ListBox>,
22 #[template_child]
23 server_entry: TemplateChild<gtk::Entry>,
24 #[property(get, set = Self::set_session, explicit_notify)]
26 session: glib::WeakRef<Session>,
27 #[property(get)]
29 server_list: ExploreServerList,
30 #[property(get = Self::selected_server)]
32 selected_server: PhantomData<Option<ExploreServer>>,
33 }
34
35 #[glib::object_subclass]
36 impl ObjectSubclass for ExploreServersPopover {
37 const NAME: &'static str = "ExploreServersPopover";
38 type Type = super::ExploreServersPopover;
39 type ParentType = gtk::Popover;
40
41 fn class_init(klass: &mut Self::Class) {
42 Self::bind_template(klass);
43 Self::bind_template_callbacks(klass);
44
45 klass.install_action("explore-servers-popover.add-server", None, |obj, _, _| {
46 obj.imp().add_server();
47 });
48
49 klass.install_action(
50 "explore-servers-popover.remove-server",
51 Some(&String::static_variant_type()),
52 |obj, _, variant| {
53 let Some(value) = variant.and_then(String::from_variant) else {
54 error!("Could not remove server without a server name");
55 return;
56 };
57 let Ok(server_name) = ServerName::parse(&value) else {
58 error!("Could not remove server with an invalid server name");
59 return;
60 };
61
62 obj.imp().remove_server(&server_name);
63 },
64 );
65 }
66
67 fn instance_init(obj: &InitializingObject<Self>) {
68 obj.init_template();
69 }
70 }
71
72 #[glib::derived_properties]
73 impl ObjectImpl for ExploreServersPopover {
74 fn constructed(&self) {
75 self.parent_constructed();
76
77 self.listbox.bind_model(Some(&self.server_list), |obj| {
78 let Some(server) = obj.downcast_ref::<ExploreServer>() else {
79 error!("explore servers GtkListBox did not receive an ExploreServer");
80 return adw::Bin::new().upcast();
81 };
82
83 ExploreServerRow::new(server).upcast()
84 });
85
86 self.update_add_server_state();
87 }
88 }
89
90 impl WidgetImpl for ExploreServersPopover {}
91 impl PopoverImpl for ExploreServersPopover {}
92
93 #[gtk::template_callbacks]
94 impl ExploreServersPopover {
95 fn set_session(&self, session: &Session) {
97 if self.session.upgrade().as_ref() == Some(session) {
98 return;
99 }
100
101 self.session.set(Some(session));
102 self.server_list.set_session(session);
103
104 self.listbox
106 .select_row(self.listbox.row_at_index(0).as_ref());
107
108 self.obj().notify_session();
109 }
110
111 #[template_callback]
113 fn selected_server_changed(&self) {
114 self.obj().notify_selected_server();
115 }
116
117 #[template_callback]
119 fn server_activated(&self) {
120 self.obj().popdown();
121 }
122
123 fn selected_server(&self) -> Option<ExploreServer> {
125 self.listbox
126 .selected_row()
127 .and_downcast_ref()
128 .and_then(ExploreServerRow::server)
129 }
130
131 fn can_add_server(&self) -> bool {
133 let Ok(server_name) = ServerName::parse(self.server_entry.text()) else {
134 return false;
135 };
136
137 !self.server_list.contains_matrix_server(&server_name)
139 }
140
141 #[template_callback]
144 fn update_add_server_state(&self) {
145 self.obj()
146 .action_set_enabled("explore-servers-popover.add-server", self.can_add_server());
147 }
148
149 #[template_callback]
151 fn add_server(&self) {
152 if !self.can_add_server() {
153 return;
154 }
155
156 let Ok(server_name) = ServerName::parse(self.server_entry.text()) else {
157 return;
158 };
159 self.server_entry.set_text("");
160
161 self.server_list.add_custom_server(server_name);
162
163 let index = i32::try_from(self.server_list.n_items()).unwrap_or(i32::MAX);
165 self.listbox
166 .select_row(self.listbox.row_at_index(index - 1).as_ref());
167 }
168
169 fn remove_server(&self, server_name: &ServerName) {
171 if self
173 .selected_server()
174 .as_ref()
175 .and_then(|server| server.server())
176 .is_some_and(|s| s == server_name)
177 {
178 self.listbox
179 .select_row(self.listbox.row_at_index(0).as_ref());
180 }
181
182 self.server_list.remove_custom_server(server_name);
183 }
184 }
185}
186
187glib::wrapper! {
188 pub struct ExploreServersPopover(ObjectSubclass<imp::ExploreServersPopover>)
190 @extends gtk::Widget, gtk::Popover, @implements gtk::Accessible;
191}
192
193impl ExploreServersPopover {
194 pub fn new(session: &Session) -> Self {
195 glib::Object::builder().property("session", session).build()
196 }
197}