/* $Id$ Copyright (C) 2005-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_rename.c @brief file-rename plugin This file contains functions related to creation of a file-rename dialog, and execution of a find & rename task in accord with options selected in that dialog. */ /* TODO FIXME's hard = detect end of shell command, and update start/stop/help btn sensitivity accordingly share code with the find plugin ? */ #include "emelfm2.h" #include #include #include #include #include "e2p_rename.h" #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_filelist.h" static gboolean scanaborted = FALSE; //FIXME make it auto variable static gboolean flags[MAX_FLAGS]; //session-cache for toggle values static GList *dir_history; static GList *pattern_history; static GList *newpattern_history; //counter macros in replacement name pattern #define E2_COUNTER_ENABLED #ifdef E2_COUNTER_ENABLED #define MAX_COUNTERS 4 //enum { CINDEX, enum { CLEN, CSTART, CWIDTH, CCOLSCOUNT }; static guint countercount; static guint counterdata [MAX_COUNTERS][CCOLSCOUNT]; #endif static gboolean _e2p_renameQ (E2_ActionTaskData *qed); /*********************/ /***** utilities *****/ /*********************/ /** @brief set specified flag to T/F The relevant array value is set @param f enumerated value of flag to be set @param value new value for the flag @param rt ptr to dialog data struct @return */ static void _e2p_ren_set_flag (renflag_t f, gboolean value) //, E2_RenDialogRuntime *rt) { if (f < MAX_FLAGS) // rt-> flags [(gint) f] = value; } /** @brief return the value of a specified flag @param f enumerated value of flag to be interrogated @param rt @return flag value, T/F, or FALSE if the value is not recognised */ static gboolean _e2p_ren_get_flag (renflag_t f) //, E2_RenDialogRuntime *rt) { if (f < MAX_FLAGS) return (//rt-> flags [(gint) f]); else return FALSE; } /* * @brief set all flags to FALSE @param rt ptr to dialog data struct @return */ /*UNUSED static void _e2p_ren_reset_flags (E2_RenDialogRuntime *rt) { gint i; for (i = 0; i < MAX_FLAGS; i++) flags[i] = FALSE; } */ #ifdef E2_COUNTER_ENABLED /** @brief setup counter information for replacement name Up to MAX_COUNTERS counter macros can be used in @a newtemplate. For each counter, if "i" is provided it is the number which starts that series For each counter, if "w" is provided it is the minumum width of each count string in that series, which will be padded with leading 0's as required @param newtemplate replace pattern string, maybe with %c[i[,w]] @param rt pointer to dialog data struct @return TRUE if one or more counter macros were processed */ //FIXME make this work with replacement name, instead of replacement pattern static gboolean _e2p_ren_parse_counters (const gchar *newtemplate, E2_RenDialogRuntime *rt) { guint indx = 0; //array index gulong start, width; const gchar *c, *s; gchar *endptr; rt->modeflags &= ~E2PR_COUNTER; s = newtemplate; while ((s = strstr (s, "%c")) != NULL) { rt->modeflags |= E2PR_COUNTER; //signal we have counter(s) c = s; s += 2; //skip ascii macro signature start = strtoul (s, &endptr, 10); if (endptr == s) start = 1; //no number provided else s = endptr; if (*s == ',') //no whitespace check { s++; width = strtoul (s, &endptr, 10); if (endptr == s) width = 1; //no number provided else s = endptr; } else width = 1; //store counter data for later use // counterdata [indx][CINDEX] = c - newtemplate; we need to scan each replacement name counterdata [indx][CLEN] = s - c; counterdata [indx][CSTART] = (guint) start; counterdata [indx][CWIDTH] = (guint) width; if (++indx == MAX_COUNTERS) break; } countercount = indx; return (rt->modeflags & E2PR_COUNTER); } #endif /** @brief parse replacement pattern a regex-matched file Carve up new tempate into chunks separated by \1, \2 .... \ and store copies in rt->chunks[1] .... chunks[E2_HUNK_LIMIT-1] Chunk[0] used for pointerised chunks count (=last index, since chunk[0] is not used). Sets rt->modeflags E2PR_NEWALL, and chunk[1] to @a newtemplate if that has no \1 ... Sets rt->modeflags E2PR_WHOLE if "\0" detected. Sets rt->modeflags E2PR_COUNTER if "%c" detected. @param newtemplate replace pattern string maybe with regex \1 [\2 ...] or %c['s] @param rt pointer to dialog data struct @return */ static void _e2p_ren_parse_regexpattern (const gchar *newtemplate, E2_RenDialogRuntime *rt) { gint thiscount = 0; gchar *s, *s1, *s2, *s3; s = s1 = g_strdup (newtemplate); //work with a copy so original is not clobbered rt->modeflags = E2PR_NORMAL; while ((s2=strchr (s1, '\\')) != NULL) //always ascii { s2++; if (*s2=='\\') { //ignore any escaped "\" s1=s2+1; continue; } //log but don't store any "\0" if (*s2 == '0') { rt->modeflags = E2PR_WHOLE; s1=s2+1; continue; } s3 = s2; while (*s3 >= '0' && *s3 <= '9') //atoi() doesn't handle errors s3++; if (s3 > s2) { gchar r = *s3; *s3 = '\0'; thiscount = atoi (s2); *(s2-1) = '\0'; if (thiscount > 0 && thiscount < E2_CHUNK_LIMIT) //don't record \0 rt->chunks[thiscount] = g_strdup (s1); *s3 = r; } s1 = s3; } if (thiscount == 0) { rt->modeflags |= E2PR_NEWALL; rt->chunks[0] = GINT_TO_POINTER (1); rt->chunks[1] = s; //use whole pattern in case there was a "\0" } else { //get the tail of the pattern too rt->chunks[0] = GINT_TO_POINTER (thiscount+1); //this assumes that the last \N is the highest number backref ? rt->chunks[thiscount+1] = g_strdup (s1); g_free (s); } #ifdef E2_COUNTER_ENABLED _e2p_ren_parse_counters (newtemplate, rt); #endif } /** @brief parse replacement pattern for a wildcard matched file Carve up new tempate into chunks separated by * or ? and store copies in rt->chunks[1] .... chunks[E2_HUNK_LIMIT-1]. Some may be "". Chunk[0] used for pointerised chunks count (=last index, since chunk[0] is not used). Sets rt->modeflags E2PR_NEWALL, and chunk[1] to @a newtemplate if that has no "*" or "?" Sets rt->modeflags E2PR_WHOLE if "\0" detected. Sets rt->modeflags E2PR_COUNTER, and parses counter string(s), if any "%c" detected. @param newtemplate replace pattern string, maybe with one or more *, ? or %c @param rt pointer to dialog data struct @return */ static void _e2p_ren_parse_wildpattern (const gchar *newtemplate, E2_RenDialogRuntime *rt) { if ( (strchr (newtemplate, '?') == NULL) && (strchr (newtemplate, '*') == NULL) ) //always ascii { //there's nothing wild about it ... rt->modeflags = E2PR_NEWALL; rt->chunks[0] = GINT_TO_POINTER (1); rt->chunks[1] = g_strdup (newtemplate); } else { rt->modeflags = E2PR_NORMAL; gint thiscount = 1; //bypass chunk[0] gchar **split = g_strsplit_set (newtemplate, "*?", E2_CHUNK_LIMIT); gchar **tmp = split; while (*tmp != NULL && (thiscount < E2_CHUNK_LIMIT)) { rt->chunks[thiscount++] = *tmp; tmp++; } while (*tmp != NULL) { //FIXME leftovers should be rejoined into last chunk g_free (*tmp); tmp++; } rt->chunks[0] = GINT_TO_POINTER (--thiscount); g_free (split); } if (strstr (newtemplate, "\\0") != NULL) rt->modeflags |= E2PR_WHOLE; #ifdef E2_COUNTER_ENABLED _e2p_ren_parse_counters (newtemplate, rt); #endif } #ifdef E2_COUNTER_ENABLED /** @brief update @a newname with current counter values @param newname string with occurrence(s) of counter macro %c[n[,m]] @param rt pointer to dialog data struct @return replacement name, newly-allocated string in same encoding as original */ static gchar *_e2p_ren_count_replace (gchar *newname, E2_RenDialogRuntime *rt) { // gint adj = 0; guint i; gchar *c, *s, *p, *expanded = g_strdup (newname); gchar numfmt[20]; numfmt[0] = '%'; //use each stored start, width, macro string from when parsed for (i = 0; i < countercount; i++) { //get count string for current value and width if (counterdata [i][CWIDTH] > 1) g_snprintf (numfmt+1, sizeof(numfmt)-1, "0%uu", counterdata [i][CWIDTH]); else g_strlcpy (numfmt+1, "u", sizeof(numfmt)-1); c = g_strdup_printf (numfmt, counterdata [i][CSTART]); //substitute count string for counter macro p = strstr (expanded, "%c"); if (p == NULL) break; //oops ! // p = (expanded + counterdata [i][CINDEX] + adj); *p = '\0'; s = (p + counterdata [i][CLEN]); p = expanded; expanded = g_strconcat (p, c, s, NULL); //updates // adj += strlen (c) - counterdata [i][CLEN]; counterdata [i][CSTART]++; //cleanups g_free (c); g_free (p); } return expanded; } #endif /** @brief determine replacement name for a file For same-case renames, new tempate chunks[] will have been populated before this is called @param oldtemplate search pattern string (utf8) possibly with 'extended' regex as appropriate, or NULL @param oldpath absolute path (utf8 string) of item to be changed @param rt pointer to dialog data struct @return replacement name, newly allocated utf8 string */ static gchar *_e2p_ren_name_replace (gchar *oldtemplate, gchar *oldpath, E2_RenDialogRuntime *rt) { gchar *s1, *result; gchar *newbase = g_path_get_basename (oldpath); if (rt->modeflags & E2PR_NEWALL) //no wild or regex to interpret in replacement template { #ifdef E2_COUNTER_ENABLED if ((rt->modeflags & (E2PR_COUNTER | E2PR_WHOLE)) == (E2PR_COUNTER | E2PR_WHOLE)) { //get replacement pattern with counters substituted s1 = _e2p_ren_count_replace (rt->chunks[1], rt); result = e2_utils_str_replace (s1, "\\0", newbase); //handle "\0" } else if (rt->modeflags & E2PR_WHOLE) result = e2_utils_str_replace (rt->chunks[1], "\\0", newbase); else //no \0 in pattern result = (rt->modeflags & E2PR_COUNTER) ? //if counter[s] required, use the template, ignore the found filename _e2p_ren_count_replace (rt->chunks[1], rt) : g_strdup (newbase); #else result = (rt->modeflags & E2PR_WHOLE) ? e2_utils_str_replace (rt->chunks[1], "\\0", newbase): //handle any "\0" g_strdup (newbase); #endif } else { //not just a case-change regex_t oldcompiled; // gint cflags = (ext_regex) ? REG_EXTENDED : 0 ; //not REG_ICASE if (!regcomp (&oldcompiled, oldtemplate, REG_EXTENDED)) { //need this many data spots gint matchcount = (gint)oldcompiled.re_nsub + 1; regmatch_t matcholdptr [matchcount]; gint error; gint eflags = 0; //not REG_NOTBOL | REG_NOTEOL; if ((error = regexec (&oldcompiled, newbase, matchcount, matcholdptr, eflags)) != 0) {//handle nomatch //DEBUG size_t len = regerror (error, &oldcompiled, NULL, 0); gchar local[len+2]; regerror (error, &oldcompiled, local, len+2); printd (DEBUG, local); e2_output_print_error (local, FALSE); } //transform newtemplate into new name, with corresponding matches from oldname gchar buf[NAME_MAX+2]; result = g_strdup (""); regmatch_t rx; gint i, j, L; //ignore chunk[0] j=1; //ignore chunks past what we asked for gint newwilds = GPOINTER_TO_INT (rt->chunks[0]); if (newwilds < matchcount) matchcount = newwilds; //ignore the initial 'whole pattern' match for (i = 1; i < matchcount; i++) { //get the matched substring rx=matcholdptr[i]; if (rx.rm_so > -1) { //the substring was actually used in the match //get a null-terminated copy s1 = newbase + rx.rm_so; L = rx.rm_eo - rx.rm_so; if (L > NAME_MAX+1) L = NAME_MAX+1; //prevent buffer overflow memcpy (buf, s1, L); buf[L] = '\0'; } else { //FIXME continue; } //progressively join up fixed and variable for ( ; j <= i ; j++) { if (rt->chunks[j] == NULL || *(rt->chunks[j]) == '\0') continue; s1=result; result = g_strconcat (s1, rt->chunks[j], NULL); g_free (s1); } s1=result; result = g_strconcat (s1, buf, NULL); g_free (s1); } //and add any left-over chunks if ((newwilds <= (gint)oldcompiled.re_nsub + 1) && j < E2_CHUNK_LIMIT && rt->chunks[j] != NULL && *(rt->chunks[j]) != '\0') { s1=result; result = g_strconcat (s1, rt->chunks[j], NULL); g_free (s1); } /* for (;jchunks[j] == NULL || *(rt->chunks[j]) == '\0') continue; s1=result; result = g_strconcat (s1, rt->chunks[j], NULL); g_free (s1); } */ //cleanup regfree (&oldcompiled); } else { //handle error - eg no regex in expression result = g_strdup (newbase); } #ifdef E2_COUNTER_ENABLED if (rt->modeflags & E2PR_COUNTER) { s1 = result; result = _e2p_ren_count_replace (result, rt); g_free (s1); } #endif if (rt->modeflags & E2PR_WHOLE) { //handle all "\0" s1 = result; result = e2_utils_str_replace (result, "\\0", newbase); g_free (s1); } } if (rt->modeflags & E2PR_LOWER) { s1 = result; result = g_utf8_strdown (result, -1); g_free (s1); } else if (rt->modeflags & E2PR_UPPER) { s1 = result; result = g_utf8_strup (result, -1); g_free (s1); } g_free (newbase); return result; } /** @brief check whether @a name is a candidate for renaming @param name localised name string of candidate @param data pointer to rename data struct @return TRUE if @a name matches the pattern sought */ static gboolean _e2p_ren_match_name (gchar *name, E2P_RenameData *data) { if (data->flags & (E2PR_NORMAL | E2PR_WILD)) //non-regex search return (!fnmatch (data->pattern, name, 0)); else //regex search return (!regexec (data->compiled, name, 0, NULL, REG_NOTBOL)); } /** @brief when recursing, update matching items array This is a callback for a treewalk function. Name(s) (utf8) of matched item(s) are stored in an array Expects BGL to be closed @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param twdata pointer to tw data struct @return E2TW_CONTINUE always */ static E2_TwResult _e2p_ren_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2P_RenameData *twdata) { WAIT_FOR_EVENTS if (scanaborted) { scanaborted = FALSE; return E2TW_STOP; } gchar *s, *utf; switch (status) { case E2TW_DM: //directory, not opened due to different file system (reported upstream) case E2TW_DL: //directory, not opened due to tree-depth limit (reported upstream) case E2TW_DNR: //unreadable directory (for which, error is reported upstream) case E2TW_DRR: //directory now readable case E2TW_D: //directory case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file //the first call here provides the root dir for the search //distinguished by a trailing / s = strrchr (VPSTR (localpath), G_DIR_SEPARATOR); //really, there must be a / in the string, but test anyway ... s = (s == NULL) ? (gchar *)localpath : s+1; //s now at the name start, or after a trailing / from the root dir if ((ITEM_ISHIDDEN(s) && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) || *s == '\0' //this is the first call, with just the root dir ) break; if (_e2p_ren_match_name (s, twdata)) { utf = D_FILENAME_FROM_LOCALE (VPSTR(localpath)); //always get a copy g_ptr_array_add (twdata->candidates, (gpointer) utf); } break; // case E2TW_DP: //directory, finished // case E2TW_NS: //un-stattable item (for which, error is reported upstream) default: break; } return E2TW_CONTINUE; } /* * @brief when not recursing, update matching items array @param dirpath localised path of directory to scan for matching items @param data pointer to rename data struct @return */ /*static void _e2p_ren_dirscan (gchar *dirpath, E2P_RenameData *data) { gchar *msg, *joined, *utf; struct dirent *entry; struct dirent entrybuf; #ifdef E2_VFSTMP //this open/iterate/clode mechanism probably useless for vfs dir #endif DIR *dp = e2_fs_dir_open (dirpath E2_ERR_NONE()); if (dp == NULL) { utf = F_DISPLAYNAME_FROM_LOCALE (dirpath); msg = g_strdup_printf (_("Cannot open directory %s"), utf); F_FREE (utf, dirpath); e2_output_print_error (msg, TRUE); return; } while (!e2_fs_dir_read (dp, &entrybuf, &entry E2_ERR_NONE()) && entry != NULL) //FIXME vfs { if (ITEM_ISHIDDEN(entry->d_name) && (entry->d_name[1] == '\0' || !strcmp (entry->d_name, "..")) ) continue; if (_e2p_ren_match_name (entry->d_name, data)) { joined = e2_utils_strcat (dirpath, entry->d_name); utf = F_FILENAME_FROM_LOCALE (joined); //always get a copy g_ptr_array_add (data->candidates, (gpointer) utf); if (joined != utf) //conversion really happened g_free (joined); } } e2_fs_dir_close (dp E2_ERR_NONE()); } */ /** @brief perform rename task This is called only from within callbacks, BGL closed Then processes the results @param rt ptr to dialog data struct @return */ static void _e2p_ren_rename (E2_RenDialogRuntime *rt) { const gchar *old, *new; if (!_e2p_ren_get_flag (OLD_SEL_P)) //, rt)) { old = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->pattern))) #else GTK_ENTRY (GTK_BIN (rt->pattern)->child) #endif ); if (*old == '\0') { e2_output_print_error (_("No current name pattern is specified"), FALSE); return; } e2_list_update_history ((gchar *) old, &pattern_history, NULL, 20, FALSE); } else old = NULL; if (_e2p_ren_get_flag (NEW_THIS_P)) //, rt)) { new = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->newpattern))) #else GTK_ENTRY (GTK_BIN (rt->newpattern)->child) #endif ); if (*new == '\0') { e2_output_print_error (_("No replacement name pattern is specified"), FALSE); return; } //eliminate inappropriate replacement pattern entered when processing selected items //(but no check for regex backrefs, \0 is ok) if (_e2p_ren_get_flag (OLD_SEL_P) //, rt) && ( (strchr (new, '?') != NULL) || (strchr (new, '*') != NULL) ) ) //always ascii { //there's something wild about it ... e2_output_print_error (_("Replacement name pattern cannot have wildcard(s)"), FALSE); return; } e2_list_update_history ((gchar *) new, &newpattern_history, NULL, 20, FALSE); } else new = NULL; *rt->status = E2_TASK_RUNNING; //update dialog button sensitivities while we search gtk_widget_set_sensitive (rt->help_button, FALSE); gtk_widget_set_sensitive (rt->start_button, FALSE); gtk_widget_set_sensitive (rt->stop_button, TRUE); WAIT_FOR_EVENTS gchar *localroot; //ignore warning about uninitialized usage gchar *tmp2 = NULL; //assignment for complier-warning prevention only const gchar *startdir; regex_t compiled; E2P_RenameData data; #ifdef E2_VFS VPATH sdata; #endif //get paths/names of items to replace gboolean result; data.candidates = g_ptr_array_new (); if (_e2p_ren_get_flag (OLD_SEL_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = curr_view->spacedata; #endif //to distinguish items, get quoted names then remove quotes gchar *macro = (_e2p_ren_get_flag (SEARCH_OTHER_P)) ? "%F" : "%p"; //%F==%P gchar *itempaths = e2_utils_expand_macros (macro, NULL); if (itempaths != NULL) { gchar *s, *p = itempaths; while (*p != '\0') { s = strchr (p, '"'); p = s+1; s = strchr (p, '"'); *s = '\0'; g_ptr_array_add (data.candidates, g_strdup (p)); p = s+1; } g_free (itempaths); } result = (data.candidates->len > 0); } else //not renaming selected items { result = TRUE; //get search-start dir, with a trailing /, among other reasons //to signal to the tw cb which is the search-root to be ignored if (_e2p_ren_get_flag (SEARCH_CURRENT_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = curr_view->spacedata; #endif startdir = curr_view->dir; } else if (_e2p_ren_get_flag (SEARCH_OTHER_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = other_view->spacedata; #endif startdir = other_view->dir; } else if (_e2p_ren_get_flag (SEARCH_ALL_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = curr_view->spacedata; #endif startdir = G_DIR_SEPARATOR_S; //root of filesystem tree FIXME vfs _e2p_ren_set_flag (SEARCH_SUBDIRS_P, TRUE); //, rt); } else //search in specified dir { #ifdef E2_VFS # ifdef E2_VFSTMP FIXME get relevant space # endif sdata.spacedata = curr_view->spacedata; #endif startdir = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->directory))) #else GTK_ENTRY (GTK_BIN (rt->directory)->child) #endif ); startdir = e2_utils_pass_whitespace ((gchar *)startdir); if (startdir == NULL) { result = FALSE; } else { if (*startdir != G_DIR_SEPARATOR) { tmp2 = e2_utils_strcat (curr_view->dir, startdir); gtk_entry_set_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->directory))), #else GTK_ENTRY (GTK_BIN (rt->directory)->child), #endif tmp2); g_free (tmp2); startdir = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->directory))) #else GTK_ENTRY (GTK_BIN (rt->directory)->child) #endif ); } e2_list_update_history (startdir, &dir_history, NULL, 20, FALSE); } } if (result) { if (_e2p_ren_get_flag (OLD_WILD_P)) //, rt)) //exact or wildcard match { data.flags = E2PR_WILD; data.pattern = old; } else //regex match { if (regcomp (&compiled, old, REG_EXTENDED)) { result = FALSE; tmp2 = g_strdup_printf (_("Error in regular expression %s"), old); e2_output_print_error (tmp2, TRUE); } else { data.flags = E2PR_REGEX; data.compiled = &compiled; } } if (result) { //accumulate array of matching items, path-by-path localroot = D_FILENAME_TO_LOCALE (startdir); //always copied //add trailing / if need be if ((tmp2 = strrchr (localroot, G_DIR_SEPARATOR)) == NULL || tmp2 != (localroot + strlen (localroot) - 1)) { tmp2 = localroot; localroot = e2_utils_strcat (localroot, G_DIR_SEPARATOR_S); g_free (tmp2); } #ifdef E2_VFS sdata.path = localroot; #endif if (_e2p_ren_get_flag (SEARCH_SUBDIRS_P)) //, rt)) { data.flags |= E2PR_RECURSE; e2_dialog_set_cursor (rt->dialog, GDK_WATCH); gdk_threads_leave (); #ifdef E2_VFS result = e2_fs_tw (&sdata, _e2p_ren_twcb, &data, -1, #else result = e2_fs_tw (localroot, _e2p_ren_twcb, &data, -1, #endif E2TW_PHYS E2_ERR_NONE()); gdk_threads_enter (); e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); } else { gdk_threads_leave (); #ifdef E2_VFS result = e2_fs_tw (&sdata, _e2p_ren_twcb, &data, 1, #else result = e2_fs_tw (localroot, _e2p_ren_twcb, &data, 1, #endif E2TW_QT | E2TW_PHYS E2_ERR_NONE()); gdk_threads_enter (); } g_free (localroot); //restore real flag state if (_e2p_ren_get_flag (SEARCH_ALL_P)) //, rt)) { gboolean oldstate = #ifdef USE_GTK2_14 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->recurse_button)); #else GTK_TOGGLE_BUTTON (rt->recurse_button)->active; #endif _e2p_ren_set_flag (SEARCH_SUBDIRS_P, oldstate); //, rt); } if (data.candidates->len == 0) { //couldn't find anything tmp2 = g_strdup_printf (_("Cannot find anything which matches %s"), old); e2_output_print_error (tmp2, TRUE); result = FALSE; } } } } if (!result) { gtk_widget_set_sensitive (rt->help_button, TRUE); gtk_widget_set_sensitive (rt->start_button, TRUE); gtk_widget_set_sensitive (rt->stop_button, FALSE); //#ifndef USE_GLIB2_22 g_ptr_array_foreach (data.candidates, (GFunc)g_free, NULL); //#endif g_ptr_array_free (data.candidates, TRUE); WAIT_FOR_EVENTS *rt->status = E2_TASK_PAUSED; return; } gboolean multi = (data.candidates->len > 1); g_ptr_array_add (data.candidates, NULL); //null-terminate the array gchar **candidates, **thisone; candidates = thisone = (gchar **) data.candidates->pdata; g_ptr_array_free (data.candidates, FALSE); if (_e2p_ren_get_flag (NEW_THIS_P)) //*new != '\0' checked before { if (_e2p_ren_get_flag (OLD_WILD_P)) //, rt)) (data.flags == E2PR_WILD) { //wildcard or specific names used //adjust the find pattern, by replacing all '.', '*' and '?' //in the pattern with their extended regex equivalents gchar **split = g_strsplit (old, ".", -1); tmp2 = g_strjoinv ("\\.", split); g_strfreev (split); split = g_strsplit (tmp2, "?", -1); g_free (tmp2); tmp2 = g_strjoinv ("(.)", split); g_strfreev (split); split = g_strsplit (tmp2, "*", -1); g_free (tmp2); tmp2 = g_strjoinv ("(.*?)", split); //freeme later lazy-star to prevent greedy matching ? g_strfreev (split); // printd (DEBUG, "wildcard rename pattern is %s", pattern); //get the chunks of the replacement pattern _e2p_ren_parse_wildpattern (new, rt); } else if (_e2p_ren_get_flag (OLD_REGEX_P)) { //regex names (actually, paths) used //convert the name using the same regex as 'find' used tmp2 = (gchar *)old; //get the chunks of the replacement pattern _e2p_ren_parse_regexpattern (new, rt); } else //OLD_SEL_P //for this case, real tmp2 is set inside remame loop //tmp2 = NULL; //warning prevention only //for selected-item renaming \0 or %c can be in the pattern //cheap parsing, having already checked that new pattern does not have * or ? _e2p_ren_parse_wildpattern (new, rt); rt->modeflags |= E2PR_PATTERN; } else //we are _only_ changing case { rt->modeflags = E2PR_NEWALL; //prevent rename func from trying to parse tmp2 tmp2 = NULL; //don't need an old pattern } if (_e2p_ren_get_flag (OLD_SEL_P)) //, rt)) rt->modeflags |= E2PR_SEL; if (_e2p_ren_get_flag (NEW_LOWER_P)) //, rt)) rt->modeflags |= E2PR_LOWER; else if (_e2p_ren_get_flag (NEW_UPPER_P)) //, rt)) rt->modeflags |= E2PR_UPPER; //FIXME start an async task-thread DialogButtons choice; GtkWidget *dialog; if (_e2p_ren_get_flag (CONFIRM_P)) //, rt)) { //setup for repeated non-modal dialogs dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, "", _("confirm"), DUMMY_RESPONSE_CB, NULL); //set default button to 'no' if (multi) { e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL); e2_dialog_add_defined_button (dialog, &E2_BUTTON_CANCEL); } else e2_dialog_set_negative_response (dialog, GTK_RESPONSE_NO); E2_Button local_btn; //set no label e2_button_derive (&local_btn, &E2_BUTTON_NO, (multi) ? BTN_NO_SKIP : BTN_NO_CANCEL); local_btn.showflags |= E2_BTN_DEFAULT; e2_dialog_add_defined_button (dialog, &local_btn); local_btn = E2_BUTTON_YES; local_btn.label = _("_Rename"); local_btn.stock = GTK_STOCK_CONVERT; local_btn.showflags &= ~E2_BTN_DEFAULT; e2_dialog_add_defined_button (dialog, &local_btn); e2_dialog_setup (dialog, app.main_window); } else dialog = NULL; gboolean check = e2_option_bool_get ("confirm-overwrite"); e2_filelist_disable_refresh (); e2_dialog_set_cursor (rt->dialog, GDK_WATCH); //walk the vector of matching files while (*thisone != NULL) { gchar *base = g_path_get_basename (*thisone); gchar *dir = g_path_get_dirname (*thisone); //for other cases, tmp2 is set outside loop (maybe NULL) if ((rt->modeflags & (E2PR_SEL | E2PR_PATTERN)) == (E2PR_SEL | E2PR_PATTERN)) tmp2 = base; //get the replacement basename gchar *newbase = _e2p_ren_name_replace (tmp2, *thisone, rt); //escape any pango-annoying component of the filenames gchar *base_public = g_markup_escape_text (base, -1); gchar *newbase_public = g_markup_escape_text (newbase, -1); //ask, if the confirm option is is force, and the parent's stop btn not pressed if (_e2p_ren_get_flag (CONFIRM_P) && ! rt->abort) { choice = NO_TO_ALL; //default in case of abort gchar *prompt = g_strdup_printf ("%s\n%s\n%s\n%s\n%s %s", _("Rename"), base_public, _("to"), newbase_public, _("in"), dir); GtkWidget *label = g_object_get_data (G_OBJECT (dialog), "e2-dialog-label"); gtk_label_set_markup (GTK_LABEL (label), prompt); g_free (prompt); e2_dialog_run (dialog, NULL, 0); *rt->status = E2_TASK_PAUSED; choice = e2_dialog_wait (dialog, TRUE, TRUE, multi, TRUE); *rt->status = E2_TASK_RUNNING; gtk_widget_hide (dialog); //CHECKME if closed by user gtk_widget_grab_focus (rt->dialog); } else //no confirmation choice = OK; if (choice == OK) { gchar *newpath = g_build_filename (dir, newbase, NULL); gchar *tempname, *slocal, *dlocal = F_FILENAME_TO_LOCALE (newpath); gboolean success; #ifdef E2_VFS VPATH ddata = { dlocal, sdata.spacedata }; VPATH tdata; tdata.spacedata = sdata.spacedata; #endif //get o/w confirmation if that option is in force #ifdef E2_VFS if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (check && e2_fs_access2 (dlocal E2_ERR_NONE()) == 0) #endif { //maybe new name exists, or maybe it's just the old name with some different case gchar *old_name_lc = g_utf8_casefold (*thisone, -1); gchar *new_name_lc = g_utf8_casefold (newpath, -1); if (strcmp (old_name_lc, new_name_lc)) { //we may have just a case-difference that looks the same to the kernel //do interim name change to check, & also to ensure case-change happens slocal = F_FILENAME_TO_LOCALE (*thisone); tempname = e2_utils_get_tempname (slocal); #ifdef E2_VFS tdata.path = tempname; #endif gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS success = e2_task_backend_rename (&sdata, &tdata); #else success = e2_task_backend_rename (slocal, tempname); #endif gdk_threads_enter (); if (success) { #ifdef E2_VFS if (! e2_fs_access2 (&ddata E2_ERR_NONE())) #else if (! e2_fs_access2 (dlocal E2_ERR_NONE())) #endif { //the new name really does exist //revert old name and process normally gdk_threads_leave (); #ifdef E2_VFS e2_task_backend_rename (&tdata, &sdata); #else e2_task_backend_rename (tempname, slocal); #endif gdk_threads_enter (); } else { //the former detection was fake, just do the rest of the rename gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS e2_task_backend_rename (&tdata, &ddata); #else e2_task_backend_rename (tempname, dlocal); #endif gdk_threads_enter (); choice = CANCEL; } } else { gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS tdata.path = *thisone; e2_fs_error_simple (_("Cannot rename %s"), &tdata); #else e2_fs_error_simple (_("Cannot rename %s"), *thisone); #endif gdk_threads_enter (); choice = CANCEL; } F_FREE (slocal, *thisone); g_free (tempname); } /* else //we have a real difference { //FIXME duplication here gdk_threads_leave (); doit = e2_dialog_ow_check ( #ifdef E2_VFS # ifdef E2_VFSTMP &tdata??, # endif &ddata, #else *thisone, dlocal, #endif NOALL); gdk_threads_enter (); } */ g_free (old_name_lc); g_free (new_name_lc); } if (choice == OK) { gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS tdata.path = *thisone; success = e2_task_backend_rename (&tdata, &ddata); #else success = e2_task_backend_rename (*thisone, dlocal); #endif gdk_threads_enter (); if (success && !_e2p_ren_get_flag (CONFIRM_P)) //, rt)) { //we didn't ask already, so show what's done gchar *msg = g_strdup_printf ("%s %s %s %s %s %s", _("Renamed"), base_public, _("to"), newbase_public, _("in"), dir); e2_output_print (&app.tab, msg, NULL, TRUE, NULL); g_free (msg); } } g_free (newpath); F_FREE (dlocal, newpath); } g_free (base); g_free (dir); g_free (newbase); g_free (base_public); g_free (newbase_public); if (choice == NO_TO_ALL || rt->abort) { //confirm dialog or o/w dialog or main dialog stop btn pressed rt->abort = FALSE; //make sure this is now clear break; } thisone++; } //end of items loop if (GTK_IS_DIALOG(dialog)) //not explicitly closed by the user gtk_widget_destroy (dialog); *rt->status = E2_TASK_PAUSED; //we don't know what really needs refreshing ... if (_e2p_ren_get_flag (SEARCH_CURRENT_P)) //, rt)) e2_filelist_request_refresh (curr_view->dir, TRUE); else if (_e2p_ren_get_flag (SEARCH_OTHER_P)) //, rt)) e2_filelist_request_refresh (other_view->dir, TRUE); e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); e2_filelist_enable_refresh(); //cleanups if ((rt->modeflags & E2PR_PATTERN) && (data.flags == E2PR_WILD)) //new pattern with wildcards was constructed g_free (tmp2); g_strfreev (candidates); gint j; //[0] used for counter, = index of last chunk with contents gint m = GPOINTER_TO_INT (rt->chunks[0]); rt->chunks[0] = NULL; for (j = 1; j <= m; j++) { g_free (rt->chunks[j]); rt->chunks[j] = NULL; } rt->parsed = FALSE; //revert the buttons gtk_widget_set_sensitive (rt->help_button, TRUE); gtk_widget_set_sensitive (rt->start_button, TRUE); gtk_widget_set_sensitive (rt->stop_button, FALSE); gtk_window_present (GTK_WINDOW (rt->dialog)); /* gtk_widget_grab_focus ( #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->pattern)) #else GTK_BIN (rt->pattern)->child #endif ); */ } /*********************/ /***** callbacks *****/ /*********************/ #ifdef E2_RENCHOOSER /** @brief change search directory @param chooser the selection object @param rt pointer to dialog data @return */ static void _e2p_ren_choose_directory_cb (GtkFileChooser *chooser, E2_RenDialogRuntime *rt) { gchar *uri = gtk_file_chooser_get_uri (chooser); if (uri != NULL) { gchar *dirpath = g_filename_from_uri (uri, NULL, NULL); if (dirpath != NULL) { if (*dirpath != '\0') { gchar *opendir = F_FILENAME_FROM_LOCALE (dirpath); gtk_entry_set_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->directory))), #else GTK_ENTRY (GTK_BIN (rt->directory)->child), #endif opendir); F_FREE (opendir, dirpath); } g_free (dirpath); } g_free (uri); } } /** @brief idle callback to add file-chooser button to dialog This allows adding button in main-thread, which is needed to avoid crash @param rt pointer to dialog data struct @return FALSE to cancel idle */ static gboolean _e2p_ren_add_chooser (E2_RenDialogRuntime *rt) { const gchar *message = _("Choose directory"); rt->chooser_button = gtk_file_chooser_button_new (message, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (rt->chooser_button), TRUE); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (rt->chooser_button), rt->chooser_startdir); g_free (rt->chooser_startdir); rt->chooser_startdir = NULL; e2_widget_set_safetip (rt->chooser_button, message); g_signal_connect (G_OBJECT (rt->chooser_button), "current-folder-changed", G_CALLBACK (_e2p_ren_choose_directory_cb), rt); gtk_box_pack_end (GTK_BOX (rt->chooser_box), rt->chooser_button, FALSE, FALSE, 0); gboolean state = _e2p_ren_get_flag (SEARCH_THIS_P); gtk_widget_set_sensitive (rt->chooser_button, state); gdk_threads_enter (); gtk_widget_show (rt->chooser_button); gdk_threads_leave (); return FALSE; } #endif /** @brief toggle specified option flag @param button toggled button widget @param flagnum pointerized number of the flag to be toggled @return */ static void _e2p_ren_toggle_cb (GtkToggleButton *button, gpointer flagnum) { E2_RenDialogRuntime *rt = g_object_get_data (G_OBJECT (button), "e2-runtime"); //if this if this is during setup, before a widget is created ... #ifdef USE_GTK2_20 if (!gtk_widget_get_mapped (rt->dialog)) #else if (!GTK_WIDGET_MAPPED (rt->dialog)) #endif return; renflag_t flg = (renflag_t) flagnum; gboolean newflag = ! _e2p_ren_get_flag (flg); //, rt); _e2p_ren_set_flag (flg, newflag); //, rt); switch (flg) { case OLD_SEL_P: if (newflag && (_e2p_ren_get_flag (SEARCH_ALL_P) //, rt) || _e2p_ren_get_flag (SEARCH_THIS_P))) //, rt))) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->active_button), TRUE); if (newflag) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_button), FALSE); gtk_widget_set_sensitive (rt->pattern, FALSE); } gtk_widget_set_sensitive (rt->recurse_button, !newflag); break; case OLD_WILD_P: case OLD_REGEX_P: if (newflag) { gtk_widget_set_sensitive (rt->pattern, TRUE); gtk_widget_grab_focus ( #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->pattern)) #else GTK_BIN (rt->pattern)->child #endif ); } break; case SEARCH_ALL_P: if (newflag) { if (_e2p_ren_get_flag (OLD_SEL_P)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->wild_button), TRUE); } break; case SEARCH_THIS_P: gtk_widget_set_sensitive (rt->directory, newflag); #ifdef E2_RENCHOOSER gtk_widget_set_sensitive (rt->chooser_button, newflag); #endif if (newflag) { if (_e2p_ren_get_flag (OLD_SEL_P)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->wild_button), TRUE); gtk_widget_grab_focus ( #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->directory)) #else GTK_BIN (rt->directory)->child #endif ); } break; case NEW_THIS_P: gtk_widget_set_sensitive (rt->newpattern, newflag); if (newflag) gtk_widget_grab_focus ( #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->newpattern)) #else GTK_BIN (rt->newpattern)->child #endif ); break; default: break; } } /** @brief toggle specified option flag @param button toggled button widget @param flagnum pointerized number of the flag to be toggled @return */ static void _e2p_ren_grouptoggle_cb (GtkToggleButton *button, gpointer flagnum) { renflag_t flg = (renflag_t) flagnum; // E2_RenDialogRuntime *rt = g_object_get_data (G_OBJECT (button), "e2-runtime"); gboolean newflag = ! _e2p_ren_get_flag (flg); //, rt); _e2p_ren_set_flag (flg, newflag); //, rt); if (newflag) { //clear all other members of the group GtkToggleButton *tmp = (GtkToggleButton *)g_object_get_data (G_OBJECT (button), "group_leader"); GSList *members; for (members = g_object_get_data (G_OBJECT (tmp), "group_members"); members != NULL; members = members->next) { tmp = (GtkToggleButton *)members->data; if (tmp != button) gtk_toggle_button_set_active (tmp, FALSE); } } } /** @brief adjust directory string in @a entry After a keypress, this clears any selection and completes the path. If the current content is not an absolute path, the active-pane directory is referenced for completion. @param entry the entry widget for directory data @param event pointer to event data struct @param data UNUSED data specified when callback was connnected @return TRUE if the key was non-modified and a textkey and completion was done */ static gboolean _e2p_ren_key_press2_cb (GtkWidget *entry, GdkEventKey *event, gpointer data) { if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK //CHECKME #ifdef USE_GTK2_10 | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK #endif )) == 0 && (event->keyval < 0xF000 || event->keyval > 0xFFFF) && e2_fs_complete_dir (entry, event->keyval, 0)) //default is active pane return TRUE; return FALSE; } /** @brief entry-activation signal callback to commence renaming @param entry UNUSED entry widget which was activated @param rt ptr to dialog data struct @return */ static void _e2p_ren_activation_cb (GtkEntry *entry, E2_RenDialogRuntime *rt) { _e2p_ren_rename (rt); } /** @brief dialog response callback @param dialog the dialog where the response was triggered @param response the response for the clicked button @param rt pointer to dialog data struct @return */ static void _e2p_ren_response_cb (GtkDialog *dialog, gint response, E2_RenDialogRuntime *rt) { switch (response) { case E2_RESPONSE_USER1: //rename _e2p_ren_rename (rt); break; case E2_RESPONSE_USER2: //help e2_utils_show_help ("rename plugin"); //no translation unless help doc is translated gtk_widget_grab_focus (rt->dialog); break; case E2_RESPONSE_NOTOALL: //cancel button click //this can be clicked during a scan for matching items, or before //renaming a matched item rt->abort = TRUE; //this is the pre-rename signal scanaborted = TRUE; //and this for intra-scan signal break; default: //no if (rt->groups != NULL) { //rt->groups is a list of "leader" buttons in the dialog GSList *members, *tmp; for (tmp = rt->groups; tmp != NULL; tmp=tmp->next) { members = g_object_get_data (G_OBJECT (tmp->data), "group_members"); g_slist_free (members); } g_slist_free (rt->groups); } break; } } /***********************/ /*** widget creation ***/ /***********************/ /** @brief create and show a toggle in a specified container @param box the widget into which the button is to be placed @param label translated string for the button label @param state T/F initial state of the toggle @param callback the button's "toggled" signal callback function @param f enumerated value of flag to be associated with the button @param rt ptr to dialog data struct @return the button widget (UNUSED, now) */ static GtkWidget *__e2p_ren_create_toggle_button (GtkWidget *box, gchar *label, gboolean state, void (*callback)(GtkToggleButton*,gpointer), renflag_t f, E2_RenDialogRuntime *rt) { GtkWidget *button = e2_button_add_toggle (box, TRUE, state, label, NULL, FALSE, E2_PADDING_XSMALL, callback, (gpointer) f); g_object_set_data (G_OBJECT (button), "e2-runtime", rt); //cached flags default to FALSE // if (state) // _e2p_ren_set_flag (f, TRUE); //, rt); return button; } /** @brief create and show a grouped toggle in a specified container @param box the widget into which the button is to be placed @param leader widget for the 'leader' of the group, or NULL if this is the leader @param label translated string for the button label @param f enumerated value of flag to be associated with the button @param rt ptr to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_toggle_grouped_button (GtkWidget *box, GtkWidget *leader, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { gboolean state = _e2p_ren_get_flag (f); GtkWidget *button = __e2p_ren_create_toggle_button (box, label, state, _e2p_ren_grouptoggle_cb, f, rt); GtkWidget *leadptr; GSList *members; if (leader == NULL) { //this is the leader of a new group leadptr = button; //leader points to self members = NULL; //start a new list rt->groups = g_slist_append (rt->groups, button); //remember it, for cleaning up } else { //this is a group member leadptr = leader; //point to group leader, which has list members = g_object_get_data (G_OBJECT (leader), "group_members"); } members = g_slist_append (members, button); g_object_set_data (G_OBJECT (leadptr), "group_members", members); g_object_set_data (G_OBJECT (button), "group_leader", leadptr); return button; } /** @brief create and show a toggle in a specified container @param box the widget into which the button is to be placed @param label translated string for the button label @param state T/F initial state of the toggle @param f enumerated value of flag to be associated with the button @param rt pointer to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_toggle_button (GtkWidget *box, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { gboolean state = _e2p_ren_get_flag (f); GtkWidget *button = __e2p_ren_create_toggle_button (box, label, state, _e2p_ren_toggle_cb, f, rt); return button; } /** @brief create and show a 'leader' radio button in a specified container The initial button-state is set to value of @a f, it may be toggled by other group members @param box the widget into which the button is to be placed @param label translated string for the button label @param f enumerated value of flag to be associated with the button @param rt pointer to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_radio_button (GtkWidget *box, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { GtkWidget *button = e2_button_add_radio (box, label, NULL, _e2p_ren_get_flag (f), TRUE, E2_PADDING_XSMALL, _e2p_ren_toggle_cb, (gpointer) f); g_object_set_data (G_OBJECT (button), "e2-runtime", rt); return button; } /** @brief create and show a radio btn in a specified container The intial button-state is set to the value of @a f @param box the widget into which the button is to be placed @param leader the radio button widget that 'leads' the group @param label translated string for the button label @param f enumerated value of flag to be associated with the button @param rt pointer to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_radio_grouped_button (GtkWidget *box, GtkWidget *leader, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { GSList *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (leader)); GtkWidget *button = e2_button_add_radio (box, label, group, _e2p_ren_get_flag (f), TRUE, E2_PADDING_XSMALL, _e2p_ren_toggle_cb, (gpointer) f); g_object_set_data (G_OBJECT (button), "e2-runtime", rt); return button; } /* * @brief create and show a file selection widget for identifying the search-root dir @param title string used for file selection widget title @param ok ptr to void ok-callback for the widget @param cancel ptr to void cancel-callback for the widget @param rt ptr to find data struct @return the file selection widget */ /* UNUSED static GtkWidget *_e2p_ren_create_filesel (gchar *title, void (*ok)(GtkWidget *, E2_FindDialogRuntime *), void(*cancel)(GtkWidget *, E2_FindDialogRuntime *), E2_FindDialogRuntime *rt ) { GtkWidget *wid = gtk_file_selection_new (title); g_signal_connect (G_OBJECT (GTK_FILE_SELECTION (wid)->ok_button), "clicked", G_CALLBACK (ok), rt); g_signal_connect (G_OBJECT (GTK_FILE_SELECTION (wid)->cancel_button), "clicked", G_CALLBACK (cancel), rt); return wid; } */ /** @brief establish and show rename dialog Operation is queued, even though it does not necessarily work on either displayed filelist. @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_rename_dialog_create (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_RENAME, art, from, _e2p_renameQ, NULL)); } static gboolean _e2p_renameQ (E2_ActionTaskData *qed) { E2_RenDialogRuntime rt; memset (&rt, 0, sizeof(E2_RenDialogRuntime)); rt.status = qed->status; //enable status changes on-the-fly //don't need to be considered active while simply showing the dialog *qed->status = E2_TASK_PAUSED; //create dialog rt.dialog = e2_dialog_create (NULL, NULL, _("rename items"), (ResponseFunc)_e2p_ren_response_cb, &rt); //populate it with widgets GtkWidget *dialog_vbox, *hbox; dialog_vbox = #ifdef USE_GTK2_14 gtk_dialog_get_content_area (GTK_DIALOG (rt.dialog)); #else GTK_DIALOG (rt.dialog)->vbox; #endif e2_widget_add_mid_label (dialog_vbox, _("Search for items:"), 0.02, TRUE, 0); GtkWidget *radio = _e2p_ren_create_radio_button (dialog_vbox, _("any_where"), SEARCH_ALL_P, &rt); hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, TRUE, E2_PADDING); rt.active_button = _e2p_ren_create_radio_grouped_button (hbox, radio, _("in _active directory"), SEARCH_CURRENT_P, &rt); _e2p_ren_create_radio_grouped_button (hbox, radio, _("in _other directory"), SEARCH_OTHER_P, &rt); #ifdef E2_RENCHOOSER rt.chooser_box = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, FALSE, E2_PADDING); #endif _e2p_ren_create_radio_grouped_button ( #ifdef E2_RENCHOOSER rt.chooser_box, #else dialog_vbox, #endif radio, _("in _directory"), SEARCH_THIS_P, &rt); /* DO THIS IN IDLE CALLBACK TO PREVENT CRASH #ifdef E2_RENCHOOSER const gchar *message = _("Choose directory"); rt.chooser_button = gtk_file_chooser_button_new (message, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (rt.chooser_button), TRUE); gchar *local = qed->currdir; gchar *s = local + strlen (local) - sizeof(gchar); if (s > local && *s == G_DIR_SEPARATOR) *s = '\0'; gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (rt.chooser_button), local); if (s > local && *s == '\0') *s = G_DIR_SEPARATOR; e2_widget_set_safetip (rt.chooser_button, message); g_signal_connect (G_OBJECT (rt.chooser_button), "current-folder-changed", G_CALLBACK (_e2p_rename_choose_directory_cb), &rt); gtk_box_pack_end (GTK_BOX (hbox), rt.chooser_button, FALSE, FALSE, 0); gboolean state = _e2p_ren_get_flag (SEARCH_THIS_P); gtk_widget_set_sensitive (rt.chooser_button, state); #endif */ rt.directory = e2_combobox_add (dialog_vbox, FALSE, E2_PADDING_XSMALL, (ActivateFunc)_e2p_ren_activation_cb, &rt, &dir_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); //#ifndef E2_RENCHOOSER gboolean state = _e2p_ren_get_flag (SEARCH_THIS_P); //#endif gtk_widget_set_sensitive (rt.directory, state); //later toggled if corresponsing radio is selected //handle path completion g_signal_connect (G_OBJECT #ifdef USE_GTK2_14 (gtk_bin_get_child (GTK_BIN (rt.directory))), #else (GTK_BIN (rt.directory)->child), #endif "key-press-event", G_CALLBACK (_e2p_ren_key_press2_cb), NULL); #ifdef E2_RENCHOOSER //to avoid crash, must setup chooser in main thread rt.chooser_startdir = g_strdup (qed->currdir); gchar *s = rt.chooser_startdir + strlen (rt.chooser_startdir) - sizeof(gchar); if (s > rt.chooser_startdir && *s == G_DIR_SEPARATOR) *s = '\0'; g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc)_e2p_ren_add_chooser, &rt, NULL); #endif rt.recurse_button = _e2p_ren_create_toggle_button (dialog_vbox, _("R_ecurse subdirectories"), SEARCH_SUBDIRS_P, &rt); e2_widget_add_separator (dialog_vbox, TRUE, 0); hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); radio = _e2p_ren_create_radio_button (hbox, _("_Selected item(s)"), OLD_SEL_P, &rt); hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); rt.wild_button = _e2p_ren_create_radio_grouped_button (hbox, radio, _("Match _exact/wildcard"), OLD_WILD_P, &rt); _e2p_ren_create_radio_grouped_button (hbox, radio, _("Match regular e_xpression"), OLD_REGEX_P, &rt); hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, 0); GtkWidget *wid = e2_widget_add_mid_label (hbox, _("Current name is like this:"), 0.0, FALSE, E2_PADDING); GtkSizeGroup *same = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); gtk_size_group_add_widget (same, wid); rt.pattern = e2_combobox_add (hbox, TRUE, E2_PADDING_XSMALL, (ActivateFunc)_e2p_ren_activation_cb, &rt, &pattern_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); gtk_entry_set_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt.pattern))), #else GTK_ENTRY (GTK_BIN (rt.pattern)->child), #endif "(.*)"); state = _e2p_ren_get_flag (OLD_SEL_P); gtk_widget_set_sensitive (rt.pattern, !state); //later toggled if corresponsing radio is selected e2_widget_add_separator (dialog_vbox, TRUE, 0); hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); /* radio = _e2p_ren_create_radio_button (hbox, _("New name is _upper case"), NEW_UPPER_P, rt); _e2p_ren_create_radio_grouped_button (hbox, radio, _("New name is _lower case"), NEW_LOWER_P, rt); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); _e2p_ren_create_radio_grouped_button (hbox, radio, _("_New name is like this:"), NEW_THIS_P, rt); */ radio = _e2p_ren_create_toggle_grouped_button (hbox, NULL, _("New name is _upper case"), NEW_UPPER_P, &rt); _e2p_ren_create_toggle_grouped_button (hbox, radio, _("New name is _lower case"), NEW_LOWER_P, &rt); hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); wid = _e2p_ren_create_toggle_button (hbox, _("_New name is like this:"), NEW_THIS_P, &rt); gdk_threads_enter (); gtk_size_group_add_widget (same, wid); g_object_unref (G_OBJECT (same)); rt.newpattern = e2_combobox_add (hbox, TRUE, E2_PADDING_XSMALL, (ActivateFunc)_e2p_ren_activation_cb, &rt, &newpattern_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); state = _e2p_ren_get_flag (NEW_THIS_P); gtk_widget_set_sensitive (rt.newpattern, state); //later toggled if corresponsing radio is selected // gtk_entry_set_text (GTK_ENTRY (rt->newpattern), "\\1"); //FIXME utf-8 // g_object_set_data (G_OBJECT (rt->newpattern), "reset_yourself", reset_entry); gdk_threads_leave (); e2_widget_add_separator (dialog_vbox, TRUE, 0); _e2p_ren_create_toggle_button (dialog_vbox, _("Con_firm before each rename"), CONFIRM_P, &rt); //add action buttons in the order that they will appear rt.help_button = e2_dialog_add_custom_button_full (rt.dialog, FALSE, E2_RESPONSE_USER2, _("_Help"), GTK_STOCK_HELP, _("Get advice on rename options"), NULL, NULL); E2_Button stop_btn = { _("_Stop"), GTK_STOCK_STOP, _("Stop the current search"), E2_BTN_TIPPED, 0, E2_RESPONSE_NOTOALL }; rt.stop_button = e2_dialog_add_defined_button (rt.dialog, &stop_btn); //de-sensitize stop btn, at this stage gtk_widget_set_sensitive (rt.stop_button, FALSE); E2_BUTTON_CLOSE.showflags |= E2_BTN_DEFAULT; //CHECKME local copy ? e2_dialog_add_defined_button (rt.dialog, &E2_BUTTON_CLOSE); rt.start_button = e2_dialog_add_custom_button_full (rt.dialog, FALSE, E2_RESPONSE_USER1, _("_Rename"), GTK_STOCK_CONVERT, _("Begin renaming"), NULL, NULL); e2_dialog_set_negative_response (rt.dialog, E2_RESPONSE_NOTOALL); if (!_e2p_ren_get_flag (OLD_SEL_P)) gtk_widget_grab_focus ( #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt.pattern)) #else GTK_BIN (rt.pattern)->child #endif ); gdk_threads_enter (); e2_dialog_setup (rt.dialog, app.main_window); //block actions Q until user closes e2_dialog_run (rt.dialog, NULL, E2_DIALOG_BLOCKED | E2_DIALOG_FREE); // gtk_widget_grab_focus (curr_view->treeview); gdk_threads_leave (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @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 "rename" aname = _("renext"); p->signature = ANAME VERSION; p->menu_name = _("_Rename.."); p->description = _("Rename item(s), using wildcards or regular-expressions"); p->icon = "plugin_rename"E2ICONTB; if (p->action == NULL) { //don't free name string here E2_Action plugact = {g_strconcat (_A(1),".",aname,NULL),_e2p_rename_dialog_create,FALSE,E2_ACTION_TYPE_ITEM,0,NULL,NULL}; p->action = e2_plugins_action_register (&plugact); if G_UNLIKELY((p->action == NULL)) { g_free (plugact.name); return FALSE; } dir_history = (GList *)g_new0 (gpointer, 1); pattern_history = (GList *)g_new0 (gpointer, 1); newpattern_history = (GList *)g_new0 (gpointer, 1); if (!e2_cache_check ("rename-flags")) { //initialise TRUE flags flags[SEARCH_CURRENT_P] = TRUE; flags[OLD_WILD_P] = TRUE; flags[NEW_THIS_P] = TRUE; flags[CONFIRM_P] = TRUE; } e2_cache_array_register ("rename-flags", MAX_FLAGS, flags, flags); e2_cache_list_register ("rename-dir-history", &dir_history); e2_cache_list_register ("rename-oldpattern-history", &pattern_history); e2_cache_list_register ("rename-newpattern-history", &newpattern_history); return TRUE; } 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(1),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { //backup the cache data e2_cache_unregister ("rename-flags"); e2_cache_unregister ("rename-dir-history"); e2_cache_unregister ("rename-oldpattern-history"); e2_cache_unregister ("rename-newpattern-history"); //cleanup e2_list_free_with_data (&dir_history); e2_list_free_with_data (&pattern_history); e2_list_free_with_data (&newpattern_history); } return ret; }