/* $Id$ Copyright (C) 2003-2011 tooar This file is part of emelFM2. emelFM2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses. */ /** @file src/actions/e2_action.c @brief action system This file contains functions for the action system. */ /** \page actions the action system ToDo - description of how actions work \section file_operations operations on selected items ToDo */ #include "emelfm2.h" #include #include "e2_action.h" #include "e2_action_option.h" #include "e2_task.h" #include "e2_filelist.h" #include "e2_plugins.h" #include "e2_filetype.h" #include "e2_dialog.h" #include "e2_mkdir_dialog.h" #include "e2_complete.h" //static GHashTable *actions_hash; //static GPtrArray *actions_array; GtkTreeStore *actions_store; //for action-names in config dialog combo renderers /*******************/ /***** actions *****/ /*******************/ /** @brief process a toggle-button click This expects as data the key of the relevant toggle data struct in the toggles hash @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ static gboolean _e2_action_do_toggle (gpointer from, E2_ActionRuntime *art) { gboolean newstate, retval; gchar *hash_name = (gchar *) art->data; E2_ToggleData *ex = g_hash_table_lookup (toggles_hash, hash_name); //check if action name is in the array of "self-managed" toggles gint i; for (i = 0; i < E2_TOGGLE_COUNT; i++) { if (!strcmp (toggles_array [i], ex->true_action) && !strcmp (ex->true_action, ex->false_action)) break; } if (i < E2_TOGGLE_COUNT) //this is a self-mangaged toggle newstate = !ex->current_state; else newstate = e2_toolbar_button_toggle_custom (hash_name); //get the operation corresponding to the un-toggled state gchar *cc = g_strdup ((newstate) ? ex->false_action : ex->true_action); if (e2_action_check (cc) != NULL) { gchar *gap = e2_utils_find_whitespace (cc); if (gap != NULL) { *gap = '\0'; gap = e2_utils_pass_whitespace (gap+1); } retval = e2_action_run_simple_from (cc, gap, from); } else { gint res = e2_command_run (cc, E2_COMMAND_RANGE_DEFAULT, from #ifdef E2_COMMANDQ , TRUE #endif ); retval = (res == 0); } g_free (cc); return retval; } /** @brief process a constructed custom command This expects data string of the form " realcmd [realargs]" @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if command returned 0 */ static gboolean _e2_action_custom_command (gpointer from, E2_ActionRuntime *art) { gchar *command = g_strdup ((gchar *)art->data); gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, from #ifdef E2_COMMANDQ , TRUE #endif ); g_free (command); return (res == 0); } /** @brief initiate configuration change @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_action_configure (gpointer from, E2_ActionRuntime *art) { //put this in config dialog file ? // gtk_widget_set_sensitive (app.main_window, FALSE); gchar *page = (gchar *)art->data; e2_config_dialog_create (page); // gtk_widget_set_sensitive (app.main_window, TRUE); return TRUE; } /** @brief revert all configuration settings to defailt values @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_action_configure_default (gpointer from, E2_ActionRuntime *art) { //dump old data, don't re-read the config file, recreate screen e2_option_refresh (FALSE, TRUE); return TRUE; } #ifdef E2_VFS /** @brief dummy action for plugins which do not really belong on a plugins menu @param from the button, menu item etc which was activated @param art action runtime data @return FALSE always */ gboolean e2_action_inaction (gpointer from, E2_ActionRuntime *art) { return FALSE; } #endif /*********************/ /***** callbacks *****/ /*********************/ /** @brief actions treestore sort-function This sorts on column 1 of the store @param model the GtkTreeModel for the store being sorted @param a a GtkTreeIter in @a model @param b another GtkTreeIter in @a model @param userdata UNUSED data specified when the compare func was assigned @return <0, 0, >0 if a sorts before b, with b, or after b, respectively */ static gint _e2_action_tree_compare_cb (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata) { gint ret; gchar *name1, *name2; gtk_tree_model_get (model, a, 1, &name1, -1); gtk_tree_model_get (model, b, 1, &name2, -1); if (name1 == NULL || name2 == NULL) { if (name1 == NULL && name2 == NULL) return 0; if (name1 == NULL) { g_free (name2); return -1; } else { g_free (name1); return 1; } } else { ret = g_utf8_collate (name1,name2); g_free(name1); g_free(name2); } return ret; } /** @brief actions treestore visibility-function This determines which actions are visible in a filter model for the store, based on data stored in column 2 @param model the filtermodel for the actions store @param iter pointer to data for row in @a model to be evaluated @param data pointerised flags specified when @a model was created @return TRUE if the row is visible */ static gboolean _e2_action_visible_cb (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gboolean retval; E2_ACTION_EXCLUDE ex1 = GPOINTER_TO_INT (data); E2_ACTION_EXCLUDE ex2; gchar *name; //, *child; // gboolean notfake, col5; gtk_tree_model_get (model, iter, 0, &name, //1, &child, 2, &ex2, // 3, ¬fake, 4, &col5, -1); retval = !(ex1 & ex2 & 0xffff); //special cases if ((ex1 & E2_ACTION_INCLUDE_FILES) && name != NULL) retval = (retval && g_str_has_prefix (name, _A(6))); g_free (name); // g_free (child); return retval; } static GtkWidget *selitem = NULL; /** @brief "button-press-event" signal callback for a hover-menu widget @param item the menu-item where the button was pressed @param event button-event data @param data pointer to data struct for the hover action @return TRUE if the event was processed */ static gboolean _e2_action_hover_buttonpress_cb (GtkWidget *item, GdkEventButton *event, E2HoverData *data) { if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { GtkWidget *menuitem = gtk_menu_get_active (GTK_MENU (data->menu)); gtk_menu_shell_activate_item (GTK_MENU_SHELL (data->menu), menuitem, TRUE); data->menu = NULL; //will have been destroyed by activation selitem = NULL; return TRUE; } return FALSE; } /** @brief "motion-notify" event signal for entered history menu @param widget the widget which has been traversed @param event motion-event data @param data pointer to data struct for the hover action @return FALSE always */ static gboolean _e2_action_hover_pointermove_cb (GtkWidget *widget, GdkEventMotion *event, E2HoverData *data) { GtkWidget *menuitem = gtk_get_event_widget ((GdkEvent*) event); if (selitem != menuitem) { if (selitem != NULL) g_signal_handlers_disconnect_by_func ((gpointer)selitem, _e2_action_hover_buttonpress_cb, data); selitem = menuitem; //ensure it shows properly gtk_menu_shell_select_item (GTK_MENU_SHELL (data->menu), menuitem); //hack to select it too GList *items = gtk_container_get_children (GTK_CONTAINER (data->menu)); guint index = g_list_index (items, menuitem); gtk_menu_set_active (GTK_MENU (data->menu), index); g_list_free (items); //detect button presses g_signal_connect (G_OBJECT (menuitem), "button-press-event", G_CALLBACK (_e2_action_hover_buttonpress_cb), data); } return FALSE; } /** @brief "enter-notify-event" signal callback for a hover-menu parent-window widget This simply kills any hover-timer @param widget the entered widget @param event crossing-event data @param data pointer to data struct for the hover action @return FALSE always so the event is propagated */ static gboolean _e2_action_enter_menu_cb (GtkWidget *widget, GdkEventCrossing *event, E2HoverData *data) { //kill timer initiated when button was exited, prevent the popup menu from closing if (data->timer_id > 0) { g_source_remove (data->timer_id); data->timer_id = 0; } #ifdef USE_GTK2_18 if (G_LIKELY(!gtk_widget_has_grab (widget))) #else if (G_LIKELY(!GTK_WIDGET_HAS_GRAB (widget))) #endif gtk_grab_add (widget); //apply keyboard and mouse focus g_signal_connect (G_OBJECT (widget), "motion-notify-event", G_CALLBACK (_e2_action_hover_pointermove_cb), data); return FALSE; } /** @brief "leave-notify-event" signal callback for a hover-menu parent-window widget @param widget the exited widget @param event crossing-event data @param data pointer to data struct for the hover action @return FALSE always so the event is propagated */ static gboolean _e2_action_leave_menu_cb (GtkWidget *widget, GdkEventCrossing *event, E2HoverData *data) { printd (DEBUG, "In _e2_action_leave_menu_cb()"); #ifdef USE_GTK2_18 if (G_LIKELY (gtk_widget_has_grab (widget))) #else if (G_LIKELY (GTK_WIDGET_HAS_GRAB (widget))) #endif gtk_grab_remove (widget); gtk_widget_destroy (widget); data->menu = NULL; selitem = NULL; return FALSE; } /** @brief timer callback to implement a hover callback @brief data pointer to hover data @return FALSE to cancel the timer */ static gboolean _e2_action_do_hover_cb (E2HoverData *data) { //get rid of this ASAP in case of pending departure from hover-widget if (data->timer_id > 0) { g_source_remove (data->timer_id); data->timer_id = 0; } gdk_threads_enter (); (*data->callback) (data->hovered, data); //e.g. _e2_pane_forward_hover if (GTK_IS_MENU (data->menu) && #ifdef USE_GTK2_18 !gtk_widget_get_visible (data->menu) #else !GTK_WIDGET_VISIBLE (data->menu) #endif ) { GtkWidget *popwin = gtk_widget_get_ancestor (data->menu, GTK_TYPE_WINDOW); //would be parent, actually if (popwin != NULL) gtk_window_set_transient_for (GTK_WINDOW (popwin), GTK_WINDOW (app.main_window)); else printd (DEBUG, "No parent window for history menu"); g_signal_connect (G_OBJECT (popwin), "enter-notify-event", G_CALLBACK (_e2_action_enter_menu_cb), data); g_signal_connect (G_OBJECT (popwin), "leave-notify-event", G_CALLBACK (_e2_action_leave_menu_cb), data); gtk_widget_show (data->menu); //setup size for use when positioning ? gint x, y; gboolean push = TRUE; e2_toolbar_set_menu_position (GTK_MENU (data->menu), &x, &y, &push, data->hovered); gtk_window_move (GTK_WINDOW (popwin), x, y); //this might be ignored by WM ? gtk_widget_show_all (popwin); } gdk_threads_leave (); return FALSE; } /** @brief timer callback to cleanup after a hover @brief data pointer to hover data @return FALSE to cancel the timer */ static gboolean _e2_action_clear_hover_cb (E2HoverData *data) { if (GTK_IS_WIDGET (data->menu)) { GtkWidget *popwin = gtk_widget_get_toplevel (data->menu); gdk_threads_enter (); gtk_widget_destroy (popwin); gdk_threads_leave (); } data->menu = NULL; return FALSE; } /** @brief "clicked" signal callback on a button which supports a hover-menu If the button is clicked, we should respond to that, and abort any hover activity @param button the clicked item @param data hover data struct @return */ static void _e2_action_abort_hover_cb (GtkButton *button, E2HoverData *data) { printd (DEBUG, "in function _e2_action_abort_hover_cb"); if (data->timer_id > 0) { g_source_remove (data->timer_id); data->timer_id = 0; } if (GTK_IS_WIDGET (data->menu)) { GtkWidget *popwin = gtk_widget_get_toplevel (data->menu); gtk_widget_destroy (popwin); } data->menu = NULL; } /******************/ /***** public *****/ /******************/ /** @brief setup for a potential hover process @param widget the entered widget (for now at least, only a toolbar button) @param event pointer to event data @para data pointer to hover data struct This is a "enter-notify-event" signal callback for all toolbar buttons based on a E2_ACTION_TYPE_HOVER action. @return FALSE always so the event is propagated */ gboolean e2_action_start_hover_cb (GtkWidget *button, GdkEventCrossing *event, E2HoverData *data) { printd (DEBUG, "in function e2_action_start_hover_cb"); if (data->timer_id > 0) { //we're re-entering the buttton from a menu g_source_remove (data->timer_id); data->timer_id = 0; } if (data->menu == NULL) { //no popup if button is clicked g_signal_connect (G_OBJECT (button), "clicked", //not "button_press-event", that's blocked G_CALLBACK (_e2_action_abort_hover_cb), data); //FIXME relate interval to gtk's tooltip popup time data->timer_id = g_timeout_add (1000, (GSourceFunc)_e2_action_do_hover_cb, data); } return FALSE; } /** @brief abort an actual or potential hover process @param widget the departed widget @param event pointer to event data @para data pointer to hover data struct This is a "leave-notify-event" signal callback for all toolbar buttons based on a E2_ACTION_TYPE_HOVER action, and for a popped-up menu widget. @return FALSE always so the event is propagated */ gboolean e2_action_end_hover_cb (GtkWidget *widget, GdkEventCrossing *event, E2HoverData *data) { printd (DEBUG, "in function e2_action_end_hover_cb"); if (data->timer_id > 0) { g_source_remove (data->timer_id); data->timer_id = 0; } if (GTK_IS_WIDGET (data->menu)) //allow short interval to move pointer from button into the menu or vice-versa, before killing it data->timer_id = g_timeout_add (100, (GSourceFunc)_e2_action_clear_hover_cb, data); else data->menu = NULL; g_signal_handlers_disconnect_by_func ((gpointer)widget, _e2_action_abort_hover_cb, data); return FALSE; } /** @brief option helper for action columns This function is used to source a model for cell renderers in columns of type E2_OPTION_TREE_TYPE_SEL in tree-option config-dialog treeviews @param ex pointerised flags which specify categories of actions that should not be visible @return GtkTreeFilterModel of all actions */ GtkTreeModel *e2_action_filter_store (gpointer ex) { GtkTreeModel *filter = gtk_tree_model_filter_new ( GTK_TREE_MODEL (actions_store), NULL); gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), (GtkTreeModelFilterVisibleFunc) _e2_action_visible_cb, ex, NULL); return filter; } gint entry_level = 0; /** @brief register 'public' action names, and corresponding parameters, for later use as needed. It replaces any previously-registered action with the same name. Also adds the action name etc to the options store, as a convenience for the option system Action names need to be freed (= action removed from the hash) when the session ends, if not before. Potentially re-entrant. As another convenience, the non-constant name string used to register the action is freed at exit. @param newaction pointer to action data to be copied and heaped @return registered action E2_Action */ E2_Action *e2_action_register (const E2_Action *newaction) { //unregister any previously registered actions with that name //(for convenience) entry_level++; //keep note of whether re-entrant //now allocate the action structure and initialize it from the //function parameters #ifdef USE_GLIB2_10 E2_Action *action = (E2_Action *) g_slice_alloc (sizeof (E2_Action)); // E2_Action *action = ALLOCATE (E2_Action); #else E2_Action *action = ALLOCATE (E2_Action); #endif CHECKALLOCATEDFATAL (action); //action name is freed if the action is removed from the actions_hash table //because the names are used as keys *action = *newaction; //add the action to the static hash table and pointer array register g_hash_table_replace (actions_hash, action->name, action); // g_ptr_array_add (actions_array, action); //to get structured dropdown lists, find and register the 'parent' action, if any gboolean registered_parent = FALSE; E2_Action *parent = NULL; gchar *search = g_strdup (newaction->name); gchar *child = strrchr (search, '.'); //always ascii '.', don't need g_utf8_strrchr() if (child != NULL) { //also register a new parent, if need be *child = '\0'; parent = e2_action_get (search); if (parent == NULL) { E2_Action found = {search, NULL, FALSE, E2_ACTION_TYPE_DUMMY, 0, NULL, NULL}; //re-entry !! parent = e2_action_register (&found); registered_parent = TRUE; } } //now add the action to the treestore GtkTreeIter iter; GtkTreeIter iter_parent; GtkTreeIter *parentptr; //setup store //(sortable, with filter-model for selection-type cell renderers in config dialog trees) if (parent == NULL) parentptr = NULL; else { parentptr = &iter_parent; if (!e2_tree_ref_to_iter (actions_store, parent->ref, parentptr)) parentptr = NULL; } //CHECKME only columns 0 and 2 are used ? #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (actions_store, &iter, parentptr, -1, #else gtk_tree_store_append (actions_store, &iter, parentptr); gtk_tree_store_set (actions_store, &iter, #endif 0, action->name, 1, (child == NULL) ? action->name : g_strstrip (child + 1), 2, action->exclude, 3, action->type != E2_ACTION_TYPE_DUMMY, 4, TRUE, -1); //keep reference around to be able to to find out the parent iter //for possible children later on action->ref = e2_tree_iter_to_ref (actions_store, &iter); //cleanup if (!registered_parent) g_free (search); //registered actions are kept, but this one was not needed // else if (child != NULL) // *child = '.'; //what about ':' ? // if (--entry_level == 0) //revert re-entrant level, for next time // g_free (name); //and this is not re-entrant, so free the original name-space entry_level--; return action; } /** @brief unregister action named @a name This is for plugin actions, essentially @param name action name string @return TRUE if the action was registered */ gboolean e2_action_unregister (gchar *name) { E2_Action *action = g_hash_table_lookup (actions_hash, name); if (action == NULL) return FALSE; GtkTreeIter iter, parent; if (e2_tree_ref_to_iter (actions_store, action->ref, &iter) && gtk_tree_model_iter_parent (GTK_TREE_MODEL (actions_store), &parent, &iter) && gtk_tree_model_iter_n_children (GTK_TREE_MODEL (actions_store), &parent) == 1) gtk_tree_store_remove (actions_store, &parent); g_hash_table_remove (actions_hash, name); return TRUE; } /** @brief find an action by name The action with the name @a name is looked up in the internal hash table and a pointer to the action object is returned. The name should be the complete name of the action including any parent categories. If the action cannot be found, NULL is returned. @param name the name of the action @return pointer to an action object or NULL */ E2_Action *e2_action_get (gchar *name) { return (g_hash_table_lookup (actions_hash, name)); } /** @brief find an action by name, reverting to custom if not a registered action @param taskname action or external command string @param arg the argument to be supplied to the action or command, or NULL @param use_arg pointer to store newly-allocated "real" argument string @return pointer to an action object, or NULL if @a taskname is NULL */ E2_Action *e2_action_get_with_custom (gchar *taskname, gchar *arg, gchar **use_arg) { if (taskname == NULL) return NULL; E2_Action *action = e2_action_get (taskname); if (action == NULL) { //fallback to custom command action = e2_action_get (_A(20)); if (arg == NULL || *arg == '\0') *use_arg = g_strdup (taskname); else *use_arg = g_strconcat (taskname, " ", arg, NULL); } else { if (arg == NULL) *use_arg = g_strdup (""); else *use_arg = g_strdup (arg); } return action; } /** @brief check whether @a command is an action @param command the command string @return pointer to the action, or NULL if @a command not an action */ E2_Action* e2_action_check (gchar *command) { if (command == NULL) //this should never happen, but ... return NULL; //#warning ignore compiler warning about unitialized usage of c gchar c; gchar *gap = e2_utils_find_whitespace (command); if (gap != NULL) { c = *gap; *gap = '\0'; } E2_Action *action = e2_action_get (command); if (gap != NULL) *gap = c; if (action == NULL) return NULL; if (action->type == E2_ACTION_TYPE_DUMMY) return NULL; return action; } /** @brief run a named 'task' (may be a command or an action) This function is to run a certain action. First, the action specified by @a name is looked up. Then a runtime object is created and the action is run. Afterwards the runtime object is freed again. If the action could not be found, an error message is printed to the output pane. @param name action name or command, string @param arg pointer to data for the command, or possibly arguments or more of the command, or NULL @param from the widget activated to initiate the action (best if non-NULL) @return TRUE on success, FALSE on failure */ gboolean e2_action_run_simple_from (gchar *name, gpointer arg, gpointer from) { //CHECKME should these variables be volatilized ? if ((name == NULL || *name == '\0') //chained keybindings may have no command && (arg == NULL || *(gchar *)arg == '\0')) return FALSE; gchar *real_arg; E2_Action *action = e2_action_get_with_custom (name, arg, &real_arg); E2_ActionRuntime *rt = e2_action_pack_runtime (action, real_arg, g_free); gboolean retval = e2_action_run (from, rt); e2_action_free_runtime (rt); return retval; } /* * @brief callback to run an action This function wraps the real action run function, with thread unlocking so that any such locking can be managed locally, without mutux reentrance. @param from the item (GtkButton GtkMenuItem etc) activated to trigger the action @param rt runtime data for the action @return */ /*void e2_action_run_cb (gpointer from, E2_ActionRuntime *rt) { gdk_threads_leave (); e2_action_run (from, rt); gdk_threads_enter (); }*/ /** @brief run a certain action This function is the real action run function. It may be used as callback for gtk signals, if downstream thread [un]locks are managed. There are several wrappers to simplify access. Runtime data @a rt may contain additional user data. @a from may be used by the action handler to determine the context. @param from the item activated to trigger the action (eg toolbar GtkButton, maybe NULL) @param art runtime data for the action @return TRUE on success of the action, FALSE on failure */ gboolean e2_action_run (gpointer from, E2_ActionRuntime *art) { //some quick checks if (art == NULL || art->action == NULL || art->action->func == NULL) { printd (DEBUG, "cannot run action, it doesn't exist"); return FALSE; } printd (DEBUG, "e2_action_run (from:, art:%s)", art->action->name); if (from == NULL) from = app.main_window; art->state = e2_action_get_current_state (from); gboolean (*func) (gpointer, E2_ActionRuntime *) = art->action->func; #ifndef DEBUG_MESSAGES return func (from, art); #else gboolean res = func (from, art); printd (DEBUG,"e2_action_run () ends"); return res; #endif } /** @brief create an action runtime object This function creates an action runtime object and initializes it with supplied user data. @param action the action to create the runtime object for, or command string for a "custom command" @param data action user data @param data_free function to free data when the runtime object is freed @return newly allocated action runtime object that should be freed */ E2_ActionRuntime *e2_action_pack_runtime (E2_Action *action, gpointer data, void(*data_free)(gpointer)) { // printd (DEBUG, "action pack runtime for %s", action->name); E2_ActionRuntime *art = ALLOCATE (E2_ActionRuntime); CHECKALLOCATEDFATAL (art); art->action = action; art->data = data; art->data_free = (data == NULL) ? NULL : data_free; art->state = 0; //usually updated when action is run return art; } /** @brief free action runtime object The runtime data is freed if a data free function has been supplied. In addition the runtime object is freed and must not be used anymore after calling this function. @param rt the action runtime object to free @return */ void e2_action_free_runtime (E2_ActionRuntime *rt) { // printd (DEBUG, "e2_action_free_runtime"); if (rt != NULL) { if (rt->data != NULL && rt->data_free != NULL) (*rt->data_free) (rt->data); DEALLOCATE (E2_ActionRuntime, rt); } } /** @brief determine state flags for an action about to run @param from pointer to the widget where the action was initiated @return tne state flags */ E2_ActionState e2_action_get_current_state (gpointer from) { E2_ActionState state = 0; guint btn; GdkEvent *event = gtk_get_current_event (); if (event != NULL) { if (!gdk_event_get_state (event, &state)) #ifdef USE_GTK3_0 { GtkWidget *win = gtk_widget_get_toplevel (from); if (gtk_widget_is_toplevel (win)) state = e2_utils_get_savedstate (win); else state = 0; } #else state = e2_utils_get_modifiers (); #endif switch (event->type) { case GDK_BUTTON_PRESS: case GDK_2BUTTON_PRESS: case GDK_3BUTTON_PRESS: case GDK_BUTTON_RELEASE: //normal for a "clicked" callback btn = ((GdkEventButton *)event)->button; break; case GDK_KEY_PRESS: case GDK_KEY_RELEASE: btn = 0; default: //CHECKME dnd btn = 127; break; } gdk_event_free (event); } else { #ifdef USE_GTK3_0 GtkWidget *win = gtk_widget_get_toplevel (from); if (gtk_widget_is_toplevel (win)) state = e2_utils_get_savedstate (win); else state = 0; #else state = e2_utils_get_modifiers (); #endif btn = 0; } ACTION_SETSTATE (state, btn); return state; } /** @brief clear data for an entry in the toggles hash @param ex pointer to hash table data item @return */ static void _e2_action_free_toggle (E2_ToggleData *ex) { e2_list_free_with_data (&ex->boxes); if (ex->true_action != NULL) g_free (ex->true_action); if (ex->false_action != NULL) g_free (ex->false_action); DEALLOCATE (E2_ToggleData, ex); } /** @brief internal action system memory cleanup @return */ static void _e2_action_clean1 (E2_Action *action) { //action->name is the actions-hash key, therefore cleared by g_free if (G_UNLIKELY(action->type == E2_ACTION_TYPE_HOVER)) DEMALLOCATE (E2_Callback, action->data); // if (action->data != NULL && action->cleaner != (GDestroyNotify)NULL) // (*action->cleaner) (data); GtkTreeIter iter; if (e2_tree_ref_to_iter (actions_store, action->ref, &iter)) gtk_tree_store_remove (actions_store, &iter); gtk_tree_row_reference_free (action->ref); #ifdef USE_GLIB2_10 g_slice_free1 (sizeof (E2_Action), action); // DEALLOCATE (E2_Action, action); #else DEALLOCATE (E2_Action, action); #endif } /* presently unused static void free_name (gchar *key, E2_Action *value, gpointer data) { g_free (key); //=value->name return; } */ /** @brief internal action system cleanup After calling the function, the action system may not be used anymore. It is only called internally just before exit. @return */ /* this done only at session end, don't bother void e2_actions_clean () { //CHECKME = should this be able to handle ->data &| ->data2 ? g_object_unref (actions_store); //clean up non-constant action name strings in the hash g_hash_table_foreach (actions_hash, (GFunc) free_name, NULL); g_hash_table_destroy (actions_hash); //CHECKME = array cleanup ok for non-constant strings? could this replace the hash foreach? // g_ptr_array_free (actions_array, TRUE); } */ /** @brief internal action system initialization This function is only called once at startup to initialize the action system. Afterwards, it has no effect. It also registers most actions (and "pseudo-actions" for toolbars). Some other actions are registered in the pane create func. @return */ void e2_actions_init (void) { //ensure that this function is only performed once RUN_ONCE_CHECK (); //initialize local data structures that are used to keep track of //the action parameters //note - this needs to be done before any plugin is loaded actions_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) _e2_action_clean1); // actions_array = g_ptr_array_new (); actions_store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); //CHECKME is this still necessary? // g_object_ref (actions_store); toggles_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) _e2_action_free_toggle); toggles_array [E2_TOGGLE_PANE1FULL] = g_strconcat (_A(11),".",_A(113),NULL); toggles_array [E2_TOGGLE_PANE2FULL] = g_strconcat (_A(12),".",_A(113),NULL); toggles_array [E2_TOGGLE_PANE1HIDDEN] = g_strconcat (_A(11),".",_A(84),NULL); toggles_array [E2_TOGGLE_PANE2HIDDEN] = g_strconcat (_A(12),".",_A(84),NULL); toggles_array [E2_TOGGLE_PANE1FILTERS] = g_strconcat (_A(11),".",_A(25),NULL); toggles_array [E2_TOGGLE_PANE2FILTERS] = g_strconcat (_A(12),".",_A(25),NULL); toggles_array [E2_TOGGLE_OUTPUTFULL] = g_strconcat (_A(10),".",_A(113),NULL); toggles_array [E2_TOGGLE_OUTPUTSHADE] = g_strconcat (_A(10),".",_A(83),NULL); #ifdef E2_VFS toggles_array [E2_TOGGLE_PANE1SPACE] = g_strconcat (_A(11),".",_A(120),NULL); toggles_array [E2_TOGGLE_PANE2SPACE] = g_strconcat (_A(12),".",_A(120),NULL); #endif //register most actions //note translated action labels needed here, i.e. before config loaded //all action names must be freeable E2_Action homeless_actions[] = { //here we don't bother explicitly setting 3 trailing NULL parameters { g_strconcat(_A(6),".",_A(23),NULL), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_FILE_ACTIONS, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_LAYOUT, }, { g_strconcat(_A(16),".",_A(27),NULL), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_PLUGINS, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR, }, { g_strdup(_A(21)), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_SEPARATOR, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT, }, { g_strdup(_A(22)), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_SUBMENU, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT, }, { g_strconcat(_A(1),".",_A(72),NULL), e2_main_user_shutdown, E2_ACTION_TYPE_ITEM, FALSE, 0, }, { g_strconcat(_A(3),".",_C(34),NULL), e2_plugins_configure, FALSE, E2_ACTION_TYPE_ITEM, 0, }, { g_strdup(_A(20)), _e2_action_custom_command, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_GENERAL, }, { g_strconcat(_A(17),".",_A(115),NULL), _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOGGLE, }, { g_strconcat(_A(17),".",_A(116),NULL), _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOGGLE, }, { g_strconcat(_A(3),".",_A(32),NULL), _e2_action_configure, FALSE, E2_ACTION_TYPE_ITEM, 0, }, { g_strconcat(_A(3),".",_A(40),NULL), _e2_action_configure_default, FALSE, E2_ACTION_TYPE_ITEM, 0, }, }; guint i, count = sizeof (homeless_actions) / sizeof (E2_Action); for (i = 0; i < count; i++) e2_action_register (&homeless_actions[i]); //register most other actions // (some others are in pane create fn) e2_task_actions_register (); e2_output_actions_register(); // e2_context_menu_actions_register(); e2_bookmark_actions_register (); e2_filetype_actions_register (); e2_command_line_actions_register (); e2_command_actions_register (); e2_window_actions_register (); e2_pane_actions_register (); // e2_toolbar_actions_register (); e2_about_dialog_actions_register (); e2_edit_dialog_actions_register (); e2_view_dialog_actions_register (); // e2_search_dialog_actions_register (); e2_mkdir_dialog_actions_register (); #ifdef WITH_KEYFAKE e2_keybinding_actions_register (); #endif #ifdef WITH_BUTTONFAKE e2_mousebinding_actions_register (); #endif e2_action_option_actions_register (); #ifdef E2_FS_MOUNTABLE e2_fs_mount_actions_register (); #endif //sort the actions tree store GtkTreeSortable *sortable = GTK_TREE_SORTABLE (actions_store); gtk_tree_sortable_set_sort_func (sortable, 0, _e2_action_tree_compare_cb, NULL, NULL); gtk_tree_sortable_set_sort_column_id (sortable, 0, GTK_SORT_ASCENDING); } /** @brief setup array of translated action labels No spaces in names, so action arguments can be separated by a space. Some of these are the same as config dialog labels. Array size defined in e2_action.h @return */ void e2_action_setup_labels (void) { //CHECKME= which of these names can be rationalised ? //'parent' names action_labels[0] = _("bookmark"); action_labels[1] = _("command"); action_labels[2] = _("children"); action_labels[3] = _("configure"); action_labels[4] = _("dialog"); action_labels[5] = _("dirline"); action_labels[6] = _("file"); action_labels[7] = _("filelist"); action_labels[8] = _("history"); action_labels[9] = _("option"); action_labels[10] = _("output"); // = _C(28) action_labels[11] = _("pane1"); // ~ _C(29) action_labels[12] = _("pane2"); // ~ _C(31) action_labels[13] = _("pane"); action_labels[14] = _("panes"); //= _C(33) action_labels[15] = _("pending"); action_labels[16] = _("plugin"); //this is also used in menus - see #define PLUGIN in emelfm2.h action_labels[17] = _("toggle"); action_labels[18] = _("trash"); action_labels[19] = _("separator"); //not really an action, but not a config either action_labels[20] = _(""); action_labels[21] = _(""); action_labels[22] = _(""); //'child' names action_labels[23] = _(""); action_labels[24] = _(""); action_labels[25] = _(""); action_labels[26] = _(""); action_labels[27] = _(""); action_labels[28] = _("about"); action_labels[29] = _("activate"); action_labels[30] = _("add"); action_labels[31] = _("adjust_ratio"); action_labels[32] = _("application"); //see 127 _("change"); action_labels[33] = _("clear"); action_labels[34] = _("clear_history"); action_labels[35] = _("complete"); action_labels[36] = _("copy"); action_labels[37] = _("copy_as"); action_labels[38] = _("copy_merge"); action_labels[39] = _("copy_with_time"); action_labels[40] = _("default"); action_labels[42] = _("delete"); action_labels[43] = _("edit"); action_labels[44] = _("edit_again"); action_labels[45] = _("filetype"); action_labels[46] = _("find"); action_labels[47] = _("focus"); action_labels[48] = _("fullscreen"); action_labels[49] = _("go_back"); action_labels[50] = _("go_forward"); action_labels[51] = _("go_up"); action_labels[52] = _("goto_bottom"); action_labels[53] = _("goto_top"); action_labels[54] = _("help"); action_labels[55] = _("info"); action_labels[56] = _("insert_selection"); action_labels[57] = _("invert_selection"); action_labels[58] = _("list"); action_labels[59] = _("mirror"); action_labels[60] = _("mkdir"); action_labels[61] = _("mountpoints"); action_labels[62] = _("move"); action_labels[63] = _("move_as"); action_labels[64] = _("open"); action_labels[65] = _("open_in_other"); action_labels[66] = _("open_with"); action_labels[67] = _("owners"); action_labels[68] = _("page_down"); action_labels[69] = _("page_up"); action_labels[70] = _("permissions"); action_labels[71] = _("print"); action_labels[72] = _("quit"); action_labels[73] = _("refresh"); action_labels[74] = _("refreshresume"); action_labels[75] = _("refreshsuspend"); action_labels[76] = _("rename"); action_labels[77] = _("scroll_down"); action_labels[78] = _("scroll_up"); action_labels[79] = _("search"); //= _C(37) action_labels[80] = _("select_type"); action_labels[81] = _("send"); action_labels[82] = _("set"); action_labels[83] = _("show"); action_labels[84] = _("show_hidden"); action_labels[85] = _("show_menu"); action_labels[86] = _("sortaccesssed"); action_labels[87] = _("sortchanged"); action_labels[88] = _("sortext"); action_labels[89] = _("sortgroup"); action_labels[90] = _("sortmodified"); action_labels[91] = _("sortname"); action_labels[92] = _("sortpermission"); action_labels[93] = _("sortsize"); action_labels[94] = _("sortuser"); action_labels[95] = _("switch"); action_labels[96] = _("symlink"); action_labels[97] = _("symlink_as"); action_labels[98] = _("sync"); action_labels[99] = _("toggle_direction"); action_labels[100] = _("toggle_focus"); action_labels[101] = _("toggle_select_all"); action_labels[102] = _("toggle_selected"); #ifdef E2_TREEDIALOG action_labels[103] = _("tree"); #endif action_labels[104] = _("unpack"); #ifdef WITH_UNTRASH action_labels[105] = _("untrash"); #endif action_labels[106] = _("view"); //= _C(42) action_labels[107] = _("view_again"); action_labels[108] = _("view_at"); //these are action _parameter_ strings action_labels[109] = _("child"); action_labels[110] = _("ctrl"); action_labels[111] = _("dirs"); action_labels[112] = _("escape"); action_labels[113] = _("expand"); action_labels[114] = _("files"); action_labels[115] = _("off"); action_labels[116] = _("on"); action_labels[117] = _("quote"); action_labels[118] = _("shift"); action_labels[119] = _("top"); //late entries, out of order //maybe more space here, see array definition in header file #ifdef E2_VFS action_labels[120] = _(""); action_labels[121] = _("dummy"); action_labels[122] = _("namespace"); action_labels[123] = _("unpack_in_other"); #endif #ifdef WITH_KEYFAKE action_labels[124] = _("key"); #endif #if defined(WITH_KEYFAKE) || defined(WITH_BUTTONFAKE) action_labels[125] = _("fake"); #endif #ifdef WITH_BUTTONFAKE action_labels[126] = _("button"); #endif action_labels[127] = _("change"); } #define LAST_NAME_INDEX 127 #if LAST_NAME_INDEX > ALLOCATED_NAMES - 1 # warning must increase size of action names array #endif #undef LAST_NAME_INDEX /* //copy all actions, for help document void build_list (gpointer key, gpointer value, GString *text) { E2_Action *action = (E2_Action *) value; if (action->type != E2_ACTION_TYPE_DUMMY) { text = g_string_append (text, action->name); text = g_string_append_c (text, '\t'); text = g_string_append_c (text, (action->has_arg) ? 'Y' : 'N'); text = g_string_append_c (text, '\n'); } return; } void e2_action_list_all (void) { GString *cmds_list = g_string_new (""); g_hash_table_foreach (actions_hash, (GHFunc) build_list, cmds_list); GtkClipboard* clipper = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clipper, cmds_list->str, cmds_list->len); ls e2* g_string_free (cmds_list, TRUE); } */