/* $Id$ Copyright (C) 2004-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/e2_pane.c @brief pane creation and action functions includes actions on pane contents, but not on panes per se */ /** \page panes the filelist panes ToDo \section treeviews */ /** \page histories pane histories \section logged data Several data items are logged for each directory displayed in each pane during a session. There is a single hash-table (app.dir_history) which holds a E2_DirHistoryEntry for each dir opened in the current session, in either pane. Each data item caches the path of the dir, and other information to facilitate stateful interaction with the dir during the session. In both panes' runtime data is a list - opendirs. Effectively it's a list of paths (UTF-8, absolute for current namespace, with trailing "/") of dirs previously opened. This list is used for respective toolbars' forward/back buttons, and related menus (right-click and hover), and for the history sub-menu in the main filelist context menu for the pane. Actually, this list shares data with app.dir_history, so no data cleanup is needed when manipulating the list. Unlike app.dir_history, this list may have multiple entries for the same dir, to represent the order in which dirs were opened. To increase speed but at some cost to maintainability, new items in this list are prepended. app.dir_history and opendirs are manipulated in backend function _e2_fileview_change_dir(). In both panes' runtime data is an index - opendir_cur. This is a 0-based index of 'current' position in the forward/back history. Since history items are prepended to the opendirs list, index value 0 corresponds to the _last_ member of the list, and so on. Moving forward in a history increases the index, but uses the data at the previous position in the list. The converse applies to moving backward. List-position index is (list-length - 1 - index). When maximally-forward in the history, the index is (list-length - 1). Actions for go-forward and go-backward cause change (if possible) of index, and corresponding filelist display. Forward increases the index and thus uses data from earlier in the list. After forward/back move(s) to some index other than (list-length - 1), any dir explicitly opened represents a 'branch' of the history. In such case, we assume that the most recent move(s) were backwards, in which case, to preserve an intuitive semblance of places to go back in future, any items for indices greater than current (= list-positions before current) are reversed, the opened dir is added to become the new last index, and made current (remember, items-order != index!) As a special case (to avoid adjacent matching dirs, opening the dir for (current-index + 1) omits addition of an item for the new dir. Depending on configuration option "cache-history", some of the data in opendirs lists may be cached between sessions. If "cache-history" is set, the number of cached items is limited, to keep the history to a reasonably size. Currently the limit is: current-index +/- 5, or less if there are not +/- 5 in the list. This is applied immediately before the cache file is written - see e2_pane_trim_history() usage. \section menus A menu can be created from opendirs list data for going forward (right click or hover on a toolbar go-forward button), or going backward (as for going forward), or for going anywhere (submenu of filelist context-menu). A forward-menu shows items (if any) whose index is > opendir_cur i.e. are in the list at positions before (list-len - 1 - opendir_cur). Any items whose dir is the same as current is excluded. Any duplicate item is excluded. Most recent first, these are displayed in index-order i.e. the reverse order to their list positions. A backward-menu is similar, it shows items indexed < opendir_cur and positioned after (list-len - 1 - opendir_cur). Most-recent first, these are shown in the same order as in the list. A anywhere-menu shows all items in the list. Any duplicate item is excluded, with priority to the current item (which is marked as such in the menu). Backwards first, these are displayed in reverse order to their list positions. */ #include "emelfm2.h" //#include #include #include #include "e2_pane.h" #include "e2_action.h" #include "e2_dialog.h" #ifdef E2_TREEDIALOG #include "e2_tree_dialog.h" #endif #include "e2_context_menu.h" #include "e2_filelist.h" static void _e2_pane_change_dir (E2_PaneRuntime *rt, const gchar *newpath, gboolean history, gboolean hook); static GtkWidget *_e2_pane_popup_forward (GtkWidget *from, E2_PaneRuntime *rt); static GtkWidget *_e2_pane_popup_backward (GtkWidget *from, E2_PaneRuntime *rt); extern guint last_selected_rows; /*****************/ /***** utils *****/ /*****************/ #ifdef E2_VFS /** @brief setup the pane to which @a rt applies to show data for a specified namespace Any error messages downstream expect BGL closed here @a spacedescriptor can have various forms, from full-URI to "-1" - see plugin for details @param rt pointer to pane data struct, NULL for active pane @param spacedescriptor UTF-8 string to be interpreted into existing or new namespace @return TRUE on successful completion or no change needed */ gboolean e2_pane_change_space_byuri (E2_PaneRuntime *rt, const gchar *spacedescriptor) { if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.set_namedspace)) { ViewInfo *view = (rt == NULL) ? curr_view : (ViewInfo *)rt; return (vfs.set_namedspace (view, spacedescriptor)); //CHECKME last-used dir is opened when we change space ? } else { printd (WARN, "Cannot find vfs plugin"); return FALSE; } } /** @brief setup the pane to which @a rt applies to show data for a specified namespace If @a utfpath has a NULL localpath, then the relevant history or vtab path will be used @param rt pointer to pane data struct @param localpath pointer to path and namespace data struct @return TRUE on successful change or no change needed */ gboolean e2_pane_change_space (E2_PaneRuntime *rt, VPATH *utfpath) { PlaceInfo *current = rt->view->spacedata; if (current == utfpath->spacedata) return TRUE; //nothing to change //E2_VFSTMP may want to change dir anyway if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.set_space)) { ViewInfo *view = (rt == NULL) ? curr_view : (ViewInfo *)rt; if (!vfs.set_space (view, utfpath->spacedata, TRUE)) return FALSE; //E2_VFSTMP CHECKME last-used dir is opened when we change space ? if (utfpath->path == NULL) _e2_pane_change_dir (rt, "FIXME-HISTORY-ITEM", FALSE, TRUE); else _e2_pane_change_dir (rt, utfpath->path, TRUE, TRUE); return TRUE; } else { printd (WARN, "Cannot find vfs plugin"); return FALSE; } } #endif //def E2_VFS /* * @brief adjust any update-history-flag in command line(s) present in the pane represented by @a rt This is a mechanism for anything to specify whether to update dirline history during a cd process In principle, the flag read/write could be racy, but the risk seems way too low to bother with a mutex @param rt pointer to pane data struct, or NULL for active pane @value new value for the flag @return */ /*void e2_pane_flag_history (E2_PaneRuntime *rt, gboolean value) { if (rt == NULL) rt = curr_pane; if (rt->toolbar.has_command_line) { GList *line; for (line = app.command_lines; line != NULL ; line = line->next) { E2_CommandLineRuntime *clrt = (E2_CommandLineRuntime *)line->data; if (clrt->pane == rt) clrt->update_history = value; } } } */ /** @brief set filepanes' properties to indicate which one is active The "active-pane-signal" config option is checked, and: the column headers will be re-coloured, or the filename column title will be [un]bolded, or the treeviews' sensitivity will be toggled @return */ void e2_pane_flag_active (void) { E2_OptionSet *set = e2_option_get ("active-pane-signal"); gint htype = e2_option_int_get_direct (set); switch (htype) { case 1: { //set bold Filename-column header title gchar *new_title = gettext (e2_all_columns[FILENAME].title); gchar *new_title2 = g_strconcat ("", new_title,"", NULL); gtk_label_set_markup (curr_view->name_label, new_title2); g_free (new_title2); //set normal Filename-column header title gtk_label_set_text (other_view->name_label, new_title); break; } case 2: { //make active pane sensitive gtk_widget_set_sensitive (curr_view->treeview, TRUE); //make inactive pane insensitive gtk_widget_set_sensitive (other_view->treeview, FALSE); break; } default: { //set all active columns' header color #ifdef USE_GTK3_0 GdkRGBA color; GdkRGBA *active_btncolor = &color; e2_option_color_get_RGBA ("color-active-pane", active_btncolor); #else GdkColor *active_btncolor = e2_option_color_get ("color-active-pane"); #endif GList *base = gtk_tree_view_get_columns (GTK_TREE_VIEW (curr_view->treeview)); GList *columns; #ifdef USE_GTK2_14 GtkWidget *header_button; #endif for (columns = base; columns !=NULL; columns = columns->next) { #ifdef USE_GTK2_14 header_button = gtk_widget_get_ancestor ( gtk_tree_view_column_get_widget ((GtkTreeViewColumn *)columns->data), GTK_TYPE_BUTTON); # ifdef DEBUG_MESSAGES if (header_button == NULL) printd (ERROR, "missing column-header button"); else { # endif # ifdef USE_GTK3_0 gtk_widget_override_background_color (header_button, # else //gtk 2.14-2.22 gtk_widget_modify_bg (header_button, # endif #else //gtk < 2.14 gtk_widget_modify_bg (((GtkTreeViewColumn *)columns->data)->button, #endif GTK_STATE_NORMAL, active_btncolor); #ifdef USE_GTK2_14 # ifdef DEBUG_MESSAGES } # endif #endif } g_list_free (base); //revert all inactive columns' header color to default base = gtk_tree_view_get_columns (GTK_TREE_VIEW (other_view->treeview)); for (columns = base; columns != NULL; columns = columns->next) { #ifdef USE_GTK2_14 header_button = gtk_widget_get_ancestor ( gtk_tree_view_column_get_widget ((GtkTreeViewColumn *)columns->data), GTK_TYPE_BUTTON); # ifdef DEBUG_MESSAGES if (header_button == NULL) printd (ERROR, "missing column-header button"); else { # endif # ifdef USE_GTK3_0 gtk_widget_override_background_color (header_button, # else //gtk 2.14-2.22 gtk_widget_modify_bg (header_button, # endif #else //gtk < 2.14 gtk_widget_modify_bg (((GtkTreeViewColumn *)columns->data)->button, #endif GTK_STATE_NORMAL, NULL); #ifdef USE_GTK2_14 # ifdef DEBUG_MESSAGES } # endif #endif } g_list_free (base); break; } } } /** @brief change active pane from the current one to the other one Assumes BGL is closed on arrival here @return */ void e2_pane_activate_other (void) { // printd (DEBUG, "e2_pane_activate_other ()"); E2_PaneRuntime *tmp = other_pane; other_pane = curr_pane; curr_pane = tmp; //force a status-line update last_selected_rows = -1; #ifdef E2_STATUS_DEMAND gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); #endif e2_fileview_switch_views (); // gtk_widget_grab_focus (tmp->focus_widget); #ifdef USE_GTK2_18 if (gtk_widget_has_focus (other_view->treeview)) #else if (GTK_WIDGET_HAS_FOCUS (other_view->treeview)) #endif gtk_widget_grab_focus (curr_view->treeview); gdk_threads_leave (); e2_hook_list_run (&app.hook_pane_focus_changed, tmp); gdk_threads_enter (); } /** @brief decide which pane is relevant, from available resources @param from the widget which triggered an action @param actiondata action data or action-runtime data of unspecified form @param multidata store for start of non-pane data if @a actiondata is a string and may have > 1 part (comma- or space-separated, pane num first), else NULL @return rt data for pane 1 or pane 2 or active pane if can't decide 1 or 2, or if @a multidata is non-NULL and data can't be deciphered, NULL */ E2_PaneRuntime *e2_pane_get_runtime (gpointer from, gpointer actiondata, const gchar **multidata) { gboolean freearg = FALSE; if (multidata != NULL) { //parse multi-part string data, with pane number (if any) first gchar *first = e2_utils_pass_whitespace ((gchar *) actiondata); if (first == NULL) { *multidata = NULL; // return NULL; //total failure if some data is required actiondata = NULL; } else { gchar *second = strchr (first, ','); //always ascii ',' if (second == NULL) second = strchr (first, ' '); if (second == NULL) { //no second arg, assume the pane-num is missing and data is first arg *multidata = first; actiondata = NULL; } else { *multidata = e2_utils_pass_whitespace (second + sizeof (gchar)); //maybe NULL actiondata = (gpointer) g_strndup (first, second - first); freearg = TRUE; } } } guint which = E2PANECUR; //check data first if (actiondata != NULL) { if (!freearg && (E2_PaneRuntime *)actiondata == &app.pane1 /*UNUSED || actiondata == GUINT_TO_POINTER (1)*/ ) which = E2PANE1; else if (!freearg && (E2_PaneRuntime *)actiondata == &app.pane2 /*UNUSED || actiondata == GUINT_TO_POINTER (2)*/ ) which = E2PANE2; else if (*(gchar *)actiondata == '1') which = E2PANE1; else if (*(gchar *)actiondata == '2') which = E2PANE2; else if (*(gchar *)actiondata == '0') //current from = NULL; //no further checking } if (freearg) g_free (actiondata); //now try to interpret the originator if (which == E2PANECUR && from != NULL) { if (GTK_IS_TREE_VIEW (from)) { if (GTK_WIDGET (from) == app.pane1.view.treeview) which = E2PANE1; if (GTK_WIDGET (from) == app.pane2.view.treeview) which = E2PANE2; } else if (GTK_IS_ENTRY (from)) { E2_CommandLineRuntime *clrt = g_object_get_data (G_OBJECT (from), "command-line-runtime"); if (clrt != NULL && !clrt->original) { if (clrt->pane == &app.pane1) which = E2PANE1; else //only 2 panes which = E2PANE2; } } else if (GTK_IS_BUTTON (from)) { GtkWidget *bar = gtk_widget_get_ancestor (GTK_WIDGET (from), GTK_TYPE_TOOLBAR); if (bar != NULL) { E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) { E2_ToolbarRuntime *rt = (*thisbar)->rt; if (GTK_WIDGET(rt->toolbar) == bar) { if ((*thisbar)->type == E2_BAR_PANE1) which = E2PANE1; else if ((*thisbar)->type == E2_BAR_PANE2) which = E2PANE2; break; } } } } /* else if (GTK_IS_MENU_ITEM (from)) { //FIGURE something out ? } */ } switch (which) { case E2PANE1: return &app.pane1; case E2PANE2: return &app.pane2; default: return curr_pane; } } /** @brief decide which pane is relevant for @button @param button a toolbar button widget which triggered a callback @return rt data for pane 1 or pane 2 or active pane if can't decide 1 or 2 */ static E2_PaneRuntime *_e2_pane_get_button_runtime (GtkWidget *button) { printd (DEBUG, "_e2_pane_get_button_runtime, button %x", GPOINTER_TO_INT(button)); GtkWidget *bar = gtk_widget_get_ancestor (button, GTK_TYPE_TOOLBAR); if (bar != NULL) { E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) { E2_ToolbarRuntime *rt = (*thisbar)->rt; if (GTK_WIDGET(rt->toolbar) == bar) { if ((*thisbar)->type == E2_BAR_PANE1) { return &app.pane1; } else if ((*thisbar)->type == E2_BAR_PANE2) { return &app.pane2; } break; } } } return curr_pane; } /* * @brief change-directory completion-watch timer-function @param completed_flag store for flag to watch @return FALSE when ready to shutdown timer */ /*static gboolean _e2_pane_cd_watch (E2_CDType *completed_flag) { return (*completed_flag == CD_NOTFINISHED); //FIXME signal that this timer is being shutdown } */ /* * @brief synchronously change the directory for a specified pane This is a wrapper for _e2_pane_change_dir(), with update of pane history list and hooklist It must be called with gtk's thread-lock closed @param rt pointer to pane data struct, or NULL for current pane @param path utf8 string containing actual or virtual path to directory to be opened @param completed_flag store for flag to set to CD_SUCCESS if/when cd is completed @return */ /* void e2_pane_change_dir_sync (E2_PaneRuntime *rt, gchar *path, E2_CDType *completed_flag) { *completed_flag = CD_NOTFINISHED; if (rt == NULL) rt = curr_pane; E2_Listman *data = (rt == &app.pane1) ? &app.pane1.view.listcontrols : &app.pane2.view.listcontrols; _e2_pane_change_dir (rt, path, completed_flag, TRUE, TRUE); LISTS_LOCK gchar *new = g_strdup (data->newpath); BAD, MAYBE NULL ALREADY LISTS_UNLOCK //FIXME wait for completion, without blocking the cd process // app.timers[?] = g_timeout_add (90, (GSourceFunc) _e2_pane_cd_watch, &completed_flag); //FIXME nicely block while the timer is still running while (TRUE) { gboolean wait; LISTS_LOCK wait = data->cd_requested || g_atomic_int_get (&data->cd_working) || strcmp (new, rt->view->dir); LISTS_UNLOCK if (wait) usleep (90000) else { *completed_flag = CD_SUCCESS; break; } } g_free (new); } */ /** @brief if the dir shown in pane related to @a rt is accessible, open it, otherwise open a parent or other directory that is accessible Used only at session-start so dir-history update is applied only if history is not cached This expects BGL closed (for showing any error message) @param rt pointer to file-pane data struct @return TRUE if rt->path was opened, FALSE if an alternative was opened */ gboolean e2_pane_goto_accessible_path (E2_PaneRuntime *rt) { printd (DEBUG, "check that %s is accessible", rt->path); #ifdef E2_VFSTMP //FIXME path for non-native dirs #else gchar *checkpath = g_strdup (rt->path); //don't clobber the rt->path string #endif #ifdef E2_VFSTMP //FIXME path check for non-mounted dirs not relevant ? //what about incomplete mount of fuse-dir #endif gboolean retval = e2_fs_get_valid_path (&checkpath, TRUE E2_ERR_NONE()); if (!retval) { gchar *msg = g_strdup_printf (_("Cannot access %s, going to %s instead"), #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else rt->path, checkpath); #endif e2_output_print_error (msg, TRUE); } gboolean history = (rt->opendirs == NULL); //start history if cached one not present _e2_pane_change_dir (rt, checkpath, history, TRUE); g_free (checkpath); return retval; } /** @brief change the directory for a specified pane This is a wrapper for _e2_pane_change_dir (), with update of pane history list and hooklist gtk's thread-lock (BGL) may be open or closed @param rt pointer to pane data struct, or NULL for current pane @param newpath utf8 string containing actual or virtual path to directory to be opened @return */ void e2_pane_change_dir (E2_PaneRuntime *rt, const gchar *newpath) { _e2_pane_change_dir (rt, newpath, TRUE, TRUE); } /** @brief change directory displayed in a specified pane Makes no assumption about whether BGL is active, but uses timer to force-open BGL for downstream processing where needed ~ at start of path is interpreted to $HOME Surrounding single- or double-quotes are removed Redundant separators are removed, and one is added to the end, if not already there Any ".." at start or later in the path is corrected if possible (or if not, ignored) Does nothing if the path string is empty, shows error msg if path is unreachable Session-start check for valid path done in main(), not here. The supplied path string is unchanged. New path is stored in heap @ rt->path. New path is also stored in array @ rt->view->dir. @param rt pointer to pane data struct, or NULL for current pane @param newpath UTF-8 string containing space-relative path of directory to be opened @param history TRUE to update pane history list @param hook TRUE to run change-dir hook functions (if any, normally has at least update dirline) @return */ static void _e2_pane_change_dir (E2_PaneRuntime *rt, const gchar *newpath, gboolean history, gboolean hook) { gchar *freeme, *path, *currpath; printd (DEBUG, "_e2_pane_change_dir (rt:,path:\"%s\",update:%d,hook:%d)", newpath, history, hook); if (newpath == NULL) return; path = e2_utils_unquote_string (newpath); g_strchug (path); //trailing whitespace may be deliberate if (*path == '\0') { //quick exit g_free (path); return; } //work on currently active file pane if argument is NULL if (rt == NULL) rt = curr_pane; //work with a copy of path string which might be in transition by a prior cd LISTS_LOCK currpath = g_strdup (rt->path); LISTS_UNLOCK if (!strcmp (path, "..") && !strcmp (currpath, G_DIR_SEPARATOR_S) #ifdef E2_VFSTMP //CHECKME how does updir from archive-root work here ? #endif ) { //ignore impossible change g_free (path); g_free (currpath); return; } //this fails in the event of trailing whitespace else if (path[0] == '.' && (path[1] == '\0' //e2_utils_pass_whitespace (path+1) == NULL || (path[1] == G_DIR_SEPARATOR && e2_utils_pass_whitespace (path+2) == NULL))) { g_free (path); #ifdef E2_VFSTMP freeme = ?; #else freeme = g_get_current_dir (); #endif path = F_FILENAME_FROM_LOCALE (freeme); if (path != freeme) g_free (freeme); } else { //this may be a directly-entered cd, or path-completion may be turned off //so we interpret here too, but with limited "~" handling freeme = path; path = e2_utils_expand_macros (path, NULL); if (path == NULL || path == GINT_TO_POINTER (0x1)) path = freeme; else g_free (freeme); freeme = path; path = e2_utils_replace_vars (path, TRUE); g_free (freeme); } /* #ifndef E2_FILES_UTF8ONLY if (!app.utf8_filenames) { //this test not needed with only-utf8 file coding if (! g_utf8_validate (path, -1, NULL)) { freeme = path; path = e2_utf8_filename_from_locale (path); g_free (freeme); } } #endif */ #ifdef E2_VFSTMP //FIXME for changes up from a virtual root ... if ( strcmp (path, "..") || strcmp (currpath, G_DIR_SEPARATOR_S)) { #endif //cleanup the new path string //these funcs both ensure a trailing /, which gets into rt->path //(see below) and onto dir lines if (e2_option_bool_get_direct (rt->opt_transparent) || !strcmp (path, "..")) //always handle cd .. properly { //relative path segment(s) conversion //clean and convert the path string, if needed freeme = path; #ifdef E2_VFSTMP //CHECKME confirm this is ok for non-local work-dir #endif path = e2_utils_translate_relative_path (currpath, path); g_free (freeme); } else { if (!g_path_is_absolute (path)) { freeme = path; path = g_strconcat (currpath, path, NULL); g_free (freeme); } //make sure the user didn't enter a 'dirty' path string #ifdef E2_VFSTMP //CHECKME confirm this is ok for non-local work-dir path = e2_utils_path_clean (path); #else path = e2_utils_path_clean (path); #endif } #ifdef E2_VFSTMP } #endif g_free (currpath); E2_Listman *data = (rt == &app.pane1) ? &app.pane1.view.listcontrols : &app.pane2.view.listcontrols; LISTS_LOCK data->view = (ViewInfo *)rt; data->history = history; data->hook = hook; //now signal we're ready to do a new cd // any prior data->newpath is copied and cleared downstream, when ready data->newpath = path; //this is a copy /*we use a timer, not a thread directly, to control the cd process cuz: . we're happy to do the cd at a reasonably non-busy time . if gtk's "native" BGL mutex is in play, _must_ ensure that BGL is off, for the downstream code that performs the cd (BUT this does make it tough to wait for the end of any specific cd) we check in the timer callback, not here, for any duplicated timer for this same pane (or the other one), so as to eliminate any race and missed cd request */ gint interval = (g_atomic_int_get (&data->cd_working) != 0) ? 90 : //check about every 90 mS for completion of prior cd 4; //short initial delay in case there's a following request for other pane // data->timer = with possible repeated timers for the same pane, the id is useless LISTS_UNLOCK //#ifdef DEBUG_MESSAGES // guint tid = //#endif g_timeout_add_full (G_PRIORITY_HIGH, interval, (GSourceFunc) e2_fileview_cd_manage, data, NULL); // printd (DEBUG, "initiated cd timer = %d", tid); // LISTS_UNLOCK /* //setup for completion-watching if requested if (completed_flag != NULL) { E2_CDwatch *data2 = ALLOCATE (E2_CDwatch); CHECKALLOCATEDWARN (data2, return;) data2->view = rt->view; data2->newpath = g_strdup (path); data2->repeats = 0; data2->completed_flag = completed_flag; //this timer stops after 100 callbacks = 10 seconds //CHECKME make this cancellable at session-end ? //data->watchtimer_id = g_timeout_add (100, (GSourceFunc) e2_fileview_cd_watch, data2); } */ } /** @brief show a chosen directory in a pane @param rt pointer to pane data struct @param entry entry widget of dirline for pane associated with @a rt @return TRUE if the directory was changed successfully */ gboolean e2_pane_choose_new_dir (E2_PaneRuntime *rt, GtkWidget *entry) { #ifdef E2_VFSTMP //FIXME ensure this works with non-mounted dirs #endif //create relevant path string const gchar *choice = gtk_entry_get_text (GTK_ENTRY (entry)); gchar *dir; if (g_str_has_prefix (choice, G_DIR_SEPARATOR_S)) dir = g_strdup (choice); else { #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else //path string might be involved in a current cd LISTS_LOCK dir = g_strconcat (rt->path, choice, NULL); LISTS_UNLOCK #endif } #ifdef E2_VFSTMP //FIXME handle vfs error reasonably #endif e2_fs_get_valid_path (&dir, FALSE E2_ERR_NONE()); gchar *newdir = NULL; e2_opendir_dialog_create (&rt->view, dir, &newdir); g_free (dir); if (newdir != NULL) { //something was selected _e2_pane_change_dir (rt, newdir, TRUE, TRUE); g_free (newdir); return TRUE; } //user cancelled gtk_widget_grab_focus (entry); gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); //unselect the entry contents return FALSE; } /* * @brief timeout function for checking completion of fileview creation @param userdata data specified when the timer was initialised, here, always NULL, unused @return TRUE until update is completed, then FALSE */ /*gboolean _e2_pane_views_complete (gpointer userdata) { if (!(curr_view->listing || other_view->listing)) { printd (DEBUG, "finished waiting for fileview completion"); gdk_threads_enter (); e2_fileview_switch_views (); gtk_widget_grab_focus (curr_view->treeview); gdk_threads_leave (); //run this with BGL open/off e2_hook_list_run (&app.hook_pane_focus_changed, curr_pane); return FALSE; } return TRUE; //continue waiting } */ static GPtrArray *menued = NULL; static void _e2_pane_history_menu_filter_init (void) { if (menued == NULL) menued = g_ptr_array_sized_new (8); else g_ptr_array_set_size (menued, 0); } static void _e2_pane_history_menu_filter_finalise (void) { if (menued == NULL) { g_ptr_array_free (menued, TRUE); menued = NULL; } } /** @brief decide whether to include the dir whose path is data for @a node in a history menu Mainly, this checks for duplicates. Also excludes _all_ items same as current path. @param node a member of rt->opendirs @param rt pointer to data struct for pane being processed @return 0 if @a node has a duplicate string or a non-current path same as current, 1 if it belongs to the current (displayed) dir, or 2 otherwise */ static guint _e2_pane_history_menu_filter (GList *node, E2_PaneRuntime *rt) { if (strcmp ((gchar *)node->data, rt->path) == 0) { guint len = g_list_length (rt->opendirs); if (g_list_position (rt->opendirs, node) != len - 1 - rt->opendir_cur) return 0; else return 1; } guint retval; if (g_ptr_array_remove_fast (menued, node->data)) //hack for quick finding retval = 0; else retval = 2; g_ptr_array_add (menued, node->data); //log it (again) return retval; } /** @brief pane history goto-menu callback Note that this is called after activation from a right-click menu. A hover-menu is handled by _e2_action_hover_buttonpress_cb() @param menuitem the selected item @param rt pointer to pane data struct @return */ static void _e2_pane_history_selected_cb (GtkMenuItem *menuitem, E2_PaneRuntime *rt) { #ifdef E2_VFSTMP //FIXME ensure this works with non-mounted dirs i.e. history is specific to the fs type/place #endif GtkWidget* label = gtk_bin_get_child (GTK_BIN (menuitem)); const gchar* dir = gtk_label_get_label (GTK_LABEL (label)); printd (DEBUG, "Selected history menu item for %s", dir); _e2_pane_change_dir (rt, (gchar *)dir, TRUE, TRUE); } /** @brief construct destroyable menu of all history for the pane associated with @rt @param rt pointer to pane data struct @return the menu widget (unshown), or NULL if no item is relevant */ GtkWidget *e2_pane_visited_menu (E2_PaneRuntime *rt) { _e2_pane_history_menu_filter_init (); guint done = 0; /* show all non-duplicated items in the list, with priority to the current item, in reverse order to their list positions = in history-order e.g. history-index 6 5 4 3 2 1 0 list-position 0 1 2 3 4 5 6 */ GtkWidget *menu = gtk_menu_new (); HISTORY_LOCK GList *member = g_list_last (rt->opendirs); HISTORY_UNLOCK while (member != NULL) { guint choice = _e2_pane_history_menu_filter (member, rt); if (choice > 0) { //an item we will display GtkWidget *item; if (choice == 1) item = gtk_radio_menu_item_new_with_label (NULL, (gchar *)member->data); else //==2 { item = gtk_menu_item_new_with_label ((gchar *)member->data); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_e2_pane_history_selected_cb), rt); } gtk_widget_show_all (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); done = 1; } HISTORY_LOCK member = member->prev; HISTORY_UNLOCK } _e2_pane_history_menu_filter_finalise (); if (done == 0) { gtk_widget_destroy (menu); menu = NULL; } return menu; } /*******************/ /***** actions *****/ /*******************/ /** @brief refresh content of both pane views Downstream functions expect gtk's BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the request was logged */ static gboolean _e2_pane_refresh_lists (gpointer from, E2_ActionRuntime *art) { #ifdef E2_FAM # ifdef E2_VFSTMP gboolean withother = other_view->spacedata == NULL || other_view->spacedata->monitored; gboolean retval = (curr_view->spacedata == NULL || curr_view->spacedata->monitored) ? e2_filelist_request_refresh (curr_view->dir, !withother) : TRUE; if (withother) retval = e2_filelist_request_refresh (other_view->dir, TRUE) && retval; # else //2 tests, to lessen race due to dir-swap during first call gboolean retval = e2_filelist_request_refresh (curr_view->dir, FALSE); retval = e2_filelist_request_refresh (other_view->dir, TRUE) || retval; # endif return retval; #else e2_filelist_check_dirty (GINT_TO_POINTER (1)); return TRUE; #endif } /** @brief change active pane from the current one to the other one This also toggles the visible filepane, if only 1 is shown @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_activate_other_action (gpointer from, E2_ActionRuntime *art) { //if showing only 1 filepane, show the other one now if (app.window.panes_paned_ratio < 0.001) e2_window_adjust_pane_ratio ("1"); else if (app.window.panes_paned_ratio > 0.999) e2_window_adjust_pane_ratio ("0"); e2_pane_activate_other (); return TRUE; } /** @brief make a specified pane active if not already so, or else just focus the pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the active pane was changed */ static gboolean _e2_pane_activate (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); if (rt != curr_pane) { e2_pane_activate_other (); return TRUE; } else { gtk_widget_grab_focus (curr_view->treeview); return FALSE; } } /** @brief focus specified pane's file list, without changing active pane This is primarily intended for the start of a chained a key-binding @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_focus (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); gtk_widget_grab_focus (rt->view.treeview); return TRUE; } /** @brief action to go to top of specified pane file list This is primarily intended for a custom key-binding Downstream code expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_focus_top (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); if (gtk_tree_model_iter_n_children (rt->view.model, NULL) > 0) { e2_fileview_focus_row (&rt->view, 0, TRUE, TRUE, FALSE, TRUE); return TRUE; } return FALSE; } /** @brief action to go to bottom of specified pane file list This is primarily intended for a custom key-binding Downstream code expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_focus_bottom (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); gint n = gtk_tree_model_iter_n_children (rt->view.model, NULL); if (n > 0) { e2_fileview_focus_row (&rt->view, n-1, TRUE, TRUE, FALSE, TRUE); return TRUE; } return FALSE; } /** @brief change the directory shown in a specified pane or the current one The cd is asynchronous, so probably not done immediately Downstream function expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_pane_change_dir_action (gpointer from, E2_ActionRuntime *art) { const gchar *newpath = NULL; E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, &newpath); if (rt == NULL || newpath == NULL) return FALSE; _e2_pane_change_dir (rt, newpath, TRUE, TRUE); return TRUE; } /* * @brief for a specified pane, open the directory @a number places forward in the history list The last entry in the list will be used if we try to go forward too far @param rt pointer to pane data struct @param number integer no.of places in the history list to go forward @return */ /*static void _e2_pane_go_forward_ (E2_PaneRuntime *rt, gint number) { NOTE - IF USED, THIS MAY NEED A BASE-INDEX CORRECTION (1 TO 0) AND LIST-DIRECTION SWAP printd (DEBUG, "_e2_pane_go_forward (rt:,number:%d", number); guint old = rt->opendir_cur; guint len = g_list_length (rt->history); rt->opendir_cur += number; if (rt->opendir_cur > g_list_length (rt->history)) rt->opendir_cur = len; if (old != rt->opendir_cur) { // gchar *dir = F_FILENAME_TO_LOCALE (); gchar *dir = g_list_nth_data (rt->history, rt->opendir_cur - 1); _e2_pane_change_dir (rt, dir, NULL, FALSE, TRUE); // F_FREE (dir, ); } } */ /** @brief goto the next dir in the history list for specified or current pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE unless there's nowhere to goto */ static gboolean _e2_pane_go_forward (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "_e2_pane_go_forward action"); E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); //history entries are prepended to list, forward in history = back in list HISTORY_LOCK guint len; if (rt->opendirs != NULL) len = g_list_length (rt->opendirs); else len = 0; if (len == 0 || rt->opendir_cur == len - 1) { HISTORY_UNLOCK return FALSE; //nowhere forward to go } gboolean popup; if ((ACTION_BUTTON (art,3) && ACTION_MASK (art, 0)) || (ACTION_BUTTON (art,1) && ACTION_MASK (art, GDK_CONTROL_MASK)) //deprecated || (art->data != NULL && strstr ((gchar *)art->data, _("ctrl")) != NULL)) popup = TRUE; /* else if (GTK_IS_BUTTON (from) || GTK_IS_MENU_ITEM (from)) { popup = ACTION_MASK (art, GDK_CONTROL_MASK); } */ else popup = FALSE; if (popup) { HISTORY_UNLOCK GtkWidget *menu = _e2_pane_popup_forward (GTK_WIDGET (from), rt); if (menu != NULL) { guint32 event_time = gtk_get_current_event_time (); if (GTK_IS_BUTTON(from)) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, event_time); else //FIXME gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event_time); } } else { rt->opendir_cur++; // gchar *dir = F_FILENAME_TO_LOCALE (); gchar *dir = g_list_nth_data (rt->opendirs, len - 1 - rt->opendir_cur); HISTORY_UNLOCK _e2_pane_change_dir (rt, dir, FALSE, TRUE); // F_FREE (dir, ); } return TRUE; } /** @brief hover callback to create and pop up menu for going forward in history list Un-grabbed display of the menu is handled upstream. @param from the button, menu item etc which was hovered @param data pointer to hover data specified when the action was established @return */ static void _e2_pane_forward_hover (GtkWidget *from, E2HoverData *data) { E2_PaneRuntime *rt = _e2_pane_get_button_runtime (from); data->menu = _e2_pane_popup_forward (from, rt); //menu not shown } /** @brief create destroyable menu of directories for going forward in history list @param from the button, menu item etc which was activated @param rt pointer to pane data struct @return the created menu widget (unshown), or NULL if no item is relevant */ static GtkWidget *_e2_pane_popup_forward (GtkWidget *from, E2_PaneRuntime *rt) { if (rt->opendirs == NULL) return NULL; guint len = g_list_length (rt->opendirs); if (rt->opendir_cur == len - 1) return NULL; //nowhere forward to go _e2_pane_history_menu_filter_init (); //setup for duplicate-filtering guint done = 0; /* display items whose index is > opendir_cur i.e. positioned in the list before (list-len - 1 - opendir_cur), and in index-order i.e. the reverse order of their list positions e.g. history-index 6 5 4 3 2 1 0 list-position 0 1 2 3 4 5 6 current-index 4 show index 5 6 list index 1 0 */ GtkWidget *menu = gtk_menu_new (); HISTORY_LOCK GList *member = g_list_nth (rt->opendirs, len - 2 - rt->opendir_cur); HISTORY_UNLOCK while (member != NULL) { if (_e2_pane_history_menu_filter (member, rt) == 2) { //a unique item != current dir GtkWidget *item; item = gtk_menu_item_new_with_label ((gchar *)member->data); gtk_widget_show_all (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); printd (DEBUG, "added history menu item for %s", (gchar *)member->data); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_e2_pane_history_selected_cb), rt); done = 1; } HISTORY_LOCK member = member->prev; HISTORY_UNLOCK } _e2_pane_history_menu_filter_finalise (); //cleanup if (done > 0) g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_selection_done_cb), NULL); else { gtk_widget_destroy (menu); menu = NULL; } return menu; } /* * @brief for a specified pane, open the directory @a number places back in the history list The first entry in the list will be used if we try to go back too far @param rt pointer to pane data struct @param number integer no.of places in the history list to go back @return */ /*static void _e2_pane_go_back_ (E2_PaneRuntime *rt, gint number) { NOTE - IF USED, THIS MAY NEED A BASE-INDEX CORRECTION (1 TO 0) AND LIST-DIRECTION SWAP printd (DEBUG, "_e2_pane_go_back (rt:,number:%d", number); guint old = rt->opendir_cur; rt->opendir_cur -= number; if (rt->opendir_cur < 1) rt->opendir_cur = 1; if (old != rt->opendir_cur) { gchar *dir = g_list_nth_data (rt->history, rt->opendir_cur - 1); _e2_pane_change_dir (rt, dir, FALSE, TRUE); } }*/ /** @brief goto the previous dir in the history list for specified or current pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE unless there's nowhere to goto */ static gboolean _e2_pane_go_back (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "_e2_pane_go_back action"); E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); //history entries are prepended to list, so back in history = forward in list HISTORY_LOCK guint len; if (rt->opendirs != NULL) len = g_list_length (rt->opendirs); else len = 0; if (len == 0 || rt->opendir_cur == 0) { HISTORY_UNLOCK return FALSE; } gboolean popup; if ((ACTION_BUTTON (art,3) && ACTION_MASK (art, 0)) || (ACTION_BUTTON (art,1) && ACTION_MASK (art, GDK_CONTROL_MASK)) //deprecated || (art->data != NULL && strstr ((gchar *)art->data, _("ctrl")) != NULL)) popup = TRUE; /* else if (GTK_IS_BUTTON (from) || GTK_IS_MENU_ITEM (from)) { popup = ACTION_MASK (art, GDK_CONTROL_MASK); } */ else popup = FALSE; if (popup) { HISTORY_UNLOCK GtkWidget *menu = _e2_pane_popup_backward (GTK_WIDGET (from), rt); if (menu != NULL) { guint32 event_time = gtk_get_current_event_time (); if (GTK_IS_BUTTON(from)) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, event_time); else //FIXME gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 1, event_time); } } else { rt->opendir_cur--; gchar *dir = g_list_nth_data (rt->opendirs, len - 1 - rt->opendir_cur); HISTORY_UNLOCK _e2_pane_change_dir (rt, dir, FALSE, TRUE); } return TRUE; } /** @brief hover-action callback to create menu for going backward in history list Un-grabbed display of the menu is handled upstream @param from the button, menu item etc which was hovered @param data pointer to hover data struct specified when the action was established @return */ static void _e2_pane_backward_hover (GtkWidget *from, E2HoverData *data) { E2_PaneRuntime *rt = _e2_pane_get_button_runtime (from); data->menu = _e2_pane_popup_backward (from, rt); //menu not shown } /** @brief create destroyable menu of directories for going backward in history list @param rt pointer to pane data struct @return created menu widget (unshown), or NULL if no item is relevant */ static GtkWidget *_e2_pane_popup_backward (GtkWidget *from, E2_PaneRuntime *rt) { if (rt->opendir_cur == 0) { // printd (DEBUG, "can't go back, no backward-menu"); return NULL; } if (rt->opendirs == NULL) { // printd (DEBUG, "no history, no backward-menu"); return NULL; } _e2_pane_history_menu_filter_init (); //setup to eliminate duplicates guint done = 0; /* display items whose index is < opendir_cur i.e. positioned in the list after (list-len - 1 - opendir_cur), and in reverse-index-order i.e. the same order as their list positions e.g. history-index 6 5 4 3 2 1 0 list-position 0 1 2 3 4 5 6 current-index 4 show index 3 2 1 0 list index 3 4 5 6 */ guint len = g_list_length (rt->opendirs); //say, 7 // printd (DEBUG, "current history-index %u", rt->opendir_cur); //say, 4 //#ifdef DEBUG_MESSAGES // guint curr = len - rt->opendir_cur; //(-1 + 1 cancelled) 3 // printd (DEBUG, "list-length %u list-start-position %u", len, curr); //#endif GtkWidget *menu = gtk_menu_new (); HISTORY_LOCK GList *member = g_list_nth (rt->opendirs, len - rt->opendir_cur); HISTORY_UNLOCK while (member != NULL) { if (_e2_pane_history_menu_filter (member, rt) == 2) { //a unique item != current dir GtkWidget *item = gtk_menu_item_new_with_label ((gchar *)member->data); gtk_widget_show_all (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); printd (DEBUG, "added history menu item for %s", (gchar *)member->data); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_e2_pane_history_selected_cb), rt); // printd (DEBUG, "list-index of displayed dir is %u", curr); done = 1; } // else // printd (DEBUG, "omit duplicate dir at list-index %u", curr); HISTORY_LOCK member = member->next; HISTORY_UNLOCK //#ifdef DEBUG_MESSAGES // curr--; //#endif } _e2_pane_history_menu_filter_finalise (); //cleanup if (done > 0) g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_selection_done_cb), NULL); else { // printd (DEBUG, "empty backward-menu"); gtk_widget_destroy (menu); menu = NULL; } return menu; } /** @brief in a specified pane or current pane, open the parent of the current directory The helper function prevents any change if at root dir already @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_go_up (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); _e2_pane_change_dir (rt, "..", TRUE, TRUE); return TRUE; } /** @brief open pane other pane directory in this pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_mirror (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); E2_PaneRuntime *ort = (rt == &app.pane1) ? &app.pane2 : &app.pane1; LISTS_LOCK gchar *pathcopy = g_strdup (ort->path); LISTS_UNLOCK _e2_pane_change_dir (rt, pathcopy, TRUE, TRUE); g_free (pathcopy); return TRUE; } /** @brief change 'other' pane to be the same as 'current' pane Unqueued. Downstream function expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_sync (gpointer from, E2_ActionRuntime *art) { _e2_pane_change_dir (other_pane, curr_pane->path, TRUE, TRUE); return TRUE; } /** @brief show a specified directory in a pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if directory was changed */ static gboolean _e2_pane_goto (gpointer from, E2_ActionRuntime *art) { const gchar *path; E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, &path); if (rt != NULL) { _e2_pane_change_dir (rt, path, TRUE, TRUE); return TRUE; } return FALSE; } /** @brief show a chosen directory in a pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if directory was changed */ static gboolean _e2_pane_goto_choice (gpointer from, E2_ActionRuntime *art) { E2_CommandLineRuntime *clrt; E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); GtkWidget *entry = NULL; if (GTK_IS_ENTRY (from)) { clrt = g_object_get_data (G_OBJECT (from), "command-line-runtime"); if (clrt != NULL && !clrt->original && clrt->pane == rt) entry = GTK_WIDGET (from); } if (entry == NULL) { //try to find relevant entry GList *line; for (line = app.command_lines; line != NULL; line = line->next) { clrt = line->data; if (!clrt->original && clrt->pane == rt) { entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (clrt->combo)); #else GTK_BIN (clrt->combo)->child; #endif break; } } } if (entry == NULL) return FALSE; //can't do anything return (e2_pane_choose_new_dir (rt, entry)); } /** @brief open the trash directory in the inactive pane There is no hook update for the pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if trash directory is recorded */ static gboolean _e2_pane_goto_trash (gpointer from, E2_ActionRuntime *art) { gchar *tp = e2_utils_get_trash_path (NULL, TRUE); //UTF-8 string, "Trash/files/" at end if (tp != NULL) { //change the dir line entry, if possible E2_CommandLineRuntime *clrt; GList *line; for (line = app.command_lines; line != NULL ; line = g_list_next (line)) { clrt = line->data; if (! clrt->original //this is a dir line && clrt->pane == other_pane) { GtkWidget *entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (clrt->combo)); #else GTK_BIN (clrt->combo)->child; #endif gtk_entry_set_text (GTK_ENTRY (entry), tp); break; } } _e2_pane_change_dir (other_pane, tp, TRUE, FALSE); //NOTE no hook so no change of dirline ! g_free (tp); return TRUE; } e2_output_print_error (_("No trash directory is available"), FALSE); return FALSE; } /** @brief create and pop up adjacent to a toolbar button a destroyable filters menu for a specified pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_filter_menu_create (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); GtkWidget *menu = e2_menu_create_filter_menu (&rt->view); guint32 event_time = gtk_get_current_event_time (); if (GTK_IS_BUTTON(from)) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, event_time); else //FIXME gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 1, event_time); return TRUE; } /** @brief create and pop up near middle of pane filelist a destroyable filters-menu @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_filters_show (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); GtkWidget *menu = e2_menu_create_filter_menu (&rt->view); guint32 event_time = gtk_get_current_event_time (); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_fileview_set_menu_position, rt->view.treeview, 0, event_time); return TRUE; } #ifdef E2_VFS /** @brief select-namespace action, where a string argument is supplied @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the change was successful */ static gboolean _e2_pane_select_space (gpointer from, E2_ActionRuntime *art) { const gchar *name = NULL; E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, &name); if (rt == NULL || name == NULL) return FALSE; return (e2_pane_change_space_byuri (rt, name)); } /** @brief vfs menu-item callback This must be here, not in vfs plugin, as it is one mechanism to load that plugin Other callbacks are in the plugin @param menu_item the activated menu item widget @param view pointer to data struct for view to be changed @return */ static void _e2_pane_vfs_dialog_cb (GtkMenuItem *menu_item, ViewInfo *view) { if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.show_historydialog)) vfs.show_historydialog (view); else printd (WARN, "Cannot find vfs plugin"); } /** @brief create and pop up destroyable menu of vfs places @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_vfs_menu_show (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); ViewInfo *view = rt->view; GtkWidget *item, *menu = gtk_menu_new (); if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.create_placesmenu)) //note no cursor change in event of slow loading { gboolean history = FALSE; if ((ACTION_BUTTON (art,3) && ACTION_MASK (art,0)) || (ACTION_BUTTON (art,1) && ACTION_MASK (art,GDK_CONTROL_MASK)) //deprecated || (art->data != NULL && strstr ((gchar *)art->data, _("ctrl")) != NULL)) history = TRUE; /* else if (GTK_IS_BUTTON (from) || GTK_IS_MENU_ITEM (from)) { if (ACTION_MASK (art,GDK_CONTROL_MASK)) history = TRUE; } */ vfs.create_placesmenu (menu, view, history); //add plugin-specific items to menu } item = e2_menu_add (menu, _("_Edit places"), NULL, NULL, _e2_pane_vfs_dialog_cb, view); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_selection_done_cb), NULL); guint32 event_time = gtk_get_current_event_time (); if (art->action->data == NULL) //CHECKME gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_fileview_set_menu_position, curr_view->treeview, 0, event_time); else gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, event_time); return TRUE; } #endif /** @brief select all items in specified pane The pane is made active, if it wasn't already @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_select_all (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); if (rt != curr_pane) e2_pane_activate_other (); e2_fileview_select_all (NULL, curr_view); #ifdef E2_STATUS_DEMAND use a selection-change cb ? gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); #endif return TRUE; } /** @brief select all items in specified pane which have same extension as first-selected item The pane is made active, if it wasn't already @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the pane is not empty */ static gboolean _e2_pane_select_extension (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt; GtkTreeModel *model; GtkTreeIter iter; rt = e2_pane_get_runtime (from, art->data, NULL); if (rt != curr_pane) e2_pane_activate_other (); WAIT_FOR_REFRESH(curr_view) model = curr_view->model; if (gtk_tree_model_get_iter_first (model, &iter)); { //it's not empty FileInfo *info = e2_fileview_get_selected_first_local (curr_view, FALSE); if (info != NULL) { gboolean any; gchar *base, *ext1; GtkTreeSelection *sel = curr_view->selection; gtk_tree_selection_unselect_all (sel); //start with clean slate any = e2_option_bool_get ("anycase-filetypes"); //get right-most extension base = info->filename; ext1 = strrchr (base, '.'); if (ext1 == base) ext1 = NULL; //ignore hidden-file indicator do { gchar *ext2; gtk_tree_model_get (model, &iter, FINFO, &info, -1); //get extension base = info->filename; ext2 = strrchr (base, '.'); if (ext2 == base) ext2 = NULL; //ignore hidden-file indicator if ((ext1 == NULL && ext2 == NULL) || (ext1 != NULL && ext2 != NULL && ((any && strcasecmp (ext1, ext2) == 0) || (!any && strcmp (ext1, ext2) == 0)))) gtk_tree_selection_select_iter (sel, &iter); } while (gtk_tree_model_iter_next (model, &iter)); return TRUE; } } return FALSE; } /** @brief invert selection state of all items in specified pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_invert_all (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); GtkTreeIter iter; GtkTreeModel *mdl = rt->view.model; if (gtk_tree_model_get_iter_first (mdl, &iter)) { GtkTreeSelection *sel = rt->view.selection; do { if (gtk_tree_selection_iter_is_selected (sel, &iter)) gtk_tree_selection_unselect_iter (sel, &iter); else gtk_tree_selection_select_iter (sel, &iter); } while (gtk_tree_model_iter_next (mdl, &iter)); #ifdef E2_STATUS_DEMAND use a selection-change cb ? gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); #endif return TRUE; } return FALSE; } /** @brief invert selection state of focused items in specified pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_invert_current (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (rt->view.model, &iter, NULL, rt->view.row)) { GtkTreeSelection *sel = rt->view.selection; if (gtk_tree_selection_iter_is_selected (sel, &iter)) gtk_tree_selection_unselect_iter (sel, &iter); else gtk_tree_selection_select_iter (sel, &iter); #ifdef E2_STATUS_DEMAND use a selection-change cb ? gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); #endif return TRUE; } return FALSE; } /* * @brief helper for toggle hidded action Downstream functions require BGL closed @param view pointer to data for view being changed @return TRUE always */ /*static gboolean _e2_pane_toggle_hidden_ (ViewInfo *view) { view->show_hidden = !view->show_hidden; e2_fileview_refilter_list (view); #ifdef E2_STATUS_DEMAND if (view == curr_view) { use a selection-change cb ? gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); } #endif //toggle the button E2_ToggleType num = (view == &app.pane1.view) ? E2_TOGGLE_PANE1HIDDEN : E2_TOGGLE_PANE2HIDDEN; e2_toolbar_button_toggle (toggles_array[num]); return TRUE; } */ /** @brief toggle display of hidden items in a specified pane Downstream functions require BGL closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_toggle_hidden (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); ViewInfo *view = (ViewInfo *)rt; // return (_e2_pane_toggle_hidden_ (view)); view->show_hidden = !view->show_hidden; e2_fileview_refilter_list (view); #ifdef E2_STATUS_DEMAND if (view == curr_view) { use a selection-change cb ? gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); } #endif //toggle the button E2_ToggleType num = (view == &app.pane1.view) ? E2_TOGGLE_PANE1HIDDEN : E2_TOGGLE_PANE2HIDDEN; e2_toolbar_button_toggle (toggles_array[num]); return TRUE; } /** @brief sort a visible column in specified pane This is primarily intended for a key-binding @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ static gboolean _e2_pane_sort (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); return (e2_fileview_sort_column (GPOINTER_TO_INT (art->action->data), &rt->view)); } /** @brief clear the current dirline content from the corresponding history This is primarily intended for a key-binding. Unlike the hard-coded binding, it does not clear the actual entry string @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if a combo is found */ static gboolean _e2_pane_clear_dirline_history (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); //find the first-registered dir-line for the pane GList *list; for (list = app.command_lines; list != NULL ; list = g_list_next (list)) { E2_CommandLineRuntime *clrt = (E2_CommandLineRuntime *) list->data; if (!clrt->original && clrt->pane == rt) { GtkWidget *entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (clrt->combo)); #else GTK_BIN (clrt->combo)->child; #endif e2_command_line_highlight (entry, FALSE); const gchar *this = gtk_entry_get_text (GTK_ENTRY (entry)); e2_combobox_clear_value (clrt->combo, this, FALSE); return TRUE; } } return FALSE; } /** @brief clear the current forward-back history content @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_clear_goto_history (gpointer from, E2_ActionRuntime *art) { gboolean retval = FALSE; const gchar *arg = NULL; E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, &arg); if (rt != NULL) { rt->opendir_cur = 0; //do not clear the data, it's owned by app.dir_history g_list_free (rt->opendirs); rt->opendirs = NULL; retval = TRUE; } if (arg != NULL) { if (g_str_has_prefix (arg, "1")) rt = &app.pane1; else if (g_str_has_prefix (arg, "2")) rt = &app.pane2; else if ((strchr (arg, '1') != NULL && strchr (arg, '2') != NULL) || g_str_has_prefix (arg, _("both"))) { rt = &app.pane1; rt->opendir_cur = 0; g_list_free (rt->opendirs); rt->opendirs = NULL; rt = &app.pane2; } rt->opendir_cur = 0; g_list_free (rt->opendirs); rt->opendirs = NULL; retval = TRUE; } return retval; } /******************/ /***** public *****/ /******************/ /** @brief grab pointers to options previously registered @param rt pointer to pane data struct @return */ void e2_pane_create_option_data (E2_PaneRuntime *rt) { /* gchar *option_name = (rt == &app.pane1) ? "panebar1" : "panebar2" ; //do not translate rt->set = e2_option_get (option_name); option_name = g_strconcat (rt->name,"-use-startup-dir",NULL); rt->opt_use_startup = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat(rt->name,"-use-startup-dir-startup-dir",NULL); rt->opt_startup = e2_option_get (option_name); g_free (option_name); //create dependency rt->opt_startup->depends = rt->opt_use_startup->name; */ rt->opt_transparent = e2_option_get ("transparent-dir-links"); } /** @brief create file pane and all its contents This is used only at session-start @param rt pointer to pane data struct @return */ void e2_pane_create (E2_PaneRuntime *rt) { gint num = (rt == &app.pane1) ? 1 : 2; rt->name = g_strdup_printf ("pane%d", num); rt->pane_sw = NULL; e2_pane_create_option_data (rt); //waiting until first-registration of a hook func may be too late g_hook_list_init (&rt->hook_change_dir, sizeof (GHook)); g_hook_list_init (&rt->view.hook_refresh, sizeof (GHook)); #ifdef E2_VFSTMP //CHECKME path when non-mounted dir cached at session end #endif e2_cache_str_register (rt->name, &rt->path, G_DIR_SEPARATOR_S); //do we need to over-ride the cached startup dir by a runtime option ? gchar *startdir = (num == 1) ? e2_cl_options.pane1_path : e2_cl_options.pane2_path ; if (startdir != NULL) startdir = D_FILENAME_FROM_LOCALE (startdir); //don't worry about trailer #ifdef E2_VFSTMP //CHECKME startup dir always local? set other paths accordingly #endif else //or by a config option ? { gchar *option_name = g_strconcat (rt->name,"-use-startup-dir", NULL); if (e2_option_bool_get (option_name)) { g_free (option_name); option_name = g_strconcat (rt->name,"-use-startup-dir-startup-dir", NULL); startdir = e2_option_str_get (option_name); if (startdir != NULL) startdir = g_strdup (startdir); // startdir = F_FILENAME_FROM_LOCALE (startdir); } else startdir = NULL; g_free (option_name); } if (startdir != NULL) { g_free (rt->path); rt->path = startdir; } if (e2_option_bool_get ("cache-history")) { gchar *history_name = g_strconcat (rt->name, "-history", NULL); e2_cache_list_register (history_name, &rt->opendirs); g_free (history_name); if (rt->opendirs != NULL) { //populate hash with data from cache list GList *node; for (node = rt->opendirs; node != NULL; node = node->next) { gchar *dirpath = (gchar *)node->data; E2_DirHistoryEntry *hist_entry = g_hash_table_lookup (app.dir_history, dirpath); if (hist_entry == NULL) {//need a new entry for the history cache hist_entry = ALLOCATE0 (E2_DirHistoryEntry); CHECKALLOCATEDWARNT (hist_entry, ); if (hist_entry != NULL) { g_strlcpy (hist_entry->path, dirpath, sizeof(hist_entry->path)); hist_entry->selrow = -1; #ifdef E2_VFSTMP hist_entry->spacedata = view->spacedata; #endif g_hash_table_insert (app.dir_history, dirpath, hist_entry); node->data = hist_entry; //now safe to substitute shared data } } else { g_free (dirpath); //clean redundant standalone data node->data = hist_entry; //substitute shared data } } } history_name = g_strconcat (rt->name, "-current", NULL); e2_cache_int_register (history_name, (gint*)&rt->opendir_cur, 0); g_free (history_name); if (rt->opendirs == NULL) rt->opendir_cur = 0; else if (startdir == NULL) { g_free (rt->path); guint len = g_list_length (rt->opendirs); rt->path = g_strdup (g_list_nth_data (rt->opendirs, len - 1 - rt->opendir_cur)); } } //register pane-specific actions E2_Action actions[] = { //non-NULL data signals a dirline instead of command line, and _is_ used {NULL,(gboolean(*)(gpointer,E2_ActionRuntime*))NULL, FALSE,E2_ACTION_TYPE_COMMAND_LINE, E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_ACCEL, rt, NULL}, {NULL,_e2_pane_toggle_hidden, FALSE,E2_ACTION_TYPE_ITEM, 0, rt, NULL}, {NULL,_e2_pane_filter_menu_create,FALSE,E2_ACTION_TYPE_ITEM, 0, rt, NULL}, #ifdef E2_VFS {NULL,_e2_pane_vfs_menu_show, FALSE,E2_ACTION_TYPE_ITEM, 0, rt, NULL}, #endif }; //all name strings freed during action-cleanup actions[0].name = g_strconcat ((num == 1) ? _A(11) : _A(12),".",_A(26), NULL); actions[1].name = g_strdup ((num == 1) ? toggles_array [E2_TOGGLE_PANE1HIDDEN] : toggles_array [E2_TOGGLE_PANE2HIDDEN]); actions[2].name = g_strdup ((num == 1) ? toggles_array [E2_TOGGLE_PANE1FILTERS] : toggles_array [E2_TOGGLE_PANE2FILTERS]); #ifdef E2_VFS actions[3].name = g_strdup ((num == 1) ? toggles_array [E2_TOGGLE_PANE1SPACE] : toggles_array [E2_TOGGLE_PANE2SPACE]); #endif guint i, count = sizeof (actions)/sizeof (E2_Action); for (i = 0; i < count; i++) e2_action_register (&actions[i]); //after the actions are in place, fill in the pane contents e2_pane_create_part (rt); } /** @brief part of pane creation, used also in re-creation @param rt pointer to pane data struct @return */ void e2_pane_create_part (E2_PaneRuntime *rt) { //existing hooklists are cleared if appropriate in e2_window_recreate() rt->pane_sw = e2_fileview_create_list (&rt->view); rt->inner_box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (rt->inner_box), rt->pane_sw, TRUE, TRUE, 0); rt->outer_box = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (rt->outer_box), rt->inner_box, TRUE, TRUE, 0); gtk_widget_show (rt->inner_box); gtk_widget_show (rt->outer_box); e2_toolbar_create (&rt->toolbar); if (e2_option_bool_get_direct (rt->toolbar.show)) { //show full/split window button according to cached pane ratio if (rt == &app.pane1 && app.window.panes_paned_ratio > 0.999) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_PANE1FULL], TRUE); else if (rt == &app.pane2 && app.window.panes_paned_ratio < 0.001) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_PANE2FULL], TRUE); //show (un)hide toggle buttons which match corresponding cached state E2_ToggleType num = (rt == &app.pane1) ? E2_TOGGLE_PANE1HIDDEN : E2_TOGGLE_PANE2HIDDEN; e2_toolbar_toggle_button_set_state (toggles_array [num], rt->view.show_hidden); num = (rt == &app.pane1) ? E2_TOGGLE_PANE1FILTERS : E2_TOGGLE_PANE2FILTERS; gboolean filtered = ( rt->view.name_filter.active || rt->view.size_filter.active || rt->view.date_filter.active); //this needs to be inverted to show correct button e2_toolbar_toggle_button_set_state (toggles_array [num], !filtered); //set initial filter tooltip if (filtered) e2_toolbar_toggle_filter_button (&rt->view); #ifdef E2_VFSTMP //FIXME gchar *tip = "faketip"; e2_toolbar_update_vfs_toggle_button (rt->view, tip); #endif } } /* * @brief destroy pane data @param rt pointer to pane data struct @return */ /*static void _e2_pane_destroy (E2_PaneRuntime *rt) { gchar *history_name = g_strconcat (rt->name, "-history", NULL); e2_cache_unregister (history_name); e2_list_free_with_data (&); g_free (history_name); //pane toolbar may be outside rt->outer_box, so destroy it independently gtk_widget_destroy (rt->toolbar.toolbar_container); // gtk_widget_destroy (rt->pane_sw); gtk_widget_destroy (rt->outer_box); g_hook_list_clear (&rt->hook_change_dir); g_hook_list_clear (&rt->view->hook_refresh); }*/ /* * @brief destroy and re-create specified pane @param rt pointer to pane data struct @return */ /*void e2_pane_recreate (E2_PaneRuntime *rt) { _e2_pane_destroy (rt); e2_pane_create_part (rt); e2_toolbar_rebadge (rt->toolbar); }*/ /** @brief limit the size of the history list to be cached for pane related to @a rt The extent is limited to current-dir +/- 5 items max. @param rt pointer to pane data struct @return */ void e2_pane_trim_history (E2_PaneRuntime *rt) { if (rt->opendirs != NULL) { GList *node; //process any excess 'forward' items at start of history-list guint len = g_list_length (rt->opendirs); gint limit = (gint)rt->opendir_cur + 5; if (limit < (gint) len - 1) //allow for 0-base { //split the list before the limiting item node = g_list_nth (rt->opendirs, len - 1 - limit); node->prev->next = NULL; node->prev = NULL; //data in the list owned by app.dir_history, don't free it separately g_list_free (rt->opendirs); rt->opendirs = node; len = limit + 1; } //now any excess 'backward' items at end of history-list limit = rt->opendir_cur - 5; if (limit > 0) { //split after limiting item node = g_list_nth (rt->opendirs, len - limit); node->prev->next = NULL; node->prev = NULL; g_list_free (node); rt->opendir_cur = rt->opendir_cur - limit; } } } /** @brief register pane-related actions that don't have a pane-specifc action Actions with pane-specific function are registered in e2_pane_create() @return */ void e2_pane_actions_register (void) { E2_Callback *hdata1 = MALLOCATE (E2_Callback); CHECKALLOCATEDWARN (hdata1, ); hdata1->callback = _e2_pane_backward_hover; hdata1->data = NULL; E2_Callback *hdata2 = MALLOCATE (E2_Callback); CHECKALLOCATEDWARN (hdata2, ); hdata2->callback = _e2_pane_forward_hover; hdata2->data = NULL; //all name-strings must be freeable E2_Action actions[] = { {g_strconcat(_A(14),".",_A(73),NULL),_e2_pane_refresh_lists,FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL}, {g_strconcat(_A(14),".",_A(74),NULL),e2_filelist_enable_refresh_action,FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL}, {g_strconcat(_A(14),".",_A(75),NULL),e2_filelist_disable_refresh_action,FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL}, {g_strconcat(_A(14),".",_A(95),NULL),_e2_pane_activate_other_action,FALSE,E2_ACTION_TYPE_ITEM,0,NULL,NULL}, {g_strconcat(_A(14),".",_A(98),NULL),_e2_pane_sync, FALSE,E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(13),".",_A(25),NULL),_e2_pane_filters_show, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, //CHECKME name //c.f. panes.switch doesn't choose a specific pane, pane.focus doesn't change active pane {g_strconcat(_A(13),".",_A(29), NULL),_e2_pane_activate, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(13),".",_A(47),NULL),_e2_pane_focus, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_TOOLBAR, //CHECKME exclusions NULL, NULL}, {g_strconcat(_A(8),".",_A(27),NULL), NULL, TRUE, E2_ACTION_TYPE_HISTORY, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, {g_strconcat(_A(13),".",_A(34),NULL),_e2_pane_clear_goto_history, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, {g_strconcat(_A(13),".",_A(49),NULL),_e2_pane_go_back, TRUE, E2_ACTION_TYPE_HOVER, E2_ACTION_EXCLUDE_TOGGLE, hdata1, NULL}, {g_strconcat(_A(13),".",_A(50),NULL),_e2_pane_go_forward, TRUE, E2_ACTION_TYPE_HOVER, E2_ACTION_EXCLUDE_TOGGLE, hdata2, NULL}, {g_strconcat(_A(13),".",_A(51),NULL),_e2_pane_go_up, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, {g_strconcat (_A(13),".",_A(59),NULL),_e2_pane_mirror, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, {g_strconcat(_A(13),".",_A(64),NULL),_e2_pane_goto_choice, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, {g_strconcat(_A(13),".",_A(127),NULL),_e2_pane_goto, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, {g_strconcat(_A(18),".",_A(64),NULL),_e2_pane_goto_trash, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_TOGGLE, NULL, NULL}, //CHECKME ok with toggles in pane 1 & 2 ? {g_strconcat(_A(13),".",_A(84),NULL),_e2_pane_toggle_hidden,TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(13),".",_A(85),NULL),e2_context_menu_show_menu_action, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_TOOLBAR, NULL, NULL}, #ifdef E2_TREEDIALOG {g_strconcat(_A(13),".",_A(103),NULL),e2_tree_dialog_show_action,TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, #endif #ifdef E2_VFS {g_strconcat(_A(13),".",_A(122),NULL),_e2_pane_select_space, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, #endif {g_strconcat(_A(5),".",_A(34),NULL),_e2_pane_clear_dirline_history, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(7),".",_A(52),NULL),_e2_pane_focus_bottom, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(7),".",_A(53),NULL),_e2_pane_focus_top, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(7),".",_A(57),NULL),_e2_pane_invert_all, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(7),".",_A(101),NULL),_e2_pane_select_all, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(7),".",_A(102),NULL),_e2_pane_invert_current, TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, {g_strconcat(_A(7),".",_A(80),NULL),_e2_pane_select_extension,TRUE, E2_ACTION_TYPE_ITEM, 0, NULL, NULL}, //CHECKME which exclusions for these sort-actions ? {g_strconcat(_A(7),".",_A(91),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (FILENAME), NULL}, {g_strconcat(_A(7),".",_A(88),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (EXTENSION), NULL}, {g_strconcat(_A(7),".",_A(93),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (SIZE), NULL}, {g_strconcat(_A(7),".",_A(92),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (PERM), NULL}, {g_strconcat(_A(7),".",_A(94),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (OWNER), NULL}, {g_strconcat(_A(7),".",_A(89),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (GROUP), NULL}, {g_strconcat(_A(7),".",_A(90),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (MODIFIED), NULL}, {g_strconcat(_A(7),".",_A(86),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (ACCESSED), NULL}, {g_strconcat(_A(7),".",_A(87),NULL),_e2_pane_sort,TRUE,E2_ACTION_TYPE_ITEM, 0, GINT_TO_POINTER (CHANGED), NULL}, }; guint i, count = sizeof (actions)/sizeof (E2_Action); for (i = 0; i < count; i++) e2_action_register (&actions[i]); } /** @brief register pane-related config options Panebar default contents are set here, too @param num integer pane number (1 or 2) @return */ void e2_pane_options_register (gint num) { gint i; gchar *option_name, *group_name, *desc, *tip, *grey_me; E2_OptionFlags flags; //position these here to get paneN before paneN bar option_name = g_strdup_printf ("pane%d-use-startup-dir", num); group_name = g_strconcat ((num == 1) ? _C(29) :_C(31), ":", _C(38), NULL); // _("paneN:startup" e2_option_bool_register (option_name, group_name, _("At startup, show a specific directory in this pane"), _("This causes the directory named below, instead of the last-opened directory, to be shown at session start"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEGROUP); gchar *dep = option_name; option_name = g_strconcat (option_name,"-startup-dir", NULL); desc = g_strdup_printf (_("pane %d startup directory:"), num); e2_option_str_register (option_name, group_name, desc, _("This is the directory to show in this pane, at session start"), dep, (num == 1) ? "~" : G_DIR_SEPARATOR_S, //pane1 default = home, pane2 default = root directory E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC); group_name = g_strconcat ((num == 1) ? _C(29) : _C(31), ":", _C(26), NULL); // _("paneN:misc" if (num == 1) { e2_option_bool_register ("pane1-uses-other", group_name, _("this pane is like pane 2"), g_strdup_printf ( _("This makes pane %d options (other than toolbar placement and content) apply to pane %d"), 2,1), "!pane2-uses-other", FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDPANES); } else { e2_option_bool_register ("pane2-uses-other", group_name, _("this pane is like pane 1"), g_strdup_printf ( _("This makes pane %d options (other than toolbar placement and content) apply to pane %d"), 1,2), "!pane1-uses-other", FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDPANES); } group_name = g_strconcat ((num == 1) ? _C(29) : _C(31), ":", _C(3), NULL); // _("paneN:columns" for (i = 0; i < MAX_COLUMNS; i++) { option_name = g_strdup_printf ("pane%d-show-column%d", num, i); desc = g_strdup_printf (_("show %s column"), gettext(e2_all_columns[i].title)); tip = g_strdup_printf (_("This causes the the named column to be displayed in pane %d (duh ...)"), num); flags = E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDPANES; if (i > 0) grey_me = (num == 1) ? "!pane1-uses-other" : "!pane2-uses-other"; else { flags |= E2_OPTION_FLAG_FREEGROUP; //disable hiding of col 0 = filename grey_me = g_strconcat ("!",option_name,NULL); //a 'depends' entry = ! disables any change } e2_option_bool_register (option_name, group_name, desc, tip, grey_me, TRUE, flags); } //this needs to be after at least one non-tree option, so that the //first-registered option is non-tree and dialog scrolling is setup properly e2_toolbar_panebar_register (num); //setup the pane bar if (num == 1) e2_toolbar_options_register (E2_BAR_PANE1); //set general toolbar options else e2_toolbar_options_register (E2_BAR_PANE2); }