From 2f1599632a1691414a7b183472159be9f1bcdab1 Mon Sep 17 00:00:00 2001 From: rlbxku1r <32811754+rlbxku1r@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:31:59 +0900 Subject: [PATCH] menu: Add context menu actions for favorites/recents Add `Remove from favorites` and `Open containing folder` action for favorites/recents. --- .../applets/menu@cinnamon.org/applet.js | 142 +++++++++++++++--- 1 file changed, 119 insertions(+), 23 deletions(-) diff --git a/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js index 1516d80bb4..5377fd1d72 100644 --- a/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js @@ -135,7 +135,7 @@ class VisibleChildIterator { * no-favorites "No favorite documents" button * none Default type * place PlaceButton - * favorite PathButton + * favorite FavoriteDocumentButton * recent PathButton * recent-clear "Clear recent documents" button * search-provider SearchProviderResultButton @@ -346,11 +346,11 @@ class SimpleMenuItem { } } -class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem { - constructor(appButton, label, action, iconName) { +class ContextMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, label, action, iconName) { super({focusOnHover: false}); - this._appButton = appButton; + this._button = button; this._action = action; this.label = new St.Label({ text: label }); @@ -374,6 +374,12 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem { this.actor.remove_accessible_state(Atk.StateType.FOCUSED); }); } +} + +class ApplicationContextMenuItem extends ContextMenuItem { + constructor(appButton, label, action, iconName) { + super(appButton, label, action, iconName); + } activate (event) { let closeMenu = true; @@ -394,13 +400,13 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem { let launcherApplet = Main.AppletManager.get_role_provider(Main.AppletManager.Roles.PANEL_LAUNCHER); if (!launcherApplet) return true; - launcherApplet.acceptNewLauncher(this._appButton.app.get_id()); + launcherApplet.acceptNewLauncher(this._button.app.get_id()); } return false; }); break; case "add_to_desktop": - let file = Gio.file_new_for_path(this._appButton.app.get_app_info().get_filename()); + let file = Gio.file_new_for_path(this._button.app.get_app_info().get_filename()); let destFile = Gio.file_new_for_path(USER_DESKTOP_PATH+"/"+file.get_basename()); try{ file.copy(destFile, 0, null, function(){}); @@ -410,28 +416,28 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem { } break; case "add_to_favorites": - AppFavorites.getAppFavorites().addFavorite(this._appButton.app.get_id()); + AppFavorites.getAppFavorites().addFavorite(this._button.app.get_id()); this.label.set_text(_("Remove from favorites")); this.icon.icon_name = "xsi-starred"; this._action = "remove_from_favorites"; closeMenu = false; break; case "remove_from_favorites": - AppFavorites.getAppFavorites().removeFavorite(this._appButton.app.get_id()); + AppFavorites.getAppFavorites().removeFavorite(this._button.app.get_id()); this.label.set_text(_("Add to favorites")); this.icon.icon_name = "xsi-non-starred"; this._action = "add_to_favorites"; closeMenu = false; break; case "app_properties": - Util.spawnCommandLine("cinnamon-desktop-editor -mlauncher -o" + GLib.shell_quote(this._appButton.app.get_app_info().get_filename())); + Util.spawnCommandLine("cinnamon-desktop-editor -mlauncher -o" + GLib.shell_quote(this._button.app.get_app_info().get_filename())); break; case "uninstall": - Util.spawnCommandLine("/usr/bin/cinnamon-remove-application '" + this._appButton.app.get_app_info().get_filename() + "'"); + Util.spawnCommandLine("/usr/bin/cinnamon-remove-application '" + this._button.app.get_app_info().get_filename() + "'"); break; case "offload_launch": try { - this._appButton.app.launch_offloaded(0, [], -1); + this._button.app.launch_offloaded(0, [], -1); } catch (e) { logError(e, "Could not launch app with dedicated gpu: "); } @@ -439,16 +445,13 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem { default: if (this._action.startsWith("action_")) { let action = this._action.substring(7); - this._appButton.app.get_app_info().launch_action(action, global.create_app_launch_context()); + this._button.app.get_app_info().launch_action(action, global.create_app_launch_context()); } else return true; } - if (closeMenu) { - this._appButton.applet.toggleContextMenu(this._appButton); - this._appButton.applet.menu.close(); - } + if (closeMenu) + this._button.applet.menu.close(); return false; } - } class GenericApplicationButton extends SimpleMenuItem { @@ -788,15 +791,53 @@ class RecentButton extends SimpleMenuItem { } } +class PathContextMenuItem extends ContextMenuItem { + constructor(pathButton, label, action, iconName) { + super(pathButton, label, action, iconName); + } + + activate(event) { + switch (this._action) { + case "open_containing_folder": + this._openContainingFolder(); + this._button.applet.menu.close(); + return false; + } + return true; + } + + _openContainingFolder() { + try { + Gio.DBus.session.call_sync( + "org.freedesktop.FileManager1", + "/org/freedesktop/FileManager1", + "org.freedesktop.FileManager1", + "ShowItems", + new GLib.Variant("(ass)", [ + [this._button.uri], + global.get_pid().toString() + ]), + null, + Gio.DBusCallFlags.NONE, + 1000, + null + ); + } catch (e) { + global.logError(`Could not open containing folder: ${e}`); + } + } +} + class PathButton extends SimpleMenuItem { - constructor(applet, type, name, uri, icon) { + constructor(applet, type, name, uri, mimeType, icon) { super(applet, { name: name, description: shorten_path(uri, name), type: type, styleClass: 'appmenu-application-button', - withMenu: false, + withMenu: true, uri: uri, + mimeType: mimeType }); this.icon = icon; @@ -827,6 +868,55 @@ class PathButton extends SimpleMenuItem { source.notify(notification); } } + + populateMenu(menu) { + if (this.mimeType !== "inode/directory") { + let menuItem = new PathContextMenuItem(this, _("Open containing folder"), "open_containing_folder", "xsi-go-jump-symbolic"); + menu.addMenuItem(menuItem); + } + } +} + +class FavoriteDocumentContextMenuItem extends ContextMenuItem { + constructor(favDocButton, label, action, iconName) { + super(favDocButton, label, action, iconName); + } + + activate(event) { + switch (this._action) { + case "remove_from_favorite_documents": + this._button._unfavorited = true; + // Do not refresh the favdoc menu during interaction, as it will destroy every menu item. + this._button.applet.deferRefreshMask |= RefreshFlags.FAV_DOC; + this._button.applet.closeContextMenu(true); + this._button.actor.hide(); + XApp.Favorites.get_default().remove(this._button.uri); + return false; + } + return true; + } +} + +class FavoriteDocumentButton extends PathButton { + constructor(applet, type, name, uri, mimeType, icon) { + super(applet, type, name, uri, mimeType, icon); + + this._unfavorited = false; + this._signals.connect(this.actor, "show", () => { + if (this._unfavorited) { + this.actor.hide(); + return Clutter.EVENT_STOP; + } + return Clutter.EVENT_PROPAGATE; + }); + } + + populateMenu(menu) { + let menuItem = new FavoriteDocumentContextMenuItem(this, _("Remove from favorites"), "remove_from_favorite_documents", "xsi-unfavorite-symbolic"); + menu.addMenuItem(menuItem); + + super.populateMenu(menu); + } } class CategoryButton extends SimpleMenuItem { @@ -1259,6 +1349,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { // In that particular case we get no signal at all. this.refreshId = 0; this.refreshMask = REFRESH_ALL_MASK; + this.deferRefreshMask = 0; this._doRefresh(); this.set_show_label_in_vertical_panels(false); @@ -1312,7 +1403,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { _doRefresh() { this.refreshId = 0; - if (this.refreshMask === 0) + if ((this.refreshMask &= ~this.deferRefreshMask) === 0) return; let m = this.refreshMask; @@ -1451,6 +1542,10 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { if (this.searchActive) { this.resetSearch(); } + if (this.deferRefreshMask !== 0) { + this.queueRefresh(this.deferRefreshMask); + this.deferRefreshMask = 0; + } this.hoveredCategory = null; this.hoveredApp = null; @@ -1618,7 +1713,8 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { button.populateMenu(this.contextMenu); } - this.contextMenu.toggle(); + if (this.contextMenu.numMenuItems !== 0) + this.contextMenu.toggle(); } _navigateContextMenu(button, symbol, ctrlKey) { @@ -2223,7 +2319,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { this.noRecentDocuments = false; recents.forEach( info => { let icon = info.createIcon(this.applicationIconSize); - let button = new PathButton(this, 'recent', info.name, info.uri, icon); + let button = new PathButton(this, 'recent', info.name, info.uri, info.mimeType, icon); this._recentButtons.push(button); this.applicationsBox.add_actor(button.actor); button.actor.visible = this.menu.isOpen && this.lastSelectedCategory === "recent"; @@ -2287,7 +2383,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { gicon: Gio.content_type_get_icon(info.cached_mimetype), icon_size: this.applicationIconSize }); - let button = new PathButton(this, 'favorite', info.display_name, info.uri, icon); + let button = new FavoriteDocumentButton(this, 'favorite', info.display_name, info.uri, info.cached_mimetype, icon); this._favoriteDocButtons.push(button); this.applicationsBox.add_actor(button.actor); button.actor.visible = this.menu.isOpen && this.lastSelectedCategory === "favorite";