[fractal/fractal-next] content: Add context menu for ItemRow



commit 727d063f602d5ebd6f7d1dbbd59d7fe7acebaa7d
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Fri May 28 11:22:51 2021 +0200

    content: Add context menu for ItemRow

 data/resources/resources.gresource.xml     |   1 +
 data/resources/ui/content-item-row-menu.ui | 163 +++++++++++------------------
 src/session/content/item_row.rs            | 156 +++++++++++++++++----------
 src/session/content/message_row.rs         |   6 +-
 4 files changed, 169 insertions(+), 157 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index f106321d..3355c92b 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -5,6 +5,7 @@
     <file compressed="true" preprocess="xml-stripblanks" alias="content.ui">ui/content.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-item.ui">ui/content-item.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="content-item-row-menu.ui">ui/content-item-row-menu.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-divider-row.ui">ui/content-divider-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-state-row.ui">ui/content-state-row.ui</file>
diff --git a/data/resources/ui/content-item-row-menu.ui b/data/resources/ui/content-item-row-menu.ui
index 5e4fe940..54e3c418 100644
--- a/data/resources/ui/content-item-row-menu.ui
+++ b/data/resources/ui/content-item-row-menu.ui
@@ -1,102 +1,67 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <object class="GtkPopoverMenu" id="message_menu_popover">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="margin_start">6</property>
-        <property name="margin_end">6</property>
-        <property name="margin_top">6</property>
-        <property name="margin_bottom">6</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkModelButton" id="reply_button">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="action_name">message.reply</property>
-            <property name="text" translatable="yes">Reply</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="open_with_button">
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="text" translatable="yes">Open With…</property>
-            <property name="action_name">message.open_with</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="save_image_as_button">
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="text" translatable="yes">Save Image As…</property>
-            <property name="action_name">message.save_as</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="save_video_as_button">
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="text" translatable="yes">Save Video As…</property>
-            <property name="action_name">message.save_as</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="copy_image_button">
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="text" translatable="yes">Copy Image</property>
-            <property name="action_name">message.copy_image</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="copy_selected_text_button">
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="text" translatable="yes">Copy Selection</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="copy_text_button">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="text" translatable="yes">Copy Text</property>
-            <property name="action_name">message.copy_text</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="view_source_button">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="action_name">message.show_source</property>
-            <property name="text" translatable="yes">View Source</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkSeparator" id="message_menu_separator">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="delete_message_button">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="action_name">message.delete</property>
-            <property name="text" translatable="yes">Delete Message</property>
-          </object>
-        </child>
-      </object>
-      <packing>
-        <property name="submenu">main</property>
-        <property name="position">1</property>
-      </packing>
-    </child>
-  </object>
+  <menu id="menu_model">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Reply</attribute>
+        <attribute name="action">item-row.reply</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Edit</attribute>
+        <attribute name="action">item-row.edit</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Forward</attribute>
+        <attribute name="action">item-row.forward</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Select</attribute>
+        <attribute name="action">item-row.select</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Copy Text</attribute>
+        <attribute name="action">item-row.copy-text</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Copy Image</attribute>
+        <attribute name="action">item-row.copy-image</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">S_ave Image</attribute>
+        <attribute name="action">item-row.save-image</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Permalink</attribute>
+        <attribute name="action">item-row.permalink</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_View Source</attribute>
+        <attribute name="action">item-row.view-source</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Re_move</attribute>
+        <attribute name="action">item-row.remove</attribute>
+        <attribute name="hidden-when">action-missing</attribute>
+      </item>
+    </section>
+  </menu>
 </interface>
+
diff --git a/src/session/content/item_row.rs b/src/session/content/item_row.rs
index 9254a404..b728d29c 100644
--- a/src/session/content/item_row.rs
+++ b/src/session/content/item_row.rs
@@ -1,8 +1,8 @@
 use adw::{prelude::*, subclass::prelude::*};
 use gettextrs::gettext;
-use gtk::{glib, prelude::*, subclass::prelude::*};
+use gtk::{gio, glib, prelude::*, subclass::prelude::*};
 
-use crate::components::{ContextMenuBin, ContextMenuBinImpl};
+use crate::components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl};
 use crate::session::content::{DividerRow, MessageRow, StateRow};
 use crate::session::room::{Item, ItemType};
 use matrix_sdk::events::AnyRoomEvent;
@@ -14,6 +14,7 @@ mod imp {
     #[derive(Debug, Default)]
     pub struct ItemRow {
         pub item: RefCell<Option<Item>>,
+        pub menu_model: RefCell<Option<gio::MenuModel>>,
     }
 
     #[glib::object_subclass]
@@ -21,6 +22,16 @@ mod imp {
         const NAME: &'static str = "ContentItemRow";
         type Type = super::ItemRow;
         type ParentType = ContextMenuBin;
+
+        fn class_init(klass: &mut Self::Class) {
+            // View Event Source
+            klass.install_action("item-row.view-source", None, move |widget, _, _| {
+                let window = widget.root().unwrap().downcast().unwrap();
+                let dialog =
+                    EventSourceDialog::new(&window, widget.item().unwrap().event().unwrap());
+                dialog.show();
+            });
+        }
     }
 
     impl ObjectImpl for ItemRow {
@@ -61,10 +72,6 @@ mod imp {
                 _ => unimplemented!(),
             }
         }
-
-        fn constructed(&self, obj: &Self::Type) {
-            self.parent_constructed(obj);
-        }
     }
 
     impl WidgetImpl for ItemRow {}
@@ -74,17 +81,30 @@ mod imp {
 
 glib::wrapper! {
     pub struct ItemRow(ObjectSubclass<imp::ItemRow>)
-        @extends gtk::Widget, ContextMenuBin, adw::Bin, @implements gtk::Accessible;
+        @extends gtk::Widget, adw::Bin, ContextMenuBin, @implements gtk::Accessible;
 }
 
 // TODO:
-// - [ ] Add context menu for operations
 // - [ ] Don't show rows for items that don't have a visible UI
 impl ItemRow {
     pub fn new() -> Self {
         glib::Object::new(&[]).expect("Failed to create ItemRow")
     }
 
+    /// Get the row's `Item`.
+    pub fn item(&self) -> Option<Item> {
+        let priv_ = imp::ItemRow::from_instance(&self);
+        priv_.item.borrow().clone()
+    }
+
+    fn enable_gactions(&self) {
+        self.action_set_enabled("item-row.view-source", true);
+    }
+
+    fn disable_gactions(&self) {
+        self.action_set_enabled("item-row.view-source", false);
+    }
+
     /// This method sets this row to a new `Item`.
     ///
     /// It tries to reuse the widget and only update the content whenever possible, but it will
@@ -94,58 +114,75 @@ impl ItemRow {
 
         if let Some(ref item) = item {
             match item.type_() {
-                ItemType::Event(event) => match event.matrix_event() {
-                    AnyRoomEvent::Message(_message) => {
-                        let child = if let Some(Ok(child)) =
-                            self.child().map(|w| w.downcast::<MessageRow>())
-                        {
-                            child
-                        } else {
-                            let child = MessageRow::new();
-                            self.set_child(Some(&child));
-                            child
-                        };
-                        child.set_event(event.clone());
-                    }
-                    AnyRoomEvent::State(state) => {
-                        let child = if let Some(Ok(child)) =
-                            self.child().map(|w| w.downcast::<StateRow>())
-                        {
-                            child
-                        } else {
-                            let child = StateRow::new();
-                            self.set_child(Some(&child));
-                            child
-                        };
-
-                        child.update(&state);
+                ItemType::Event(event) => {
+                    if self.context_menu().is_none() {
+                        let menu_model = gtk::Builder::from_resource(
+                            "/org/gnome/FractalNext/content-item-row-menu.ui",
+                        )
+                        .object("menu_model");
+                        self.set_context_menu(menu_model);
+
+                        self.enable_gactions();
                     }
-                    AnyRoomEvent::RedactedMessage(_) => {
-                        let child = if let Some(Ok(child)) =
-                            self.child().map(|w| w.downcast::<MessageRow>())
-                        {
-                            child
-                        } else {
-                            let child = MessageRow::new();
-                            self.set_child(Some(&child));
-                            child
-                        };
-                        child.set_event(event.clone());
-                    }
-                    AnyRoomEvent::RedactedState(_) => {
-                        let child = if let Some(Ok(child)) =
-                            self.child().map(|w| w.downcast::<MessageRow>())
-                        {
-                            child
-                        } else {
-                            let child = MessageRow::new();
-                            self.set_child(Some(&child));
-                            child
-                        };
-                        child.set_event(event.clone());
+
+                    match event.matrix_event() {
+                        AnyRoomEvent::Message(_message) => {
+                            let child = if let Some(Ok(child)) =
+                                self.child().map(|w| w.downcast::<MessageRow>())
+                            {
+                                child
+                            } else {
+                                let child = MessageRow::new();
+                                self.set_child(Some(&child));
+                                child
+                            };
+                            child.set_event(event.clone());
+                        }
+                        AnyRoomEvent::State(state) => {
+                            let child = if let Some(Ok(child)) =
+                                self.child().map(|w| w.downcast::<StateRow>())
+                            {
+                                child
+                            } else {
+                                let child = StateRow::new();
+                                self.set_child(Some(&child));
+                                child
+                            };
+
+                            child.update(&state);
+                        }
+                        AnyRoomEvent::RedactedMessage(_) => {
+                            let child = if let Some(Ok(child)) =
+                                self.child().map(|w| w.downcast::<MessageRow>())
+                            {
+                                child
+                            } else {
+                                let child = MessageRow::new();
+                                self.set_child(Some(&child));
+                                child
+                            };
+                            child.set_event(event.clone());
+                        }
+                        AnyRoomEvent::RedactedState(_) => {
+                            let child = if let Some(Ok(child)) =
+                                self.child().map(|w| w.downcast::<MessageRow>())
+                            {
+                                child
+                            } else {
+                                let child = MessageRow::new();
+                                self.set_child(Some(&child));
+                                child
+                            };
+                            child.set_event(event.clone());
+                        }
                     }
-                },
+                }
                 ItemType::DayDivider(date) => {
+                    if self.context_menu().is_some() {
+                        self.set_context_menu(None);
+                        self.disable_gactions();
+                    }
+
                     let fmt = if date.year() == glib::DateTime::new_now_local().unwrap().year() {
                         // Translators: This is a date format in the day divider without the year
                         gettext("%A, %B %e")
@@ -163,6 +200,11 @@ impl ItemRow {
                     };
                 }
                 ItemType::NewMessageDivider => {
+                    if self.context_menu().is_some() {
+                        self.set_context_menu(None);
+                        self.disable_gactions();
+                    }
+
                     let label = gettext("New Messages");
 
                     if let Some(Ok(child)) = self.child().map(|w| w.downcast::<DividerRow>()) {
diff --git a/src/session/content/message_row.rs b/src/session/content/message_row.rs
index 469d45b4..82b41b42 100644
--- a/src/session/content/message_row.rs
+++ b/src/session/content/message_row.rs
@@ -1,6 +1,6 @@
 use adw::{prelude::*, subclass::prelude::*};
 use gtk::{
-    glib, glib::clone, glib::signal::SignalHandlerId, prelude::*, subclass::prelude::*,
+    gio, glib, glib::clone, glib::signal::SignalHandlerId, prelude::*, subclass::prelude::*,
     CompositeTemplate,
 };
 use html2pango::{
@@ -352,6 +352,10 @@ fn set_label_styles(w: &gtk::Label) {
     w.set_valign(gtk::Align::Start);
     w.set_halign(gtk::Align::Fill);
     w.set_selectable(true);
+    let menu_model: Option<gio::MenuModel> =
+        gtk::Builder::from_resource("/org/gnome/FractalNext/content-item-row-menu.ui")
+            .object("menu_model");
+    w.set_extra_menu(menu_model.as_ref());
 }
 
 fn create_widget_for_html_block(block: &HtmlBlock) -> gtk::Widget {


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