[fractal] use Gaction to change avatar in room/account settings



commit cf68e36c75e50436b0a306695d758326565149e2
Author: Julian Sparber <julian sparber net>
Date:   Fri Dec 28 15:30:55 2018 +0100

    use Gaction to change avatar in room/account settings

 fractal-gtk/src/actions/account_settings.rs |  40 +++++++++++
 fractal-gtk/src/actions/mod.rs              |  65 ++++++++++++++++++
 fractal-gtk/src/actions/room_history.rs     |  36 +++-------
 fractal-gtk/src/actions/room_settings.rs    |  46 +++++++++++++
 fractal-gtk/src/app/connect/account.rs      |  57 +++++++++-------
 fractal-gtk/src/appop/account.rs            |  18 -----
 fractal-gtk/src/appop/room_settings.rs      |  13 +++-
 fractal-gtk/src/widgets/room_settings.rs    | 101 ++++++++++++----------------
 8 files changed, 246 insertions(+), 130 deletions(-)
---
diff --git a/fractal-gtk/src/actions/account_settings.rs b/fractal-gtk/src/actions/account_settings.rs
new file mode 100644
index 00000000..a3fdfe03
--- /dev/null
+++ b/fractal-gtk/src/actions/account_settings.rs
@@ -0,0 +1,40 @@
+use crate::i18n::i18n;
+use gio::prelude::*;
+use gio::SimpleAction;
+use gio::SimpleActionGroup;
+use gtk;
+use gtk::prelude::*;
+use std::sync::mpsc::Sender;
+
+use crate::backend::BKCommand;
+
+use crate::widgets::ErrorDialog;
+use crate::widgets::FileDialog::open;
+
+use crate::actions::ButtonState;
+
+// This creates all actions a user can perform in the account settings
+pub fn new(window: &gtk::Window, backend: &Sender<BKCommand>) -> gio::SimpleActionGroup {
+    let actions = SimpleActionGroup::new();
+    // TODO create two stats loading interaction and connect it to the avatar box
+    let change_avatar =
+        SimpleAction::new_stateful("change-avatar", None, &ButtonState::Sensitive.into());
+
+    actions.add_action(&change_avatar);
+
+    let window_weak = window.downgrade();
+    let backend = backend.clone();
+    change_avatar.connect_activate(move |a, _| {
+        let window = upgrade_weak!(window_weak);
+        if let Some(path) = open(&window, i18n("Select a new avatar").as_str()) {
+            if let Some(file) = path.to_str() {
+                a.change_state(&ButtonState::Insensitive.into());
+                let _ = backend.send(BKCommand::SetUserAvatar(file.to_string()));
+            } else {
+                ErrorDialog::new(false, &i18n("Couldn't open file"));
+            }
+        }
+    });
+
+    actions
+}
diff --git a/fractal-gtk/src/actions/mod.rs b/fractal-gtk/src/actions/mod.rs
index f1710986..70697a83 100644
--- a/fractal-gtk/src/actions/mod.rs
+++ b/fractal-gtk/src/actions/mod.rs
@@ -1,6 +1,71 @@
+use gio::SimpleAction;
+use gio::SimpleActionExt;
+use glib::Cast;
+use glib::ObjectExt;
+use glib::ToVariant;
+use gtk::WidgetExt;
+
+pub mod account_settings;
 pub mod global;
 pub mod room_history;
+pub mod room_settings;
 
+pub use self::account_settings as AccountSettings;
 pub use self::global as Global;
 pub use self::global::AppState;
 pub use self::room_history as RoomHistory;
+pub use self::room_settings as RoomSettings;
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum ButtonState {
+    Sensitive,
+    Insensitive,
+}
+
+impl<'a> From<&'a glib::Variant> for ButtonState {
+    fn from(v: &glib::Variant) -> ButtonState {
+        v.get::<bool>().expect("Invalid button state type").into()
+    }
+}
+
+impl From<bool> for ButtonState {
+    fn from(v: bool) -> ButtonState {
+        if v {
+            ButtonState::Sensitive
+        } else {
+            ButtonState::Insensitive
+        }
+    }
+}
+
+impl From<ButtonState> for bool {
+    fn from(v: ButtonState) -> bool {
+        (v == ButtonState::Sensitive)
+    }
+}
+
+impl From<ButtonState> for glib::Variant {
+    fn from(v: ButtonState) -> glib::Variant {
+        (v == ButtonState::Sensitive).to_variant()
+    }
+}
+
+pub trait StateExt {
+    fn bind_button_state(&self, button: &gtk::Button);
+}
+
+// FIXME: workaround till we get GPropertyAction
+impl StateExt for gio::Action {
+    fn bind_button_state(&self, button: &gtk::Button) {
+        let button = button.downgrade();
+        if let Some(action) = self.downcast_ref::<SimpleAction>() {
+            action.connect_change_state(move |_, data| {
+                if let Some(data) = data {
+                    let state: ButtonState = data.into();
+                    let button = upgrade_weak!(button);
+                    button.set_sensitive(state.into());
+                }
+            });
+        }
+    }
+}
diff --git a/fractal-gtk/src/actions/room_history.rs b/fractal-gtk/src/actions/room_history.rs
index 255115af..f9c363ec 100644
--- a/fractal-gtk/src/actions/room_history.rs
+++ b/fractal-gtk/src/actions/room_history.rs
@@ -15,9 +15,9 @@ use gio::SimpleActionExt;
 use gio::SimpleActionGroup;
 use gtk;
 use gtk::prelude::*;
-use gtk::ResponseType;
 
 use crate::widgets::ErrorDialog;
+use crate::widgets::FileDialog::save;
 use crate::widgets::SourceDialog;
 
 /* This creates all actions the room history can perform */
@@ -113,9 +113,13 @@ pub fn new(backend: Sender<BKCommand>, ui: UI) -> gio::SimpleActionGroup {
                         gtk::Continue(true)
                     },
                     Ok(fname) => {
-                        let parent = upgrade_weak!(parent_weak, gtk::Continue(true));
-                        open_save_as_dialog(&parent, fname, &name);
-
+                        let window = upgrade_weak!(parent_weak, gtk::Continue(true));
+                        if let Some(path) = save(&window, &name) {
+                            // TODO use glib to copy file
+                            if let Err(_) = fs::copy(fname.clone(), path) {
+                                ErrorDialog::new(false, &i18n("Couldn't save file"));
+                            }
+                        }
                         gtk::Continue(false)
                     }
                 }),
@@ -187,30 +191,6 @@ fn get_room_id(data: &Option<glib::Variant>) -> Option<String> {
     data.as_ref()?.get_str().map(|s| s.to_string())
 }
 
-fn open_save_as_dialog(parent: &gtk::Window, src: String, name: &str) {
-    let file_chooser = gtk::FileChooserNative::new(
-        Some(i18n("Save media as").as_str()),
-        Some(parent),
-        gtk::FileChooserAction::Save,
-        Some(i18n("_Save").as_str()),
-        Some(i18n("_Cancel").as_str()),
-    );
-
-    file_chooser.set_current_folder(dirs::download_dir().unwrap_or_default());
-    file_chooser.set_current_name(name);
-
-    file_chooser.connect_response(move |fcd, res| {
-        if ResponseType::from(res) == ResponseType::Accept {
-            if let Err(_) = fs::copy(src.clone(), fcd.get_filename().unwrap_or_default()) {
-                let msg = i18n("Could not save the file");
-                ErrorDialog::new(false, &msg);
-            }
-        }
-    });
-
-    file_chooser.run();
-}
-
 fn request_more_messages(backend: &Sender<BKCommand>, id: Option<String>) -> Option<()> {
     let op = App::get_op()?;
     let op = op.lock().unwrap();
diff --git a/fractal-gtk/src/actions/room_settings.rs b/fractal-gtk/src/actions/room_settings.rs
new file mode 100644
index 00000000..45494841
--- /dev/null
+++ b/fractal-gtk/src/actions/room_settings.rs
@@ -0,0 +1,46 @@
+use gio::prelude::*;
+use gio::SimpleAction;
+use gio::SimpleActionGroup;
+use glib;
+use gtk;
+use gtk::prelude::*;
+use std::sync::mpsc::Sender;
+
+use crate::backend::BKCommand;
+use crate::i18n::i18n;
+
+use crate::widgets::ErrorDialog;
+use crate::widgets::FileDialog::open;
+
+use crate::actions::ButtonState;
+
+// This creates all actions a user can perform in the room settings
+pub fn new(window: &gtk::Window, backend: &Sender<BKCommand>) -> gio::SimpleActionGroup {
+    let actions = SimpleActionGroup::new();
+    // TODO create two stats loading interaction and conect it to the avatar box
+    let change_avatar = SimpleAction::new_stateful(
+        "change-avatar",
+        glib::VariantTy::new("s").ok(),
+        &ButtonState::Sensitive.into(),
+    );
+
+    actions.add_action(&change_avatar);
+
+    let window_weak = window.downgrade();
+    let backend = backend.clone();
+    change_avatar.connect_activate(move |a, data| {
+        if let Some(id) = data.as_ref().map(|x| x.to_string()) {
+            let window = upgrade_weak!(window_weak);
+            if let Some(path) = open(&window, i18n("Select a new avatar").as_str()) {
+                if let Some(file) = path.to_str() {
+                    a.change_state(&ButtonState::Insensitive.into());
+                    let _ = backend.send(BKCommand::SetRoomAvatar(id, file.to_string()));
+                } else {
+                    ErrorDialog::new(false, &i18n("Couldn't open file"));
+                }
+            }
+        }
+    });
+
+    actions
+}
diff --git a/fractal-gtk/src/app/connect/account.rs b/fractal-gtk/src/app/connect/account.rs
index 087ded11..9e693793 100644
--- a/fractal-gtk/src/app/connect/account.rs
+++ b/fractal-gtk/src/app/connect/account.rs
@@ -1,11 +1,12 @@
 use fractal_api::clone;
+use gio::ActionMapExt;
+use glib;
 use gtk;
 use gtk::prelude::*;
 
-use glib;
-
 use crate::app::App;
-use crate::i18n::i18n;
+
+use crate::actions::{AccountSettings, StateExt};
 
 impl App {
     pub fn connect_account_settings(&self) {
@@ -92,28 +93,38 @@ impl App {
             .get_object::<gtk::Button>("account_settings_delete_btn")
             .expect("Can't find account_settings_delete_btn in ui file.");
 
+        // FIXME: don't clone the backend
+        let backend = self.op.lock().unwrap().backend.clone();
+        let window = self.main_window.upcast_ref::<gtk::Window>();
+        let actions = AccountSettings::new(&window, &backend);
+        let container = self
+            .ui
+            .builder
+            .get_object::<gtk::Box>("account_settings_box")
+            .expect("Can't find account_settings_box in ui file.");
+        container.insert_action_group("user-settings", &actions);
+
         /* Body */
-        avatar_btn.connect_clicked(clone!(op, builder => move |_| {
-            let window = builder
-                .get_object::<gtk::Window>("main_window")
-                .expect("Can't find main_window in ui file.");
-            let file_chooser = gtk::FileChooserNative::new(
-                i18n("Pick a new avatar").as_str(),
-                Some(&window),
-                gtk::FileChooserAction::Open,
-                Some(i18n("Select").as_str()),
-                None
-            );
-            /* http://gtk-rs.org/docs/gtk/struct.FileChooser.html */
-            let result = file_chooser.run();
-            if gtk::ResponseType::from(result) == gtk::ResponseType::Accept {
-                if let Some(file) = file_chooser.get_filename() {
-                    if let Some(path) = file.to_str() {
-                        op.lock().unwrap().update_avatar_account_settings(String::from(path));
-                    }
+        if let Some(action) = actions.lookup_action("change-avatar") {
+            action.bind_button_state(&avatar_btn);
+            avatar_btn.set_action_name("user-settings.change-avatar");
+            let avatar_spinner = self
+                .ui
+                .builder
+                .get_object::<gtk::Spinner>("account_settings_avatar_spinner")
+                .expect("Can't find account_settings_avatar_spinner in ui file.");
+            let spinner = avatar_spinner.downgrade();
+            avatar_btn.connect_property_sensitive_notify(move |w| {
+                let spinner = upgrade_weak!(spinner);
+                if !w.get_sensitive() {
+                    spinner.start();
+                    spinner.show();
+                } else {
+                    spinner.hide();
+                    spinner.stop();
                 }
-            }
-        }));
+            });
+        }
 
         let button = name_btn.clone();
         name_entry.connect_property_text_notify(clone!(op => move |w| {
diff --git a/fractal-gtk/src/appop/account.rs b/fractal-gtk/src/appop/account.rs
index 04a16b5d..d0a84f8d 100644
--- a/fractal-gtk/src/appop/account.rs
+++ b/fractal-gtk/src/appop/account.rs
@@ -475,24 +475,6 @@ impl AppOp {
         }
     }
 
-    pub fn update_avatar_account_settings(&mut self, file: String) {
-        let avatar_spinner = self
-            .ui
-            .builder
-            .get_object::<gtk::Spinner>("account_settings_avatar_spinner")
-            .expect("Can't find account_settings_avatar_spinner in ui file.");
-        let avatar_btn = self
-            .ui
-            .builder
-            .get_object::<gtk::Button>("account_settings_avatar_button")
-            .expect("Can't find account_settings_avatar_button in ui file.");
-        let command = BKCommand::SetUserAvatar(file.clone());
-        self.backend.send(command).unwrap();
-        avatar_btn.set_sensitive(false);
-        avatar_spinner.show();
-        self.show_avatar();
-    }
-
     pub fn show_new_username(&mut self, name: Option<String>) {
         let entry = self
             .ui
diff --git a/fractal-gtk/src/appop/room_settings.rs b/fractal-gtk/src/appop/room_settings.rs
index 9505aae3..6bb3fb9d 100644
--- a/fractal-gtk/src/appop/room_settings.rs
+++ b/fractal-gtk/src/appop/room_settings.rs
@@ -8,6 +8,11 @@ use crate::widgets;
 
 impl AppOp {
     pub fn create_room_settings(&mut self) -> Option<()> {
+        let window = self
+            .ui
+            .builder
+            .get_object::<gtk::Window>("main_window")
+            .expect("Can't find main_window in ui file.");
         let stack = self
             .ui
             .builder
@@ -21,8 +26,12 @@ impl AppOp {
 
         {
             let room = self.rooms.get(&self.active_room.clone()?)?;
-            let mut panel =
-                widgets::RoomSettings::new(self.backend.clone(), self.uid.clone(), room.clone());
+            let mut panel = widgets::RoomSettings::new(
+                &window,
+                self.backend.clone(),
+                self.uid.clone(),
+                room.clone(),
+            );
             let (body, header) = panel.create()?;
 
             /* remove old panel */
diff --git a/fractal-gtk/src/widgets/room_settings.rs b/fractal-gtk/src/widgets/room_settings.rs
index 07d525e9..b983983c 100644
--- a/fractal-gtk/src/widgets/room_settings.rs
+++ b/fractal-gtk/src/widgets/room_settings.rs
@@ -1,12 +1,15 @@
 use fractal_api::clone;
 use std::cell::RefCell;
 use std::rc::Rc;
+use std::sync::mpsc::Sender;
 
-use crate::i18n::i18n;
 use crate::i18n::ni18n_f;
+use gio::prelude::*;
 use gtk;
 use gtk::prelude::*;
 
+use crate::actions;
+use crate::actions::{ButtonState, StateExt};
 use crate::backend::BKCommand;
 use crate::cache::download_to_cache;
 use crate::types::Member;
@@ -15,10 +18,10 @@ use crate::widgets;
 use crate::widgets::avatar::AvatarExt;
 use crate::widgets::members_list::MembersList;
 use fractal_api::types::Room;
-use std::sync::mpsc::Sender;
 
 #[derive(Debug, Clone)]
 pub struct RoomSettings {
+    actions: gio::SimpleActionGroup,
     room: Room,
     uid: Option<String>,
     builder: gtk::Builder,
@@ -27,14 +30,27 @@ pub struct RoomSettings {
 }
 
 impl RoomSettings {
-    pub fn new(backend: Sender<BKCommand>, uid: Option<String>, room: Room) -> RoomSettings {
+    pub fn new(
+        window: &gtk::Window,
+        backend: Sender<BKCommand>,
+        uid: Option<String>,
+        room: Room,
+    ) -> RoomSettings {
         let builder = gtk::Builder::new();
 
         builder
             .add_from_resource("/org/gnome/Fractal/ui/room_settings.ui")
             .expect("Can't load ui file: room_settings.ui");
 
+        let stack = builder
+            .get_object::<gtk::Stack>("room_settings_stack")
+            .expect("Can't find room_settings_stack in ui file.");
+
+        let actions = actions::RoomSettings::new(&window, &backend);
+        stack.insert_action_group("room-settings", Some(&actions));
+
         RoomSettings {
+            actions,
             room: room,
             uid: uid,
             builder: builder,
@@ -114,6 +130,7 @@ impl RoomSettings {
             button.set_visible(result.is_some());
         }));
 
+        // TODO: create actions for all button
         let button = name_btn.clone();
         name_entry.connect_activate(move |_w| {
             let _ = button.emit("clicked", &[]);
@@ -132,33 +149,27 @@ impl RoomSettings {
             this.borrow_mut().update_room_topic();
         }));
 
-        /* Connect avatar button */
-        avatar_btn.connect_clicked(clone!(this => move |w| {
-            this.borrow_mut().create_file_chooser(w);
-        }));
-    }
-
-    fn create_file_chooser(&mut self, w: &gtk::Button) -> Option<()> {
-        let window = w.get_toplevel()?;
-        if let Ok(window) = window.downcast::<gtk::Window>() {
-            /* http://gtk-rs.org/docs/gtk/struct.FileChooser.html */
-            let file_chooser = gtk::FileChooserNative::new(
-                i18n("Pick a new room avatar").as_str(),
-                Some(&window),
-                gtk::FileChooserAction::Open,
-                Some(i18n("Select").as_str()),
-                None,
-            );
-            let result = file_chooser.run();
-            if gtk::ResponseType::from(result) == gtk::ResponseType::Accept {
-                if let Some(file) = file_chooser.get_filename() {
-                    if let Some(path) = file.to_str() {
-                        self.update_room_avatar(String::from(path));
-                    }
+        if let Some(action) = self.actions.lookup_action("change-avatar") {
+            action.bind_button_state(&avatar_btn);
+            let data = glib::Variant::from(&self.room.id);
+            avatar_btn.set_action_target_value(&data);
+            avatar_btn.set_action_name("room-settings.change-avatar");
+            let avatar_spinner = self
+                .builder
+                .get_object::<gtk::Spinner>("room_settings_avatar_spinner")
+                .expect("Can't find room_settings_avatar_spinner in ui file.");
+            let spinner = avatar_spinner.downgrade();
+            avatar_btn.connect_property_sensitive_notify(move |w| {
+                let spinner = upgrade_weak!(spinner);
+                if !w.get_sensitive() {
+                    spinner.start();
+                    spinner.show();
+                } else {
+                    spinner.hide();
+                    spinner.stop();
                 }
-            }
+            });
         }
-        None
     }
 
     fn init_room_settings(&mut self) -> Option<()> {
@@ -439,24 +450,6 @@ impl RoomSettings {
         return None;
     }
 
-    pub fn update_room_avatar(&mut self, file: String) -> Option<()> {
-        let avatar_spinner = self
-            .builder
-            .get_object::<gtk::Spinner>("room_settings_avatar_spinner")
-            .expect("Can't find room_settings_avatar_spinner in ui file.");
-        let avatar_btn = self
-            .builder
-            .get_object::<gtk::Button>("room_settings_avatar_button")
-            .expect("Can't find room_settings_avatar_button in ui file.");
-        let room = &self.room;
-        let command = BKCommand::SetRoomAvatar(room.id.clone(), file.clone());
-        self.backend.send(command).unwrap();
-        self.room_settings_show_avatar(true);
-        avatar_btn.set_sensitive(false);
-        avatar_spinner.show();
-        None
-    }
-
     pub fn update_room_name(&mut self) -> Option<()> {
         let entry = self
             .builder
@@ -530,19 +523,9 @@ impl RoomSettings {
     }
 
     pub fn show_new_room_avatar(&self) {
-        let avatar_spinner = self
-            .builder
-            .get_object::<gtk::Spinner>("room_settings_avatar_spinner")
-            .expect("Can't find room_settings_avatar_spinner in ui file.");
-        let avatar_btn = self
-            .builder
-            .get_object::<gtk::Button>("room_settings_avatar_button")
-            .expect("Can't find room_settings_avatar_button in ui file.");
-
-        /* We could update the avatar for this room,
-         * but we are waiting for the new avatar event */
-        avatar_spinner.hide();
-        avatar_btn.set_sensitive(true);
+        if let Some(action) = self.actions.lookup_action("change-avatar") {
+            action.change_state(&ButtonState::Sensitive.into());
+        }
     }
 
     pub fn show_new_room_name(&self) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]