/* $Id: e2p_rename.c 731 2007-11-19 22:28:30Z tpgww $

Copyright (C) 2005-2007 tooar <tooar@gmx.net>

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, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file plugins/e2p_rename_ext.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 <string.h>
#include <dirent.h>
#include <fnmatch.h>
#include <regex.h>
#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 .... \<E2_HUNK_LIMIT-1>
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 (;j<E2_CHUNK_LIMIT;j++)
			{
				if (rt->chunks[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 ((gchar *) 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);
		e2_output_print_error (msg, TRUE);
		F_FREE (utf);
		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' || g_str_equal (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 (GTK_ENTRY (GTK_BIN (rt->pattern)->child));
		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 (GTK_ENTRY (GTK_BIN (rt->newpattern)->child));
		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
		{
#ifdef E2_VFS
# ifdef E2_VFSTMP
	FIXME get relevant space
# endif
			sdata.spacedata = curr_view->spacedata;
#endif
			startdir = (gchar *) gtk_entry_get_text
				(GTK_ENTRY (GTK_BIN (rt->directory)->child));
			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.localpath = 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 = GTK_TOGGLE_BUTTON (rt->recurse_button)->active;
					_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);
		g_ptr_array_free (data.candidates, TRUE);
		WAIT_FOR_EVENTS
		*rt->status = E2_TASK_PAUSED;
		return;
	}

	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 doit;
	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"), e2_dialog_response_decode_cb, &doit);
		//set default button to 'no'
		e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL);
		E2_BUTTON_NO.showflags |= E2_BTN_DEFAULT;
		E2_BUTTON_YES.showflags &= ~E2_BTN_DEFAULT;
		e2_dialog_add_defined_button (dialog, &E2_BUTTON_NOTOALL);
		e2_dialog_add_defined_button (dialog, &E2_BUTTON_NO);
		e2_dialog_add_defined_button (dialog, &E2_BUTTON_YES);
		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)
		{
			gchar *prompt = g_strdup_printf ("%s\n<b>%s</b>\n%s\n<b>%s</b>\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);
			gtk_widget_show (dialog);
			*rt->status = E2_TASK_PAUSED;
			gtk_main ();
			*rt->status = E2_TASK_RUNNING;
			gtk_widget_hide (dialog);
			gtk_widget_grab_focus (rt->dialog);
		}
		else	//no confirmation
			doit = OK;

		if (doit == 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 (!g_str_equal (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.localpath = 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 ();
							doit = CANCEL;
						}
					}
					else
					{
						gdk_threads_leave ();	//downstream errors invoke local mutex locking
#ifdef E2_VFS
						tdata.localpath = *thisone;
						e2_fs_error_simple (_("Cannot rename %s"), &tdata);
#else
						e2_fs_error_simple (_("Cannot rename %s"), *thisone);
#endif
						gdk_threads_enter ();
						doit = CANCEL;
					}
					F_FREE (slocal);
					g_free (tempname);
				}
		/*			else	//we have a real difference
				{	//FIXME duplication here
					e2_filelist_enable_refresh ();  //allow updates while we wait
					doit = e2_dialog_ow_check (*thisone, dlocal, NOALL);
					e2_filelist_disable_refresh ();
				} */
				g_free (old_name_lc);
				g_free (new_name_lc);
			}
			if (doit == OK)
			{
				gdk_threads_leave ();	//downstream errors invoke local mutex locking
#ifdef E2_VFS
				tdata.localpath = *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);
		}

		g_free (base);
		g_free (dir);
		g_free (newbase);
		g_free (base_public);
		g_free (newbase_public);

		if (doit == 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 (dialog != NULL)
		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 (GTK_BIN (rt->pattern)->child);
}

  /*********************/
 /***** callbacks *****/
/*********************/

/**
@brief toggle specified option flag

@param button clicked button widget
@param flagnum pointerized number of the flag to be toggled

@return
*/
static void _e2p_ren_toggle_cb (GtkWidget *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 ...
	if (!GTK_WIDGET_MAPPED (rt->dialog))
	  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 (GTK_BIN (rt->pattern)->child);
		  }
		  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);
		  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 (GTK_BIN (rt->directory)->child);
		  }
		  break;
		case NEW_THIS_P:
		  gtk_widget_set_sensitive (rt->newpattern, newflag);
		  if (newflag)
			gtk_widget_grab_focus (GTK_BIN (rt->newpattern)->child);
		  break;
		default:
		  break;
	}
}
/**
@brief toggle specified option flag

@param button clicked button widget
@param flagnum pointerized number of the flag to be toggled

@return
*/
static void _e2p_ren_grouptoggle_cb (GtkWidget *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
		GtkWidget *tmp = g_object_get_data (G_OBJECT (button), "group_leader");
		GSList *members = g_object_get_data (G_OBJECT (tmp), "group_members");
		for (;members != NULL; members = members->next)
		{
			tmp = (GtkWidget *)members->data;
			if (tmp != button)
			  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (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)) == 0
//#ifdef USE_GTK2_10 | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK
			&& (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: //stop 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;
//		case GTK_RESPONSE_CLOSE:
		default:	//cancel
			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);
			}
			//now that we don't need buttons any longer ...
			gtk_widget_destroy (rt->dialog);
			DEALLOCATE (E2_RenDialogRuntime, rt);
//			gtk_widget_grab_focus (curr_view->treeview);

			gtk_main_quit ();
			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_fn the button's 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, gpointer callback_fn,
	renflag_t f, E2_RenDialogRuntime *rt)
{
  GtkWidget *button = e2_button_add_toggle (box, TRUE, state, label,
	NULL, FALSE, E2_PADDING_XSMALL, callback_fn, (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, 0, _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, 0, _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);
  gtk_widget_show (wid);
  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)
{
	//init runtime object
	E2_RenDialogRuntime *rt = ALLOCATE0 (E2_RenDialogRuntime);
	CHECKALLOCATEDWARN (rt, return FALSE;)
	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"),
		_e2p_ren_response_cb, rt);

	//populate it with widgets
	GtkWidget *vbox, *hbox;
	vbox = GTK_DIALOG (rt->dialog)->vbox;
//	hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, 0);
	e2_widget_add_mid_label (vbox, _("Search for items:"), 0.02, TRUE, 0);
	GtkWidget *radio = _e2p_ren_create_radio_button (vbox, _("any_where"),
		SEARCH_ALL_P, rt);
	rt->active_button =
	_e2p_ren_create_radio_grouped_button (vbox, radio, _("in _active directory"),
		SEARCH_CURRENT_P, rt);
	_e2p_ren_create_radio_grouped_button (vbox, radio, _("in _other directory"),
		SEARCH_OTHER_P, rt);
	_e2p_ren_create_radio_grouped_button (vbox, radio, _("in _this directory"),
		SEARCH_THIS_P, rt);
	gdk_threads_enter ();	//reduce delay when keying in data (dunno why)
	rt->directory = e2_combobox_add (vbox, FALSE, 0, _e2p_ren_activation_cb, rt,
		&dir_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE);
	gboolean state = _e2p_ren_get_flag (SEARCH_THIS_P);
	gtk_widget_set_sensitive (rt->directory, state);	//later toggled if corresponsing radio is selected
	gdk_threads_leave ();
	//handle path completion
	g_signal_connect (G_OBJECT (GTK_BIN (rt->directory)->child),
		"key-press-event", G_CALLBACK (_e2p_ren_key_press2_cb), NULL);
	rt->recurse_button =
	_e2p_ren_create_toggle_button (vbox, _("R_ecurse subdirectories"),
		SEARCH_SUBDIRS_P, rt);
	e2_widget_add_separator (vbox, TRUE, 0);

	hbox = e2_widget_add_box (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 (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 (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING);
	e2_widget_add_mid_label (hbox, _("Current name is like this:"), 0.05, TRUE, 0);

	gdk_threads_enter ();
	rt->pattern = e2_combobox_add (hbox, FALSE, 0, _e2p_ren_activation_cb, rt,
		&pattern_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE);
	gtk_entry_set_text (GTK_ENTRY (GTK_BIN (rt->pattern)->child), "(.*)");
	state = _e2p_ren_get_flag (OLD_SEL_P);
	gtk_widget_set_sensitive (rt->pattern, !state);	//later toggled if corresponsing radio is selected
	gdk_threads_leave ();

	e2_widget_add_separator (vbox, TRUE, 0);

	hbox = e2_widget_add_box (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 (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING);
	_e2p_ren_create_toggle_button (hbox, _("_New name is like this:"),
		NEW_THIS_P, rt);
	gdk_threads_enter ();
	rt->newpattern =
	e2_combobox_add (hbox, FALSE, 0, _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 (vbox, TRUE, 0);
	_e2p_ren_create_toggle_button (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_undefined_button_custom
		(rt->dialog, FALSE, E2_RESPONSE_USER2, _("_Help"), GTK_STOCK_HELP,
		_("Get advice on rename options"), NULL, NULL);
	rt->stop_button =
	e2_dialog_add_button_custom (rt->dialog, FALSE,
		&E2_BUTTON_NOTOALL, _("Stop the current search"), NULL, NULL);
	//de-sensitize stop btn, at this stage
	gtk_widget_set_sensitive (rt->stop_button, FALSE);
	rt->start_button =
	e2_dialog_add_undefined_button_custom (rt->dialog, FALSE,
		E2_RESPONSE_USER1, _("_Rename"),
		GTK_STOCK_CONVERT, _("Begin renaming"), NULL, NULL);
	e2_dialog_add_button_custom (rt->dialog, TRUE, &E2_BUTTON_CLOSE,
		NULL, NULL, NULL);
	e2_dialog_set_negative_response (rt->dialog, E2_RESPONSE_NOTOALL);

	state = _e2p_ren_get_flag (OLD_SEL_P);
	if (!state)
		gtk_widget_grab_focus (GTK_BIN (rt->pattern)->child);

	e2_dialog_setup (rt->dialog, app.main_window);
	gdk_threads_enter ();
	e2_dialog_run (rt->dialog, NULL, 0);
	gtk_main ();
	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_"E2IP".png";

	if (p->action == NULL)
	{
		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);
		//no need to free this
		gchar *action_name = g_strconcat (_A(1),".",aname,NULL);
		p->action = e2_plugins_action_register
		  (action_name, E2_ACTION_TYPE_ITEM, _e2p_rename_dialog_create, NULL, FALSE, 0, NULL);
		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;
}
