/* Popup menus for the Midnight Commander * * Copyright (C) 1998 The Free Software Foundation * * Authors: Federico Mena * Miguel de Icaza */ #include #include "global.h" #include #include "panel.h" #include "cmd.h" #include "dialog.h" #include "ext.h" #include "gpageprop.h" #include "gpopup.h" #include "main.h" #include "gnome-file-property-dialog.h" #define CLIST_FROM_SW(panel_list) GTK_CLIST (GTK_BIN (panel_list)->child) extern int we_can_afford_the_speed; /* 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 a multiple selection */ F_NOTDIR = 1 << 4, /* Applies to non-directories */ F_DICON = 1 << 5, /* Applies to desktop icons */ F_PANEL = 1 << 6, /* Applies to files from a panel window */ F_MOUNTABLE = 1 << 7, /* Only if the device is mountable */ F_UNMOUNTABLE = 1 << 8, /* Only if the device is unmountable */ F_EJECTABLE = 1 << 9, /* Only if the device is ejectable */ F_LOCAL = 1 << 10 /* File must be on a local file system */ }; struct action { char *text; /* Menu item text */ int flags; /* Flags from the above enum */ GtkSignalFunc callback; /* Callback for menu item */ }; static void panel_action_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 panel_action_open (GtkWidget *widget, WPanel *panel) { if (do_enter (panel)) return; panel_action_open_with (widget, panel); } static void panel_action_view (GtkWidget *widget, WPanel *panel) { view_cmd (panel); } static void panel_action_view_unfiltered (GtkWidget *widget, WPanel *panel) { view_simple_cmd (panel); } static void panel_action_edit (GtkWidget *widget, WPanel *panel) { char *full = g_concat_dir_and_file (panel->cwd, selection (panel)->fname); gmc_edit_filename (full); g_free (full); } /* Pops up the icon properties pages */ static void dicon_properties (GtkWidget *widget, DesktopIconInfo *dii) { int retval = 1; char *path; GtkWidget *dlg; path = concat_dir_and_file (desktop_directory, dii->filename); /* retval = item_properties (dii->dicon, path, dii);*/ dlg = gnome_file_property_dialog_new (path, TRUE); gtk_widget_show_all (dlg); if (gnome_dialog_run (GNOME_DIALOG (dlg)) == 0) retval = gnome_file_property_dialog_make_changes (GNOME_FILE_PROPERTY_DIALOG (dlg)); /* * Apply: desktop icon name change and url change */ if (dii->url) desktop_icon_update_url (dii); gtk_widget_destroy (dlg); g_free(path); if (retval) reread_cmd (); } /* Executes a desktop icon */ static void dicon_execute (GtkWidget *widget, DesktopIconInfo *dii) { desktop_icon_info_open (dii); } static void dicon_unmount (GtkWidget *widget, DesktopIconInfo *dii) { char *full = g_concat_dir_and_file (desktop_directory, dii->filename); do_mount_umount (full, FALSE); g_free (full); } static void dicon_mount (GtkWidget *widget, DesktopIconInfo *dii) { char *full = g_concat_dir_and_file (desktop_directory, dii->filename); do_mount_umount (full, TRUE); g_free (full); } static void dicon_eject (GtkWidget *widget, DesktopIconInfo *dii) { char *full = g_concat_dir_and_file (desktop_directory, dii->filename); do_mount_umount (full, FALSE); do_eject (full); g_free (full); } static void panel_action_properties (GtkWidget *widget, WPanel *panel) { gint retval; file_entry *fe = &panel->dir.list [panel->selected]; char *full_name = concat_dir_and_file (panel->cwd, fe->fname); GtkWidget *dlg; /* if (item_properties (GTK_WIDGET (CLIST_FROM_SW (panel->list)), full_name, NULL) != 0) reread_cmd ();*/ 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 dicon_move (GtkWidget *widget, DesktopIconInfo *dii) { g_warning ("Implement this function!"); } static void dicon_copy (GtkWidget *widget, DesktopIconInfo *dii) { g_warning ("Implement this function!"); } static void dicon_delete (GtkWidget *widget, DesktopIconInfo *dii) { desktop_icon_info_delete (dii); } /* 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, gpointer data); 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); } /* The context menu: text displayed, condition that must be met and the routine that gets invoked * upon activation. */ static struct action file_actions[] = { { N_("Properties"), F_SINGLE | F_PANEL, (GtkSignalFunc) panel_action_properties }, { N_("Delete"), F_PANEL | F_ALL, (GtkSignalFunc) delete_cmd }, { N_("Delete"), F_DICON | F_ALL, (GtkSignalFunc) dicon_delete }, { N_("Properties"), F_SINGLE | F_DICON, (GtkSignalFunc) dicon_properties }, { N_("Mount device"), F_SINGLE|F_MOUNTABLE|F_DICON, (GtkSignalFunc) dicon_mount }, { N_("Unmount device"), F_SINGLE|F_UNMOUNTABLE|F_DICON, (GtkSignalFunc) dicon_unmount }, { N_("Eject device"), F_SINGLE|F_EJECTABLE|F_DICON, (GtkSignalFunc) dicon_eject }, { "", F_SINGLE, NULL }, { N_("Open"), F_LOCAL| F_PANEL | F_ALL, (GtkSignalFunc) panel_action_open }, { N_("Open"), F_LOCAL | F_DICON | F_ALL, (GtkSignalFunc) dicon_execute }, { N_("Open with"), F_LOCAL | F_PANEL | F_ALL, (GtkSignalFunc) panel_action_open_with }, { N_("View"), F_PANEL | F_NOTDIR, (GtkSignalFunc) panel_action_view }, { N_("View unfiltered"), F_PANEL | F_NOTDIR, (GtkSignalFunc) panel_action_view_unfiltered }, { N_("Edit"), F_PANEL | F_NOTDIR, (GtkSignalFunc) panel_action_edit }, { "", 0, NULL }, { N_("Move/rename..."), F_PANEL | F_ALL, (GtkSignalFunc) ren_cmd }, { N_("Copy..."), F_PANEL | F_ALL, (GtkSignalFunc) copy_cmd }, { "", 0, NULL }, { N_("Link..."), F_PANEL | F_REGULAR | F_SINGLE, (GtkSignalFunc) link_cmd }, { N_("Symlink..."), F_PANEL | F_SINGLE, (GtkSignalFunc) symlink_cmd }, { N_("Edit symlink..."), F_PANEL | F_SYMLINK, (GtkSignalFunc) edit_symlink_cmd }, { NULL, 0, NULL }, }; /* Creates the menu items for the standard actions. Returns the position at which additional menu * items should be inserted. */ static int create_actions (GtkWidget *menu, WPanel *panel, DesktopIconInfo *dii, int panel_row, char *filename) { gpointer closure; struct action *action; int pos; GnomeUIInfo uiinfo[] = { { 0 }, GNOMEUIINFO_END }; closure = panel ? (gpointer) panel : (gpointer) dii; pos = 0; for (action = file_actions; action->text; action++) { /* First, try F_PANEL and F_DICON flags */ if (!panel && (action->flags & F_PANEL)) continue; if (!dii && (action->flags & F_DICON)) continue; /* Items with F_ALL bypass any other condition */ if (!(action->flags & F_ALL)) { /* Items with F_SINGLE require that ONLY ONE marked files exist */ if (panel && (action->flags & F_SINGLE)) if (panel->marked > 1) continue; /* Items with F_NOTDIR requiere that the selection is not a directory */ if (panel && (action->flags & F_NOTDIR)) { struct stat *s = &panel->dir.list[panel_row].buf; if (panel->dir.list [panel_row].f.link_to_dir) continue; if (S_ISDIR (s->st_mode)) continue; } /* Items with F_REGULAR do not accept any strange file types */ if (panel && (action->flags & F_REGULAR)) { struct stat *s = &panel->dir.list[panel_row].buf; if (S_ISLNK (s->st_mode) && panel->dir.list[panel_row].f.link_to_dir) continue; if (S_ISSOCK (s->st_mode) || S_ISCHR (s->st_mode) || S_ISFIFO (s->st_mode) || S_ISBLK (s->st_mode)) continue; } /* Items with F_SYMLINK only operate on symbolic links */ if (panel && action->flags & F_SYMLINK) { struct stat *s = &panel->dir.list[panel_row].buf; if (!S_ISLNK (s->st_mode)) continue; } if (action->flags & (F_MOUNTABLE|F_UNMOUNTABLE)){ char *fullname; file_entry *fe; int v; int is_mounted; fullname = g_concat_dir_and_file (desktop_directory, dii->filename); fe = file_entry_from_file (fullname); if (fe){ v = is_mountable (fullname, fe, &is_mounted, NULL); file_entry_free (fe); g_free (fullname); if (!v) continue; if (is_mounted && (action->flags & F_MOUNTABLE)) continue; if ((!is_mounted) && (action->flags & F_UNMOUNTABLE)) continue; } else { g_free (fullname); continue; } } if (action->flags & (F_EJECTABLE)){ char *fullname; file_entry *fe; int v; int is_mounted; fullname = g_concat_dir_and_file (desktop_directory, dii->filename); fe = file_entry_from_file (fullname); if (fe){ v = is_mountable (fullname, fe, &is_mounted, NULL); file_entry_free (fe); if (!v){ g_free (fullname); continue; } if (!is_ejectable (fullname)){ g_free (fullname); continue; } g_free (fullname); } else { g_free (fullname); continue; } } } /* Create the appropriate GnomeUIInfo structure for the menu item */ 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 = closure; 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); pos++; } return pos; } /* These functions activate a menu popup action for a filename in a hackish way, as they have to * fetch the action from the menu item's label. */ static char * get_label_text (GtkMenuItem *item) { GtkWidget *label; /* THIS DEPENDS ON THE LATEST GNOME-LIBS! Keep this in sync with gnome-app-helper until we * figure a way to do this in a non-hackish way. */ label = GTK_BIN (item)->child; g_assert (label != NULL); return GTK_LABEL (label)->label; } static void mime_command_from_panel (GtkMenuItem *item, WPanel *panel) { char *filename; char *action; int movedir; /* * This is broken, but we dont mind. Federico * needs to explain me what was he intending here. * panel->selected does not mean, it was the icon * that got clicked. */ } static void mime_command_from_desktop_icon (GtkMenuItem *item, char *filename) { char *action, *buf; int movedir, size; char *key, *flag_key; const char *mime_type, *val, *flags; int needs_terminal = 0; action = get_label_text (item); key = gtk_object_get_user_data (GTK_OBJECT (item)); mime_type = gnome_mime_type_or_default (filename, NULL); if (!mime_type) return; /* * Find out if we need to run this in a terminal */ if (gnome_metadata_get (filename, "flags", &size, &buf) == 0){ needs_terminal = strstr (flags, "needsterminal") != 0; g_free (buf); } else { 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; } val = gnome_mime_get_value (mime_type, key); exec_extension (filename, val, NULL, NULL, 0, needs_terminal); } /* Create the menu items common to files from panel window and desktop icons, and also the items for * regexp_command() actions. */ static void create_regexp_actions (GtkWidget *menu, WPanel *panel, DesktopIconInfo *dii, int panel_row, char *filename, int insert_pos) { gpointer closure, regex_closure; int i; GtkSignalFunc regex_callback; const char *mime_type; GList *keys, *l; GnomeUIInfo uiinfo[] = { { 0 }, GNOMEUIINFO_END }; regex_callback = mime_command_from_desktop_icon; if (panel) { closure = panel; regex_closure = filename; } else { closure = dii; regex_closure = filename; } /* Fill in the regex command part */ mime_type = gnome_mime_type_or_default (filename, NULL); if (!mime_type) return; keys = gnome_mime_get_keys (mime_type); for (l = keys; l; l = l->next) { char *key = l->data; char *str; str = key; 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 = regex_callback; uiinfo[0].user_data = regex_closure; 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, insert_pos++); gtk_object_set_user_data (GTK_OBJECT (uiinfo [0].widget), key); } g_list_free (keys); } /* Convenience callback to exit the main loop of a modal popup menu when it is deactivated*/ static void menu_shell_deactivated (GtkMenuShell *menu_shell, gpointer data) { 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; } /* * Create a context menu * It can take either a WPanel or a GnomeDesktopEntry. One of them should * be set to NULL. */ int gpopup_do_popup (GdkEventButton *event, WPanel *from_panel, DesktopIconInfo *dii, int panel_row, char *filename) { GtkWidget *menu; int pos; guint id; g_return_val_if_fail (event != NULL, -1); g_return_val_if_fail (from_panel != NULL || dii != NULL || filename != 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); /* Fill the menu */ pos = create_actions (menu, from_panel, dii, panel_row, filename); create_regexp_actions (menu, from_panel, dii, panel_row, filename, pos); /* 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); return get_active_index (GTK_MENU (menu)); }