fractal/account_chooser_dialog/
mod.rs

1use adw::{prelude::*, subclass::prelude::*};
2use futures_channel::oneshot;
3use gtk::{glib, CompositeTemplate};
4use tracing::error;
5
6mod account_row;
7
8use self::account_row::AccountRow;
9use crate::session_list::{SessionInfo, SessionList};
10
11mod imp {
12    use std::cell::RefCell;
13
14    use glib::subclass::InitializingObject;
15
16    use super::*;
17
18    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
19    #[template(resource = "/org/gnome/Fractal/ui/account_chooser_dialog/mod.ui")]
20    #[properties(wrapper_type = super::AccountChooserDialog)]
21    pub struct AccountChooserDialog {
22        #[template_child]
23        pub accounts: TemplateChild<gtk::ListBox>,
24        /// The list of logged-in sessions.
25        #[property(get, set = Self::set_session_list, construct)]
26        pub session_list: glib::WeakRef<SessionList>,
27        pub sender: RefCell<Option<oneshot::Sender<Option<String>>>>,
28    }
29
30    #[glib::object_subclass]
31    impl ObjectSubclass for AccountChooserDialog {
32        const NAME: &'static str = "AccountChooserDialog";
33        type Type = super::AccountChooserDialog;
34        type ParentType = adw::Dialog;
35
36        fn class_init(klass: &mut Self::Class) {
37            Self::bind_template(klass);
38            Self::Type::bind_template_callbacks(klass);
39        }
40
41        fn instance_init(obj: &InitializingObject<Self>) {
42            obj.init_template();
43        }
44    }
45
46    #[glib::derived_properties]
47    impl ObjectImpl for AccountChooserDialog {}
48
49    impl WidgetImpl for AccountChooserDialog {}
50
51    impl AdwDialogImpl for AccountChooserDialog {
52        fn closed(&self) {
53            if let Some(sender) = self.sender.take() {
54                if sender.send(None).is_err() {
55                    error!("Could not send selected session");
56                }
57            }
58        }
59    }
60
61    impl AccountChooserDialog {
62        /// Set the list of logged-in sessions.
63        fn set_session_list(&self, session_list: &SessionList) {
64            self.accounts.bind_model(Some(session_list), |session| {
65                let row = AccountRow::new(session.downcast_ref().unwrap());
66                row.upcast()
67            });
68
69            self.session_list.set(Some(session_list));
70        }
71    }
72}
73
74glib::wrapper! {
75    /// A dialog to choose an account among the ones that are connected.
76    ///
77    /// Should be used by calling [`Self::choose_account()`].
78    pub struct AccountChooserDialog(ObjectSubclass<imp::AccountChooserDialog>)
79        @extends gtk::Widget, adw::Dialog, @implements gtk::Accessible;
80}
81
82#[gtk::template_callbacks]
83impl AccountChooserDialog {
84    pub fn new(session_list: &SessionList) -> Self {
85        glib::Object::builder()
86            .property("session-list", session_list)
87            .build()
88    }
89
90    /// Open this dialog to choose an account.
91    pub async fn choose_account(&self, parent: &impl IsA<gtk::Widget>) -> Option<String> {
92        let (sender, receiver) = oneshot::channel();
93        self.imp().sender.replace(Some(sender));
94
95        self.present(Some(parent));
96
97        receiver.await.ok().flatten()
98    }
99
100    /// Select the given row in the session list.
101    #[template_callback]
102    fn select_row(&self, row: &gtk::ListBoxRow) {
103        if let Some(sender) = self.imp().sender.take() {
104            let index = row
105                .index()
106                .try_into()
107                .expect("selected row should have an index");
108
109            let session_id = self
110                .session_list()
111                .and_then(|l| l.item(index))
112                .and_downcast::<SessionInfo>()
113                .map(|s| s.session_id());
114
115            if sender.send(session_id).is_err() {
116                error!("Could not send selected session");
117            }
118        }
119
120        self.close();
121    }
122}