/* 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 "cmd.h" #include "dialog.h" #include "ext.h" #include "main.h" #include "../vfs/vfs.h" #include "gpageprop.h" #include "gpopup.h" #include "gnome-file-property-dialog.h" #define CLIST_FROM_SW(panel_list) GTK_CLIST (GTK_BIN (panel_list)->child) /* 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 */ }; /* typedefs */ struct action { char *text; /* Menu item text */ int flags; /* Flags from the above enum */ gpointer callback; /* Callback for menu item */ }; /* Multiple File commands */ static void handle_open (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_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_hard_link (GtkWidget *widget, WPanel *panel); static void handle_symlink (GtkWidget *widget, WPanel *panel); static void handle_edit_symlink (GtkWidget *widget, WPanel *panel); /* global vars */ extern int we_can_afford_the_speed; static struct action file_actions[] = { { N_("Open"), F_NOTDEV, handle_open }, { "", F_NOTDEV, NULL }, { "", F_MIME_ACTIONS, NULL }, { N_("Open with..."), F_REGULAR | F_SINGLE, handle_open_with }, { N_("View"), F_REGULAR | F_SINGLE, handle_view }, { N_("View Unfiltered"), F_REGULAR | F_ADVANCED | F_SINGLE, handle_view_unfiltered }, { N_("Edit"), F_REGULAR | F_SINGLE, handle_edit }, { "", F_REGULAR | F_SINGLE, NULL }, { N_("Copy..."), F_ALL, handle_copy }, { N_("Delete"), F_ALL, handle_delete }, { N_("Move..."), F_ALL, handle_move }, { N_("Hard Link..."), F_ADVANCED | F_SINGLE, handle_hard_link }, { N_("Symlink..."), F_SINGLE, handle_symlink }, { N_("Edit Symlink..."), F_SYMLINK | F_SINGLE, handle_edit_symlink }, { "", F_SINGLE | F_ALL, NULL }, { N_("Properties..."), F_SINGLE | F_ALL, handle_properties }, { NULL, 0, NULL } }; #if 0 static action generic_actions[] = { { N_("NEW(FIXME)"), F_ALL, NULL }, { "", F_ALL, NULL }, { N_("Change Background"), F_DICON, handle_display_properties }, { N_("Rescan Directory"), F_ALL, handle_rescan }, { N_("Arrange Icons"), F_DICON, handle_arrange_icons }, { N_(""), F_DICON, NULL }, { N_("Logout"), F_DICON, handle_logout }, { NULL, 0, NULL } }; #endif /* 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; char *mime_type; char *value; filename = data; key = gtk_object_get_user_data (GTK_OBJECT (widget)); g_assert (filename != NULL); g_assert (key != NULL); mime_type = gnome_mime_type_or_default (filename, NULL); g_assert (mime_type != NULL); value = gnome_mime_get_value (mime_type, key); exec_extension (filename, value, NULL, NULL, 0); } /* 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) { char *full_name; const char *mime_type; GList *keys, *l; GnomeUIInfo uiinfo[] = { { 0 }, GNOMEUIINFO_END }; full_name = g_concat_dir_and_file (panel->cwd, panel->dir.list[panel->selected].fname); mime_type = gnome_mime_type_or_default (full_name, NULL); g_free (full_name); if (!mime_type) 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 */ 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++); gtk_object_set_user_data (GTK_OBJECT (uiinfo[0].widget), key); /* Remember to free this memory */ gtk_signal_connect (GTK_OBJECT (uiinfo[0].widget), "destroy", (GtkSignalFunc) free_on_destroy, full_name); } 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) { 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)) { pos = create_mime_actions (menu, panel, pos); continue; } /* Filter the actions that are not appropriate */ if ((action->flags & flags) != action->flags) 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) { 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); } g_assert (marked > 0); if (marked > 1) REMOVE (flags, F_SINGLE); /* Fill the menu */ create_actions (menu, flags, panel); /* 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_destroy (menu); return i; } static void handle_open (GtkWidget *widget, WPanel *panel) { if (do_enter (panel)) return; handle_open_with (widget, panel); } static void handle_view (GtkWidget *widget, WPanel *panel) { view_cmd (panel); } static void handle_view_unfiltered (GtkWidget *widget, WPanel *panel) { view_simple_cmd (panel); } static void handle_edit (GtkWidget *widget, WPanel *panel) { edit_cmd (panel); } static void handle_copy (GtkWidget *widget, WPanel *panel) { copy_cmd (); } 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; file_entry *fe; char *full_name; GtkWidget *dlg; fe = &panel->dir.list [panel->selected]; full_name = concat_dir_and_file (panel->cwd, fe->fname); dlg = gnome_file_property_dialog_new (full_name, we_can_afford_the_speed); gnome_dialog_set_parent (GNOME_DIALOG (dlg), GTK_WINDOW (gtk_widget_get_toplevel (panel->ministatus))); if (gnome_dialog_run (GNOME_DIALOG (dlg)) == 0) retval = gnome_file_property_dialog_make_changes (GNOME_FILE_PROPERTY_DIALOG (dlg)); gtk_widget_destroy (dlg); g_free (full_name); if (retval) reread_cmd (); } static void handle_open_with (GtkWidget *widget, WPanel *panel) { char *command; command = input_expand_dialog (_("Open with"), _("Enter extra arguments:"), panel->dir.list [panel->selected].fname); if (!command) return; execute (command); g_free (command); } 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 (); } static void handle_rescan (GtkWidget *widget, WPanel *panel) { reread_cmd (); }