create-dm-dialog: Port to glib::Properties

This commit is contained in:
Kévin Commaille 2023-12-17 22:23:24 +01:00
parent e4daec38d6
commit 7db1e462d4
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
4 changed files with 121 additions and 233 deletions

View File

@ -8,12 +8,13 @@ use crate::{
};
mod imp {
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default)]
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::DmUser)]
pub struct DmUser {
/// The direct chat with this user, if any.
#[property(get, set = Self::set_direct_chat, explicit_notify, nullable)]
pub direct_chat: glib::WeakRef<Room>,
}
@ -24,36 +25,30 @@ mod imp {
type ParentType = User;
}
#[glib::derived_properties]
impl ObjectImpl for DmUser {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Room>("direct-chat")
.read_only()
.build()]
});
PROPERTIES.as_ref()
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
match pspec.name() {
"direct-chat" => obj.direct_chat().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
spawn!(clone!(@weak obj => async move {
let direct_chat = obj.upcast_ref::<User>().direct_chat().await;
obj.set_direct_chat(direct_chat.as_ref());
obj.set_direct_chat(direct_chat);
}));
}
}
impl DmUser {
/// Set the direct chat with this user.
fn set_direct_chat(&self, direct_chat: Option<Room>) {
if self.direct_chat.upgrade() == direct_chat {
return;
}
self.direct_chat.set(direct_chat.as_ref());
self.obj().notify_direct_chat();
}
}
}
glib::wrapper! {
@ -77,19 +72,4 @@ impl DmUser {
obj.set_avatar_url(avatar_url.map(std::borrow::ToOwned::to_owned));
obj
}
/// Get the direct chat with this user, if any.
pub fn direct_chat(&self) -> Option<Room> {
self.imp().direct_chat.upgrade()
}
/// Set the direct chat with this user.
fn set_direct_chat(&self, direct_chat: Option<&Room>) {
if self.direct_chat().as_ref() == direct_chat {
return;
}
self.imp().direct_chat.set(direct_chat);
self.notify("direct-chat");
}
}

View File

@ -21,15 +21,21 @@ mod imp {
use std::cell::{Cell, RefCell};
use futures_util::future::AbortHandle;
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default)]
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::DmUserList)]
pub struct DmUserList {
pub list: RefCell<Vec<DmUser>>,
/// The current session.
#[property(get, construct_only)]
pub session: glib::WeakRef<Session>,
/// The state of the list.
#[property(get, builder(DmUserListState::default()))]
pub state: Cell<DmUserListState>,
/// The search term.
#[property(get, set = Self::set_search_term, explicit_notify, nullable)]
pub search_term: RefCell<Option<String>>,
pub abort_handle: RefCell<Option<AbortHandle>>,
}
@ -41,52 +47,18 @@ mod imp {
type Interfaces = (gio::ListModel,);
}
impl ObjectImpl for DmUserList {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Session>("session")
.construct_only()
.build(),
glib::ParamSpecString::builder("search-term")
.explicit_notify()
.build(),
glib::ParamSpecEnum::builder::<DmUserListState>("state")
.read_only()
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.session.set(value.get().unwrap()),
"search-term" => self.obj().set_search_term(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(),
"search-term" => obj.search_term().to_value(),
"state" => obj.state().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for DmUserList {}
impl ListModelImpl for DmUserList {
fn item_type(&self) -> glib::Type {
DmUser::static_type()
}
fn n_items(&self) -> u32 {
self.list.borrow().len() as u32
}
fn item(&self, position: u32) -> Option<glib::Object> {
self.list
.borrow()
@ -95,6 +67,26 @@ mod imp {
.and_upcast()
}
}
impl DmUserList {
/// Set the search term.
fn set_search_term(&self, search_term: Option<String>) {
let search_term = search_term.filter(|s| !s.is_empty());
if search_term.as_ref() == self.search_term.borrow().as_ref() {
return;
}
let obj = self.obj();
self.search_term.replace(search_term);
spawn!(clone!(@weak obj => async move {
obj.search_users().await;
}));
obj.notify_search_term();
}
}
}
glib::wrapper! {
@ -108,34 +100,6 @@ impl DmUserList {
glib::Object::builder().property("session", session).build()
}
/// The session this list refers to.
pub fn session(&self) -> Session {
self.imp().session.upgrade().unwrap()
}
/// Set the search term.
pub fn set_search_term(&self, search_term: Option<String>) {
let imp = self.imp();
let search_term = search_term.filter(|s| !s.is_empty());
if search_term.as_ref() == imp.search_term.borrow().as_ref() {
return;
}
imp.search_term.replace(search_term);
spawn!(clone!(@weak self as obj => async move {
obj.search_users().await;
}));
self.notify("search_term");
}
/// The search term.
fn search_term(&self) -> Option<String> {
self.imp().search_term.borrow().clone()
}
/// Set the state of the list.
fn set_state(&self, state: DmUserListState) {
let imp = self.imp();
@ -148,11 +112,6 @@ impl DmUserList {
self.notify("state");
}
/// The state of the list.
pub fn state(&self) -> DmUserListState {
self.imp().state.get()
}
fn set_list(&self, users: Vec<DmUser>) {
let added = users.len();
@ -166,7 +125,9 @@ impl DmUserList {
}
async fn search_users(&self) {
let session = self.session();
let Some(session) = self.session() else {
return;
};
let client = session.client();
let Some(search_term) = self.search_term() else {
self.set_state(DmUserListState::Initial);

View File

@ -6,13 +6,15 @@ mod imp {
use std::cell::RefCell;
use glib::subclass::InitializingObject;
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/create_dm_dialog/dm_user_row.ui")]
#[properties(wrapper_type = super::DmUserRow)]
pub struct DmUserRow {
/// The user displayed by this row.
#[property(get, set = Self::set_user, explicit_notify)]
pub user: RefCell<Option<DmUser>>,
}
@ -31,36 +33,27 @@ mod imp {
}
}
impl ObjectImpl for DmUserRow {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<DmUser>("user")
.explicit_notify()
.build()]
});
#[glib::derived_properties]
impl ObjectImpl for DmUserRow {}
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"user" => self.obj().set_user(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"user" => self.obj().user().to_value(),
_ => unimplemented!(),
}
}
}
impl WidgetImpl for DmUserRow {}
impl ListBoxRowImpl for DmUserRow {}
impl DmUserRow {
/// Set the user displayed by this row.
fn set_user(&self, user: Option<DmUser>) {
if self.user.borrow().clone() == user {
return;
}
self.user.replace(user);
self.obj().notify_user();
}
}
}
glib::wrapper! {
/// A row of the DM user list.
pub struct DmUserRow(ObjectSubclass<imp::DmUserRow>)
@extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
}
@ -69,22 +62,4 @@ impl DmUserRow {
pub fn new(user: &DmUser) -> Self {
glib::Object::builder().property("user", user).build()
}
/// The user displayed by this row.
pub fn user(&self) -> Option<DmUser> {
self.imp().user.borrow().clone()
}
/// Set the user displayed by this row.
pub fn set_user(&self, user: Option<DmUser>) {
let imp = self.imp();
let prev_user = self.user();
if prev_user == user {
return;
}
imp.user.replace(user);
self.notify("user");
}
}

View File

@ -17,14 +17,17 @@ use crate::{
};
mod imp {
use glib::{object::WeakRef, subclass::InitializingObject};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/create_dm_dialog/mod.ui")]
#[properties(wrapper_type = super::CreateDmDialog)]
pub struct CreateDmDialog {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, explicit_notify)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub list_box: TemplateChild<gtk::ListBox>,
#[template_child]
@ -59,40 +62,56 @@ mod imp {
}
}
impl ObjectImpl for CreateDmDialog {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("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 {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for CreateDmDialog {}
impl WidgetImpl for CreateDmDialog {}
impl WindowImpl for CreateDmDialog {}
impl AdwWindowImpl for CreateDmDialog {}
impl CreateDmDialog {
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
if self.session.upgrade() == session {
return;
}
let obj = self.obj();
if let Some(session) = &session {
let user_list = DmUserList::new(session);
// We don't need to disconnect this signal since the `DmUserList` will be
// disposed once unbound from the `gtk::ListBox`
user_list.connect_state_notify(clone!(@weak obj => move |model| {
obj.update_view(model);
}));
self.search_entry
.bind_property("text", &user_list, "search-term")
.sync_create()
.build();
self.list_box.bind_model(Some(&user_list), |user| {
DmUserRow::new(
user.downcast_ref::<DmUser>()
.expect("DmUserList must contain only `DmUser`"),
)
.upcast()
});
obj.update_view(&user_list);
} else {
self.list_box.unbind_model();
}
self.session.set(session.as_ref());
obj.notify_session();
}
}
}
glib::wrapper! {
/// Preference Window to display and update room details.
/// Dialog to create a new direct chat.
pub struct CreateDmDialog(ObjectSubclass<imp::CreateDmDialog>)
@extends gtk::Widget, gtk::Window, adw::Window, adw::Bin, @implements gtk::Accessible;
}
@ -106,53 +125,6 @@ impl CreateDmDialog {
.build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
let imp = self.imp();
if self.session() == session {
return;
}
if let Some(ref session) = session {
let user_list = DmUserList::new(session);
// We don't need to disconnect this signal since the `DmUserList` will be
// disposed once unbound from the `gtk::ListBox`
user_list.connect_notify_local(
Some("state"),
clone!(@weak self as obj => move |model, _| {
obj.update_view(model);
}),
);
imp.search_entry
.bind_property("text", &user_list, "search-term")
.sync_create()
.build();
imp.list_box.bind_model(Some(&user_list), |user| {
DmUserRow::new(
user.downcast_ref::<DmUser>()
.expect("DmUserList must contain only `DmUser`"),
)
.upcast()
});
self.update_view(&user_list);
} else {
imp.list_box.unbind_model();
}
imp.session.set(session.as_ref());
self.notify("session");
}
fn update_view(&self, model: &DmUserList) {
let visible_child_name = match model.state() {
DmUserListState::Initial => "no-search-page",