1 /*
2 ** 1998-05-25 -	The amount of code in the main module dealing with dirpanes simply got too big. Dike, dike.
3 ** 1998-06-08 -	Redesigned sorting somewhat. Now, the information used to determine how to sort resides
4 **		in the configuration structure, not the individual dir pane.
5 ** 1998-08-02 -	Fixed big performance mishap; when changing the sort mode, a full dir reread was done,
6 **		rather than just a (relatively quick) redisplay.
7 ** 1998-08-08 -	Now finally supports high-speed dragging without losing lines. Great.
8 ** 1999-03-05 -	Massive changes since we now rely on GTK+'s CList widget to handle all selection details.
9 **		Gives dragging, scrolling, and stuff.
10 ** 1999-03-14 -	Opaque-ified the access to DirRow fields.
11 ** 1999-05-29 -	Added support for symlinks. More controlled and more memory efficient than the old code.
12 ** 2000-04-16 -	Simplified sorting code somewhat, implemented actual comparison functions for {u,g}name.
13 */
14 
15 #include "gentoo.h"
16 
17 #include <ctype.h>
18 #include <glob.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include <gdk/gdkkeysyms.h>
23 
24 #include "children.h"
25 #include "cmdseq.h"
26 #include "configure.h"
27 #include "controls.h"
28 #include "dirhistory.h"
29 #include "dpformat.h"
30 #include "errors.h"
31 #include "events.h"
32 #include "fileutil.h"
33 #include "gfam.h"
34 #include "guiutil.h"
35 #include "sizeutil.h"
36 #include "strutil.h"
37 #include "styles.h"
38 #include "types.h"
39 #include "userinfo.h"
40 
41 #include "cmd_dpfocus.h"
42 
43 #include "dirpane.h"
44 
45 /* ----------------------------------------------------------------------------------------- */
46 
47 typedef gint	(*StrCmpFunc)(const gchar *a, const gchar *b);
48 
49 static DPSort	the_sort;			/* Used to get state across to qsort() callback. */
50 
51 static GtkStyle	*pane_selected = NULL,		/* The style used on selected pane's column buttons. */
52 		*focus_style   = NULL;		/* For focusing. */
53 
54 static gboolean	no_repeat = FALSE;		/* Ugly hack to prevent repeat on focused row to go into infinity. Ugly. Ugly. */
55 
56 /* This is used by dp_activate_queued() to keep track of state. */
57 static struct {
58 	guint	handler;
59 	DirPane	*old_cur;
60 } the_activate_queue_info = { 0, NULL };
61 
62 static GSList	*pathwidgetry_builders = NULL;
63 
64 enum { COL_FILE = 0, COL_INFO, COL_FTYPE, COL_FILENAME_COLLATE_KEY, COL_LINK_TARGET_INFO, COL_FLAGS,
65 	COL_NUMBER_OF_COLUMNS };
66 
67 /* ----------------------------------------------------------------------------------------- */
68 
69 static void	clear_total_stats(DirPane *dp);
70 static void	clear_selection_stats(SelInfo *si);
71 
72 static void	dp_activate_queued(DirPane *dp);
73 
74 /* ----------------------------------------------------------------------------------------- */
75 
76 /* 2010-11-21 -	Rewritten. Just initialize some of the string data fields of all panes. */
dp_initialize(DirPane * dp,size_t num)77 void dp_initialize(DirPane *dp, size_t num)
78 {
79 	size_t	i;
80 
81 	/* Clear the path settings. */
82 	for(i = 0; i < num; i++)
83 	{
84 		dp[i].dir.path[0] = '\0';
85 		dp[i].dir.pathd = NULL;
86 		dp[i].dir.root = NULL;
87 	}
88 }
89 
90 /* ----------------------------------------------------------------------------------------- */
91 
92 /* 2010-06-02 -	Encapsulate this tiny porting/compatibility issue to spare us from #ifdef:s all over. */
dp_realized(const MainInfo * min)93 gboolean dp_realized(const MainInfo *min)
94 {
95 	return gtk_widget_get_realized(min->gui->panes);
96 }
97 
98 /* ----------------------------------------------------------------------------------------- */
99 
100 /* 2002-08-02 -	Rewrote this classic, to be a bit shorter and take const-ant time, too. Whoo. */
dp_mirror(const MainInfo * min,const DirPane * dp)101 DirPane * dp_mirror(const MainInfo *min, const DirPane *dp)
102 {
103 	return &min->gui->pane[1 - dp->index];
104 }
105 
106 /* ----------------------------------------------------------------------------------------- */
107 
108 /* 1998-09-05 -	Get the full name, with path, for row number <row> of <dp>. Trivial, but
109 **		saves a couple of lines here and there elsewhere in the program.
110 ** 1999-01-20 -	Now tries to avoid starting the name with two slashes for root entries.
111 ** 2002-02-25 -	Generalized to never produce double slashes, removed special case for root.
112 */
dp_full_name(const DirPane * dp,const DirRow2 * row)113 const gchar * dp_full_name(const DirPane *dp, const DirRow2 *row)
114 {
115 	static gchar	buf[PATH_MAX];
116 
117 	g_snprintf(buf, sizeof buf, "%s/%s", g_file_get_path(dp->dir.root), dp_row_get_name(dp_get_tree_model(dp), row));
118 	return buf;
119 }
120 
121 /* 1999-02-23 -	Get the name of a row, but quoted and with any embedded quotes
122 **		escaped by backslashes. Very handy when evaluating {f}-codes...
123 **		If <path> is TRUE, the path is included as well. Doesn't nest.
124 */
dp_name_quoted(const DirPane * dp,const DirRow2 * row,gboolean path)125 const gchar * dp_name_quoted(const DirPane *dp, const DirRow2 *row, gboolean path)
126 {
127 	static gchar	buf[2 * PATH_MAX];
128 	const gchar	*fn, *ptr;
129 	gchar		*put = buf;
130 
131 	fn = path ? dp_full_name(dp, row) : dp_row_get_name(dp_get_tree_model(dp), row);
132 	*put++ = '"';
133 	for(ptr = fn; *ptr; ptr++)
134 	{
135 		if(*ptr == '"' || *ptr == '\\')
136 			*put++ = '\\';
137 		*put++ = *ptr;
138 	}
139 	*put++ = '"';
140 	*put = '\0';
141 
142 	return buf;
143 }
144 
145 /* ----------------------------------------------------------------------------------------- */
146 
147 /* 2008-09-20 -	Clear the completion cache. */
completion_clear(DirPane * dp)148 static void completion_clear(DirPane *dp)
149 {
150 	GtkListStore	*cm;
151 
152 	cm = GTK_LIST_STORE(gtk_entry_completion_get_model(dp->complete.compl));
153 	if(cm == NULL)
154 		return;
155 	gtk_list_store_clear(cm);
156 }
157 
158 /* 2008-09-20 -	Update the completion for the given pane, considering that 'prefix' is the
159  *		current directory root. If this is the same as the cached, do nothing.
160 */
completion_update(DirPane * dp,const gchar * prefix)161 static void completion_update(DirPane *dp, const gchar *prefix)
162 {
163 	GtkListStore	*cm;
164 	GFile		*dir;
165 	GFileEnumerator	*fen;
166 
167 	if(strcmp(dp->complete.prefix, prefix) == 0)
168 		return;
169 	cm = GTK_LIST_STORE(gtk_entry_completion_get_model(dp->complete.compl));
170 	if(cm == NULL)
171 		return;
172 	/* We're about to check on-disk, so clear the cache in the GtkEntryCompletion. */
173 	gtk_list_store_clear(cm);
174 	dir = g_file_parse_name(prefix);
175 	if((fen = g_file_enumerate_children(dir, "standard::*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL)) != NULL)
176 	{
177 		GFileInfo	*child;
178 		GtkTreeIter	iter;
179 
180 		while((child = g_file_enumerator_next_file(fen, NULL, NULL)) != NULL)
181 		{
182 			if(g_file_info_get_file_type(child) == G_FILE_TYPE_DIRECTORY)
183 			{
184 				gchar	buf[4 * PATH_MAX];
185 
186 				/* Assumes prefix ends with slash. Slightly Unix-centric. */
187 				g_snprintf(buf, sizeof buf, "%s%s", prefix, g_file_info_get_name(child));
188 				gtk_list_store_insert_with_values(cm, &iter, -1, 0, buf, -1);
189 			}
190 			g_object_unref(G_OBJECT(child));
191 		}
192 		g_strlcpy(dp->complete.prefix, prefix, sizeof dp->complete.prefix);
193 		g_object_unref(fen);
194 	}
195 	g_object_unref(G_OBJECT(dir));
196 }
197 
completion_match_function(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,gpointer user)198 static gboolean completion_match_function(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user)
199 {
200 	gchar	*mv;
201 
202 	gtk_tree_model_get(gtk_entry_completion_get_model(completion), iter, 0, &mv, -1);
203 	if(mv != NULL)
204 	{
205 		gsize		klen = strlen(key);	/* NOTE: This is in *bytes*, not UTF-8 chars. */
206 		gboolean	eq = memcmp(key, mv, klen) == 0;
207 
208 		g_free(mv);
209 		return eq;
210 	}
211 	return FALSE;
212 }
213 
214 /* ----------------------------------------------------------------------------------------- */
215 
216 /* 2000-02-03 -	Here's the handler for the optional "huge parent" button, in the pane margin. */
evt_hugeparent_clicked(GtkWidget * wid,gpointer user)217 static void evt_hugeparent_clicked(GtkWidget *wid, gpointer user)
218 {
219 	DirPane	*dp = user;
220 
221 	csq_execute(dp->main, dp->index == 0 ? "ActivateLeft" : "ActivateRight");
222 	csq_execute(dp->main, "DirParent");
223 }
224 
225 /* 1998-09-15 -	This handler gets run when user clicks the "up" button next to path entry. */
evt_parent_clicked(GtkWidget * wid,gpointer user)226 static void evt_parent_clicked(GtkWidget *wid, gpointer user)
227 {
228 	DirPane	*dp = user;
229 
230 	csq_execute(dp->main, dp->index == 0 ? "ActivateLeft" : "ActivateRight");
231 	csq_execute(dp->main, "DirParent");
232 }
233 
234 /* 2008-09-13 -	A simple rewrite of the older code, considering the switch to a GtkComboBoxEntry
235  *		widget for the path entry/history tasks.
236 */
evt_path_new(GtkWidget * wid,gpointer user)237 static void evt_path_new(GtkWidget *wid, gpointer user)
238 {
239 	DirPane	*dp = user;
240 
241 	dp_activate(dp);
242 	csq_execute_format(dp->main, "DirEnter 'dir=%s'", stu_escape(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dp->path))))));
243 	dp_path_unfocus(dp);
244 }
245 
evt_path_changed(GtkWidget * wid,gpointer user)246 static void evt_path_changed(GtkWidget *wid, gpointer user)
247 {
248 	gint		index;
249 	const gchar	*now;
250 
251 	now = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(wid))));
252 	index = gtk_combo_box_get_active(GTK_COMBO_BOX(wid));
253 	if(index < 0)
254 	{
255 		gchar	*ptr;
256 
257 		/* Build "prefix" version of the current path. The prefix is defined, for completion
258 		 * purposes, as the text up to and including the right-most directory separator. We
259 		 * do this in non-dynamic storage, for speed.
260 		*/
261 		ptr = g_utf8_strrchr(now, -1, G_DIR_SEPARATOR);
262 		if(ptr != NULL)
263 		{
264 			gchar	buf[1024];
265 			size_t	plen = ptr - now + 1;
266 			if(plen < sizeof buf)
267 			{
268 				memcpy(buf, now, plen);
269 				buf[plen] = '\0';
270 				/* Now, we have a UTF-8 prefix in buf, which is perfect. */
271 				completion_update(user, buf);
272 			}
273 		}
274 	}
275 	else
276 		evt_path_new(wid, user);
277 }
278 
279 /* 2008-09-20 -	Cut away Tab handling, we now use the slower but more modern complete-as-you type style. */
evt_path_key_press(GtkWidget * wid,GdkEventKey * evt,gpointer user)280 static gboolean evt_path_key_press(GtkWidget *wid, GdkEventKey *evt, gpointer user)
281 {
282 	DirPane	*dp = user;
283 
284 	if(evt->keyval == GDK_KEY_Escape)
285 		dp_path_unfocus(dp);
286 	return FALSE;
287 }
288 
289 /* 1998-05-27 -	I had forgotten about the possibility for users to click in the path entry widget, and
290 **		by doing so circumventing my clever (hairy) flag system. This remedies that.
291 ** 1998-05-29 -	Changed polarity of operation. Was broken. Sloppy me.
292 */
evt_path_focus(GtkWidget * wid,GdkEventFocus * ev,gpointer user)293 static gboolean evt_path_focus(GtkWidget *wid, GdkEventFocus *ev, gpointer user)
294 {
295 	DirPane	*dp = user;
296 
297 	kbd_context_detach(dp->main->gui->kbd_ctx, GTK_WINDOW(dp->main->gui->window));
298 	dp_activate(dp);
299 
300 	g_signal_connect(G_OBJECT(wid), "key_press_event", G_CALLBACK(evt_path_key_press), dp);
301 
302 	return FALSE;
303 }
304 
evt_path_unfocus(GtkWidget * wid,GdkEventFocus * ev,gpointer user)305 static gboolean evt_path_unfocus(GtkWidget *wid, GdkEventFocus *ev, gpointer user)
306 {
307 	DirPane	*dp = user;
308 
309 	kbd_context_attach(dp->main->gui->kbd_ctx, GTK_WINDOW(dp->main->gui->window));
310 
311 	g_signal_handlers_disconnect_by_func(G_OBJECT(wid), G_CALLBACK(evt_path_key_press), dp);
312 
313 	return FALSE;
314 }
315 
316 /* 1998-12-19 -	The tiny little "hide" button was clicked. Act. */
evt_hide_clicked(GtkWidget * wid,gpointer user)317 static void evt_hide_clicked(GtkWidget *wid, gpointer user)
318 {
319 	DirPane	*dp = user;
320 
321 	dp_activate(dp);
322 	dp->main->cfg.dp_format[dp->index].hide_allowed = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
323 	csq_execute(dp->main, "DirRescan");
324 }
325 
326 /* ----------------------------------------------------------------------------------------- */
327 
328 /* 1999-03-06 -	The given <dp> has been double-clicked. Time to execute some fun action,
329 **		namely the Default for the style of the clicked row.
330 */
doubleclick(DirPane * dp)331 static void doubleclick(DirPane *dp)
332 {
333 	csq_execute(dp->main, "FileAction");
334 	if(chd_get_running(NULL) == NULL)	/* If command sequence finished, reset. Else keep around. */
335 		dp->dbclk_row = -1;
336 }
337 
338 /* 1999-05-13 -	Simulate a double click on <row> in <dp>. Handy for use by the focusing
339 **		module.
340 */
dp_dbclk_row(DirPane * dp,gint row)341 void dp_dbclk_row(DirPane *dp, gint row)
342 {
343 	if(row != -1)
344 	{
345 		dp->dbclk_row = row;
346 		doubleclick(dp);
347 	}
348 }
349 
350 /* 1999-03-04 -	Rewritten. Now very much simpler. :) */
dp_select(DirPane * dp,const DirRow2 * row)351 void dp_select(DirPane *dp, const DirRow2 *row)
352 {
353 	gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), (GtkTreeIter *) row);
354 }
355 
356 /* 1999-03-04 -	Select all rows. Handy for the SelectAll command. Since selection management
357 **		is now largely done by the GtkCList widget for us, this is not rocket science.
358 ** 2000-09-16 -	Noticed that using gtk_clist_select_all() reset the vertical scroll of the list
359 **		to zero, which annoyed me. This happens regardless of callback. Did a work around.
360 */
dp_select_all(DirPane * dp)361 void dp_select_all(DirPane *dp)
362 {
363 	gtk_tree_selection_select_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)));
364 }
365 
366 /* 1999-03-04 -	Rewritten in a lot simpler way. */
dp_unselect(DirPane * dp,const DirRow2 * row)367 void dp_unselect(DirPane *dp, const DirRow2 *row)
368 {
369 	gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), (GtkTreeIter *) row);
370 	/* FIXME: Doesn't handle double-click specifically, might be bad. */
371 }
372 
373 /* 1999-03-04 -	Unselect all rows. Real simple. */
dp_unselect_all(DirPane * dp)374 void dp_unselect_all(DirPane *dp)
375 {
376 	dp->dbclk_row = -1;
377 	gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)));
378 }
379 
380 /* 2000-03-18 -	Since the select/unselect functions no longer return success or failure, the check
381 **		is done explicitly here instead. Much better, really.
382 */
dp_toggle(DirPane * dp,const DirRow2 * row)383 void dp_toggle(DirPane *dp, const DirRow2 *row)
384 {
385 	if(gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), (GtkTreeIter *) row))
386 		dp_unselect(dp, row);
387 	else
388 		dp_select(dp, row);
389 }
390 
391 /* 1998-06-18 -	This handles the situation when a user clicks on a row in a directory pane. Might envoke
392 **		action on files/dirs.
393 ** 1998-06-04 -	Added support for a little menu popping up when the right button is pressed. First it changes
394 **		to the given pane.
395 ** 1998-06-17 -	Added shift-sensing to extend the current selection. Not so crucial in my mind, since dragging
396 **		is so supported. But, nice never the less (and somewhat standard, so people might expect it).
397 ** 1998-06-17 -	Added control-sensing to "extend" the current UNselection. Cool?
398 ** 1999-03-05 -	Simplified since we now use GTK+'s built-in CList selection.
399 ** 2003-10-08 -	Implemented Click-M-Click recognition.
400 ** 2009-11-07 -	Simplified for GTK+ 2.0 implementation of the main dirpane list widget. Dropped customized selection
401 **		support (i.e. click and drag to multi-select); now it's always "system default" mode.
402 */
evt_dirpane_button_press(GtkWidget * wid,GdkEventButton * event,gpointer user)403 static gboolean evt_dirpane_button_press(GtkWidget *wid, GdkEventButton *event, gpointer user)
404 {
405 	static DirPane	*last_dp = NULL;
406 	static GTimeVal	last_time;
407 	const gchar	*mcmd;
408 	DirPane		*dp = user;
409 	const gboolean	change = last_dp && dp != last_dp;
410 	GTimeVal	now;
411 
412 	/* Handle commands mapped to mouse buttons. This includes the RMB menu, typically. */
413 	if((event->type == GDK_BUTTON_PRESS) && (mcmd = ctrl_mouse_map(dp->main->cfg.ctrlinfo, event)) != NULL)
414 	{
415 		dp_activate_queued(dp);
416 		last_dp = dp;
417 		evt_event_set((GdkEvent *) event);
418 		csq_execute(dp->main, mcmd);
419 		evt_event_clear(event->type);
420 		return TRUE;
421 	}
422 
423 	/* Look for click-m-click, i.e. a rapid click in opposite pane after a selection. */
424 	g_get_current_time(&now);
425 	if(change)
426 	{
427 		const gchar *cmd = ctrl_clickmclick_get_cmdseq(dp->main->cfg.ctrlinfo);
428 		if(cmd && *cmd)
429 		{
430 			const gfloat	elapsed = 1E-6f * (now.tv_usec - last_time.tv_usec) + (now.tv_sec - last_time.tv_sec);
431 
432 			if(elapsed < ctrl_clickmclick_get_delay(dp->main->cfg.ctrlinfo))
433 			{
434 				csq_execute(dp->main, cmd);
435 				return TRUE;
436 			}
437 		}
438 	}
439 
440 	dp_activate_queued(dp);
441 	last_dp = dp;
442 	last_time = now;
443 	return FALSE;
444 }
445 
446 /* 2009-11-07 -	User clicked a column; update sorting data. Much easier now with GTK+ 2. */
evt_pane_sort_column_clicked(GtkTreeSortable * sortable,gpointer user)447 static void evt_pane_sort_column_clicked(GtkTreeSortable *sortable, gpointer user)
448 {
449 	DirPane		*dp = user;
450 	DPSort		*sort;
451 	gint		column;
452 	GtkSortType	type;
453 
454 	dp_activate(dp);
455 	gtk_tree_sortable_get_sort_column_id(sortable, &column, &type);
456 
457 	sort = &dp->main->cfg.dp_format[dp->index].sort;
458 	sort->content = column;
459 	sort->invert = (type == GTK_SORT_DESCENDING);
460 }
461 
462 /* ----------------------------------------------------------------------------------------- */
463 
464 /* 1999-04-27 -	Redisplay given pane. Will totally clear the GtkCList widget, and reformat
465 **		all row data into it. Does *not* resort the pane's contents; use (the new)
466 **		dp_resort() function for that. Also does *not* preserve the set of selected
467 **		rows or the vertical position; use dp_redisplay_preserve() for that.
468 */
dp_redisplay(DirPane * dp)469 void dp_redisplay(DirPane *dp)
470 {
471 	g_warning("dp_redisplay() totally non-implemented");
472 }
473 
474 /* 1999-04-27 -	Redisplay given pane, keeping both the selected set and the vertical position.
475 **		This is the one to use in most cases.
476 */
dp_redisplay_preserve(DirPane * dp)477 void dp_redisplay_preserve(DirPane *dp)
478 {
479 	DHSel	*sel;
480 	gfloat	vpos;
481 
482 	sel  = dph_dirsel_new(dp);
483 	vpos = dph_vpos_get(dp);
484 	dp_redisplay(dp);
485 	dph_vpos_set(dp, vpos);
486 	dph_dirsel_apply(dp, sel);
487 	dph_dirsel_destroy(sel);
488 }
489 
490 /* ----------------------------------------------------------------------------------------- */
491 
492 /* 1998-05-25 -	Moved this old workhorse into the new dirpane module, and discovered that
493 **		it wasn't commented. Lazy me. Also modified its prototype, to take the
494 **		DirPane to display rather than figuring it out from MainInfo.
495 */
dp_show_stats(DirPane * dp)496 void dp_show_stats(DirPane *dp)
497 {
498 	MainInfo *min;
499 
500 	if((min = dp->main) != NULL)
501 	{
502 		gchar		buf[256], selbuf[32] = "", totbuf[32] = "", *ptr = buf;
503 		const SelInfo	*sel = &dp->dir.sel;
504 
505 		ptr += g_snprintf(ptr, sizeof buf, _("%u/%u dirs, %u/%u files"),
506 				  sel->num_dirs, dp->dir.tot_dirs,
507 				  sel->num_files, dp->dir.tot_files);
508 		sze_put_offset(selbuf, sizeof selbuf, sel->num_bytes,    SZE_AUTO, 1, ',');
509 		sze_put_offset(totbuf, sizeof totbuf, dp->dir.tot_bytes, SZE_AUTO, 1, ',');
510 		ptr += sprintf(ptr, _(" (%s/%s)"), selbuf, totbuf);
511 		if(dp->dir.fs.valid)
512 		{
513 			gchar	dbuf[32], pbuf[32];
514 
515 			sze_put_offset(dbuf, sizeof dbuf, dp->dir.fs.fs_size - dp->dir.fs.fs_free, SZE_AUTO, 1, ',');
516 			g_snprintf(pbuf, sizeof pbuf, "%.1f%%",
517 				100.0 *
518 				((gdouble) (dp->dir.fs.fs_size - dp->dir.fs.fs_free)
519 				/
520 				(gdouble) dp->dir.fs.fs_size));
521 			ptr += sprintf(ptr, _(", %s (%s) used"), dbuf, pbuf);
522 			sze_put_offset(dbuf, sizeof dbuf, dp->dir.fs.fs_free, SZE_AUTO, 3, ',');
523 			ptr += sprintf(ptr, _(", %s free"), dbuf);
524 		}
525 		if(min->cfg.errors.display != ERR_DISPLAY_TITLEBAR)
526 			gtk_label_set_text(GTK_LABEL(min->gui->top), buf);
527 		else
528 			gui_set_main_title(dp->main, buf);
529 	}
530 }
531 
532 /* 2010-08-03 -	Set the activate-status of the given pane's columns (that's what "col" refers to). Slighly hackish. */
dp_set_col_active(DirPane * dp,gboolean active)533 static void dp_set_col_active(DirPane *dp, gboolean active)
534 {
535 	if(dp != NULL)
536 	{
537 		GtkTreeViewColumn	*column;
538 		guint			i;
539 
540 		for(i = 0; (column = gtk_tree_view_get_column(GTK_TREE_VIEW(dp->view), i)) != NULL; i++)
541 		{
542 			GtkWidget	*header = gtk_tree_view_column_get_button(column);
543 
544 			if(header != NULL)
545  				gtk_widget_set_sensitive(header, active);
546 		}
547 		gtk_widget_set_name(dp->view, active ? "pane-current" : "pane");
548 	}
549 }
550 
551 /* 2002-07-14 -	Do the pane rendering necessary to change active pane from <from> into <to>. */
activate_render(DirPane * from,DirPane * to)552 static void activate_render(DirPane *from, DirPane *to)
553 {
554 	if(from != NULL)
555 		dp_set_col_active(from, FALSE);
556 	if(to != NULL)
557 	{
558 		dp_set_col_active(to, TRUE);
559 		gtk_widget_grab_focus(to->view);
560 		dp_show_stats(to);
561 	}
562 }
563 
564 /* 1999-06-09 -	Activate <dp>, making it the source pane for all operations. Returns TRUE if the activation
565 **		meant that another pane was deactivated, FALSE if <dp> was already the activate pane.
566 */
dp_activate(DirPane * dp)567 gboolean dp_activate(DirPane *dp)
568 {
569 	if(dp != NULL && dp->main->gui->cur_pane != dp)
570 	{
571 		gchar	*name;
572 
573 		activate_render(dp->main->gui->cur_pane, dp);
574 		dp->main->gui->cur_pane = dp;
575 		if(dp->dir.root != NULL && (name = g_file_get_parse_name(dp->dir.root)) != NULL)
576 		{
577 			gchar	tbuf[256];
578 
579 			g_snprintf(tbuf, sizeof tbuf, "%s - gentoo", name);
580 			win_window_set_title(dp->main->gui->window, tbuf);
581 			g_free(name);
582 		}
583 		return TRUE;
584 	}
585 	return FALSE;
586 }
587 
588 /* 2002-07-13 -	This runs when GTK+ is idle, and does the pane activation rendering, once. */
idle_activate(gpointer data)589 static gint idle_activate(gpointer data)
590 {
591 	activate_render(the_activate_queue_info.old_cur, ((MainInfo *) data)->gui->cur_pane);
592 	g_source_remove(the_activate_queue_info.handler);
593 	the_activate_queue_info.handler = 0U;
594 	return 0;
595 }
596 
597 /* 2002-07-13 -	Activate a pane, but queue the rendering until GTK+ is idle. This works around
598 **		a very annoying bug/misfeature which causes "ghost" pane scrolling to occur.
599 **		We only queue the rendering, since that seems to suffice, and we need to really
600 **		change gentoo's idea of "active pane" immediately or things break.
601 */
dp_activate_queued(DirPane * dp)602 static void dp_activate_queued(DirPane *dp)
603 {
604 	if(the_activate_queue_info.handler)
605 		g_source_remove(the_activate_queue_info.handler);
606 	the_activate_queue_info.handler = g_idle_add(idle_activate, dp->main);
607 	the_activate_queue_info.old_cur = dp->main->gui->cur_pane;
608 	dp->main->gui->cur_pane = dp;
609 }
610 
611 /* ----------------------------------------------------------------------------------------- */
612 
613 /* 1999-03-05 -	Answer whether given pane has one or more rows selected or not. */
dp_has_selection(DirPane * dp)614 gboolean dp_has_selection(DirPane *dp)
615 {
616 	if(dp->dbclk_row != -1)
617 		return TRUE;
618 	return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view))) > 0 && !no_repeat;
619 }
620 
621 /* 2011-01-03 -	Determine whether the given pane has a single selected row. */
dp_has_single_selection(const DirPane * dp)622 gboolean dp_has_single_selection(const DirPane *dp)
623 {
624 	return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view))) == 1;
625 }
626 
627 /* 1999-03-06 -	Answer whether given <row> is currently selected. A bit more efficient
628 **		than getting the entire list through dp_get_selection(), but only if
629 **		you're not going to iterate the selection anyway.
630 */
dp_is_selected(DirPane * dp,const DirRow2 * row)631 gboolean dp_is_selected(DirPane *dp, const DirRow2 *row)
632 {
633 	if(dp == NULL || row == NULL)
634 		return FALSE;
635 	return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), (GtkTreeIter *) row);
636 }
637 
append_row(GSList * list,DirPane * dp,gint index)638 static GSList * append_row(GSList *list, DirPane *dp, gint index)
639 {
640 	gchar		buf[16];
641 	GtkTreeIter	*iter;
642 
643 	if((iter = g_malloc(sizeof *iter)) != NULL)
644 	{
645 		g_snprintf(buf, sizeof buf, "%d", index);
646 		if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dp->dir.store), iter, buf))
647 			return g_slist_prepend(list, iter);
648 		else
649 			g_warning("**Unable to get iter for selection");
650 	}
651 	return list;
652 }
653 
cb_append_row(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user)654 static void cb_append_row(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user)
655 {
656 	GSList		**sel = user;
657 	GtkTreeIter	*di;
658 
659 	if((di = g_malloc(sizeof *di)) != NULL)
660 	{
661 		*di = *iter;
662 		*sel = g_slist_prepend(*sel, di);
663 	}
664 }
665 
666 /* 1999-03-04 -	Return a GSList of selected rows. Each item's data member is a DirRow.
667 **		Knows how to deal with a double click, too. This is going to be the new
668 **		interface for all commands.
669 ** 1999-03-15 -	Now sorts the rows in address order, since otherwise the selection retains
670 **		the order in which it was done by the user, and that is simply confusing.
671 */
dp_get_selection(DirPane * dp)672 GSList * dp_get_selection(DirPane *dp)
673 {
674 	GSList	*sel = NULL;
675 
676 	no_repeat = FALSE;
677 
678 	if(dp->dbclk_row != -1)
679 		sel = append_row(sel, dp, dp->dbclk_row);
680 	else
681 		gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), cb_append_row, &sel);
682 
683 	if(sel)
684 		fam_rescan_block();
685 	sel = g_slist_reverse(sel);
686 
687 	return sel;
688 }
689 
690 /* 1999-03-06 -	Get the "full" selection, regardless of whether there's a double clicked row or not.
691 **		This should only be used if you really know what you're doing, and never by actual
692 **		commands (which need the double-click support).
693 */
dp_get_selection_full(const DirPane * dp)694 GSList * dp_get_selection_full(const DirPane *dp)
695 {
696 	GSList	*sel = NULL;
697 
698 	gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), cb_append_row, &sel);
699 	if(sel)
700 		fam_rescan_block();
701 	return sel;
702 }
703 
704 /* 1999-03-04 -	Free a selection list, handy when you're done traversing it. */
dp_free_selection(GSList * sel)705 void dp_free_selection(GSList *sel)
706 {
707 	if(sel != NULL)
708 	{
709 		GSList	*iter;
710 
711 		for(iter = sel; iter != NULL; iter = g_slist_next(iter))
712 			g_free(iter->data);
713 		g_slist_free(sel);
714 		fam_rescan_unblock();
715 	}
716 }
717 
718 /* ----------------------------------------------------------------------------------------- */
719 
720 /* 2009-10-01 -	Count entries as they are read in. */
statistics_add_row(DirPane * dp,const GFileInfo * fi)721 static void statistics_add_row(DirPane *dp, const GFileInfo *fi)
722 {
723 	if(g_file_info_get_file_type((GFileInfo *) fi) == G_FILE_TYPE_DIRECTORY)
724 		dp->dir.tot_dirs++;
725 	else
726 		dp->dir.tot_files++;
727 	dp->dir.tot_bytes += g_file_info_get_size((GFileInfo *) fi);
728 }
729 
730 /* 2009-10-16 -	Add given row to the selection. */
selection_add_row(DirPane * dp,const GFileInfo * fi)731 static void selection_add_row(DirPane *dp, const GFileInfo *fi)
732 {
733 	if(g_file_info_get_file_type((GFileInfo *) fi) == G_FILE_TYPE_DIRECTORY)
734 		dp->dir.sel.num_dirs++;
735 	else
736 		dp->dir.sel.num_files++;
737 	dp->dir.sel.num_bytes += g_file_info_get_size((GFileInfo *) fi);
738 }
739 
740 /* 1999-04-08 -	Clear the selected statistics. */
clear_selection_stats(SelInfo * sel)741 static void clear_selection_stats(SelInfo *sel)
742 {
743 	if(sel != NULL)
744 	{
745 		sel->num_dirs = 0;
746 		sel->num_files = 0;
747 		sel->num_bytes = 0;
748 	}
749 }
750 
751 /* 1999-04-09 -	Clear the total statistics fields for <dp>. */
clear_total_stats(DirPane * dp)752 static void clear_total_stats(DirPane *dp)
753 {
754 	dp->dir.tot_files = dp->dir.tot_dirs = 0;
755 	dp->dir.tot_bytes = 0;
756  	dp->dir.fs.valid = FALSE;
757 }
758 
759 /* 1999-01-03 -	Clear the statistics for <dp>. Useful when the pane's path is about to change. */
clear_stats(DirPane * dp)760 static void clear_stats(DirPane *dp)
761 {
762 	clear_total_stats(dp);
763 	clear_selection_stats(&dp->dir.sel);
764 	dp->last_row = dp->last_row2 = -1;
765 }
766 
767 /* 1999-04-08 -	Update the selection statistics for <dp>, by simply flushing them and recomputing from
768 **		scratch. Does NOT use dp_get_selection(), for performance reasons (this is run on every
769 **		unselection).
770 */
update_selection_stats(DirPane * dp)771 static void update_selection_stats(DirPane *dp)
772 {
773 	GtkTreeModel	*model = dp_get_tree_model(dp);
774 	GtkTreeIter	iter;
775 	SelInfo		*sel = &dp->dir.sel;
776 
777 	clear_selection_stats(sel);
778 	if(gtk_tree_model_get_iter_first(model, &iter))
779 	{
780 		GtkTreeSelection	*ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view));
781 
782 		/* Don't use gtk_tree_selection_get_selected_rows(), it's kind of costly. */
783 		do
784 		{
785 			if(gtk_tree_selection_iter_is_selected(ts, &iter))
786 			{
787 				GFileInfo	*fi;
788 
789 				gtk_tree_model_get(model, &iter, COL_INFO, &fi, -1);
790 				selection_add_row(dp, fi);
791 			}
792 		} while(gtk_tree_model_iter_next(model, &iter));
793 	}
794 }
795 
796 /* 1999-04-09 -	Update the tot_XXX fields in <dp>'s directory statistics. Handy after a (Get|Clear)Size. */
dp_update_stats(DirPane * dp)797 void dp_update_stats(DirPane *dp)
798 {
799 	GtkTreeModel	*model = dp_get_tree_model(dp);
800 	GtkTreeIter	iter;
801 
802 	clear_total_stats(dp);
803 	clear_selection_stats(&dp->dir.sel);
804 	if(gtk_tree_model_get_iter_first(model, &iter))
805 	{
806 		GtkTreeSelection	*ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view));
807 
808 		do
809 		{
810 			GFileInfo	*fi;
811 
812 			gtk_tree_model_get(model, &iter, COL_INFO, &fi, -1);
813 			statistics_add_row(dp, fi);
814 			if(gtk_tree_selection_iter_is_selected(ts, &iter))
815 				selection_add_row(dp, fi);
816 		} while(gtk_tree_model_iter_next(model, &iter));
817 	}
818 }
819 
820 /* 1999-03-30 -	Update information about filesystem for given <dp>. */
update_fs_info(DirPane * dp)821 static void update_fs_info(DirPane *dp)
822 {
823 	GFileInfo	*fi;
824 
825 	if((fi = g_file_query_filesystem_info(dp->dir.root, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE "," G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, NULL)) != NULL)
826 	{
827 		dp->dir.fs.fs_size = g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
828 		dp->dir.fs.fs_free = g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
829 	}
830 	dp->dir.fs.valid = (fi != NULL) && dp->dir.fs.fs_size > 0;
831 }
832 
dp_path_clear(DirPane * dp)833 void dp_path_clear(DirPane *dp)
834 {
835 	GtkListStore	*store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(dp->path)));
836 
837 	gtk_list_store_clear(store);
838 }
839 
dp_path_focus(DirPane * dp)840 void dp_path_focus(DirPane *dp)
841 {
842 	gtk_widget_grab_focus(dp->path);
843 }
844 
dp_path_unfocus(DirPane * dp)845 void dp_path_unfocus(DirPane *dp)
846 {
847 	gtk_widget_grab_focus(dp->view);
848 }
849 
850 /* 2013-07-09 -	Clear the FType column. This is useful when re-scanning panes after editing the
851  *		FType definitions in the configuration window. It prevents crashes due to stale
852  *		pointers being followed.
853 */
untype_rows(DirPane * dp)854 static void untype_rows(DirPane *dp)
855 {
856 	GtkTreeModel	*model = dp_get_tree_model(dp);
857 	GtkTreeIter	iter;
858 
859 	if(gtk_tree_model_get_iter_first(model, &iter))
860 	{
861 		do
862 		{
863 			gtk_list_store_set(GTK_LIST_STORE(model), &iter, COL_FTYPE, NULL, -1);
864 		} while(gtk_tree_model_iter_next(model, &iter));
865 	}
866 }
867 
868 /* 2010-06-05 -	Internal "do it" routine to enter a new directory. Uses GIO error handling for all I/O. */
do_enter_dir(DirPane * dp,const gchar * path,GError ** err)869 static gboolean do_enter_dir(DirPane *dp, const gchar *path, GError **err)
870 {
871 	GFile		*here;
872 	GFileEnumerator	*fe;
873 
874 	if((here = g_vfs_parse_name(dp->main->vfs.vfs, path)) == NULL)
875 		return FALSE;
876 
877 	if((fe = g_file_enumerate_children(here, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, err)) != NULL)
878 	{
879 		GFileInfo	*fi, *tfi;
880 		GtkTreeIter	iter;
881 
882 		if(dp->dir.root != NULL)
883 			g_object_unref(dp->dir.root);
884 		dp->dir.root = here;
885 		untype_rows(dp);	/* Clearing is not atomic, and can cause redraws. */
886 		gtk_list_store_clear(dp->dir.store);
887 		g_strlcpy(dp->dir.path, path, sizeof dp->dir.path);
888 
889 		/* Decide whether the current directory is "local". */
890 		if((fi = g_file_query_filesystem_info(here, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, NULL, err)) != NULL)
891 		{
892 			dp->dir.is_local = g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW) != G_FILESYSTEM_PREVIEW_TYPE_NEVER;
893 			g_object_unref(fi);
894 		}
895 		else
896 			dp->dir.is_local = TRUE;
897 
898 		while((fi = g_file_enumerator_next_file(fe, NULL, err)) != NULL)
899 		{
900 			const gchar	*name = g_file_info_get_name(fi), *fn, *tname;
901 			guint32		flags;
902 			gchar		*ck;
903 			gunichar	initial;
904 
905 			/* Apply classic Hide filtering rule. Nice. */
906 			if(!(dp->main->cfg.dir_filter(name) && fut_check_hide(dp, name)))
907 			{
908 				g_object_unref(fi);
909 				continue;
910 			}
911 			flags = (g_file_info_get_file_type(fi) != G_FILE_TYPE_DIRECTORY) ? DPRF_HAS_SIZE : 0;
912 			fn = g_file_info_get_display_name(fi);
913 			initial = g_utf8_get_char(fn);
914 			ck = g_utf8_collate_key_for_filename(fn, -1);
915 			/* The modern default is to be case-insensitive, glib's collation keys always are.
916 			 * For us bearded old-sk00l folks, let's implement a kinda-sorta case-sensitive
917 			 * collation key, based on the initial only. I think this will be Good Enough.
918 			 *
919 			 * It is a bit limited (@test and @TEST won't sort as you'd expect), but short.
920 			*/
921 			if(!dp->main->cfg.dp_format[dp->index].sort.nocase)
922 			{
923 				const gchar	ORDER_PUNCTUATION = '\1';
924 				const gchar	ORDER_UPPERCASE = '\2';
925 				const gchar	ORDER_LOWERCASE = '\3';
926 
927 				const size_t	out = strlen(ck) + 2;
928 				gchar		*csck, *put;
929 
930 				if((csck = g_malloc(out)) != NULL)
931 				{
932 					put = csck;
933 					if(g_unichar_ispunct(initial))
934 						*put++ = ORDER_PUNCTUATION;
935 					else if(g_unichar_isupper(initial))
936 						*put++ = ORDER_UPPERCASE;
937 					else if(g_unichar_islower(initial))
938 						*put++ = ORDER_LOWERCASE;
939 					memcpy(put, ck, out - 1);
940 					g_free(ck);
941 					ck = csck;
942 				}
943 			}
944 			/* If the local object looks like a symbolic link, chase down the link target too.
945 			 * This is not exactly Captain Slim on the memory side, but very handy especially
946 			 * since we want to do things like style links to directories like directories.
947 			 * Pre-GIO versions of gentoo did this too, by storing extra struct stats for links.
948 			 */
949 			if((tname = g_file_info_get_symlink_target(fi)) != NULL)
950 			{
951 				GFile	*target;
952 
953 				/* Try to be clever and figure out how to interpret the link target text. */
954 				if(strstr(tname, "://") != NULL)	/* Does it look like an URI? */
955 				{
956 					target = g_vfs_get_file_for_uri(dp->main->vfs.vfs, tname);
957 				}	/* No, does it look like a relative path, then? */
958 				else if(strncmp(tname, "./", 2) == 0 || strncmp(tname, "..", 2) == 0)
959 				{
960 					target = g_file_resolve_relative_path(here, tname);
961 				}
962 				else	/* Assume just bare name, meaning link to child with same root. */
963 				{
964 					target = g_file_get_child(here, tname);
965 				}
966 				if(target != NULL)
967 				{
968 					GError	*terr = NULL;
969 
970 					/* If we got something, try to query the info for it. */
971 					tfi = g_file_query_info(target, "standard::*", G_FILE_QUERY_INFO_NONE, NULL, &terr);
972 					g_object_unref(G_OBJECT(target));
973 					if(tfi != NULL && terr == NULL)
974 					{
975 						flags |= DPRF_LINK_EXISTS;
976 						if(g_file_info_get_file_type(tfi) == G_FILE_TYPE_DIRECTORY)
977 							flags |= DPRF_LINK_TO_DIR;
978 					}
979 					g_clear_error(&terr);
980 				}
981 					else
982 						tfi = NULL;
983 			}
984 			else
985 				tfi = NULL;
986 
987 			gtk_list_store_insert_with_values(dp->dir.store, &iter, -1,
988 					COL_FILE, NULL,
989 					COL_INFO, fi,
990 					COL_FLAGS, flags,
991 					COL_FILENAME_COLLATE_KEY, ck,
992 					(tfi != NULL) ? COL_LINK_TARGET_INFO : -1, tfi,	/* Not too clever, I hope. */
993 					-1);
994 			statistics_add_row(dp, fi);
995 			g_free(ck);
996 			if(tfi)
997 				g_object_unref(G_OBJECT(tfi));
998 			g_object_unref(G_OBJECT(fi));
999 		}
1000 		g_object_unref(fe);
1001 	}
1002 	if(fe != NULL)
1003 	{
1004 		GtkTreeIter	iter;
1005 
1006 		typ_identify_begin(dp);
1007 		if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dp->dir.store), &iter))
1008 		{
1009 			do
1010 			{
1011 				typ_identify(dp, &iter);
1012 			} while(gtk_tree_model_iter_next(GTK_TREE_MODEL(dp->dir.store), &iter));
1013 		}
1014 		if(!typ_identify_end(dp))
1015 			fe = NULL;
1016 	}
1017 	return fe != NULL;
1018 }
1019 
1020 /* 2003-11-14 -	Brand new version of this very old workhorse. Now reads in the directory in a single
1021 **		pass, using realloc() to grow various buffers on the fly. Slightly more wasteful of
1022 **		memory than the old two-pass version, but less code, more robust, and possibly a
1023 **		little bit faster. Returns TRUE on success.
1024 */
dp_enter_dir(DirPane * dp,const gchar * path)1025 gboolean dp_enter_dir(DirPane *dp, const gchar *path)
1026 {
1027 	GError		*err = NULL;
1028 	gboolean	ok;
1029 
1030 	clear_stats(dp);
1031 
1032 	completion_clear(dp);
1033 
1034 	the_sort = dp->main->cfg.dp_format[dp->index].sort;	/* Must be accessible by qsort() callbacks. */
1035 
1036 	if((ok = do_enter_dir(dp, path, &err)) == TRUE)
1037 	{
1038 		const gboolean	has_parent = g_file_has_parent(dp->dir.root, NULL);
1039 
1040 		update_fs_info(dp);
1041 		gtk_widget_set_sensitive(dp->parent, has_parent);
1042 		if(dp->hparent != NULL)
1043 			gtk_widget_set_sensitive(dp->hparent, has_parent);
1044 	}
1045 	else if(err)
1046 		err_set_gerror(dp->main, &err, path, NULL);
1047 
1048 	return ok;
1049 }
1050 
1051 /* ----------------------------------------------------------------------------------------- */
1052 
1053 /* 2002-07-20 -	Rescan contents of given pane.
1054 ** NOTE NOTE	This routine does NOT change the error status, i.e. it does not call anything
1055 **		in the error module. This is important, since it's used in the exiting code
1056 **		of generic commands, for instance.
1057 ** 2003-09-26 -	Don't activate <dp> for refresh, just do it anyway. Faster, hopefully safe.
1058 */
dp_rescan(DirPane * dp)1059 void dp_rescan(DirPane *dp)
1060 {
1061 	dph_state_save(dp);
1062 	dp_enter_dir(dp, dp->dir.path);
1063 	dph_state_restore(dp);
1064 }
1065 
1066 /* 2002-07-23 -	Special "weaker" version of dp_rescan(), with the added semantic difference that
1067 **		this is only ever called as part of the "clean-up" *after* a command has finished.
1068 **		The intent is to catch any changes to the pane done by the command, such as a
1069 **		deleted, renamed, moved or otherwise changed file. The reason it has a separate
1070 **		entrypoint is that with FAM, this needs to be stopped.
1071 ** 2009-11-15 -	Rewritten, now that "FAM" is actually just an alias for GIO monitoring.
1072 */
dp_rescan_post_cmd(DirPane * dp)1073 void dp_rescan_post_cmd(DirPane *dp)
1074 {
1075 	if(!fam_is_monitored(dp))
1076 		dp_rescan(dp);
1077 }
1078 
1079 /* 2009-10-16 -	Rescans a single row of a pane. Handy for ClearSize, for instance. Not to be used for bulk reading. */
dp_rescan_row(DirPane * dp,const DirRow2 * row,GError ** error)1080 gboolean dp_rescan_row(DirPane *dp, const DirRow2 *row, GError **error)
1081 {
1082 	GFile		*file;
1083 	GFileInfo	*fi = NULL;
1084 
1085 	if(dp == NULL || row == NULL)
1086 		return FALSE;
1087 	if((file = dp_get_file_from_row(dp, row)) != NULL)
1088 	{
1089 		GtkTreeModel	*m = dp_get_tree_model(dp);
1090 
1091 		if((fi = g_file_query_info(file, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error)) != NULL)
1092 		{
1093 			guint32	flags;
1094 
1095 			/* Computes new flags value; clears HAS_SIZE for directories. */
1096 			gtk_tree_model_get(m, (GtkTreeIter *) row, COL_FLAGS, &flags, -1);
1097 			flags &= ~DPRF_HAS_SIZE;
1098 			flags |= (g_file_info_get_file_type(fi) != G_FILE_TYPE_DIRECTORY) ? DPRF_HAS_SIZE : 0;
1099 			gtk_list_store_set(GTK_LIST_STORE(m), (GtkTreeIter *) row, COL_INFO, fi, COL_FLAGS, flags, -1);
1100 		}
1101 		g_object_unref(file);
1102 	}
1103 	return fi != NULL;
1104 }
1105 
1106 /* ----------------------------------------------------------------------------------------- */
1107 
1108 /* 2008-09-20 -	Sets the history strings, available in the combo box. This is to prevent
1109 **		other modules (dirhistory, I'm looking at you!) from poking the widgets
1110 **		directly. Abstract, hide, and so on.
1111 ** 2010-02-09 -	Converted to assume too much about dirhistory's internals. The list is now
1112 **		supposed to point at structures, that begin with a gchar * URI. Yes.
1113 */
dp_history_set(DirPane * dp,const GList * locations)1114 void dp_history_set(DirPane *dp, const GList *locations)
1115 {
1116 	GtkListStore	*store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(dp->path)));
1117 	GtkTreeIter	iter;
1118 	guint		i;
1119 
1120 	gtk_list_store_clear(store);
1121 	for(i = 0; locations != NULL; locations = g_list_next(locations))
1122 	{
1123 		const gchar	*name = dph_entry_get_parse_name(locations->data);
1124 
1125 		if(*name)
1126 		{
1127 			gtk_list_store_insert_with_values(store, &iter, -1, 0, name, -1);
1128 			++i;
1129 		}
1130 	}
1131 	if(i > 0)
1132 	{
1133 		/* Was there any history set? If so, also jump to the first entry. */
1134 		g_signal_handler_block(G_OBJECT(gtk_bin_get_child(GTK_BIN(dp->path))), dp->sig_path_activate);
1135 		g_signal_handler_block(G_OBJECT(dp->path), dp->sig_path_changed);
1136 		gtk_combo_box_set_active(GTK_COMBO_BOX(dp->path), 0);
1137 		g_signal_handler_unblock(G_OBJECT(dp->path), dp->sig_path_changed);
1138 		g_signal_handler_unblock(G_OBJECT(gtk_bin_get_child(GTK_BIN(dp->path))), dp->sig_path_activate);
1139 	}
1140 }
1141 
1142 /* ----------------------------------------------------------------------------------------- */
1143 
1144 /* 2009-07-04 -	Refreshes the split, making sure it's correctly sized according to current settings. */
dp_split_refresh(MainInfo * min)1145 void dp_split_refresh(MainInfo *min)
1146 {
1147 	GdkWindow	*pwin;
1148 	gint		np, pos = -1;
1149 
1150 	if(min == NULL || min->gui == NULL || min->gui->panes == NULL)
1151 		return;
1152 	if(!dp_realized(min))
1153 		return;
1154 
1155 	/* Inspect the size of the GtkPaned that holds the panes; the window's size
1156 	** doesn't work for vertical due to buttons. This is slightly hackish.
1157 	*/
1158 	pwin = gtk_widget_get_window(min->gui->panes);
1159 	np = min->cfg.dp_paning.orientation == DPORIENT_HORIZ ? gdk_window_get_width(pwin) : gdk_window_get_height(pwin);
1160 
1161 	/* Compute the proper new position, depending on the active paning mode. */
1162 	switch(min->cfg.dp_paning.mode)
1163 	{
1164 		case DPSPLIT_FREE:
1165 			return;
1166 		case DPSPLIT_RATIO:
1167 			pos = np * min->cfg.dp_paning.value;
1168 			break;
1169 		case DPSPLIT_ABS_LEFT:
1170 			pos = min->cfg.dp_paning.value;
1171 			break;
1172 		case DPSPLIT_ABS_RIGHT:
1173 			pos = np - min->cfg.dp_paning.value;
1174 			break;
1175 	}
1176 	if(pos >= 0)
1177 	{
1178 		g_signal_handler_block(G_OBJECT(min->gui->window), min->gui->sig_main_configure);
1179 		g_signal_handler_block(G_OBJECT(min->gui->panes), min->gui->sig_pane_notify);
1180 		gtk_paned_set_position(GTK_PANED(min->gui->panes), pos);
1181 		g_signal_handler_unblock(G_OBJECT(min->gui->panes), min->gui->sig_pane_notify);
1182 		g_signal_handler_unblock(G_OBJECT(min->gui->window), min->gui->sig_main_configure);
1183 	}
1184 }
1185 
1186 /* ----------------------------------------------------------------------------------------- */
1187 
1188 /* 2003-11-08 -	Add a pathwidgtry user. Indirect, just adds a function that is later called
1189 **		(in dp_build()) to construct the widgetry. Returns key to use with show().
1190 */
dp_pathwidgetry_add(PageBuilder func)1191 guint dp_pathwidgetry_add(PageBuilder func)
1192 {
1193 	if(func && !g_slist_find(pathwidgetry_builders, func))
1194 	{
1195 		pathwidgetry_builders = g_slist_append(pathwidgetry_builders, func);
1196 		return g_slist_length(pathwidgetry_builders);
1197 	}
1198 	return 0;
1199 }
1200 
1201 /* 2003-11-08 -	Change to a new page of dirpane path widgetry. Returns page. */
dp_pathwidgetry_show(DirPane * dp,guint key)1202 GtkWidget ** dp_pathwidgetry_show(DirPane *dp, guint key)
1203 {
1204 	GtkWidget	*page;
1205 
1206 	if((page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(dp->notebook), key)) != NULL)
1207 	{
1208 		gtk_notebook_set_current_page(GTK_NOTEBOOK(dp->notebook), key);
1209 		return g_object_get_data(G_OBJECT(page), "dp-pathwidgetry");
1210 	}
1211 	return NULL;
1212 }
1213 
1214 /* ----------------------------------------------------------------------------------------- */
1215 
dp_get_tree_model(const DirPane * dp)1216 GtkTreeModel * dp_get_tree_model(const DirPane *dp)
1217 {
1218 	return (dp != NULL) ? GTK_TREE_MODEL(dp->dir.store) : NULL;
1219 }
1220 
dp_get_file_from_row(const DirPane * dp,const DirRow2 * row)1221 GFile * dp_get_file_from_row(const DirPane *dp, const DirRow2 *row)
1222 {
1223 	if(dp == NULL || row == NULL)
1224 		return NULL;
1225 	return g_file_get_child(dp->dir.root, dp_row_get_name(GTK_TREE_MODEL(dp->dir.store), row));
1226 }
1227 
dp_get_file_from_name(const DirPane * dp,const gchar * name)1228 GFile * dp_get_file_from_name(const DirPane *dp, const gchar *name)
1229 {
1230 	if(dp == NULL || name == NULL)
1231 		return NULL;
1232 	return g_file_get_child(dp->dir.root, name);
1233 }
1234 
dp_get_file_from_name_display(const DirPane * dp,const gchar * name)1235 GFile * dp_get_file_from_name_display(const DirPane *dp, const gchar *name)
1236 {
1237 	if(dp == NULL || name == NULL)
1238 		return NULL;
1239 	return g_file_get_child_for_display_name(dp->dir.root, name, NULL);
1240 }
1241 
row_emit_changed(GtkTreeModel * model,const DirRow2 * row)1242 static void row_emit_changed(GtkTreeModel *model, const DirRow2 *row)
1243 {
1244 	GtkTreePath	*path;
1245 
1246 	if((path = gtk_tree_model_get_path(model, (GtkTreeIter *) row)) != NULL)
1247 	{
1248 		gtk_tree_model_row_changed(model, path, (GtkTreeIter *) row);
1249 		gtk_tree_path_free(path);
1250 	}
1251 }
1252 
dp_row_set_ftype(GtkTreeModel * model,const DirRow2 * row,FType * ft)1253 void dp_row_set_ftype(GtkTreeModel *model, const DirRow2 *row, FType *ft)
1254 {
1255 	gtk_list_store_set(GTK_LIST_STORE(model), (GtkTreeIter *) row, COL_FTYPE, ft, -1);
1256 	row_emit_changed(model, row);
1257 }
1258 
dp_row_set_size(GtkTreeModel * model,const DirRow2 * row,guint64 size)1259 void dp_row_set_size(GtkTreeModel *model, const DirRow2 *row, guint64 size)
1260 {
1261 	GFileInfo	*fi;
1262 
1263 	gtk_tree_model_get(model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1264 	g_file_info_set_size(fi, size);
1265 	/* Notify the owning GtkTreeModel that the data has changed; refreshes TreeViews. */
1266 	row_emit_changed(model, row);
1267 }
1268 
dp_row_set_flag(GtkTreeModel * model,const DirRow2 * row,guint32 mask)1269 void dp_row_set_flag(GtkTreeModel *model, const DirRow2 *row, guint32 mask)
1270 {
1271 	guint32	old;
1272 
1273 	gtk_tree_model_get(model, (GtkTreeIter *) row, COL_FLAGS, &old, -1);
1274 	gtk_list_store_set(GTK_LIST_STORE(model), (GtkTreeIter *) row, COL_FLAGS, old | mask, -1);
1275 }
1276 
dp_row_get_name(const GtkTreeModel * model,const DirRow2 * row)1277 const gchar * dp_row_get_name(const GtkTreeModel *model, const DirRow2 *row)
1278 {
1279 	GFileInfo	*fi;
1280 
1281 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1282 	return g_file_info_get_name(fi);
1283 }
1284 
dp_row_get_name_display(const GtkTreeModel * model,const DirRow2 * row)1285 const gchar * dp_row_get_name_display(const GtkTreeModel *model, const DirRow2 *row)
1286 {
1287 	GFileInfo	*fi;
1288 
1289 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1290 	return g_file_info_get_display_name(fi);
1291 }
1292 
dp_row_get_name_edit(const GtkTreeModel * model,const DirRow2 * row)1293 const gchar * dp_row_get_name_edit(const GtkTreeModel *model, const DirRow2 *row)
1294 {
1295 	GFileInfo	*fi;
1296 
1297 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1298 	return g_file_info_get_edit_name(fi);
1299 }
1300 
dp_row_get_ftype(const GtkTreeModel * model,const DirRow2 * row)1301 FType * dp_row_get_ftype(const GtkTreeModel *model, const DirRow2 *row)
1302 {
1303 	FType	*ft = NULL;
1304 
1305 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_FTYPE, &ft, -1);
1306 	return ft;
1307 }
1308 
1309 /* 2010-11-23 -	Returns the type of the object in the indicated row, or its target if it's a symlink
1310  *		and target is true. If it's a broken symlink, G_FILE_TYPE_UNKNOWN is returned.
1311 */
dp_row_get_file_type(const GtkTreeModel * model,const DirRow2 * row,gboolean target)1312 GFileType dp_row_get_file_type(const GtkTreeModel *model, const DirRow2 *row, gboolean target)
1313 {
1314 	guint32		flags;
1315 	GFileInfo	*fi, *tfi;
1316 
1317 	/* On the assumption that this call is somewhat expensive, grab data for both if-branches. */
1318 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_FLAGS, &flags, COL_INFO, &fi, COL_LINK_TARGET_INFO, &tfi, -1);
1319 	if(target && g_file_info_get_file_type(fi) == G_FILE_TYPE_SYMBOLIC_LINK)
1320 	{
1321 		if((flags & DPRF_LINK_EXISTS) && tfi != NULL)
1322 			return g_file_info_get_file_type(tfi);
1323 		return G_FILE_TYPE_UNKNOWN;
1324 	}
1325 	return g_file_info_get_file_type(fi);
1326 }
1327 
dp_row_get_size(const GtkTreeModel * model,const DirRow2 * row)1328 goffset dp_row_get_size(const GtkTreeModel *model, const DirRow2 *row)
1329 {
1330 	GFileInfo	*fi;
1331 
1332 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1333 	return g_file_info_get_size(fi);
1334 }
1335 
dp_row_get_blocks(const GtkTreeModel * model,const DirRow2 * row)1336 guint64 dp_row_get_blocks(const GtkTreeModel *model, const DirRow2 *row)
1337 {
1338 	GFileInfo	*fi;
1339 
1340 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1341 	return g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_UNIX_BLOCKS);
1342 }
1343 
dp_row_get_blocksize(const GtkTreeModel * model,const DirRow2 * row)1344 guint64 dp_row_get_blocksize(const GtkTreeModel *model, const DirRow2 *row)
1345 {
1346 	GFileInfo	*fi;
1347 
1348 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1349 	return g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_UNIX_BLOCKS) * g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE);
1350 }
1351 
dp_row_get_mode(const GtkTreeModel * model,const DirRow2 * row)1352 guint32 dp_row_get_mode(const GtkTreeModel *model, const DirRow2 *row)
1353 {
1354 	GFileInfo	*fi;
1355 
1356 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1357 	return g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_MODE);
1358 }
1359 
dp_row_get_time_accessed(const GtkTreeModel * model,const DirRow2 * row)1360 guint64 dp_row_get_time_accessed(const GtkTreeModel *model, const DirRow2 *row)
1361 {
1362 	GFileInfo	*fi;
1363 
1364 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1365 	return g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_TIME_ACCESS);
1366 }
1367 
dp_row_get_time_changed(const GtkTreeModel * model,const DirRow2 * row)1368 guint64 dp_row_get_time_changed(const GtkTreeModel *model, const DirRow2 *row)
1369 {
1370 	GFileInfo	*fi;
1371 
1372 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1373 	return g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_TIME_CHANGED);
1374 }
1375 
dp_row_get_time_created(const GtkTreeModel * model,const DirRow2 * row)1376 guint64 dp_row_get_time_created(const GtkTreeModel *model, const DirRow2 *row)
1377 {
1378 	GFileInfo	*fi;
1379 
1380 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1381 	return g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_TIME_CREATED);
1382 }
1383 
dp_row_get_time_modified(const GtkTreeModel * model,const DirRow2 * row)1384 guint64 dp_row_get_time_modified(const GtkTreeModel *model, const DirRow2 *row)
1385 {
1386 	GFileInfo	*fi;
1387 
1388 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1389 	return g_file_info_get_attribute_uint64(fi, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1390 }
1391 
dp_row_get_device(const GtkTreeModel * model,const DirRow2 * row)1392 guint32 dp_row_get_device(const GtkTreeModel *model, const DirRow2 *row)
1393 {
1394 	GFileInfo	*fi;
1395 
1396 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1397 	return g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_RDEV);
1398 }
1399 
dp_row_get_gid(const GtkTreeModel * model,const DirRow2 * row)1400 guint32 dp_row_get_gid(const GtkTreeModel *model, const DirRow2 *row)
1401 {
1402 	GFileInfo	*fi;
1403 
1404 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1405 	return g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_GID);
1406 }
1407 
dp_row_get_uid(const GtkTreeModel * model,const DirRow2 * row)1408 guint32 dp_row_get_uid(const GtkTreeModel *model, const DirRow2 *row)
1409 {
1410 	GFileInfo	*fi;
1411 
1412 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1413 	return g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_UID);
1414 }
1415 
dp_row_get_nlink(const GtkTreeModel * model,const DirRow2 * row)1416 guint32 dp_row_get_nlink(const GtkTreeModel *model, const DirRow2 *row)
1417 {
1418 	GFileInfo	*fi;
1419 
1420 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1421 	return g_file_info_get_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_NLINK);
1422 }
1423 
dp_row_get_link_target(const GtkTreeModel * model,const DirRow2 * row)1424 const gchar * dp_row_get_link_target(const GtkTreeModel *model, const DirRow2 *row)
1425 {
1426 	GFileInfo	*fi;
1427 
1428 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1429 	return g_file_info_get_attribute_byte_string(fi, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
1430 }
1431 
dp_row_get_flags(const GtkTreeModel * model,const DirRow2 * row,guint32 mask)1432 gboolean dp_row_get_flags(const GtkTreeModel *model, const DirRow2 *row, guint32 mask)
1433 {
1434 	guint32	flags;
1435 
1436 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_FLAGS, &flags, -1);
1437 	return (flags & mask) == mask;
1438 }
1439 
dp_row_get_can_read(const GtkTreeModel * model,const DirRow2 * row)1440 gboolean dp_row_get_can_read(const GtkTreeModel *model, const DirRow2 *row)
1441 {
1442 	GFileInfo	*fi;
1443 
1444 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1445 	return g_file_info_get_attribute_boolean(fi, G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
1446 }
1447 
dp_row_get_can_write(const GtkTreeModel * model,const DirRow2 * row)1448 gboolean dp_row_get_can_write(const GtkTreeModel *model, const DirRow2 *row)
1449 {
1450 	GFileInfo	*fi;
1451 
1452 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1453 	return g_file_info_get_attribute_boolean(fi, G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
1454 }
1455 
dp_row_get_can_execute(const GtkTreeModel * model,const DirRow2 * row)1456 gboolean dp_row_get_can_execute(const GtkTreeModel *model, const DirRow2 *row)
1457 {
1458 	GFileInfo	*fi;
1459 
1460 	gtk_tree_model_get((GtkTreeModel *) model, (GtkTreeIter *) row, COL_INFO, &fi, -1);
1461 	return g_file_info_get_attribute_boolean(fi, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
1462 }
1463 
1464 /* ----------------------------------------------------------------------------------------- */
1465 
1466 /* 2009-11-02 -	Porting of the old qsort_result() function, adjusts comparison result to implement directory/file-grouping. */
sort_result(gint r,GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,const GFileInfo * fia,const GFileInfo * fib)1467 static gint sort_result(gint r, GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, const GFileInfo *fia, const GFileInfo *fib)
1468 {
1469 	guint32		flags;
1470 	gboolean	da, db;
1471 
1472 	/* Did the two rows compare equal? Then resolve by checking the names. */
1473 	if(r == 0)
1474 	{
1475 		gchar	*cka, *ckb;
1476 
1477 		/* Read out the previously created collation keys, and just compare them. */
1478 		gtk_tree_model_get(model, a, COL_FILENAME_COLLATE_KEY, &cka, -1);
1479 		gtk_tree_model_get(model, b, COL_FILENAME_COLLATE_KEY, &ckb, -1);
1480 		r = strcmp(cka, ckb);
1481 		/* FIXME: This feels highly inefficient ... To work around, column must be G_TYPE_POINTER. */
1482 		g_free(ckb);
1483 		g_free(cka);
1484 	}
1485 
1486 	/* Resolve type of the row. If not immediate directory, it might be a symlink to one. */
1487 	da = g_file_info_get_file_type((GFileInfo *) fia) == G_FILE_TYPE_DIRECTORY;
1488 	if(!da)
1489 	{
1490 		gtk_tree_model_get(model, a, COL_FLAGS, &flags, -1);
1491 		da = (flags & DPRF_LINK_TO_DIR) != 0;
1492 	}
1493 	db = g_file_info_get_file_type((GFileInfo *) fib) == G_FILE_TYPE_DIRECTORY;
1494 	if(!db)
1495 	{
1496 		gtk_tree_model_get(model, b, COL_FLAGS, &flags, -1);
1497 		db = (flags & DPRF_LINK_TO_DIR) != 0;
1498 	}
1499 
1500 	switch(the_sort.mode)
1501 	{
1502 		case DPS_DIRS_FIRST:
1503 			if(da == db)
1504 				return r;
1505 			else if(da)
1506 				return -1;
1507 			return 1;
1508 		case DPS_DIRS_LAST:
1509 			if(da == db)
1510 				return r;
1511 			else if(db)
1512 				return -1;
1513 			return 1;
1514 		case DPS_DIRS_MIXED:
1515 			return r;
1516 	}
1517 	return r;		/* I bet this doesn't ever run. Gcc doesn't. */
1518 }
1519 
1520 /* 2009-11-04 -	Return sort result, generating initial value by comparing <va> and <vb>. */
sort_result_uint32(guint32 va,guint32 vb,GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,const GFileInfo * fia,const GFileInfo * fib)1521 static gint sort_result_uint32(guint32 va, guint32 vb, GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, const GFileInfo *fia, const GFileInfo *fib)
1522 {
1523 	if(va < vb)
1524 		return sort_result(-1, model, a, b, fia, fib);
1525 	else if(va > vb)
1526 		return sort_result(1, model, a, b, fia, fib);
1527 	return sort_result(0, model, a, b, fia, fib);
1528 }
1529 
1530 /* 2009-11-04 -	Return sort result, generating initial value by comparing <va> and <vb>. */
sort_result_uint64(guint64 va,guint64 vb,GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,const GFileInfo * fia,const GFileInfo * fib)1531 static gint sort_result_uint64(guint64 va, guint64 vb, GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, const GFileInfo *fia, const GFileInfo *fib)
1532 {
1533 	if(va < vb)
1534 		return sort_result(-1, model, a, b, fia, fib);
1535 	else if(va > vb)
1536 		return sort_result(1, model, a, b, fia, fib);
1537 	return sort_result(0, model, a, b, fia, fib);
1538 }
1539 
1540 /* 2009-11-02 -	Compare by name. This is assumed to be the most common case, so it should be quick. */
cmp_name(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1541 static gint cmp_name(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1542 {
1543 	GFileInfo	*fia, *fib;
1544 	gint		r;
1545 
1546 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1547 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1548 	r = sort_result(0, model, a, b, fia, fib);
1549 	g_object_unref(fib);
1550 	g_object_unref(fia);
1551 
1552 	return r;
1553 }
1554 
1555 /* 2009-11-04 -	Compare by size. */
cmp_size(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1556 static gint cmp_size(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1557 {
1558 	GFileInfo	*fia, *fib;
1559 	guint64		sa, sb;
1560 	gint		r;
1561 
1562 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1563 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1564 	sa = g_file_info_get_size(fia);
1565 	sb = g_file_info_get_size(fib);
1566 	r = sort_result_uint64(sa, sb, model, a, b, fia, fib);
1567 	g_object_unref(fib);
1568 	g_object_unref(fia);
1569 
1570 	return r;
1571 }
1572 
1573 /* 2009-11-04 -	Compare access times. */
cmp_atime(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1574 static gint cmp_atime(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1575 {
1576 	GFileInfo	*fia, *fib;
1577 	guint64		ta, tb;
1578 	gint		r;
1579 
1580 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1581 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1582 	ta = g_file_info_get_attribute_uint64(fia, G_FILE_ATTRIBUTE_TIME_ACCESS);
1583 	tb = g_file_info_get_attribute_uint64(fib, G_FILE_ATTRIBUTE_TIME_ACCESS);
1584 	r = sort_result_uint64(ta, tb, model, a, b, fia, fib);
1585 	g_object_unref(fib);
1586 	g_object_unref(fia);
1587 
1588 	return r;
1589 }
1590 
1591 /* 2009-11-04 -	Compare creation times. */
cmp_crtime(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1592 static gint cmp_crtime(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1593 {
1594 	GFileInfo	*fia, *fib;
1595 	guint64		ta, tb;
1596 	gint		r;
1597 
1598 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1599 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1600 	ta = g_file_info_get_attribute_uint64(fia, G_FILE_ATTRIBUTE_TIME_CREATED);
1601 	tb = g_file_info_get_attribute_uint64(fib, G_FILE_ATTRIBUTE_TIME_CREATED);
1602 	r = sort_result_uint64(ta, tb, model, a, b, fia, fib);
1603 	g_object_unref(fib);
1604 	g_object_unref(fia);
1605 
1606 	return r;
1607 }
1608 
1609 /* 2009-11-04 -	Compare modification times. */
cmp_mtime(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1610 static gint cmp_mtime(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1611 {
1612 	GFileInfo	*fia, *fib;
1613 	guint64		ta, tb;
1614 	gint		r;
1615 
1616 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1617 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1618 	ta = g_file_info_get_attribute_uint64(fia, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1619 	tb = g_file_info_get_attribute_uint64(fib, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1620 	r = sort_result_uint64(ta, tb, model, a, b, fia, fib);
1621 	g_object_unref(fib);
1622 	g_object_unref(fia);
1623 
1624 	return r;
1625 }
1626 
1627 /* 2010-10-19 -	Compare changed times. */
cmp_chtime(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1628 static gint cmp_chtime(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1629 {
1630 	GFileInfo	*fia, *fib;
1631 	guint64		ta, tb;
1632 	gint		r;
1633 
1634 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1635 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1636 	ta = g_file_info_get_attribute_uint64(fia, G_FILE_ATTRIBUTE_TIME_CHANGED);
1637 	tb = g_file_info_get_attribute_uint64(fib, G_FILE_ATTRIBUTE_TIME_CHANGED);
1638 	r = sort_result_uint64(ta, tb, model, a, b, fia, fib);
1639 	g_object_unref(fib);
1640 	g_object_unref(fia);
1641 
1642 	return r;
1643 }
1644 
cmp_device(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1645 static gint cmp_device(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1646 {
1647 	GFileInfo	*fia, *fib;
1648 	guint32		da, db;
1649 	gint		r;
1650 
1651 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1652 	da = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_RDEV);
1653 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1654 	db = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_RDEV);
1655 	r = sort_result_uint32(da, db, model, a, b, fia, fib);
1656 	g_object_unref(fib);
1657 	g_object_unref(fia);
1658 
1659 	return r;
1660 }
1661 
cmp_device_major(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1662 static gint cmp_device_major(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1663 {
1664 	GFileInfo	*fia, *fib;
1665 	guint32		da, db;
1666 	gint		r;
1667 
1668 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1669 	da = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_RDEV);
1670 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1671 	db = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_RDEV);
1672 	r = sort_result_uint32(da >> 8, db >> 8, model, a, b, fia, fib);
1673 	g_object_unref(fib);
1674 	g_object_unref(fia);
1675 
1676 	return r;
1677 }
1678 
cmp_device_minor(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1679 static gint cmp_device_minor(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1680 {
1681 	GFileInfo	*fia, *fib;
1682 	guint32		da, db;
1683 	gint		r;
1684 
1685 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1686 	da = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_RDEV);
1687 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1688 	db = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_RDEV);
1689 	r = sort_result_uint32(da & 255, db & 255, model, a, b, fia, fib);
1690 	g_object_unref(fib);
1691 	g_object_unref(fia);
1692 
1693 	return r;
1694 }
1695 
cmp_gid_num(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1696 static gint cmp_gid_num(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1697 {
1698 	GFileInfo	*fia, *fib;
1699 	guint32		ga, gb;
1700 	gint		r;
1701 
1702 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1703 	ga = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_GID);
1704 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1705 	gb = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_GID);
1706 	r = sort_result_uint32(ga, gb, model, a, b, fia, fib);
1707 	g_object_unref(fib);
1708 	g_object_unref(fia);
1709 
1710 	return r;
1711 }
1712 
cmp_gid_str(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1713 static gint cmp_gid_str(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1714 {
1715 	GFileInfo	*fia, *fib;
1716 	guint32		ga, gb;
1717 	const gchar	*gas, *gbs;
1718 	gint		r;
1719 
1720 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1721 	ga = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_GID);
1722 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1723 	gb = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_GID);
1724 	if((gas = usr_lookup_gname(ga)) != NULL && (gbs = usr_lookup_gname(gb)) != NULL)
1725 		r = sort_result(strcmp(gas, gbs), model, a, b, fia, fib);
1726 	else
1727 		r = sort_result_uint32(ga, gb, model, a, b, fia, fib);
1728 	g_object_unref(fib);
1729 	g_object_unref(fia);
1730 
1731 	return r;
1732 }
1733 
cmp_uid_num(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1734 static gint cmp_uid_num(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1735 {
1736 	GFileInfo	*fia, *fib;
1737 	guint32		ua, ub;
1738 	gint		r;
1739 
1740 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1741 	ua = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_UID);
1742 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1743 	ub = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_UID);
1744 	r = sort_result_uint32(ua, ub, model, a, b, fia, fib);
1745 	g_object_unref(fib);
1746 	g_object_unref(fia);
1747 
1748 	return r;
1749 }
1750 
cmp_uid_str(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1751 static gint cmp_uid_str(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1752 {
1753 	GFileInfo	*fia, *fib;
1754 	guint32		ua, ub;
1755 	const gchar	*uas, *ubs;
1756 	gint		r;
1757 
1758 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1759 	ua = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_UID);
1760 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1761 	ub = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_UID);
1762 	if((uas = usr_lookup_uname(ua)) != NULL && (ubs = usr_lookup_gname(ub)) != NULL)
1763 		r = sort_result(strcmp(uas, ubs), model, a, b, fia, fib);
1764 	else
1765 		r = sort_result_uint32(ua, ub, model, a, b, fia, fib);
1766 	g_object_unref(fib);
1767 	g_object_unref(fia);
1768 
1769 	return r;
1770 }
1771 
cmp_mode_num(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1772 static gint cmp_mode_num(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1773 {
1774 	GFileInfo	*fia, *fib;
1775 	guint32		ma, mb;
1776 	gint		r;
1777 
1778 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1779 	ma = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_MODE);
1780 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1781 	mb = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_MODE);
1782 	r = sort_result_uint32(ma, mb, model, a, b, fia, fib);
1783 	g_object_unref(fib);
1784 	g_object_unref(fia);
1785 
1786 	return r;
1787 }
1788 
cmp_mode_str(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1789 static gint cmp_mode_str(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1790 {
1791 	GFileInfo	*fia, *fib;
1792 	gint		r;
1793 	char		mab[16], mbb[16];
1794 
1795 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1796 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1797 	stu_mode_to_text(mab, sizeof mab, g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_MODE));
1798 	stu_mode_to_text(mbb, sizeof mbb, g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_MODE));
1799 	r = sort_result(strcmp(mab, mbb), model, a, b, fia, fib);
1800 	g_object_unref(fib);
1801 	g_object_unref(fia);
1802 
1803 	return r;
1804 }
1805 
cmp_nlink(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1806 static gint cmp_nlink(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1807 {
1808 	GFileInfo	*fia, *fib;
1809 	guint32		na, nb;
1810 	gint		r;
1811 
1812 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1813 	na = g_file_info_get_attribute_uint32(fia, G_FILE_ATTRIBUTE_UNIX_NLINK);
1814 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1815 	nb = g_file_info_get_attribute_uint32(fib, G_FILE_ATTRIBUTE_UNIX_NLINK);
1816 	r = sort_result_uint32(na, nb, model, a, b, fia, fib);
1817 	g_object_unref(fib);
1818 	g_object_unref(fia);
1819 
1820 	return r;
1821 }
1822 
cmp_type(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1823 static gint cmp_type(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1824 {
1825 	GFileInfo	*fia, *fib;
1826 	FType		*ta, *tb;
1827 	gint		r;
1828 
1829 	gtk_tree_model_get(model, a, COL_INFO, &fia, COL_FTYPE, &ta, -1);
1830 	gtk_tree_model_get(model, b, COL_INFO, &fib, COL_FTYPE, &tb, -1);
1831 	if(ta != NULL && tb != NULL)
1832 		r = sort_result(strcmp(ta->name, tb->name), model, a, b, fia, fib);
1833 	else
1834 		r = sort_result(0, model, a, b, fia, fib);
1835 	g_object_unref(fib);
1836 	g_object_unref(fia);
1837 
1838 	return r;
1839 }
1840 
cmp_uri(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user)1841 static gint cmp_uri(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user)
1842 {
1843 	DirPane		*dp = user;
1844 	GFileInfo	*fia, *fib;
1845 	GFile		*fa, *fb;
1846 	gint		r;
1847 
1848 	gtk_tree_model_get(model, a, COL_INFO, &fia, -1);
1849 	fa = dp_get_file_from_row(dp, a);
1850 	gtk_tree_model_get(model, b, COL_INFO, &fib, -1);
1851 	fb = dp_get_file_from_row(dp, b);
1852 	if(fa != NULL && fb != NULL)
1853 	{
1854 		gchar	*ua, *ub;
1855 
1856 		ua = g_file_get_uri(fa);
1857 		ub = g_file_get_uri(fb);
1858 		r = sort_result(strcmp(ua, ub), model, a, b, fia, fib);
1859 		g_free(ub);
1860 		g_free(ua);
1861 	}
1862 	else
1863 		r = sort_result(0, model, a, b, fia, fib);
1864 	g_object_unref(fb);
1865 	g_object_unref(fib);
1866 	g_object_unref(fa);
1867 	g_object_unref(fia);
1868 
1869 	return r;
1870 
1871 }
1872 
evt_row_activated(GtkWidget * wid,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user)1873 static void evt_row_activated(GtkWidget *wid, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user)
1874 {
1875 	dp_dbclk_row(user, gtk_tree_path_get_indices(path)[0]);
1876 }
1877 
1878 /* 2009-10-01 -	The selection has somehow changed -- so recompute statistics display. */
evt_selection_changed(GtkTreeSelection * ts,gpointer user)1879 static void evt_selection_changed(GtkTreeSelection *ts, gpointer user)
1880 {
1881 	update_selection_stats(user);
1882 	dp_show_stats(user);
1883 }
1884 
dp_build_list(DirPane * dp,const DPFormat * fmt)1885 static void dp_build_list(DirPane *dp, const DPFormat *fmt)
1886 {
1887 	const struct {
1888 		DPContent		content;
1889 		GtkTreeIterCompareFunc	func;
1890 	} sorters[] = {
1891 			{ DPC_NAME, cmp_name },
1892 			{ DPC_SIZE, cmp_size }, { DPC_BLOCKS, cmp_size }, { DPC_BLOCKSIZE, cmp_size },
1893 			{ DPC_ATIME, cmp_atime },
1894 			{ DPC_CRTIME, cmp_crtime },
1895 			{ DPC_MTIME, cmp_mtime },
1896 			{ DPC_CHTIME, cmp_chtime },
1897 			{ DPC_DEVICE, cmp_device }, { DPC_DEVMAJ, cmp_device_major}, { DPC_DEVMIN, cmp_device_minor },
1898 			{ DPC_GIDNUM, cmp_gid_num }, { DPC_GIDSTR, cmp_gid_str },
1899 			{ DPC_UIDNUM, cmp_uid_num }, { DPC_UIDSTR, cmp_uid_str },
1900 			{ DPC_MODENUM, cmp_mode_num }, { DPC_MODESTR, cmp_mode_str },
1901 			{ DPC_NLINK, cmp_nlink },
1902 			{ DPC_ICON, cmp_type } /* This might be unexpected? */,
1903 			{ DPC_TYPE, cmp_type },
1904 			{ DPC_URI_NOFILE, cmp_uri },
1905 	};
1906 	gint	i;
1907 
1908 	dp->dir.store = gtk_list_store_new(COL_NUMBER_OF_COLUMNS, G_TYPE_OBJECT, G_TYPE_OBJECT, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_UINT );
1909 	for(i = 0; i < sizeof sorters / sizeof *sorters; i++)
1910 		gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(dp->dir.store), sorters[i].content, sorters[i].func, dp, NULL);
1911 
1912 	dp->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dp->dir.store));
1913 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(dp->view), -1);
1914 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(dp->view), FALSE);
1915 
1916 	/* Connect signals. */
1917 	g_signal_connect(G_OBJECT(dp->view), "button_press_event", G_CALLBACK(evt_dirpane_button_press), dp);
1918 	g_signal_connect(G_OBJECT(dp->view), "row_activated", G_CALLBACK(evt_row_activated), dp);
1919 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view)), GTK_SELECTION_MULTIPLE);
1920 	gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(dp->view), fmt->rubber_banding);
1921 	dp->sig_sel_changed = g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(dp->view))), "changed", G_CALLBACK(evt_selection_changed), dp);
1922 
1923 	for(i = 0; i < fmt->num_columns; i++)
1924 	{
1925 		GtkCellRenderer		*cr;
1926 		GtkTreeCellDataFunc	cdf;
1927 		gpointer		user;
1928 		GtkWidget		*lab;
1929 
1930 		/* Ask the formatting module for a cell data function. */
1931 		cdf = dpf_get_cell_data_func(dp, i, &user);
1932 		if(cdf != NULL)
1933 		{
1934 			GtkTreeViewColumn	*tc;
1935 			gfloat			xa;
1936 
1937 			switch(fmt->format[i].content)
1938 			{
1939 			case DPC_NAME:
1940 			case DPC_SIZE:
1941 			case DPC_BLOCKS:
1942 			case DPC_ATIME:
1943 			case DPC_CRTIME:
1944 			case DPC_MTIME:
1945 			case DPC_CHTIME:
1946 			case DPC_MODESTR:
1947 			case DPC_MODENUM:
1948 			case DPC_UIDNUM:
1949 			case DPC_UIDSTR:
1950 			case DPC_GIDNUM:
1951 			case DPC_GIDSTR:
1952 			case DPC_NLINK:
1953 			case DPC_DEVICE:
1954 			case DPC_DEVMIN:
1955 			case DPC_DEVMAJ:
1956 			case DPC_TYPE:
1957 			case DPC_URI_NOFILE:
1958 				cr = gtk_cell_renderer_text_new();
1959 				break;
1960 			case DPC_ICON:
1961 				cr = gtk_cell_renderer_pixbuf_new();
1962 				break;
1963 			default:
1964 				g_warning("Unsupported dirpane content type %d detected", fmt->format[i].content);
1965 				continue;
1966 			}
1967 
1968 			tc = gtk_tree_view_column_new();
1969 			lab = gtk_label_new(fmt->format[i].title);
1970 			gtk_widget_show_all(lab);
1971 			gtk_tree_view_column_set_widget(tc, lab);
1972 			/* Compute legacy justification into alignment, and set for both header and content. */
1973 			switch(fmt->format[i].just)
1974 			{
1975 			case GTK_JUSTIFY_LEFT:
1976 				xa = 0.f;
1977 				break;
1978 			case GTK_JUSTIFY_CENTER:
1979 				xa = 0.5f;
1980 				break;
1981 			case GTK_JUSTIFY_RIGHT:
1982 				xa = 1.0f;
1983 				break;
1984 			default:
1985 				xa = 0.5f;
1986 			}
1987 			gtk_tree_view_column_set_alignment(tc, xa);		/* Header. */
1988 			g_object_set(cr, "xalign", xa, NULL);			/* Content. */
1989 			g_object_set_data(G_OBJECT(cr), "main", dp->main);	/* Always handy. */
1990 			/* Set the content ID as the sort ID; they're unique and all. */
1991 			gtk_tree_view_column_set_sort_column_id(tc, fmt->format[i].content);
1992 			/* Set the size, which we try to control ourselves. */
1993 			gtk_tree_view_column_set_min_width(tc, fmt->format[i].width);
1994 			gtk_tree_view_column_set_sizing(tc, GTK_TREE_VIEW_COLUMN_FIXED);
1995 			gtk_tree_view_column_set_resizable(tc, FALSE);
1996 			gtk_tree_view_column_set_expand(tc, FALSE);
1997 			gtk_tree_view_append_column(GTK_TREE_VIEW(dp->view), tc);
1998 			gtk_tree_view_column_pack_start(tc, cr, TRUE);
1999 			/* Expose the column index. */
2000 			g_object_set_data(G_OBJECT(tc), "index", GINT_TO_POINTER(i));
2001 			/* This must be done after column is appended. */
2002 			gtk_tree_view_column_set_cell_data_func(tc, cr, cdf, user, NULL);
2003 		}
2004 	}
2005 	/* Set the user's preferred sorting column and order. */
2006 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dp->dir.store), fmt->sort.content, fmt->sort.invert ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
2007 	g_signal_connect(G_OBJECT(GTK_TREE_SORTABLE(dp->dir.store)), "sort_column_changed", G_CALLBACK(evt_pane_sort_column_clicked), dp);
2008 	gtk_container_add(GTK_CONTAINER(dp->scwin), dp->view);
2009 }
2010 
2011 /* 1998-05-23 -	Rewrote this one, now encloses stuff in a frame, which provides a fairly nice way to
2012 **		indicate current directory. Now also uses the new dirpane formatting/config stuff.
2013 ** 1998-06-26 -	Cut away the code above (dp_build_list), making this function a lot leaner.
2014 ** 1998-09-06 -	Frame removed, since I've become cool enough to use styles to just change the
2015 **		background color of the pane's column buttons. Looks great!
2016 ** 1998-10-26 -	Now supports configuring the position of the path entry (above or below).
2017 */
dp_build(MainInfo * min,DPFormat * fmt,DirPane * dp)2018 GtkWidget * dp_build(MainInfo *min, DPFormat *fmt, DirPane *dp)
2019 {
2020 	GtkWidget	*hbox, *ihbox;
2021 	GtkListStore	*model;
2022 	GSList		*iter;
2023 
2024 	if(pane_selected != NULL)
2025 	{
2026 		g_object_unref(pane_selected);
2027 		pane_selected = NULL;
2028 	}
2029 	if(focus_style != NULL)
2030 	{
2031 		g_object_unref(focus_style);
2032 		focus_style = NULL;
2033 	}
2034 	if(dp == NULL)
2035 		return NULL;
2036 
2037 	dp->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2038 	/* "Internal" hbox, holds the scrolled window of the pane and the optional
2039 	** "huge" parent button. If the latter is disabled, it's redundant, but hey.
2040 	*/
2041 	ihbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2042 
2043 	dp->scwin = gtk_scrolled_window_new(NULL, NULL);
2044 	if(fmt->sbar_pos == SBP_LEFT)
2045 		gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(dp->scwin), GTK_CORNER_TOP_RIGHT);
2046 	else if(fmt->sbar_pos == SBP_RIGHT)
2047 		gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(dp->scwin), GTK_CORNER_TOP_LEFT);
2048 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dp->scwin), GTK_POLICY_AUTOMATIC, fmt->scrollbar_always ? GTK_POLICY_ALWAYS : GTK_POLICY_AUTOMATIC);
2049 
2050 	dp_build_list(dp, fmt);
2051 
2052 	dp->notebook = gtk_notebook_new();
2053 	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(dp->notebook), FALSE);
2054 	gtk_notebook_set_show_border(GTK_NOTEBOOK(dp->notebook), FALSE);
2055 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2056 	dp->parent = gtk_button_new_from_icon_name("go-up", GTK_ICON_SIZE_MENU);
2057 	gtk_widget_set_can_focus(dp->parent, FALSE);
2058 	g_signal_connect(G_OBJECT(dp->parent), "clicked", G_CALLBACK(evt_parent_clicked), dp);
2059 	gtk_box_pack_start(GTK_BOX(hbox), dp->parent, FALSE, FALSE, 0);
2060 	gtk_widget_set_tooltip_text(dp->parent, _("Move up to the parent directory"));
2061 	dp->menu_top	 = NULL;
2062 	dp->menu_action  = NULL;
2063 	dp->mitem_action = NULL;
2064 
2065 	model = gtk_list_store_new(1, G_TYPE_STRING);
2066 	dp->path = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
2067 	gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(dp->path), 0);
2068 
2069 	dp->complete.compl = gtk_entry_completion_new();
2070 	model = gtk_list_store_new(1, G_TYPE_STRING);
2071 	gtk_entry_completion_set_model(dp->complete.compl, GTK_TREE_MODEL(model));
2072 	gtk_entry_completion_set_text_column(dp->complete.compl, 0);
2073 	gtk_entry_completion_set_inline_completion(dp->complete.compl, TRUE);
2074 	gtk_entry_completion_set_popup_single_match(dp->complete.compl, FALSE);
2075 	gtk_entry_completion_set_match_func(dp->complete.compl, completion_match_function, dp, NULL);
2076 	gtk_entry_set_completion(DP_ENTRY(dp), dp->complete.compl);
2077 	g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(dp->path))), "focus_in_event", G_CALLBACK(evt_path_focus), dp);
2078 	g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(dp->path))), "focus_out_event", G_CALLBACK(evt_path_unfocus), dp);
2079 	dp->sig_path_activate = g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(dp->path))), "activate", G_CALLBACK(evt_path_new), dp);
2080 	dp->sig_path_changed = g_signal_connect(G_OBJECT(dp->path), "changed", G_CALLBACK(evt_path_changed), dp);
2081 
2082 	gtk_box_pack_start(GTK_BOX(hbox), dp->path, TRUE, TRUE, 0);
2083 	gtk_widget_set_tooltip_text(dp->path, _("Enter path, then press Return to go there"));
2084 	dp->hide = gtk_toggle_button_new_with_label(_("H"));
2085 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dp->hide), fmt->hide_allowed);
2086 	g_signal_connect(G_OBJECT(dp->hide), "clicked", G_CALLBACK(evt_hide_clicked), dp);
2087 	gtk_box_pack_start(GTK_BOX(hbox), dp->hide, FALSE, FALSE, 0);
2088 	gtk_widget_set_tooltip_text(dp->hide, _("Click to enable/disable Hide rule (When pressed in, the hide rule is active, and matching entries are hidden)"));
2089 	gtk_notebook_append_page(GTK_NOTEBOOK(dp->notebook), hbox, NULL);
2090 
2091 	/* If we have any registered pathwidgetry builders, go through and add their output to notebook. */
2092 	for(iter = pathwidgetry_builders; iter != NULL; iter = g_slist_next(iter))
2093 	{
2094 		PageBuilder	func = iter->data;
2095 		GtkWidget	**page;
2096 
2097 		if(func && (page = func(min)) != NULL)
2098 		{
2099 			g_object_set_data(G_OBJECT(*page), "dp-pathwidgetry", page);
2100 			gtk_notebook_append_page(GTK_NOTEBOOK(dp->notebook), *page, NULL);
2101 		}
2102 	}
2103 	if(fmt->huge_parent)
2104 	{
2105 		dp->hparent = gtk_button_new();
2106 		gtk_button_set_relief(GTK_BUTTON(dp->hparent), GTK_RELIEF_NONE);
2107 		g_signal_connect(G_OBJECT(dp->hparent), "clicked", G_CALLBACK(evt_hugeparent_clicked), dp);
2108 		gtk_widget_set_tooltip_text(dp->hparent, _("Move up to the parent directory"));
2109 		gtk_widget_set_can_focus(dp->hparent, FALSE);
2110 		if(dp->index == 0)
2111 		{
2112 			gtk_box_pack_start(GTK_BOX(ihbox), dp->hparent, FALSE, FALSE, 0);
2113 			gtk_box_pack_start(GTK_BOX(ihbox), dp->scwin, TRUE, TRUE, 0);
2114 		}
2115 		else
2116 		{
2117 			gtk_box_pack_start(GTK_BOX(ihbox), dp->scwin, TRUE, TRUE, 0);
2118 			gtk_box_pack_start(GTK_BOX(ihbox), dp->hparent, FALSE, FALSE, 0);
2119 		}
2120 	}
2121 	else
2122 	{
2123 		gtk_box_pack_start(GTK_BOX(ihbox), dp->scwin, TRUE, TRUE, 0);
2124 		dp->hparent = NULL;
2125 	}
2126 
2127 	if(fmt->path_above)
2128 	{
2129 		gtk_box_pack_start(GTK_BOX(dp->vbox), dp->notebook, FALSE, FALSE, 0);
2130 		gtk_box_pack_start(GTK_BOX(dp->vbox), ihbox, TRUE, TRUE, 0);
2131 	}
2132 	else
2133 	{
2134 		gtk_box_pack_start(GTK_BOX(dp->vbox), ihbox, TRUE, TRUE, 0);
2135 		gtk_box_pack_start(GTK_BOX(dp->vbox), dp->notebook, FALSE, FALSE, 0);
2136 	}
2137 
2138 	if(fmt->set_font)
2139 	{
2140 		PangoFontDescription	*fdesc;
2141 
2142 		if((fdesc = pango_font_description_from_string(fmt->font_name)) != NULL)
2143 		{
2144 			gtk_widget_override_font(dp->view, fdesc);
2145 			pango_font_description_free(fdesc);
2146 		}
2147 	}
2148 
2149 	gtk_widget_show_all(dp->vbox);
2150 	dp->dbclk_row = -1;
2151 	clear_stats(dp);
2152 
2153 	return dp->vbox;
2154 }
2155