Browse Source

content: Add context menu for ItemRow

merge-requests/1327/merge
Kévin Commaille 5 years ago committed by Julian Sparber
parent
commit
727d063f60
  1. 1
      data/resources/resources.gresource.xml
  2. 163
      data/resources/ui/content-item-row-menu.ui
  3. 156
      src/session/content/item_row.rs
  4. 6
      src/session/content/message_row.rs

1
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>

163
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>

156
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>()) {

6
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 {

Loading…
Cancel
Save