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