1 /* Popup menus for the Midnight Commander
3 * Copyright (C) 1998 The Free Software Foundation
5 * Authors: Federico Mena <federico@nuclecu.unam.mx>
6 * Miguel de Icaza <miguel@nuclecu.unam.mx>
7 * Jonathan Blandford <jrb@redhat.com>
10 /* These rules really need to be duplicated elsewhere.
11 * They should exist for the menu bar too...
23 #include "../vfs/vfs.h"
25 #include "gnome-file-property-dialog.h"
26 #include "gnome-open-dialog.h"
29 #define CLIST_FROM_SW(panel_list) GTK_CLIST (GTK_BIN (panel_list)->child)
31 int is_trash_panel
= FALSE
;
33 /* Flags for the popup menu entries. They specify to which kinds of files an
37 F_ALL
= 1 << 0, /* Applies to all files */
38 F_REGULAR
= 1 << 1, /* Applies only to regular files */
39 F_SYMLINK
= 1 << 2, /* Applies only to symlinks */
40 F_SINGLE
= 1 << 3, /* Applies only to a single file, not to multiple files */
41 F_NOTDIR
= 1 << 4, /* Applies to non-directories */
42 F_NOTDEV
= 1 << 5, /* Applies to non-devices only (ie. reg, lnk, dir) */
43 F_ADVANCED
= 1 << 6, /* Only appears in advanced mode */
44 F_MIME_ACTIONS
= 1 << 7 /* Special marker for the position of MIME actions */
47 /* An entry in the actions menu */
49 typedef gboolean (*menu_func
) (WPanel
*panel
, DesktopIconInfo
*dii
);
52 char *text
; /* Menu item text */
53 int flags
; /* Flags from the above enum */
54 gpointer callback
; /* Callback for menu item */
55 menu_func func
; /* NULL if item is always present; a predicate otherwise */
59 /* Multiple File commands */
60 static void handle_open (GtkWidget
*widget
, WPanel
*panel
);
61 static void handle_mount (GtkWidget
*widget
, WPanel
*panel
);
62 static void handle_unmount (GtkWidget
*widget
, WPanel
*panel
);
63 static void handle_eject (GtkWidget
*widget
, WPanel
*panel
);
64 static void handle_view (GtkWidget
*widget
, WPanel
*panel
);
65 static void handle_view_unfiltered (GtkWidget
*widget
, WPanel
*panel
);
66 static void handle_edit (GtkWidget
*widget
, WPanel
*panel
);
67 static void handle_copy (GtkWidget
*widget
, WPanel
*panel
);
68 static void handle_trash (GtkWidget
*widget
, WPanel
*panel
);
69 static void handle_empty_trash (GtkWidget
*widget
, WPanel
*panel
);
70 static void handle_delete (GtkWidget
*widget
, WPanel
*panel
);
71 static void handle_move (GtkWidget
*widget
, WPanel
*panel
);
73 /* F_SINGLE file commands */
74 static void handle_properties (GtkWidget
*widget
, WPanel
*panel
);
75 static void handle_open_with (GtkWidget
*widget
, WPanel
*panel
);
76 static void handle_symlink (GtkWidget
*widget
, WPanel
*panel
);
77 static void handle_hard_link (GtkWidget
*widget
, WPanel
*panel
);
78 static void handle_edit_symlink (GtkWidget
*widget
, WPanel
*panel
);
80 /* Helper funcs and testing funcs. */
81 static gboolean
check_mount_func (WPanel
*panel
, DesktopIconInfo
*dii
);
82 static gboolean
check_unmount_func (WPanel
*panel
, DesktopIconInfo
*dii
);
83 static gboolean
check_eject_func (WPanel
*panel
, DesktopIconInfo
*dii
);
84 static gboolean
check_device_func (WPanel
*panel
, DesktopIconInfo
*dii
);
85 static gboolean
check_trash_func (WPanel
*panel
, DesktopIconInfo
*dii
);
86 static gboolean
check_trash_icon_func (WPanel
*panel
, DesktopIconInfo
*dii
);
88 static gchar
* get_full_filename (WPanel
*panel
);
90 /* Now, the actual code */
92 get_full_filename (WPanel
*panel
)
94 if (is_a_desktop_panel (panel
)) {
96 for (i
= 0; i
< panel
->count
; i
++)
97 if (panel
->dir
.list
[i
].f
.marked
) {
98 return concat_dir_and_file (panel
->cwd
,
99 panel
->dir
.list
[i
].fname
);
101 g_return_val_if_fail (FALSE
, NULL
);
103 return concat_dir_and_file (panel
->cwd
, selection (panel
)->fname
);
108 check_mount_umount (DesktopIconInfo
*dii
, int mount
)
115 full_name
= g_concat_dir_and_file (desktop_directory
, dii
->filename
);
116 fe
= file_entry_from_file (full_name
);
122 v
= is_mountable (full_name
, fe
, &is_mounted
, NULL
);
123 file_entry_free (fe
);
129 if (is_mounted
&& mount
)
132 if (!is_mounted
&& !mount
)
139 check_mount_func (WPanel
*panel
, DesktopIconInfo
*dii
)
144 return check_mount_umount (dii
, TRUE
);
148 check_unmount_func (WPanel
*panel
, DesktopIconInfo
*dii
)
153 return check_mount_umount (dii
, FALSE
);
157 check_eject_func (WPanel
*panel
, DesktopIconInfo
*dii
)
168 full_name
= g_concat_dir_and_file (desktop_directory
, dii
->filename
);
169 fe
= file_entry_from_file (full_name
);
175 v
= is_mountable (full_name
, fe
, &is_mounted
, NULL
);
176 file_entry_free (fe
);
180 else if (!is_ejectable (full_name
))
189 check_device_func (WPanel
*panel
, DesktopIconInfo
*dii
)
191 return (check_mount_func (panel
, dii
) ||
192 check_unmount_func (panel
, dii
) ||
193 check_eject_func (panel
, dii
));
197 check_trash_func (WPanel
*panel
, DesktopIconInfo
*dii
)
202 trash_dir
= g_strconcat (gnome_user_home_dir
, "/",
203 DESKTOP_DIR_NAME
, "/",
207 if (mc_stat (trash_dir
, &st
) || !S_ISDIR (st
.st_mode
)) {
212 if (strncmp (trash_dir
, panel
->cwd
, strlen (trash_dir
)) == 0) {
217 for (i
= 0; i
< panel
->count
; i
++) {
218 if (panel
->dir
.list
[i
].f
.marked
) {
219 gchar
*desktop_file
= concat_dir_and_file (panel
->cwd
,
220 panel
->dir
.list
[i
].fname
);
221 if (strncmp (desktop_file
, trash_dir
, strlen (trash_dir
)) == 0) {
222 g_free (desktop_file
);
226 g_free (desktop_file
);
235 check_trash_icon_func (WPanel
*panel
, DesktopIconInfo
*dii
)
240 if (!is_a_desktop_panel (panel
))
243 trash_dir
= g_strconcat (gnome_user_home_dir
, "/",
244 DESKTOP_DIR_NAME
, "/",
247 file_name
= get_full_filename (panel
);
249 if (strncmp (trash_dir
, file_name
, strlen (trash_dir
)) == 0) {
254 dirp
= mc_opendir (file_name
);
258 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
)) {
274 extern int we_can_afford_the_speed
;
276 static struct action file_actions
[] = {
277 { N_("Open"), F_NOTDEV
| F_SINGLE
, handle_open
, NULL
},
278 { "", F_NOTDEV
| F_SINGLE
, NULL
, NULL
},
279 { N_("Mount device"), F_ALL
| F_SINGLE
, handle_mount
, check_mount_func
},
280 { N_("Unmount device"), F_ALL
| F_SINGLE
, handle_unmount
, check_unmount_func
},
281 { N_("Eject device"), F_ALL
| F_SINGLE
, handle_eject
, check_eject_func
},
282 { N_("Empty Trash"), F_SINGLE
, handle_empty_trash
, check_trash_icon_func
},
283 /* Custom actions go here */
284 { "", F_MIME_ACTIONS
| F_SINGLE
, NULL
, check_device_func
},
285 { N_("Open with..."), F_REGULAR
| F_SINGLE
, handle_open_with
, NULL
},
286 { N_("View"), F_REGULAR
| F_SINGLE
, handle_view
, NULL
},
287 { N_("View Unfiltered"), F_REGULAR
| F_ADVANCED
| F_SINGLE
, handle_view_unfiltered
, NULL
},
288 { N_("Edit"), F_REGULAR
| F_SINGLE
, handle_edit
, NULL
},
289 { "", F_REGULAR
| F_SINGLE
, NULL
, NULL
},
290 { N_("Copy..."), F_ALL
, handle_copy
, NULL
},
291 { N_("Move to Trash"), F_ALL
, handle_trash
, check_trash_func
},
292 { N_("Delete"), F_ALL
, handle_delete
, NULL
},
293 { N_("Move..."), F_ALL
, handle_move
, NULL
},
294 { N_("Hard Link..."), F_ADVANCED
| F_SINGLE
, handle_hard_link
, NULL
},
295 { N_("Symlink..."), F_SINGLE
, handle_symlink
, NULL
},
296 { N_("Edit Symlink..."), F_SYMLINK
| F_SINGLE
, handle_edit_symlink
, NULL
},
297 { "", F_SINGLE
| F_ALL
, NULL
, NULL
},
298 { N_("Properties..."), F_SINGLE
| F_ALL
, handle_properties
, NULL
},
299 { NULL
, 0, NULL
, NULL
}
302 /* This is our custom signal connection function for popup menu items -- see below for the
303 * marshaller information. We pass the original callback function as the data pointer for the
304 * marshaller (uiinfo->moreinfo).
307 popup_connect_func (GnomeUIInfo
*uiinfo
, gchar
*signal_name
, GnomeUIBuilderData
*uibdata
)
309 g_assert (uibdata
->is_interp
);
311 if (uiinfo
->moreinfo
) {
312 gtk_object_set_data (GTK_OBJECT (uiinfo
->widget
), "popup_user_data",
314 gtk_signal_connect_full (GTK_OBJECT (uiinfo
->widget
), signal_name
,
318 uibdata
->destroy_func
,
324 /* Our custom marshaller for menu items. We need it so that it can extract the
325 * per-attachment user_data pointer from the parent menu shell and pass it to
326 * the callback. This overrides the user-specified data from the GnomeUIInfo
330 typedef void (* ActivateFunc
) (GtkObject
*object
, WPanel
*panel
);
333 popup_marshal_func (GtkObject
*object
, gpointer data
, guint n_args
, GtkArg
*args
)
338 func
= (ActivateFunc
) data
;
339 user_data
= gtk_object_get_data (object
, "popup_user_data");
341 gtk_object_set_data (GTK_OBJECT (GTK_WIDGET (object
)->parent
), "popup_active_item", object
);
342 (* func
) (object
, user_data
);
345 /* Fills the menu with the specified uiinfo at the specified position, using our
346 * magic marshallers to be able to fetch the active item. The code is
347 * shamelessly ripped from gnome-popup-menu.
350 fill_menu (GtkMenuShell
*menu_shell
, GnomeUIInfo
*uiinfo
, int pos
)
352 GnomeUIBuilderData uibdata
;
354 /* We use our own callback marshaller so that it can fetch the popup
355 * user data from the popup menu and pass it on to the user-defined
359 uibdata
.connect_func
= popup_connect_func
;
361 uibdata
.is_interp
= TRUE
;
362 uibdata
.relay_func
= popup_marshal_func
;
363 uibdata
.destroy_func
= NULL
;
365 gnome_app_fill_menu_custom (menu_shell
, uiinfo
, &uibdata
, NULL
, FALSE
, pos
);
368 /* Convenience function to free something when an object is destroyed */
370 free_on_destroy (GtkObject
*object
, gpointer data
)
375 /* Callback for MIME-based actions */
377 mime_action_callback (GtkWidget
*widget
, gpointer data
)
381 const char *mime_type
;
383 int needs_terminal
= 0;
387 key
= gtk_object_get_user_data (GTK_OBJECT (widget
));
389 g_assert (filename
!= NULL
);
390 g_assert (key
!= NULL
);
393 mime_type
= gnome_mime_type_or_default_of_file (filename
, NULL
);
395 mime_type
= gnome_mime_type_or_default (filename
, NULL
);
396 g_assert (mime_type
!= NULL
);
399 * Find out if we need to run this in a terminal
401 if (gnome_metadata_get (filename
, "flags", &size
, &buf
) == 0){
402 needs_terminal
= strstr (buf
, "needsterminal") != 0;
408 flag_key
= g_strconcat ("flags.", key
, "flags", NULL
);
409 flags
= gnome_mime_get_value (filename
, flag_key
);
412 needs_terminal
= strstr (flags
, "needsterminal") != 0;
415 value
= gnome_mime_get_value (mime_type
, key
);
416 exec_extension (filename
, value
, NULL
, NULL
, 0, needs_terminal
);
419 /* Escapes the underlines in the specified string for use by GtkLabel */
421 escape_underlines (char *str
)
426 buf
= g_new (char, 2 * strlen (str
) + 1);
428 for (p
= buf
; *str
; str
++) {
440 /* Creates the menu items for actions based on the MIME type of the selected
444 create_mime_actions (GtkWidget
*menu
, WPanel
*panel
, int pos
, DesktopIconInfo
*dii
)
447 const char *mime_type
;
450 GnomeUIInfo uiinfo
[] = {
455 if (is_a_desktop_panel (panel
)) {
456 g_assert (dii
!= NULL
);
457 full_name
= g_concat_dir_and_file (panel
->cwd
, dii
->filename
);
459 full_name
= g_concat_dir_and_file (panel
->cwd
,
460 panel
->dir
.list
[panel
->selected
].fname
);
462 mime_type
= gnome_mime_type_or_default_of_file (full_name
, NULL
);
464 mime_type
= gnome_mime_type_or_default (full_name
, NULL
);
470 keys
= gnome_mime_get_keys (mime_type
);
471 for (l
= keys
; l
; l
= l
->next
) {
477 if (strncmp (key
, "open.", 5) != 0)
481 while (*str
&& *str
!= '.')
490 /* Create the item for that entry */
492 str
= escape_underlines (str
);
494 uiinfo
[0].type
= GNOME_APP_UI_ITEM
;
495 uiinfo
[0].label
= str
;
496 uiinfo
[0].hint
= NULL
;
497 uiinfo
[0].moreinfo
= mime_action_callback
;
498 uiinfo
[0].user_data
= full_name
;
499 uiinfo
[0].unused_data
= NULL
;
500 uiinfo
[0].pixmap_type
= GNOME_APP_PIXMAP_NONE
;
501 uiinfo
[0].pixmap_info
= NULL
;
502 uiinfo
[0].accelerator_key
= 0;
503 uiinfo
[0].ac_mods
= 0;
504 uiinfo
[0].widget
= NULL
;
506 fill_menu (GTK_MENU_SHELL (menu
), uiinfo
, pos
++);
509 gtk_object_set_user_data (GTK_OBJECT (uiinfo
[0].widget
), key
);
513 /* Remember to free this memory */
514 gtk_signal_connect (GTK_OBJECT (menu
), "destroy",
515 (GtkSignalFunc
) free_on_destroy
,
518 if (pos_init
!= pos
) {
519 uiinfo
[0].type
= GNOME_APP_UI_SEPARATOR
;
520 fill_menu (GTK_MENU_SHELL (menu
), uiinfo
, pos
++);
527 /* Creates the menu items for the standard actions. Returns the position at
528 * which additional menu items should be inserted.
531 create_actions (GtkWidget
*menu
, gint flags
, WPanel
*panel
, DesktopIconInfo
*dii
)
533 struct action
*action
;
535 GnomeUIInfo uiinfo
[] = {
542 for (action
= file_actions
; action
->text
; action
++) {
543 /* Insert the MIME actions if appropriate */
544 if ((action
->flags
& F_MIME_ACTIONS
) && (flags
& F_SINGLE
)) {
546 pos
= create_mime_actions (menu
, panel
, pos
, dii
);
547 /* Why do we do this? If the mime actions are there, and it's mountable,
548 * we can count on the separator at the end of the mime_actions
549 * menu. However, if the mime_actions aren't there, we need a separator */
550 if (pos
== curpos
&& (action
->func
)(panel
, dii
)) {
551 uiinfo
[0].type
= GNOME_APP_UI_SEPARATOR
;
552 fill_menu (GTK_MENU_SHELL (menu
), uiinfo
, pos
++);
557 /* Filter the actions that are not appropriate */
558 if ((action
->flags
& flags
) != action
->flags
)
561 if (action
->func
&& !((action
->func
)(panel
, dii
)))
564 /* Create the menu item for this action */
565 if (action
->text
[0]) {
566 uiinfo
[0].type
= GNOME_APP_UI_ITEM
;
567 uiinfo
[0].label
= _(action
->text
);
568 uiinfo
[0].hint
= NULL
;
569 uiinfo
[0].moreinfo
= action
->callback
;
570 uiinfo
[0].user_data
= (gpointer
) panel
;
571 uiinfo
[0].unused_data
= NULL
;
572 uiinfo
[0].pixmap_type
= GNOME_APP_PIXMAP_NONE
;
573 uiinfo
[0].pixmap_info
= NULL
;
574 uiinfo
[0].accelerator_key
= 0;
575 uiinfo
[0].ac_mods
= 0;
576 uiinfo
[0].widget
= NULL
;
578 uiinfo
[0].type
= GNOME_APP_UI_SEPARATOR
;
580 fill_menu (GTK_MENU_SHELL (menu
), uiinfo
, pos
++);
584 /* Convenience callback to exit the main loop of a modal popup menu when it is deactivated*/
586 menu_shell_deactivated (GtkMenuShell
*menu_shell
, WPanel
*panel
)
591 /* Returns the index of the active item in the specified menu, or -1 if none is active */
593 get_active_index (GtkMenu
*menu
)
599 active
= gtk_object_get_data (GTK_OBJECT (menu
), "popup_active_item");
601 for (i
= 0, l
= GTK_MENU_SHELL (menu
)->children
; l
; l
= l
->next
, i
++)
602 if (active
== l
->data
)
608 #define REMOVE(x,f) x &= ~f
611 gpopup_do_popup2 (GdkEventButton
*event
, WPanel
*panel
, DesktopIconInfo
*dii
)
614 gint flags
= F_ALL
| F_REGULAR
| F_SYMLINK
| F_SINGLE
| F_NOTDEV
| F_NOTDIR
;
620 g_return_val_if_fail (event
!= NULL
, -1);
621 g_return_val_if_fail (panel
!= NULL
, -1);
623 menu
= gtk_menu_new ();
625 /* Connect to the deactivation signal to be able to quit our modal main
628 id
= gtk_signal_connect (GTK_OBJECT (menu
), "deactivate",
629 (GtkSignalFunc
) menu_shell_deactivated
,
634 for (i
= 0; i
< panel
->count
; i
++) {
635 if (!strcmp (panel
->dir
.list
[i
].fname
, "..") || !panel
->dir
.list
[i
].f
.marked
)
640 s
= panel
->dir
.list
[i
].buf
;
642 if (S_ISLNK (s
.st_mode
))
643 mc_stat (panel
->dir
.list
[i
].fname
, &s
);
645 REMOVE (flags
, F_SYMLINK
);
647 if (S_ISDIR (s
.st_mode
))
648 REMOVE (flags
, F_NOTDIR
);
650 if (!S_ISREG (s
.st_mode
))
651 REMOVE (flags
, F_REGULAR
);
653 if (S_ISCHR (s
.st_mode
) || S_ISBLK (s
.st_mode
))
654 REMOVE (flags
, F_NOTDEV
);
661 REMOVE (flags
, F_SINGLE
);
664 create_actions (menu
, flags
, panel
, dii
);
667 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
, event
->button
, event
->time
);
670 gtk_grab_remove (menu
);
672 gtk_signal_disconnect (GTK_OBJECT (menu
), id
);
674 i
= get_active_index (GTK_MENU (menu
));
675 gtk_widget_unref (menu
);
680 handle_open (GtkWidget
*widget
, WPanel
*panel
)
684 if (is_a_desktop_panel (panel
)) {
686 DesktopIconInfo
*dii
;
688 full_name
= get_full_filename (panel
);
689 dii
= desktop_icon_info_get_by_filename (x_basename (full_name
));
690 g_assert (dii
!= NULL
);
692 desktop_icon_info_open (dii
);
700 perform_mount_unmount (WPanel
*panel
, int mount
)
703 DesktopIconInfo
*dii
;
705 g_assert (is_a_desktop_panel (panel
));
707 full_name
= get_full_filename (panel
);
708 dii
= desktop_icon_info_get_by_filename (x_basename (full_name
));
709 g_assert (dii
!= NULL
);
711 desktop_icon_set_busy (dii
, TRUE
);
712 do_mount_umount (full_name
, mount
);
713 desktop_icon_set_busy (dii
, FALSE
);
718 handle_mount (GtkWidget
*widget
, WPanel
*panel
)
720 perform_mount_unmount (panel
, TRUE
);
721 update_panels (UP_RELOAD
, UP_KEEPSEL
);
725 handle_unmount (GtkWidget
*widget
, WPanel
*panel
)
727 perform_mount_unmount (panel
, FALSE
);
728 update_panels (UP_RELOAD
, UP_KEEPSEL
);
732 handle_eject (GtkWidget
*widget
, WPanel
*panel
)
736 DesktopIconInfo
*dii
;
738 g_assert (is_a_desktop_panel (panel
));
740 full_name
= get_full_filename (panel
);
741 dii
= desktop_icon_info_get_by_filename (x_basename (full_name
));
742 g_assert (dii
!= NULL
);
744 desktop_icon_set_busy (dii
, TRUE
);
746 lname
= g_readlink (full_name
);
749 desktop_icon_set_busy (dii
, FALSE
);
753 if (is_block_device_mounted (lname
))
754 do_mount_umount (full_name
, FALSE
);
758 desktop_icon_set_busy (dii
, FALSE
);
759 update_panels (UP_RELOAD
, UP_KEEPSEL
);
765 handle_view (GtkWidget
*widget
, WPanel
*panel
)
769 full_name
= get_full_filename (panel
);
770 gmc_view (full_name
, 0);
775 handle_view_unfiltered (GtkWidget
*widget
, WPanel
*panel
)
777 /* We need it to do the right thing later. */
778 /*view_simple_cmd (panel);*/
783 handle_edit (GtkWidget
*widget
, WPanel
*panel
)
787 full_name
= get_full_filename (panel
);
788 gmc_edit (full_name
);
793 handle_copy (GtkWidget
*widget
, WPanel
*panel
)
798 /* Empties trash when in the Trash panel */
800 handle_trash (GtkWidget
*widget
, WPanel
*panel
)
804 trash_dir
= g_strconcat (gnome_user_home_dir
, "/",
805 DESKTOP_DIR_NAME
, "/",
809 if (panel_operate (cpanel
, OP_MOVE
, trash_dir
, FALSE
)) {
810 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
815 /* Empties trash from the desktop */
817 handle_empty_trash (GtkWidget
*widget
, WPanel
*panel
)
819 gnome_empty_trash (NULL
, NULL
);
823 handle_delete (GtkWidget
*widget
, WPanel
*panel
)
829 handle_move (GtkWidget
*widget
, WPanel
*panel
)
834 /* F_SINGLE file commands */
836 handle_properties (GtkWidget
*widget
, WPanel
*panel
)
840 gchar
*full_name
= NULL
;
843 full_name
= get_full_filename (panel
);
844 dialog
= gnome_file_property_dialog_new (full_name
,
845 (is_a_desktop_panel (panel
)
847 : we_can_afford_the_speed
));
849 if (!is_a_desktop_panel (panel
))
850 gnome_dialog_set_parent (GNOME_DIALOG (dialog
), GTK_WINDOW (panel
->xwindow
));
852 run
= gnome_dialog_run (GNOME_DIALOG (dialog
));
854 retval
= gnome_file_property_dialog_make_changes (
855 GNOME_FILE_PROPERTY_DIALOG (dialog
));
858 gtk_widget_destroy (dialog
);
861 if (retval
&& !is_a_desktop_panel (panel
))
866 handle_open_with (GtkWidget
*widget
, WPanel
*panel
)
869 full_name
= get_full_filename (panel
);
870 gmc_open_with (full_name
);
875 handle_hard_link (GtkWidget
*widget
, WPanel
*panel
)
877 /* yeah right d: -jrb */
882 handle_symlink (GtkWidget
*widget
, WPanel
*panel
)
888 handle_edit_symlink (GtkWidget
*widget
, WPanel
*panel
)