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