@@ -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
455458class 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+
792858class 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
833949class 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