From f366b02137cb46908fd6e18f7b960f5dd7f6a453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 18 Dec 2023 18:45:17 +0100 Subject: [PATCH] explore: Port to glib::Properties macro --- src/session/view/content/explore/mod.rs | 135 +++++------ src/session/view/content/explore/mod.ui | 91 ++++---- .../view/content/explore/public_room.rs | 109 +++------ .../view/content/explore/public_room_list.rs | 135 +++++------ .../view/content/explore/public_room_row.rs | 212 +++++++----------- src/session/view/content/explore/server.rs | 84 +------ .../view/content/explore/server_list.rs | 88 +++----- .../view/content/explore/server_row.rs | 40 +--- .../view/content/explore/servers_popover.rs | 93 +++----- 9 files changed, 348 insertions(+), 639 deletions(-) diff --git a/src/session/view/content/explore/mod.rs b/src/session/view/content/explore/mod.rs index 7ad84f8a..37b70aa0 100644 --- a/src/session/view/content/explore/mod.rs +++ b/src/session/view/content/explore/mod.rs @@ -19,22 +19,20 @@ use crate::{components::Spinner, session::model::Session}; mod imp { use std::cell::RefCell; - use glib::{object::WeakRef, subclass::InitializingObject}; - use once_cell::sync::Lazy; + use glib::subclass::InitializingObject; use super::*; - #[derive(Debug, Default, CompositeTemplate)] + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/mod.ui")] + #[properties(wrapper_type = super::Explore)] pub struct Explore { - pub session: WeakRef, + /// The current session. + #[property(get, set = Self::set_session, explicit_notify)] + pub session: glib::WeakRef, #[template_child] pub stack: TemplateChild, #[template_child] - pub spinner: TemplateChild, - #[template_child] - pub empty_label: TemplateChild, - #[template_child] pub search_entry: TemplateChild, #[template_child] pub servers_button: TemplateChild, @@ -57,7 +55,10 @@ mod imp { PublicRoom::static_type(); PublicRoomList::static_type(); PublicRoomRow::static_type(); + Spinner::static_type(); + Self::bind_template(klass); + klass.set_accessible_role(gtk::AccessibleRole::Group); } @@ -66,35 +67,8 @@ mod imp { } } + #[glib::derived_properties] impl ObjectImpl for Explore { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("session") - .explicit_notify() - .build()] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - let obj = self.obj(); - - match pspec.name() { - "session" => obj.set_session(value.get().unwrap()), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "session" => obj.session().to_value(), - _ => unimplemented!(), - } - } - fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); @@ -116,7 +90,7 @@ mod imp { self.servers_popover.connect_selected_server_changed( clone!(@weak obj => move |_, server| { if let Some(server) = server { - obj.imp().servers_button.set_label(server.name()); + obj.imp().servers_button.set_label(&server.name()); obj.trigger_search(); } }), @@ -126,9 +100,46 @@ mod imp { impl WidgetImpl for Explore {} impl BinImpl for Explore {} + + impl Explore { + /// Set the current session. + fn set_session(&self, session: Option) { + if session == self.session.upgrade() { + return; + } + let obj = self.obj(); + + if let Some(session) = &session { + let public_room_list = PublicRoomList::new(session); + self.listview + .set_model(Some(>k::NoSelection::new(Some(public_room_list.clone())))); + + public_room_list.connect_notify_local( + Some("loading"), + clone!(@weak obj => move |_, _| { + obj.update_visible_child(); + }), + ); + + public_room_list.connect_notify_local( + Some("empty"), + clone!(@weak obj => move |_, _| { + obj.update_visible_child(); + }), + ); + + self.public_room_list.replace(Some(public_room_list)); + obj.update_visible_child(); + } + + self.session.set(session.as_ref()); + obj.notify_session(); + } + } } glib::wrapper! { + /// A view to explore rooms in the public directory of homeservers. pub struct Explore(ObjectSubclass) @extends gtk::Widget, adw::Bin, @implements gtk::Accessible; } @@ -138,18 +149,13 @@ impl Explore { glib::Object::builder().property("session", session).build() } - /// The current session. - pub fn session(&self) -> Option { - self.imp().session.upgrade() - } - pub fn init(&self) { let imp = self.imp(); imp.servers_popover.init(); if let Some(server) = imp.servers_popover.selected_server() { - imp.servers_button.set_label(server.name()); + imp.servers_button.set_label(&server.name()); } if let Some(public_room_list) = &*imp.public_room_list.borrow() { @@ -159,49 +165,16 @@ impl Explore { self.imp().search_entry.grab_focus(); } - /// Set the current session. - pub fn set_session(&self, session: Option) { - let imp = self.imp(); - - if session == self.session() { - return; - } - - if let Some(ref session) = session { - let public_room_list = PublicRoomList::new(session); - imp.listview - .set_model(Some(>k::NoSelection::new(Some(public_room_list.clone())))); - - public_room_list.connect_notify_local( - Some("loading"), - clone!(@weak self as obj => move |_, _| { - obj.set_visible_child(); - }), - ); - - public_room_list.connect_notify_local( - Some("empty"), - clone!(@weak self as obj => move |_, _| { - obj.set_visible_child(); - }), - ); - - imp.public_room_list.replace(Some(public_room_list)); - } - - imp.session.set(session.as_ref()); - self.notify("session"); - } - - fn set_visible_child(&self) { + /// Update the visible child according to the current state. + fn update_visible_child(&self) { let imp = self.imp(); if let Some(public_room_list) = &*imp.public_room_list.borrow() { if public_room_list.loading() { - imp.stack.set_visible_child(&*imp.spinner); + imp.stack.set_visible_child_name("loading"); } else if public_room_list.empty() { - imp.stack.set_visible_child(&*imp.empty_label); + imp.stack.set_visible_child_name("empty"); } else { - imp.stack.set_visible_child(&*imp.scrolled_window); + imp.stack.set_visible_child_name("results"); } } } diff --git a/src/session/view/content/explore/mod.ui b/src/session/view/content/explore/mod.ui index a7815bd4..90d45631 100644 --- a/src/session/view/content/explore/mod.ui +++ b/src/session/view/content/explore/mod.ui @@ -41,43 +41,56 @@ - - center - center - True - - - - - - center - center - True - No rooms matching the search were found - - - - - - True - never + + loading - + + center + center True - True - 750 - 550 + + + + + + + + empty + + + center + center + True + No rooms matching the search were found + + + + + + + + results + + + True + never - - 24 - 24 - - - + True + True + 750 + 550 + + + 24 + 24 + + + - ]]> + ]]> + + + + Room List + - - Room List - diff --git a/src/session/view/content/explore/public_room.rs b/src/session/view/content/explore/public_room.rs index 9e4c4947..4b466cf8 100644 --- a/src/session/view/content/explore/public_room.rs +++ b/src/session/view/content/explore/public_room.rs @@ -4,22 +4,34 @@ use matrix_sdk::ruma::directory::PublicRoomsChunk; use crate::session::model::{AvatarData, AvatarImage, AvatarUriSource, Room, RoomList}; mod imp { - use std::cell::{Cell, RefCell}; + use std::cell::{Cell, OnceCell, RefCell}; use glib::signal::SignalHandlerId; - use once_cell::{sync::Lazy, unsync::OnceCell}; use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::PublicRoom)] pub struct PublicRoom { + /// The list of rooms in this session. + #[property(get, construct_only)] pub room_list: OnceCell, /// The server that returned the room. + #[property(get, construct_only)] pub server: OnceCell, pub matrix_public_room: OnceCell, + /// The [`AvatarData`] of this room. + #[property(get)] pub avatar_data: OnceCell, - pub room: OnceCell, - pub is_pending: Cell, + /// The `Room` object for this room, if the user is already a member of + /// this room. + #[property(get)] + pub room: RefCell>, + /// Whether the room is pending. + /// + /// A room is pending when the user clicked to join it. + #[property(get)] + pub pending: Cell, pub room_handler: RefCell>, } @@ -29,52 +41,8 @@ mod imp { type Type = super::PublicRoom; } + #[glib::derived_properties] impl ObjectImpl for PublicRoom { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpecObject::builder::("room-list") - .construct_only() - .build(), - glib::ParamSpecString::builder("server") - .construct_only() - .build(), - glib::ParamSpecObject::builder::("room") - .read_only() - .build(), - glib::ParamSpecBoolean::builder("pending") - .read_only() - .build(), - glib::ParamSpecObject::builder::("avatar-data") - .read_only() - .build(), - ] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "room-list" => self.room_list.set(value.get().unwrap()).unwrap(), - "server" => self.server.set(value.get().unwrap()).unwrap(), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "room-list" => obj.room_list().to_value(), - "server" => obj.server().to_value(), - "avatar-data" => obj.avatar_data().to_value(), - "room" => obj.room().to_value(), - "pending" => obj.is_pending().to_value(), - _ => unimplemented!(), - } - } - fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); @@ -107,6 +75,7 @@ mod imp { } glib::wrapper! { + /// A room in a homeserver's public directory. pub struct PublicRoom(ObjectSubclass); } @@ -118,48 +87,20 @@ impl PublicRoom { .build() } - /// The list of rooms in this session. - pub fn room_list(&self) -> &RoomList { - self.imp().room_list.get().unwrap() - } - - /// The server that returned the room. - pub fn server(&self) -> &str { - self.imp().server.get().unwrap() - } - - /// The [`AvatarData`] of this room. - pub fn avatar_data(&self) -> &AvatarData { - self.imp().avatar_data.get().unwrap() - } - - /// The `Room` object for this room, if the user is already a member of this - /// room. - pub fn room(&self) -> Option<&Room> { - self.imp().room.get() - } - /// Set the `Room` object for this room. fn set_room(&self, room: Room) { - self.imp().room.set(room).unwrap(); - self.notify("room"); + self.imp().room.replace(Some(room)); + self.notify_room(); } /// Set whether this room is pending. - fn set_pending(&self, is_pending: bool) { - if self.is_pending() == is_pending { + fn set_pending(&self, pending: bool) { + if self.pending() == pending { return; } - self.imp().is_pending.set(is_pending); - self.notify("pending"); - } - - /// Whether the room is pending. - /// - /// A room is pending when the user clicked to join it. - pub fn is_pending(&self) -> bool { - self.imp().is_pending.get() + self.imp().pending.set(pending); + self.notify_pending(); } pub fn set_matrix_public_room(&self, room: PublicRoomsChunk) { diff --git a/src/session/view/content/explore/public_room_list.rs b/src/session/view/content/explore/public_room_list.rs index cc5089e3..4c72a9d5 100644 --- a/src/session/view/content/explore/public_room_list.rs +++ b/src/session/view/content/explore/public_room_list.rs @@ -14,14 +14,15 @@ use super::{PublicRoom, Server}; use crate::{session::model::Session, spawn, spawn_tokio}; mod imp { - use std::cell::{Cell, RefCell}; - - use glib::object::WeakRef; - use once_cell::sync::Lazy; + use std::{ + cell::{Cell, RefCell}, + marker::PhantomData, + }; use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::PublicRoomList)] pub struct PublicRoomList { pub list: RefCell>, pub search_term: RefCell>, @@ -30,7 +31,18 @@ mod imp { pub next_batch: RefCell>, pub request_sent: Cell, pub total_room_count_estimate: Cell>, - pub session: WeakRef, + /// The current session. + #[property(get, construct_only)] + pub session: glib::WeakRef, + /// Whether the list is loading. + #[property(get = Self::loading)] + pub loading: PhantomData, + /// Whether the list is empty. + #[property(get = Self::empty)] + pub empty: PhantomData, + /// Whether all results for the current search were loaded. + #[property(get = Self::complete)] + pub complete: PhantomData, } #[glib::object_subclass] @@ -40,53 +52,18 @@ mod imp { type Interfaces = (gio::ListModel,); } - impl ObjectImpl for PublicRoomList { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpecObject::builder::("session") - .construct_only() - .build(), - glib::ParamSpecBoolean::builder("loading") - .read_only() - .build(), - glib::ParamSpecBoolean::builder("empty").read_only().build(), - glib::ParamSpecBoolean::builder("complete") - .read_only() - .build(), - ] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "session" => self.obj().set_session(value.get().unwrap()), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "session" => obj.session().to_value(), - "loading" => obj.loading().to_value(), - "empty" => obj.empty().to_value(), - "complete" => obj.complete().to_value(), - _ => unimplemented!(), - } - } - } + #[glib::derived_properties] + impl ObjectImpl for PublicRoomList {} impl ListModelImpl for PublicRoomList { fn item_type(&self) -> glib::Type { PublicRoom::static_type() } + fn n_items(&self) -> u32 { self.list.borrow().len() as u32 } + fn item(&self, position: u32) -> Option { self.list .borrow() @@ -95,9 +72,27 @@ mod imp { .cloned() } } + + impl PublicRoomList { + /// Whether the list is loading. + fn loading(&self) -> bool { + self.request_sent.get() && self.list.borrow().is_empty() + } + + /// Whether the list is empty. + fn empty(&self) -> bool { + !self.request_sent.get() && self.list.borrow().is_empty() + } + + /// Whether all results for the current search were loaded. + fn complete(&self) -> bool { + self.next_batch.borrow().is_none() + } + } } glib::wrapper! { + /// A list of rooms in a homeserver's public directory. pub struct PublicRoomList(ObjectSubclass) @implements gio::ListModel; } @@ -107,46 +102,18 @@ impl PublicRoomList { glib::Object::builder().property("session", session).build() } - /// The current session. - pub fn session(&self) -> Option { - self.imp().session.upgrade() - } - - /// Set the current session. - fn set_session(&self, session: Option) { - if session == self.session() { - return; - } - - self.imp().session.set(session.as_ref()); - self.notify("session"); - } - - /// Whether the list is loading. - pub fn loading(&self) -> bool { - self.request_sent() && self.imp().list.borrow().is_empty() - } - - /// Whether the list is empty. - pub fn empty(&self) -> bool { - !self.request_sent() && self.imp().list.borrow().is_empty() - } - - /// Whether all results for the current search were loaded. - pub fn complete(&self) -> bool { - self.imp().next_batch.borrow().is_none() - } - + /// Whether a request is in progress. fn request_sent(&self) -> bool { self.imp().request_sent.get() } + /// Set whether a request is in progress. fn set_request_sent(&self, request_sent: bool) { self.imp().request_sent.set(request_sent); - self.notify("loading"); - self.notify("empty"); - self.notify("complete"); + self.notify_loading(); + self.notify_empty(); + self.notify_complete(); } pub fn init(&self) { @@ -156,21 +123,22 @@ impl PublicRoomList { } } + /// Search the given term on the given server. pub fn search(&self, search_term: Option, server: Server) { let imp = self.imp(); let network = Some(server.network()); let server = server.server(); - if imp.search_term.borrow().as_ref() == search_term.as_ref() - && imp.server.borrow().as_deref() == server - && imp.network.borrow().as_deref() == network + if *imp.search_term.borrow() == search_term + && *imp.server.borrow() == server + && *imp.network.borrow() == network { return; } imp.search_term.replace(search_term); - imp.server.replace(server.map(ToOwned::to_owned)); - imp.network.replace(network.map(ToOwned::to_owned)); + imp.server.replace(server); + imp.network.replace(network); self.load_public_rooms(true); } @@ -223,6 +191,7 @@ impl PublicRoomList { self.set_request_sent(false); } + /// Whether this is the response for the latest request that was sent. fn is_valid_response( &self, search_term: Option, diff --git a/src/session/view/content/explore/public_room_row.rs b/src/session/view/content/explore/public_room_row.rs index 786608da..66227208 100644 --- a/src/session/view/content/explore/public_room_row.rs +++ b/src/session/view/content/explore/public_room_row.rs @@ -7,21 +7,25 @@ use super::PublicRoom; use crate::{ components::{Avatar, Spinner, SpinnerButton}, prelude::*, - spawn, toast, Window, + spawn, toast, + utils::BoundObject, + Window, }; mod imp { use std::cell::RefCell; - use glib::{signal::SignalHandlerId, subclass::InitializingObject}; - use once_cell::sync::Lazy; + use glib::subclass::InitializingObject; use super::*; - #[derive(Debug, Default, CompositeTemplate)] + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/public_room_row.ui")] + #[properties(wrapper_type = super::PublicRoomRow)] pub struct PublicRoomRow { - pub public_room: RefCell>, + /// The public room displayed by this row. + #[property(get, set= Self::set_public_room, explicit_notify)] + pub public_room: BoundObject, #[template_child] pub avatar: TemplateChild, #[template_child] @@ -35,8 +39,6 @@ mod imp { #[template_child] pub button: TemplateChild, pub original_child: RefCell>, - pub pending_handler: RefCell>, - pub room_handler: RefCell>, } #[glib::object_subclass] @@ -54,30 +56,8 @@ mod imp { } } + #[glib::derived_properties] impl ObjectImpl for PublicRoomRow { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("public-room") - .explicit_notify() - .build()] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "public-room" => self.obj().set_public_room(value.get().unwrap()), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "public-room" => self.obj().public_room().to_value(), - _ => unimplemented!(), - } - } fn constructed(&self) { self.parent_constructed(); self.button @@ -85,24 +65,81 @@ mod imp { imp.obj().join_or_view(); })); } - - fn dispose(&self) { - if let Some(ref old_public_room) = self.obj().public_room() { - if let Some(handler) = self.pending_handler.take() { - old_public_room.disconnect(handler); - } - if let Some(handler_id) = self.room_handler.take() { - old_public_room.disconnect(handler_id); - } - } - } } impl WidgetImpl for PublicRoomRow {} impl BinImpl for PublicRoomRow {} + + impl PublicRoomRow { + /// Set the public room displayed by this row. + fn set_public_room(&self, public_room: Option) { + if self.public_room.obj() == public_room { + return; + } + let obj = self.obj(); + + self.public_room.disconnect_signals(); + + if let Some(public_room) = public_room { + if let Some(child) = self.original_child.take() { + obj.set_child(Some(&child)); + } + if let Some(matrix_public_room) = public_room.matrix_public_room() { + self.avatar + .set_data(Some(public_room.avatar_data().clone())); + + let display_name = matrix_public_room + .name + .as_deref() + // FIXME: display some other identification for this room + .unwrap_or("Room without name"); + self.display_name.set_text(display_name); + + if let Some(topic) = &matrix_public_room.topic { + self.description.set_text(topic); + } + self.description + .set_visible(matrix_public_room.topic.is_some()); + + if let Some(alias) = &matrix_public_room.canonical_alias { + self.alias.set_text(alias.as_str()); + } + self.alias + .set_visible(matrix_public_room.canonical_alias.is_some()); + + self.members_count + .set_text(&matrix_public_room.num_joined_members.to_string()); + + let pending_handler = public_room.connect_pending_notify( + clone!(@weak obj => move |public_room| { + obj.update_button(public_room); + }), + ); + + let room_handler = + public_room.connect_room_notify(clone!(@weak obj => move |public_room| { + obj.update_button(public_room); + })); + + obj.update_button(&public_room); + self.public_room + .set(public_room, vec![pending_handler, room_handler]); + } else if self.original_child.borrow().is_none() { + let spinner = Spinner::default(); + spinner.set_margin_top(12); + spinner.set_margin_bottom(12); + self.original_child.replace(obj.child()); + obj.set_child(Some(&spinner)); + } + } + + obj.notify_public_room(); + } + } } glib::wrapper! { + /// A row representing a room for a homeserver's public directory. pub struct PublicRoomRow(ObjectSubclass) @extends gtk::Widget, adw::Bin, @implements gtk::Accessible; } @@ -112,97 +149,6 @@ impl PublicRoomRow { glib::Object::new() } - /// The public room displayed by this row. - pub fn public_room(&self) -> Option { - self.imp().public_room.borrow().clone() - } - - /// Set the public room displayed by this row. - pub fn set_public_room(&self, public_room: Option) { - let imp = self.imp(); - let old_public_room = self.public_room(); - - if old_public_room == public_room { - return; - } - - if let Some(ref old_public_room) = old_public_room { - if let Some(handler) = imp.room_handler.take() { - old_public_room.disconnect(handler); - } - if let Some(handler) = imp.pending_handler.take() { - old_public_room.disconnect(handler); - } - } - - if let Some(ref public_room) = public_room { - if let Some(child) = imp.original_child.take() { - self.set_child(Some(&child)); - } - if let Some(matrix_public_room) = public_room.matrix_public_room() { - imp.avatar.set_data(Some(public_room.avatar_data().clone())); - - let display_name = matrix_public_room - .name - .as_deref() - .map(AsRef::as_ref) - // FIXME: display some other identification for this room - .unwrap_or("Room without name"); - imp.display_name.set_text(display_name); - - let has_topic = if let Some(ref topic) = matrix_public_room.topic { - imp.description.set_text(topic); - true - } else { - false - }; - - imp.description.set_visible(has_topic); - - let has_alias = if let Some(ref alias) = matrix_public_room.canonical_alias { - imp.alias.set_text(alias.as_str()); - true - } else { - false - }; - - imp.alias.set_visible(has_alias); - imp.members_count - .set_text(&matrix_public_room.num_joined_members.to_string()); - - let pending_handler = public_room.connect_notify_local( - Some("pending"), - clone!(@weak self as obj => move |public_room, _| { - obj.update_button(public_room); - }), - ); - - imp.pending_handler.replace(Some(pending_handler)); - - let room_handler = public_room.connect_notify_local( - Some("room"), - clone!(@weak self as obj => move |public_room, _| { - obj.update_button(public_room); - }), - ); - - imp.room_handler.replace(Some(room_handler)); - - self.update_button(public_room); - } else if imp.original_child.borrow().is_none() { - let spinner = Spinner::default(); - spinner.set_margin_top(12); - spinner.set_margin_bottom(12); - imp.original_child.replace(self.child()); - self.set_child(Some(&spinner)); - } - } - imp.avatar - .set_data(public_room.clone().map(|room| room.avatar_data().clone())); - imp.public_room.replace(public_room); - self.notify("public-room"); - } - fn update_button(&self, public_room: &PublicRoom) { let button = &self.imp().button; if public_room.room().is_some() { @@ -212,7 +158,7 @@ impl PublicRoomRow { button.set_label(gettext("Join")); } - button.set_loading(public_room.is_pending()); + button.set_loading(public_room.pending()); } /// Join or view the public room. diff --git a/src/session/view/content/explore/server.rs b/src/session/view/content/explore/server.rs index ef2c05d5..bc64a2b1 100644 --- a/src/session/view/content/explore/server.rs +++ b/src/session/view/content/explore/server.rs @@ -2,22 +2,24 @@ use gtk::{glib, prelude::*, subclass::prelude::*}; use ruma::thirdparty::ProtocolInstance; mod imp { - use once_cell::{sync::Lazy, unsync::OnceCell}; + use std::cell::{OnceCell, RefCell}; use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::Server)] pub struct Server { /// The name of the server that is displayed in the list. + #[property(get, construct_only)] pub name: OnceCell, - /// The ID of the network that is used during search. + #[property(get, construct_only)] pub network: OnceCell, - /// The server name that is used during search. - pub server: OnceCell, - + #[property(get, construct_only)] + pub server: RefCell>, /// Whether this server can be deleted from the list. + #[property(get, construct_only)] pub deletable: OnceCell, } @@ -27,54 +29,8 @@ mod imp { type Type = super::Server; } - impl ObjectImpl for Server { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpecString::builder("name") - .construct_only() - .build(), - glib::ParamSpecString::builder("network") - .construct_only() - .build(), - glib::ParamSpecString::builder("server") - .construct_only() - .build(), - glib::ParamSpecBoolean::builder("deletable") - .construct_only() - .build(), - ] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "name" => self.name.set(value.get().unwrap()).unwrap(), - "network" => self.network.set(value.get().unwrap()).unwrap(), - "server" => { - if let Some(server) = value.get().unwrap() { - self.server.set(server).unwrap(); - } - } - "deletable" => self.deletable.set(value.get().unwrap()).unwrap(), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "name" => obj.name().to_value(), - "network" => obj.network().to_value(), - "server" => obj.server().to_value(), - "deletable" => obj.deletable().to_value(), - _ => unimplemented!(), - } - } - } + #[glib::derived_properties] + impl ObjectImpl for Server {} } glib::wrapper! { @@ -107,24 +63,4 @@ impl Server { .property("deletable", true) .build() } - - /// The name of the server. - pub fn name(&self) -> &str { - self.imp().name.get().unwrap() - } - - /// The ID of the network that is used during search. - pub fn network(&self) -> &str { - self.imp().network.get().unwrap() - } - - /// The server name that is used during search. - pub fn server(&self) -> Option<&str> { - self.imp().server.get().map(String::as_ref) - } - - /// Whether this server can be deleted from the list. - pub fn deletable(&self) -> bool { - *self.imp().deletable.get().unwrap() - } } diff --git a/src/session/view/content/explore/server_list.rs b/src/session/view/content/explore/server_list.rs index faa819ab..c8935fbb 100644 --- a/src/session/view/content/explore/server_list.rs +++ b/src/session/view/content/explore/server_list.rs @@ -8,14 +8,14 @@ use crate::{prelude::*, session::model::Session, spawn, spawn_tokio}; mod imp { use std::cell::RefCell; - use glib::object::WeakRef; - use once_cell::sync::Lazy; - use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::ServerList)] pub struct ServerList { - pub session: WeakRef, + /// The current session. + #[property(get, set = Self::set_session, construct_only)] + pub session: glib::WeakRef, pub list: RefCell>, } @@ -26,31 +26,8 @@ mod imp { type Interfaces = (gio::ListModel,); } - impl ObjectImpl for ServerList { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("session") - .construct_only() - .build()] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "session" => self.obj().set_session(value.get().unwrap()), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "session" => self.obj().session().to_value(), - _ => unimplemented!(), - } - } - } + #[glib::derived_properties] + impl ObjectImpl for ServerList {} impl ListModelImpl for ServerList { fn item_type(&self) -> glib::Type { @@ -69,6 +46,25 @@ mod imp { .cloned() } } + + impl ServerList { + /// Set the current session. + fn set_session(&self, session: Session) { + let obj = self.obj(); + + self.session.set(Some(&session)); + + let user_id = session.user_id(); + self.list.replace(vec![Server::with_default_server( + user_id.server_name().as_str(), + )]); + obj.items_changed(0, 0, 1); + + spawn!(clone!(@weak obj => async move { + obj.load_servers().await; + })); + } + } } glib::wrapper! { @@ -82,28 +78,7 @@ impl ServerList { glib::Object::builder().property("session", session).build() } - /// Set the current session. - fn set_session(&self, session: Session) { - let imp = self.imp(); - - imp.session.set(Some(&session)); - - let user_id = session.user_id(); - imp.list.replace(vec![Server::with_default_server( - user_id.server_name().as_str(), - )]); - self.items_changed(0, 0, 1); - - spawn!(clone!(@weak self as obj => async move { - obj.load_servers().await; - })); - } - - /// The current session. - pub fn session(&self) -> Option { - self.imp().session.upgrade() - } - + /// Load all the servers. async fn load_servers(&self) { self.load_protocols().await; @@ -118,6 +93,7 @@ impl ServerList { self.items_changed(1, 0, (added - 1) as u32); } + /// Load the protocols of the session's homeserver. async fn load_protocols(&self) { let client = self.session().unwrap().client(); @@ -132,6 +108,7 @@ impl ServerList { } } + /// Add the given protocol to this list. fn add_protocols(&self, protocols: get_protocols::v3::Response) { let protocols_servers = protocols @@ -146,13 +123,15 @@ impl ServerList { self.imp().list.borrow_mut().extend(protocols_servers) } + /// Whether this list contains the given Matrix server. pub fn contains_matrix_server(&self, server: &str) -> bool { let list = &self.imp().list.borrow(); // The user's matrix server is a special case that doesn't have a "server", so // use its name. - list[0].name() == server || list.iter().any(|s| s.server() == Some(server)) + list[0].name() == server || list.iter().any(|s| s.server().as_deref() == Some(server)) } + /// Add a custom Matrix server. pub fn add_custom_matrix_server(&self, server_name: String) { let server = Server::with_custom_matrix_server(&server_name); let pos = { @@ -172,12 +151,13 @@ impl ServerList { self.items_changed(pos as u32, 0, 1); } + /// Remove a custom Matrix server. pub fn remove_custom_matrix_server(&self, server_name: &str) { let pos = { let mut list = self.imp().list.borrow_mut(); let pos = list .iter() - .position(|s| s.deletable() && s.server() == Some(server_name)); + .position(|s| s.deletable() && s.server().as_deref() == Some(server_name)); if let Some(pos) = pos { list.remove(pos); diff --git a/src/session/view/content/explore/server_row.rs b/src/session/view/content/explore/server_row.rs index 08eb0c49..1064df74 100644 --- a/src/session/view/content/explore/server_row.rs +++ b/src/session/view/content/explore/server_row.rs @@ -3,16 +3,19 @@ use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate}; use super::server::Server; mod imp { + use std::cell::RefCell; + use glib::subclass::InitializingObject; - use once_cell::{sync::Lazy, unsync::OnceCell}; use super::*; - #[derive(Debug, Default, CompositeTemplate)] + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/server_row.ui")] + #[properties(wrapper_type = super::ExploreServerRow)] pub struct ExploreServerRow { /// The server displayed by this row. - pub server: OnceCell, + #[property(get, construct_only)] + pub server: RefCell>, #[template_child] pub remove_button: TemplateChild, } @@ -32,31 +35,8 @@ mod imp { } } + #[glib::derived_properties] impl ObjectImpl for ExploreServerRow { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("server") - .construct_only() - .build()] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "server" => self.server.set(value.get().unwrap()).unwrap(), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "server" => self.obj().server().to_value(), - _ => unimplemented!(), - } - } - fn constructed(&self) { self.parent_constructed(); @@ -73,6 +53,7 @@ mod imp { } glib::wrapper! { + /// A row representing a server to explore. pub struct ExploreServerRow(ObjectSubclass) @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible; } @@ -81,9 +62,4 @@ impl ExploreServerRow { pub fn new(server: &Server) -> Self { glib::Object::builder().property("server", server).build() } - - /// The server displayed by this row. - pub fn server(&self) -> Option<&Server> { - self.imp().server.get() - } } diff --git a/src/session/view/content/explore/servers_popover.rs b/src/session/view/content/explore/servers_popover.rs index 5ddab30f..fa7962bf 100644 --- a/src/session/view/content/explore/servers_popover.rs +++ b/src/session/view/content/explore/servers_popover.rs @@ -13,15 +13,19 @@ use crate::session::model::Session; mod imp { use std::cell::RefCell; - use glib::{object::WeakRef, subclass::InitializingObject}; - use once_cell::sync::Lazy; + use glib::subclass::InitializingObject; use super::*; - #[derive(Debug, Default, CompositeTemplate)] + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/servers_popover.ui")] + #[properties(wrapper_type = super::ExploreServersPopover)] pub struct ExploreServersPopover { - pub session: WeakRef, + /// The current session. + #[property(get, set = Self::set_session, explicit_notify)] + pub session: glib::WeakRef, + /// The server list. + #[property(get)] pub server_list: RefCell>, #[template_child] pub listbox: TemplateChild, @@ -61,39 +65,8 @@ mod imp { } } + #[glib::derived_properties] impl ObjectImpl for ExploreServersPopover { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpecObject::builder::("server-list") - .read_only() - .build(), - glib::ParamSpecObject::builder::("session") - .explicit_notify() - .build(), - ] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "session" => self.obj().set_session(value.get().unwrap()), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "session" => obj.session().to_value(), - "server-list" => obj.server_list().to_value(), - _ => unimplemented!(), - } - } - fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); @@ -113,9 +86,22 @@ mod imp { impl WidgetImpl for ExploreServersPopover {} impl PopoverImpl for ExploreServersPopover {} + + impl ExploreServersPopover { + /// Set the current session. + fn set_session(&self, session: Option) { + if session == self.session.upgrade() { + return; + } + + self.session.set(session.as_ref()); + self.obj().notify_session(); + } + } } glib::wrapper! { + /// A popover that lists the servers that can be explored. pub struct ExploreServersPopover(ObjectSubclass) @extends gtk::Widget, gtk::Popover, @implements gtk::Accessible; } @@ -125,21 +111,7 @@ impl ExploreServersPopover { glib::Object::builder().property("session", session).build() } - /// The current session. - pub fn session(&self) -> Option { - self.imp().session.upgrade() - } - - /// Set the current session. - pub fn set_session(&self, session: Option) { - if session == self.session() { - return; - } - - self.imp().session.set(session.as_ref()); - self.notify("session"); - } - + /// Initialize the list of servers. pub fn init(&self) { let Some(session) = &self.session() else { return; @@ -156,20 +128,16 @@ impl ExploreServersPopover { imp.listbox.select_row(imp.listbox.row_at_index(0).as_ref()); imp.server_list.replace(Some(server_list)); - self.notify("server-list"); - } - - /// The server list. - pub fn server_list(&self) -> Option { - self.imp().server_list.borrow().clone() + self.notify_server_list(); } + /// The server that is currently selected, if any. pub fn selected_server(&self) -> Option { self.imp() .listbox .selected_row() .and_downcast::() - .and_then(|row| row.server().cloned()) + .and_then(|row| row.server()) } pub fn connect_selected_server_changed) + 'static>( @@ -179,10 +147,11 @@ impl ExploreServersPopover { self.imp() .listbox .connect_row_selected(clone!(@weak self as obj => move |_, row| { - f(&obj, row.and_then(|row| row.downcast_ref::()).and_then(|row| row.server().cloned())); + f(&obj, row.and_then(|row| row.downcast_ref::()).and_then(|row| row.server())); })) } + /// Whether the server currently in the text entry can be added. fn can_add_server(&self) -> bool { let server = self.imp().server_entry.text(); ServerName::parse(server.as_str()).is_ok() @@ -193,10 +162,13 @@ impl ExploreServersPopover { .is_some() } + /// Update the state of the action to add a server according to the current + /// state. fn update_add_server_state(&self) { self.action_set_enabled("explore-servers-popover.add-server", self.can_add_server()) } + /// Add the server currently in the text entry. fn add_server(&self) { if !self.can_add_server() { return; @@ -218,6 +190,7 @@ impl ExploreServersPopover { ); } + /// Remove the given server. fn remove_server(&self, server: &str) { let Some(server_list) = self.server_list() else { return; @@ -226,7 +199,7 @@ impl ExploreServersPopover { let imp = self.imp(); // If the selected server is gonna be removed, select the first one. - if self.selected_server().unwrap().server() == Some(server) { + if self.selected_server().and_then(|s| s.server()).as_deref() == Some(server) { imp.listbox.select_row(imp.listbox.row_at_index(0).as_ref()); }