From e96b6397925c8b9291da158f3abd1a4427d5edf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Tue, 25 Aug 2026 16:55:47 -0700 Subject: [PATCH] Add ShareMenuModel --- demo/Views/ListsView.vala | 48 +++++++------ lib/Widgets/ListItem.vala | 2 +- lib/Widgets/ShareMenuModel.vala | 119 ++++++++++++++++++++++++++++++++ lib/meson.build | 1 + meson.build | 2 + 5 files changed, 149 insertions(+), 23 deletions(-) create mode 100644 lib/Widgets/ShareMenuModel.vala diff --git a/demo/Views/ListsView.vala b/demo/Views/ListsView.vala index f8421db7d..da532634b 100644 --- a/demo/Views/ListsView.vala +++ b/demo/Views/ListsView.vala @@ -32,28 +32,6 @@ public class ListsView : DemoPage { secondary_text = "ScrolledWindow with \"has-frame = true\" has a view level background color" }; - var reply_menuitem = new GLib.MenuItem ("Reply", null); - reply_menuitem.set_attribute_value ("verb-icon", "mail-reply-sender-symbolic"); - - var reply_all_menuitem = new GLib.MenuItem ("Reply All", null); - reply_all_menuitem.set_attribute_value ("verb-icon", "mail-reply-all-symbolic"); - - var forward_menuitem = new GLib.MenuItem ("Forward", null); - forward_menuitem.set_attribute_value ("verb-icon", "mail-forward-symbolic"); - - var button_menu = new GLib.Menu (); - button_menu.append_item (reply_menuitem); - button_menu.append_item (reply_all_menuitem); - button_menu.append_item (forward_menuitem); - - var button_section = new GLib.MenuItem.section (null, button_menu); - button_section.set_attribute_value ("display-hint", "circular-buttons"); - - var menu_model = new GLib.Menu (); - menu_model.append_item (button_section); - menu_model.append ("Move", null); - menu_model.append ("Delete", null); - var list_store = new GLib.ListStore (typeof (ListObject)); list_store.append (new ListObject () { text = "Row 1" @@ -76,9 +54,35 @@ public class ListsView : DemoPage { var list_factory = new Gtk.SignalListItemFactory (); list_factory.setup.connect ((obj) => { var list_item = (Gtk.ListItem) obj; + + var reply_menuitem = new GLib.MenuItem ("Reply", null); + reply_menuitem.set_attribute_value ("verb-icon", "mail-reply-sender-symbolic"); + + var reply_all_menuitem = new GLib.MenuItem ("Reply All", null); + reply_all_menuitem.set_attribute_value ("verb-icon", "mail-reply-all-symbolic"); + + var forward_menuitem = new GLib.MenuItem ("Forward", null); + forward_menuitem.set_attribute_value ("verb-icon", "mail-forward-symbolic"); + + var button_menu = new GLib.Menu (); + button_menu.append_item (reply_menuitem); + button_menu.append_item (reply_all_menuitem); + button_menu.append_item (forward_menuitem); + + var button_section = new GLib.MenuItem.section (null, button_menu); + button_section.set_attribute_value ("display-hint", "circular-buttons"); + + var share_menu = new Granite.ShareMenuModel (); + + var menu_model = new GLib.Menu (); + menu_model.append_item (button_section); + menu_model.append_item (share_menu.get_section_for_files ()); + list_item.child = new Granite.ListItem () { menu_model = menu_model }; + + share_menu.popovermenu = ((Granite.ListItem) list_item.child).context_menu; }); list_factory.bind.connect ((obj) => { diff --git a/lib/Widgets/ListItem.vala b/lib/Widgets/ListItem.vala index 056f4f806..52d1619be 100644 --- a/lib/Widgets/ListItem.vala +++ b/lib/Widgets/ListItem.vala @@ -65,7 +65,7 @@ public class Granite.ListItem : Gtk.Widget { private Gtk.GestureClick? click_controller; private Gtk.GestureLongPress? long_press_controller; private Gtk.EventControllerKey menu_key_controller; - private Gtk.PopoverMenu? context_menu; + public Gtk.PopoverMenu? context_menu { get; private set; } class construct { set_css_name ("granite-listitem"); diff --git a/lib/Widgets/ShareMenuModel.vala b/lib/Widgets/ShareMenuModel.vala new file mode 100644 index 000000000..13789dc0f --- /dev/null +++ b/lib/Widgets/ShareMenuModel.vala @@ -0,0 +1,119 @@ +public class Granite.ShareMenuModel : Object { + private Gtk.PopoverMenu _popovermenu; + public Gtk.PopoverMenu popovermenu { + get { + return _popovermenu; + } + set { + _popovermenu = value; + + var email_button = new ImageModelButton (_("Send by Mail"), ACTION_PREFIX + EMAIL_ACTION, "io.elementary.mail"); + var bluetooth_button = new ImageModelButton (_("Send by Bluetooth"), ACTION_PREFIX + WALLPAPER_ACTION, "io.elementary.bluetooth"); + var wallpaper_button = new ImageModelButton (_("Use as Wallpaper"), ACTION_PREFIX + WALLPAPER_ACTION, "preferences-desktop-wallpaper"); + var open_button = new ImageModelButton (_("Open With…"), ACTION_PREFIX + WALLPAPER_ACTION, "document-open"); + + var app_box = new Granite.Box (HORIZONTAL, NONE) { + homogeneous = true + }; + app_box.add_css_class ("inline-buttons"); + app_box.append (email_button); + app_box.append (bluetooth_button); + app_box.append (wallpaper_button); + app_box.append (open_button); + + _popovermenu.insert_action_group (ACTION_GROUP, action_group); + _popovermenu.add_child (app_box, "apps"); + } + } + + private const string ACTION_GROUP = "share"; + private const string ACTION_PREFIX = ACTION_GROUP + "."; + private const string EMAIL_ACTION = "email"; + private const string WALLPAPER_ACTION = "wallpaper"; + + private SimpleActionGroup action_group; + + //TODO: decide arg type + public GLib.MenuItem get_section_for_files () { + var email_action = new SimpleAction (EMAIL_ACTION, null); + email_action.activate.connect (action_email); + + var wallpaper_action = new SimpleAction (WALLPAPER_ACTION, null); + + action_group = new SimpleActionGroup (); + action_group.add_action (email_action); + + var app_section = new MenuItem (null, null); + app_section.set_attribute_value ("custom", "apps"); + + var system_section = new Menu (); + system_section.append (_("Save to Files"), ACTION_PREFIX + WALLPAPER_ACTION); + system_section.append (_("Print"), ACTION_PREFIX + WALLPAPER_ACTION); + system_section.append (_("Move to Trash"), ACTION_PREFIX + WALLPAPER_ACTION); + + var menu = new Menu (); + menu.append_item (app_section); + menu.append_section (null, system_section); + + return new MenuItem.section (null, menu); + } + + private void action_email () { + var active_window = (Gtk.Window) _popovermenu.get_root (); + Xdp.Parent? parent = active_window != null ? Xdp.parent_new_gtk (active_window) : null; + + critical ("emailing"); + + string[] attachments = {"file:///dogwater"}; + + var portal = new Xdp.Portal (); + portal.compose_email.begin (parent, null, null, null, null, null, attachments, NONE, null, (obj, res) => { + try { + portal.compose_email.end (res); + } catch (Error e) { + // FIXME: throw error dialog + critical (e.message); + } + }); + + } + + private class ImageModelButton : Gtk.Button { + public string action_name { get; construct; } + public string icon_name { get; construct; } + public string label { get; construct; } + + public ImageModelButton (string label, string action_name, string icon_name) { + Object ( + label: label, + action_name: action_name, + icon_name: icon_name + ); + } + + construct { + accessible_role = MENU_ITEM; + + var image = new Gtk.Image.from_icon_name (icon_name) { + icon_size = LARGE + }; + + var label_widget = new Gtk.Label (label) { + ellipsize = MIDDLE, + justify = CENTER, + lines = 2, + max_width_chars = 8 + }; + label_widget.add_css_class (Granite.CssClass.SMALL); + + var box = new Granite.Box (VERTICAL, HALF); + box.append (image); + box.append (label_widget); + + child = box; + action_name = action_name; + has_frame = false; + add_css_class ("model"); + } + } +} diff --git a/lib/meson.build b/lib/meson.build index a3215c723..62a6a3c9a 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -29,6 +29,7 @@ libgranite_sources = files( 'Widgets/ModeSwitch.vala', 'Widgets/OverlayBar.vala', 'Widgets/Placeholder.vala', + 'Widgets/ShareMenuModel.vala', 'Widgets/SettingsSidebarRow.vala', 'Widgets/SettingsSidebar.vala', 'Widgets/Settings.vala', diff --git a/meson.build b/meson.build index 004caaeeb..df2958875 100644 --- a/meson.build +++ b/meson.build @@ -58,6 +58,8 @@ libgranite_deps = [ dependency('glib-2.0', version: '>=' + glib_min_version), dependency('gobject-2.0', version: '>=' + glib_min_version), dependency('gtk4', version: '>=4.12'), + dependency('libportal'), + dependency('libportal-gtk4') ] pkgconfig = import('pkgconfig')