[fractal] context-menu-bin: Fix non-working actions
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] context-menu-bin: Fix non-working actions
- Date: Wed, 13 Apr 2022 16:40:18 +0000 (UTC)
commit 5ac882eaa15ba0ae47d4581a78fda47242b3772e
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Wed Apr 13 18:21:22 2022 +0200
context-menu-bin: Fix non-working actions
Destroying the popover on close results in the action not being
called.
Instead do like the members list and keep a single popover for the
whole list. To do that we need to pass a weak reference to the
closest common parent of the list items, via the list view factory.
data/resources/resources.gresource.xml | 2 -
data/resources/style.css | 5 +-
data/resources/ui/content-item.ui | 18 ---
data/resources/ui/content-room-history.ui | 5 -
data/resources/ui/sidebar-item.ui | 13 --
data/resources/ui/sidebar-room-row.ui | 53 --------
data/resources/ui/sidebar.ui | 59 +++++++-
po/POTFILES.in | 1 -
src/components/context_menu_bin.rs | 192 ++++++++++++++++++++-------
src/session/content/room_history/item_row.rs | 128 ++++++++++++------
src/session/content/room_history/mod.rs | 20 ++-
src/session/sidebar/mod.rs | 28 +++-
src/session/sidebar/room_row.rs | 22 ++-
src/session/sidebar/row.rs | 39 ++++--
14 files changed, 373 insertions(+), 212 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 8f23ec3f8..a364c6e5a 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -48,7 +48,6 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="content-invite.ui">ui/content-invite.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-invitee-item.ui">ui/content-invitee-item.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-invitee-row.ui">ui/content-invitee-row.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-markdown-popover.ui">ui/content-markdown-popover.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-member-item.ui">ui/content-member-item.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-member-page.ui">ui/content-member-page.ui</file>
@@ -91,7 +90,6 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-account-switcher.ui">ui/sidebar-account-switcher.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-category-row.ui">ui/sidebar-category-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-entry-row.ui">ui/sidebar-entry-row.ui</file>
- <file compressed="true" preprocess="xml-stripblanks" alias="sidebar-item.ui">ui/sidebar-item.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-room-row.ui">ui/sidebar-room-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-verification-row.ui">ui/sidebar-verification-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 64e3e1dda..ee2cb0867 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -240,7 +240,6 @@ login {
.sidebar-list row > * {
margin: 0 12px;
- padding: 12px 6px;
border-radius: 6px;
transition-property: outline, outline-width, outline-offset, outline-color;
transition-duration: 300ms;
@@ -249,6 +248,10 @@ login {
outline-offset: 2px;
}
+.sidebar-list sidebar-row > * > box {
+ padding: 12px 6px;
+}
+
.sidebar-list row:focus-within > * {
outline-color: alpha(@accent_color, 0.5);
outline-width: 2px;
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
index 9e06944b6..35e85cc8d 100644
--- a/data/resources/ui/content-room-history.ui
+++ b/data/resources/ui/content-room-history.ui
@@ -201,11 +201,6 @@
<style>
<class name="navigation-sidebar"/>
</style>
- <property name="factory">
- <object class="GtkBuilderListItemFactory">
- <property name="resource">/org/gnome/Fractal/content-item.ui</property>
- </object>
- </property>
<property name="single-click-activate">True</property>
<accessibility>
<property name="label" translatable="yes">Room History</property>
diff --git a/data/resources/ui/sidebar-room-row.ui b/data/resources/ui/sidebar-room-row.ui
index d8ab375e8..a722ef3fa 100644
--- a/data/resources/ui/sidebar-room-row.ui
+++ b/data/resources/ui/sidebar-room-row.ui
@@ -1,58 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <menu id="room_row_menu">
- <section>
- <item>
- <attribute name="label" translatable="yes">_Accept</attribute>
- <attribute name="action">room-row.accept-invite</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">_Reject</attribute>
- <attribute name="action">room-row.reject-invite</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- </section>
- <section>
- <item>
- <attribute name="label" translatable="yes">Mark as _Favorite</attribute>
- <attribute name="action">room-row.set-favorite</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">Unmark as _Favorite</attribute>
- <attribute name="action">room-row.unset-favorite</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">Mark as Low _Priority</attribute>
- <attribute name="action">room-row.set-lowpriority</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">Unmark as Low _Priority</attribute>
- <attribute name="action">room-row.unset-lowpriority</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- </section>
- <section>
- <item>
- <attribute name="label" translatable="yes">_Leave Room</attribute>
- <attribute name="action">room-row.leave</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">Re_join Room</attribute>
- <attribute name="action">room-row.join</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">_Forget Room</attribute>
- <attribute name="action">room-row.forget</attribute>
- <attribute name="hidden-when">action-disabled</attribute>
- </item>
- </section>
- </menu>
<template class="SidebarRoomRow" parent="ContextMenuBin">
<child>
<object class="GtkBox">
diff --git a/data/resources/ui/sidebar.ui b/data/resources/ui/sidebar.ui
index 4dabb491a..2fe1a64ef 100644
--- a/data/resources/ui/sidebar.ui
+++ b/data/resources/ui/sidebar.ui
@@ -22,6 +22,59 @@
</item>
</section>
</menu>
+ <menu id="room_row_menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Accept</attribute>
+ <attribute name="action">room-row.accept-invite</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Reject</attribute>
+ <attribute name="action">room-row.reject-invite</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Mark as _Favorite</attribute>
+ <attribute name="action">room-row.set-favorite</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Unmark as _Favorite</attribute>
+ <attribute name="action">room-row.unset-favorite</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Mark as Low _Priority</attribute>
+ <attribute name="action">room-row.set-lowpriority</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Unmark as Low _Priority</attribute>
+ <attribute name="action">room-row.unset-lowpriority</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Leave Room</attribute>
+ <attribute name="action">room-row.leave</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Re_join Room</attribute>
+ <attribute name="action">room-row.join</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Forget Room</attribute>
+ <attribute name="action">room-row.forget</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </menu>
<template class="Sidebar" parent="AdwBin">
<child>
<object class="GtkBox">
@@ -98,11 +151,6 @@
<class name="sidebar-list"/>
</style>
<property name="single-click-activate">true</property>
- <property name="factory">
- <object class="GtkBuilderListItemFactory">
- <property name="resource">/org/gnome/Fractal/sidebar-item.ui</property>
- </object>
- </property>
<accessibility>
<property name="label" translatable="yes">Sidebar</property>
<property name="description" translatable="yes">Allows to navigate between rooms</property>
@@ -115,4 +163,3 @@
</child>
</template>
</interface>
-
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1f3367553..8f7a71ec4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -36,7 +36,6 @@ data/resources/ui/member-menu.ui
data/resources/ui/room-creation.ui
data/resources/ui/session-verification.ui
data/resources/ui/shortcuts.ui
-data/resources/ui/sidebar-room-row.ui
data/resources/ui/sidebar.ui
data/resources/ui/qr-code-scanner.ui
diff --git a/src/components/context_menu_bin.rs b/src/components/context_menu_bin.rs
index 758f61b27..16a2e57de 100644
--- a/src/components/context_menu_bin.rs
+++ b/src/components/context_menu_bin.rs
@@ -5,26 +5,43 @@ use log::debug;
mod imp {
use std::cell::RefCell;
- use glib::subclass::InitializingObject;
+ use glib::{subclass::InitializingObject, SignalHandlerId};
use super::*;
- type FactoryFn = RefCell<Option<Box<dyn Fn(&super::ContextMenuBin, >k::PopoverMenu)>>>;
- #[derive(Default, CompositeTemplate)]
+ #[repr(C)]
+ pub struct ContextMenuBinClass {
+ pub parent_class: glib::object::Class<adw::Bin>,
+ pub menu_opened: fn(&super::ContextMenuBin),
+ }
+
+ unsafe impl ClassStruct for ContextMenuBinClass {
+ type Type = ContextMenuBin;
+ }
+
+ pub(super) fn context_menu_bin_menu_opened(this: &super::ContextMenuBin) {
+ let klass = this.class();
+ (klass.as_ref().menu_opened)(this)
+ }
+
+ #[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/context-menu-bin.ui")]
pub struct ContextMenuBin {
#[template_child]
pub click_gesture: TemplateChild<gtk::GestureClick>,
#[template_child]
pub long_press_gesture: TemplateChild<gtk::GestureLongPress>,
- pub factory: FactoryFn,
+ pub popover: RefCell<Option<gtk::PopoverMenu>>,
+ pub signal_handler: RefCell<Option<SignalHandlerId>>,
}
#[glib::object_subclass]
impl ObjectSubclass for ContextMenuBin {
const NAME: &'static str = "ContextMenuBin";
+ const ABSTRACT: bool = true;
type Type = super::ContextMenuBin;
type ParentType = adw::Bin;
+ type Class = ContextMenuBinClass;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@@ -44,6 +61,12 @@ mod imp {
"context-menu.activate",
None,
);
+
+ klass.install_action("context-menu.close", None, move |widget, _, _| {
+ if let Some(popover) = widget.popover() {
+ popover.popdown();
+ }
+ });
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -52,6 +75,41 @@ mod imp {
}
impl ObjectImpl for ContextMenuBin {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpecObject::new(
+ "popover",
+ "Popover",
+ "The popover of the context menu",
+ gtk::PopoverMenu::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "popover" => obj.set_popover(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "popover" => obj.popover().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
fn constructed(&self, obj: &Self::Type) {
self.long_press_gesture
.connect_pressed(clone!(@weak obj => move |gesture, x, y| {
@@ -72,6 +130,12 @@ mod imp {
);
self.parent_constructed(obj);
}
+
+ fn dispose(&self, _obj: &Self::Type) {
+ if let Some(popover) = self.popover.take() {
+ popover.unparent()
+ }
+ }
}
impl WidgetImpl for ContextMenuBin {}
@@ -86,27 +150,12 @@ glib::wrapper! {
}
impl ContextMenuBin {
- pub fn new() -> Self {
- glib::Object::new(&[]).expect("Failed to create ContextMenuBin")
- }
-
fn open_menu_at(&self, x: i32, y: i32) {
- debug!("Context menu was activated");
- if let Some(factory) = &*self.imp().factory.borrow() {
- let popover = gtk::PopoverMenu::builder()
- .position(gtk::PositionType::Bottom)
- .has_arrow(false)
- .halign(gtk::Align::Start)
- .build();
-
- popover.set_parent(self);
-
- popover.connect_closed(|popover| {
- popover.unparent();
- });
-
- (factory)(self, &popover);
+ debug!("Open menu at ({x}, {y})");
+ self.menu_opened();
+ if let Some(popover) = self.popover() {
+ debug!("Context menu was activated");
popover.set_pointing_to(Some(&gdk::Rectangle::new(x, y, 0, 0)));
popover.popup();
}
@@ -114,43 +163,90 @@ impl ContextMenuBin {
}
pub trait ContextMenuBinExt: 'static {
- /// Set the closure used to create the content of the `gtk::PopoverMenu`
- fn set_factory<F>(&self, factory: F)
- where
- F: Fn(&Self, >k::PopoverMenu) + 'static;
+ /// Get the `PopoverMenu` used in the context menu.
+ fn popover(&self) -> Option<gtk::PopoverMenu>;
+
+ /// Set the `PopoverMenu` used in the context menu.
+ fn set_popover(&self, popover: Option<gtk::PopoverMenu>);
- fn remove_factory(&self);
+ /// Called when the menu was requested to open but before the menu is shown.
+ fn menu_opened(&self);
}
impl<O: IsA<ContextMenuBin>> ContextMenuBinExt for O {
- fn set_factory<F>(&self, factory: F)
- where
- F: Fn(&O, >k::PopoverMenu) + 'static,
- {
- let f = move |obj: &ContextMenuBin, popover: >k::PopoverMenu| {
- factory(obj.downcast_ref::<O>().unwrap(), popover);
- };
- self.upcast_ref().imp().factory.replace(Some(Box::new(f)));
+ fn popover(&self) -> Option<gtk::PopoverMenu> {
+ self.upcast_ref().imp().popover.borrow().clone()
}
- fn remove_factory(&self) {
- self.upcast_ref().imp().factory.take();
+ fn set_popover(&self, popover: Option<gtk::PopoverMenu>) {
+ let obj = self.upcast_ref();
+
+ if obj.popover() == popover {
+ return;
+ }
+
+ let priv_ = obj.imp();
+
+ if let Some(popover) = &popover {
+ popover.unparent();
+ popover.set_parent(obj);
+ priv_
+ .signal_handler
+ .replace(Some(popover.connect_parent_notify(
+ clone!(@weak obj => move |popover| {
+ if popover.parent().as_ref() != Some(obj.upcast_ref()) {
+ let priv_ = obj.imp();
+ if let Some(popover) = priv_.popover.take() {
+ if let Some(signal_handler) = priv_.signal_handler.take() {
+ popover.disconnect(signal_handler)
+ }
+ }
+ }
+ }),
+ )));
+ }
+
+ obj.imp().popover.replace(popover);
+ obj.notify("popover");
+ }
+
+ fn menu_opened(&self) {
+ imp::context_menu_bin_menu_opened(self.upcast_ref())
}
}
-pub trait ContextMenuBinImpl: BinImpl {}
+/// Public trait that must be implemented for everything that derives from
+/// `ContextMenuBin`.
+///
+/// Overriding a method from this Trait overrides also its behavior in
+/// `ContextMenuBinExt`.
+pub trait ContextMenuBinImpl: BinImpl {
+ /// Called when the menu was requested to open but before the menu is shown.
+ ///
+ /// This method should be used to set the popover dynamically.
+ fn menu_opened(&self, _obj: &Self::Type) {}
+}
-unsafe impl<T: ContextMenuBinImpl> IsSubclassable<T> for ContextMenuBin {
+unsafe impl<T> IsSubclassable<T> for ContextMenuBin
+where
+ T: ContextMenuBinImpl,
+ T::Type: IsA<ContextMenuBin>,
+{
fn class_init(class: &mut glib::Class<Self>) {
- <gtk::Widget as IsSubclassable<T>>::class_init(class);
- }
- fn instance_init(instance: &mut glib::subclass::InitializingObject<T>) {
- <gtk::Widget as IsSubclassable<T>>::instance_init(instance);
+ Self::parent_class_init::<T>(class.upcast_ref_mut());
+
+ let klass = class.as_mut();
+
+ klass.menu_opened = menu_opened_trampoline::<T>;
}
}
-impl Default for ContextMenuBin {
- fn default() -> Self {
- Self::new()
- }
+// Virtual method implementation trampolines.
+fn menu_opened_trampoline<T>(this: &ContextMenuBin)
+where
+ T: ObjectSubclass + ContextMenuBinImpl,
+ T::Type: IsA<ContextMenuBin>,
+{
+ let this = this.downcast_ref::<T::Type>().unwrap();
+ this.imp().menu_opened(this)
}
diff --git a/src/session/content/room_history/item_row.rs b/src/session/content/room_history/item_row.rs
index 49ac0f3c3..6e4b3862f 100644
--- a/src/session/content/room_history/item_row.rs
+++ b/src/session/content/room_history/item_row.rs
@@ -6,7 +6,7 @@ use matrix_sdk::ruma::events::AnySyncRoomEvent;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl, ReactionChooser},
session::{
- content::room_history::{message_row::MessageRow, DividerRow, StateRow},
+ content::room_history::{message_row::MessageRow, DividerRow, RoomHistory, StateRow},
room::{
Event, EventActions, TimelineDayDivider, TimelineItem, TimelineNewMessagesDivider,
TimelineSpinner,
@@ -17,14 +17,16 @@ use crate::{
mod imp {
use std::cell::RefCell;
- use glib::signal::SignalHandlerId;
+ use glib::{signal::SignalHandlerId, WeakRef};
+ use once_cell::unsync::OnceCell;
use super::*;
#[derive(Debug, Default)]
pub struct ItemRow {
+ pub room_history: OnceCell<WeakRef<RoomHistory>>,
pub item: RefCell<Option<TimelineItem>>,
- pub menu_model: RefCell<Option<gio::MenuModel>>,
+ pub action_group: RefCell<Option<gio::SimpleActionGroup>>,
pub notify_handler: RefCell<Option<SignalHandlerId>>,
pub binding: RefCell<Option<glib::Binding>>,
pub reaction_chooser: RefCell<Option<ReactionChooser>>,
@@ -42,13 +44,22 @@ mod imp {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
- vec![glib::ParamSpecObject::new(
- "item",
- "Item",
- "The timeline item represented by this row",
- TimelineItem::static_type(),
- glib::ParamFlags::READWRITE,
- )]
+ vec![
+ glib::ParamSpecObject::new(
+ "item",
+ "Item",
+ "The timeline item represented by this row",
+ TimelineItem::static_type(),
+ glib::ParamFlags::READWRITE,
+ ),
+ glib::ParamSpecObject::new(
+ "room-history",
+ "room-history",
+ "The ancestor room history of this row",
+ RoomHistory::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ ]
});
PROPERTIES.as_ref()
@@ -63,6 +74,10 @@ mod imp {
) {
match pspec.name() {
"item" => obj.set_item(value.get().unwrap()),
+ "room-history" => self
+ .room_history
+ .set(value.get::<RoomHistory>().unwrap().downgrade())
+ .unwrap(),
_ => unimplemented!(),
}
}
@@ -70,6 +85,7 @@ mod imp {
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"item" => obj.item().to_value(),
+ "room-history" => obj.room_history().to_value(),
_ => unimplemented!(),
}
}
@@ -90,7 +106,40 @@ mod imp {
impl WidgetImpl for ItemRow {}
impl BinImpl for ItemRow {}
- impl ContextMenuBinImpl for ItemRow {}
+
+ impl ContextMenuBinImpl for ItemRow {
+ fn menu_opened(&self, obj: &Self::Type) {
+ if let Some(event) = obj.item().and_then(|item| item.downcast::<Event>().ok()) {
+ let popover = obj.room_history().item_context_menu().clone();
+
+ if event.message_content().is_some() {
+ let menu_model = Self::Type::event_message_menu_model();
+
+ if popover.menu_model().as_ref() != Some(menu_model) {
+ let action_group = obj.action_group().unwrap();
+ popover.set_menu_model(Some(menu_model));
+
+ let reaction_chooser = ReactionChooser::new();
+ reaction_chooser.set_reactions(Some(event.reactions().to_owned()));
+ popover.add_child(&reaction_chooser, "reaction-chooser");
+
+ // Open emoji chooser
+ let more_reactions = gio::SimpleAction::new("more-reactions", None);
+ more_reactions.connect_activate(
+ clone!(@weak obj, @weak popover => move |_, _| {
+ obj.show_emoji_chooser(&popover);
+ }),
+ );
+ action_group.add_action(&more_reactions);
+ }
+ } else {
+ popover.set_menu_model(Some(Self::Type::event_state_menu_model()));
+ }
+
+ obj.set_popover(Some(popover));
+ }
+ }
+ }
}
glib::wrapper! {
@@ -101,8 +150,24 @@ glib::wrapper! {
// TODO:
// - [ ] 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")
+ pub fn new(room_history: &RoomHistory) -> Self {
+ glib::Object::new(&[("room-history", room_history)]).expect("Failed to create ItemRow")
+ }
+
+ pub fn room_history(&self) -> RoomHistory {
+ self.imp().room_history.get().unwrap().upgrade().unwrap()
+ }
+
+ pub fn action_group(&self) -> Option<gio::SimpleActionGroup> {
+ self.imp().action_group.borrow().clone()
+ }
+
+ fn set_action_group(&self, action_group: Option<gio::SimpleActionGroup>) {
+ if self.action_group() == action_group {
+ return;
+ }
+
+ self.imp().action_group.replace(action_group);
}
/// Get the row's [`TimelineItem`].
@@ -133,26 +198,7 @@ impl ItemRow {
if let Some(ref item) = item {
if let Some(event) = item.downcast_ref::<Event>() {
- if event.message_content().is_some() {
- let action_group = self.set_event_actions(Some(event)).unwrap();
- self.set_factory(clone!(@weak event => move |obj, popover| {
- popover.set_menu_model(Some(Self::event_message_menu_model()));
- let reaction_chooser = ReactionChooser::new();
- reaction_chooser.set_reactions(Some(event.reactions().to_owned()));
- popover.add_child(&reaction_chooser, "reaction-chooser");
-
- // Open emoji chooser
- let more_reactions = gio::SimpleAction::new("more-reactions", None);
- more_reactions.connect_activate(clone!(@weak obj, @weak popover => move |_, _| {
- obj.show_emoji_chooser(&popover);
- }));
- action_group.add_action(&more_reactions);
- }));
- } else {
- self.set_factory(|_, popover| {
- popover.set_menu_model(Some(Self::event_state_menu_model()));
- });
- }
+ self.set_action_group(self.set_event_actions(Some(event)));
let notify_handler = event.connect_notify_local(
Some("event"),
@@ -164,7 +210,8 @@ impl ItemRow {
self.set_event_widget(event);
} else if let Some(divider) = item.downcast_ref::<TimelineDayDivider>() {
- self.remove_factory();
+ self.set_popover(None);
+ self.set_action_group(None);
self.set_event_actions(None);
let child = if let Some(child) =
@@ -188,6 +235,10 @@ impl ItemRow {
.filter(|widget| widget.is::<gtk::Spinner>())
.is_none()
{
+ self.set_popover(None);
+ self.set_action_group(None);
+ self.set_event_actions(None);
+
let spinner = gtk::Spinner::builder()
.spinning(true)
.margin_top(12)
@@ -195,7 +246,8 @@ impl ItemRow {
.build();
self.set_child(Some(&spinner));
} else if item.downcast_ref::<TimelineNewMessagesDivider>().is_some() {
- self.remove_factory();
+ self.set_popover(None);
+ self.set_action_group(None);
self.set_event_actions(None);
let label = gettext("New Messages");
@@ -258,10 +310,4 @@ impl ItemRow {
}
}
-impl Default for ItemRow {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl EventActions for ItemRow {}
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index 2b32b174b..d74d28b8c 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -37,7 +37,7 @@ use crate::{
i18n::gettext_f,
session::{
content::{MarkdownPopover, RoomDetails},
- room::{Event, Room, RoomType, Timeline, TimelineState},
+ room::{Event, Room, RoomType, Timeline, TimelineItem, TimelineState},
user::UserExt,
},
spawn,
@@ -55,6 +55,7 @@ mod imp {
use std::cell::{Cell, RefCell};
use glib::{signal::SignalHandlerId, subclass::InitializingObject};
+ use once_cell::unsync::OnceCell;
use super::*;
use crate::{components::Toast, window::Window, Application};
@@ -70,6 +71,7 @@ mod imp {
pub md_enabled: Cell<bool>,
pub is_auto_scrolling: Cell<bool>,
pub sticky: Cell<bool>,
+ pub item_context_menu: OnceCell<gtk::PopoverMenu>,
#[template_child]
pub headerbar: TemplateChild<adw::HeaderBar>,
#[template_child]
@@ -270,6 +272,16 @@ mod imp {
}
fn constructed(&self, obj: &Self::Type) {
+ let factory = gtk::SignalListItemFactory::new();
+ factory.connect_setup(clone!(@weak obj => move |_, item| {
+ let row = ItemRow::new(&obj);
+ item.set_child(Some(&row));
+ ItemRow::this_expression("item").chain_property::<TimelineItem>("activatable").bind(item,
"activatable", Some(&row));
+ item.bind_property("item", &row, "item").build();
+ item.set_selectable(false);
+ }));
+ self.listview.set_factory(Some(&factory));
+
// Needed to use the natural height of GtkPictures
self.listview
.set_vscroll_policy(gtk::ScrollablePolicy::Natural);
@@ -926,6 +938,12 @@ impl RoomHistory {
Err(err) => log::debug!("Could not read file: {}", err),
}
}
+
+ pub fn item_context_menu(&self) -> >k::PopoverMenu {
+ self.imp()
+ .item_context_menu
+ .get_or_init(|| gtk::PopoverMenu::from_model(gio::MenuModel::NONE))
+ }
}
impl Default for RoomHistory {
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index c4e4d5596..199ac91f1 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -14,7 +14,12 @@ mod verification_row;
use account_switcher::AccountSwitcher;
use adw::{prelude::*, subclass::prelude::*};
-use gtk::{gio, glib, glib::closure, subclass::prelude::*, CompositeTemplate, SelectionModel};
+use gtk::{
+ gio, glib,
+ glib::{clone, closure},
+ subclass::prelude::*,
+ CompositeTemplate, SelectionModel,
+};
pub use self::{
category::Category,
@@ -44,7 +49,7 @@ mod imp {
};
use glib::subclass::InitializingObject;
- use once_cell::sync::Lazy;
+ use once_cell::{sync::Lazy, unsync::OnceCell};
use super::*;
@@ -63,6 +68,9 @@ mod imp {
pub room_search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child]
pub room_search: TemplateChild<gtk::SearchBar>,
+ #[template_child]
+ pub room_row_menu: TemplateChild<gio::MenuModel>,
+ pub room_row_popover: OnceCell<gtk::PopoverMenu>,
pub user: RefCell<Option<User>>,
/// The type of the source that activated drop mode.
pub drop_source_type: Cell<Option<RoomType>>,
@@ -205,6 +213,15 @@ mod imp {
fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj);
+ let factory = gtk::SignalListItemFactory::new();
+ factory.connect_setup(clone!(@weak obj => move |_, item| {
+ let row = Row::new(&obj);
+ item.set_child(Some(&row));
+ item.bind_property("item", &row, "list-row").build();
+ row.set_can_focus(false);
+ }));
+ self.listview.set_factory(Some(&factory));
+
self.listview.connect_activate(move |listview, pos| {
let model: Option<Selection> = listview.model().and_then(|o| o.downcast().ok());
let row: Option<gtk::TreeListRow> = model
@@ -464,6 +481,13 @@ impl Sidebar {
child = widget.next_sibling();
}
}
+
+ pub fn room_row_popover(&self) -> >k::PopoverMenu {
+ let priv_ = self.imp();
+ priv_
+ .room_row_popover
+ .get_or_init(|| gtk::PopoverMenu::from_model(Some(&*priv_.room_row_menu)))
+ }
}
impl Default for Sidebar {
diff --git a/src/session/sidebar/room_row.rs b/src/session/sidebar/room_row.rs
index 53de48e2d..144ae42fa 100644
--- a/src/session/sidebar/room_row.rs
+++ b/src/session/sidebar/room_row.rs
@@ -1,6 +1,7 @@
use adw::subclass::prelude::BinImpl;
use gtk::{gdk, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use super::Row;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl},
session::room::{HighlightFlags, Room, RoomType},
@@ -24,8 +25,6 @@ mod imp {
pub display_name: TemplateChild<gtk::Label>,
#[template_child]
pub notification_count: TemplateChild<gtk::Label>,
- #[template_child]
- pub room_row_menu: TemplateChild<gtk::gio::MenuModel>,
}
#[glib::object_subclass]
@@ -113,10 +112,6 @@ mod imp {
fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj);
- obj.set_factory(|obj, popover| {
- popover.set_menu_model(Some(&obj.imp().room_row_menu.get()));
- });
-
// Allow to drag rooms
let drag = gtk::DragSource::builder()
.actions(gdk::DragAction::MOVE)
@@ -146,7 +141,20 @@ mod imp {
impl WidgetImpl for RoomRow {}
impl BinImpl for RoomRow {}
- impl ContextMenuBinImpl for RoomRow {}
+
+ impl ContextMenuBinImpl for RoomRow {
+ fn menu_opened(&self, obj: &Self::Type) {
+ if let Some(sidebar) = obj
+ .parent()
+ .as_ref()
+ .and_then(|obj| obj.downcast_ref::<Row>())
+ .map(|row| row.sidebar())
+ {
+ let popover = sidebar.room_row_popover();
+ obj.set_popover(Some(popover.to_owned()));
+ }
+ }
+ }
}
glib::wrapper! {
diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs
index eda20b99f..1323bb3f5 100644
--- a/src/session/sidebar/row.rs
+++ b/src/session/sidebar/row.rs
@@ -6,19 +6,23 @@ use gtk::{gdk, glib, glib::clone, subclass::prelude::*};
use super::EntryType;
use crate::session::{
room::{Room, RoomType},
- sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow, SidebarItem, VerificationRow},
+ sidebar::{
+ Category, CategoryRow, Entry, EntryRow, RoomRow, Sidebar, SidebarItem, VerificationRow,
+ },
verification::IdentityVerification,
};
mod imp {
use std::cell::RefCell;
- use once_cell::sync::Lazy;
+ use glib::WeakRef;
+ use once_cell::{sync::Lazy, unsync::OnceCell};
use super::*;
#[derive(Debug, Default)]
pub struct Row {
+ pub sidebar: OnceCell<WeakRef<Sidebar>>,
pub list_row: RefCell<Option<gtk::TreeListRow>>,
pub bindings: RefCell<Vec<glib::Binding>>,
}
@@ -52,6 +56,13 @@ mod imp {
gtk::TreeListRow::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
+ glib::ParamSpecObject::new(
+ "sidebar",
+ "sidebar",
+ "The ancestor sidebar of this row",
+ Sidebar::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
]
});
@@ -66,10 +77,11 @@ mod imp {
pspec: &glib::ParamSpec,
) {
match pspec.name() {
- "list-row" => {
- let list_row = value.get().unwrap();
- obj.set_list_row(list_row);
- }
+ "list-row" => obj.set_list_row(value.get().unwrap()),
+ "sidebar" => self
+ .sidebar
+ .set(value.get::<Sidebar>().unwrap().downgrade())
+ .unwrap(),
_ => unimplemented!(),
}
}
@@ -78,6 +90,7 @@ mod imp {
match pspec.name() {
"item" => obj.item().to_value(),
"list-row" => obj.list_row().to_value(),
+ "sidebar" => obj.sidebar().to_value(),
_ => unimplemented!(),
}
}
@@ -115,8 +128,12 @@ glib::wrapper! {
}
impl Row {
- pub fn new() -> Self {
- glib::Object::new(&[]).expect("Failed to create Row")
+ pub fn new(sidebar: &Sidebar) -> Self {
+ glib::Object::new(&[("sidebar", sidebar)]).expect("Failed to create Row")
+ }
+
+ pub fn sidebar(&self) -> Sidebar {
+ self.imp().sidebar.get().unwrap().upgrade().unwrap()
}
pub fn item(&self) -> Option<SidebarItem> {
@@ -315,9 +332,3 @@ impl Row {
ret
}
}
-
-impl Default for Row {
- fn default() -> Self {
- Self::new()
- }
-}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]