1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20 /* filer.c - code for handling filer windows */
21
22 #include "config.h"
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <netdb.h>
30 #include <sys/param.h>
31 #include <fnmatch.h>
32
33 #include <gtk/gtk.h>
34 #include <gdk/gdkx.h>
35 #include <gdk/gdkkeysyms.h>
36
37 #include "global.h"
38
39 #include "filer.h"
40 #include "display.h"
41 #include "main.h"
42 #include "fscache.h"
43 #include "support.h"
44 #include "gui_support.h"
45 #include "choices.h"
46 #include "pixmaps.h"
47 #include "menu.h"
48 #include "dnd.h"
49 #include "dir.h"
50 #include "diritem.h"
51 #include "run.h"
52 #include "type.h"
53 #include "options.h"
54 #include "minibuffer.h"
55 #include "icon.h"
56 #include "toolbar.h"
57 #include "bind.h"
58 #include "appinfo.h"
59 #include "mount.h"
60 #include "xml.h"
61 #include "view_iface.h"
62 #include "view_collection.h"
63 #include "view_details.h"
64 #include "action.h"
65 #include "bookmarks.h"
66 #include "xtypes.h"
67
68 static XMLwrapper *groups = NULL;
69
70 /* Item we are about to display a tooltip for */
71 static DirItem *tip_item = NULL;
72
73 /* The window which the motion event for the tooltip came from. Use this
74 * to get the correct widget for finding the item under the pointer.
75 */
76 static GdkWindow *motion_window = NULL;
77
78 /* This is rather badly named. It's actually the filer window which received
79 * the last key press or Menu click event.
80 */
81 FilerWindow *window_with_focus = NULL;
82
83 GList *all_filer_windows = NULL;
84
85 static GHashTable *window_with_id = NULL;
86
87 static FilerWindow *window_with_primary = NULL;
88
89 static GHashTable *settings_table=NULL;
90
91 typedef struct {
92 gchar *path;
93
94 guint flags; /* Which parts are valid, see below */
95
96 gint x, y;
97 gint width, height;
98 gboolean show_hidden;
99 ViewType view_type;
100 SortType sort_type;
101 GtkSortType sort_order;
102 gboolean show_thumbs;
103
104 DetailsType details_type;
105 DisplayStyle display_style;
106
107 FilterType filter_type;
108 char *filter;
109 gboolean filter_directories;
110 } Settings;
111
112 enum settings_flags{
113 SET_POSITION=1, /* x, y */
114 SET_SIZE=2, /* width, height */
115 SET_HIDDEN=4, /* show_hidden */
116 SET_STYLE=8, /* display_style */
117 SET_SORT=16, /* sort_type, sort_order */
118 SET_DETAILS=32, /* view_type, details_type */
119 SET_THUMBS=64, /* show_thumbs */
120 SET_FILTER=128, /* filter_type, filter */
121 };
122
123 static GHashTable *unmount_prompt_actions = NULL;
124
125 /* Static prototypes */
126 static void attach(FilerWindow *filer_window);
127 static void detach(FilerWindow *filer_window);
128 static void filer_window_destroyed(GtkWidget *widget,
129 FilerWindow *filer_window);
130 static void update_display(Directory *dir,
131 DirAction action,
132 GPtrArray *items,
133 FilerWindow *filer_window);
134 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
135 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
136 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
137 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
138 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class);
139 static void filer_add_signals(FilerWindow *filer_window);
140
141 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
142 static void filer_next_thumb(GObject *window, const gchar *path);
143 static void start_thumb_scanning(FilerWindow *filer_window);
144 static void filer_options_changed(void);
145 static void drag_end(GtkWidget *widget, GdkDragContext *context,
146 FilerWindow *filer_window);
147 static void drag_leave(GtkWidget *widget,
148 GdkDragContext *context,
149 guint32 time,
150 FilerWindow *filer_window);
151 static gboolean drag_motion(GtkWidget *widget,
152 GdkDragContext *context,
153 gint x,
154 gint y,
155 guint time,
156 FilerWindow *filer_window);
157
158 static void load_learnt_mounts(void);
159 static void save_learnt_mounts(void);
160 static void load_settings(void);
161 static void save_settings(void);
162 static void check_settings(FilerWindow *filer_window);
163 static char *tip_from_desktop_file(const char *full_path);
164
165 static GdkCursor *busy_cursor = NULL;
166 static GdkCursor *crosshair = NULL;
167
168 /* Indicates whether the filer's display is different to the machine it
169 * is actually running on.
170 */
171 static gboolean not_local = FALSE;
172
173 static Option o_short_flag_names;
174 static Option o_filer_view_type;
175 Option o_filer_auto_resize, o_unique_filer_windows;
176 Option o_filer_size_limit;
177
178 #define ROX_RESPONSE_EJECT 99 /**< User clicked on Eject button */
179
filer_init(void)180 void filer_init(void)
181 {
182 const gchar *ohost;
183 const gchar *dpy;
184 gchar *dpyhost, *tmp;
185
186 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
187 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
188 RESIZE_ALWAYS);
189 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
190
191 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
192
193 option_add_int(&o_filer_view_type, "filer_view_type",
194 VIEW_TYPE_COLLECTION);
195
196 option_add_notify(filer_options_changed);
197
198 busy_cursor = gdk_cursor_new(GDK_WATCH);
199 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
200
201 window_with_id = g_hash_table_new_full(g_str_hash, g_str_equal,
202 NULL, NULL);
203
204 /* Is the display on the local machine, or are we being
205 * run remotely? See filer_set_title().
206 */
207 ohost = our_host_name();
208 dpy = gdk_get_display();
209 dpyhost = g_strdup(dpy);
210 tmp = strchr(dpyhost, ':');
211 if (tmp)
212 *tmp = '\0';
213
214 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
215 {
216 /* Try the cannonical name for dpyhost (see our_host_name()
217 * in support.c).
218 */
219 struct hostent *ent;
220
221 ent = gethostbyname(dpyhost);
222 if (!ent || strcmp(ohost, ent->h_name) != 0)
223 not_local = TRUE;
224 }
225
226 g_free(dpyhost);
227
228 load_settings();
229 load_learnt_mounts();
230 }
231
if_deleted(gpointer item,gpointer removed)232 static gboolean if_deleted(gpointer item, gpointer removed)
233 {
234 int i = ((GPtrArray *) removed)->len;
235 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
236 char *leafname = ((DirItem *) item)->leafname;
237
238 while (i--)
239 {
240 if (strcmp(leafname, r[i]->leafname) == 0)
241 return TRUE;
242 }
243
244 return FALSE;
245 }
246
247 #define DECOR_BORDER 32
248
249 /* Resize the filer window to w x h pixels, plus border (not clamped).
250 * If triggered by a key event, warp the pointer (for SloppyFocus users).
251 */
filer_window_set_size(FilerWindow * filer_window,int w,int h)252 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
253 {
254 GtkWidget *window;
255
256 g_return_if_fail(filer_window != NULL);
257
258 if (filer_window->scrollbar)
259 w += filer_window->scrollbar->allocation.width;
260
261 if (o_toolbar.int_value != TOOLBAR_NONE)
262 h += filer_window->toolbar->allocation.height;
263 if (filer_window->message)
264 h += filer_window->message->allocation.height;
265
266 window = filer_window->window;
267
268 if (GTK_WIDGET_VISIBLE(window))
269 {
270 gint x, y, m;
271 GtkRequisition *req = &window->requisition;
272 GdkWindow *gdk_window = window->window;
273 GdkEvent *event;
274
275 w = MAX(req->width, w);
276 h = MAX(req->height, h);
277 gdk_window_get_pointer(NULL, &x, &y, NULL);
278 m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(),
279 x, y);
280 gdk_window_get_position(gdk_window, &x, &y);
281
282 if (x + w + DECOR_BORDER >
283 monitor_geom[m].x + monitor_geom[m].width ||
284 y + h + DECOR_BORDER >
285 monitor_geom[m].y + monitor_geom[m].height)
286 {
287 if (x + w + DECOR_BORDER >
288 monitor_geom[m].x + monitor_geom[m].width)
289 {
290 x = monitor_geom[m].x + monitor_geom[m].width -
291 w - 4 - DECOR_BORDER;
292 }
293 if (y + h + DECOR_BORDER >
294 monitor_geom[m].y + monitor_geom[m].height)
295 {
296 y = monitor_geom[m].y + monitor_geom[m].height -
297 h - 4 - DECOR_BORDER;
298 }
299 gdk_window_move_resize(gdk_window, x, y, w, h);
300 }
301 else
302 gdk_window_resize(gdk_window, w, h);
303
304 /* If the resize was triggered by a key press, keep
305 * the pointer inside the window so that it doesn't
306 * lose focus when using pointer-follows-mouse.
307 */
308 event = gtk_get_current_event();
309 if (event && event->type == GDK_KEY_PRESS)
310 {
311 int x, y;
312 int nx, ny;
313
314 GdkWindow *win = filer_window->window->window;
315
316 gdk_window_get_pointer(filer_window->window->window,
317 &x, &y, NULL);
318
319 nx = CLAMP(x, 4, w - 4);
320 ny = CLAMP(y, 4, h - 4);
321
322 if (nx != x || ny != y)
323 {
324 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
325 None,
326 gdk_x11_drawable_get_xid(win),
327 0, 0, 0, 0,
328 nx, ny);
329 }
330 }
331 if (event)
332 gdk_event_free(event);
333 }
334 else
335 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
336 }
337
338 /* Called on a timeout while scanning or when scanning ends
339 * (whichever happens first).
340 */
open_filer_window(FilerWindow * filer_window)341 static gint open_filer_window(FilerWindow *filer_window)
342 {
343 Settings *dir_settings;
344 gboolean force_resize;
345
346 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
347 filer_window->sym_path);
348
349 force_resize = !(o_filer_auto_resize.int_value == RESIZE_NEVER &&
350 dir_settings && dir_settings->flags & SET_POSITION);
351
352 view_style_changed(filer_window->view, 0);
353
354 if (filer_window->open_timeout)
355 {
356 g_source_remove(filer_window->open_timeout);
357 filer_window->open_timeout = 0;
358 }
359
360 if (!GTK_WIDGET_VISIBLE(filer_window->window))
361 {
362 display_set_actual_size(filer_window, force_resize);
363 gtk_widget_show(filer_window->window);
364 }
365
366 return FALSE;
367 }
368
369 /* Look through all items we want to display, and queue a recheck on any
370 * that require it.
371 */
queue_interesting(FilerWindow * filer_window)372 static void queue_interesting(FilerWindow *filer_window)
373 {
374 DirItem *item;
375 ViewIter iter;
376
377 view_get_iter(filer_window->view, &iter, 0);
378 while ((item = iter.next(&iter)))
379 {
380 if (item->flags & ITEM_FLAG_NEED_RESCAN_QUEUE)
381 dir_queue_recheck(filer_window->directory, item);
382 }
383 }
384
update_display(Directory * dir,DirAction action,GPtrArray * items,FilerWindow * filer_window)385 static void update_display(Directory *dir,
386 DirAction action,
387 GPtrArray *items,
388 FilerWindow *filer_window)
389 {
390 ViewIface *view = (ViewIface *) filer_window->view;
391
392 switch (action)
393 {
394 case DIR_ADD:
395 view_add_items(view, items);
396 /* Open and resize if currently hidden */
397 open_filer_window(filer_window);
398 break;
399 case DIR_REMOVE:
400 view_delete_if(view, if_deleted, items);
401 toolbar_update_info(filer_window);
402 break;
403 case DIR_START_SCAN:
404 set_scanning_display(filer_window, TRUE);
405 toolbar_update_info(filer_window);
406 break;
407 case DIR_END_SCAN:
408 if (filer_window->window->window)
409 gdk_window_set_cursor(
410 filer_window->window->window,
411 NULL);
412 set_scanning_display(filer_window, FALSE);
413 toolbar_update_info(filer_window);
414 open_filer_window(filer_window);
415
416 if (filer_window->had_cursor &&
417 !view_cursor_visible(view))
418 {
419 ViewIter start;
420 view_get_iter(view, &start, 0);
421 if (start.next(&start))
422 view_cursor_to_iter(view, &start);
423 view_show_cursor(view);
424 filer_window->had_cursor = FALSE;
425 }
426 if (filer_window->auto_select)
427 display_set_autoselect(filer_window,
428 filer_window->auto_select);
429 null_g_free(&filer_window->auto_select);
430
431 filer_create_thumbs(filer_window);
432
433 if (filer_window->thumb_queue)
434 start_thumb_scanning(filer_window);
435 break;
436 case DIR_UPDATE:
437 view_update_items(view, items);
438 break;
439 case DIR_ERROR_CHANGED:
440 filer_set_title(filer_window);
441 break;
442 case DIR_QUEUE_INTERESTING:
443 queue_interesting(filer_window);
444 break;
445 }
446 }
447
attach(FilerWindow * filer_window)448 static void attach(FilerWindow *filer_window)
449 {
450 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
451 view_clear(filer_window->view);
452 filer_window->scanning = TRUE;
453 dir_attach(filer_window->directory, (DirCallback) update_display,
454 filer_window);
455 filer_set_title(filer_window);
456 bookmarks_add_history(filer_window->sym_path);
457
458 if (filer_window->directory->error)
459 {
460 if (spring_in_progress)
461 g_printerr(_("Error scanning '%s':\n%s\n"),
462 filer_window->sym_path,
463 filer_window->directory->error);
464 else
465 delayed_error(_("Error scanning '%s':\n%s"),
466 filer_window->sym_path,
467 filer_window->directory->error);
468 }
469 }
470
detach(FilerWindow * filer_window)471 static void detach(FilerWindow *filer_window)
472 {
473 g_return_if_fail(filer_window->directory != NULL);
474
475 dir_detach(filer_window->directory,
476 (DirCallback) update_display, filer_window);
477 g_object_unref(filer_window->directory);
478 filer_window->directory = NULL;
479 }
480
481 /* If 'start' was mounted by ROX-Filer, return it. Otherwise, try the
482 * parents up the tree.
483 * NULL if we're not in a user mount point.
484 * g_free() the result.
485 */
get_ancestor_user_mount_point(const char * start)486 static char *get_ancestor_user_mount_point(const char *start)
487 {
488 char *path;
489
490 path = strdup(start);
491
492 while (1)
493 {
494 char *slash;
495
496 if (mount_is_user_mounted(path))
497 return path;
498
499 if (!path[1])
500 {
501 g_free(path);
502 return NULL;
503 }
504
505 slash = strrchr(path + 1, '/');
506 if (!slash)
507 slash = path + 1;
508 *slash = '\0';
509 }
510 }
511
unmount_dialog_response(GtkWidget * dialog,int response,char * mount)512 static void unmount_dialog_response(GtkWidget *dialog,
513 int response, char *mount)
514 {
515 GList *list = NULL;
516 UnmountPrompt prompt_val = UNMOUNT_PROMPT_ASK;
517
518 switch (response)
519 {
520 case GTK_RESPONSE_OK:
521 list = g_list_prepend(NULL, mount);
522 action_mount(list, FALSE, FALSE, TRUE);
523 prompt_val = UNMOUNT_PROMPT_UNMOUNT;
524 break;
525
526 case ROX_RESPONSE_EJECT:
527 list = g_list_prepend(NULL, mount);
528 action_eject(list);
529 prompt_val = UNMOUNT_PROMPT_EJECT;
530 break;
531
532 default:
533 prompt_val = UNMOUNT_PROMPT_NO_CHANGE;
534 break;
535 }
536 if (list)
537 g_list_free(list);
538
539 if (gtk_toggle_button_get_active(g_object_get_data(G_OBJECT(dialog),
540 "unmount_mem_btn")))
541 {
542 filer_set_unmount_action(mount, prompt_val);
543 }
544
545 g_free(mount);
546
547 gtk_widget_destroy(dialog);
548
549 one_less_window();
550 }
551
552 /* 'filer_window' shows a directory under 'mount'. If no other window also
553 * shows a directory under it, display a non-modal dialog offering to
554 * unmount the directory.
555 * 'mount' is freed by this function, either directly, or after the dialog
556 * closes.
557 */
may_offer_unmount(FilerWindow * filer_window,char * mount)558 static void may_offer_unmount(FilerWindow *filer_window, char *mount)
559 {
560 GtkWidget *dialog, *button, *unmount_mem_btn;
561 GList *next;
562 int len;
563
564 len = strlen(mount);
565
566 for (next = all_filer_windows; next; next = next->next)
567 {
568 FilerWindow *other = (FilerWindow *) next->data;
569
570 if (other == filer_window)
571 continue;
572
573 if (strncmp(filer_window->real_path, other->real_path,
574 len) != 0)
575 continue;
576
577 g_return_if_fail(
578 filer_window->real_path[len] != '/' ||
579 filer_window->real_path[len] != '\0');
580
581 if (other->real_path[len] != '/' &&
582 other->real_path[len] != '\0')
583 continue;
584
585 /* Found another window. Don't offer to unmount. */
586 g_free(mount);
587 return;
588 }
589
590 if (unmount_prompt_actions)
591 {
592 GList *list = NULL;
593 UnmountPrompt unmount_val = filer_get_unmount_action(mount);
594
595 switch (unmount_val)
596 {
597 case UNMOUNT_PROMPT_UNMOUNT:
598 list = g_list_prepend(NULL, mount);
599 action_mount(list, FALSE, FALSE, TRUE);
600 break;
601
602 case UNMOUNT_PROMPT_EJECT:
603 list = g_list_prepend(NULL, mount);
604 action_eject(list);
605 break;
606
607 default:
608 break;
609 }
610 if (list)
611 g_list_free(list);
612 if (unmount_val != UNMOUNT_PROMPT_ASK)
613 {
614 g_free(mount);
615 return;
616 }
617 }
618
619 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
620 GTK_BUTTONS_NONE,
621 _("Do you want to unmount this device?\n\n"
622 "Unmounting a device makes it safe to remove "
623 "the disk."));
624
625 unmount_mem_btn = gtk_check_button_new_with_label(
626 _("Perform the same action in future for this mount point"));
627 gtk_widget_show(unmount_mem_btn);
628 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), unmount_mem_btn,
629 FALSE, FALSE, 0);
630 g_object_set_data(G_OBJECT(dialog), "unmount_mem_btn",
631 unmount_mem_btn);
632
633 button = button_new_mixed(ROX_STOCK_MOUNTED, _("No change"));
634 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
635 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
636 GTK_RESPONSE_CANCEL);
637 gtk_widget_show(button);
638
639 button = button_new_mixed(ROX_STOCK_MOUNT, _("Unmount"));
640 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
641 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
642 GTK_RESPONSE_OK);
643 gtk_widget_show(button);
644
645 /* We need a better icon, but I can't draw */
646 button = button_new_mixed(GTK_STOCK_UNDO, _("Eject"));
647 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
648 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
649 ROX_RESPONSE_EJECT);
650 gtk_widget_show(button);
651
652 g_signal_connect(G_OBJECT(dialog), "response",
653 G_CALLBACK(unmount_dialog_response), mount);
654
655 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
656 GTK_RESPONSE_OK);
657
658 number_of_windows++;
659 gtk_widget_show(dialog);
660 }
661
662 /* Returns TRUE to prevent closing the window. May offer to unmount a
663 * device.
664 */
filer_window_delete(GtkWidget * window,GdkEvent * unused,FilerWindow * filer_window)665 gboolean filer_window_delete(GtkWidget *window,
666 GdkEvent *unused, /* (may be NULL) */
667 FilerWindow *filer_window)
668 {
669 char *mount;
670
671 mount = get_ancestor_user_mount_point(filer_window->real_path);
672
673 if (mount)
674 may_offer_unmount(filer_window, mount);
675
676 return FALSE;
677 }
678
filer_window_destroyed(GtkWidget * widget,FilerWindow * filer_window)679 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
680 {
681 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
682
683 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
684
685 if (window_with_primary == filer_window)
686 window_with_primary = NULL;
687
688 if (window_with_focus == filer_window)
689 {
690 menu_popdown();
691 window_with_focus = NULL;
692 }
693
694 if (filer_window->directory)
695 detach(filer_window);
696
697 if (filer_window->open_timeout)
698 {
699 g_source_remove(filer_window->open_timeout);
700 filer_window->open_timeout = 0;
701 }
702
703 if (filer_window->auto_scroll != -1)
704 {
705 g_source_remove(filer_window->auto_scroll);
706 filer_window->auto_scroll = -1;
707 }
708
709 if (filer_window->thumb_queue)
710 destroy_glist(&filer_window->thumb_queue);
711
712 tooltip_show(NULL);
713
714 filer_set_id(filer_window, NULL);
715
716 if(filer_window->filter_string)
717 g_free(filer_window->filter_string);
718 if(filer_window->regexp)
719 g_free(filer_window->regexp);
720
721 g_free(filer_window->auto_select);
722 g_free(filer_window->real_path);
723 g_free(filer_window->sym_path);
724 g_free(filer_window);
725
726 one_less_window();
727 }
728
729 /* Returns TRUE iff the directory still exists. */
may_rescan(FilerWindow * filer_window,gboolean warning)730 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
731 {
732 Directory *dir;
733
734 g_return_val_if_fail(filer_window != NULL, FALSE);
735
736 /* We do a fresh lookup (rather than update) because the inode may
737 * have changed.
738 */
739 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
740 if (!dir)
741 {
742 if (warning)
743 info_message(_("Directory missing/deleted"));
744 gtk_widget_destroy(filer_window->window);
745 return FALSE;
746 }
747 if (dir == filer_window->directory)
748 g_object_unref(dir);
749 else
750 {
751 detach(filer_window);
752 filer_window->directory = dir;
753 attach(filer_window);
754 }
755
756 return TRUE;
757 }
758
759 /* No items are now selected. This might be because another app claimed
760 * the selection or because the user unselected all the items.
761 */
filer_lost_selection(FilerWindow * filer_window,guint time)762 void filer_lost_selection(FilerWindow *filer_window, guint time)
763 {
764 if (window_with_primary == filer_window)
765 {
766 window_with_primary = NULL;
767 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
768 }
769 }
770
771 /* Another app has claimed the primary selection */
filer_lost_primary(GtkWidget * window,GdkEventSelection * event,gpointer user_data)772 static void filer_lost_primary(GtkWidget *window,
773 GdkEventSelection *event,
774 gpointer user_data)
775 {
776 FilerWindow *filer_window = (FilerWindow *) user_data;
777
778 if (window_with_primary && window_with_primary == filer_window)
779 {
780 window_with_primary = NULL;
781 set_selection_state(filer_window, FALSE);
782 }
783 }
784
785 /* Someone wants us to send them the selection */
selection_get(GtkWidget * widget,GtkSelectionData * selection_data,guint info,guint time,gpointer data)786 static void selection_get(GtkWidget *widget,
787 GtkSelectionData *selection_data,
788 guint info,
789 guint time,
790 gpointer data)
791 {
792 GString *reply, *header;
793 FilerWindow *filer_window = (FilerWindow *) data;
794 ViewIter iter;
795 DirItem *item;
796
797 reply = g_string_new(NULL);
798 header = g_string_new(NULL);
799
800 switch (info)
801 {
802 case TARGET_STRING:
803 g_string_printf(header, " %s",
804 make_path(filer_window->sym_path, ""));
805 break;
806 case TARGET_URI_LIST:
807 g_string_printf(header, " file://%s%s",
808 our_host_name_for_dnd(),
809 make_path(filer_window->sym_path, ""));
810 break;
811 }
812
813 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
814
815 while ((item = iter.next(&iter)))
816 {
817 g_string_append(reply, header->str);
818 g_string_append(reply, item->leafname);
819 }
820
821 if (reply->len > 0)
822 gtk_selection_data_set_text(selection_data,
823 reply->str + 1, reply->len - 1);
824 else
825 {
826 g_warning("Attempt to paste empty selection!");
827 gtk_selection_data_set_text(selection_data, "", 0);
828 }
829
830 g_string_free(reply, TRUE);
831 g_string_free(header, TRUE);
832 }
833
834 /* Selection has been changed -- try to grab the primary selection
835 * if we don't have it. Also called when clicking on an insensitive selection
836 * to regain primary.
837 * Also updates toolbar info.
838 */
filer_selection_changed(FilerWindow * filer_window,gint time)839 void filer_selection_changed(FilerWindow *filer_window, gint time)
840 {
841 g_return_if_fail(filer_window != NULL);
842
843 toolbar_update_info(filer_window);
844
845 if (window_with_primary == filer_window)
846 return; /* Already got primary */
847
848 if (!view_count_selected(filer_window->view))
849 return; /* Nothing selected */
850
851 if (filer_window->temp_item_selected == FALSE &&
852 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
853 GDK_SELECTION_PRIMARY,
854 time))
855 {
856 window_with_primary = filer_window;
857 set_selection_state(filer_window, TRUE);
858 }
859 else
860 set_selection_state(filer_window, FALSE);
861 }
862
863 /* Open the item (or add it to the shell command minibuffer) */
filer_openitem(FilerWindow * filer_window,ViewIter * iter,OpenFlags flags)864 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
865 {
866 gboolean shift = (flags & OPEN_SHIFT) != 0;
867 gboolean close_mini = flags & OPEN_FROM_MINI;
868 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
869 DirItem *item;
870 const guchar *full_path;
871 gboolean wink = TRUE;
872 Directory *old_dir;
873
874 g_return_if_fail(filer_window != NULL && iter != NULL);
875
876 item = iter->peek(iter);
877
878 g_return_if_fail(item != NULL);
879
880 if (filer_window->mini_type == MINI_SHELL)
881 {
882 minibuffer_add(filer_window, item->leafname);
883 return;
884 }
885
886 if (item->base_type == TYPE_UNKNOWN)
887 dir_update_item(filer_window->directory, item->leafname);
888
889 if (item->base_type == TYPE_DIRECTORY)
890 {
891 /* Never close a filer window when opening a directory
892 * (click on a dir or click on an app with shift).
893 */
894 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
895 close_window = FALSE;
896 }
897
898 full_path = make_path(filer_window->sym_path, item->leafname);
899 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
900 wink = FALSE;
901
902 old_dir = filer_window->directory;
903 if (run_diritem(full_path, item,
904 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
905 filer_window,
906 shift))
907 {
908 if (old_dir != filer_window->directory)
909 return;
910
911 if (close_window)
912 gtk_widget_destroy(filer_window->window);
913 else
914 {
915 if (wink)
916 view_wink_item(filer_window->view, iter);
917 if (close_mini)
918 minibuffer_hide(filer_window);
919 }
920 }
921 }
922
pointer_in(GtkWidget * widget,GdkEventCrossing * event,FilerWindow * filer_window)923 static gint pointer_in(GtkWidget *widget,
924 GdkEventCrossing *event,
925 FilerWindow *filer_window)
926 {
927 may_rescan(filer_window, TRUE);
928 return FALSE;
929 }
930
pointer_out(GtkWidget * widget,GdkEventCrossing * event,FilerWindow * filer_window)931 static gint pointer_out(GtkWidget *widget,
932 GdkEventCrossing *event,
933 FilerWindow *filer_window)
934 {
935 tooltip_show(NULL);
936 return FALSE;
937 }
938
939 /* Move the cursor to the next selected item in direction 'dir'
940 * (+1 or -1).
941 */
filer_next_selected(FilerWindow * filer_window,int dir)942 void filer_next_selected(FilerWindow *filer_window, int dir)
943 {
944 ViewIter iter, cursor;
945 gboolean have_cursor;
946 ViewIface *view = filer_window->view;
947
948 g_return_if_fail(dir == 1 || dir == -1);
949
950 view_get_cursor(view, &cursor);
951 have_cursor = cursor.peek(&cursor) != NULL;
952
953 view_get_iter(view, &iter,
954 VIEW_ITER_SELECTED |
955 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
956 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
957
958 if (have_cursor && view_get_selected(view, &cursor))
959 iter.next(&iter); /* Skip the cursor itself */
960
961 if (iter.next(&iter))
962 view_cursor_to_iter(view, &iter);
963 else
964 gdk_beep();
965
966 return;
967 }
968
return_pressed(FilerWindow * filer_window,GdkEventKey * event)969 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
970 {
971 TargetFunc cb = filer_window->target_cb;
972 gpointer data = filer_window->target_data;
973 OpenFlags flags = 0;
974 ViewIter iter;
975
976 filer_target_mode(filer_window, NULL, NULL, NULL);
977
978 view_get_cursor(filer_window->view, &iter);
979 if (!iter.peek(&iter))
980 return;
981
982 if (cb)
983 {
984 cb(filer_window, &iter, data);
985 return;
986 }
987
988 if (event->state & GDK_SHIFT_MASK)
989 flags |= OPEN_SHIFT;
990 if (event->state & GDK_MOD1_MASK)
991 flags |= OPEN_CLOSE_WINDOW;
992 else
993 flags |= OPEN_SAME_WINDOW;
994
995 filer_openitem(filer_window, &iter, flags);
996 }
997
998 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
999 * changed. If no groups were loaded and there is no file then initialised
1000 * groups to an empty document.
1001 * Return the node for the 'name' group.
1002 */
group_find(char * name)1003 static xmlNode *group_find(char *name)
1004 {
1005 xmlNode *node;
1006 gchar *path;
1007
1008 /* Update the groups, if possible */
1009 path = choices_find_xdg_path_load("Groups.xml", PROJECT, SITE);
1010 if (path)
1011 {
1012 XMLwrapper *wrapper;
1013 wrapper = xml_cache_load(path);
1014 if (wrapper)
1015 {
1016 if (groups)
1017 g_object_unref(groups);
1018 groups = wrapper;
1019 }
1020
1021 g_free(path);
1022 }
1023
1024 if (!groups)
1025 {
1026 groups = xml_new(NULL);
1027 groups->doc = xmlNewDoc("1.0");
1028
1029 xmlDocSetRootElement(groups->doc,
1030 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
1031 return NULL;
1032 }
1033
1034 node = xmlDocGetRootElement(groups->doc);
1035
1036 for (node = node->xmlChildrenNode; node; node = node->next)
1037 {
1038 guchar *gid;
1039
1040 gid = xmlGetProp(node, "name");
1041
1042 if (!gid)
1043 continue;
1044
1045 if (strcmp(name, gid) != 0)
1046 continue;
1047
1048 g_free(gid);
1049
1050 return node;
1051 }
1052
1053 return NULL;
1054 }
1055
group_save(FilerWindow * filer_window,char * name)1056 static void group_save(FilerWindow *filer_window, char *name)
1057 {
1058 xmlNode *group;
1059 guchar *save_path;
1060 DirItem *item;
1061 ViewIter iter;
1062
1063 group = group_find(name);
1064 if (group)
1065 {
1066 xmlUnlinkNode(group);
1067 xmlFreeNode(group);
1068 }
1069 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
1070 NULL, "group", NULL);
1071 xmlSetProp(group, "name", name);
1072
1073 xmlNewTextChild(group, NULL, "directory", filer_window->sym_path);
1074
1075 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1076
1077 while ((item = iter.next(&iter)))
1078 xmlNewTextChild(group, NULL, "item", item->leafname);
1079
1080 save_path = choices_find_xdg_path_save("Groups.xml", PROJECT, SITE,
1081 TRUE);
1082 if (save_path)
1083 {
1084 save_xml_file(groups->doc, save_path);
1085 g_free(save_path);
1086 }
1087 }
1088
group_restore_cb(ViewIter * iter,gpointer data)1089 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
1090 {
1091 GHashTable *in_group = (GHashTable *) data;
1092
1093 return g_hash_table_lookup(in_group,
1094 iter->peek(iter)->leafname) != NULL;
1095 }
1096
group_restore(FilerWindow * filer_window,char * name)1097 static void group_restore(FilerWindow *filer_window, char *name)
1098 {
1099 GHashTable *in_group;
1100 char *path;
1101 xmlNode *group, *node;
1102
1103 group = group_find(name);
1104
1105 if (!group)
1106 {
1107 report_error(_("Group %s is not set. Select some files "
1108 "and press Ctrl+%s to set the group. Press %s "
1109 "on its own to reselect the files later.\n"
1110 "Make sure NumLock is on if you use the keypad."),
1111 name, name, name);
1112 return;
1113 }
1114
1115 node = get_subnode(group, NULL, "directory");
1116 g_return_if_fail(node != NULL);
1117 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
1118 g_return_if_fail(path != NULL);
1119
1120 if (strcmp(path, filer_window->sym_path) != 0)
1121 filer_change_to(filer_window, path, NULL);
1122 g_free(path);
1123
1124 in_group = g_hash_table_new(g_str_hash, g_str_equal);
1125 for (node = group->xmlChildrenNode; node; node = node->next)
1126 {
1127 gchar *leaf;
1128 if (node->type != XML_ELEMENT_NODE)
1129 continue;
1130 if (strcmp(node->name, "item") != 0)
1131 continue;
1132
1133 leaf = xmlNodeListGetString(groups->doc,
1134 node->xmlChildrenNode, 1);
1135 if (!leaf)
1136 g_warning("Missing leafname!\n");
1137 else
1138 g_hash_table_insert(in_group, leaf, filer_window);
1139 }
1140
1141 view_select_if(filer_window->view, &group_restore_cb, in_group);
1142
1143 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1144 g_hash_table_destroy(in_group);
1145 }
1146
popup_menu(GtkWidget * widget,FilerWindow * filer_window)1147 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
1148 {
1149 ViewIter iter;
1150 GdkEvent *event;
1151
1152 view_get_cursor(filer_window->view, &iter);
1153
1154 event = gtk_get_current_event();
1155 show_filer_menu(filer_window, event, &iter);
1156 if (event)
1157 gdk_event_free(event);
1158
1159 return TRUE;
1160 }
1161
filer_window_toggle_cursor_item_selected(FilerWindow * filer_window)1162 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
1163 {
1164 ViewIface *view = filer_window->view;
1165 ViewIter iter;
1166
1167 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
1168 if (!iter.next(&iter))
1169 return; /* No cursor */
1170
1171 if (view_get_selected(view, &iter))
1172 view_set_selected(view, &iter, FALSE);
1173 else
1174 view_set_selected(view, &iter, TRUE);
1175
1176 if (iter.next(&iter))
1177 view_cursor_to_iter(view, &iter);
1178 }
1179
filer_key_press_event(GtkWidget * widget,GdkEventKey * event,FilerWindow * filer_window)1180 gint filer_key_press_event(GtkWidget *widget,
1181 GdkEventKey *event,
1182 FilerWindow *filer_window)
1183 {
1184 ViewIface *view = filer_window->view;
1185 ViewIter cursor;
1186 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
1187 guint key = event->keyval;
1188 char group[2] = "1";
1189
1190 window_with_focus = filer_window;
1191
1192 /* Delay setting up the keys until now to speed loading... */
1193 if (ensure_filer_menu())
1194 {
1195 /* Gtk updates in an idle-handler, so force a recheck now */
1196 g_signal_emit_by_name(widget, "keys_changed");
1197 }
1198
1199 if (focus && focus == filer_window->minibuffer)
1200 if (gtk_widget_event(focus, (GdkEvent *) event))
1201 return TRUE; /* Handled */
1202
1203 if (!focus)
1204 gtk_widget_grab_focus(GTK_WIDGET(view));
1205
1206 view_get_cursor(view, &cursor);
1207 if (!cursor.peek(&cursor) && (key == GDK_Up || key == GDK_Down))
1208 {
1209 ViewIter iter;
1210 view_get_iter(view, &iter, 0);
1211 if (iter.next(&iter))
1212 view_cursor_to_iter(view, &iter);
1213 gtk_widget_grab_focus(GTK_WIDGET(view)); /* Needed? */
1214 return TRUE;
1215 }
1216
1217 switch (key)
1218 {
1219 case GDK_Escape:
1220 filer_target_mode(filer_window, NULL, NULL, NULL);
1221 view_cursor_to_iter(filer_window->view, NULL);
1222 view_clear_selection(filer_window->view);
1223 return FALSE;
1224 case GDK_Return:
1225 return_pressed(filer_window, event);
1226 break;
1227 case GDK_ISO_Left_Tab:
1228 filer_next_selected(filer_window, -1);
1229 break;
1230 case GDK_Tab:
1231 filer_next_selected(filer_window, 1);
1232 break;
1233 case GDK_BackSpace:
1234 change_to_parent(filer_window);
1235 break;
1236 case GDK_backslash:
1237 {
1238 ViewIter iter;
1239
1240 tooltip_show(NULL);
1241
1242 view_get_cursor(filer_window->view, &iter);
1243 show_filer_menu(filer_window,
1244 (GdkEvent *) event, &iter);
1245 break;
1246 }
1247 case ' ':
1248 filer_window_toggle_cursor_item_selected(filer_window);
1249 break;
1250 default:
1251 if (key >= GDK_0 && key <= GDK_9)
1252 group[0] = key - GDK_0 + '0';
1253 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1254 group[0] = key - GDK_KP_0 + '0';
1255 else
1256 {
1257 if (focus && focus != widget &&
1258 gtk_widget_get_toplevel(focus) == widget)
1259 if (gtk_widget_event(focus,
1260 (GdkEvent *) event))
1261 return TRUE; /* Handled */
1262 return FALSE;
1263 }
1264
1265 if (event->state & GDK_CONTROL_MASK)
1266 group_save(filer_window, group);
1267 else
1268 group_restore(filer_window, group);
1269 }
1270
1271 return TRUE;
1272 }
1273
filer_open_parent(FilerWindow * filer_window)1274 void filer_open_parent(FilerWindow *filer_window)
1275 {
1276 char *dir;
1277 const char *current = filer_window->sym_path;
1278
1279 if (current[0] == '/' && current[1] == '\0')
1280 return; /* Already in the root */
1281
1282 dir = g_path_get_dirname(current);
1283 filer_opendir(dir, filer_window, NULL);
1284 g_free(dir);
1285 }
1286
change_to_parent(FilerWindow * filer_window)1287 void change_to_parent(FilerWindow *filer_window)
1288 {
1289 char *dir;
1290 const char *current = filer_window->sym_path;
1291
1292 if (current[0] == '/' && current[1] == '\0')
1293 return; /* Already in the root */
1294
1295 if (mount_is_user_mounted(filer_window->real_path))
1296 may_offer_unmount(filer_window,
1297 g_strdup(filer_window->real_path));
1298
1299 dir = g_path_get_dirname(current);
1300 filer_change_to(filer_window, dir, g_basename(current));
1301 g_free(dir);
1302 }
1303
1304 /* Removes trailing /s from path (modified in place) */
tidy_sympath(gchar * path)1305 static void tidy_sympath(gchar *path)
1306 {
1307 int l;
1308
1309 g_return_if_fail(path != NULL);
1310
1311 l = strlen(path);
1312 while (l > 1 && path[l - 1] == '/')
1313 {
1314 l--;
1315 path[l] = '\0';
1316 }
1317 }
1318
1319 /* Make filer_window display path. When finished, highlight item 'from', or
1320 * the first item if from is NULL. If there is currently no cursor then
1321 * simply wink 'from' (if not NULL).
1322 * If the cause was a key event and we resize, warp the pointer.
1323 */
filer_change_to(FilerWindow * filer_window,const char * path,const char * from)1324 void filer_change_to(FilerWindow *filer_window,
1325 const char *path, const char *from)
1326 {
1327 char *from_dup;
1328 char *sym_path, *real_path;
1329 Directory *new_dir;
1330
1331 g_return_if_fail(filer_window != NULL);
1332
1333 filer_cancel_thumbnails(filer_window);
1334
1335 tooltip_show(NULL);
1336
1337 sym_path = g_strdup(path);
1338 real_path = pathdup(path);
1339 new_dir = g_fscache_lookup(dir_cache, real_path);
1340
1341 if (!new_dir)
1342 {
1343 delayed_error(_("Directory '%s' is not accessible"),
1344 sym_path);
1345 g_free(real_path);
1346 g_free(sym_path);
1347 return;
1348 }
1349
1350 if (o_unique_filer_windows.int_value && !spring_in_progress)
1351 {
1352 FilerWindow *fw;
1353
1354 fw = find_filer_window(sym_path, filer_window);
1355 if (fw)
1356 gtk_widget_destroy(fw->window);
1357 }
1358
1359 from_dup = from && *from ? g_strdup(from) : NULL;
1360
1361 detach(filer_window);
1362 g_free(filer_window->real_path);
1363 g_free(filer_window->sym_path);
1364 filer_window->real_path = real_path;
1365 filer_window->sym_path = sym_path;
1366 tidy_sympath(filer_window->sym_path);
1367
1368 filer_window->directory = new_dir;
1369
1370 g_free(filer_window->auto_select);
1371 filer_window->auto_select = from_dup;
1372
1373 filer_window->had_cursor = filer_window->had_cursor ||
1374 view_cursor_visible(filer_window->view);
1375
1376 filer_set_title(filer_window);
1377 if (filer_window->window->window)
1378 gdk_window_set_role(filer_window->window->window,
1379 filer_window->sym_path);
1380 view_cursor_to_iter(filer_window->view, NULL);
1381
1382 attach(filer_window);
1383
1384 check_settings(filer_window);
1385
1386 display_set_actual_size(filer_window, FALSE);
1387
1388 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1389 view_autosize(filer_window->view);
1390
1391 if (filer_window->mini_type == MINI_PATH)
1392 g_idle_add((GSourceFunc) minibuffer_show_cb, filer_window);
1393 }
1394
1395 /* Returns a list containing the full (sym) pathname of every selected item.
1396 * You must g_free() each item in the list.
1397 */
filer_selected_items(FilerWindow * filer_window)1398 GList *filer_selected_items(FilerWindow *filer_window)
1399 {
1400 GList *retval = NULL;
1401 guchar *dir = filer_window->sym_path;
1402 ViewIter iter;
1403 DirItem *item;
1404
1405 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1406 while ((item = iter.next(&iter)))
1407 {
1408 retval = g_list_prepend(retval,
1409 g_strdup(make_path(dir, item->leafname)));
1410 }
1411
1412 return g_list_reverse(retval);
1413 }
1414
1415 /* Return the single selected item. Error if nothing is selected. */
filer_selected_item(FilerWindow * filer_window)1416 DirItem *filer_selected_item(FilerWindow *filer_window)
1417 {
1418 ViewIter iter;
1419 DirItem *item;
1420
1421 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1422
1423 item = iter.next(&iter);
1424 g_return_val_if_fail(item != NULL, NULL);
1425 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1426
1427 return item;
1428 }
1429
1430 /* Creates and shows a new filer window.
1431 * If src_win != NULL then display options can be taken from that source window.
1432 * Returns the new filer window, or NULL on error.
1433 * Note: if unique windows is in use, may return an existing window.
1434 */
filer_opendir(const char * path,FilerWindow * src_win,const gchar * wm_class)1435 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1436 const gchar *wm_class)
1437 {
1438 FilerWindow *filer_window;
1439 char *real_path;
1440 DisplayStyle dstyle;
1441 DetailsType dtype;
1442 SortType s_type;
1443 GtkSortType s_order;
1444 Settings *dir_settings = NULL;
1445 gboolean force_resize = TRUE;
1446
1447 /* Get the real pathname of the directory and copy it */
1448 real_path = pathdup(path);
1449
1450 if (o_unique_filer_windows.int_value && !spring_in_progress)
1451 {
1452 FilerWindow *same_dir_window;
1453
1454 same_dir_window = find_filer_window(path, NULL);
1455
1456 if (same_dir_window)
1457 {
1458 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1459 return same_dir_window;
1460 }
1461 }
1462
1463 filer_window = g_new(FilerWindow, 1);
1464 filer_window->message = NULL;
1465 filer_window->minibuffer = NULL;
1466 filer_window->minibuffer_label = NULL;
1467 filer_window->minibuffer_area = NULL;
1468 filer_window->temp_show_hidden = FALSE;
1469 filer_window->sym_path = g_strdup(path);
1470 filer_window->real_path = real_path;
1471 filer_window->scanning = FALSE;
1472 filer_window->had_cursor = FALSE;
1473 filer_window->auto_select = NULL;
1474 filer_window->toolbar_text = NULL;
1475 filer_window->target_cb = NULL;
1476 filer_window->mini_type = MINI_NONE;
1477 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1478 filer_window->toolbar = NULL;
1479 filer_window->toplevel_vbox = NULL;
1480 filer_window->view_hbox = NULL;
1481 filer_window->view = NULL;
1482 filer_window->scrollbar = NULL;
1483 filer_window->auto_scroll = -1;
1484 filer_window->window_id = NULL;
1485
1486 tidy_sympath(filer_window->sym_path);
1487
1488 /* Finds the entry for this directory in the dir cache, creating
1489 * a new one if needed. This does not cause a scan to start,
1490 * so if a new entry is created then it will be empty.
1491 */
1492 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1493 if (!filer_window->directory)
1494 {
1495 delayed_error(_("Directory '%s' not found."), path);
1496 g_free(filer_window->real_path);
1497 g_free(filer_window->sym_path);
1498 g_free(filer_window);
1499 return NULL;
1500 }
1501
1502 filer_window->temp_item_selected = FALSE;
1503 filer_window->flags = (FilerFlags) 0;
1504 filer_window->details_type = DETAILS_TIMES;
1505 filer_window->display_style = UNKNOWN_STYLE;
1506 filer_window->display_style_wanted = UNKNOWN_STYLE;
1507 filer_window->thumb_queue = NULL;
1508 filer_window->max_thumbs = 0;
1509 filer_window->sort_type = -1;
1510
1511 filer_window->filter = FILER_SHOW_ALL;
1512 filer_window->filter_string = NULL;
1513 filer_window->regexp = NULL;
1514 filer_window->filter_directories = FALSE;
1515
1516 if (src_win && o_display_inherit_options.int_value)
1517 {
1518 s_type = src_win->sort_type;
1519 s_order = src_win->sort_order;
1520 dstyle = src_win->display_style_wanted;
1521 dtype = src_win->details_type;
1522 filer_window->show_hidden = src_win->show_hidden;
1523 filer_window->show_thumbs = src_win->show_thumbs;
1524 filer_window->view_type = src_win->view_type;
1525
1526 filer_window->filter_directories = src_win->filter_directories;
1527 filer_set_filter(filer_window, src_win->filter,
1528 src_win->filter_string);
1529 }
1530 else
1531 {
1532 s_type = o_display_sort_by.int_value;
1533 s_order = GTK_SORT_ASCENDING;
1534 dstyle = o_display_size.int_value;
1535 dtype = o_display_details.int_value;
1536 filer_window->show_hidden = o_display_show_hidden.int_value;
1537 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1538 filer_window->view_type = o_filer_view_type.int_value;
1539 }
1540
1541 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
1542 filer_window->sym_path);
1543 if (dir_settings)
1544 {
1545 /* Override the current defaults with the per-directory
1546 * user settings.
1547 */
1548 if (dir_settings->flags & SET_HIDDEN)
1549 filer_window->show_hidden = dir_settings->show_hidden;
1550
1551 if (dir_settings->flags & SET_STYLE)
1552 dstyle = dir_settings->display_style;
1553
1554 if (dir_settings->flags & SET_DETAILS)
1555 {
1556 dtype = dir_settings->details_type;
1557 filer_window->view_type = dir_settings->view_type;
1558 }
1559
1560 if (dir_settings->flags & SET_SORT)
1561 {
1562 s_type = dir_settings->sort_type;
1563 s_order = dir_settings->sort_order;
1564 }
1565
1566 if (dir_settings->flags & SET_THUMBS)
1567 filer_window->show_thumbs = dir_settings->show_thumbs;
1568
1569 if (dir_settings->flags & SET_FILTER)
1570 {
1571 filer_set_filter(filer_window,
1572 dir_settings->filter_type,
1573 dir_settings->filter);
1574 filer_set_filter_directories(filer_window,
1575 dir_settings->filter_directories);
1576 }
1577 }
1578
1579 /* Add all the user-interface elements & realise */
1580 filer_add_widgets(filer_window, wm_class);
1581 if (src_win)
1582 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1583 GTK_WIN_POS_MOUSE);
1584
1585 if (dir_settings)
1586 {
1587 if (dir_settings->flags & SET_POSITION)
1588 {
1589 gtk_window_move(GTK_WINDOW(filer_window->window),
1590 dir_settings->x, dir_settings->y);
1591 }
1592 if (dir_settings->flags & SET_SIZE)
1593 {
1594 filer_window_set_size(filer_window,
1595 dir_settings->width,
1596 dir_settings->height);
1597 force_resize = o_filer_auto_resize.int_value != RESIZE_NEVER;
1598 }
1599 }
1600
1601 /* Connect to all the signal handlers */
1602 filer_add_signals(filer_window);
1603
1604 display_set_layout(filer_window, dstyle, dtype, force_resize);
1605 display_set_sort_type(filer_window, s_type, s_order);
1606
1607 /* Open the window after a timeout, or when scanning stops.
1608 * Do this before attaching, because attach() might tell us to
1609 * stop scanning (if a scan isn't needed).
1610 */
1611 filer_window->open_timeout = g_timeout_add(500,
1612 (GSourceFunc) open_filer_window,
1613 filer_window);
1614
1615 /* The view is created empty and then attach() is called, which
1616 * links the filer window to the entry in the directory cache we
1617 * looked up / created above.
1618 *
1619 * The attach() function will immediately callback to the filer window
1620 * to deliver a list of all known entries in the directory (so,
1621 * the number of items will be known after attach() returns).
1622 *
1623 * If the directory was not in the cache (because it hadn't been
1624 * opened it before) then the types and icons for the entries are
1625 * not know, but the list of names is.
1626 */
1627
1628 attach(filer_window);
1629
1630 number_of_windows++;
1631 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1632
1633 return filer_window;
1634 }
1635
filer_set_view_type(FilerWindow * filer_window,ViewType type)1636 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1637 {
1638 GtkWidget *view = NULL;
1639 Directory *dir = NULL;
1640 GHashTable *selected = NULL;
1641
1642 g_return_if_fail(filer_window != NULL);
1643
1644 motion_window = NULL;
1645
1646 if (filer_window->view)
1647 {
1648 /* Save the current selection */
1649 ViewIter iter;
1650 DirItem *item;
1651
1652 selected = g_hash_table_new(g_str_hash, g_str_equal);
1653 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1654 while ((item = iter.next(&iter)))
1655 g_hash_table_insert(selected, item->leafname, "yes");
1656
1657 /* Destroy the old view */
1658 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1659 filer_window->view = NULL;
1660
1661 dir = filer_window->directory;
1662 g_object_ref(dir);
1663 detach(filer_window);
1664 }
1665
1666 switch (type)
1667 {
1668 case VIEW_TYPE_COLLECTION:
1669 view = view_collection_new(filer_window);
1670 break;
1671 case VIEW_TYPE_DETAILS:
1672 view = view_details_new(filer_window);
1673 break;
1674 }
1675
1676 g_return_if_fail(view != NULL);
1677
1678 filer_window->view = VIEW(view);
1679 filer_window->view_type = type;
1680
1681 gtk_box_pack_start(filer_window->view_hbox, view, TRUE, TRUE, 0);
1682 gtk_widget_show(view);
1683
1684 /* Drag and drop events */
1685 make_drop_target(view, 0);
1686 g_signal_connect(view, "drag_motion",
1687 G_CALLBACK(drag_motion), filer_window);
1688 g_signal_connect(view, "drag_leave",
1689 G_CALLBACK(drag_leave), filer_window);
1690 g_signal_connect(view, "drag_end",
1691 G_CALLBACK(drag_end), filer_window);
1692 /* Dragging from us... */
1693 g_signal_connect(view, "drag_data_get",
1694 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1695
1696 if (dir)
1697 {
1698 /* Only when changing type. Otherwise, will attach later. */
1699 filer_window->directory = dir;
1700 attach(filer_window);
1701
1702 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
1703 view_autosize(filer_window->view);
1704 }
1705
1706 if (selected)
1707 {
1708 ViewIter iter;
1709 DirItem *item;
1710
1711 view_get_iter(filer_window->view, &iter, 0);
1712 while ((item = iter.next(&iter)))
1713 {
1714 if (g_hash_table_lookup(selected, item->leafname))
1715 view_set_selected(filer_window->view,
1716 &iter, TRUE);
1717 }
1718 g_hash_table_destroy(selected);
1719 }
1720 }
1721
1722 /* This adds all the widgets to a new filer window. It is in a separate
1723 * function because filer_opendir() was getting too long...
1724 */
filer_add_widgets(FilerWindow * filer_window,const gchar * wm_class)1725 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1726 {
1727 GtkWidget *hbox, *vbox;
1728
1729 /* Create the top-level window widget */
1730 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1731 filer_set_title(filer_window);
1732 gtk_widget_set_name(filer_window->window, "rox-filer");
1733
1734 if (wm_class)
1735 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1736 wm_class, PROJECT);
1737
1738 /* This property is cleared when the window is destroyed.
1739 * You can thus ref filer_window->window and use this to see
1740 * if the window no longer exists.
1741 */
1742 g_object_set_data(G_OBJECT(filer_window->window),
1743 "filer_window", filer_window);
1744
1745 /* Create this now to make the Adjustment before the View */
1746 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1747
1748 vbox = gtk_vbox_new(FALSE, 0);
1749 gtk_container_add(GTK_CONTAINER(filer_window->window), vbox);
1750
1751 filer_window->toplevel_vbox = GTK_BOX(vbox);
1752
1753 /* If there's a message that should be displayed in each window (eg
1754 * 'Running as root'), add it here.
1755 */
1756 if (show_user_message)
1757 {
1758 filer_window->message = gtk_label_new(show_user_message);
1759 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1760 FALSE, TRUE, 0);
1761 gtk_widget_show(filer_window->message);
1762 }
1763
1764 hbox = gtk_hbox_new(FALSE, 0);
1765 filer_window->view_hbox = GTK_BOX(hbox);
1766 gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
1767 /* Add the main View widget */
1768 filer_set_view_type(filer_window, filer_window->view_type);
1769 /* Put the scrollbar next to the View */
1770 gtk_box_pack_end(GTK_BOX(hbox),
1771 filer_window->scrollbar, FALSE, TRUE, 0);
1772 gtk_widget_show(hbox);
1773
1774 /* If we want a toolbar, create it now */
1775 toolbar_update_toolbar(filer_window);
1776
1777 /* And the minibuffer (hidden to start with) */
1778 create_minibuffer(filer_window);
1779 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1780 FALSE, TRUE, 0);
1781
1782 /* And the thumbnail progress bar (also hidden) */
1783 {
1784 GtkWidget *cancel;
1785
1786 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1787 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1788 FALSE, TRUE, 0);
1789
1790 filer_window->thumb_progress = gtk_progress_bar_new();
1791
1792 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1793 filer_window->thumb_progress, TRUE, TRUE, 0);
1794
1795 cancel = gtk_button_new_with_label(_("Cancel"));
1796 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1797 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1798 cancel, FALSE, TRUE, 0);
1799 g_signal_connect_swapped(cancel, "clicked",
1800 G_CALLBACK(filer_cancel_thumbnails),
1801 filer_window);
1802 }
1803
1804 gtk_widget_show(vbox);
1805 gtk_widget_show(filer_window->scrollbar);
1806
1807 gtk_widget_realize(filer_window->window);
1808
1809 gdk_window_set_role(filer_window->window->window,
1810 filer_window->sym_path);
1811
1812 filer_window_set_size(filer_window, 4, 4);
1813 }
1814
filer_add_signals(FilerWindow * filer_window)1815 static void filer_add_signals(FilerWindow *filer_window)
1816 {
1817 GtkTargetEntry target_table[] =
1818 {
1819 {"text/uri-list", 0, TARGET_URI_LIST},
1820 {"UTF8_STRING", 0, TARGET_STRING},
1821 {"STRING", 0, TARGET_STRING},
1822 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1823 };
1824
1825 /* Events on the top-level window */
1826 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1827 g_signal_connect(filer_window->window, "enter-notify-event",
1828 G_CALLBACK(pointer_in), filer_window);
1829 g_signal_connect(filer_window->window, "leave-notify-event",
1830 G_CALLBACK(pointer_out), filer_window);
1831 g_signal_connect(filer_window->window, "destroy",
1832 G_CALLBACK(filer_window_destroyed), filer_window);
1833 g_signal_connect(filer_window->window, "delete-event",
1834 G_CALLBACK(filer_window_delete), filer_window);
1835
1836 g_signal_connect(filer_window->window, "selection_clear_event",
1837 G_CALLBACK(filer_lost_primary), filer_window);
1838
1839 g_signal_connect(filer_window->window, "selection_get",
1840 G_CALLBACK(selection_get), filer_window);
1841 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1842 GDK_SELECTION_PRIMARY,
1843 target_table,
1844 sizeof(target_table) / sizeof(*target_table));
1845
1846 g_signal_connect(filer_window->window, "popup-menu",
1847 G_CALLBACK(popup_menu), filer_window);
1848 g_signal_connect(filer_window->window, "key_press_event",
1849 G_CALLBACK(filer_key_press_event), filer_window);
1850
1851 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1852 filer_keys);
1853 }
1854
clear_scanning_display(FilerWindow * filer_window)1855 static gint clear_scanning_display(FilerWindow *filer_window)
1856 {
1857 if (filer_exists(filer_window))
1858 filer_set_title(filer_window);
1859 return FALSE;
1860 }
1861
set_scanning_display(FilerWindow * filer_window,gboolean scanning)1862 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1863 {
1864 if (scanning == filer_window->scanning)
1865 return;
1866 filer_window->scanning = scanning;
1867
1868 if (scanning)
1869 filer_set_title(filer_window);
1870 else
1871 g_timeout_add(300, (GSourceFunc) clear_scanning_display,
1872 filer_window);
1873 }
1874
1875 /* Note that filer_window may not exist after this call.
1876 * Returns TRUE iff the directory still exists.
1877 */
filer_update_dir(FilerWindow * filer_window,gboolean warning)1878 gboolean filer_update_dir(FilerWindow *filer_window, gboolean warning)
1879 {
1880 gboolean still_exists;
1881
1882 still_exists = may_rescan(filer_window, warning);
1883
1884 if (still_exists)
1885 dir_update(filer_window->directory, filer_window->sym_path);
1886
1887 return still_exists;
1888 }
1889
filer_update_all(void)1890 void filer_update_all(void)
1891 {
1892 GList *next = all_filer_windows;
1893
1894 while (next)
1895 {
1896 FilerWindow *filer_window = (FilerWindow *) next->data;
1897
1898 /* Updating directory may remove it from list -- stop sending
1899 * patches to move this line!
1900 */
1901 next = next->next;
1902
1903 /* Don't trigger a refresh if we're already scanning.
1904 * Otherwise, two views of a single directory will trigger
1905 * two scans.
1906 */
1907 if (filer_window->directory &&
1908 !filer_window->directory->scanning)
1909 filer_update_dir(filer_window, TRUE);
1910 }
1911 }
1912
1913 /* Refresh the various caches even if we don't think we need to */
full_refresh(void)1914 void full_refresh(void)
1915 {
1916 mount_update(TRUE);
1917 reread_mime_files(); /* Refreshes all windows */
1918 }
1919
1920 /* See whether a filer window with a given path already exists
1921 * and is different from diff.
1922 */
find_filer_window(const char * sym_path,FilerWindow * diff)1923 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1924 {
1925 GList *next;
1926
1927 for (next = all_filer_windows; next; next = next->next)
1928 {
1929 FilerWindow *filer_window = (FilerWindow *) next->data;
1930
1931 if (filer_window != diff &&
1932 strcmp(sym_path, filer_window->sym_path) == 0)
1933 return filer_window;
1934 }
1935
1936 return NULL;
1937 }
1938
1939 /* This path has been mounted/umounted/deleted some files - update all dirs */
filer_check_mounted(const char * real_path)1940 void filer_check_mounted(const char *real_path)
1941 {
1942 GList *next = all_filer_windows;
1943 gchar *parent;
1944 int len;
1945 gboolean resize = o_filer_auto_resize.int_value == RESIZE_ALWAYS;
1946
1947 /* DOS disks, etc, often don't change the mtime of the root directory
1948 * on modification, so force a refresh now.
1949 */
1950 g_fscache_update(dir_cache, real_path);
1951
1952 len = strlen(real_path);
1953
1954 while (next)
1955 {
1956 FilerWindow *filer_window = (FilerWindow *) next->data;
1957
1958 next = next->next;
1959
1960 if (strncmp(real_path, filer_window->real_path, len) == 0)
1961 {
1962 char s = filer_window->real_path[len];
1963
1964 if (s == '/' || s == '\0')
1965 {
1966 if (filer_update_dir(filer_window, FALSE) &&
1967 resize)
1968 view_autosize(filer_window->view);
1969 }
1970 }
1971 }
1972
1973 parent = g_path_get_dirname(real_path);
1974 refresh_dirs(parent);
1975 g_free(parent);
1976
1977 icons_may_update(real_path);
1978 }
1979
1980 /* Close all windows displaying 'path' or subdirectories of 'path' */
filer_close_recursive(const char * path)1981 void filer_close_recursive(const char *path)
1982 {
1983 GList *next = all_filer_windows;
1984 gchar *real;
1985 int len;
1986
1987 real = pathdup(path);
1988 len = strlen(real);
1989
1990 while (next)
1991 {
1992 FilerWindow *filer_window = (FilerWindow *) next->data;
1993
1994 next = next->next;
1995
1996 if (strncmp(real, filer_window->real_path, len) == 0)
1997 {
1998 char s = filer_window->real_path[len];
1999
2000 if (len == 1 || s == '/' || s == '\0')
2001 gtk_widget_destroy(filer_window->window);
2002 }
2003 }
2004 }
2005
2006 /* Like minibuffer_show(), except that:
2007 * - It returns FALSE (to be used from an idle callback)
2008 * - It checks that the filer window still exists.
2009 */
minibuffer_show_cb(FilerWindow * filer_window)2010 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
2011 {
2012 if (filer_exists(filer_window))
2013 minibuffer_show(filer_window, MINI_PATH);
2014 return FALSE;
2015 }
2016
2017 /* TRUE iff filer_window points to an existing FilerWindow
2018 * structure.
2019 */
filer_exists(FilerWindow * filer_window)2020 gboolean filer_exists(FilerWindow *filer_window)
2021 {
2022 GList *next;
2023
2024 for (next = all_filer_windows; next; next = next->next)
2025 {
2026 FilerWindow *fw = (FilerWindow *) next->data;
2027
2028 if (fw == filer_window)
2029 return TRUE;
2030 }
2031
2032 return FALSE;
2033 }
2034
filer_get_by_id(const char * id)2035 FilerWindow *filer_get_by_id(const char *id)
2036 {
2037 return g_hash_table_lookup(window_with_id, id);
2038 }
2039
filer_set_id(FilerWindow * filer_window,const char * id)2040 void filer_set_id(FilerWindow *filer_window, const char *id)
2041 {
2042 g_return_if_fail(filer_window != NULL);
2043
2044 if (filer_window->window_id)
2045 {
2046 g_hash_table_remove(window_with_id, filer_window->window_id);
2047 g_free(filer_window->window_id);
2048 filer_window->window_id = NULL;
2049 }
2050
2051 if (id)
2052 {
2053 filer_window->window_id = g_strdup(id);
2054 g_hash_table_insert(window_with_id,
2055 filer_window->window_id,
2056 filer_window);
2057 }
2058 }
2059
2060 /* Make sure the window title is up-to-date */
filer_set_title(FilerWindow * filer_window)2061 void filer_set_title(FilerWindow *filer_window)
2062 {
2063 gchar *title = NULL;
2064 guchar *flags = "";
2065
2066 if (filer_window->scanning ||
2067 filer_window->filter != FILER_SHOW_ALL ||
2068 filer_window->show_hidden || filer_window->show_thumbs)
2069 {
2070 if (o_short_flag_names.int_value)
2071 {
2072 const gchar *hidden = "";
2073
2074 switch(filer_window->filter) {
2075 case FILER_SHOW_ALL:
2076 hidden=filer_window->show_hidden? _("A") : "";
2077 break;
2078 case FILER_SHOW_GLOB: hidden = _("G"); break;
2079 default: break;
2080 }
2081
2082 flags = g_strconcat(" +",
2083 filer_window->scanning ? _("S") : "",
2084 hidden,
2085 filer_window->show_thumbs ? _("T") : "",
2086 NULL);
2087 }
2088 else
2089 {
2090 gchar *hidden = NULL;
2091
2092 switch(filer_window->filter) {
2093 case FILER_SHOW_ALL:
2094 hidden = g_strdup(filer_window->show_hidden
2095 ? _("All, ") : "");
2096 break;
2097 case FILER_SHOW_GLOB:
2098 hidden = g_strdup_printf(_("Glob (%s), "),
2099 filer_window->filter_string);
2100 break;
2101 default:
2102 hidden =g_strdup("");
2103 break;
2104 }
2105 flags = g_strconcat(" (",
2106 filer_window->scanning ? _("Scanning, ") : "",
2107 hidden,
2108 filer_window->show_thumbs ? _("Thumbs, ") : "",
2109 NULL);
2110 flags[strlen(flags) - 2] = ')';
2111 g_free(hidden);
2112 }
2113 }
2114
2115 if (not_local)
2116 title = g_strconcat("//", our_host_name(),
2117 filer_window->sym_path, flags, NULL);
2118
2119 if (!title && home_dir_len > 1 &&
2120 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
2121 {
2122 guchar sep = filer_window->sym_path[home_dir_len];
2123
2124 if (sep == '\0' || sep == '/')
2125 title = g_strconcat("~",
2126 filer_window->sym_path + home_dir_len,
2127 flags,
2128 NULL);
2129 }
2130
2131 if (!title)
2132 title = g_strconcat(filer_window->sym_path, flags, NULL);
2133
2134 ensure_utf8(&title);
2135
2136 if (filer_window->directory->error)
2137 {
2138 gchar *old = title;
2139 title = g_strconcat(old, ": ", filer_window->directory->error,
2140 NULL);
2141 g_free(old);
2142 }
2143
2144 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
2145
2146 g_free(title);
2147
2148 if (flags[0] != '\0')
2149 g_free(flags);
2150 }
2151
2152 /* Reconnect to the same directory (used when the Show Hidden option is
2153 * toggled). This has the side-effect of updating the window title.
2154 */
filer_detach_rescan(FilerWindow * filer_window)2155 void filer_detach_rescan(FilerWindow *filer_window)
2156 {
2157 Directory *dir = filer_window->directory;
2158
2159 g_object_ref(dir);
2160 detach(filer_window);
2161 filer_window->directory = dir;
2162 attach(filer_window);
2163 }
2164
2165 /* Puts the filer window into target mode. When an item is chosen,
2166 * fn(filer_window, iter, data) is called. 'reason' will be displayed
2167 * on the toolbar while target mode is active.
2168 *
2169 * Use fn == NULL to cancel target mode.
2170 */
filer_target_mode(FilerWindow * filer_window,TargetFunc fn,gpointer data,const char * reason)2171 void filer_target_mode(FilerWindow *filer_window,
2172 TargetFunc fn,
2173 gpointer data,
2174 const char *reason)
2175 {
2176 TargetFunc old_fn = filer_window->target_cb;
2177
2178 if (fn != old_fn)
2179 gdk_window_set_cursor(
2180 GTK_WIDGET(filer_window->view)->window,
2181 fn ? crosshair : NULL);
2182
2183 filer_window->target_cb = fn;
2184 filer_window->target_data = data;
2185
2186 if (filer_window->toolbar_text == NULL)
2187 return;
2188
2189 if (fn)
2190 gtk_label_set_text(
2191 GTK_LABEL(filer_window->toolbar_text), reason);
2192 else if (o_toolbar_info.int_value)
2193 {
2194 if (old_fn)
2195 toolbar_update_info(filer_window);
2196 }
2197 else
2198 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2199 }
2200
set_selection_state(FilerWindow * filer_window,gboolean normal)2201 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2202 {
2203 GtkStateType old_state = filer_window->selection_state;
2204
2205 filer_window->selection_state = normal
2206 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2207
2208 if (old_state != filer_window->selection_state
2209 && view_count_selected(filer_window->view))
2210 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
2211 }
2212
filer_cancel_thumbnails(FilerWindow * filer_window)2213 void filer_cancel_thumbnails(FilerWindow *filer_window)
2214 {
2215 gtk_widget_hide(filer_window->thumb_bar);
2216
2217 destroy_glist(&filer_window->thumb_queue);
2218 filer_window->max_thumbs = 0;
2219 }
2220
2221 /* Generate the next thumb for this window. The window object is
2222 * unref'd when there is nothing more to do.
2223 * If the window no longer has a filer window, nothing is done.
2224 */
filer_next_thumb_real(GObject * window)2225 static gboolean filer_next_thumb_real(GObject *window)
2226 {
2227 FilerWindow *filer_window;
2228 gchar *path;
2229 int done, total;
2230
2231 filer_window = g_object_get_data(window, "filer_window");
2232
2233 if (!filer_window)
2234 {
2235 g_object_unref(window);
2236 return FALSE;
2237 }
2238
2239 if (!filer_window->thumb_queue)
2240 {
2241 filer_cancel_thumbnails(filer_window);
2242 g_object_unref(window);
2243 return FALSE;
2244 }
2245
2246 total = filer_window->max_thumbs;
2247 done = total - g_list_length(filer_window->thumb_queue);
2248
2249 path = (gchar *) filer_window->thumb_queue->data;
2250
2251 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2252
2253 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2254 path);
2255 g_free(path);
2256
2257 gtk_progress_bar_set_fraction(
2258 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2259 done / (float) total);
2260
2261 return FALSE;
2262 }
2263
2264 /* path is the thumb just loaded, if any.
2265 * window is unref'd (eventually).
2266 */
filer_next_thumb(GObject * window,const gchar * path)2267 static void filer_next_thumb(GObject *window, const gchar *path)
2268 {
2269 if (path)
2270 dir_force_update_path(path);
2271
2272 g_idle_add((GSourceFunc) filer_next_thumb_real, window);
2273 }
2274
start_thumb_scanning(FilerWindow * filer_window)2275 static void start_thumb_scanning(FilerWindow *filer_window)
2276 {
2277 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2278 return; /* Already scanning */
2279
2280 gtk_widget_show_all(filer_window->thumb_bar);
2281
2282 g_object_ref(G_OBJECT(filer_window->window));
2283 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2284 }
2285
2286 /* Set this image to be loaded some time in the future */
filer_create_thumb(FilerWindow * filer_window,const gchar * path)2287 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2288 {
2289 if (g_list_find_custom(filer_window->thumb_queue, path,
2290 (GCompareFunc) strcmp))
2291 return;
2292
2293 if (!filer_window->thumb_queue)
2294 filer_window->max_thumbs=0;
2295 filer_window->max_thumbs++;
2296
2297 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2298 g_strdup(path));
2299
2300 if (filer_window->scanning)
2301 return; /* Will start when scan ends */
2302
2303 start_thumb_scanning(filer_window);
2304 }
2305
2306 /* If thumbnail display is on, look through all the items in this directory
2307 * and start creating or updating the thumbnails as needed.
2308 */
filer_create_thumbs(FilerWindow * filer_window)2309 void filer_create_thumbs(FilerWindow *filer_window)
2310 {
2311 DirItem *item;
2312 ViewIter iter;
2313
2314 if (!filer_window->show_thumbs)
2315 return;
2316
2317 view_get_iter(filer_window->view, &iter, 0);
2318
2319 while ((item = iter.next(&iter)))
2320 {
2321 MaskedPixmap *pixmap;
2322 const guchar *path;
2323 gboolean found;
2324
2325 if (item->base_type != TYPE_FILE)
2326 continue;
2327
2328 /*if (strcmp(item->mime_type->media_type, "image") != 0)
2329 continue;*/
2330
2331 path = make_path(filer_window->real_path, item->leafname);
2332
2333 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2334 FSCACHE_LOOKUP_ONLY_NEW, &found);
2335 if (pixmap)
2336 g_object_unref(pixmap);
2337
2338 /* If we didn't get an image, it could be because:
2339 *
2340 * - We're loading the image now. found is TRUE,
2341 * and we'll update the item later.
2342 * - We tried to load the image and failed. found
2343 * is TRUE.
2344 * - We haven't tried loading the image. found is
2345 * FALSE, and we start creating the thumb here.
2346 */
2347 if (!found)
2348 filer_create_thumb(filer_window, path);
2349 }
2350 }
2351
filer_options_changed(void)2352 static void filer_options_changed(void)
2353 {
2354 if (o_short_flag_names.has_changed)
2355 {
2356 GList *next;
2357
2358 for (next = all_filer_windows; next; next = next->next)
2359 {
2360 FilerWindow *filer_window = (FilerWindow *) next->data;
2361
2362 filer_set_title(filer_window);
2363 }
2364 }
2365 }
2366
2367 /* Append interesting information to this GString */
filer_add_tip_details(FilerWindow * filer_window,GString * tip,DirItem * item)2368 void filer_add_tip_details(FilerWindow *filer_window,
2369 GString *tip, DirItem *item)
2370 {
2371 const guchar *fullpath = NULL;
2372
2373 fullpath = make_path(filer_window->real_path, item->leafname);
2374
2375 if (item->flags & ITEM_FLAG_SYMLINK)
2376 {
2377 char *target;
2378
2379 target = readlink_dup(fullpath);
2380 if (target)
2381 {
2382 ensure_utf8(&target);
2383
2384 g_string_append(tip, _("Symbolic link to "));
2385 g_string_append(tip, target);
2386 g_string_append_c(tip, '\n');
2387 g_free(target);
2388 }
2389 }
2390
2391 if (item->flags & ITEM_FLAG_APPDIR)
2392 {
2393 XMLwrapper *info;
2394 xmlNode *node;
2395
2396 info = appinfo_get(fullpath, item);
2397 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
2398 {
2399 guchar *str;
2400 str = xmlNodeListGetString(node->doc,
2401 node->xmlChildrenNode, 1);
2402 if (str)
2403 {
2404 g_string_append(tip, str);
2405 g_string_append_c(tip, '\n');
2406 g_free(str);
2407 }
2408 }
2409 if (info)
2410 g_object_unref(info);
2411 }
2412 else if (item->mime_type == application_x_desktop)
2413 {
2414 char *summary;
2415 summary = tip_from_desktop_file(fullpath);
2416 if (summary)
2417 {
2418 g_string_append(tip, summary);
2419 g_string_append_c(tip, '\n');
2420 g_free(summary);
2421 }
2422 }
2423
2424 if (!g_utf8_validate(item->leafname, -1, NULL))
2425 g_string_append(tip,
2426 _("This filename is not valid UTF-8. "
2427 "You should rename it.\n"));
2428 }
2429
2430 /* Return the selection as a text/uri-list.
2431 * g_free() the result.
2432 */
filer_create_uri_list(FilerWindow * filer_window)2433 static guchar *filer_create_uri_list(FilerWindow *filer_window)
2434 {
2435 GString *string;
2436 GString *leader;
2437 ViewIter iter;
2438 DirItem *item;
2439 guchar *retval;
2440
2441 g_return_val_if_fail(filer_window != NULL, NULL);
2442
2443 string = g_string_new(NULL);
2444
2445 leader = g_string_new(filer_window->sym_path);
2446 if (leader->str[leader->len - 1] != '/')
2447 g_string_append_c(leader, '/');
2448
2449 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
2450 while ((item = iter.next(&iter)))
2451 {
2452 EscapedPath *uri;
2453 char *path;
2454
2455 path = g_strconcat(leader->str, item->leafname, NULL);
2456 uri = encode_path_as_uri(path);
2457 g_string_append(string, (char *) uri);
2458 g_string_append(string, "\r\n");
2459 g_free(path);
2460 g_free(uri);
2461 }
2462
2463 g_string_free(leader, TRUE);
2464 retval = string->str;
2465 g_string_free(string, FALSE);
2466
2467 return retval;
2468 }
2469
filer_perform_action(FilerWindow * filer_window,GdkEventButton * event)2470 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
2471 {
2472 BindAction action;
2473 ViewIface *view = filer_window->view;
2474 DirItem *item = NULL;
2475 gboolean press = event->type == GDK_BUTTON_PRESS;
2476 ViewIter iter;
2477 OpenFlags flags = 0;
2478
2479 if (event->button > 3)
2480 return;
2481
2482 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2483 item = iter.peek(&iter);
2484
2485 if (item && view_cursor_visible(view))
2486 view_cursor_to_iter(view, &iter);
2487
2488 if (item && event->button == 1 &&
2489 view_get_selected(view, &iter) &&
2490 filer_window->selection_state == GTK_STATE_INSENSITIVE)
2491 {
2492 /* Possibly a really slow DnD operation? */
2493 filer_window->temp_item_selected = FALSE;
2494
2495 filer_selection_changed(filer_window, event->time);
2496 return;
2497 }
2498
2499 if (filer_window->target_cb)
2500 {
2501 dnd_motion_ungrab();
2502 if (item && press && event->button == 1)
2503 filer_window->target_cb(filer_window, &iter,
2504 filer_window->target_data);
2505
2506 filer_target_mode(filer_window, NULL, NULL, NULL);
2507
2508 return;
2509 }
2510
2511 if (!o_single_click.int_value)
2512 {
2513 /* Make sure both parts of a double-click fall on
2514 * the same file.
2515 */
2516 static guchar *first_click = NULL;
2517 static guchar *second_click = NULL;
2518
2519 if (event->type == GDK_BUTTON_PRESS)
2520 {
2521 g_free(first_click);
2522 first_click = second_click;
2523
2524 if (item)
2525 second_click = g_strdup(item->leafname);
2526 else
2527 second_click = NULL;
2528 }
2529
2530 if (event->type == GDK_2BUTTON_PRESS)
2531 {
2532 if (first_click && second_click &&
2533 strcmp(first_click, second_click) != 0)
2534 return;
2535 if ((first_click || second_click) &&
2536 !(first_click && second_click))
2537 return;
2538 }
2539 }
2540
2541 action = bind_lookup_bev(
2542 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
2543 event);
2544
2545 switch (action)
2546 {
2547 case ACT_CLEAR_SELECTION:
2548 view_clear_selection(view);
2549 break;
2550 case ACT_TOGGLE_SELECTED:
2551 view_set_selected(view, &iter,
2552 !view_get_selected(view, &iter));
2553 break;
2554 case ACT_SELECT_EXCL:
2555 view_select_only(view, &iter);
2556 break;
2557 case ACT_EDIT_ITEM:
2558 flags |= OPEN_SHIFT;
2559 /* (no break) */
2560 case ACT_OPEN_ITEM:
2561 if (event->button != 1 || event->state & GDK_MOD1_MASK)
2562 flags |= OPEN_CLOSE_WINDOW;
2563 else
2564 flags |= OPEN_SAME_WINDOW;
2565 if (o_new_button_1.int_value)
2566 flags ^= OPEN_SAME_WINDOW;
2567 if (event->type == GDK_2BUTTON_PRESS)
2568 view_set_selected(view, &iter, FALSE);
2569 dnd_motion_ungrab();
2570
2571 filer_openitem(filer_window, &iter, flags);
2572 break;
2573 case ACT_POPUP_MENU:
2574 dnd_motion_ungrab();
2575 tooltip_show(NULL);
2576 show_filer_menu(filer_window,
2577 (GdkEvent *) event, &iter);
2578 break;
2579 case ACT_PRIME_AND_SELECT:
2580 if (item && !view_get_selected(view, &iter))
2581 view_select_only(view, &iter);
2582 dnd_motion_start(MOTION_READY_FOR_DND);
2583 break;
2584 case ACT_PRIME_AND_TOGGLE:
2585 view_set_selected(view, &iter,
2586 !view_get_selected(view, &iter));
2587 dnd_motion_start(MOTION_READY_FOR_DND);
2588 break;
2589 case ACT_PRIME_FOR_DND:
2590 dnd_motion_start(MOTION_READY_FOR_DND);
2591 break;
2592 case ACT_IGNORE:
2593 if (press && event->button < 4)
2594 {
2595 if (item)
2596 view_wink_item(view, &iter);
2597 dnd_motion_start(MOTION_NONE);
2598 }
2599 break;
2600 case ACT_LASSO_CLEAR:
2601 view_clear_selection(view);
2602 /* (no break) */
2603 case ACT_LASSO_MODIFY:
2604 view_start_lasso_box(view, event);
2605 break;
2606 case ACT_RESIZE:
2607 view_autosize(filer_window->view);
2608 break;
2609 default:
2610 g_warning("Unsupported action : %d\n", action);
2611 break;
2612 }
2613 }
2614
2615 /* It's time to make the tooltip appear. If we're not over the item any
2616 * more, or the item doesn't need a tooltip, do nothing.
2617 */
tooltip_activate(GtkWidget * window)2618 static gboolean tooltip_activate(GtkWidget *window)
2619 {
2620 FilerWindow *filer_window;
2621 ViewIface *view;
2622 ViewIter iter;
2623 gint x, y;
2624 DirItem *item = NULL;
2625 GString *tip = NULL;
2626
2627 g_return_val_if_fail(tip_item != NULL, 0);
2628
2629 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2630
2631 if (!motion_window || !filer_window)
2632 return FALSE; /* Window has been destroyed */
2633
2634 view = filer_window->view;
2635
2636 tooltip_show(NULL);
2637
2638 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2639 view_get_iter_at_point(view, &iter, motion_window, x, y);
2640
2641 item = iter.peek(&iter);
2642 if (item != tip_item)
2643 return FALSE; /* Not still under the pointer */
2644
2645 /* OK, the filer window still exists and the pointer is still
2646 * over the same item. Do we need to show a tip?
2647 */
2648
2649 tip = g_string_new(NULL);
2650
2651 view_extend_tip(filer_window->view, &iter, tip);
2652
2653 filer_add_tip_details(filer_window, tip, tip_item);
2654
2655 if (tip->len > 1)
2656 {
2657 g_string_truncate(tip, tip->len - 1);
2658
2659 tooltip_show(tip->str);
2660 }
2661
2662 g_string_free(tip, TRUE);
2663
2664 return FALSE;
2665 }
2666
2667 /* Motion detected on the View widget */
filer_motion_notify(FilerWindow * filer_window,GdkEventMotion * event)2668 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2669 {
2670 ViewIface *view = filer_window->view;
2671 ViewIter iter;
2672 DirItem *item;
2673
2674 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2675 item = iter.peek(&iter);
2676
2677 if (item)
2678 {
2679 if (item != tip_item)
2680 {
2681 tooltip_show(NULL);
2682
2683 tip_item = item;
2684 motion_window = event->window;
2685 tooltip_prime((GtkFunction) tooltip_activate,
2686 G_OBJECT(filer_window->window));
2687 }
2688 }
2689 else
2690 {
2691 tooltip_show(NULL);
2692 tip_item = NULL;
2693 }
2694
2695 if (motion_state != MOTION_READY_FOR_DND)
2696 return FALSE;
2697
2698 if (!dnd_motion_moved(event))
2699 return FALSE;
2700
2701 view_get_iter_at_point(view, &iter,
2702 event->window,
2703 event->x - (event->x_root - drag_start_x),
2704 event->y - (event->y_root - drag_start_y));
2705 item = iter.peek(&iter);
2706 if (!item)
2707 return FALSE;
2708
2709 view_wink_item(view, NULL);
2710
2711 if (!view_get_selected(view, &iter))
2712 {
2713 /* If we drag an unselected item, select it only.
2714 * Unless we're also holding down Ctrl, in which case
2715 * it's probably unselected only because we
2716 * mis-interpreted the click as toggle-selected.
2717 */
2718 if ((event->state & GDK_BUTTON1_MASK) &&
2719 !(event->state & GDK_CONTROL_MASK))
2720 {
2721 /* Select just this one */
2722 filer_window->temp_item_selected = TRUE;
2723 view_select_only(view, &iter);
2724 }
2725 else
2726 {
2727 if (view_count_selected(view) == 0)
2728 filer_window->temp_item_selected = TRUE;
2729 view_set_selected(view, &iter, TRUE);
2730 }
2731 }
2732
2733 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2734
2735 if (view_count_selected(view) == 1)
2736 {
2737 if (item->base_type == TYPE_UNKNOWN)
2738 item = dir_update_item(filer_window->directory,
2739 item->leafname);
2740
2741 if (!item)
2742 {
2743 report_error(_("Item no longer exists!"));
2744 return FALSE;
2745 }
2746
2747 drag_one_item(GTK_WIDGET(view), event,
2748 make_path(filer_window->sym_path, item->leafname),
2749 item, di_image(item));
2750 #if 0
2751 /* XXX: Use thumbnail */
2752 item, view ? view->image : NULL);
2753 #endif
2754 }
2755 else
2756 {
2757 guchar *uris;
2758
2759 uris = filer_create_uri_list(filer_window);
2760 drag_selection(GTK_WIDGET(view), event, uris);
2761 g_free(uris);
2762 }
2763
2764 return FALSE;
2765 }
2766
drag_end(GtkWidget * widget,GdkDragContext * context,FilerWindow * filer_window)2767 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2768 FilerWindow *filer_window)
2769 {
2770 filer_set_autoscroll(filer_window, FALSE);
2771
2772 if (filer_window->temp_item_selected)
2773 {
2774 view_clear_selection(filer_window->view);
2775 filer_window->temp_item_selected = FALSE;
2776 }
2777 }
2778
2779 /* Remove highlights */
drag_leave(GtkWidget * widget,GdkDragContext * context,guint32 time,FilerWindow * filer_window)2780 static void drag_leave(GtkWidget *widget,
2781 GdkDragContext *context,
2782 guint32 time,
2783 FilerWindow *filer_window)
2784 {
2785 dnd_spring_abort();
2786 }
2787
2788 /* Called during the drag when the mouse is in a widget registered
2789 * as a drop target. Returns TRUE if we can accept the drop.
2790 */
drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,FilerWindow * filer_window)2791 static gboolean drag_motion(GtkWidget *widget,
2792 GdkDragContext *context,
2793 gint x,
2794 gint y,
2795 guint time,
2796 FilerWindow *filer_window)
2797 {
2798 DirItem *item;
2799 ViewIface *view = filer_window->view;
2800 ViewIter iter;
2801 GdkDragAction action = context->suggested_action;
2802 const guchar *new_path = NULL;
2803 const char *type = NULL;
2804 gboolean retval = FALSE;
2805 gboolean same_window;
2806
2807 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value)
2808 {
2809 guint state;
2810 gdk_window_get_pointer(NULL, NULL, NULL, &state);
2811 if (state & GDK_BUTTON1_MASK)
2812 action = GDK_ACTION_ASK;
2813 }
2814
2815 same_window = gtk_drag_get_source_widget(context) == widget;
2816
2817 filer_set_autoscroll(filer_window, TRUE);
2818
2819 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2820 {
2821 GdkWindow *bin;
2822 int bin_y;
2823 /* Correct for position of bin window */
2824 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2825 gdk_window_get_position(bin, NULL, &bin_y);
2826 y -= bin_y;
2827 }
2828
2829 if (o_dnd_drag_to_icons.int_value)
2830 {
2831 view_get_iter_at_point(view, &iter, widget->window, x, y);
2832 item = iter.peek(&iter);
2833 }
2834 else
2835 item = NULL;
2836
2837 if (item && same_window && view_get_selected(view, &iter))
2838 type = NULL;
2839 else
2840 type = dnd_motion_item(context, &item);
2841
2842 if (!type)
2843 item = NULL;
2844
2845 /* Don't allow drops to non-writeable directories. BUT, still
2846 * allow drops on non-writeable SUBdirectories so that we can
2847 * do the spring-open thing.
2848 */
2849 if (item && type == drop_dest_dir &&
2850 !(item->flags & ITEM_FLAG_APPDIR))
2851 {
2852 dnd_spring_load(context, filer_window);
2853 }
2854 else
2855 dnd_spring_abort();
2856
2857 if (item)
2858 view_cursor_to_iter(view, &iter);
2859 else
2860 {
2861 view_cursor_to_iter(view, NULL);
2862
2863 /* Disallow background drops within a single window */
2864 if (type && same_window)
2865 type = NULL;
2866 }
2867
2868 if (type)
2869 {
2870 if (item)
2871 new_path = make_path(filer_window->sym_path,
2872 item->leafname);
2873 else
2874 new_path = filer_window->sym_path;
2875 }
2876
2877 /* Don't ask about dragging to an application! */
2878 if (type == drop_dest_prog && action == GDK_ACTION_ASK)
2879 action = GDK_ACTION_COPY;
2880
2881 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2882 if (type)
2883 {
2884 gdk_drag_status(context, action, time);
2885 g_dataset_set_data_full(context, "drop_dest_path",
2886 g_strdup(new_path), g_free);
2887 retval = TRUE;
2888 }
2889
2890 return retval;
2891 }
2892
as_timeout(FilerWindow * filer_window)2893 static gboolean as_timeout(FilerWindow *filer_window)
2894 {
2895 gboolean retval;
2896
2897 retval = view_auto_scroll_callback(filer_window->view);
2898
2899 if (!retval)
2900 filer_window->auto_scroll = -1;
2901
2902 return retval;
2903 }
2904
2905 /* When autoscroll is on, a timer keeps track of the pointer position.
2906 * While it's near the top or bottom of the window, the window scrolls.
2907 *
2908 * If the mouse buttons are released, the pointer leaves the window, or
2909 * a drag-and-drop operation finishes, auto_scroll is turned off.
2910 */
filer_set_autoscroll(FilerWindow * filer_window,gboolean auto_scroll)2911 void filer_set_autoscroll(FilerWindow *filer_window, gboolean auto_scroll)
2912 {
2913 g_return_if_fail(filer_window != NULL);
2914
2915 if (auto_scroll)
2916 {
2917 if (filer_window->auto_scroll != -1)
2918 return; /* Already on! */
2919
2920 filer_window->auto_scroll = g_timeout_add(50,
2921 (GSourceFunc) as_timeout,
2922 filer_window);
2923 }
2924 else
2925 {
2926 if (filer_window->auto_scroll == -1)
2927 return; /* Already off! */
2928
2929 g_source_remove(filer_window->auto_scroll);
2930 filer_window->auto_scroll = -1;
2931 }
2932 }
2933
2934 #define ZERO_MNT "/uri/0install"
2935
refresh_done(FilerWindow * filer_window)2936 static void refresh_done(FilerWindow *filer_window)
2937 {
2938 if (filer_exists(filer_window))
2939 filer_update_dir(filer_window, TRUE);
2940 }
2941
filer_refresh(FilerWindow * filer_window)2942 void filer_refresh(FilerWindow *filer_window)
2943 {
2944 if (!strncmp(ZERO_MNT "/", filer_window->real_path, sizeof(ZERO_MNT)))
2945 {
2946 /* Try to run 0refresh */
2947 gint pid;
2948 gchar *argv[] = {"0refresh", NULL, NULL};
2949 const char *host = filer_window->real_path + sizeof(ZERO_MNT);
2950 const char *slash;
2951
2952 slash = strchr(host, '/');
2953 if (slash)
2954 argv[1] = g_strndup(host, slash - host);
2955 else
2956 argv[1] = g_strdup(host);
2957 pid = rox_spawn(filer_window->real_path, (const char **) argv);
2958 g_free(argv[1]);
2959 if (pid)
2960 on_child_death(pid, (CallbackFn) refresh_done,
2961 filer_window);
2962 }
2963
2964 full_refresh();
2965 }
2966
is_hidden(const char * dir,DirItem * item)2967 static inline gboolean is_hidden(const char *dir, DirItem *item)
2968 {
2969 /* If the leaf name starts with '.' then the item is hidden */
2970 if(item->leafname[0]=='.')
2971 return TRUE;
2972
2973 /*** Test disabled for now. The flags aren't set on the first pass...
2974 */
2975 #if 0
2976 /* Most files will not have extended attributes, so this should
2977 * be quick. */
2978 if(!o_xattr_ignore.int_value && (item->flags & ITEM_FLAG_HAS_XATTR)) {
2979 gchar *path, *val;
2980 int len;
2981 gboolean hidden=FALSE;
2982
2983 path=g_build_filename(dir, item->leafname, NULL);
2984 val=xattr_get(path, XATTR_HIDDEN, &len);
2985 if(val) {
2986 hidden=atoi(val) || (strcmp(val, "true")==0);
2987 g_free(val);
2988 }
2989 g_free(path);
2990
2991 if(hidden)
2992 return TRUE;
2993 }
2994 #endif
2995
2996 /* Otherwise not hidden */
2997 return FALSE;
2998 }
2999
filer_match_filter(FilerWindow * filer_window,DirItem * item)3000 gboolean filer_match_filter(FilerWindow *filer_window, DirItem *item)
3001 {
3002 g_return_val_if_fail(item != NULL, FALSE);
3003
3004 if(is_hidden(filer_window->real_path, item) &&
3005 (!filer_window->temp_show_hidden && !filer_window->show_hidden))
3006 return FALSE;
3007
3008 switch(filer_window->filter) {
3009 case FILER_SHOW_GLOB:
3010 return fnmatch(filer_window->filter_string,
3011 item->leafname, 0)==0 ||
3012 (item->base_type==TYPE_DIRECTORY &&
3013 !filer_window->filter_directories);
3014
3015 case FILER_SHOW_ALL:
3016 default:
3017 break;
3018 }
3019 return TRUE;
3020 }
3021
3022 /* Provided to hide the implementation */
filer_set_hidden(FilerWindow * filer_window,gboolean hidden)3023 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
3024 {
3025 filer_window->show_hidden=hidden;
3026 }
3027
3028 /* Provided to hide the implementation */
filer_set_filter_directories(FilerWindow * filer_window,gboolean filter_directories)3029 void filer_set_filter_directories(FilerWindow *filer_window,
3030 gboolean filter_directories)
3031 {
3032 filer_window->filter_directories=filter_directories;
3033 }
3034
3035 /* Set the filter type. Returns TRUE if the type has changed
3036 * (must call filer_detach_rescan).
3037 */
filer_set_filter(FilerWindow * filer_window,FilterType type,const gchar * filter_string)3038 gboolean filer_set_filter(FilerWindow *filer_window, FilterType type,
3039 const gchar *filter_string)
3040 {
3041 /* Is this new filter the same as the old one? */
3042 if (filer_window->filter == type)
3043 {
3044 switch(filer_window->filter)
3045 {
3046 case FILER_SHOW_ALL:
3047 return FALSE;
3048 case FILER_SHOW_GLOB:
3049 if (strcmp(filer_window->filter_string,
3050 filter_string) == 0)
3051 return FALSE;
3052 break;
3053 }
3054 }
3055
3056 /* Clean up old filter */
3057 if (filer_window->filter_string)
3058 {
3059 g_free(filer_window->filter_string);
3060 filer_window->filter_string = NULL;
3061 }
3062 /* Also clean up compiled regexp when implemented */
3063
3064 filer_window->filter = type;
3065
3066 switch(type)
3067 {
3068 case FILER_SHOW_ALL:
3069 /* No extra work */
3070 break;
3071
3072 case FILER_SHOW_GLOB:
3073 filer_window->filter_string = g_strdup(filter_string);
3074 break;
3075
3076 default:
3077 /* oops */
3078 filer_window->filter = FILER_SHOW_ALL;
3079 g_warning("Impossible: filter type %d", type);
3080 break;
3081 }
3082
3083 return TRUE;
3084 }
3085
3086 /* Setting stuff */
settings_new(const char * path)3087 static Settings *settings_new(const char *path)
3088 {
3089 Settings *set;
3090
3091 set=g_new(Settings, 1);
3092 memset(set, 0, sizeof(Settings));
3093 if(path)
3094 set->path=g_strdup(path);
3095
3096 return set;
3097 }
3098
settings_free(Settings * set)3099 static void settings_free(Settings *set)
3100 {
3101 g_free(set->path);
3102 if(set->filter)
3103 g_free(set->filter);
3104 g_free(set);
3105 }
3106
store_settings(Settings * set)3107 static void store_settings(Settings *set)
3108 {
3109 Settings *old;
3110
3111 old=g_hash_table_lookup(settings_table, set->path);
3112 if(old)
3113 {
3114 g_hash_table_remove(settings_table, set->path);
3115 settings_free(old);
3116 }
3117
3118 g_hash_table_insert(settings_table, set->path, set);
3119 }
3120
3121 /* TODO: use symbolic names in the XML file where possible */
load_from_node(Settings * set,xmlDocPtr doc,xmlNodePtr node)3122 static void load_from_node(Settings *set, xmlDocPtr doc, xmlNodePtr node)
3123 {
3124 xmlChar *str=NULL;
3125
3126 str=xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
3127
3128 if(strcmp(node->name, "X") == 0) {
3129 set->x=atoi(str);
3130 set->flags|=SET_POSITION;
3131 } else if(strcmp(node->name, "Y") == 0) {
3132 set->y=atoi(str);
3133 set->flags|=SET_POSITION;
3134 } else if(strcmp(node->name, "Width") == 0) {
3135 set->width=atoi(str);
3136 set->flags|=SET_SIZE;
3137 } else if(strcmp(node->name, "Height") == 0) {
3138 set->height=atoi(str);
3139 set->flags|=SET_SIZE;
3140 } else if(strcmp(node->name, "ShowHidden") == 0) {
3141 set->show_hidden=atoi(str);
3142 set->flags|=SET_HIDDEN;
3143 } else if(strcmp(node->name, "ViewType") == 0) {
3144 set->view_type=atoi(str);
3145 set->flags|=SET_DETAILS;
3146 } else if(strcmp(node->name, "DetailsType") == 0) {
3147 set->details_type=atoi(str);
3148 set->flags|=SET_DETAILS;
3149 } else if(strcmp(node->name, "SortType") == 0) {
3150 set->sort_type=atoi(str);
3151 set->flags|=SET_SORT;
3152 } else if(strcmp(node->name, "SortOrder") == 0) {
3153 set->sort_order=atoi(str);
3154 set->flags|=SET_SORT;
3155 } else if(strcmp(node->name, "DisplayStyle") == 0) {
3156 set->display_style=atoi(str);
3157 set->flags|=SET_STYLE;
3158 } else if(strcmp(node->name, "ShowThumbs") == 0) {
3159 set->show_thumbs=atoi(str);
3160 set->flags|=SET_THUMBS;
3161 } else if(strcmp(node->name, "FilterType") == 0) {
3162 set->filter_type=atoi(str);
3163 set->flags|=SET_FILTER;
3164 } else if(strcmp(node->name, "Filter") == 0) {
3165 set->filter=g_strdup(str);
3166 set->flags|=SET_FILTER;
3167 } else if(strcmp(node->name, "FilterDirectories") == 0) {
3168 set->filter_directories=atoi(str);
3169 set->flags|=SET_FILTER;
3170 }
3171
3172 if(str)
3173 xmlFree(str);
3174 }
3175
load_learnt_mounts(void)3176 static void load_learnt_mounts(void)
3177 {
3178 gchar *path;
3179 gchar *buffer = NULL;
3180 gsize len = 0;
3181 gchar **entries;
3182 int n;
3183
3184 unmount_prompt_actions = g_hash_table_new_full(g_str_hash,
3185 g_str_equal, g_free, NULL);
3186
3187 path = choices_find_xdg_path_load("Mounts", PROJECT, SITE);
3188 if (!path)
3189 return;
3190 if (!g_file_get_contents(path, &buffer, &len, NULL))
3191 {
3192 g_free(path);
3193 return;
3194 }
3195 g_free(path);
3196 if (len)
3197 {
3198 buffer[len - 1] = 0;
3199 }
3200 else
3201 {
3202 g_free(buffer);
3203 return;
3204 }
3205
3206 entries = g_strsplit(buffer, "\n", 0);
3207 g_free(buffer);
3208 for (n = 0; entries[n]; ++n)
3209 {
3210 gchar *p;
3211 buffer = entries[n];
3212 p = strrchr(buffer, ' ');
3213 if (p && p > buffer) {
3214 *p = 0;
3215 g_hash_table_insert(unmount_prompt_actions, g_strdup(buffer),
3216 GINT_TO_POINTER(atoi(p+1)));
3217 }
3218 }
3219 g_strfreev(entries);
3220 }
3221
save_mount(char * path,gpointer value,FILE ** pfp)3222 static void save_mount(char *path, gpointer value, FILE **pfp)
3223 {
3224 int v;
3225
3226 if (!*pfp)
3227 {
3228 gchar *spath = choices_find_xdg_path_save("Mounts",
3229 PROJECT, SITE, TRUE);
3230
3231 if (!spath)
3232 return;
3233 *pfp = fopen(spath, "w");
3234 g_free(spath);
3235 if (!*pfp)
3236 return;
3237 }
3238
3239 if ((v = GPOINTER_TO_INT(value)) != UNMOUNT_PROMPT_ASK)
3240 fprintf(*pfp, "%s %d\n", path, v);
3241 }
3242
save_learnt_mounts(void)3243 static void save_learnt_mounts(void)
3244 {
3245 FILE *fp = NULL;
3246
3247 /* A GHashTableIter would be easier, but it's a relatively new feature */
3248 if (unmount_prompt_actions)
3249 g_hash_table_foreach(unmount_prompt_actions, (GHFunc) save_mount, &fp);
3250 if (fp)
3251 fclose(fp);
3252 }
3253
load_settings(void)3254 static void load_settings(void)
3255 {
3256 gchar *path;
3257 XMLwrapper *settings_doc=NULL;
3258
3259 path=choices_find_xdg_path_load("Settings.xml", PROJECT, SITE);
3260 if(path) {
3261 settings_doc=xml_new(path);
3262 g_free(path);
3263 }
3264
3265 if(!settings_table)
3266 settings_table=g_hash_table_new(g_str_hash, g_str_equal);
3267
3268 if(settings_doc) {
3269 xmlNodePtr node, subnode;
3270
3271 node = xmlDocGetRootElement(settings_doc->doc);
3272
3273 for (node = node->xmlChildrenNode; node; node = node->next)
3274 {
3275 Settings *set;
3276 xmlChar *path;
3277
3278 if (node->type != XML_ELEMENT_NODE)
3279 continue;
3280 if (strcmp(node->name, "FilerWindow") != 0)
3281 continue;
3282
3283 path=xmlGetProp(node, "path");
3284 set=settings_new(path);
3285 xmlFree(path);
3286
3287 for (subnode=node->xmlChildrenNode; subnode;
3288 subnode=subnode->next) {
3289
3290 if (subnode->type != XML_ELEMENT_NODE)
3291 continue;
3292 load_from_node(set, settings_doc->doc,
3293 subnode);
3294 }
3295
3296 store_settings(set);
3297 }
3298 g_object_unref(settings_doc);
3299 }
3300 }
3301
add_nodes(gpointer key,gpointer value,gpointer data)3302 static void add_nodes(gpointer key, gpointer value, gpointer data)
3303 {
3304 xmlNodePtr node=(xmlNodePtr) data;
3305 xmlNodePtr sub;
3306 Settings *set=(Settings *) value;
3307 char *tmp;
3308
3309 sub=xmlNewChild(node, NULL, "FilerWindow", NULL);
3310
3311 xmlSetProp(sub, "path", set->path);
3312
3313 if(set->flags & SET_POSITION) {
3314 tmp=g_strdup_printf("%d", set->x);
3315 xmlNewChild(sub, NULL, "X", tmp);
3316 g_free(tmp);
3317 tmp=g_strdup_printf("%d", set->y);
3318 xmlNewChild(sub, NULL, "Y", tmp);
3319 g_free(tmp);
3320 }
3321 if(set->flags & SET_SIZE) {
3322 tmp=g_strdup_printf("%d", set->width);
3323 xmlNewChild(sub, NULL, "Width", tmp);
3324 g_free(tmp);
3325 tmp=g_strdup_printf("%d", set->height);
3326 xmlNewChild(sub, NULL, "Height", tmp);
3327 g_free(tmp);
3328 }
3329 if(set->flags & SET_HIDDEN) {
3330 tmp=g_strdup_printf("%d", set->show_hidden);
3331 xmlNewChild(sub, NULL, "ShowHidden", tmp);
3332 g_free(tmp);
3333 }
3334 if(set->flags & SET_STYLE) {
3335 tmp=g_strdup_printf("%d", set->display_style);
3336 xmlNewChild(sub, NULL, "DisplayStyle", tmp);
3337 g_free(tmp);
3338 }
3339 if(set->flags & SET_SORT) {
3340 tmp=g_strdup_printf("%d", set->sort_type);
3341 xmlNewChild(sub, NULL, "SortType", tmp);
3342 g_free(tmp);
3343 tmp=g_strdup_printf("%d", set->sort_order);
3344 xmlNewChild(sub, NULL, "SortOrder", tmp);
3345 g_free(tmp);
3346 }
3347 if(set->flags & SET_DETAILS) {
3348 tmp=g_strdup_printf("%d", set->view_type);
3349 xmlNewChild(sub, NULL, "ViewType", tmp);
3350 g_free(tmp);
3351 tmp=g_strdup_printf("%d", set->details_type);
3352 xmlNewChild(sub, NULL, "DetailsType", tmp);
3353 g_free(tmp);
3354 }
3355 if(set->flags & SET_STYLE) {
3356 tmp=g_strdup_printf("%d", set->show_thumbs);
3357 xmlNewChild(sub, NULL, "ShowThumbs", tmp);
3358 g_free(tmp);
3359 }
3360 if(set->flags & SET_FILTER) {
3361 tmp=g_strdup_printf("%d", set->filter_type);
3362 xmlNewChild(sub, NULL, "FilterType", tmp);
3363 g_free(tmp);
3364 if(set->filter && set->filter[0])
3365 xmlNewChild(sub, NULL, "Filter", set->filter);
3366 tmp=g_strdup_printf("%d", set->filter_directories);
3367 xmlNewChild(sub, NULL, "FilterDirectories", tmp);
3368 }
3369 }
3370
save_settings(void)3371 static void save_settings(void)
3372 {
3373 gchar *path;
3374
3375 path=choices_find_xdg_path_save("Settings.xml", PROJECT, SITE, TRUE);
3376 if(path) {
3377 xmlDocPtr doc = xmlNewDoc("1.0");
3378 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL,
3379 "Settings", NULL));
3380
3381 g_hash_table_foreach(settings_table, add_nodes,
3382 xmlDocGetRootElement(doc));
3383
3384 save_xml_file(doc, path);
3385
3386 g_free(path);
3387 xmlFreeDoc(doc);
3388 }
3389
3390 }
3391
check_settings(FilerWindow * filer_window)3392 static void check_settings(FilerWindow *filer_window)
3393 {
3394 Settings *set;
3395
3396 set=(Settings *) g_hash_table_lookup(settings_table,
3397 filer_window->sym_path);
3398
3399 if(set) {
3400 if(set->flags & SET_POSITION)
3401 gtk_window_move(GTK_WINDOW(filer_window->window),
3402 set->x, set->y);
3403 if(set->flags & SET_SIZE)
3404 filer_window_set_size(filer_window, set->width,
3405 set->height);
3406 if(set->flags & SET_HIDDEN)
3407 filer_set_hidden(filer_window, set->show_hidden);
3408
3409 if(set->flags & (SET_STYLE|SET_DETAILS)) {
3410 DisplayStyle style=filer_window->display_style;
3411 DetailsType details=filer_window->details_type;
3412
3413 if(set->flags & SET_STYLE)
3414 style=set->display_style;
3415
3416 if(set->flags & SET_DETAILS) {
3417 details=set->details_type;
3418
3419 filer_set_view_type(filer_window,
3420 set->view_type);
3421 }
3422
3423 display_set_layout(filer_window, style,
3424 details, FALSE);
3425 }
3426
3427 if(set->flags & SET_SORT)
3428 display_set_sort_type(filer_window,
3429 set->sort_type,
3430 set->sort_order);
3431
3432 if(set->flags & SET_THUMBS)
3433 display_set_thumbs(filer_window,
3434 set->show_thumbs);
3435
3436 if(set->flags & SET_FILTER)
3437 {
3438 display_set_filter(filer_window,
3439 set->filter_type,
3440 set->filter);
3441 display_set_filter_directories(filer_window,
3442 set->filter_directories);
3443 }
3444 }
3445
3446 }
3447
3448 typedef struct settings_window {
3449 GtkWidget *window;
3450
3451 GtkWidget *pos, *size, *hidden, *style, *sort, *details,
3452 *thumbs, *filter;
3453
3454 Settings *set;
3455 } SettingsWindow;
3456
3457
settings_response(GtkWidget * window,gint response,SettingsWindow * set_win)3458 static void settings_response(GtkWidget *window, gint response,
3459 SettingsWindow *set_win)
3460 {
3461 if(response==GTK_RESPONSE_OK) {
3462 gint flags=0;
3463
3464 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->pos)))
3465 flags|=SET_POSITION;
3466 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->size)))
3467 flags|=SET_SIZE;
3468 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->hidden)))
3469 flags|=SET_HIDDEN;
3470 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->style)))
3471 flags|=SET_STYLE;
3472 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->sort)))
3473 flags|=SET_SORT;
3474 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->details)))
3475 flags|=SET_DETAILS;
3476 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->thumbs)))
3477 flags|=SET_THUMBS;
3478 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->filter)))
3479 flags|=SET_FILTER;
3480
3481 set_win->set->flags=flags;
3482 store_settings(set_win->set);
3483 save_settings();
3484 }
3485
3486 gtk_widget_destroy(window);
3487 }
3488
filer_save_settings(FilerWindow * fwin)3489 void filer_save_settings(FilerWindow *fwin)
3490 {
3491 SettingsWindow *set_win;
3492 GtkWidget *vbox, *frame;
3493 GtkWidget *path, *lbl;
3494 gint x, y;
3495
3496 Settings *set=settings_new(fwin->sym_path);
3497
3498 gtk_window_get_position(GTK_WINDOW(fwin->window),&x, &y);
3499 set->flags|=SET_POSITION;
3500 set->x=x;
3501 set->y=y;
3502
3503 gtk_window_get_size(GTK_WINDOW(fwin->window),&x, &y);
3504 set->flags|=SET_SIZE;
3505 set->width=x;
3506 set->height=y;
3507
3508 set->flags|=SET_HIDDEN;
3509 set->show_hidden=fwin->show_hidden;
3510
3511 set->flags|=SET_STYLE;
3512 set->display_style=fwin->display_style;
3513
3514 set->flags|=SET_SORT;
3515 set->sort_type=fwin->sort_type;
3516 set->sort_order=fwin->sort_order;
3517
3518 set->flags|=SET_DETAILS;
3519 set->view_type=fwin->view_type;
3520 set->details_type=fwin->details_type;
3521
3522 set->flags|=SET_THUMBS;
3523 set->show_thumbs=fwin->show_thumbs;
3524
3525 set->flags|=SET_FILTER;
3526 set->filter_type=fwin->filter;
3527 if(fwin->filter_string)
3528 set->filter=g_strdup(fwin->filter_string);
3529 set->filter_directories=fwin->filter_directories;
3530
3531 /* Store other parameters
3532 */
3533 set_win=g_new(SettingsWindow, 1);
3534
3535 set_win->window=gtk_dialog_new();
3536 number_of_windows++;
3537
3538 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3539 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
3540 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3541 GTK_STOCK_OK, GTK_RESPONSE_OK);
3542
3543 g_signal_connect(set_win->window, "destroy",
3544 G_CALLBACK(one_less_window), NULL);
3545 g_signal_connect_swapped(set_win->window, "destroy",
3546 G_CALLBACK(g_free), set_win);
3547
3548 gtk_window_set_title(GTK_WINDOW(set_win->window),
3549 _("Select display properties to save"));
3550
3551 vbox=GTK_DIALOG(set_win->window)->vbox;
3552
3553 lbl=gtk_label_new(_("<b>Save display settings for directory</b>"));
3554 gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);
3555 gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 2);
3556
3557 path=gtk_label_new(set->path);
3558 gtk_box_pack_start(GTK_BOX(vbox), path, FALSE, FALSE, 2);
3559
3560 frame=gtk_frame_new(_("Select settings to save"));
3561 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 2);
3562
3563 /*Make new vbox to go in the frame */
3564 vbox=gtk_vbox_new(FALSE, 2);
3565 gtk_container_add(GTK_CONTAINER(frame), vbox);
3566
3567 set_win->pos=gtk_check_button_new_with_label(_("Position"));
3568 gtk_box_pack_start(GTK_BOX(vbox), set_win->pos, FALSE, FALSE, 2);
3569 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->pos),
3570 set->flags & SET_POSITION);
3571
3572 set_win->size=gtk_check_button_new_with_label(_("Size"));
3573 gtk_box_pack_start(GTK_BOX(vbox), set_win->size, FALSE, FALSE, 2);
3574 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->size),
3575 set->flags & SET_SIZE);
3576
3577 set_win->hidden=gtk_check_button_new_with_label(_("Show hidden"));
3578 gtk_box_pack_start(GTK_BOX(vbox), set_win->hidden,
3579 FALSE, FALSE, 2);
3580 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->hidden),
3581 set->flags & SET_HIDDEN);
3582
3583 set_win->style=gtk_check_button_new_with_label(_("Display style"));
3584 gtk_box_pack_start(GTK_BOX(vbox), set_win->style,
3585 FALSE, FALSE, 2);
3586 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->style),
3587 set->flags & SET_STYLE);
3588
3589 set_win->sort=gtk_check_button_new_with_label(_("Sort type and order"));
3590 gtk_box_pack_start(GTK_BOX(vbox), set_win->sort, FALSE, FALSE, 2);
3591 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->sort),
3592 set->flags & SET_SORT);
3593
3594 set_win->details=gtk_check_button_new_with_label(_("Details"));
3595 gtk_box_pack_start(GTK_BOX(vbox), set_win->details, FALSE, FALSE, 2);
3596 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->details),
3597 set->flags & SET_DETAILS);
3598
3599 set_win->thumbs=gtk_check_button_new_with_label(_("Thumbnails"));
3600 gtk_box_pack_start(GTK_BOX(vbox), set_win->thumbs,
3601 FALSE, FALSE, 2);
3602 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->thumbs),
3603 set->flags & SET_THUMBS);
3604
3605 set_win->filter=gtk_check_button_new_with_label(_("Filter"));
3606 gtk_box_pack_start(GTK_BOX(vbox), set_win->filter,
3607 FALSE, FALSE, 2);
3608 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->filter),
3609 set->flags & SET_FILTER);
3610
3611 set_win->set=set;
3612 g_signal_connect(set_win->window, "response",
3613 G_CALLBACK(settings_response), set_win);
3614
3615 gtk_widget_show_all(set_win->window);
3616 }
3617
tip_from_desktop_file(const char * full_path)3618 static char *tip_from_desktop_file(const char *full_path)
3619 {
3620 GError *error = NULL;
3621 char *comment = NULL;
3622
3623 comment = get_value_from_desktop_file(full_path,
3624 "Desktop Entry", "Comment", &error);
3625 if (error)
3626 {
3627 delayed_error("Failed to parse .desktop file '%s':\n%s",
3628 full_path, error->message);
3629 goto err;
3630 }
3631
3632 err:
3633 if (error != NULL)
3634 g_error_free(error);
3635
3636 return comment;
3637 }
3638
filer_get_unmount_action(const char * path)3639 UnmountPrompt filer_get_unmount_action(const char *path)
3640 {
3641 return GPOINTER_TO_INT(g_hash_table_lookup(unmount_prompt_actions, path));
3642 }
3643
filer_set_unmount_action(const char * path,UnmountPrompt action)3644 void filer_set_unmount_action(const char *path, UnmountPrompt action)
3645 {
3646 g_hash_table_insert(unmount_prompt_actions, g_strdup(path),
3647 GINT_TO_POINTER(action));
3648 save_learnt_mounts();
3649 }
3650