[fractal/fractal-next] content: Add context menu for ItemRow
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] content: Add context menu for ItemRow
- Date: Mon, 31 May 2021 16:18:15 +0000 (UTC)
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: >k::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]