/* $Id$ Copyright (C) 2009-2010 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 plugins/e2p_selmatch.c @brief plugin for selecting active[-pane items which are selected in inactive pane */ #include "emelfm2.h" #include "e2_plugins.h" #include "e2_fileview.h" #include "e2_filelist.h" static void _e2p_selsame_reselect (GtkTreePath *tp, GtkTreeSelection *sel) { gtk_tree_selection_select_path (sel, tp); gtk_tree_path_free (tp); } /** @brief iterate over active pane file list to check for matches @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_select_same (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt; ViewInfo *selview; GtkTreeModel *models; GtkTreeIter iters; rt = e2_pane_get_runtime (from, art->data, NULL); selview = (ViewInfo *)rt; WAIT_FOR_REFRESH(selview) models = selview->model; if (gtk_tree_model_get_iter_first (models, &iters)) { //it's not empty ViewInfo *othview; GtkTreeModel *modelo; GtkTreeSelection *sel; GList *selpaths, *rowpath; GHashTable *selitems; FileInfo *info; gboolean fullmatch, forward; const gchar *seps; e2_filelist_disable_refresh (); othview = (rt == curr_pane) ? &other_pane->view : &curr_pane->view; WAIT_FOR_REFRESH(othview) selpaths = gtk_tree_selection_get_selected_rows (othview->selection, &modelo); if (selpaths == NULL) { e2_filelist_enable_refresh (); return FALSE; } // art->action->data is NULL for full name-scan, non-NULL for partial fullmatch = (art->action->data == NULL); if (fullmatch) { //warning prevention seps = NULL; forward = TRUE; } else { seps = e2_option_str_get ("selmatch-separators"); if (seps != NULL && *seps == '\0') { fullmatch = TRUE; forward = TRUE; } else forward = e2_option_bool_get ("selmatch-start"); } // Log the selected items, for quick lookup selitems = (fullmatch) ? g_hash_table_new (g_str_hash, g_str_equal): g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (rowpath = selpaths; rowpath != NULL; rowpath = rowpath->next) { GtkTreePath *path; GtkTreeIter itero; path = (GtkTreePath *) rowpath->data; if (gtk_tree_model_get_iter (modelo, &itero, path)) { gtk_tree_model_get (modelo, &itero, FINFO, &info, -1); if (fullmatch) g_hash_table_insert (selitems, info->filename, info); //no key dup, info must persist else { gchar c; gchar *target; if (forward) { for (target = info->filename; (c = *target) != '\0'; target++) { if (strchr (seps, c) != NULL) //assumes no UTF-8 chars { target = g_strndup (info->filename, target - info->filename); g_hash_table_insert (selitems, target, info); break; } } if (c == '\0') g_hash_table_insert (selitems, g_strdup (info->filename), info); } else { for (target = info->filename + strlen (info->filename) - 1 ; target >= info->filename; target--) { if (strchr (seps, *target) != NULL) //assumes no UTF-8 chars { target = g_strndup (info->filename, target - info->filename); g_hash_table_insert (selitems, target, info); break; } } if (target < info->filename) g_hash_table_insert (selitems, g_strdup (info->filename), info); } } } gtk_tree_path_free (path); } g_list_free (selpaths); sel = selview->selection; gtk_tree_selection_unselect_all (sel); //start with clean slate do { gboolean partial; gchar *scan; gtk_tree_model_get (models, &iters, FINFO, &info, -1); //We only check for name, ignore other statbuf parameters partial = FALSE; scan = NULL; if (!fullmatch) { gchar c; if (forward) { for (scan = info->filename; (c = *scan) != '\0'; scan++) { if (strchr (seps, c) != NULL) //assumes no UTF-8 chars, too bad if different sep from other pane { partial = TRUE; break; } } } else { for (scan = info->filename + strlen (info->filename) - 1 ; scan >= info->filename; scan--) { c = *scan; if (strchr (seps, c) != NULL) //assumes no UTF-8 chars { partial = TRUE; break; } } } } if (partial) scan = g_strndup (info->filename, scan - info->filename); else scan = info->filename; if (g_hash_table_lookup (selitems, scan) != NULL) gtk_tree_selection_select_iter (sel, &iters); if (partial) g_free (scan); } while (gtk_tree_model_iter_next (models, &iters)); g_hash_table_destroy (selitems); //ensure some part of selection is visible selpaths = gtk_tree_selection_get_selected_rows (sel, NULL); if (selpaths != NULL) { printd (DEBUG, "scroll to show selection"); GtkTreePath *tpath = gtk_tree_path_copy ((GtkTreePath *)selpaths->data); //this kills other selections gtk_tree_view_set_cursor (GTK_TREE_VIEW (selview->treeview), tpath, NULL, FALSE); //so back again, and cleanup g_list_foreach (selpaths, (GFunc)_e2p_selsame_reselect, sel); g_list_free (selpaths); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (selview->treeview), tpath, NULL, TRUE, 0.382, 0.0); gtk_tree_path_free (tpath); } e2_filelist_enable_refresh (); return TRUE; } return FALSE; } //aname must be confined to this module static gchar *aname, *aname2; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "selmatch" aname = _("selmatch"); aname2 = _("selmatchpart"); p->signature = ANAME VERSION; p->menu_name = _("_Select same"); p->description = ""; p->icon = "plugin_"ANAME E2ICONTB; //prepend path if appropriate //child UI data gchar *sig1 = "0-"ANAME VERSION; gchar *label1 = _("_Whole"); // gchar *icon1 = ""; //no icon "plugin_"ANAME E2ICONTB; //use icon file pathname if appropriate gchar *tip1 = _("Select items whose whole name matches a selected item in the other pane"); gchar *sig2 = "1-"ANAME VERSION; gchar *label2 = _("_Partial"); // gchar *icon2 = ""; gchar *tip2 = _("Select items whose name partially matches a selected item in the other pane"); if (p->action == NULL) { gboolean retval; Plugin *pc = e2_plugins_create_child (p); if (pc != NULL) { //for reconciling with config treestore data, signatures must reflect //0-based index of each child's order in the list of children pc->signature = sig1; //begin with "n-", n=0,1, ... //these will generallly be discarded in favour of config treestore data //meaning that any non-constant string will leak pc->menu_name = label1; //or whatever // pc->description = _("Copy selected item(s), with displayed progress details"); pc->description = tip1; //or whatever // pc->icon = "plugin_copy"E2ICONTB; //use icon file pathname if appropriate //normally don't free name string here E2_Action plugact = {g_strconcat (_A(7),".",aname,NULL),_e2p_select_same,FALSE,E2_ACTION_TYPE_ITEM,0,NULL,NULL}; // not _A(6) ! pc->action = e2_plugins_action_register (&plugact); retval = (pc->action != NULL); if (!retval) g_free (plugact.name); } else retval = FALSE; //this will not be used, but MUST be set to allow checking whether to show //the item in the context menu if (retval) p->action = pc->action; pc = e2_plugins_create_child (p); if (pc != NULL) { pc->signature = sig2; //for reconciling with config treestore data pc->menu_name = label2; pc->description = tip2; // pc->icon = "plugin_copy"E2ICONTB; //use icon file pathname if appropriate //don't free name string here E2_Action plugact = {g_strconcat (_A(7),".",aname2,NULL),_e2p_select_same,FALSE,E2_ACTION_TYPE_ITEM,0,GINT_TO_POINTER(1),NULL}; // not _A(6) ! pc->action = e2_plugins_action_register (&plugact); retval = (pc->action != NULL); } else retval = FALSE; if (retval) { E2_OptionSet *set; E2_OptionSetupExtra ex; gchar *group = g_strconcat(_C(34),".",_C(27),":",aname2,NULL); //_("plugins.options:selmatchpart" memset (&ex, 0, sizeof (E2_OptionSetupExtra)); ex.exbool = TRUE; set = e2_plugins_option_register (E2_OPTION_TYPE_BOOL, "selmatch-start", group, _("match to first separator"), _("If enabled, name matching stops at the first instance of any specified separator, otherwise, at the last instance"), NULL, &ex, E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_ADVANCED); //because plugins are loaded after config data, config options need to //get any data from unknown-options data e2_option_transient_value_get (set); ex.exstr = "."; set = e2_plugins_option_register (E2_OPTION_TYPE_STR, "selmatch-separators", group, _("separator character(s)"), _("String comprising all chars considered to be a 'separator'"), NULL, &ex, E2_OPTION_FLAG_ADVANCED); e2_option_transient_value_get (set); } if (retval && (p->action == NULL)) p->action = pc->action; return retval; } else //setup children UI data for pushing into a config dialog { E2_Sextet *uidata; uidata = e2_utils_sextet_new (); p->child_list = g_list_append (p->child_list, uidata); uidata->a = label1; uidata->b = ""; //no icon uidata->c = tip1; uidata->d = sig1; uidata = e2_utils_sextet_new (); p->child_list = g_list_append (p->child_list, uidata); uidata->a = label2; uidata->b = ""; uidata->c = tip2; uidata->d = sig2; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(7),".",aname,NULL); gboolean ret1 = e2_plugins_action_unregister (action_name); g_free (action_name); action_name = g_strconcat (_A(7),".",aname2,NULL); gboolean ret2 = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret1 && ret2) { ret1 = ret1 && e2_plugins_option_unregister ("selmatch-start"); ret2 = ret2 && e2_plugins_option_unregister ("selmatch-separators"); } return (ret1 && ret2); }