[fractal] room-details: Move general page to own subclass



commit 988e6bf1c99b930e09ce9056acd2e8b80747b1fd
Author: Julian Sparber <julian sparber net>
Date:   Mon Sep 12 11:56:48 2022 +0200

    room-details: Move general page to own subclass

 data/resources/resources.gresource.xml             |   1 +
 .../ui/content-room-details-general-page.ui        | 180 ++++++++++++++
 data/resources/ui/content-room-details.ui          | 185 +-------------
 po/POTFILES.in                                     |   2 +
 .../content/room_details/general_page/mod.rs       | 270 +++++++++++++++++++++
 src/session/content/room_details/mod.rs            | 202 +++------------
 6 files changed, 482 insertions(+), 358 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 1f0edb68b..77945325c 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -67,6 +67,7 @@
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-message-reply.ui">ui/content-message-reply.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-message-row.ui">ui/content-message-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-public-room-row.ui">ui/content-public-room-row.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="content-room-details-general-page.ui">ui/content-room-details-general-page.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-room-details.ui">ui/content-room-details.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-room-history.ui">ui/content-room-history.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-state-creation.ui">ui/content-state-creation.ui</file>
diff --git a/data/resources/ui/content-room-details-general-page.ui 
b/data/resources/ui/content-room-details-general-page.ui
new file mode 100644
index 000000000..65ee75088
--- /dev/null
+++ b/data/resources/ui/content-room-details-general-page.ui
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ContentRoomDetailsGeneralPage" parent="AdwBin">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkHeaderBar"/>
+        </child>
+        <child>
+          <object class="AdwClamp">
+            <property name="maximum-size">400</property>
+            <property name="tightening-threshold">400</property>
+            <property name="margin-top">12</property>
+            <property name="child">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="spacing">24</property>
+                <child>
+                  <object class="AdwPreferencesGroup">
+                    <style>
+                      <class name="room-details-group"/>
+                    </style>
+                    <child>
+                      <object class="GtkOverlay">
+                        <property name="halign">center</property>
+                        <child>
+                          <object class="ComponentsAvatar">
+                            <property name="size">128</property>
+                            <binding name="item">
+                              <lookup name="avatar">
+                                <lookup name="room">ContentRoomDetailsGeneralPage</lookup>
+                              </lookup>
+                            </binding>
+                          </object>
+                        </child>
+                        <child type="overlay">
+                          <object class="AdwBin" id="avatar_remove_button">
+                            <style>
+                              <class name="cutout-button"/>
+                            </style>
+                            <property name="halign">end</property>
+                            <property name="valign">start</property>
+                            <child>
+                              <object class="GtkButton">
+                                <property name="icon-name">user-trash-symbolic</property>
+                                <property name="action-name">details.remove-avatar</property>
+                                <style>
+                                  <class name="circular"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child type="overlay">
+                          <object class="AdwBin" id="avatar_edit_button">
+                            <style>
+                              <class name="cutout-button"/>
+                            </style>
+                            <property name="halign">end</property>
+                            <property name="valign">end</property>
+                            <child>
+                              <object class="GtkButton">
+                                <property name="icon-name">document-edit-symbolic</property>
+                                <property name="action-name">details.choose-avatar</property>
+                                <style>
+                                  <class name="circular"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="spacing">6</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <object class="GtkEntry" id="room_name_entry">
+                            <property name="sensitive">false</property>
+                            <property name="activates-default">True</property>
+                            <property name="xalign">0.5</property>
+                            <property name="buffer">
+                              <object class="GtkEntryBuffer" id="room_name_buffer">
+                                <binding name="text">
+                                  <lookup name="display-name">
+                                    <lookup name="room">ContentRoomDetailsGeneralPage</lookup>
+                                  </lookup>
+                                </binding>
+                              </object>
+                            </property>
+                            <style>
+                              <class name="room-details-name"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="room_topic_label">
+                            <property name="visible">false</property>
+                            <property name="margin-top">12</property>
+                            <property name="label" translatable="yes">Description</property>
+                            <property name="halign">start</property>
+                            <style>
+                              <class name="dim-label"/>
+                              <class name="caption-heading"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="CustomEntry" id="room_topic_entry">
+                            <property name="sensitive">false</property>
+                            <property name="margin-bottom">18</property>
+                            <child>
+                              <object class="GtkTextView" id="room_topic_text_view">
+                                <property name="justification">center</property>
+                                <property name="wrap-mode">word-char</property>
+                                <property name="accepts-tab">False</property>
+                                <property name="top-margin">7</property>
+                                <property name="bottom-margin">7</property>
+                                <property name="buffer">
+                                  <object class="GtkTextBuffer" id="room_topic_buffer">
+                                    <binding name="text">
+                                      <lookup name="topic">
+                                        <lookup name="room">ContentRoomDetailsGeneralPage</lookup>
+                                      </lookup>
+                                    </binding>
+                                  </object>
+                                </property>
+                              </object>
+                            </child>
+                            <style>
+                              <class name="room-details-topic"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="edit_toggle">
+                            <property name="halign">center</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwPreferencesGroup">
+                    <child>
+                      <object class="AdwActionRow">
+                        <property name="title" translatable="yes">Members</property>
+                        <property name="icon-name">system-users-symbolic</property>
+                        <property name="action-name">details.next-page</property>
+                        <property name="action-target">'members'</property>
+                        <property name="activatable">True</property>
+                        <child type="suffix">
+                          <object class="GtkLabel" id="members_count">
+                            <property name="valign">center</property>
+                            <property name="halign">center</property>
+                          </object>
+                        </child>
+                        <child type="suffix">
+                          <object class="GtkImage">
+                            <property name="valign">center</property>
+                            <property name="halign">center</property>
+                            <property name="icon-name">go-next-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </property>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
+
diff --git a/data/resources/ui/content-room-details.ui b/data/resources/ui/content-room-details.ui
index 3432f810c..9a6acb559 100644
--- a/data/resources/ui/content-room-details.ui
+++ b/data/resources/ui/content-room-details.ui
@@ -2,195 +2,12 @@
 <interface>
   <template class="RoomDetails" parent="AdwWindow">
     <property name="title" translatable="yes">Room Details</property>
-    <property name="default-widget">edit_toggle</property>
     <property name="modal">True</property>
     <property name="destroy_with_parent">True</property>
     <property name="default-width">640</property>
     <property name="default-height">576</property>
     <property name="content">
-      <object class="GtkStack" id="main_stack">
-        <child>
-          <object class="GtkStackPage">
-            <property name="icon-name">applications-system-symbolic</property>
-            <property name="title" translatable="yes">General</property>
-            <property name="name">general</property>
-            <property name="child">
-              <object class="GtkBox">
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkHeaderBar"/>
-                </child>
-                <child>
-                  <object class="AdwClamp">
-                    <property name="maximum-size">400</property>
-                    <property name="tightening-threshold">400</property>
-                    <property name="margin-top">12</property>
-                    <property name="child">
-                      <object class="GtkBox">
-                        <property name="orientation">vertical</property>
-                        <property name="spacing">24</property>
-                        <child>
-                          <object class="AdwPreferencesGroup">
-                            <style>
-                              <class name="room-details-group"/>
-                            </style>
-                            <child>
-                              <object class="GtkOverlay">
-                                <property name="halign">center</property>
-                                <child>
-                                  <object class="ComponentsAvatar">
-                                    <property name="size">128</property>
-                                    <binding name="item">
-                                      <lookup name="avatar">
-                                        <lookup name="room">RoomDetails</lookup>
-                                      </lookup>
-                                    </binding>
-                                  </object>
-                                </child>
-                                <child type="overlay">
-                                  <object class="AdwBin" id="avatar_remove_button">
-                                    <style>
-                                      <class name="cutout-button"/>
-                                    </style>
-                                    <property name="halign">end</property>
-                                    <property name="valign">start</property>
-                                    <child>
-                                      <object class="GtkButton">
-                                        <property name="icon-name">user-trash-symbolic</property>
-                                        <property name="action-name">details.remove-avatar</property>
-                                        <style>
-                                          <class name="circular"/>
-                                        </style>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                                <child type="overlay">
-                                  <object class="AdwBin" id="avatar_edit_button">
-                                    <style>
-                                      <class name="cutout-button"/>
-                                    </style>
-                                    <property name="halign">end</property>
-                                    <property name="valign">end</property>
-                                    <child>
-                                      <object class="GtkButton">
-                                        <property name="icon-name">document-edit-symbolic</property>
-                                        <property name="action-name">details.choose-avatar</property>
-                                        <style>
-                                          <class name="circular"/>
-                                        </style>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkBox">
-                                <property name="spacing">6</property>
-                                <property name="orientation">vertical</property>
-                                <child>
-                                  <object class="GtkEntry" id="room_name_entry">
-                                    <property name="sensitive">false</property>
-                                    <property name="activates-default">True</property>
-                                    <property name="xalign">0.5</property>
-                                    <property name="buffer">
-                                      <object class="GtkEntryBuffer" id="room_name_buffer">
-                                        <binding name="text">
-                                          <lookup name="display-name">
-                                            <lookup name="room">RoomDetails</lookup>
-                                          </lookup>
-                                        </binding>
-                                      </object>
-                                    </property>
-                                    <style>
-                                      <class name="room-details-name"/>
-                                    </style>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkLabel" id="room_topic_label">
-                                    <property name="visible">false</property>
-                                    <property name="margin-top">12</property>
-                                    <property name="label" translatable="yes">Description</property>
-                                    <property name="halign">start</property>
-                                    <style>
-                                      <class name="dim-label"/>
-                                      <class name="caption-heading"/>
-                                    </style>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="CustomEntry" id="room_topic_entry">
-                                    <property name="sensitive">false</property>
-                                    <property name="margin-bottom">18</property>
-                                    <child>
-                                      <object class="GtkTextView" id="room_topic_text_view">
-                                        <property name="justification">center</property>
-                                        <property name="wrap-mode">word-char</property>
-                                        <property name="accepts-tab">False</property>
-                                        <property name="top-margin">7</property>
-                                        <property name="bottom-margin">7</property>
-                                        <property name="buffer">
-                                          <object class="GtkTextBuffer" id="room_topic_buffer">
-                                            <binding name="text">
-                                              <lookup name="topic">
-                                                <lookup name="room">RoomDetails</lookup>
-                                              </lookup>
-                                            </binding>
-                                          </object>
-                                        </property>
-                                      </object>
-                                    </child>
-                                    <style>
-                                      <class name="room-details-topic"/>
-                                    </style>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkButton" id="edit_toggle">
-                                    <property name="halign">center</property>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="AdwPreferencesGroup">
-                            <child>
-                              <object class="AdwActionRow">
-                                <property name="title" translatable="yes">Members</property>
-                                <property name="icon-name">system-users-symbolic</property>
-                                <property name="action-name">details.next-page</property>
-                                <property name="action-target">&apos;members&apos;</property>
-                                <property name="activatable">True</property>
-                                <child type="suffix">
-                                  <object class="GtkLabel" id="members_count">
-                                    <property name="valign">center</property>
-                                    <property name="halign">center</property>
-                                  </object>
-                                </child>
-                                <child type="suffix">
-                                  <object class="GtkImage">
-                                    <property name="valign">center</property>
-                                    <property name="halign">center</property>
-                                    <property name="icon-name">go-next-symbolic</property>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </property>
-                  </object>
-                </child>
-              </object>
-            </property>
-          </object>
-        </child>
-      </object>
+      <object class="GtkStack" id="main_stack" />
     </property>
     <style>
       <class name="room-details"/>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c75a4fbdb..b58010e62 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -21,6 +21,7 @@ data/resources/ui/content-markdown-popover.ui
 data/resources/ui/content-member-page.ui
 data/resources/ui/content-message-file.ui
 data/resources/ui/content-room-details.ui
+data/resources/ui/content-room-details-general-page.ui
 data/resources/ui/content-room-history.ui
 data/resources/ui/content-state-creation.ui
 data/resources/ui/content-state-tombstone.ui
@@ -55,6 +56,7 @@ src/session/account_settings/user_page/deactivate_account_subpage.rs
 src/session/account_settings/user_page/mod.rs
 src/session/content/explore/public_room_row.rs
 src/session/content/invite.rs
+src/session/content/room_details/general_page/mod.rs
 src/session/content/room_details/invite_subpage/invitee_list.rs
 src/session/content/room_details/member_page/mod.rs
 src/session/content/room_details/mod.rs
diff --git a/src/session/content/room_details/general_page/mod.rs 
b/src/session/content/room_details/general_page/mod.rs
new file mode 100644
index 000000000..061c7b9a0
--- /dev/null
+++ b/src/session/content/room_details/general_page/mod.rs
@@ -0,0 +1,270 @@
+use std::convert::From;
+
+use adw::{prelude::*, subclass::prelude::*};
+use gettextrs::gettext;
+use gtk::{
+    gdk,
+    glib::{self, clone, closure},
+    CompositeTemplate,
+};
+use log::error;
+use matrix_sdk::ruma::events::RoomEventType;
+
+use crate::{
+    components::CustomEntry,
+    session::{self, room::RoomAction, Room},
+    utils::{and_expr, or_expr},
+};
+
+mod imp {
+    use std::cell::Cell;
+
+    use glib::subclass::InitializingObject;
+    use once_cell::unsync::OnceCell;
+
+    use super::*;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/Fractal/content-room-details-general-page.ui")]
+    pub struct GeneralPage {
+        pub room: OnceCell<Room>,
+        pub avatar_chooser: OnceCell<gtk::FileChooserNative>,
+        #[template_child]
+        pub avatar_remove_button: TemplateChild<adw::Bin>,
+        #[template_child]
+        pub avatar_edit_button: TemplateChild<adw::Bin>,
+        #[template_child]
+        pub edit_toggle: TemplateChild<gtk::Button>,
+        #[template_child]
+        pub room_name_entry: TemplateChild<gtk::Entry>,
+        #[template_child]
+        pub room_topic_text_view: TemplateChild<gtk::TextView>,
+        #[template_child]
+        pub room_topic_entry: TemplateChild<CustomEntry>,
+        #[template_child]
+        pub room_topic_label: TemplateChild<gtk::Label>,
+        #[template_child]
+        pub members_count: TemplateChild<gtk::Label>,
+        pub edit_mode: Cell<bool>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for GeneralPage {
+        const NAME: &'static str = "ContentRoomDetailsGeneralPage";
+        type Type = super::GeneralPage;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            CustomEntry::static_type();
+            Self::bind_template(klass);
+
+            klass.install_action("details.choose-avatar", None, move |widget, _, _| {
+                widget.open_avatar_chooser()
+            });
+            klass.install_action("details.remove-avatar", None, move |widget, _, _| {
+                widget.room().store_avatar(None)
+            });
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for GeneralPage {
+        fn properties() -> &'static [glib::ParamSpec] {
+            use once_cell::sync::Lazy;
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpecObject::new(
+                    "room",
+                    "Room",
+                    "The room backing all details of the preference window",
+                    Room::static_type(),
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "room" => obj.set_room(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "room" => self.room.get().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            obj.init_avatar();
+            obj.init_edit_toggle();
+
+            let members = obj.room().members();
+            members.connect_items_changed(clone!(@weak obj => move |members, _, _, _| {
+                obj.member_count_changed(members.n_items());
+            }));
+
+            obj.member_count_changed(members.n_items());
+        }
+    }
+
+    impl WidgetImpl for GeneralPage {}
+    impl BinImpl for GeneralPage {}
+}
+
+glib::wrapper! {
+    /// Preference Window to display and update room details.
+    pub struct GeneralPage(ObjectSubclass<imp::GeneralPage>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl GeneralPage {
+    pub fn new(room: &Room) -> Self {
+        glib::Object::new(&[("room", room)]).expect("Failed to create GeneralPage")
+    }
+
+    pub fn room(&self) -> &Room {
+        // Use unwrap because room property is CONSTRUCT_ONLY.
+        self.imp().room.get().unwrap()
+    }
+
+    fn set_room(&self, room: Room) {
+        self.imp().room.set(room).expect("Room already initialized");
+    }
+
+    fn init_avatar(&self) {
+        let priv_ = self.imp();
+        let avatar_remove_button = &priv_.avatar_remove_button;
+        let avatar_edit_button = &priv_.avatar_edit_button;
+
+        // Hide avatar controls when the user is not eligible to perform the actions.
+        let room = self.room();
+
+        let room_avatar_exists = room
+            .property_expression("avatar")
+            .chain_property::<session::Avatar>("image")
+            .chain_closure::<bool>(closure!(
+                |_: Option<glib::Object>, image: Option<gdk::Paintable>| { image.is_some() }
+            ));
+
+        let room_avatar_changeable =
+            room.new_allowed_expr(RoomAction::StateEvent(RoomEventType::RoomAvatar));
+        let room_avatar_removable = and_expr(&room_avatar_changeable, &room_avatar_exists);
+
+        room_avatar_removable.bind(&avatar_remove_button.get(), "visible", gtk::Widget::NONE);
+        room_avatar_changeable.bind(&avatar_edit_button.get(), "visible", gtk::Widget::NONE);
+    }
+
+    fn init_edit_toggle(&self) {
+        let priv_ = self.imp();
+        let edit_toggle = &priv_.edit_toggle;
+        let label_enabled = gettext("Save Details");
+        let label_disabled = gettext("Edit Details");
+
+        edit_toggle.set_label(&label_disabled);
+
+        // Save changes of name and topic on toggle button release.
+        edit_toggle.connect_clicked(clone!(@weak self as this => move |button| {
+            let priv_ = this.imp();
+            if !priv_.edit_mode.get() {
+                priv_.edit_mode.set(true);
+                button.set_label(&label_enabled);
+                priv_.room_topic_text_view.set_justification(gtk::Justification::Left);
+                priv_.room_name_entry.set_xalign(0.0);
+                priv_.room_name_entry.set_halign(gtk::Align::Center);
+                priv_.room_name_entry.set_sensitive(true);
+                priv_.room_name_entry.set_width_chars(25);
+                priv_.room_topic_entry.set_sensitive(true);
+                priv_.room_topic_label.show();
+                return;
+            }
+            priv_.edit_mode.set(false);
+            button.set_label(&label_disabled);
+            priv_.room_topic_text_view.set_justification(gtk::Justification::Center);
+            priv_.room_name_entry.set_xalign(0.5);
+            priv_.room_name_entry.set_sensitive(false);
+            priv_.room_name_entry.set_halign(gtk::Align::Fill);
+            priv_.room_name_entry.set_width_chars(-1);
+            priv_.room_topic_entry.set_sensitive(false);
+            priv_.room_topic_label.hide();
+
+            let room = this.room();
+
+            let room_name = priv_.room_name_entry.buffer().text();
+            let topic_buffer = priv_.room_topic_text_view.buffer();
+            let topic = topic_buffer.text(&topic_buffer.start_iter(), &topic_buffer.end_iter(), true);
+            room.store_room_name(room_name);
+            room.store_topic(topic.to_string());
+        }));
+
+        // Hide edit controls when the user is not eligible to perform the actions.
+        let room = self.room();
+        let room_name_changeable =
+            room.new_allowed_expr(RoomAction::StateEvent(RoomEventType::RoomName));
+        let room_topic_changeable =
+            room.new_allowed_expr(RoomAction::StateEvent(RoomEventType::RoomTopic));
+
+        let edit_toggle_visible = or_expr(room_name_changeable, room_topic_changeable);
+        edit_toggle_visible.bind(&edit_toggle.get(), "visible", gtk::Widget::NONE);
+    }
+
+    fn avatar_chooser(&self) -> Option<&gtk::FileChooserNative> {
+        if let Some(avatar_chooser) = self.imp().avatar_chooser.get() {
+            Some(avatar_chooser)
+        } else {
+            let window = self.root()?.downcast::<adw::Window>().ok()?;
+
+            let avatar_chooser = gtk::FileChooserNative::new(
+                Some(&gettext("Choose avatar")),
+                Some(&window),
+                gtk::FileChooserAction::Open,
+                None,
+                None,
+            );
+            avatar_chooser.connect_response(
+                clone!(@weak self as this => move |chooser, response| {
+                    let file = chooser.file().and_then(|f| f.path());
+                    if let (gtk::ResponseType::Accept, Some(file)) = (response, file) {
+                        log::debug!("Chose file {:?}", file);
+                        this.room().store_avatar(Some(file));
+                    }
+                }),
+            );
+
+            // We must keep a reference to FileChooserNative around as it is not
+            // managed by GTK.
+            self.imp()
+                .avatar_chooser
+                .set(avatar_chooser)
+                .expect("File chooser already initialized");
+
+            self.avatar_chooser()
+        }
+    }
+
+    fn open_avatar_chooser(&self) {
+        if let Some(avatar_chooser) = self.avatar_chooser() {
+            avatar_chooser.show();
+        } else {
+            error!("Failed to create the FileChooserNative");
+        }
+    }
+
+    fn member_count_changed(&self, n: u32) {
+        self.imp().members_count.set_text(&format!("{}", n));
+    }
+}
diff --git a/src/session/content/room_details/mod.rs b/src/session/content/room_details/mod.rs
index e5043991c..c6e429c46 100644
--- a/src/session/content/room_details/mod.rs
+++ b/src/session/content/room_details/mod.rs
@@ -1,3 +1,4 @@
+mod general_page;
 mod invite_subpage;
 mod member_page;
 
@@ -5,24 +6,17 @@ use std::convert::From;
 
 use adw::{prelude::*, subclass::prelude::*};
 use gettextrs::gettext;
-use gtk::{
-    gdk,
-    glib::{self, clone, closure},
-    CompositeTemplate,
-};
-use matrix_sdk::ruma::events::RoomEventType;
-
-pub use self::{invite_subpage::InviteSubpage, member_page::MemberPage};
-use crate::{
-    components::CustomEntry,
-    session::{self, room::RoomAction, Room},
-    utils::{and_expr, or_expr},
-};
+use gtk::{glib, CompositeTemplate};
+use log::warn;
+
+pub use self::{general_page::GeneralPage, invite_subpage::InviteSubpage, member_page::MemberPage};
+use crate::session::Room;
 
 #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
 #[repr(u32)]
 #[enum_type(name = "RoomDetailsPageName")]
 pub enum PageName {
+    None,
     General,
     Members,
     Invite,
@@ -30,7 +24,7 @@ pub enum PageName {
 
 impl Default for PageName {
     fn default() -> Self {
-        Self::General
+        Self::None
     }
 }
 
@@ -46,6 +40,7 @@ impl glib::variant::FromVariant for PageName {
             "general" => Some(PageName::General),
             "members" => Some(PageName::Members),
             "invite" => Some(PageName::Invite),
+            "" => Some(PageName::None),
             _ => None,
         }
     }
@@ -54,6 +49,7 @@ impl glib::variant::FromVariant for PageName {
 impl glib::variant::ToVariant for PageName {
     fn to_variant(&self) -> glib::variant::Variant {
         match self {
+            PageName::None => "",
             PageName::General => "general",
             PageName::Members => "members",
             PageName::Invite => "invite",
@@ -77,26 +73,8 @@ mod imp {
     #[template(resource = "/org/gnome/Fractal/content-room-details.ui")]
     pub struct RoomDetails {
         pub room: OnceCell<Room>,
-        pub avatar_chooser: OnceCell<gtk::FileChooserNative>,
         #[template_child]
         pub main_stack: TemplateChild<gtk::Stack>,
-        #[template_child]
-        pub avatar_remove_button: TemplateChild<adw::Bin>,
-        #[template_child]
-        pub avatar_edit_button: TemplateChild<adw::Bin>,
-        #[template_child]
-        pub edit_toggle: TemplateChild<gtk::Button>,
-        #[template_child]
-        pub room_name_entry: TemplateChild<gtk::Entry>,
-        #[template_child]
-        pub room_topic_text_view: TemplateChild<gtk::TextView>,
-        #[template_child]
-        pub room_topic_entry: TemplateChild<CustomEntry>,
-        #[template_child]
-        pub room_topic_label: TemplateChild<gtk::Label>,
-        #[template_child]
-        pub members_count: TemplateChild<gtk::Label>,
-        pub edit_mode: Cell<bool>,
         pub list_stack_children: RefCell<HashMap<PageName, glib::WeakRef<gtk::Widget>>>,
         pub visible_page: Cell<PageName>,
         pub previous_visible_page: RefCell<Vec<PageName>>,
@@ -109,14 +87,8 @@ mod imp {
         type ParentType = adw::Window;
 
         fn class_init(klass: &mut Self::Class) {
-            CustomEntry::static_type();
             Self::bind_template(klass);
-            klass.install_action("details.choose-avatar", None, move |widget, _, _| {
-                widget.open_avatar_chooser()
-            });
-            klass.install_action("details.remove-avatar", None, move |widget, _, _| {
-                widget.room().store_avatar(None)
-            });
+
             klass.install_action("details.next-page", Some("s"), move |widget, _, param| {
                 let page = param
                     .and_then(|variant| variant.get::<PageName>())
@@ -124,6 +96,7 @@ mod imp {
 
                 widget.next_page(page);
             });
+
             klass.install_action("details.previous-page", None, move |widget, _, _| {
                 widget.previous_page();
             });
@@ -181,27 +154,6 @@ mod imp {
                 _ => unimplemented!(),
             }
         }
-
-        fn constructed(&self, obj: &Self::Type) {
-            self.parent_constructed(obj);
-
-            obj.init_avatar();
-            obj.init_edit_toggle();
-            obj.init_avatar_chooser();
-            obj.init_member_action();
-
-            self.main_stack
-                .connect_visible_child_notify(clone!(@weak obj => move |_| {
-                    obj.notify("visible-page");
-                }));
-
-            let members = obj.room().members();
-            members.connect_items_changed(clone!(@weak obj => move |members, _, _, _| {
-                obj.member_count_changed(members.n_items());
-            }));
-
-            obj.member_count_changed(members.n_items());
-        }
     }
 
     impl WidgetImpl for RoomDetails {}
@@ -245,8 +197,20 @@ impl RoomDetails {
 
         match name {
             PageName::General => {
+                let general_page = if let Some(general_page) = list_stack_children
+                    .get(&PageName::General)
+                    .and_then(glib::object::WeakRef::upgrade)
+                {
+                    general_page
+                } else {
+                    let general_page = GeneralPage::new(self.room()).upcast::<gtk::Widget>();
+                    list_stack_children.insert(PageName::General, general_page.downgrade());
+                    self.imp().main_stack.add_child(&general_page);
+                    general_page
+                };
+
                 self.set_title(Some(&gettext("Room Details")));
-                priv_.main_stack.set_visible_child_name("general");
+                priv_.main_stack.set_visible_child(&general_page);
             }
             PageName::Members => {
                 let members_page = if let Some(members_page) = list_stack_children
@@ -265,7 +229,6 @@ impl RoomDetails {
                 priv_.main_stack.set_visible_child(&members_page);
             }
             PageName::Invite => {
-                priv_.main_stack.set_visible_child_name("general");
                 let invite_page = if let Some(invite_page) = list_stack_children
                     .get(&PageName::Invite)
                     .and_then(glib::object::WeakRef::upgrade)
@@ -281,124 +244,15 @@ impl RoomDetails {
                 self.set_title(Some(&gettext("Invite new Members")));
                 priv_.main_stack.set_visible_child(&invite_page);
             }
+            PageName::None => {
+                warn!("Can't switch to PageName::None");
+            }
         }
 
         priv_.visible_page.set(name);
         self.notify("visible-page");
     }
 
-    fn init_avatar(&self) {
-        let priv_ = self.imp();
-        let avatar_remove_button = &priv_.avatar_remove_button;
-        let avatar_edit_button = &priv_.avatar_edit_button;
-
-        // Hide avatar controls when the user is not eligible to perform the actions.
-        let room = self.room();
-
-        let room_avatar_exists = room
-            .property_expression("avatar")
-            .chain_property::<session::Avatar>("image")
-            .chain_closure::<bool>(closure!(
-                |_: Option<glib::Object>, image: Option<gdk::Paintable>| { image.is_some() }
-            ));
-
-        let room_avatar_changeable =
-            room.new_allowed_expr(RoomAction::StateEvent(RoomEventType::RoomAvatar));
-        let room_avatar_removable = and_expr(&room_avatar_changeable, &room_avatar_exists);
-
-        room_avatar_removable.bind(&avatar_remove_button.get(), "visible", gtk::Widget::NONE);
-        room_avatar_changeable.bind(&avatar_edit_button.get(), "visible", gtk::Widget::NONE);
-    }
-
-    fn init_edit_toggle(&self) {
-        let priv_ = self.imp();
-        let edit_toggle = &priv_.edit_toggle;
-        let label_enabled = gettext("Save Details");
-        let label_disabled = gettext("Edit Details");
-
-        edit_toggle.set_label(&label_disabled);
-
-        // Save changes of name and topic on toggle button release.
-        edit_toggle.connect_clicked(clone!(@weak self as this => move |button| {
-            let priv_ = this.imp();
-            if !priv_.edit_mode.get() {
-                priv_.edit_mode.set(true);
-                button.set_label(&label_enabled);
-                priv_.room_topic_text_view.set_justification(gtk::Justification::Left);
-                priv_.room_name_entry.set_xalign(0.0);
-                priv_.room_name_entry.set_halign(gtk::Align::Center);
-                priv_.room_name_entry.set_sensitive(true);
-                priv_.room_name_entry.set_width_chars(25);
-                priv_.room_topic_entry.set_sensitive(true);
-                priv_.room_topic_label.show();
-                return;
-            }
-            priv_.edit_mode.set(false);
-            button.set_label(&label_disabled);
-            priv_.room_topic_text_view.set_justification(gtk::Justification::Center);
-            priv_.room_name_entry.set_xalign(0.5);
-            priv_.room_name_entry.set_sensitive(false);
-            priv_.room_name_entry.set_halign(gtk::Align::Fill);
-            priv_.room_name_entry.set_width_chars(-1);
-            priv_.room_topic_entry.set_sensitive(false);
-            priv_.room_topic_label.hide();
-
-            let room = this.room();
-
-            let room_name = priv_.room_name_entry.buffer().text();
-            let topic_buffer = priv_.room_topic_text_view.buffer();
-            let topic = topic_buffer.text(&topic_buffer.start_iter(), &topic_buffer.end_iter(), true);
-            room.store_room_name(room_name);
-            room.store_topic(topic.to_string());
-        }));
-
-        // Hide edit controls when the user is not eligible to perform the actions.
-        let room = self.room();
-        let room_name_changeable =
-            room.new_allowed_expr(RoomAction::StateEvent(RoomEventType::RoomName));
-        let room_topic_changeable =
-            room.new_allowed_expr(RoomAction::StateEvent(RoomEventType::RoomTopic));
-
-        let edit_toggle_visible = or_expr(room_name_changeable, room_topic_changeable);
-        edit_toggle_visible.bind(&edit_toggle.get(), "visible", gtk::Widget::NONE);
-    }
-
-    fn init_avatar_chooser(&self) {
-        let avatar_chooser = gtk::FileChooserNative::new(
-            Some(&gettext("Choose avatar")),
-            Some(self),
-            gtk::FileChooserAction::Open,
-            None,
-            None,
-        );
-        avatar_chooser.connect_response(clone!(@weak self as this => move |chooser, response| {
-            let file = chooser.file().and_then(|f| f.path());
-            if let (gtk::ResponseType::Accept, Some(file)) = (response, file) {
-                log::debug!("Chose file {:?}", file);
-                this.room().store_avatar(Some(file));
-            }
-        }));
-
-        // We must keep a reference to FileChooserNative around as it is not
-        // managed by GTK.
-        self.imp()
-            .avatar_chooser
-            .set(avatar_chooser)
-            .expect("File chooser already initialized");
-    }
-
-    fn avatar_chooser(&self) -> &gtk::FileChooserNative {
-        self.imp().avatar_chooser.get().unwrap()
-    }
-
-    fn open_avatar_chooser(&self) {
-        self.avatar_chooser().show();
-    }
-
-    fn member_count_changed(&self, n: u32) {
-        self.imp().members_count.set_text(&format!("{}", n));
-    }
-
     fn next_page(&self, next_page: PageName) {
         let priv_ = self.imp();
         let prev_page = self.visible_page();


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