r4716: Fixes for the filter directories option: Initialize properly, inherit from
[rox-filer/dt.git] / ROX-Filer / src / filer.c
blobf41a2cd4c5df98b3c9817fdfa999a9381e21c55d
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
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.
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.
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
20 /* filer.c - code for handling filer windows */
22 #include "config.h"
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>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkx.h>
35 #include <gdk/gdkkeysyms.h>
37 #include "global.h"
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"
68 static XMLwrapper *groups = NULL;
70 /* Item we are about to display a tooltip for */
71 static DirItem *tip_item = NULL;
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.
76 static GdkWindow *motion_window = NULL;
78 /* This is rather badly named. It's actually the filer window which received
79 * the last key press or Menu click event.
81 FilerWindow *window_with_focus = NULL;
83 GList *all_filer_windows = NULL;
85 static GHashTable *window_with_id = NULL;
87 static FilerWindow *window_with_primary = NULL;
89 static GHashTable *settings_table=NULL;
91 typedef struct {
92 gchar *path;
94 guint flags; /* Which parts are valid, see below */
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;
104 DetailsType details_type;
105 DisplayStyle display_style;
107 FilterType filter_type;
108 char *filter;
109 gboolean filter_directories;
110 } Settings;
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 */
123 /* Static prototypes */
124 static void attach(FilerWindow *filer_window);
125 static void detach(FilerWindow *filer_window);
126 static void filer_window_destroyed(GtkWidget *widget,
127 FilerWindow *filer_window);
128 static void update_display(Directory *dir,
129 DirAction action,
130 GPtrArray *items,
131 FilerWindow *filer_window);
132 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
133 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
134 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
135 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
136 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class);
137 static void filer_add_signals(FilerWindow *filer_window);
139 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
140 static void filer_next_thumb(GObject *window, const gchar *path);
141 static void start_thumb_scanning(FilerWindow *filer_window);
142 static void filer_options_changed(void);
143 static void drag_end(GtkWidget *widget, GdkDragContext *context,
144 FilerWindow *filer_window);
145 static void drag_leave(GtkWidget *widget,
146 GdkDragContext *context,
147 guint32 time,
148 FilerWindow *filer_window);
149 static gboolean drag_motion(GtkWidget *widget,
150 GdkDragContext *context,
151 gint x,
152 gint y,
153 guint time,
154 FilerWindow *filer_window);
156 static void load_settings(void);
157 static void save_settings(void);
158 static void check_settings(FilerWindow *filer_window);
159 static char *tip_from_desktop_file(const char *full_path);
161 static GdkCursor *busy_cursor = NULL;
162 static GdkCursor *crosshair = NULL;
164 /* Indicates whether the filer's display is different to the machine it
165 * is actually running on.
167 static gboolean not_local = FALSE;
169 static Option o_short_flag_names;
170 static Option o_filer_view_type;
171 Option o_filer_auto_resize, o_unique_filer_windows;
172 Option o_filer_size_limit;
174 void filer_init(void)
176 const gchar *ohost;
177 const gchar *dpy;
178 gchar *dpyhost, *tmp;
180 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
181 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
182 RESIZE_ALWAYS);
183 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
185 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
187 option_add_int(&o_filer_view_type, "filer_view_type",
188 VIEW_TYPE_COLLECTION);
190 option_add_notify(filer_options_changed);
192 busy_cursor = gdk_cursor_new(GDK_WATCH);
193 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
195 window_with_id = g_hash_table_new_full(g_str_hash, g_str_equal,
196 NULL, NULL);
198 /* Is the display on the local machine, or are we being
199 * run remotely? See filer_set_title().
201 ohost = our_host_name();
202 dpy = gdk_get_display();
203 dpyhost = g_strdup(dpy);
204 tmp = strchr(dpyhost, ':');
205 if (tmp)
206 *tmp = '\0';
208 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
210 /* Try the cannonical name for dpyhost (see our_host_name()
211 * in support.c).
213 struct hostent *ent;
215 ent = gethostbyname(dpyhost);
216 if (!ent || strcmp(ohost, ent->h_name) != 0)
217 not_local = TRUE;
220 g_free(dpyhost);
222 load_settings();
225 static gboolean if_deleted(gpointer item, gpointer removed)
227 int i = ((GPtrArray *) removed)->len;
228 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
229 char *leafname = ((DirItem *) item)->leafname;
231 while (i--)
233 if (strcmp(leafname, r[i]->leafname) == 0)
234 return TRUE;
237 return FALSE;
240 #define DECOR_BORDER 32
242 /* Resize the filer window to w x h pixels, plus border (not clamped).
243 * If triggered by a key event, warp the pointer (for SloppyFocus users).
245 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
247 GtkWidget *window;
249 g_return_if_fail(filer_window != NULL);
251 if (filer_window->scrollbar)
252 w += filer_window->scrollbar->allocation.width;
254 if (o_toolbar.int_value != TOOLBAR_NONE)
255 h += filer_window->toolbar->allocation.height;
256 if (filer_window->message)
257 h += filer_window->message->allocation.height;
259 window = filer_window->window;
261 if (GTK_WIDGET_VISIBLE(window))
263 gint x, y, m;
264 GtkRequisition *req = &window->requisition;
265 GdkWindow *gdk_window = window->window;
266 GdkEvent *event;
268 w = MAX(req->width, w);
269 h = MAX(req->height, h);
270 gdk_window_get_pointer(NULL, &x, &y, NULL);
271 m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(),
272 x, y);
273 gdk_window_get_position(gdk_window, &x, &y);
275 if (x + w + DECOR_BORDER >
276 monitor_geom[m].x + monitor_geom[m].width ||
277 y + h + DECOR_BORDER >
278 monitor_geom[m].y + monitor_geom[m].height)
280 if (x + w + DECOR_BORDER >
281 monitor_geom[m].x + monitor_geom[m].width)
283 x = monitor_geom[m].x + monitor_geom[m].width -
284 w - 4 - DECOR_BORDER;
286 if (y + h + DECOR_BORDER >
287 monitor_geom[m].y + monitor_geom[m].height)
289 y = monitor_geom[m].y + monitor_geom[m].height -
290 h - 4 - DECOR_BORDER;
292 gdk_window_move_resize(gdk_window, x, y, w, h);
294 else
295 gdk_window_resize(gdk_window, w, h);
297 /* If the resize was triggered by a key press, keep
298 * the pointer inside the window so that it doesn't
299 * lose focus when using pointer-follows-mouse.
301 event = gtk_get_current_event();
302 if (event && event->type == GDK_KEY_PRESS)
304 int x, y;
305 int nx, ny;
307 GdkWindow *win = filer_window->window->window;
309 gdk_window_get_pointer(filer_window->window->window,
310 &x, &y, NULL);
312 nx = CLAMP(x, 4, w - 4);
313 ny = CLAMP(y, 4, h - 4);
315 if (nx != x || ny != y)
317 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
318 None,
319 gdk_x11_drawable_get_xid(win),
320 0, 0, 0, 0,
321 nx, ny);
324 if (event)
325 gdk_event_free(event);
327 else
328 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
331 /* Called on a timeout while scanning or when scanning ends
332 * (whichever happens first).
334 static gint open_filer_window(FilerWindow *filer_window)
336 Settings *dir_settings;
337 gboolean force_resize;
339 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
340 filer_window->sym_path);
342 force_resize = !(o_filer_auto_resize.int_value == RESIZE_NEVER &&
343 dir_settings && dir_settings->flags & SET_POSITION);
345 view_style_changed(filer_window->view, 0);
347 if (filer_window->open_timeout)
349 g_source_remove(filer_window->open_timeout);
350 filer_window->open_timeout = 0;
353 if (!GTK_WIDGET_VISIBLE(filer_window->window))
355 display_set_actual_size(filer_window, force_resize);
356 gtk_widget_show(filer_window->window);
359 return FALSE;
362 /* Look through all items we want to display, and queue a recheck on any
363 * that require it.
365 static void queue_interesting(FilerWindow *filer_window)
367 DirItem *item;
368 ViewIter iter;
370 view_get_iter(filer_window->view, &iter, 0);
371 while ((item = iter.next(&iter)))
373 if (item->flags & ITEM_FLAG_NEED_RESCAN_QUEUE)
374 dir_queue_recheck(filer_window->directory, item);
378 static void update_display(Directory *dir,
379 DirAction action,
380 GPtrArray *items,
381 FilerWindow *filer_window)
383 ViewIface *view = (ViewIface *) filer_window->view;
385 switch (action)
387 case DIR_ADD:
388 view_add_items(view, items);
389 /* Open and resize if currently hidden */
390 open_filer_window(filer_window);
391 break;
392 case DIR_REMOVE:
393 view_delete_if(view, if_deleted, items);
394 toolbar_update_info(filer_window);
395 break;
396 case DIR_START_SCAN:
397 set_scanning_display(filer_window, TRUE);
398 toolbar_update_info(filer_window);
399 break;
400 case DIR_END_SCAN:
401 if (filer_window->window->window)
402 gdk_window_set_cursor(
403 filer_window->window->window,
404 NULL);
405 set_scanning_display(filer_window, FALSE);
406 toolbar_update_info(filer_window);
407 open_filer_window(filer_window);
409 if (filer_window->had_cursor &&
410 !view_cursor_visible(view))
412 ViewIter start;
413 view_get_iter(view, &start, 0);
414 if (start.next(&start))
415 view_cursor_to_iter(view, &start);
416 view_show_cursor(view);
417 filer_window->had_cursor = FALSE;
419 if (filer_window->auto_select)
420 display_set_autoselect(filer_window,
421 filer_window->auto_select);
422 null_g_free(&filer_window->auto_select);
424 filer_create_thumbs(filer_window);
426 if (filer_window->thumb_queue)
427 start_thumb_scanning(filer_window);
428 break;
429 case DIR_UPDATE:
430 view_update_items(view, items);
431 break;
432 case DIR_ERROR_CHANGED:
433 filer_set_title(filer_window);
434 break;
435 case DIR_QUEUE_INTERESTING:
436 queue_interesting(filer_window);
437 break;
441 static void attach(FilerWindow *filer_window)
443 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
444 view_clear(filer_window->view);
445 filer_window->scanning = TRUE;
446 dir_attach(filer_window->directory, (DirCallback) update_display,
447 filer_window);
448 filer_set_title(filer_window);
449 bookmarks_add_history(filer_window->sym_path);
451 if (filer_window->directory->error)
453 if (spring_in_progress)
454 g_printerr(_("Error scanning '%s':\n%s\n"),
455 filer_window->sym_path,
456 filer_window->directory->error);
457 else
458 delayed_error(_("Error scanning '%s':\n%s"),
459 filer_window->sym_path,
460 filer_window->directory->error);
464 static void detach(FilerWindow *filer_window)
466 g_return_if_fail(filer_window->directory != NULL);
468 dir_detach(filer_window->directory,
469 (DirCallback) update_display, filer_window);
470 g_object_unref(filer_window->directory);
471 filer_window->directory = NULL;
474 /* If 'start' was mounted by ROX-Filer, return it. Otherwise, try the
475 * parents up the tree.
476 * NULL if we're not in a user mount point.
477 * g_free() the result.
479 static char *get_ancestor_user_mount_point(const char *start)
481 char *path;
483 path = strdup(start);
485 while (1)
487 char *slash;
489 if (mount_is_user_mounted(path))
490 return path;
492 if (!path[1])
494 g_free(path);
495 return NULL;
498 slash = strrchr(path + 1, '/');
499 if (!slash)
500 slash = path + 1;
501 *slash = '\0';
505 static void umount_dialog_response(GtkWidget *dialog, int response, char *mount)
507 if (response == GTK_RESPONSE_OK)
509 GList *list;
511 list = g_list_prepend(NULL, mount);
512 action_mount(list, FALSE, FALSE, TRUE);
513 g_list_free(list);
516 g_free(mount);
518 gtk_widget_destroy(dialog);
520 one_less_window();
523 /* 'filer_window' shows a directory under 'mount'. If no other window also
524 * shows a directory under it, display a non-modal dialog offering to
525 * unmount the directory.
526 * 'mount' is freed by this function, either directly, or after the dialog
527 * closes.
529 static void may_offer_unmount(FilerWindow *filer_window, char *mount)
531 GtkWidget *dialog, *button;
532 GList *next;
533 int len;
535 len = strlen(mount);
537 for (next = all_filer_windows; next; next = next->next)
539 FilerWindow *other = (FilerWindow *) next->data;
541 if (other == filer_window)
542 continue;
544 if (strncmp(filer_window->real_path, other->real_path,
545 len) != 0)
546 continue;
548 g_return_if_fail(
549 filer_window->real_path[len] != '/' ||
550 filer_window->real_path[len] != '\0');
552 if (other->real_path[len] != '/' &&
553 other->real_path[len] != '\0')
554 continue;
556 /* Found another window. Don't offer to unmount. */
557 g_free(mount);
558 return;
561 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
562 GTK_BUTTONS_NONE,
563 _("Do you want to unmount this device?\n\n"
564 "Unmounting a device makes it safe to remove "
565 "the disk."), mount);
567 button = button_new_mixed(ROX_STOCK_MOUNTED, _("No change"));
568 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
569 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
570 GTK_RESPONSE_CANCEL);
571 gtk_widget_show(button);
573 button = button_new_mixed(ROX_STOCK_MOUNT, _("Unmount"));
574 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
575 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
576 GTK_RESPONSE_OK);
577 gtk_widget_show(button);
579 g_signal_connect(G_OBJECT(dialog), "response",
580 G_CALLBACK(umount_dialog_response), mount);
582 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
583 GTK_RESPONSE_OK);
585 number_of_windows++;
586 gtk_widget_show(dialog);
589 /* Returns TRUE to prevent closing the window. May offer to unmount a
590 * device.
592 gboolean filer_window_delete(GtkWidget *window,
593 GdkEvent *unused, /* (may be NULL) */
594 FilerWindow *filer_window)
596 char *mount;
598 mount = get_ancestor_user_mount_point(filer_window->real_path);
600 if (mount)
601 may_offer_unmount(filer_window, mount);
603 return FALSE;
606 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
608 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
610 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
612 if (window_with_primary == filer_window)
613 window_with_primary = NULL;
615 if (window_with_focus == filer_window)
617 menu_popdown();
618 window_with_focus = NULL;
621 if (filer_window->directory)
622 detach(filer_window);
624 if (filer_window->open_timeout)
626 g_source_remove(filer_window->open_timeout);
627 filer_window->open_timeout = 0;
630 if (filer_window->auto_scroll != -1)
632 g_source_remove(filer_window->auto_scroll);
633 filer_window->auto_scroll = -1;
636 if (filer_window->thumb_queue)
637 destroy_glist(&filer_window->thumb_queue);
639 tooltip_show(NULL);
641 filer_set_id(filer_window, NULL);
643 if(filer_window->filter_string)
644 g_free(filer_window->filter_string);
645 if(filer_window->regexp)
646 g_free(filer_window->regexp);
648 g_free(filer_window->auto_select);
649 g_free(filer_window->real_path);
650 g_free(filer_window->sym_path);
651 g_free(filer_window);
653 one_less_window();
656 /* Returns TRUE iff the directory still exists. */
657 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
659 Directory *dir;
661 g_return_val_if_fail(filer_window != NULL, FALSE);
663 /* We do a fresh lookup (rather than update) because the inode may
664 * have changed.
666 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
667 if (!dir)
669 if (warning)
670 info_message(_("Directory missing/deleted"));
671 gtk_widget_destroy(filer_window->window);
672 return FALSE;
674 if (dir == filer_window->directory)
675 g_object_unref(dir);
676 else
678 detach(filer_window);
679 filer_window->directory = dir;
680 attach(filer_window);
683 return TRUE;
686 /* No items are now selected. This might be because another app claimed
687 * the selection or because the user unselected all the items.
689 void filer_lost_selection(FilerWindow *filer_window, guint time)
691 if (window_with_primary == filer_window)
693 window_with_primary = NULL;
694 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
698 /* Another app has claimed the primary selection */
699 static void filer_lost_primary(GtkWidget *window,
700 GdkEventSelection *event,
701 gpointer user_data)
703 FilerWindow *filer_window = (FilerWindow *) user_data;
705 if (window_with_primary && window_with_primary == filer_window)
707 window_with_primary = NULL;
708 set_selection_state(filer_window, FALSE);
712 /* Someone wants us to send them the selection */
713 static void selection_get(GtkWidget *widget,
714 GtkSelectionData *selection_data,
715 guint info,
716 guint time,
717 gpointer data)
719 GString *reply, *header;
720 FilerWindow *filer_window = (FilerWindow *) data;
721 ViewIter iter;
722 DirItem *item;
724 reply = g_string_new(NULL);
725 header = g_string_new(NULL);
727 switch (info)
729 case TARGET_STRING:
730 g_string_printf(header, " %s",
731 make_path(filer_window->sym_path, ""));
732 break;
733 case TARGET_URI_LIST:
734 g_string_printf(header, " file://%s%s",
735 our_host_name_for_dnd(),
736 make_path(filer_window->sym_path, ""));
737 break;
740 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
742 while ((item = iter.next(&iter)))
744 g_string_append(reply, header->str);
745 g_string_append(reply, item->leafname);
748 if (reply->len > 0)
749 gtk_selection_data_set_text(selection_data,
750 reply->str + 1, reply->len - 1);
751 else
753 g_warning("Attempt to paste empty selection!");
754 gtk_selection_data_set_text(selection_data, "", 0);
757 g_string_free(reply, TRUE);
758 g_string_free(header, TRUE);
761 /* Selection has been changed -- try to grab the primary selection
762 * if we don't have it. Also called when clicking on an insensitive selection
763 * to regain primary.
764 * Also updates toolbar info.
766 void filer_selection_changed(FilerWindow *filer_window, gint time)
768 g_return_if_fail(filer_window != NULL);
770 toolbar_update_info(filer_window);
772 if (window_with_primary == filer_window)
773 return; /* Already got primary */
775 if (!view_count_selected(filer_window->view))
776 return; /* Nothing selected */
778 if (filer_window->temp_item_selected == FALSE &&
779 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
780 GDK_SELECTION_PRIMARY,
781 time))
783 window_with_primary = filer_window;
784 set_selection_state(filer_window, TRUE);
786 else
787 set_selection_state(filer_window, FALSE);
790 /* Open the item (or add it to the shell command minibuffer) */
791 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
793 gboolean shift = (flags & OPEN_SHIFT) != 0;
794 gboolean close_mini = flags & OPEN_FROM_MINI;
795 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
796 DirItem *item;
797 const guchar *full_path;
798 gboolean wink = TRUE;
799 Directory *old_dir;
801 g_return_if_fail(filer_window != NULL && iter != NULL);
803 item = iter->peek(iter);
805 g_return_if_fail(item != NULL);
807 if (filer_window->mini_type == MINI_SHELL)
809 minibuffer_add(filer_window, item->leafname);
810 return;
813 if (item->base_type == TYPE_UNKNOWN)
814 dir_update_item(filer_window->directory, item->leafname);
816 if (item->base_type == TYPE_DIRECTORY)
818 /* Never close a filer window when opening a directory
819 * (click on a dir or click on an app with shift).
821 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
822 close_window = FALSE;
825 full_path = make_path(filer_window->sym_path, item->leafname);
826 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
827 wink = FALSE;
829 old_dir = filer_window->directory;
830 if (run_diritem(full_path, item,
831 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
832 filer_window,
833 shift))
835 if (old_dir != filer_window->directory)
836 return;
838 if (close_window)
839 gtk_widget_destroy(filer_window->window);
840 else
842 if (wink)
843 view_wink_item(filer_window->view, iter);
844 if (close_mini)
845 minibuffer_hide(filer_window);
850 static gint pointer_in(GtkWidget *widget,
851 GdkEventCrossing *event,
852 FilerWindow *filer_window)
854 may_rescan(filer_window, TRUE);
855 return FALSE;
858 static gint pointer_out(GtkWidget *widget,
859 GdkEventCrossing *event,
860 FilerWindow *filer_window)
862 tooltip_show(NULL);
863 return FALSE;
866 /* Move the cursor to the next selected item in direction 'dir'
867 * (+1 or -1).
869 void filer_next_selected(FilerWindow *filer_window, int dir)
871 ViewIter iter, cursor;
872 gboolean have_cursor;
873 ViewIface *view = filer_window->view;
875 g_return_if_fail(dir == 1 || dir == -1);
877 view_get_cursor(view, &cursor);
878 have_cursor = cursor.peek(&cursor) != NULL;
880 view_get_iter(view, &iter,
881 VIEW_ITER_SELECTED |
882 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
883 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
885 if (have_cursor && view_get_selected(view, &cursor))
886 iter.next(&iter); /* Skip the cursor itself */
888 if (iter.next(&iter))
889 view_cursor_to_iter(view, &iter);
890 else
891 gdk_beep();
893 return;
896 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
898 TargetFunc cb = filer_window->target_cb;
899 gpointer data = filer_window->target_data;
900 OpenFlags flags = 0;
901 ViewIter iter;
903 filer_target_mode(filer_window, NULL, NULL, NULL);
905 view_get_cursor(filer_window->view, &iter);
906 if (!iter.peek(&iter))
907 return;
909 if (cb)
911 cb(filer_window, &iter, data);
912 return;
915 if (event->state & GDK_SHIFT_MASK)
916 flags |= OPEN_SHIFT;
917 if (event->state & GDK_MOD1_MASK)
918 flags |= OPEN_CLOSE_WINDOW;
919 else
920 flags |= OPEN_SAME_WINDOW;
922 filer_openitem(filer_window, &iter, flags);
925 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
926 * changed. If no groups were loaded and there is no file then initialised
927 * groups to an empty document.
928 * Return the node for the 'name' group.
930 static xmlNode *group_find(char *name)
932 xmlNode *node;
933 gchar *path;
935 /* Update the groups, if possible */
936 path = choices_find_xdg_path_load("Groups.xml", PROJECT, SITE);
937 if (path)
939 XMLwrapper *wrapper;
940 wrapper = xml_cache_load(path);
941 if (wrapper)
943 if (groups)
944 g_object_unref(groups);
945 groups = wrapper;
948 g_free(path);
951 if (!groups)
953 groups = xml_new(NULL);
954 groups->doc = xmlNewDoc("1.0");
956 xmlDocSetRootElement(groups->doc,
957 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
958 return NULL;
961 node = xmlDocGetRootElement(groups->doc);
963 for (node = node->xmlChildrenNode; node; node = node->next)
965 guchar *gid;
967 gid = xmlGetProp(node, "name");
969 if (!gid)
970 continue;
972 if (strcmp(name, gid) != 0)
973 continue;
975 g_free(gid);
977 return node;
980 return NULL;
983 static void group_save(FilerWindow *filer_window, char *name)
985 xmlNode *group;
986 guchar *save_path;
987 DirItem *item;
988 ViewIter iter;
990 group = group_find(name);
991 if (group)
993 xmlUnlinkNode(group);
994 xmlFreeNode(group);
996 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
997 NULL, "group", NULL);
998 xmlSetProp(group, "name", name);
1000 xmlNewTextChild(group, NULL, "directory", filer_window->sym_path);
1002 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1004 while ((item = iter.next(&iter)))
1005 xmlNewTextChild(group, NULL, "item", item->leafname);
1007 save_path = choices_find_xdg_path_save("Groups.xml", PROJECT, SITE,
1008 TRUE);
1009 if (save_path)
1011 save_xml_file(groups->doc, save_path);
1012 g_free(save_path);
1016 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
1018 GHashTable *in_group = (GHashTable *) data;
1020 return g_hash_table_lookup(in_group,
1021 iter->peek(iter)->leafname) != NULL;
1024 static void group_restore(FilerWindow *filer_window, char *name)
1026 GHashTable *in_group;
1027 char *path;
1028 xmlNode *group, *node;
1030 group = group_find(name);
1032 if (!group)
1034 report_error(_("Group %s is not set. Select some files "
1035 "and press Ctrl+%s to set the group. Press %s "
1036 "on its own to reselect the files later.\n"
1037 "Make sure NumLock is on if you use the keypad."),
1038 name, name, name);
1039 return;
1042 node = get_subnode(group, NULL, "directory");
1043 g_return_if_fail(node != NULL);
1044 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
1045 g_return_if_fail(path != NULL);
1047 if (strcmp(path, filer_window->sym_path) != 0)
1048 filer_change_to(filer_window, path, NULL);
1049 g_free(path);
1051 in_group = g_hash_table_new(g_str_hash, g_str_equal);
1052 for (node = group->xmlChildrenNode; node; node = node->next)
1054 gchar *leaf;
1055 if (node->type != XML_ELEMENT_NODE)
1056 continue;
1057 if (strcmp(node->name, "item") != 0)
1058 continue;
1060 leaf = xmlNodeListGetString(groups->doc,
1061 node->xmlChildrenNode, 1);
1062 if (!leaf)
1063 g_warning("Missing leafname!\n");
1064 else
1065 g_hash_table_insert(in_group, leaf, filer_window);
1068 view_select_if(filer_window->view, &group_restore_cb, in_group);
1070 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1071 g_hash_table_destroy(in_group);
1074 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
1076 ViewIter iter;
1077 GdkEvent *event;
1079 view_get_cursor(filer_window->view, &iter);
1081 event = gtk_get_current_event();
1082 show_filer_menu(filer_window, event, &iter);
1083 if (event)
1084 gdk_event_free(event);
1086 return TRUE;
1089 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
1091 ViewIface *view = filer_window->view;
1092 ViewIter iter;
1094 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
1095 if (!iter.next(&iter))
1096 return; /* No cursor */
1098 if (view_get_selected(view, &iter))
1099 view_set_selected(view, &iter, FALSE);
1100 else
1101 view_set_selected(view, &iter, TRUE);
1103 if (iter.next(&iter))
1104 view_cursor_to_iter(view, &iter);
1107 gint filer_key_press_event(GtkWidget *widget,
1108 GdkEventKey *event,
1109 FilerWindow *filer_window)
1111 ViewIface *view = filer_window->view;
1112 ViewIter cursor;
1113 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
1114 guint key = event->keyval;
1115 char group[2] = "1";
1117 window_with_focus = filer_window;
1119 /* Delay setting up the keys until now to speed loading... */
1120 if (ensure_filer_menu())
1122 /* Gtk updates in an idle-handler, so force a recheck now */
1123 g_signal_emit_by_name(widget, "keys_changed");
1126 if (focus && focus == filer_window->minibuffer)
1127 if (gtk_widget_event(focus, (GdkEvent *) event))
1128 return TRUE; /* Handled */
1130 if (!focus)
1131 gtk_widget_grab_focus(GTK_WIDGET(view));
1133 view_get_cursor(view, &cursor);
1134 if (!cursor.peek(&cursor) && (key == GDK_Up || key == GDK_Down))
1136 ViewIter iter;
1137 view_get_iter(view, &iter, 0);
1138 if (iter.next(&iter))
1139 view_cursor_to_iter(view, &iter);
1140 gtk_widget_grab_focus(GTK_WIDGET(view)); /* Needed? */
1141 return TRUE;
1144 switch (key)
1146 case GDK_Escape:
1147 filer_target_mode(filer_window, NULL, NULL, NULL);
1148 view_cursor_to_iter(filer_window->view, NULL);
1149 view_clear_selection(filer_window->view);
1150 return FALSE;
1151 case GDK_Return:
1152 return_pressed(filer_window, event);
1153 break;
1154 case GDK_ISO_Left_Tab:
1155 filer_next_selected(filer_window, -1);
1156 break;
1157 case GDK_Tab:
1158 filer_next_selected(filer_window, 1);
1159 break;
1160 case GDK_BackSpace:
1161 change_to_parent(filer_window);
1162 break;
1163 case GDK_backslash:
1165 ViewIter iter;
1167 tooltip_show(NULL);
1169 view_get_cursor(filer_window->view, &iter);
1170 show_filer_menu(filer_window,
1171 (GdkEvent *) event, &iter);
1172 break;
1174 case ' ':
1175 filer_window_toggle_cursor_item_selected(filer_window);
1176 break;
1177 default:
1178 if (key >= GDK_0 && key <= GDK_9)
1179 group[0] = key - GDK_0 + '0';
1180 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1181 group[0] = key - GDK_KP_0 + '0';
1182 else
1184 if (focus && focus != widget &&
1185 gtk_widget_get_toplevel(focus) == widget)
1186 if (gtk_widget_event(focus,
1187 (GdkEvent *) event))
1188 return TRUE; /* Handled */
1189 return FALSE;
1192 if (event->state & GDK_CONTROL_MASK)
1193 group_save(filer_window, group);
1194 else
1195 group_restore(filer_window, group);
1198 return TRUE;
1201 void filer_open_parent(FilerWindow *filer_window)
1203 char *dir;
1204 const char *current = filer_window->sym_path;
1206 if (current[0] == '/' && current[1] == '\0')
1207 return; /* Already in the root */
1209 dir = g_path_get_dirname(current);
1210 filer_opendir(dir, filer_window, NULL);
1211 g_free(dir);
1214 void change_to_parent(FilerWindow *filer_window)
1216 char *dir;
1217 const char *current = filer_window->sym_path;
1219 if (current[0] == '/' && current[1] == '\0')
1220 return; /* Already in the root */
1222 if (mount_is_user_mounted(filer_window->real_path))
1223 may_offer_unmount(filer_window,
1224 g_strdup(filer_window->real_path));
1226 dir = g_path_get_dirname(current);
1227 filer_change_to(filer_window, dir, g_basename(current));
1228 g_free(dir);
1231 /* Removes trailing /s from path (modified in place) */
1232 static void tidy_sympath(gchar *path)
1234 int l;
1236 g_return_if_fail(path != NULL);
1238 l = strlen(path);
1239 while (l > 1 && path[l - 1] == '/')
1241 l--;
1242 path[l] = '\0';
1246 /* Make filer_window display path. When finished, highlight item 'from', or
1247 * the first item if from is NULL. If there is currently no cursor then
1248 * simply wink 'from' (if not NULL).
1249 * If the cause was a key event and we resize, warp the pointer.
1251 void filer_change_to(FilerWindow *filer_window,
1252 const char *path, const char *from)
1254 char *from_dup;
1255 char *sym_path, *real_path;
1256 Directory *new_dir;
1258 g_return_if_fail(filer_window != NULL);
1260 filer_cancel_thumbnails(filer_window);
1262 tooltip_show(NULL);
1264 sym_path = g_strdup(path);
1265 real_path = pathdup(path);
1266 new_dir = g_fscache_lookup(dir_cache, real_path);
1268 if (!new_dir)
1270 delayed_error(_("Directory '%s' is not accessible"),
1271 sym_path);
1272 g_free(real_path);
1273 g_free(sym_path);
1274 return;
1277 if (o_unique_filer_windows.int_value && !spring_in_progress)
1279 FilerWindow *fw;
1281 fw = find_filer_window(sym_path, filer_window);
1282 if (fw)
1283 gtk_widget_destroy(fw->window);
1286 from_dup = from && *from ? g_strdup(from) : NULL;
1288 detach(filer_window);
1289 g_free(filer_window->real_path);
1290 g_free(filer_window->sym_path);
1291 filer_window->real_path = real_path;
1292 filer_window->sym_path = sym_path;
1293 tidy_sympath(filer_window->sym_path);
1295 filer_window->directory = new_dir;
1297 g_free(filer_window->auto_select);
1298 filer_window->auto_select = from_dup;
1300 filer_window->had_cursor = filer_window->had_cursor ||
1301 view_cursor_visible(filer_window->view);
1303 filer_set_title(filer_window);
1304 if (filer_window->window->window)
1305 gdk_window_set_role(filer_window->window->window,
1306 filer_window->sym_path);
1307 view_cursor_to_iter(filer_window->view, NULL);
1309 attach(filer_window);
1311 check_settings(filer_window);
1313 display_set_actual_size(filer_window, FALSE);
1315 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1316 view_autosize(filer_window->view);
1318 if (filer_window->mini_type == MINI_PATH)
1319 g_idle_add((GSourceFunc) minibuffer_show_cb, filer_window);
1322 /* Returns a list containing the full (sym) pathname of every selected item.
1323 * You must g_free() each item in the list.
1325 GList *filer_selected_items(FilerWindow *filer_window)
1327 GList *retval = NULL;
1328 guchar *dir = filer_window->sym_path;
1329 ViewIter iter;
1330 DirItem *item;
1332 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1333 while ((item = iter.next(&iter)))
1335 retval = g_list_prepend(retval,
1336 g_strdup(make_path(dir, item->leafname)));
1339 return g_list_reverse(retval);
1342 /* Return the single selected item. Error if nothing is selected. */
1343 DirItem *filer_selected_item(FilerWindow *filer_window)
1345 ViewIter iter;
1346 DirItem *item;
1348 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1350 item = iter.next(&iter);
1351 g_return_val_if_fail(item != NULL, NULL);
1352 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1354 return item;
1357 /* Creates and shows a new filer window.
1358 * If src_win != NULL then display options can be taken from that source window.
1359 * Returns the new filer window, or NULL on error.
1360 * Note: if unique windows is in use, may return an existing window.
1362 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1363 const gchar *wm_class)
1365 FilerWindow *filer_window;
1366 char *real_path;
1367 DisplayStyle dstyle;
1368 DetailsType dtype;
1369 SortType s_type;
1370 GtkSortType s_order;
1371 Settings *dir_settings = NULL;
1372 gboolean force_resize = TRUE;
1374 /* Get the real pathname of the directory and copy it */
1375 real_path = pathdup(path);
1377 if (o_unique_filer_windows.int_value && !spring_in_progress)
1379 FilerWindow *same_dir_window;
1381 same_dir_window = find_filer_window(path, NULL);
1383 if (same_dir_window)
1385 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1386 return same_dir_window;
1390 filer_window = g_new(FilerWindow, 1);
1391 filer_window->message = NULL;
1392 filer_window->minibuffer = NULL;
1393 filer_window->minibuffer_label = NULL;
1394 filer_window->minibuffer_area = NULL;
1395 filer_window->temp_show_hidden = FALSE;
1396 filer_window->sym_path = g_strdup(path);
1397 filer_window->real_path = real_path;
1398 filer_window->scanning = FALSE;
1399 filer_window->had_cursor = FALSE;
1400 filer_window->auto_select = NULL;
1401 filer_window->toolbar_text = NULL;
1402 filer_window->target_cb = NULL;
1403 filer_window->mini_type = MINI_NONE;
1404 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1405 filer_window->toolbar = NULL;
1406 filer_window->toplevel_vbox = NULL;
1407 filer_window->view_hbox = NULL;
1408 filer_window->view = NULL;
1409 filer_window->scrollbar = NULL;
1410 filer_window->auto_scroll = -1;
1411 filer_window->window_id = NULL;
1413 tidy_sympath(filer_window->sym_path);
1415 /* Finds the entry for this directory in the dir cache, creating
1416 * a new one if needed. This does not cause a scan to start,
1417 * so if a new entry is created then it will be empty.
1419 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1420 if (!filer_window->directory)
1422 delayed_error(_("Directory '%s' not found."), path);
1423 g_free(filer_window->real_path);
1424 g_free(filer_window->sym_path);
1425 g_free(filer_window);
1426 return NULL;
1429 filer_window->temp_item_selected = FALSE;
1430 filer_window->flags = (FilerFlags) 0;
1431 filer_window->details_type = DETAILS_TIMES;
1432 filer_window->display_style = UNKNOWN_STYLE;
1433 filer_window->display_style_wanted = UNKNOWN_STYLE;
1434 filer_window->thumb_queue = NULL;
1435 filer_window->max_thumbs = 0;
1436 filer_window->sort_type = -1;
1438 filer_window->filter = FILER_SHOW_ALL;
1439 filer_window->filter_string = NULL;
1440 filer_window->regexp = NULL;
1441 filer_window->filter_directories = FALSE;
1443 if (src_win && o_display_inherit_options.int_value)
1445 s_type = src_win->sort_type;
1446 s_order = src_win->sort_order;
1447 dstyle = src_win->display_style_wanted;
1448 dtype = src_win->details_type;
1449 filer_window->show_hidden = src_win->show_hidden;
1450 filer_window->show_thumbs = src_win->show_thumbs;
1451 filer_window->view_type = src_win->view_type;
1453 filer_window->filter_directories = src_win->filter_directories;
1454 filer_set_filter(filer_window, src_win->filter,
1455 src_win->filter_string);
1457 else
1459 s_type = o_display_sort_by.int_value;
1460 s_order = GTK_SORT_ASCENDING;
1461 dstyle = o_display_size.int_value;
1462 dtype = o_display_details.int_value;
1463 filer_window->show_hidden = o_display_show_hidden.int_value;
1464 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1465 filer_window->view_type = o_filer_view_type.int_value;
1468 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
1469 filer_window->sym_path);
1470 if (dir_settings)
1472 /* Override the current defaults with the per-directory
1473 * user settings.
1475 if (dir_settings->flags & SET_HIDDEN)
1476 filer_window->show_hidden = dir_settings->show_hidden;
1478 if (dir_settings->flags & SET_STYLE)
1479 dstyle = dir_settings->display_style;
1481 if (dir_settings->flags & SET_DETAILS)
1483 dtype = dir_settings->details_type;
1484 filer_window->view_type = dir_settings->view_type;
1487 if (dir_settings->flags & SET_SORT)
1489 s_type = dir_settings->sort_type;
1490 s_order = dir_settings->sort_order;
1493 if (dir_settings->flags & SET_THUMBS)
1494 filer_window->show_thumbs = dir_settings->show_thumbs;
1496 if (dir_settings->flags & SET_FILTER)
1498 filer_set_filter(filer_window,
1499 dir_settings->filter_type,
1500 dir_settings->filter);
1501 filer_set_filter_directories(filer_window,
1502 dir_settings->filter_directories);
1506 /* Add all the user-interface elements & realise */
1507 filer_add_widgets(filer_window, wm_class);
1508 if (src_win)
1509 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1510 GTK_WIN_POS_MOUSE);
1512 if (dir_settings)
1514 if (dir_settings->flags & SET_POSITION)
1516 gtk_window_move(GTK_WINDOW(filer_window->window),
1517 dir_settings->x, dir_settings->y);
1519 if (dir_settings->flags & SET_SIZE)
1521 filer_window_set_size(filer_window,
1522 dir_settings->width,
1523 dir_settings->height);
1524 force_resize = o_filer_auto_resize.int_value != RESIZE_NEVER;
1528 /* Connect to all the signal handlers */
1529 filer_add_signals(filer_window);
1531 display_set_layout(filer_window, dstyle, dtype, force_resize);
1532 display_set_sort_type(filer_window, s_type, s_order);
1534 /* Open the window after a timeout, or when scanning stops.
1535 * Do this before attaching, because attach() might tell us to
1536 * stop scanning (if a scan isn't needed).
1538 filer_window->open_timeout = g_timeout_add(500,
1539 (GSourceFunc) open_filer_window,
1540 filer_window);
1542 /* The view is created empty and then attach() is called, which
1543 * links the filer window to the entry in the directory cache we
1544 * looked up / created above.
1546 * The attach() function will immediately callback to the filer window
1547 * to deliver a list of all known entries in the directory (so,
1548 * the number of items will be known after attach() returns).
1550 * If the directory was not in the cache (because it hadn't been
1551 * opened it before) then the types and icons for the entries are
1552 * not know, but the list of names is.
1555 attach(filer_window);
1557 number_of_windows++;
1558 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1560 return filer_window;
1563 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1565 GtkWidget *view = NULL;
1566 Directory *dir = NULL;
1567 GHashTable *selected = NULL;
1569 g_return_if_fail(filer_window != NULL);
1571 motion_window = NULL;
1573 if (filer_window->view)
1575 /* Save the current selection */
1576 ViewIter iter;
1577 DirItem *item;
1579 selected = g_hash_table_new(g_str_hash, g_str_equal);
1580 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1581 while ((item = iter.next(&iter)))
1582 g_hash_table_insert(selected, item->leafname, "yes");
1584 /* Destroy the old view */
1585 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1586 filer_window->view = NULL;
1588 dir = filer_window->directory;
1589 g_object_ref(dir);
1590 detach(filer_window);
1593 switch (type)
1595 case VIEW_TYPE_COLLECTION:
1596 view = view_collection_new(filer_window);
1597 break;
1598 case VIEW_TYPE_DETAILS:
1599 view = view_details_new(filer_window);
1600 break;
1603 g_return_if_fail(view != NULL);
1605 filer_window->view = VIEW(view);
1606 filer_window->view_type = type;
1608 gtk_box_pack_start(filer_window->view_hbox, view, TRUE, TRUE, 0);
1609 gtk_widget_show(view);
1611 /* Drag and drop events */
1612 make_drop_target(view, 0);
1613 g_signal_connect(view, "drag_motion",
1614 G_CALLBACK(drag_motion), filer_window);
1615 g_signal_connect(view, "drag_leave",
1616 G_CALLBACK(drag_leave), filer_window);
1617 g_signal_connect(view, "drag_end",
1618 G_CALLBACK(drag_end), filer_window);
1619 /* Dragging from us... */
1620 g_signal_connect(view, "drag_data_get",
1621 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1623 if (dir)
1625 /* Only when changing type. Otherwise, will attach later. */
1626 filer_window->directory = dir;
1627 attach(filer_window);
1629 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
1630 view_autosize(filer_window->view);
1633 if (selected)
1635 ViewIter iter;
1636 DirItem *item;
1638 view_get_iter(filer_window->view, &iter, 0);
1639 while ((item = iter.next(&iter)))
1641 if (g_hash_table_lookup(selected, item->leafname))
1642 view_set_selected(filer_window->view,
1643 &iter, TRUE);
1645 g_hash_table_destroy(selected);
1649 /* This adds all the widgets to a new filer window. It is in a separate
1650 * function because filer_opendir() was getting too long...
1652 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1654 GtkWidget *hbox, *vbox;
1656 /* Create the top-level window widget */
1657 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1658 filer_set_title(filer_window);
1659 gtk_widget_set_name(filer_window->window, "rox-filer");
1661 if (wm_class)
1662 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1663 wm_class, PROJECT);
1665 /* This property is cleared when the window is destroyed.
1666 * You can thus ref filer_window->window and use this to see
1667 * if the window no longer exists.
1669 g_object_set_data(G_OBJECT(filer_window->window),
1670 "filer_window", filer_window);
1672 /* Create this now to make the Adjustment before the View */
1673 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1675 vbox = gtk_vbox_new(FALSE, 0);
1676 gtk_container_add(GTK_CONTAINER(filer_window->window), vbox);
1678 filer_window->toplevel_vbox = GTK_BOX(vbox);
1680 /* If there's a message that should be displayed in each window (eg
1681 * 'Running as root'), add it here.
1683 if (show_user_message)
1685 filer_window->message = gtk_label_new(show_user_message);
1686 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1687 FALSE, TRUE, 0);
1688 gtk_widget_show(filer_window->message);
1691 hbox = gtk_hbox_new(FALSE, 0);
1692 filer_window->view_hbox = GTK_BOX(hbox);
1693 gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
1694 /* Add the main View widget */
1695 filer_set_view_type(filer_window, filer_window->view_type);
1696 /* Put the scrollbar next to the View */
1697 gtk_box_pack_end(GTK_BOX(hbox),
1698 filer_window->scrollbar, FALSE, TRUE, 0);
1699 gtk_widget_show(hbox);
1701 /* If we want a toolbar, create it now */
1702 toolbar_update_toolbar(filer_window);
1704 /* And the minibuffer (hidden to start with) */
1705 create_minibuffer(filer_window);
1706 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1707 FALSE, TRUE, 0);
1709 /* And the thumbnail progress bar (also hidden) */
1711 GtkWidget *cancel;
1713 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1714 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1715 FALSE, TRUE, 0);
1717 filer_window->thumb_progress = gtk_progress_bar_new();
1719 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1720 filer_window->thumb_progress, TRUE, TRUE, 0);
1722 cancel = gtk_button_new_with_label(_("Cancel"));
1723 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1724 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1725 cancel, FALSE, TRUE, 0);
1726 g_signal_connect_swapped(cancel, "clicked",
1727 G_CALLBACK(filer_cancel_thumbnails),
1728 filer_window);
1731 gtk_widget_show(vbox);
1732 gtk_widget_show(filer_window->scrollbar);
1734 gtk_widget_realize(filer_window->window);
1736 gdk_window_set_role(filer_window->window->window,
1737 filer_window->sym_path);
1739 filer_window_set_size(filer_window, 4, 4);
1742 static void filer_add_signals(FilerWindow *filer_window)
1744 GtkTargetEntry target_table[] =
1746 {"text/uri-list", 0, TARGET_URI_LIST},
1747 {"UTF8_STRING", 0, TARGET_STRING},
1748 {"STRING", 0, TARGET_STRING},
1749 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1752 /* Events on the top-level window */
1753 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1754 g_signal_connect(filer_window->window, "enter-notify-event",
1755 G_CALLBACK(pointer_in), filer_window);
1756 g_signal_connect(filer_window->window, "leave-notify-event",
1757 G_CALLBACK(pointer_out), filer_window);
1758 g_signal_connect(filer_window->window, "destroy",
1759 G_CALLBACK(filer_window_destroyed), filer_window);
1760 g_signal_connect(filer_window->window, "delete-event",
1761 G_CALLBACK(filer_window_delete), filer_window);
1763 g_signal_connect(filer_window->window, "selection_clear_event",
1764 G_CALLBACK(filer_lost_primary), filer_window);
1766 g_signal_connect(filer_window->window, "selection_get",
1767 G_CALLBACK(selection_get), filer_window);
1768 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1769 GDK_SELECTION_PRIMARY,
1770 target_table,
1771 sizeof(target_table) / sizeof(*target_table));
1773 g_signal_connect(filer_window->window, "popup-menu",
1774 G_CALLBACK(popup_menu), filer_window);
1775 g_signal_connect(filer_window->window, "key_press_event",
1776 G_CALLBACK(filer_key_press_event), filer_window);
1778 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1779 filer_keys);
1782 static gint clear_scanning_display(FilerWindow *filer_window)
1784 if (filer_exists(filer_window))
1785 filer_set_title(filer_window);
1786 return FALSE;
1789 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1791 if (scanning == filer_window->scanning)
1792 return;
1793 filer_window->scanning = scanning;
1795 if (scanning)
1796 filer_set_title(filer_window);
1797 else
1798 g_timeout_add(300, (GSourceFunc) clear_scanning_display,
1799 filer_window);
1802 /* Note that filer_window may not exist after this call.
1803 * Returns TRUE iff the directory still exists.
1805 gboolean filer_update_dir(FilerWindow *filer_window, gboolean warning)
1807 gboolean still_exists;
1809 still_exists = may_rescan(filer_window, warning);
1811 if (still_exists)
1812 dir_update(filer_window->directory, filer_window->sym_path);
1814 return still_exists;
1817 void filer_update_all(void)
1819 GList *next = all_filer_windows;
1821 while (next)
1823 FilerWindow *filer_window = (FilerWindow *) next->data;
1825 /* Updating directory may remove it from list -- stop sending
1826 * patches to move this line!
1828 next = next->next;
1830 /* Don't trigger a refresh if we're already scanning.
1831 * Otherwise, two views of a single directory will trigger
1832 * two scans.
1834 if (filer_window->directory &&
1835 !filer_window->directory->scanning)
1836 filer_update_dir(filer_window, TRUE);
1840 /* Refresh the various caches even if we don't think we need to */
1841 void full_refresh(void)
1843 mount_update(TRUE);
1844 reread_mime_files(); /* Refreshes all windows */
1847 /* See whether a filer window with a given path already exists
1848 * and is different from diff.
1850 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1852 GList *next;
1854 for (next = all_filer_windows; next; next = next->next)
1856 FilerWindow *filer_window = (FilerWindow *) next->data;
1858 if (filer_window != diff &&
1859 strcmp(sym_path, filer_window->sym_path) == 0)
1860 return filer_window;
1863 return NULL;
1866 /* This path has been mounted/umounted/deleted some files - update all dirs */
1867 void filer_check_mounted(const char *real_path)
1869 GList *next = all_filer_windows;
1870 gchar *parent;
1871 int len;
1872 gboolean resize = o_filer_auto_resize.int_value == RESIZE_ALWAYS;
1874 /* DOS disks, etc, often don't change the mtime of the root directory
1875 * on modification, so force a refresh now.
1877 g_fscache_update(dir_cache, real_path);
1879 len = strlen(real_path);
1881 while (next)
1883 FilerWindow *filer_window = (FilerWindow *) next->data;
1885 next = next->next;
1887 if (strncmp(real_path, filer_window->real_path, len) == 0)
1889 char s = filer_window->real_path[len];
1891 if (s == '/' || s == '\0')
1893 if (filer_update_dir(filer_window, FALSE) &&
1894 resize)
1895 view_autosize(filer_window->view);
1900 parent = g_path_get_dirname(real_path);
1901 refresh_dirs(parent);
1902 g_free(parent);
1904 icons_may_update(real_path);
1907 /* Close all windows displaying 'path' or subdirectories of 'path' */
1908 void filer_close_recursive(const char *path)
1910 GList *next = all_filer_windows;
1911 gchar *real;
1912 int len;
1914 real = pathdup(path);
1915 len = strlen(real);
1917 while (next)
1919 FilerWindow *filer_window = (FilerWindow *) next->data;
1921 next = next->next;
1923 if (strncmp(real, filer_window->real_path, len) == 0)
1925 char s = filer_window->real_path[len];
1927 if (len == 1 || s == '/' || s == '\0')
1928 gtk_widget_destroy(filer_window->window);
1933 /* Like minibuffer_show(), except that:
1934 * - It returns FALSE (to be used from an idle callback)
1935 * - It checks that the filer window still exists.
1937 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1939 if (filer_exists(filer_window))
1940 minibuffer_show(filer_window, MINI_PATH);
1941 return FALSE;
1944 /* TRUE iff filer_window points to an existing FilerWindow
1945 * structure.
1947 gboolean filer_exists(FilerWindow *filer_window)
1949 GList *next;
1951 for (next = all_filer_windows; next; next = next->next)
1953 FilerWindow *fw = (FilerWindow *) next->data;
1955 if (fw == filer_window)
1956 return TRUE;
1959 return FALSE;
1962 FilerWindow *filer_get_by_id(const char *id)
1964 return g_hash_table_lookup(window_with_id, id);
1967 void filer_set_id(FilerWindow *filer_window, const char *id)
1969 g_return_if_fail(filer_window != NULL);
1971 if (filer_window->window_id)
1973 g_hash_table_remove(window_with_id, filer_window->window_id);
1974 g_free(filer_window->window_id);
1975 filer_window->window_id = NULL;
1978 if (id)
1980 filer_window->window_id = g_strdup(id);
1981 g_hash_table_insert(window_with_id,
1982 filer_window->window_id,
1983 filer_window);
1987 /* Make sure the window title is up-to-date */
1988 void filer_set_title(FilerWindow *filer_window)
1990 gchar *title = NULL;
1991 guchar *flags = "";
1993 if (filer_window->scanning ||
1994 filer_window->filter != FILER_SHOW_ALL ||
1995 filer_window->show_hidden || filer_window->show_thumbs)
1997 if (o_short_flag_names.int_value)
1999 const gchar *hidden = "";
2001 switch(filer_window->filter) {
2002 case FILER_SHOW_ALL:
2003 hidden=filer_window->show_hidden? _("A") : "";
2004 break;
2005 case FILER_SHOW_GLOB: hidden = _("G"); break;
2006 default: break;
2009 flags = g_strconcat(" +",
2010 filer_window->scanning ? _("S") : "",
2011 hidden,
2012 filer_window->show_thumbs ? _("T") : "",
2013 NULL);
2015 else
2017 gchar *hidden = NULL;
2019 switch(filer_window->filter) {
2020 case FILER_SHOW_ALL:
2021 hidden = g_strdup(filer_window->show_hidden
2022 ? _("All, ") : "");
2023 break;
2024 case FILER_SHOW_GLOB:
2025 hidden = g_strdup_printf(_("Glob (%s), "),
2026 filer_window->filter_string);
2027 break;
2028 default:
2029 hidden =g_strdup("");
2030 break;
2032 flags = g_strconcat(" (",
2033 filer_window->scanning ? _("Scanning, ") : "",
2034 hidden,
2035 filer_window->show_thumbs ? _("Thumbs, ") : "",
2036 NULL);
2037 flags[strlen(flags) - 2] = ')';
2038 g_free(hidden);
2042 if (not_local)
2043 title = g_strconcat("//", our_host_name(),
2044 filer_window->sym_path, flags, NULL);
2046 if (!title && home_dir_len > 1 &&
2047 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
2049 guchar sep = filer_window->sym_path[home_dir_len];
2051 if (sep == '\0' || sep == '/')
2052 title = g_strconcat("~",
2053 filer_window->sym_path + home_dir_len,
2054 flags,
2055 NULL);
2058 if (!title)
2059 title = g_strconcat(filer_window->sym_path, flags, NULL);
2061 ensure_utf8(&title);
2063 if (filer_window->directory->error)
2065 gchar *old = title;
2066 title = g_strconcat(old, ": ", filer_window->directory->error,
2067 NULL);
2068 g_free(old);
2071 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
2073 g_free(title);
2075 if (flags[0] != '\0')
2076 g_free(flags);
2079 /* Reconnect to the same directory (used when the Show Hidden option is
2080 * toggled). This has the side-effect of updating the window title.
2082 void filer_detach_rescan(FilerWindow *filer_window)
2084 Directory *dir = filer_window->directory;
2086 g_object_ref(dir);
2087 detach(filer_window);
2088 filer_window->directory = dir;
2089 attach(filer_window);
2092 /* Puts the filer window into target mode. When an item is chosen,
2093 * fn(filer_window, iter, data) is called. 'reason' will be displayed
2094 * on the toolbar while target mode is active.
2096 * Use fn == NULL to cancel target mode.
2098 void filer_target_mode(FilerWindow *filer_window,
2099 TargetFunc fn,
2100 gpointer data,
2101 const char *reason)
2103 TargetFunc old_fn = filer_window->target_cb;
2105 if (fn != old_fn)
2106 gdk_window_set_cursor(
2107 GTK_WIDGET(filer_window->view)->window,
2108 fn ? crosshair : NULL);
2110 filer_window->target_cb = fn;
2111 filer_window->target_data = data;
2113 if (filer_window->toolbar_text == NULL)
2114 return;
2116 if (fn)
2117 gtk_label_set_text(
2118 GTK_LABEL(filer_window->toolbar_text), reason);
2119 else if (o_toolbar_info.int_value)
2121 if (old_fn)
2122 toolbar_update_info(filer_window);
2124 else
2125 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2128 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2130 GtkStateType old_state = filer_window->selection_state;
2132 filer_window->selection_state = normal
2133 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2135 if (old_state != filer_window->selection_state
2136 && view_count_selected(filer_window->view))
2137 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
2140 void filer_cancel_thumbnails(FilerWindow *filer_window)
2142 gtk_widget_hide(filer_window->thumb_bar);
2144 destroy_glist(&filer_window->thumb_queue);
2145 filer_window->max_thumbs = 0;
2148 /* Generate the next thumb for this window. The window object is
2149 * unref'd when there is nothing more to do.
2150 * If the window no longer has a filer window, nothing is done.
2152 static gboolean filer_next_thumb_real(GObject *window)
2154 FilerWindow *filer_window;
2155 gchar *path;
2156 int done, total;
2158 filer_window = g_object_get_data(window, "filer_window");
2160 if (!filer_window)
2162 g_object_unref(window);
2163 return FALSE;
2166 if (!filer_window->thumb_queue)
2168 filer_cancel_thumbnails(filer_window);
2169 g_object_unref(window);
2170 return FALSE;
2173 total = filer_window->max_thumbs;
2174 done = total - g_list_length(filer_window->thumb_queue);
2176 path = (gchar *) filer_window->thumb_queue->data;
2178 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2180 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2181 path);
2182 g_free(path);
2184 gtk_progress_bar_set_fraction(
2185 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2186 done / (float) total);
2188 return FALSE;
2191 /* path is the thumb just loaded, if any.
2192 * window is unref'd (eventually).
2194 static void filer_next_thumb(GObject *window, const gchar *path)
2196 if (path)
2197 dir_force_update_path(path);
2199 g_idle_add((GSourceFunc) filer_next_thumb_real, window);
2202 static void start_thumb_scanning(FilerWindow *filer_window)
2204 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2205 return; /* Already scanning */
2207 gtk_widget_show_all(filer_window->thumb_bar);
2209 g_object_ref(G_OBJECT(filer_window->window));
2210 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2213 /* Set this image to be loaded some time in the future */
2214 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2216 if (g_list_find_custom(filer_window->thumb_queue, path,
2217 (GCompareFunc) strcmp))
2218 return;
2220 if (!filer_window->thumb_queue)
2221 filer_window->max_thumbs=0;
2222 filer_window->max_thumbs++;
2224 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2225 g_strdup(path));
2227 if (filer_window->scanning)
2228 return; /* Will start when scan ends */
2230 start_thumb_scanning(filer_window);
2233 /* If thumbnail display is on, look through all the items in this directory
2234 * and start creating or updating the thumbnails as needed.
2236 void filer_create_thumbs(FilerWindow *filer_window)
2238 DirItem *item;
2239 ViewIter iter;
2241 if (!filer_window->show_thumbs)
2242 return;
2244 view_get_iter(filer_window->view, &iter, 0);
2246 while ((item = iter.next(&iter)))
2248 MaskedPixmap *pixmap;
2249 const guchar *path;
2250 gboolean found;
2252 if (item->base_type != TYPE_FILE)
2253 continue;
2255 /*if (strcmp(item->mime_type->media_type, "image") != 0)
2256 continue;*/
2258 path = make_path(filer_window->real_path, item->leafname);
2260 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2261 FSCACHE_LOOKUP_ONLY_NEW, &found);
2262 if (pixmap)
2263 g_object_unref(pixmap);
2265 /* If we didn't get an image, it could be because:
2267 * - We're loading the image now. found is TRUE,
2268 * and we'll update the item later.
2269 * - We tried to load the image and failed. found
2270 * is TRUE.
2271 * - We haven't tried loading the image. found is
2272 * FALSE, and we start creating the thumb here.
2274 if (!found)
2275 filer_create_thumb(filer_window, path);
2279 static void filer_options_changed(void)
2281 if (o_short_flag_names.has_changed)
2283 GList *next;
2285 for (next = all_filer_windows; next; next = next->next)
2287 FilerWindow *filer_window = (FilerWindow *) next->data;
2289 filer_set_title(filer_window);
2294 /* Append interesting information to this GString */
2295 void filer_add_tip_details(FilerWindow *filer_window,
2296 GString *tip, DirItem *item)
2298 const guchar *fullpath = NULL;
2300 fullpath = make_path(filer_window->real_path, item->leafname);
2302 if (item->flags & ITEM_FLAG_SYMLINK)
2304 char *target;
2306 target = readlink_dup(fullpath);
2307 if (target)
2309 ensure_utf8(&target);
2311 g_string_append(tip, _("Symbolic link to "));
2312 g_string_append(tip, target);
2313 g_string_append_c(tip, '\n');
2314 g_free(target);
2318 if (item->flags & ITEM_FLAG_APPDIR)
2320 XMLwrapper *info;
2321 xmlNode *node;
2323 info = appinfo_get(fullpath, item);
2324 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
2326 guchar *str;
2327 str = xmlNodeListGetString(node->doc,
2328 node->xmlChildrenNode, 1);
2329 if (str)
2331 g_string_append(tip, str);
2332 g_string_append_c(tip, '\n');
2333 g_free(str);
2336 if (info)
2337 g_object_unref(info);
2339 else if (item->mime_type == application_x_desktop)
2341 char *summary;
2342 summary = tip_from_desktop_file(fullpath);
2343 if (summary)
2345 g_string_append(tip, summary);
2346 g_string_append_c(tip, '\n');
2347 g_free(summary);
2351 if (!g_utf8_validate(item->leafname, -1, NULL))
2352 g_string_append(tip,
2353 _("This filename is not valid UTF-8. "
2354 "You should rename it.\n"));
2357 /* Return the selection as a text/uri-list.
2358 * g_free() the result.
2360 static guchar *filer_create_uri_list(FilerWindow *filer_window)
2362 GString *string;
2363 GString *leader;
2364 ViewIter iter;
2365 DirItem *item;
2366 guchar *retval;
2368 g_return_val_if_fail(filer_window != NULL, NULL);
2370 string = g_string_new(NULL);
2372 leader = g_string_new(filer_window->sym_path);
2373 if (leader->str[leader->len - 1] != '/')
2374 g_string_append_c(leader, '/');
2376 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
2377 while ((item = iter.next(&iter)))
2379 EscapedPath *uri;
2380 char *path;
2382 path = g_strconcat(leader->str, item->leafname, NULL);
2383 uri = encode_path_as_uri(path);
2384 g_string_append(string, (char *) uri);
2385 g_string_append(string, "\r\n");
2386 g_free(path);
2387 g_free(uri);
2390 g_string_free(leader, TRUE);
2391 retval = string->str;
2392 g_string_free(string, FALSE);
2394 return retval;
2397 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
2399 BindAction action;
2400 ViewIface *view = filer_window->view;
2401 DirItem *item = NULL;
2402 gboolean press = event->type == GDK_BUTTON_PRESS;
2403 ViewIter iter;
2404 OpenFlags flags = 0;
2406 if (event->button > 3)
2407 return;
2409 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2410 item = iter.peek(&iter);
2412 if (item && view_cursor_visible(view))
2413 view_cursor_to_iter(view, &iter);
2415 if (item && event->button == 1 &&
2416 view_get_selected(view, &iter) &&
2417 filer_window->selection_state == GTK_STATE_INSENSITIVE)
2419 /* Possibly a really slow DnD operation? */
2420 filer_window->temp_item_selected = FALSE;
2422 filer_selection_changed(filer_window, event->time);
2423 return;
2426 if (filer_window->target_cb)
2428 dnd_motion_ungrab();
2429 if (item && press && event->button == 1)
2430 filer_window->target_cb(filer_window, &iter,
2431 filer_window->target_data);
2433 filer_target_mode(filer_window, NULL, NULL, NULL);
2435 return;
2438 if (!o_single_click.int_value)
2440 /* Make sure both parts of a double-click fall on
2441 * the same file.
2443 static guchar *first_click = NULL;
2444 static guchar *second_click = NULL;
2446 if (event->type == GDK_BUTTON_PRESS)
2448 g_free(first_click);
2449 first_click = second_click;
2451 if (item)
2452 second_click = g_strdup(item->leafname);
2453 else
2454 second_click = NULL;
2457 if (event->type == GDK_2BUTTON_PRESS)
2459 if (first_click && second_click &&
2460 strcmp(first_click, second_click) != 0)
2461 return;
2462 if ((first_click || second_click) &&
2463 !(first_click && second_click))
2464 return;
2468 action = bind_lookup_bev(
2469 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
2470 event);
2472 switch (action)
2474 case ACT_CLEAR_SELECTION:
2475 view_clear_selection(view);
2476 break;
2477 case ACT_TOGGLE_SELECTED:
2478 view_set_selected(view, &iter,
2479 !view_get_selected(view, &iter));
2480 break;
2481 case ACT_SELECT_EXCL:
2482 view_select_only(view, &iter);
2483 break;
2484 case ACT_EDIT_ITEM:
2485 flags |= OPEN_SHIFT;
2486 /* (no break) */
2487 case ACT_OPEN_ITEM:
2488 if (event->button != 1 || event->state & GDK_MOD1_MASK)
2489 flags |= OPEN_CLOSE_WINDOW;
2490 else
2491 flags |= OPEN_SAME_WINDOW;
2492 if (o_new_button_1.int_value)
2493 flags ^= OPEN_SAME_WINDOW;
2494 if (event->type == GDK_2BUTTON_PRESS)
2495 view_set_selected(view, &iter, FALSE);
2496 dnd_motion_ungrab();
2498 filer_openitem(filer_window, &iter, flags);
2499 break;
2500 case ACT_POPUP_MENU:
2501 dnd_motion_ungrab();
2502 tooltip_show(NULL);
2503 show_filer_menu(filer_window,
2504 (GdkEvent *) event, &iter);
2505 break;
2506 case ACT_PRIME_AND_SELECT:
2507 if (item && !view_get_selected(view, &iter))
2508 view_select_only(view, &iter);
2509 dnd_motion_start(MOTION_READY_FOR_DND);
2510 break;
2511 case ACT_PRIME_AND_TOGGLE:
2512 view_set_selected(view, &iter,
2513 !view_get_selected(view, &iter));
2514 dnd_motion_start(MOTION_READY_FOR_DND);
2515 break;
2516 case ACT_PRIME_FOR_DND:
2517 dnd_motion_start(MOTION_READY_FOR_DND);
2518 break;
2519 case ACT_IGNORE:
2520 if (press && event->button < 4)
2522 if (item)
2523 view_wink_item(view, &iter);
2524 dnd_motion_start(MOTION_NONE);
2526 break;
2527 case ACT_LASSO_CLEAR:
2528 view_clear_selection(view);
2529 /* (no break) */
2530 case ACT_LASSO_MODIFY:
2531 view_start_lasso_box(view, event);
2532 break;
2533 case ACT_RESIZE:
2534 view_autosize(filer_window->view);
2535 break;
2536 default:
2537 g_warning("Unsupported action : %d\n", action);
2538 break;
2542 /* It's time to make the tooltip appear. If we're not over the item any
2543 * more, or the item doesn't need a tooltip, do nothing.
2545 static gboolean tooltip_activate(GtkWidget *window)
2547 FilerWindow *filer_window;
2548 ViewIface *view;
2549 ViewIter iter;
2550 gint x, y;
2551 DirItem *item = NULL;
2552 GString *tip = NULL;
2554 g_return_val_if_fail(tip_item != NULL, 0);
2556 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2558 if (!motion_window || !filer_window)
2559 return FALSE; /* Window has been destroyed */
2561 view = filer_window->view;
2563 tooltip_show(NULL);
2565 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2566 view_get_iter_at_point(view, &iter, motion_window, x, y);
2568 item = iter.peek(&iter);
2569 if (item != tip_item)
2570 return FALSE; /* Not still under the pointer */
2572 /* OK, the filer window still exists and the pointer is still
2573 * over the same item. Do we need to show a tip?
2576 tip = g_string_new(NULL);
2578 view_extend_tip(filer_window->view, &iter, tip);
2580 filer_add_tip_details(filer_window, tip, tip_item);
2582 if (tip->len > 1)
2584 g_string_truncate(tip, tip->len - 1);
2586 tooltip_show(tip->str);
2589 g_string_free(tip, TRUE);
2591 return FALSE;
2594 /* Motion detected on the View widget */
2595 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2597 ViewIface *view = filer_window->view;
2598 ViewIter iter;
2599 DirItem *item;
2601 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2602 item = iter.peek(&iter);
2604 if (item)
2606 if (item != tip_item)
2608 tooltip_show(NULL);
2610 tip_item = item;
2611 motion_window = event->window;
2612 tooltip_prime((GtkFunction) tooltip_activate,
2613 G_OBJECT(filer_window->window));
2616 else
2618 tooltip_show(NULL);
2619 tip_item = NULL;
2622 if (motion_state != MOTION_READY_FOR_DND)
2623 return FALSE;
2625 if (!dnd_motion_moved(event))
2626 return FALSE;
2628 view_get_iter_at_point(view, &iter,
2629 event->window,
2630 event->x - (event->x_root - drag_start_x),
2631 event->y - (event->y_root - drag_start_y));
2632 item = iter.peek(&iter);
2633 if (!item)
2634 return FALSE;
2636 view_wink_item(view, NULL);
2638 if (!view_get_selected(view, &iter))
2640 if (event->state & GDK_BUTTON1_MASK)
2642 /* Select just this one */
2643 filer_window->temp_item_selected = TRUE;
2644 view_select_only(view, &iter);
2646 else
2648 if (view_count_selected(view) == 0)
2649 filer_window->temp_item_selected = TRUE;
2650 view_set_selected(view, &iter, TRUE);
2654 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2656 if (view_count_selected(view) == 1)
2658 if (item->base_type == TYPE_UNKNOWN)
2659 item = dir_update_item(filer_window->directory,
2660 item->leafname);
2662 if (!item)
2664 report_error(_("Item no longer exists!"));
2665 return FALSE;
2668 drag_one_item(GTK_WIDGET(view), event,
2669 make_path(filer_window->sym_path, item->leafname),
2670 item, di_image(item));
2671 #if 0
2672 /* XXX: Use thumbnail */
2673 item, view ? view->image : NULL);
2674 #endif
2676 else
2678 guchar *uris;
2680 uris = filer_create_uri_list(filer_window);
2681 drag_selection(GTK_WIDGET(view), event, uris);
2682 g_free(uris);
2685 return FALSE;
2688 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2689 FilerWindow *filer_window)
2691 filer_set_autoscroll(filer_window, FALSE);
2693 if (filer_window->temp_item_selected)
2695 view_clear_selection(filer_window->view);
2696 filer_window->temp_item_selected = FALSE;
2700 /* Remove highlights */
2701 static void drag_leave(GtkWidget *widget,
2702 GdkDragContext *context,
2703 guint32 time,
2704 FilerWindow *filer_window)
2706 dnd_spring_abort();
2709 /* Called during the drag when the mouse is in a widget registered
2710 * as a drop target. Returns TRUE if we can accept the drop.
2712 static gboolean drag_motion(GtkWidget *widget,
2713 GdkDragContext *context,
2714 gint x,
2715 gint y,
2716 guint time,
2717 FilerWindow *filer_window)
2719 DirItem *item;
2720 ViewIface *view = filer_window->view;
2721 ViewIter iter;
2722 GdkDragAction action = context->suggested_action;
2723 const guchar *new_path = NULL;
2724 const char *type = NULL;
2725 gboolean retval = FALSE;
2726 gboolean same_window;
2728 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value)
2730 guint state;
2731 gdk_window_get_pointer(NULL, NULL, NULL, &state);
2732 if (state & GDK_BUTTON1_MASK)
2733 action = GDK_ACTION_ASK;
2736 same_window = gtk_drag_get_source_widget(context) == widget;
2738 filer_set_autoscroll(filer_window, TRUE);
2740 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2742 GdkWindow *bin;
2743 int bin_y;
2744 /* Correct for position of bin window */
2745 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2746 gdk_window_get_position(bin, NULL, &bin_y);
2747 y -= bin_y;
2750 if (o_dnd_drag_to_icons.int_value)
2752 view_get_iter_at_point(view, &iter, widget->window, x, y);
2753 item = iter.peek(&iter);
2755 else
2756 item = NULL;
2758 if (item && same_window && view_get_selected(view, &iter))
2759 type = NULL;
2760 else
2761 type = dnd_motion_item(context, &item);
2763 if (!type)
2764 item = NULL;
2766 /* Don't allow drops to non-writeable directories. BUT, still
2767 * allow drops on non-writeable SUBdirectories so that we can
2768 * do the spring-open thing.
2770 if (item && type == drop_dest_dir &&
2771 !(item->flags & ITEM_FLAG_APPDIR))
2773 dnd_spring_load(context, filer_window);
2775 else
2776 dnd_spring_abort();
2778 if (item)
2779 view_cursor_to_iter(view, &iter);
2780 else
2782 view_cursor_to_iter(view, NULL);
2784 /* Disallow background drops within a single window */
2785 if (type && same_window)
2786 type = NULL;
2789 if (type)
2791 if (item)
2792 new_path = make_path(filer_window->sym_path,
2793 item->leafname);
2794 else
2795 new_path = filer_window->sym_path;
2798 /* Don't ask about dragging to an application! */
2799 if (type == drop_dest_prog && action == GDK_ACTION_ASK)
2800 action = GDK_ACTION_COPY;
2802 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2803 if (type)
2805 gdk_drag_status(context, action, time);
2806 g_dataset_set_data_full(context, "drop_dest_path",
2807 g_strdup(new_path), g_free);
2808 retval = TRUE;
2811 return retval;
2814 static gboolean as_timeout(FilerWindow *filer_window)
2816 gboolean retval;
2818 retval = view_auto_scroll_callback(filer_window->view);
2820 if (!retval)
2821 filer_window->auto_scroll = -1;
2823 return retval;
2826 /* When autoscroll is on, a timer keeps track of the pointer position.
2827 * While it's near the top or bottom of the window, the window scrolls.
2829 * If the mouse buttons are released, the pointer leaves the window, or
2830 * a drag-and-drop operation finishes, auto_scroll is turned off.
2832 void filer_set_autoscroll(FilerWindow *filer_window, gboolean auto_scroll)
2834 g_return_if_fail(filer_window != NULL);
2836 if (auto_scroll)
2838 if (filer_window->auto_scroll != -1)
2839 return; /* Already on! */
2841 filer_window->auto_scroll = g_timeout_add(50,
2842 (GSourceFunc) as_timeout,
2843 filer_window);
2845 else
2847 if (filer_window->auto_scroll == -1)
2848 return; /* Already off! */
2850 g_source_remove(filer_window->auto_scroll);
2851 filer_window->auto_scroll = -1;
2855 #define ZERO_MNT "/uri/0install"
2857 static void refresh_done(FilerWindow *filer_window)
2859 if (filer_exists(filer_window))
2860 filer_update_dir(filer_window, TRUE);
2863 void filer_refresh(FilerWindow *filer_window)
2865 if (!strncmp(ZERO_MNT "/", filer_window->real_path, sizeof(ZERO_MNT)))
2867 /* Try to run 0refresh */
2868 gint pid;
2869 gchar *argv[] = {"0refresh", NULL, NULL};
2870 const char *host = filer_window->real_path + sizeof(ZERO_MNT);
2871 const char *slash;
2873 slash = strchr(host, '/');
2874 if (slash)
2875 argv[1] = g_strndup(host, slash - host);
2876 else
2877 argv[1] = g_strdup(host);
2878 pid = rox_spawn(filer_window->real_path, (const char **) argv);
2879 g_free(argv[1]);
2880 if (pid)
2881 on_child_death(pid, (CallbackFn) refresh_done,
2882 filer_window);
2885 full_refresh();
2888 static inline gboolean is_hidden(const char *dir, DirItem *item)
2890 /* If the leaf name starts with '.' then the item is hidden */
2891 if(item->leafname[0]=='.')
2892 return TRUE;
2894 /*** Test disabled for now. The flags aren't set on the first pass...
2896 #if 0
2897 /* Most files will not have extended attributes, so this should
2898 * be quick. */
2899 if(!o_xattr_ignore.int_value && (item->flags & ITEM_FLAG_HAS_XATTR)) {
2900 gchar *path, *val;
2901 int len;
2902 gboolean hidden=FALSE;
2904 path=g_build_filename(dir, item->leafname, NULL);
2905 val=xattr_get(path, XATTR_HIDDEN, &len);
2906 if(val) {
2907 hidden=atoi(val) || (strcmp(val, "true")==0);
2908 g_free(val);
2910 g_free(path);
2912 if(hidden)
2913 return TRUE;
2915 #endif
2917 /* Otherwise not hidden */
2918 return FALSE;
2921 gboolean filer_match_filter(FilerWindow *filer_window, DirItem *item)
2923 g_return_val_if_fail(item != NULL, FALSE);
2925 if(is_hidden(filer_window->real_path, item) &&
2926 (!filer_window->temp_show_hidden && !filer_window->show_hidden))
2927 return FALSE;
2929 switch(filer_window->filter) {
2930 case FILER_SHOW_GLOB:
2931 return fnmatch(filer_window->filter_string,
2932 item->leafname, 0)==0 ||
2933 (item->base_type==TYPE_DIRECTORY &&
2934 !filer_window->filter_directories);
2936 case FILER_SHOW_ALL:
2937 default:
2938 break;
2940 return TRUE;
2943 /* Provided to hide the implementation */
2944 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
2946 filer_window->show_hidden=hidden;
2949 /* Provided to hide the implementation */
2950 void filer_set_filter_directories(FilerWindow *filer_window, gboolean filter_directories)
2952 filer_window->filter_directories=filter_directories;
2955 /* Set the filter type. Returns TRUE if the type has changed
2956 * (must call filer_detach_rescan).
2958 gboolean filer_set_filter(FilerWindow *filer_window, FilterType type,
2959 const gchar *filter_string)
2961 /* Is this new filter the same as the old one? */
2962 if (filer_window->filter == type)
2964 switch(filer_window->filter)
2966 case FILER_SHOW_ALL:
2967 return FALSE;
2968 case FILER_SHOW_GLOB:
2969 if (strcmp(filer_window->filter_string,
2970 filter_string) == 0)
2971 return FALSE;
2972 break;
2976 /* Clean up old filter */
2977 if (filer_window->filter_string)
2979 g_free(filer_window->filter_string);
2980 filer_window->filter_string = NULL;
2982 /* Also clean up compiled regexp when implemented */
2984 filer_window->filter = type;
2986 switch(type)
2988 case FILER_SHOW_ALL:
2989 /* No extra work */
2990 break;
2992 case FILER_SHOW_GLOB:
2993 filer_window->filter_string = g_strdup(filter_string);
2994 break;
2996 default:
2997 /* oops */
2998 filer_window->filter = FILER_SHOW_ALL;
2999 g_warning("Impossible: filter type %d", type);
3000 break;
3003 return TRUE;
3006 /* Setting stuff */
3007 static Settings *settings_new(const char *path)
3009 Settings *set;
3011 set=g_new(Settings, 1);
3012 memset(set, 0, sizeof(Settings));
3013 if(path)
3014 set->path=g_strdup(path);
3016 return set;
3019 static void settings_free(Settings *set)
3021 g_free(set->path);
3022 if(set->filter)
3023 g_free(set->filter);
3024 g_free(set);
3027 static void store_settings(Settings *set)
3029 Settings *old;
3031 old=g_hash_table_lookup(settings_table, set->path);
3032 if(old)
3034 g_hash_table_remove(settings_table, set->path);
3035 settings_free(old);
3038 g_hash_table_insert(settings_table, set->path, set);
3041 /* TODO: use symbolic names in the XML file where possible */
3042 static void load_from_node(Settings *set, xmlDocPtr doc, xmlNodePtr node)
3044 xmlChar *str=NULL;
3046 str=xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
3048 if(strcmp(node->name, "X") == 0) {
3049 set->x=atoi(str);
3050 set->flags|=SET_POSITION;
3051 } else if(strcmp(node->name, "Y") == 0) {
3052 set->y=atoi(str);
3053 set->flags|=SET_POSITION;
3054 } else if(strcmp(node->name, "Width") == 0) {
3055 set->width=atoi(str);
3056 set->flags|=SET_SIZE;
3057 } else if(strcmp(node->name, "Height") == 0) {
3058 set->height=atoi(str);
3059 set->flags|=SET_SIZE;
3060 } else if(strcmp(node->name, "ShowHidden") == 0) {
3061 set->show_hidden=atoi(str);
3062 set->flags|=SET_HIDDEN;
3063 } else if(strcmp(node->name, "ViewType") == 0) {
3064 set->view_type=atoi(str);
3065 set->flags|=SET_DETAILS;
3066 } else if(strcmp(node->name, "DetailsType") == 0) {
3067 set->details_type=atoi(str);
3068 set->flags|=SET_DETAILS;
3069 } else if(strcmp(node->name, "SortType") == 0) {
3070 set->sort_type=atoi(str);
3071 set->flags|=SET_SORT;
3072 } else if(strcmp(node->name, "SortOrder") == 0) {
3073 set->sort_order=atoi(str);
3074 set->flags|=SET_SORT;
3075 } else if(strcmp(node->name, "DisplayStyle") == 0) {
3076 set->display_style=atoi(str);
3077 set->flags|=SET_STYLE;
3078 } else if(strcmp(node->name, "ShowThumbs") == 0) {
3079 set->show_thumbs=atoi(str);
3080 set->flags|=SET_THUMBS;
3081 } else if(strcmp(node->name, "FilterType") == 0) {
3082 set->filter_type=atoi(str);
3083 set->flags|=SET_FILTER;
3084 } else if(strcmp(node->name, "Filter") == 0) {
3085 set->filter=g_strdup(str);
3086 set->flags|=SET_FILTER;
3087 } else if(strcmp(node->name, "FilterDirectories") == 0) {
3088 set->filter_directories=atoi(str);
3089 set->flags|=SET_FILTER;
3092 if(str)
3093 xmlFree(str);
3096 static void load_settings(void)
3098 gchar *path;
3099 XMLwrapper *settings_doc=NULL;
3101 path=choices_find_xdg_path_load("Settings.xml", PROJECT, SITE);
3102 if(path) {
3103 settings_doc=xml_new(path);
3104 g_free(path);
3107 if(!settings_table)
3108 settings_table=g_hash_table_new(g_str_hash, g_str_equal);
3110 if(settings_doc) {
3111 xmlNodePtr node, subnode;
3113 node = xmlDocGetRootElement(settings_doc->doc);
3115 for (node = node->xmlChildrenNode; node; node = node->next)
3117 Settings *set;
3118 xmlChar *path;
3120 if (node->type != XML_ELEMENT_NODE)
3121 continue;
3122 if (strcmp(node->name, "FilerWindow") != 0)
3123 continue;
3125 path=xmlGetProp(node, "path");
3126 set=settings_new(path);
3127 xmlFree(path);
3129 for (subnode=node->xmlChildrenNode; subnode;
3130 subnode=subnode->next) {
3132 if (subnode->type != XML_ELEMENT_NODE)
3133 continue;
3134 load_from_node(set, settings_doc->doc,
3135 subnode);
3138 store_settings(set);
3140 g_object_unref(settings_doc);
3144 static void add_nodes(gpointer key, gpointer value, gpointer data)
3146 xmlNodePtr node=(xmlNodePtr) data;
3147 xmlNodePtr sub;
3148 Settings *set=(Settings *) value;
3149 char *tmp;
3151 sub=xmlNewChild(node, NULL, "FilerWindow", NULL);
3153 xmlSetProp(sub, "path", set->path);
3155 if(set->flags & SET_POSITION) {
3156 tmp=g_strdup_printf("%d", set->x);
3157 xmlNewChild(sub, NULL, "X", tmp);
3158 g_free(tmp);
3159 tmp=g_strdup_printf("%d", set->y);
3160 xmlNewChild(sub, NULL, "Y", tmp);
3161 g_free(tmp);
3163 if(set->flags & SET_SIZE) {
3164 tmp=g_strdup_printf("%d", set->width);
3165 xmlNewChild(sub, NULL, "Width", tmp);
3166 g_free(tmp);
3167 tmp=g_strdup_printf("%d", set->height);
3168 xmlNewChild(sub, NULL, "Height", tmp);
3169 g_free(tmp);
3171 if(set->flags & SET_HIDDEN) {
3172 tmp=g_strdup_printf("%d", set->show_hidden);
3173 xmlNewChild(sub, NULL, "ShowHidden", tmp);
3174 g_free(tmp);
3176 if(set->flags & SET_STYLE) {
3177 tmp=g_strdup_printf("%d", set->display_style);
3178 xmlNewChild(sub, NULL, "DisplayStyle", tmp);
3179 g_free(tmp);
3181 if(set->flags & SET_SORT) {
3182 tmp=g_strdup_printf("%d", set->sort_type);
3183 xmlNewChild(sub, NULL, "SortType", tmp);
3184 g_free(tmp);
3185 tmp=g_strdup_printf("%d", set->sort_order);
3186 xmlNewChild(sub, NULL, "SortOrder", tmp);
3187 g_free(tmp);
3189 if(set->flags & SET_DETAILS) {
3190 tmp=g_strdup_printf("%d", set->view_type);
3191 xmlNewChild(sub, NULL, "ViewType", tmp);
3192 g_free(tmp);
3193 tmp=g_strdup_printf("%d", set->details_type);
3194 xmlNewChild(sub, NULL, "DetailsType", tmp);
3195 g_free(tmp);
3197 if(set->flags & SET_STYLE) {
3198 tmp=g_strdup_printf("%d", set->show_thumbs);
3199 xmlNewChild(sub, NULL, "ShowThumbs", tmp);
3200 g_free(tmp);
3202 if(set->flags & SET_FILTER) {
3203 tmp=g_strdup_printf("%d", set->filter_type);
3204 xmlNewChild(sub, NULL, "FilterType", tmp);
3205 g_free(tmp);
3206 if(set->filter && set->filter[0])
3207 xmlNewChild(sub, NULL, "Filter", set->filter);
3208 tmp=g_strdup_printf("%d", set->filter_directories);
3209 xmlNewChild(sub, NULL, "FilterDirectories", tmp);
3213 static void save_settings(void)
3215 gchar *path;
3217 path=choices_find_xdg_path_save("Settings.xml", PROJECT, SITE, TRUE);
3218 if(path) {
3219 xmlDocPtr doc = xmlNewDoc("1.0");
3220 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL,
3221 "Settings", NULL));
3223 g_hash_table_foreach(settings_table, add_nodes,
3224 xmlDocGetRootElement(doc));
3226 save_xml_file(doc, path);
3228 g_free(path);
3229 xmlFreeDoc(doc);
3234 static void check_settings(FilerWindow *filer_window)
3236 Settings *set;
3238 set=(Settings *) g_hash_table_lookup(settings_table,
3239 filer_window->sym_path);
3241 if(set) {
3242 if(set->flags & SET_POSITION)
3243 gtk_window_move(GTK_WINDOW(filer_window->window),
3244 set->x, set->y);
3245 if(set->flags & SET_SIZE)
3246 filer_window_set_size(filer_window, set->width,
3247 set->height);
3248 if(set->flags & SET_HIDDEN)
3249 filer_set_hidden(filer_window, set->show_hidden);
3251 if(set->flags & (SET_STYLE|SET_DETAILS)) {
3252 DisplayStyle style=filer_window->display_style;
3253 DetailsType details=filer_window->details_type;
3255 if(set->flags & SET_STYLE)
3256 style=set->display_style;
3258 if(set->flags & SET_DETAILS) {
3259 details=set->details_type;
3261 filer_set_view_type(filer_window,
3262 set->view_type);
3265 display_set_layout(filer_window, style,
3266 details, FALSE);
3269 if(set->flags & SET_SORT)
3270 display_set_sort_type(filer_window,
3271 set->sort_type,
3272 set->sort_order);
3274 if(set->flags & SET_THUMBS)
3275 display_set_thumbs(filer_window,
3276 set->show_thumbs);
3278 if(set->flags & SET_FILTER)
3280 display_set_filter(filer_window,
3281 set->filter_type,
3282 set->filter);
3283 display_set_filter_directories(filer_window,
3284 set->filter_directories);
3290 typedef struct settings_window {
3291 GtkWidget *window;
3293 GtkWidget *pos, *size, *hidden, *style, *sort, *details,
3294 *thumbs, *filter;
3296 Settings *set;
3297 } SettingsWindow;
3300 static void settings_response(GtkWidget *window, gint response,
3301 SettingsWindow *set_win)
3303 if(response==GTK_RESPONSE_OK) {
3304 gint flags=0;
3306 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->pos)))
3307 flags|=SET_POSITION;
3308 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->size)))
3309 flags|=SET_SIZE;
3310 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->hidden)))
3311 flags|=SET_HIDDEN;
3312 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->style)))
3313 flags|=SET_STYLE;
3314 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->sort)))
3315 flags|=SET_SORT;
3316 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->details)))
3317 flags|=SET_DETAILS;
3318 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->thumbs)))
3319 flags|=SET_THUMBS;
3320 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->filter)))
3321 flags|=SET_FILTER;
3323 set_win->set->flags=flags;
3324 store_settings(set_win->set);
3325 save_settings();
3328 gtk_widget_destroy(window);
3331 void filer_save_settings(FilerWindow *fwin)
3333 SettingsWindow *set_win;
3334 GtkWidget *vbox;
3335 GtkWidget *path;
3336 gint x, y;
3338 Settings *set=settings_new(fwin->sym_path);
3340 gtk_window_get_position(GTK_WINDOW(fwin->window),&x, &y);
3341 set->flags|=SET_POSITION;
3342 set->x=x;
3343 set->y=y;
3345 gtk_window_get_size(GTK_WINDOW(fwin->window),&x, &y);
3346 set->flags|=SET_SIZE;
3347 set->width=x;
3348 set->height=y;
3350 set->flags|=SET_HIDDEN;
3351 set->show_hidden=fwin->show_hidden;
3353 set->flags|=SET_STYLE;
3354 set->display_style=fwin->display_style;
3356 set->flags|=SET_SORT;
3357 set->sort_type=fwin->sort_type;
3358 set->sort_order=fwin->sort_order;
3360 set->flags|=SET_DETAILS;
3361 set->view_type=fwin->view_type;
3362 set->details_type=fwin->details_type;
3364 set->flags|=SET_THUMBS;
3365 set->show_thumbs=fwin->show_thumbs;
3367 set->flags|=SET_FILTER;
3368 set->filter_type=fwin->filter;
3369 if(fwin->filter_string)
3370 set->filter=g_strdup(fwin->filter_string);
3371 set->filter_directories=fwin->filter_directories;
3373 /* Store other parameters
3375 set_win=g_new(SettingsWindow, 1);
3377 set_win->window=gtk_dialog_new();
3378 number_of_windows++;
3380 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3381 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
3382 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3383 GTK_STOCK_OK, GTK_RESPONSE_OK);
3385 g_signal_connect(set_win->window, "destroy",
3386 G_CALLBACK(one_less_window), NULL);
3387 g_signal_connect_swapped(set_win->window, "destroy",
3388 G_CALLBACK(g_free), set_win);
3390 gtk_window_set_title(GTK_WINDOW(set_win->window),
3391 _("Select display properties to save"));
3393 vbox=GTK_DIALOG(set_win->window)->vbox;
3395 path=gtk_label_new(set->path);
3396 gtk_box_pack_start(GTK_BOX(vbox), path, FALSE, FALSE, 2);
3398 set_win->pos=gtk_check_button_new_with_label(_("Position"));
3399 gtk_box_pack_start(GTK_BOX(vbox), set_win->pos, FALSE, FALSE, 2);
3400 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->pos),
3401 set->flags & SET_POSITION);
3403 set_win->size=gtk_check_button_new_with_label(_("Size"));
3404 gtk_box_pack_start(GTK_BOX(vbox), set_win->size, FALSE, FALSE, 2);
3405 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->size),
3406 set->flags & SET_SIZE);
3408 set_win->hidden=gtk_check_button_new_with_label(_("Show hidden"));
3409 gtk_box_pack_start(GTK_BOX(vbox), set_win->hidden,
3410 FALSE, FALSE, 2);
3411 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->hidden),
3412 set->flags & SET_HIDDEN);
3414 set_win->style=gtk_check_button_new_with_label(_("Display style"));
3415 gtk_box_pack_start(GTK_BOX(vbox), set_win->style,
3416 FALSE, FALSE, 2);
3417 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->style),
3418 set->flags & SET_STYLE);
3420 set_win->sort=gtk_check_button_new_with_label(_("Sort type and order"));
3421 gtk_box_pack_start(GTK_BOX(vbox), set_win->sort, FALSE, FALSE, 2);
3422 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->sort),
3423 set->flags & SET_SORT);
3425 set_win->details=gtk_check_button_new_with_label(_("Details"));
3426 gtk_box_pack_start(GTK_BOX(vbox), set_win->details, FALSE, FALSE, 2);
3427 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->details),
3428 set->flags & SET_DETAILS);
3430 set_win->thumbs=gtk_check_button_new_with_label(_("Thumbnails"));
3431 gtk_box_pack_start(GTK_BOX(vbox), set_win->thumbs,
3432 FALSE, FALSE, 2);
3433 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->thumbs),
3434 set->flags & SET_THUMBS);
3436 set_win->filter=gtk_check_button_new_with_label(_("Filter"));
3437 gtk_box_pack_start(GTK_BOX(vbox), set_win->filter,
3438 FALSE, FALSE, 2);
3439 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->filter),
3440 set->flags & SET_FILTER);
3442 set_win->set=set;
3443 g_signal_connect(set_win->window, "response",
3444 G_CALLBACK(settings_response), set_win);
3446 gtk_widget_show_all(set_win->window);
3449 static char *tip_from_desktop_file(const char *full_path)
3451 GError *error = NULL;
3452 char *comment = NULL;
3454 comment = get_value_from_desktop_file(full_path,
3455 "Desktop Entry", "Comment", &error);
3456 if (error)
3458 delayed_error("Failed to parse .desktop file '%s':\n%s",
3459 full_path, error->message);
3460 goto err;
3463 err:
3464 if (error != NULL)
3465 g_error_free(error);
3467 return comment;