File: cmd_rename.c

package info (click to toggle)
gentoo 0.19.13-2
  • links: PTS
  • area: main
  • in suites: jessie, jessie-kfreebsd, wheezy
  • size: 6,900 kB
  • ctags: 5,244
  • sloc: ansic: 41,826; sh: 4,406; makefile: 817; yacc: 291; sed: 16
file content (398 lines) | stat: -rw-r--r-- 12,548 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
** 1998-05-29 -	A rename command might be useful. Since writing this also caused me
**		to implement the generic command interface in cmd_generic, it was
**		very useful indeed!
** 1998-09-18 -	Did some pretty massive changes/additions to provide overwrite protection.
** 1999-01-30 -	Bug fix: always called ovw_overwrite_end() in closing, regardless of
**		whether the _begin() function was ever called. This caused nesting errors.
** 1999-02-23 -	Um... That "bug fix" was buggy. I think I fixed it this time, though.
** 1999-03-05 -	Altered to comply with new selection handling (and its changes on the
**		generic command invocation parameters).
*/

#include "gentoo.h"
#include "cmd_delete.h"
#include "cmdseq_config.h"
#include "dirpane.h"
#include "errors.h"
#include "guiutil.h"
#include "overwrite.h"

#include "cmd_generic.h"
#include "cmd_rename.h"

#include <gdk/gdkkeysyms.h>

#define	CMD_ID	"rename"

/* ----------------------------------------------------------------------------------------- */

typedef struct {
	GtkWidget	*vbox;
	GtkWidget	*label;
	GtkWidget	*entry;
	MainInfo	*min;
	gboolean	ovw_open;

	/* These are only used when in-place rename is in progress. */
	DirPane		*inplace_pane;
	GError		*inplace_error;
	gboolean	inplace_success;
} RenInfo;

typedef enum {
	PRESELECT_NONE = 0,
	PRESELECT_FULL,
	PRESELECT_NAME_ONLY,
	PRESELECT_EXTENSION_ONLY
} RenamePreselect;

typedef struct {			/* Options used by the "Rename" command. */
	gboolean	modified;
	gboolean	single_in_place;
	RenamePreselect	preselect;
} OptRename;

static OptRename	rename_options;
static CmdCfg		*rename_cmc = NULL;

/* ----------------------------------------------------------------------------------------- */

/* Guess (!) which preselection mode is active in the given editable. */
static RenamePreselect guess_preselect(GtkEditable *editable)
{
	gint	start, end;

	if(gtk_editable_get_selection_bounds(editable, &start, &end))
	{
		/* We have a selection; grab the characters and inspect. */
		gchar	*chars = gtk_editable_get_chars(editable, 0, -1);

		if(chars != NULL)
		{
			const glong	len = g_utf8_strlen(chars, -1);
			RenamePreselect	ret = PRESELECT_NONE;
			
			/* Entire string selected? */
			if(start == 0 && end == len)
				ret = PRESELECT_FULL;
			/* Anchored to beginning, ends exactly with period? */
			else if(start == 0 && end < len && *g_utf8_offset_to_pointer(chars, end) == '.')
				ret = PRESELECT_NAME_ONLY;
			/* Anchored to end, starts right after period? */
			else if(end == len && start > 0 && *g_utf8_offset_to_pointer(chars, start - 1) == '.')
				ret = PRESELECT_EXTENSION_ONLY;
			g_free(chars);

			return ret;
		}
	}
	return PRESELECT_NONE;
}

/* Apply the indicated pre-selection. */
static void apply_preselect(GtkEditable *editable, const gchar *name, RenamePreselect mode)
{
	gpointer	dot_ptr;
	gulong		dot_pos;

	switch(mode)
	{
		case PRESELECT_NONE:
			/* Rather unexpectedly, we must do this or else GTK+ selects all. */
			gtk_editable_select_region(editable, 0, 0);
			break;
		case PRESELECT_FULL:
			gtk_editable_select_region(editable, 0, -1);
			break;
		case PRESELECT_NAME_ONLY:
		case PRESELECT_EXTENSION_ONLY:
			if((dot_ptr = g_utf8_strchr(name, -1, '.')) != NULL)
			{
				dot_pos = g_utf8_pointer_to_offset(name, dot_ptr);
				if(mode == PRESELECT_NAME_ONLY)
					gtk_editable_select_region(editable, 0, dot_pos);
				else
					gtk_editable_select_region(editable, dot_pos + 1, -1);
			}
			break;
	}
}

/* Keypress handler for whatever widget hosts the renaming. */
static gboolean evt_rename_keypress(GtkWidget *wid, GdkEvent *evt, gpointer user)
{
	const GdkEventKey	*ke = &evt->key;

	/* Hard-coded (control-period) shortcut to cycle selection. Pretty cool. */
	if(ke->type == GDK_KEY_PRESS && ke->state & GDK_CONTROL_MASK && ke->keyval == GDK_KEY_period)
	{
		const RenamePreselect	mode = guess_preselect(GTK_EDITABLE(wid));
		RenamePreselect		new_mode;
		gchar			*chars;

		if(mode == PRESELECT_EXTENSION_ONLY)
			new_mode = PRESELECT_NONE;
		else
			new_mode = mode + 1;

		/* Slightly iffy to re-allocate for the chars, but think of the frequency, here. */
		if((chars = gtk_editable_get_chars(GTK_EDITABLE(wid), 0, -1)) != NULL)
		{
			apply_preselect(GTK_EDITABLE(wid), chars, new_mode);
			g_free(chars);
		}
		return TRUE;
	}
	return FALSE;
}

/* Apply the configured preselection-mode. */
static void init_preselect(GtkEditable *editable, const gchar *name, RenamePreselect mode)
{
	g_signal_connect(G_OBJECT(editable), "key_press_event", G_CALLBACK(evt_rename_keypress), NULL);
	apply_preselect(editable, name, mode);
}

static void ren_body(MainInfo *min, DirPane *src, DirRow2 *row, gpointer gen, gpointer user)
{
	gchar		temp[PATH_MAX + 256];
	const gchar	*name;
	RenInfo		*ren = user;

	name = dp_row_get_name_display(dp_get_tree_model(src), row);
	g_snprintf(temp, sizeof temp, _("Enter New Name For \"%s\""), name);
	gtk_label_set_text(GTK_LABEL(ren->label), temp);
	gtk_entry_set_text(GTK_ENTRY(ren->entry), name);
	gtk_widget_grab_focus(ren->entry);
	init_preselect(GTK_EDITABLE(ren->entry), name, rename_options.preselect);

	cmd_generic_track_entry(gen, ren->entry);

	if(!ren->ovw_open)
	{
		ovw_overwrite_begin(ren->min, _("\"%s\" Already Exists - Proceed With Rename?"), 0U);
		ren->ovw_open = TRUE;
	}
}

static gboolean do_rename(DirPane *src, const DirRow2 *row, const gchar *new_name, GError **error)
{
	GFile	*file;

	if((file = dp_get_file_from_row(src, row)) != NULL)
	{
		GFile	*nf;

		nf = g_file_set_display_name(file, new_name, NULL, error);
		if(nf != NULL)
			g_object_unref(nf);
		g_object_unref(file);

		return nf != NULL;
	}
	return FALSE;
}

static gint ren_action(MainInfo *min, DirPane *src, DirPane *dst, DirRow2 *row, GError **error, gpointer user)
{
	RenInfo		*ren = user;
	const gchar	*old_name, *new_name;
	GFile		*df;
	gboolean	ok;

	old_name = dp_row_get_name(dp_get_tree_model(src), row);
	new_name = gtk_entry_get_text(GTK_ENTRY(ren->entry));

	/* Ignore renames to self. */
	if(strcmp(old_name, new_name) == 0)
	{
		dp_unselect(src, row);
		return 1;
	}

	err_clear(min);

	/* Check for overwrite, and attempt to clear the way. */
	switch(ovw_overwrite_unary_name(src, new_name))
	{
	case OVW_SKIP:
		return 1;
	case OVW_CANCEL:
		return 0;
	case OVW_PROCEED:
		break;
	case OVW_PROCEED_FILE:
	case OVW_PROCEED_DIR:
		df = dp_get_file_from_name(src, new_name);
		if(df == NULL)
			return 0;
		ok = del_delete_gfile(min, df, FALSE, error);
		g_object_unref(df);
		if(!ok)
			return 0;
		break;
	}

	/* All is well, open the file and do the rename. */
	return do_rename(src, row, new_name, error);
}

static void ren_free(gpointer user)
{
	if(((RenInfo *) user)->ovw_open)
		ovw_overwrite_end(((RenInfo *) user)->min);
}

/* ----------------------------------------------------------------------------------------- */

/* 2011-06-29 -	Cell editing started. We would like to just call do_preselect() here, but GTK+ seems to
**		also have ideas about the selection of the newly editing-enabled cell renderer. So we just
**		sneakily buffer the pointer, and hack it later. This is not pretty, but it seems to work.
*/
static void evt_cell_editing_started(GtkCellRenderer *cr, GtkCellEditable *editable, gchar *path, gpointer user)
{
	if(GTK_IS_EDITABLE(editable))
		*(GtkEditable **) user = GTK_EDITABLE(editable);
}

/* 2011-01-03 -	When editing ends, try to do the rename. */
static void evt_cell_edited(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user)
{
	RenInfo		*ri = user;
	GtkTreeIter	row;

	if(new_text != NULL && *new_text != '\0')
	{
		if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ri->inplace_pane->dir.store), &row, path))
		{
			ri->inplace_success = do_rename(ri->inplace_pane, &row, new_text, &ri->inplace_error);
			if(!ri->inplace_success)
			{
				/* No generic framework, we need to do this ourselves. */
				err_set_gerror(ri->min, &ri->inplace_error, NULL, NULL);	/* Doesn't include file information, but whatever. */
			}
		}
	}
	gtk_main_quit();
}

/* 2011-01-03 -	Pick up editing cancellation, and exit the recursive GTK+ main loop. */
static void evt_cell_editing_canceled(GtkCellRenderer *cr, gpointer user)
{
	gtk_main_quit();
}

gint cmd_rename(MainInfo *min, DirPane *src, DirPane *dst, const CmdArg *ca)
{
	static RenInfo	ri;

	if(rename_options.single_in_place && dp_has_single_selection(src))	/* In-place? */
	{
		gsize			i;
		GtkTreeViewColumn	*col = NULL;
		GSList			*sel;
		GtkTreePath		*path;
		GList			*cells;
		GtkCellRenderer		*cr;
		GtkEditable		*editable = NULL;

		ri.inplace_pane = src;
		ri.inplace_error = NULL;
		ri.inplace_success = FALSE;

		/* Find the (first) column that displays the name, if any. If there are several, you lose. */
		for(i = 0; i < min->cfg.dp_format[src->index].num_columns; i++)
		{
			if(min->cfg.dp_format[src->index].format[i].content == DPC_NAME)
			{
				col = gtk_tree_view_get_column(GTK_TREE_VIEW(src->view), i);
				break;
			}
		}
		if(col == NULL)
			return 0;

		/* Get the cell renderer, so we can connect a signal handler. */
		cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col));
		if(cells == NULL)
			return 0;
		cr = cells->data;
		g_list_free(cells);

		/* Make name's renderer editable, this *must* be done last-minute or it breaks double-clicking. */
		g_object_set(G_OBJECT(cr), "mode", GTK_CELL_RENDERER_MODE_EDITABLE, "editable", TRUE, "editable-set", TRUE, NULL);

		/* Make sure the signal handler is connected, but only once. */
		g_signal_handlers_disconnect_by_func(G_OBJECT(cr), G_CALLBACK(evt_cell_editing_started), &ri);
		g_signal_connect(G_OBJECT(cr), "editing-started", G_CALLBACK(evt_cell_editing_started), &editable);
		g_signal_handlers_disconnect_by_func(G_OBJECT(cr), G_CALLBACK(evt_cell_edited), &ri);
		g_signal_connect(G_OBJECT(cr), "edited", G_CALLBACK(evt_cell_edited), &ri);
		g_signal_handlers_disconnect_by_func(G_OBJECT(cr), G_CALLBACK(evt_cell_editing_canceled), &ri);
		g_signal_connect(G_OBJECT(cr), "editing-canceled", G_CALLBACK(evt_cell_editing_canceled), &ri);

		/* Get the path, and activate cell editing. */
		sel = dp_get_selection(src);
		if((path = gtk_tree_model_get_path(GTK_TREE_MODEL(src->dir.store), sel->data)) != NULL)
		{
			gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(src->view), path, col, NULL, TRUE);
			gtk_tree_path_free(path);

			/* Detach the keyboard acceleration context to prevent clashes. */
			kbd_context_detach(min->gui->kbd_ctx, GTK_WINDOW(min->gui->window));

			/* This got set by the the evt_cell_editing_started() callback, now apply preselection. */
			if(editable != NULL)
			{
				gchar	*text;

				if((text = gtk_editable_get_chars(GTK_EDITABLE(editable), 0, -1)) != NULL)
				{
					init_preselect(GTK_EDITABLE(editable), text, rename_options.preselect);
					g_free(text);
				}
			}
			/* Recurse, so we keep Rename fully synchronous. */
			gtk_main();

			/* Editing is done, remove the editability. */
			g_object_set(G_OBJECT(cr), "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, "editable", FALSE, "editable-set", FALSE, NULL);

			/* Re-attach keyboard modifiers. */
			kbd_context_attach(min->gui->kbd_ctx, GTK_WINDOW(min->gui->window));
		}
		dp_free_selection(sel);
		return ri.inplace_success;
	}
	else	/* Do the classical generic-based dialog rename. */
	{
		ri.ovw_open = FALSE;
		ri.vbox	    = gtk_vbox_new(FALSE, 0);
		ri.label    = gtk_label_new("");
		ri.entry    = gui_dialog_entry_new();
		gtk_entry_set_max_length(GTK_ENTRY(ri.entry), MAXNAMLEN - 1);
		gtk_box_pack_start(GTK_BOX(ri.vbox), ri.label, FALSE, FALSE, 0);
		gtk_box_pack_start(GTK_BOX(ri.vbox), ri.entry, FALSE, FALSE, 0);

		return cmd_generic(min, _("Rename"), CGF_NOALL | CGF_NODST, ren_body, ren_action, ren_free, &ri);
	}
}

/* ----------------------------------------------------------------------------------------- */

void cfg_rename(MainInfo *min)
{
	if(rename_cmc == NULL)
	{
		/* Set the default values for module's options. */
		rename_options.modified = FALSE;
		rename_options.single_in_place = FALSE;
		rename_options.preselect = PRESELECT_FULL;

		rename_cmc = cmc_config_new("Rename", &rename_options);
		cmc_field_add_boolean(rename_cmc, "modified", NULL, offsetof(OptRename, modified));
		cmc_field_add_boolean(rename_cmc, "inplace", _("Rename Single File In-Place (Without Dialog)?"), offsetof(OptRename, single_in_place));
		cmc_field_add_enum(rename_cmc, "preselect", _("Automatically Pre-Select"), offsetof(OptRename, preselect), _("None|Entire Name|Filename Only|Extension Only"));
		cmc_config_register(rename_cmc);
	}
}