Skip to content

Commit 2554d7a

Browse files
authored
menu: Add context menu actions for favorites/recents (#13220)
* menu: Add context menu actions for favorites/recents Add `Remove from favorites` and `Open containing folder` action for favorites/recents. * menu: Add fallback action for the `Open containing folder`
1 parent 0e7a9c6 commit 2554d7a

1 file changed

Lines changed: 145 additions & 23 deletions

File tree

files/usr/share/cinnamon/applets/[email protected]/applet.js

Lines changed: 145 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class VisibleChildIterator {
136136
* no-favorites "No favorite documents" button
137137
* none Default type
138138
* place PlaceButton
139-
* favorite PathButton
139+
* favorite FavoriteDocumentButton
140140
* recent PathButton
141141
* recent-clear "Clear recent documents" button
142142
* search-provider SearchProviderResultButton
@@ -347,11 +347,11 @@ class SimpleMenuItem {
347347
}
348348
}
349349

350-
class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem {
351-
constructor(appButton, label, action, iconName) {
350+
class ContextMenuItem extends PopupMenu.PopupBaseMenuItem {
351+
constructor(button, label, action, iconName) {
352352
super({focusOnHover: false});
353353

354-
this._appButton = appButton;
354+
this._button = button;
355355
this._action = action;
356356
this.label = new St.Label({ text: label });
357357

@@ -375,6 +375,12 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem {
375375
this.actor.remove_accessible_state(Atk.StateType.FOCUSED);
376376
});
377377
}
378+
}
379+
380+
class ApplicationContextMenuItem extends ContextMenuItem {
381+
constructor(appButton, label, action, iconName) {
382+
super(appButton, label, action, iconName);
383+
}
378384

379385
activate (event) {
380386
let closeMenu = true;
@@ -395,13 +401,13 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem {
395401
let launcherApplet = Main.AppletManager.get_role_provider(Main.AppletManager.Roles.PANEL_LAUNCHER);
396402
if (!launcherApplet)
397403
return true;
398-
launcherApplet.acceptNewLauncher(this._appButton.app.get_id());
404+
launcherApplet.acceptNewLauncher(this._button.app.get_id());
399405
}
400406
return false;
401407
});
402408
break;
403409
case "add_to_desktop":
404-
let file = Gio.file_new_for_path(this._appButton.app.get_app_info().get_filename());
410+
let file = Gio.file_new_for_path(this._button.app.get_app_info().get_filename());
405411
let destFile = Gio.file_new_for_path(USER_DESKTOP_PATH+"/"+file.get_basename());
406412
try{
407413
file.copy(destFile, 0, null, function(){});
@@ -411,45 +417,42 @@ class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem {
411417
}
412418
break;
413419
case "add_to_favorites":
414-
AppFavorites.getAppFavorites().addFavorite(this._appButton.app.get_id());
420+
AppFavorites.getAppFavorites().addFavorite(this._button.app.get_id());
415421
this.label.set_text(_("Remove from favorites"));
416422
this.icon.icon_name = "xsi-starred";
417423
this._action = "remove_from_favorites";
418424
closeMenu = false;
419425
break;
420426
case "remove_from_favorites":
421-
AppFavorites.getAppFavorites().removeFavorite(this._appButton.app.get_id());
427+
AppFavorites.getAppFavorites().removeFavorite(this._button.app.get_id());
422428
this.label.set_text(_("Add to favorites"));
423429
this.icon.icon_name = "xsi-non-starred";
424430
this._action = "add_to_favorites";
425431
closeMenu = false;
426432
break;
427433
case "app_properties":
428-
Util.spawnCommandLine("cinnamon-desktop-editor -mlauncher -o" + GLib.shell_quote(this._appButton.app.get_app_info().get_filename()));
434+
Util.spawnCommandLine("cinnamon-desktop-editor -mlauncher -o" + GLib.shell_quote(this._button.app.get_app_info().get_filename()));
429435
break;
430436
case "uninstall":
431-
Util.spawnCommandLine("/usr/bin/cinnamon-remove-application '" + this._appButton.app.get_app_info().get_filename() + "'");
437+
Util.spawnCommandLine("/usr/bin/cinnamon-remove-application '" + this._button.app.get_app_info().get_filename() + "'");
432438
break;
433439
case "offload_launch":
434440
try {
435-
this._appButton.app.launch_offloaded(0, [], -1);
441+
this._button.app.launch_offloaded(0, [], -1);
436442
} catch (e) {
437443
logError(e, "Could not launch app with dedicated gpu: ");
438444
}
439445
break;
440446
default:
441447
if (this._action.startsWith("action_")) {
442448
let action = this._action.substring(7);
443-
this._appButton.app.get_app_info().launch_action(action, global.create_app_launch_context());
449+
this._button.app.get_app_info().launch_action(action, global.create_app_launch_context());
444450
} else return true;
445451
}
446-
if (closeMenu) {
447-
this._appButton.applet.toggleContextMenu(this._appButton);
448-
this._appButton.applet.menu.close();
449-
}
452+
if (closeMenu)
453+
this._button.applet.menu.close();
450454
return false;
451455
}
452-
453456
}
454457

455458
class GenericApplicationButton extends SimpleMenuItem {
@@ -789,15 +792,79 @@ class RecentButton extends SimpleMenuItem {
789792
}
790793
}
791794

795+
class PathContextMenuItem extends ContextMenuItem {
796+
constructor(pathButton, label, action, iconName) {
797+
super(pathButton, label, action, iconName);
798+
}
799+
800+
activate(event) {
801+
switch (this._action) {
802+
case "open_containing_folder":
803+
this._openContainingFolder();
804+
this._button.applet.menu.close();
805+
return false;
806+
}
807+
return true;
808+
}
809+
810+
static _useDBus = true;
811+
812+
_openContainingFolder() {
813+
if (!PathContextMenuItem._useDBus || !this._openContainingFolderViaDBus()) {
814+
// Do not attempt to use DBus again once it's failed.
815+
PathContextMenuItem._useDBus = false;
816+
this._openContainingFolderViaMimeApp();
817+
}
818+
}
819+
820+
_openContainingFolderViaDBus() {
821+
try {
822+
Gio.DBus.session.call_sync(
823+
"org.freedesktop.FileManager1",
824+
"/org/freedesktop/FileManager1",
825+
"org.freedesktop.FileManager1",
826+
"ShowItems",
827+
new GLib.Variant("(ass)", [
828+
[this._button.uri],
829+
global.get_pid().toString()
830+
]),
831+
null,
832+
Gio.DBusCallFlags.NONE,
833+
1000,
834+
null
835+
);
836+
} catch (e) {
837+
global.log(`Could not open containing folder via DBus: ${e}`);
838+
return false;
839+
}
840+
return true;
841+
}
842+
843+
_openContainingFolderViaMimeApp() {
844+
let app = Gio.AppInfo.get_default_for_type("inode/directory", true);
845+
if (app === null) {
846+
log.logError(`Could not open containing folder via MIME app: No associated file manager found`);
847+
return;
848+
}
849+
let file = Gio.file_new_for_uri(this._button.uri);
850+
try {
851+
app.launch([file.get_parent()], null);
852+
} catch (e) {
853+
global.logError(`Could not open containing folder via MIME app: ${e}`);
854+
}
855+
}
856+
}
857+
792858
class PathButton extends SimpleMenuItem {
793-
constructor(applet, type, name, uri, icon) {
859+
constructor(applet, type, name, uri, mimeType, icon) {
794860
super(applet, {
795861
name: name,
796862
description: shorten_path(uri, name),
797863
type: type,
798864
styleClass: 'appmenu-application-button',
799-
withMenu: false,
865+
withMenu: true,
800866
uri: uri,
867+
mimeType: mimeType
801868
});
802869

803870
this.icon = icon;
@@ -828,6 +895,55 @@ class PathButton extends SimpleMenuItem {
828895
source.notify(notification);
829896
}
830897
}
898+
899+
populateMenu(menu) {
900+
if (this.mimeType !== "inode/directory") {
901+
let menuItem = new PathContextMenuItem(this, _("Open containing folder"), "open_containing_folder", "xsi-go-jump-symbolic");
902+
menu.addMenuItem(menuItem);
903+
}
904+
}
905+
}
906+
907+
class FavoriteDocumentContextMenuItem extends ContextMenuItem {
908+
constructor(favDocButton, label, action, iconName) {
909+
super(favDocButton, label, action, iconName);
910+
}
911+
912+
activate(event) {
913+
switch (this._action) {
914+
case "remove_from_favorite_documents":
915+
this._button._unfavorited = true;
916+
// Do not refresh the favdoc menu during interaction, as it will destroy every menu item.
917+
this._button.applet.deferRefreshMask |= RefreshFlags.FAV_DOC;
918+
this._button.applet.closeContextMenu(true);
919+
this._button.actor.hide();
920+
XApp.Favorites.get_default().remove(this._button.uri);
921+
return false;
922+
}
923+
return true;
924+
}
925+
}
926+
927+
class FavoriteDocumentButton extends PathButton {
928+
constructor(applet, type, name, uri, mimeType, icon) {
929+
super(applet, type, name, uri, mimeType, icon);
930+
931+
this._unfavorited = false;
932+
this._signals.connect(this.actor, "show", () => {
933+
if (this._unfavorited) {
934+
this.actor.hide();
935+
return Clutter.EVENT_STOP;
936+
}
937+
return Clutter.EVENT_PROPAGATE;
938+
});
939+
}
940+
941+
populateMenu(menu) {
942+
let menuItem = new FavoriteDocumentContextMenuItem(this, _("Remove from favorites"), "remove_from_favorite_documents", "xsi-unfavorite-symbolic");
943+
menu.addMenuItem(menuItem);
944+
945+
super.populateMenu(menu);
946+
}
831947
}
832948

833949
class CategoryButton extends SimpleMenuItem {
@@ -1258,6 +1374,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
12581374
// In that particular case we get no signal at all.
12591375
this.refreshId = 0;
12601376
this.refreshMask = REFRESH_ALL_MASK;
1377+
this.deferRefreshMask = 0;
12611378
this._doRefresh();
12621379

12631380
this.set_show_label_in_vertical_panels(false);
@@ -1308,7 +1425,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
13081425

13091426
_doRefresh() {
13101427
this.refreshId = 0;
1311-
if (this.refreshMask === 0)
1428+
if ((this.refreshMask &= ~this.deferRefreshMask) === 0)
13121429
return;
13131430

13141431
let m = this.refreshMask;
@@ -1447,6 +1564,10 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
14471564
if (this.searchActive) {
14481565
this.resetSearch();
14491566
}
1567+
if (this.deferRefreshMask !== 0) {
1568+
this.queueRefresh(this.deferRefreshMask);
1569+
this.deferRefreshMask = 0;
1570+
}
14501571

14511572
this.hoveredCategory = null;
14521573
this.hoveredApp = null;
@@ -1614,7 +1735,8 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
16141735
button.populateMenu(this.contextMenu);
16151736
}
16161737

1617-
this.contextMenu.toggle();
1738+
if (this.contextMenu.numMenuItems !== 0)
1739+
this.contextMenu.toggle();
16181740
}
16191741

16201742
_navigateContextMenu(button, symbol, ctrlKey) {
@@ -2224,7 +2346,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
22242346
this.noRecentDocuments = false;
22252347
recents.forEach( info => {
22262348
let icon = info.createIcon(this.applicationIconSize);
2227-
let button = new PathButton(this, 'recent', info.name, info.uri, icon);
2349+
let button = new PathButton(this, 'recent', info.name, info.uri, info.mimeType, icon);
22282350
this._recentButtons.push(button);
22292351
this.applicationsBox.add_actor(button.actor);
22302352
button.actor.visible = this.menu.isOpen && this.lastSelectedCategory === "recent";
@@ -2293,7 +2415,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
22932415
gicon: Gio.content_type_get_icon(info.cached_mimetype),
22942416
icon_size: this.applicationIconSize
22952417
});
2296-
let button = new PathButton(this, 'favorite', info.display_name, info.uri, icon);
2418+
let button = new FavoriteDocumentButton(this, 'favorite', info.display_name, info.uri, info.cached_mimetype, icon);
22972419
this._favoriteDocButtons.push(button);
22982420
this.applicationsBox.add_actor(button.actor);
22992421
button.actor.visible = this.menu.isOpen && this.lastSelectedCategory === "favorite";

0 commit comments

Comments
 (0)