/* Popup menus for the Midnight Commander * * Copyright (C) 1998 The Free Software Foundation * * Authors: Federico Mena * Miguel de Icaza * Jonathan Blandford */ /* These rules really need to be duplicated elsewhere. * They should exist for the menu bar too... */ #include #include "global.h" #include #include "panel.h" #include "file.h" #include "cmd.h" #include "dialog.h" #include "ext.h" #include "main.h" #include "../vfs/vfs.h" #include "gpopup.h" #include "gnome-file-property-dialog.h" #include "gnome-open-dialog.h" #include "gmain.h" #include "gmount.h" #define CLIST_FROM_SW(panel_list) GTK_CLIST (GTK_BIN (panel_list)->child) int is_trash_panel = FALSE; /* Flags for the popup menu entries. They specify to which kinds of files an * entry is valid for. */ enum { F_ALL = 1 << 0, /* Applies to all files */ F_REGULAR = 1 << 1, /* Applies only to regular files */ F_SYMLINK = 1 << 2, /* Applies only to symlinks */ F_SINGLE = 1 << 3, /* Applies only to a single file, not to multiple files */ F_NOTDIR = 1 << 4, /* Applies to non-directories */ F_NOTDEV = 1 << 5, /* Applies to non-devices only (ie. reg, lnk, dir) */ F_ADVANCED = 1 << 6, /* Only appears in advanced mode */ F_MIME_ACTIONS = 1 << 7 /* Special marker for the position of MIME actions */ }; /* An entry in the actions menu */ typedef gboolean (*menu_func) (WPanel *panel, DesktopIconInfo *dii); struct action { char *text; /* Menu item text */ int flags; /* Flags from the above enum */ gpointer callback; /* Callback for menu item */ menu_func func; /* NULL if item is always present; a predicate otherwise */ }; /* Multiple File commands */ static void handle_open (GtkWidget *widget, WPanel *panel); static void handle_mount (GtkWidget *widget, WPanel *panel); static void handle_unmount (GtkWidget *widget, WPanel *panel); static void handle_eject (GtkWidget *widget, WPanel *panel); static void handle_view (GtkWidget *widget, WPanel *panel); static void handle_view_unfiltered (GtkWidget *widget, WPanel *panel); static void handle_edit (GtkWidget *widget, WPanel *panel); static void handle_copy (GtkWidget *widget, WPanel *panel); static void handle_trash (GtkWidget *widget, WPanel *panel); static void handle_empty_trash (GtkWidget *widget, WPanel *panel); static void handle_delete (GtkWidget *widget, WPanel *panel); static void handle_move (GtkWidget *widget, WPanel *panel); /* F_SINGLE file commands */ static void handle_properties (GtkWidget *widget, WPanel *panel); static void handle_open_with (GtkWidget *widget, WPanel *panel); static void handle_symlink (GtkWidget *widget, WPanel *panel); static void handle_hard_link (GtkWidget *widget, WPanel *panel); static void handle_edit_symlink (GtkWidget *widget, WPanel *panel); /* Helper funcs and testing funcs. */ static gboolean check_mount_func (WPanel *panel, DesktopIconInfo *dii); static gboolean check_unmount_func (WPanel *panel, DesktopIconInfo *dii); static gboolean check_eject_func (WPanel *panel, DesktopIconInfo *dii); static gboolean check_device_func (WPanel *panel, DesktopIconInfo *dii); static gboolean check_trash_func (WPanel *panel, DesktopIconInfo *dii); static gboolean check_trash_icon_func (WPanel *panel, DesktopIconInfo *dii); static gchar * get_full_filename (WPanel *panel); /* Now, the actual code */ static gchar * get_full_filename (WPanel *panel) { if (is_a_desktop_panel (panel)) { gint i; for (i = 0; i < panel->count; i++) if (panel->dir.list [i].f.marked) { return concat_dir_and_file (panel->cwd, panel->dir.list [i].fname); } g_return_val_if_fail (FALSE, NULL); } else return concat_dir_and_file (panel->cwd, selection (panel)->fname); } static gboolean check_mount_umount (DesktopIconInfo *dii, int mount) { char *full_name; file_entry *fe; int v; int is_mounted; full_name = g_concat_dir_and_file (desktop_directory, dii->filename); fe = file_entry_from_file (full_name); if (!fe) { g_free (full_name); return FALSE; } v = is_mountable (full_name, fe, &is_mounted, NULL); file_entry_free (fe); g_free (full_name); if (!v) return FALSE; if (is_mounted && mount) return FALSE; if (!is_mounted && !mount) return FALSE; return TRUE; } static gboolean check_mount_func (WPanel *panel, DesktopIconInfo *dii) { if (!dii) return FALSE; return check_mount_umount (dii, TRUE); } static gboolean check_unmount_func (WPanel *panel, DesktopIconInfo *dii) { if (!dii) return FALSE; return check_mount_umount (dii, FALSE); } static gboolean check_eject_func (WPanel *panel, DesktopIconInfo *dii) { char *full_name; file_entry *fe; int v; int is_mounted; int retval; if (!dii) return FALSE; full_name = g_concat_dir_and_file (desktop_directory, dii->filename); fe = file_entry_from_file (full_name); if (!fe) { g_free (full_name); return FALSE; } v = is_mountable (full_name, fe, &is_mounted, NULL); file_entry_free (fe); if (!v) retval = FALSE; else if (!is_ejectable (full_name)) retval = FALSE; else retval = TRUE; g_free (full_name); return retval; } static gboolean check_device_func (WPanel *panel, DesktopIconInfo *dii) { return (check_mount_func (panel, dii) || check_unmount_func (panel, dii) || check_eject_func (panel, dii)); } static gboolean check_trash_func (WPanel *panel, DesktopIconInfo *dii) { gchar *trash_dir; struct stat st; gint i; trash_dir = g_strconcat (gnome_user_home_dir, "/", DESKTOP_DIR_NAME, "/", "Trash.gmc", NULL); if (mc_stat (trash_dir, &st) || !S_ISDIR (st.st_mode)) { g_free (trash_dir); return FALSE; } if (strncmp (trash_dir, panel->cwd, strlen (trash_dir)) == 0) { g_free (trash_dir); return FALSE; } for (i = 0; i < panel->count; i++) { if (panel->dir.list [i].f.marked) { gchar *desktop_file = concat_dir_and_file (panel->cwd, panel->dir.list [i].fname); if (strncmp (desktop_file, trash_dir, strlen (trash_dir)) == 0) { g_free (desktop_file); g_free (trash_dir); return FALSE; } g_free (desktop_file); } } g_free (trash_dir); return TRUE; } static gboolean check_trash_icon_func (WPanel *panel, DesktopIconInfo *dii) { gchar *trash_dir; gchar *file_name; if (!is_a_desktop_panel (panel)) return FALSE; trash_dir = g_strconcat (gnome_user_home_dir, "/", DESKTOP_DIR_NAME, "/", "Trash.gmc", NULL); file_name = get_full_filename (panel); if (strncmp (trash_dir, file_name, strlen (trash_dir)) == 0) { DIR *dirp; struct dirent *dp; gint i = 0; dirp = mc_opendir (file_name); g_free (trash_dir); g_free (file_name); for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)) { i ++; if (i > 2) { mc_closedir (dirp); return TRUE; } } mc_closedir (dirp); return FALSE; } g_free (trash_dir); g_free (file_name); return FALSE; } /* global vars */ extern int we_can_afford_the_speed; static struct action file_actions[] = { { N_("Open"), F_NOTDEV | F_SINGLE, handle_open, NULL }, { "", F_NOTDEV | F_SINGLE, NULL, NULL }, { N_("Mount device"), F_ALL | F_SINGLE, handle_mount, check_mount_func }, { N_("Unmount device"), F_ALL | F_SINGLE, handle_unmount, check_unmount_func }, { N_("Eject device"), F_ALL | F_SINGLE, handle_eject, check_eject_func }, { N_("Empty Trash"), F_SINGLE, handle_empty_trash, check_trash_icon_func }, /* Custom actions go here */ { "", F_MIME_ACTIONS | F_SINGLE, NULL, check_device_func }, { N_("Open with..."), F_REGULAR | F_SINGLE, handle_open_with, NULL }, { N_("View"), F_REGULAR | F_SINGLE, handle_view, NULL }, { N_("View Unfiltered"), F_REGULAR | F_ADVANCED | F_SINGLE, handle_view_unfiltered, NULL }, { N_("Edit"), F_REGULAR | F_SINGLE, handle_edit, NULL }, { "", F_REGULAR | F_SINGLE, NULL, NULL }, { N_("Copy..."), F_ALL, handle_copy, NULL }, { N_("Move to Trash"), F_ALL, handle_trash, check_trash_func }, { N_("Delete"), F_ALL, handle_delete, NULL }, { N_("Move..."), F_ALL, handle_move, NULL }, { N_("Hard Link..."), F_ADVANCED | F_SINGLE, handle_hard_link, NULL }, { N_("Symlink..."), F_SINGLE, handle_symlink, NULL }, { N_("Edit Symlink..."), F_SYMLINK | F_SINGLE, handle_edit_symlink, NULL }, { "", F_SINGLE | F_ALL, NULL, NULL }, { N_("Properties..."), F_SINGLE | F_ALL, handle_properties, NULL }, { NULL, 0, NULL, NULL } }; /* This is our custom signal connection function for popup menu items -- see below for the * marshaller information. We pass the original callback function as the data pointer for the * marshaller (uiinfo->moreinfo). */ static void popup_connect_func (GnomeUIInfo *uiinfo, gchar *signal_name, GnomeUIBuilderData *uibdata) { g_assert (uibdata->is_interp); if (uiinfo->moreinfo) { gtk_object_set_data (GTK_OBJECT (uiinfo->widget), "popup_user_data", uiinfo->user_data); gtk_signal_connect_full (GTK_OBJECT (uiinfo->widget), signal_name, NULL, uibdata->relay_func, uiinfo->moreinfo, uibdata->destroy_func, FALSE, FALSE); } } /* Our custom marshaller for menu items. We need it so that it can extract the * per-attachment user_data pointer from the parent menu shell and pass it to * the callback. This overrides the user-specified data from the GnomeUIInfo * structures. */ typedef void (* ActivateFunc) (GtkObject *object, WPanel *panel); static void popup_marshal_func (GtkObject *object, gpointer data, guint n_args, GtkArg *args) { ActivateFunc func; gpointer user_data; func = (ActivateFunc) data; user_data = gtk_object_get_data (object, "popup_user_data"); gtk_object_set_data (GTK_OBJECT (GTK_WIDGET (object)->parent), "popup_active_item", object); (* func) (object, user_data); } /* Fills the menu with the specified uiinfo at the specified position, using our * magic marshallers to be able to fetch the active item. The code is * shamelessly ripped from gnome-popup-menu. */ static void fill_menu (GtkMenuShell *menu_shell, GnomeUIInfo *uiinfo, int pos) { GnomeUIBuilderData uibdata; /* We use our own callback marshaller so that it can fetch the popup * user data from the popup menu and pass it on to the user-defined * callbacks. */ uibdata.connect_func = popup_connect_func; uibdata.data = NULL; uibdata.is_interp = TRUE; uibdata.relay_func = popup_marshal_func; uibdata.destroy_func = NULL; gnome_app_fill_menu_custom (menu_shell, uiinfo, &uibdata, NULL, FALSE, pos); } /* Convenience function to free something when an object is destroyed */ static void free_on_destroy (GtkObject *object, gpointer data) { g_free (data); } /* Callback for MIME-based actions */ static void mime_action_callback (GtkWidget *widget, gpointer data) { char *filename; char *key, *buf; const char *mime_type; const char *value; int needs_terminal = 0; int size; filename = data; key = gtk_object_get_user_data (GTK_OBJECT (widget)); g_assert (filename != NULL); g_assert (key != NULL); if (use_magic) mime_type = gnome_mime_type_or_default_of_file (filename, NULL); else mime_type = gnome_mime_type_or_default (filename, NULL); g_assert (mime_type != NULL); /* * Find out if we need to run this in a terminal */ if (gnome_metadata_get (filename, "flags", &size, &buf) == 0){ needs_terminal = strstr (buf, "needsterminal") != 0; g_free (buf); } else { char *flag_key; const char *flags; flag_key = g_strconcat ("flags.", key, "flags", NULL); flags = gnome_mime_get_value (filename, flag_key); g_free (flag_key); if (flags) needs_terminal = strstr (flags, "needsterminal") != 0; } value = gnome_mime_get_value (mime_type, key); exec_extension (filename, value, NULL, NULL, 0, needs_terminal); } /* Escapes the underlines in the specified string for use by GtkLabel */ static char * escape_underlines (char *str) { char *buf; char *p; buf = g_new (char, 2 * strlen (str) + 1); for (p = buf; *str; str++) { if (*str == '_') *p++ = '_'; *p++ = *str; } *p = '\0'; return buf; } /* Creates the menu items for actions based on the MIME type of the selected * file in the panel. */ static int create_mime_actions (GtkWidget *menu, WPanel *panel, int pos, DesktopIconInfo *dii) { char *full_name; const char *mime_type; GList *keys, *l; gint pos_init = pos; GnomeUIInfo uiinfo[] = { { 0 }, GNOMEUIINFO_END }; if (is_a_desktop_panel (panel)) { g_assert (dii != NULL); full_name = g_concat_dir_and_file (panel->cwd, dii->filename); } else full_name = g_concat_dir_and_file (panel->cwd, panel->dir.list[panel->selected].fname); if (use_magic) mime_type = gnome_mime_type_or_default_of_file (full_name, NULL); else mime_type = gnome_mime_type_or_default (full_name, NULL); if (!mime_type) { g_free (full_name); return pos; } keys = gnome_mime_get_keys (mime_type); for (l = keys; l; l = l->next) { char *key; char *str; str = key = l->data; if (strncmp (key, "open.", 5) != 0) continue; str += 5; while (*str && *str != '.') str++; if (*str) str++; if (!*str) continue; /* Create the item for that entry */ str = escape_underlines (str); uiinfo[0].type = GNOME_APP_UI_ITEM; uiinfo[0].label = str; uiinfo[0].hint = NULL; uiinfo[0].moreinfo = mime_action_callback; uiinfo[0].user_data = full_name; uiinfo[0].unused_data = NULL; uiinfo[0].pixmap_type = GNOME_APP_PIXMAP_NONE; uiinfo[0].pixmap_info = NULL; uiinfo[0].accelerator_key = 0; uiinfo[0].ac_mods = 0; uiinfo[0].widget = NULL; fill_menu (GTK_MENU_SHELL (menu), uiinfo, pos++); g_free (str); gtk_object_set_user_data (GTK_OBJECT (uiinfo[0].widget), key); } /* Remember to free this memory */ gtk_signal_connect (GTK_OBJECT (menu), "destroy", (GtkSignalFunc) free_on_destroy, full_name); if (pos_init != pos) { uiinfo[0].type = GNOME_APP_UI_SEPARATOR; fill_menu (GTK_MENU_SHELL (menu), uiinfo, pos++); g_list_free (keys); } return pos; } /* Creates the menu items for the standard actions. Returns the position at * which additional menu items should be inserted. */ static void create_actions (GtkWidget *menu, gint flags, WPanel *panel, DesktopIconInfo *dii) { struct action *action; int pos; GnomeUIInfo uiinfo[] = { { 0 }, GNOMEUIINFO_END }; pos = 0; for (action = file_actions; action->text; action++) { /* Insert the MIME actions if appropriate */ if ((action->flags & F_MIME_ACTIONS) && (flags & F_SINGLE)) { int curpos = pos; pos = create_mime_actions (menu, panel, pos, dii); /* Why do we do this? If the mime actions are there, and it's mountable, * we can count on the separator at the end of the mime_actions * menu. However, if the mime_actions aren't there, we need a separator */ if (pos == curpos && (action->func)(panel, dii)) { uiinfo[0].type = GNOME_APP_UI_SEPARATOR; fill_menu (GTK_MENU_SHELL (menu), uiinfo, pos++); } continue; } /* Filter the actions that are not appropriate */ if ((action->flags & flags) != action->flags) continue; if (action->func && !((action->func)(panel, dii))) continue; /* Create the menu item for this action */ if (action->text[0]) { uiinfo[0].type = GNOME_APP_UI_ITEM; uiinfo[0].label = _(action->text); uiinfo[0].hint = NULL; uiinfo[0].moreinfo = action->callback; uiinfo[0].user_data = (gpointer) panel; uiinfo[0].unused_data = NULL; uiinfo[0].pixmap_type = GNOME_APP_PIXMAP_NONE; uiinfo[0].pixmap_info = NULL; uiinfo[0].accelerator_key = 0; uiinfo[0].ac_mods = 0; uiinfo[0].widget = NULL; } else uiinfo[0].type = GNOME_APP_UI_SEPARATOR; fill_menu (GTK_MENU_SHELL (menu), uiinfo, pos++); } } /* Convenience callback to exit the main loop of a modal popup menu when it is deactivated*/ static void menu_shell_deactivated (GtkMenuShell *menu_shell, WPanel *panel) { gtk_main_quit (); } /* Returns the index of the active item in the specified menu, or -1 if none is active */ static int get_active_index (GtkMenu *menu) { GList *l; GtkWidget *active; int i; active = gtk_object_get_data (GTK_OBJECT (menu), "popup_active_item"); for (i = 0, l = GTK_MENU_SHELL (menu)->children; l; l = l->next, i++) if (active == l->data) return i; return -1; } #define REMOVE(x,f) x &= ~f int gpopup_do_popup2 (GdkEventButton *event, WPanel *panel, DesktopIconInfo *dii) { GtkWidget *menu; gint flags = F_ALL | F_REGULAR | F_SYMLINK | F_SINGLE | F_NOTDEV | F_NOTDIR; struct stat s; guint id; int i; int marked; g_return_val_if_fail (event != NULL, -1); g_return_val_if_fail (panel != NULL, -1); menu = gtk_menu_new (); /* Connect to the deactivation signal to be able to quit our modal main * loop. */ id = gtk_signal_connect (GTK_OBJECT (menu), "deactivate", (GtkSignalFunc) menu_shell_deactivated, NULL); marked = 0; for (i = 0; i < panel->count; i++) { if (!strcmp (panel->dir.list [i].fname, "..") || !panel->dir.list[i].f.marked) continue; marked++; s = panel->dir.list[i].buf; if (S_ISLNK (s.st_mode)) mc_stat (panel->dir.list [i].fname, &s); else REMOVE (flags, F_SYMLINK); if (S_ISDIR (s.st_mode)) REMOVE (flags, F_NOTDIR); if (!S_ISREG (s.st_mode)) REMOVE (flags, F_REGULAR); if (S_ISCHR (s.st_mode) || S_ISBLK (s.st_mode)) REMOVE (flags, F_NOTDEV); } if (marked == 0) return 1; if (marked > 1) REMOVE (flags, F_SINGLE); /* Fill the menu */ create_actions (menu, flags, panel, dii); /* Run it */ gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time); gtk_grab_add (menu); gtk_main (); gtk_grab_remove (menu); gtk_signal_disconnect (GTK_OBJECT (menu), id); i = get_active_index (GTK_MENU (menu)); gtk_widget_unref (menu); return 1; } static void handle_open (GtkWidget *widget, WPanel *panel) { if (is_a_desktop_panel (panel)) { gchar *full_name; DesktopIconInfo *dii; full_name = get_full_filename (panel); dii = desktop_icon_info_get_by_filename (x_basename (full_name)); g_assert (dii != NULL); desktop_icon_info_open (dii); g_free (full_name); return; } do_enter (panel); } static void perform_mount_unmount (WPanel *panel, int mount) { char *full_name; DesktopIconInfo *dii; g_assert (is_a_desktop_panel (panel)); full_name = get_full_filename (panel); dii = desktop_icon_info_get_by_filename (x_basename (full_name)); g_assert (dii != NULL); desktop_icon_set_busy (dii, TRUE); do_mount_umount (full_name, mount); desktop_icon_set_busy (dii, FALSE); g_free (full_name); } static void handle_mount (GtkWidget *widget, WPanel *panel) { perform_mount_unmount (panel, TRUE); update_panels (UP_RELOAD, UP_KEEPSEL); } static void handle_unmount (GtkWidget *widget, WPanel *panel) { perform_mount_unmount (panel, FALSE); update_panels (UP_RELOAD, UP_KEEPSEL); } static void handle_eject (GtkWidget *widget, WPanel *panel) { char *full_name; char *lname; DesktopIconInfo *dii; g_assert (is_a_desktop_panel (panel)); full_name = get_full_filename (panel); dii = desktop_icon_info_get_by_filename (x_basename (full_name)); g_assert (dii != NULL); desktop_icon_set_busy (dii, TRUE); lname = g_readlink (full_name); if (!lname){ g_free (full_name); desktop_icon_set_busy (dii, FALSE); return; } if (is_block_device_mounted (lname)) do_mount_umount (full_name, FALSE); do_eject (lname); desktop_icon_set_busy (dii, FALSE); update_panels (UP_RELOAD, UP_KEEPSEL); g_free (lname); g_free (full_name); } static void handle_view (GtkWidget *widget, WPanel *panel) { gchar *full_name; full_name = get_full_filename (panel); gmc_view (full_name, 0); g_free (full_name); } static void handle_view_unfiltered (GtkWidget *widget, WPanel *panel) { /* We need it to do the right thing later. */ /*view_simple_cmd (panel);*/ return; } static void handle_edit (GtkWidget *widget, WPanel *panel) { gchar *full_name; full_name = get_full_filename (panel); gmc_edit (full_name); g_free (full_name); } static void handle_copy (GtkWidget *widget, WPanel *panel) { copy_cmd (); } /* Empties trash when in the Trash panel */ static void handle_trash (GtkWidget *widget, WPanel *panel) { gchar *trash_dir; trash_dir = g_strconcat (gnome_user_home_dir, "/", DESKTOP_DIR_NAME, "/", "Trash.gmc", NULL); if (panel_operate (cpanel, OP_MOVE, trash_dir, FALSE)) { update_panels (UP_OPTIMIZE, UP_KEEPSEL); repaint_screen (); } } /* Empties trash from the desktop */ static void handle_empty_trash (GtkWidget *widget, WPanel *panel) { gnome_empty_trash (NULL, NULL); } static void handle_delete (GtkWidget *widget, WPanel *panel) { delete_cmd (); } static void handle_move (GtkWidget *widget, WPanel *panel) { ren_cmd (); } /* F_SINGLE file commands */ static void handle_properties (GtkWidget *widget, WPanel *panel) { gint retval = 0; GtkWidget *dialog; gchar *full_name = NULL; int run; full_name = get_full_filename (panel); dialog = gnome_file_property_dialog_new (full_name, (is_a_desktop_panel (panel) ? TRUE : we_can_afford_the_speed)); if (!is_a_desktop_panel (panel)) gnome_dialog_set_parent (GNOME_DIALOG (dialog), GTK_WINDOW (panel->xwindow)); run = gnome_dialog_run (GNOME_DIALOG (dialog)); if (run == 0) retval = gnome_file_property_dialog_make_changes ( GNOME_FILE_PROPERTY_DIALOG (dialog)); if (run != -1) gtk_widget_destroy (dialog); g_free (full_name); if (retval && !is_a_desktop_panel (panel)) reread_cmd (); } static void handle_open_with (GtkWidget *widget, WPanel *panel) { gchar *full_name; full_name = get_full_filename (panel); gmc_open_with (full_name); g_free (full_name); } static void handle_hard_link (GtkWidget *widget, WPanel *panel) { /* yeah right d: -jrb */ link_cmd (); } static void handle_symlink (GtkWidget *widget, WPanel *panel) { symlink_cmd (); } static void handle_edit_symlink (GtkWidget *widget, WPanel *panel) { edit_symlink_cmd (); }