fractal/src/session/view/content/explore/servers_popover.rs

236 lines
6.9 KiB
Rust

use adw::subclass::prelude::*;
use gtk::{
glib,
glib::{clone, FromVariant},
prelude::*,
CompositeTemplate,
};
use ruma::ServerName;
use super::{ExploreServerRow, Server, ServerList};
use crate::session::model::Session;
mod imp {
use std::cell::RefCell;
use glib::{object::WeakRef, subclass::InitializingObject};
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/servers_popover.ui")]
pub struct ExploreServersPopover {
pub session: WeakRef<Session>,
pub server_list: RefCell<Option<ServerList>>,
#[template_child]
pub listbox: TemplateChild<gtk::ListBox>,
#[template_child]
pub server_entry: TemplateChild<gtk::Entry>,
}
#[glib::object_subclass]
impl ObjectSubclass for ExploreServersPopover {
const NAME: &'static str = "ContentExploreServersPopover";
type Type = super::ExploreServersPopover;
type ParentType = gtk::Popover;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
klass.install_action(
"explore-servers-popover.add-server",
None,
move |obj, _, _| {
obj.add_server();
},
);
klass.install_action(
"explore-servers-popover.remove-server",
Some("s"),
move |obj, _, variant| {
if let Some(variant) = variant.and_then(String::from_variant) {
obj.remove_server(&variant);
}
},
);
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ExploreServersPopover {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<ServerList>("server-list")
.read_only()
.build(),
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 {
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();
self.server_entry
.connect_changed(clone!(@weak obj => move |_| {
obj.update_add_server_state()
}));
self.server_entry
.connect_activate(clone!(@weak obj => move |_| {
obj.add_server()
}));
obj.update_add_server_state();
}
}
impl WidgetImpl for ExploreServersPopover {}
impl PopoverImpl for ExploreServersPopover {}
}
glib::wrapper! {
pub struct ExploreServersPopover(ObjectSubclass<imp::ExploreServersPopover>)
@extends gtk::Widget, gtk::Popover, @implements gtk::Accessible;
}
impl ExploreServersPopover {
pub fn new(session: &Session) -> Self {
glib::Object::builder().property("session", session).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>) {
if session == self.session() {
return;
}
self.imp().session.set(session.as_ref());
self.notify("session");
}
pub fn init(&self) {
let Some(session) = &self.session() else {
return;
};
let imp = self.imp();
let server_list = ServerList::new(session);
imp.listbox.bind_model(Some(&server_list), |obj| {
ExploreServerRow::new(obj.downcast_ref::<Server>().unwrap()).upcast()
});
// Select the first server by default.
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<ServerList> {
self.imp().server_list.borrow().clone()
}
pub fn selected_server(&self) -> Option<Server> {
self.imp()
.listbox
.selected_row()
.and_then(|row| row.downcast::<ExploreServerRow>().ok())
.and_then(|row| row.server().cloned())
}
pub fn connect_selected_server_changed<F: Fn(&Self, Option<Server>) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.imp()
.listbox
.connect_row_selected(clone!(@weak self as obj => move |_, row| {
f(&obj, row.and_then(|row| row.downcast_ref::<ExploreServerRow>()).and_then(|row| row.server().cloned()));
}))
}
fn can_add_server(&self) -> bool {
let server = self.imp().server_entry.text();
ServerName::parse(server.as_str()).is_ok()
// Don't allow duplicates
&& self
.server_list()
.filter(|l| !l.contains_matrix_server(&server))
.is_some()
}
fn update_add_server_state(&self) {
self.action_set_enabled("explore-servers-popover.add-server", self.can_add_server())
}
fn add_server(&self) {
if !self.can_add_server() {
return;
}
let Some(server_list) = self.server_list() else {
return;
};
let imp = self.imp();
let server = imp.server_entry.text();
imp.server_entry.set_text("");
server_list.add_custom_matrix_server(server.into());
imp.listbox.select_row(
imp.listbox
.row_at_index(server_list.n_items() as i32 - 1)
.as_ref(),
);
}
fn remove_server(&self, server: &str) {
let Some(server_list) = self.server_list() else {
return;
};
let imp = self.imp();
// If the selected server is gonna be removed, select the first one.
if self.selected_server().unwrap().server() == Some(server) {
imp.listbox.select_row(imp.listbox.row_at_index(0).as_ref());
}
server_list.remove_custom_matrix_server(server);
}
}