diff --git a/po/POTFILES.in b/po/POTFILES.in index f33ca0d4..12ae55d6 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -25,7 +25,6 @@ src/components/dialogs/user_profile.ui src/components/offline_banner.rs src/components/media/content_viewer.rs src/components/media/location_viewer.rs -src/components/quick_reaction_chooser.ui src/components/pill/at_room.rs src/components/power_level_selection/popover.ui src/components/rows/loading_row.ui @@ -135,8 +134,9 @@ src/session/view/content/room_details/permissions/permissions_subpage.rs src/session/view/content/room_details/permissions/permissions_subpage.ui src/session/view/content/room_details/room_upgrade_dialog.rs src/session/view/content/room_history/divider_row.rs -src/session/view/content/room_history/event_actions.ui +src/session/view/content/room_history/event_context_menu.ui src/session/view/content/room_history/item_row.rs +src/session/view/content/room_history/item_row_context_menu.rs src/session/view/content/room_history/message_row/audio.rs src/session/view/content/room_history/message_row/content.rs src/session/view/content/room_history/message_row/file.rs @@ -157,6 +157,7 @@ src/session/view/content/room_history/message_toolbar/mod.ui src/session/view/content/room_history/member_timestamp/row.rs src/session/view/content/room_history/mod.rs src/session/view/content/room_history/mod.ui +src/session/view/content/room_history/quick_reaction_chooser.ui src/session/view/content/room_history/read_receipts_list/mod.rs src/session/view/content/room_history/sender_avatar/mod.rs src/session/view/content/room_history/sender_avatar/mod.ui diff --git a/src/components/mod.rs b/src/components/mod.rs index ad4ba4c1..4a9937c7 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -11,7 +11,6 @@ mod media; mod offline_banner; mod pill; mod power_level_selection; -mod quick_reaction_chooser; mod role_badge; mod rows; mod scale_revealer; @@ -30,7 +29,6 @@ pub(crate) use self::{ offline_banner::OfflineBanner, pill::*, power_level_selection::*, - quick_reaction_chooser::QuickReactionChooser, role_badge::RoleBadge, rows::*, scale_revealer::ScaleRevealer, diff --git a/src/session/view/content/room_history/event_actions.ui b/src/session/view/content/room_history/event_actions.ui deleted file mode 100644 index 6dcee1b0..00000000 --- a/src/session/view/content/room_history/event_actions.ui +++ /dev/null @@ -1,213 +0,0 @@ - - - -
- - quick-reaction-chooser - -
-
- - - _Reply - event.reply - action-missing - - - - _Edit - event.edit - action-missing - - - - _Forward - event.forward - action-missing - -
-
- - _Select - event.select - action-missing - -
-
- - _Copy Text - event.copy-text - action-missing - - - Copy T_humbnail - event.copy-image - action-missing - - - S_ave Image - event.save-image - action-missing - - - S_ave Video - event.save-video - action-missing - - - S_ave Audio - event.save-audio - action-missing - - - Copy Message _Link - event.permalink - action-missing - - - _View Details - event.view-details - action-missing - -
-
- - _Discard - event.cancel-send - action-missing - -
-
- - Rep_ort… - event.report - action-missing - - - Re_move - event.remove - action-missing - -
-
- -
- - - _Reply - event.reply - action-missing - - - - _Edit - event.edit - action-missing - - - - _Forward - event.forward - action-missing - -
-
- - _Select - event.select - action-missing - -
-
- - _Copy Text - event.copy-text - action-missing - - - _Copy Thumbnail - event.copy-image - action-missing - - - S_ave Image - event.save-image - action-missing - - - S_ave Video - event.save-video - action-missing - - - S_ave Audio - event.save-audio - action-missing - - - Copy Message _Link - event.permalink - action-missing - - - _View Details - event.view-details - action-missing - -
-
- - _Discard - event.cancel-send - action-missing - -
-
- - Rep_ort - event.report - action-missing - - - Re_move - event.remove - action-missing - -
-
- -
- - _Select - event.select - action-missing - -
-
- - Copy Message _Link - event.permalink - action-missing - - - _View Details - event.view-details - action-missing - -
-
- - Rep_ort - event.report - action-missing - - - Re_move - event.remove - action-missing - -
-
-
diff --git a/src/session/view/content/room_history/event_context_menu.ui b/src/session/view/content/room_history/event_context_menu.ui new file mode 100644 index 00000000..1ebee1f2 --- /dev/null +++ b/src/session/view/content/room_history/event_context_menu.ui @@ -0,0 +1,89 @@ + + + +
+ + + _Reply + event.reply + action-missing + + + + _Edit + event.edit + action-missing + + + + _Forward + event.forward + action-missing + +
+
+ + _Select + event.select + action-missing + +
+
+ + _Copy Text + event.copy-text + action-missing + + + Copy T_humbnail + event.copy-image + action-missing + + + S_ave Image + event.save-image + action-missing + + + S_ave Video + event.save-video + action-missing + + + S_ave Audio + event.save-audio + action-missing + + + Copy Message _Link + event.permalink + action-missing + + + _View Details + event.view-details + action-missing + +
+
+ + _Discard + event.cancel-send + action-missing + +
+
+ + Rep_ort… + event.report + action-missing + + + Re_move + event.remove + action-missing + +
+
+
diff --git a/src/session/view/content/room_history/item_row.rs b/src/session/view/content/room_history/item_row.rs index a743e1f4..c3160453 100644 --- a/src/session/view/content/room_history/item_row.rs +++ b/src/session/view/content/room_history/item_row.rs @@ -1,5 +1,3 @@ -use std::sync::LazyLock; - use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; use gtk::{gio, glib, glib::clone}; @@ -132,81 +130,39 @@ mod imp { return; }; - let popover = room_history.item_context_menu().to_owned(); - room_history.enable_sticky_mode(false); - - obj.add_css_class("has-open-popup"); + let menu = room_history.item_context_menu(); + // Reset the state when the popover is closed. let closed_handler_cell: Rc>> = Rc::default(); - let quick_reaction_chooser_handler_cell: Rc< - RefCell>, - > = Rc::default(); - let closed_handler = popover.connect_closed(clone!( + let closed_handler = menu.popover.connect_closed(clone!( #[weak] obj, #[weak] room_history, #[strong] closed_handler_cell, - #[strong] - quick_reaction_chooser_handler_cell, move |popover| { room_history.enable_sticky_mode(true); - obj.remove_css_class("has-open-popup"); if let Some(handler) = closed_handler_cell.take() { popover.disconnect(handler); } - if let Some(handler) = quick_reaction_chooser_handler_cell.take() { - room_history - .item_quick_reaction_chooser() - .disconnect(handler); - } } )); closed_handler_cell.replace(Some(closed_handler)); - if event.is_message() { - let can_be_reacted_to = event.can_be_reacted_to(); - let menu_model = if can_be_reacted_to { - event_message_menu_model_with_reactions() - } else { - event_message_menu_model_no_reactions() - }; - - if popover.menu_model().as_ref() != Some(menu_model) { - popover.set_menu_model(Some(menu_model)); - } - - if can_be_reacted_to { - let quick_reaction_chooser = room_history.item_quick_reaction_chooser(); - quick_reaction_chooser.set_reactions(Some(event.reactions())); - popover.add_child(quick_reaction_chooser, "quick-reaction-chooser"); - - // Open emoji chooser - let quick_reaction_chooser_handler = quick_reaction_chooser - .connect_more_reactions_activated(clone!( - #[weak(rename_to = imp)] - self, - #[weak] - popover, - move |_| { - imp.show_reactions_chooser(&popover); - } - )); - quick_reaction_chooser_handler_cell - .replace(Some(quick_reaction_chooser_handler)); - } + if event.can_be_reacted_to() { + menu.add_quick_reaction_chooser(event.reactions()); } else { - let menu_model = event_state_menu_model(); - if popover.menu_model().as_ref() != Some(menu_model) { - popover.set_menu_model(Some(menu_model)); - } + menu.remove_quick_reaction_chooser(); } - obj.set_popover(Some(popover)); + room_history.enable_sticky_mode(false); + obj.add_css_class("has-open-popup"); + + obj.set_popover(Some(menu.popover.clone())); } } @@ -476,9 +432,14 @@ mod imp { } } - /// Replace the given popover with an emoji chooser for reactions. - fn show_reactions_chooser(&self, popover: >k::PopoverMenu) { + /// Replace the context menu with an emoji chooser for reactions. + fn show_reactions_chooser(&self) { let obj = self.obj(); + + let Some(popover) = obj.popover() else { + return; + }; + let (_, rectangle) = popover.pointing_to(); let emoji_chooser = gtk::EmojiChooser::builder() @@ -660,23 +621,34 @@ mod imp { // Send/redact a reaction. if event.can_be_reacted_to() { - action_group.add_action_entries([gio::ActionEntry::builder("toggle-reaction") - .parameter_type(Some(&String::static_variant_type())) - .activate(clone!( - #[weak(rename_to = imp)] - self, - move |_, _, variant| { - let Some(key) = variant.unwrap().get::() else { - error!("Could not parse reaction to toggle"); - return; - }; + action_group.add_action_entries([ + gio::ActionEntry::builder("toggle-reaction") + .parameter_type(Some(&String::static_variant_type())) + .activate(clone!( + #[weak(rename_to = imp)] + self, + move |_, _, variant| { + let Some(key) = variant.unwrap().get::() else { + error!("Could not parse reaction to toggle"); + return; + }; - spawn!(async move { - imp.toggle_reaction(key).await; - }); - } - )) - .build()]); + spawn!(async move { + imp.toggle_reaction(key).await; + }); + } + )) + .build(), + gio::ActionEntry::builder("show-reactions-chooser") + .activate(clone!( + #[weak(rename_to = imp)] + self, + move |_, _, _| { + imp.show_reactions_chooser(); + } + )) + .build(), + ]); } // Reply. @@ -1076,55 +1048,6 @@ impl ItemRow { } } -// This is only safe because the trait `EventActions` can -// only be implemented on `gtk::Widgets` that run only on the main thread -struct MenuModelSendSync(gio::MenuModel); -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for MenuModelSendSync {} -unsafe impl Sync for MenuModelSendSync {} - -/// The `MenuModel` for common message event actions, including reactions. -fn event_message_menu_model_with_reactions() -> &'static gio::MenuModel { - static MODEL: LazyLock = LazyLock::new(|| { - MenuModelSendSync( - gtk::Builder::from_resource( - "/org/gnome/Fractal/ui/session/view/content/room_history/event_actions.ui", - ) - .object::("message_menu_model_with_reactions") - .unwrap(), - ) - }); - &MODEL.0 -} - -/// The `MenuModel` for common message event actions, without reactions. -fn event_message_menu_model_no_reactions() -> &'static gio::MenuModel { - static MODEL: LazyLock = LazyLock::new(|| { - MenuModelSendSync( - gtk::Builder::from_resource( - "/org/gnome/Fractal/ui/session/view/content/room_history/event_actions.ui", - ) - .object::("message_menu_model_no_reactions") - .unwrap(), - ) - }); - &MODEL.0 -} - -/// The `MenuModel` for common state event actions. -fn event_state_menu_model() -> &'static gio::MenuModel { - static MODEL: LazyLock = LazyLock::new(|| { - MenuModelSendSync( - gtk::Builder::from_resource( - "/org/gnome/Fractal/ui/session/view/content/room_history/event_actions.ui", - ) - .object::("state_menu_model") - .unwrap(), - ) - }); - &MODEL.0 -} - /// Create a spinner widget. fn spinner() -> adw::Spinner { adw::Spinner::builder() diff --git a/src/components/quick_reaction_chooser.rs b/src/session/view/content/room_history/item_row_context_menu.rs similarity index 70% rename from src/components/quick_reaction_chooser.rs rename to src/session/view/content/room_history/item_row_context_menu.rs index b602dbfd..428f86d5 100644 --- a/src/components/quick_reaction_chooser.rs +++ b/src/session/view/content/room_history/item_row_context_menu.rs @@ -1,13 +1,97 @@ -use adw::subclass::prelude::*; +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; use gtk::{ - glib, + gio, glib, glib::{clone, closure_local}, - prelude::*, CompositeTemplate, }; use crate::{session::model::ReactionList, utils::BoundObject}; +/// Helper struct for the context menu of an `ItemRow`. +#[derive(Debug)] +pub(super) struct ItemRowContextMenu { + /// The popover of the context menu. + pub(super) popover: gtk::PopoverMenu, + /// The menu model of the popover. + menu_model: gio::Menu, + /// The quick reaction chooser in the context menu. + quick_reaction_chooser: QuickReactionChooser, +} + +impl ItemRowContextMenu { + /// The identifier in the context menu for the quick reaction chooser. + const QUICK_REACTION_CHOOSER_ID: &str = "quick-reaction-chooser"; + + /// Whether the menu includes an item for the quick reaction chooser. + fn has_quick_reaction_chooser(&self) -> bool { + let first_section = self + .menu_model + .item_link(0, gio::MENU_LINK_SECTION) + .and_downcast::() + .expect("item row context menu has at least one section"); + first_section + .item_attribute_value(0, "custom", Some(&String::static_variant_type())) + .and_then(|variant| variant.get::()) + .is_some_and(|value| value == Self::QUICK_REACTION_CHOOSER_ID) + } + + /// Add the quick reaction chooser to this menu, if it is not already + /// present, and set the reaction list. + pub(super) fn add_quick_reaction_chooser(&self, reactions: ReactionList) { + if !self.has_quick_reaction_chooser() { + let section_menu = gio::Menu::new(); + let item = gio::MenuItem::new(None, None); + item.set_attribute_value( + "custom", + Some(&Self::QUICK_REACTION_CHOOSER_ID.to_variant()), + ); + section_menu.append_item(&item); + self.menu_model.insert_section(0, None, §ion_menu); + + self.popover.add_child( + &self.quick_reaction_chooser, + Self::QUICK_REACTION_CHOOSER_ID, + ); + } + + self.quick_reaction_chooser.set_reactions(Some(reactions)); + } + + /// Remove the quick reaction chooser from this menu, if it is present. + pub(super) fn remove_quick_reaction_chooser(&self) { + if !self.has_quick_reaction_chooser() { + return; + } + + self.popover.remove_child(&self.quick_reaction_chooser); + self.menu_model.remove(0); + } +} + +impl Default for ItemRowContextMenu { + fn default() -> Self { + let menu_model = gtk::Builder::from_resource( + "/org/gnome/Fractal/ui/session/view/content/room_history/event_context_menu.ui", + ) + .object::("event-menu") + .expect("resource and menu exist"); + + let popover = gtk::PopoverMenu::builder() + .has_arrow(false) + .halign(gtk::Align::Start) + .menu_model(&menu_model) + .build(); + popover.update_property(&[gtk::accessible::Property::Label(&gettext("Context Menu"))]); + + Self { + popover, + menu_model, + quick_reaction_chooser: Default::default(), + } + } +} + /// A quick reaction. #[derive(Debug, Clone, Copy)] struct QuickReaction { @@ -71,7 +155,9 @@ mod imp { use super::*; #[derive(Debug, Default, CompositeTemplate, glib::Properties)] - #[template(resource = "/org/gnome/Fractal/ui/components/quick_reaction_chooser.ui")] + #[template( + resource = "/org/gnome/Fractal/ui/session/view/content/room_history/quick_reaction_chooser.ui" + )] #[properties(wrapper_type = super::QuickReactionChooser)] pub struct QuickReactionChooser { #[template_child] diff --git a/src/session/view/content/room_history/mod.rs b/src/session/view/content/room_history/mod.rs index 4f27352d..82ccb3b9 100644 --- a/src/session/view/content/room_history/mod.rs +++ b/src/session/view/content/room_history/mod.rs @@ -1,5 +1,16 @@ +use std::time::Duration; + +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::{gdk, gio, glib, glib::clone, graphene::Point, CompositeTemplate}; +use matrix_sdk::ruma::EventId; +use matrix_sdk_ui::timeline::TimelineEventItemId; +use ruma::{api::client::receipt::create_receipt::v3::ReceiptType, OwnedEventId}; +use tracing::{error, warn}; + mod divider_row; mod item_row; +mod item_row_context_menu; mod member_timestamp; mod message_row; mod message_toolbar; @@ -10,25 +21,15 @@ mod title; mod typing_row; mod verification_info_bar; -use std::time::Duration; - -use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::gettext; -use gtk::{gdk, gio, glib, glib::clone, graphene::Point, CompositeTemplate}; -use matrix_sdk::ruma::EventId; -use matrix_sdk_ui::timeline::TimelineEventItemId; -use ruma::{api::client::receipt::create_receipt::v3::ReceiptType, OwnedEventId}; -use tracing::{error, warn}; - use self::{ - divider_row::DividerRow, item_row::ItemRow, message_row::MessageRow, - message_toolbar::MessageToolbar, read_receipts_list::ReadReceiptsList, + divider_row::DividerRow, item_row::ItemRow, item_row_context_menu::ItemRowContextMenu, + message_row::MessageRow, message_toolbar::MessageToolbar, read_receipts_list::ReadReceiptsList, sender_avatar::SenderAvatar, state_row::StateRow, title::RoomHistoryTitle, typing_row::TypingRow, verification_info_bar::VerificationInfoBar, }; use super::{room_details, RoomDetails}; use crate::{ - components::{confirm_leave_room_dialog, DragOverlay, QuickReactionChooser}, + components::{confirm_leave_room_dialog, DragOverlay}, prelude::*, session::model::{ Event, MemberList, Membership, ReceiptPosition, Room, RoomCategory, Timeline, TimelineState, @@ -86,8 +87,7 @@ mod imp { tombstoned_banner: TemplateChild, #[template_child] drag_overlay: TemplateChild, - item_context_menu: OnceCell, - item_quick_reaction_chooser: QuickReactionChooser, + item_context_menu: OnceCell, sender_context_menu: OnceCell, /// The room currently displayed. #[property(get, set = Self::set_room, explicit_notify, nullable)] @@ -989,21 +989,8 @@ mod imp { } /// The context menu for the item rows. - pub(super) fn item_context_menu(&self) -> >k::PopoverMenu { - self.item_context_menu.get_or_init(|| { - let popover = gtk::PopoverMenu::builder() - .has_arrow(false) - .halign(gtk::Align::Start) - .build(); - popover - .update_property(&[gtk::accessible::Property::Label(&gettext("Context Menu"))]); - popover - }) - } - - /// The reaction chooser for the item rows. - pub(super) fn item_quick_reaction_chooser(&self) -> &QuickReactionChooser { - &self.item_quick_reaction_chooser + pub(super) fn item_context_menu(&self) -> &ItemRowContextMenu { + self.item_context_menu.get_or_init(Default::default) } /// The context menu for the sender avatars. @@ -1077,17 +1064,12 @@ impl RoomHistory { } /// The context menu for the item rows. - pub(crate) fn item_context_menu(&self) -> >k::PopoverMenu { + fn item_context_menu(&self) -> &ItemRowContextMenu { self.imp().item_context_menu() } - /// The reaction chooser for the item rows. - pub(crate) fn item_quick_reaction_chooser(&self) -> &QuickReactionChooser { - self.imp().item_quick_reaction_chooser() - } - /// The context menu for the sender avatars. - pub(crate) fn sender_context_menu(&self) -> >k::PopoverMenu { + fn sender_context_menu(&self) -> >k::PopoverMenu { self.imp().sender_context_menu() } } diff --git a/src/components/quick_reaction_chooser.ui b/src/session/view/content/room_history/quick_reaction_chooser.ui similarity index 87% rename from src/components/quick_reaction_chooser.ui rename to src/session/view/content/room_history/quick_reaction_chooser.ui index 001c0504..b7b371d4 100644 --- a/src/components/quick_reaction_chooser.ui +++ b/src/session/view/content/room_history/quick_reaction_chooser.ui @@ -18,8 +18,8 @@ - view-more-horizontal-symbolic - + view-more-horizontal-symbolic + event.show-reactions-chooser 3 1 diff --git a/src/ui-resources.gresource.xml b/src/ui-resources.gresource.xml index ff5f98c2..76f50de1 100644 --- a/src/ui-resources.gresource.xml +++ b/src/ui-resources.gresource.xml @@ -28,7 +28,6 @@ components/power_level_selection/combo_box.ui components/power_level_selection/popover.ui components/power_level_selection/row.ui - components/quick_reaction_chooser.ui components/rows/button_count_row.ui components/rows/check_loading_row.ui components/rows/combo_loading_row.ui @@ -104,7 +103,7 @@ session/view/content/room_details/permissions/permissions_subpage.ui session/view/content/room_details/permissions/select_member_row.ui session/view/content/room_history/divider_row.ui - session/view/content/room_history/event_actions.ui + session/view/content/room_history/event_context_menu.ui session/view/content/room_history/member_timestamp/row.ui session/view/content/room_history/message_row/audio.ui session/view/content/room_history/message_row/file.ui @@ -120,6 +119,7 @@ session/view/content/room_history/message_toolbar/completion/completion_popover.ui session/view/content/room_history/message_toolbar/mod.ui session/view/content/room_history/mod.ui + session/view/content/room_history/quick_reaction_chooser.ui session/view/content/room_history/read_receipts_list/mod.ui session/view/content/room_history/read_receipts_list/read_receipts_popover.ui session/view/content/room_history/sender_avatar/mod.ui