Set sympath to a Unix path, not a URI
[rox-filer.git] / ROX-Filer / src / filer.c
blob77043c02f274d0acdd68196cad70f15c840e6007
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 #define ROX_RESPONSE_EJECT 99 /**< User clicked on Eject button */
176 void filer_init(void)
178 const gchar *ohost;
179 const gchar *dpy;
180 gchar *dpyhost, *tmp;
182 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
183 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
184 RESIZE_ALWAYS);
185 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
187 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
189 option_add_int(&o_filer_view_type, "filer_view_type",
190 VIEW_TYPE_COLLECTION);
192 option_add_notify(filer_options_changed);
194 busy_cursor = gdk_cursor_new(GDK_WATCH);
195 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
197 window_with_id = g_hash_table_new_full(g_str_hash, g_str_equal,
198 NULL, NULL);
200 /* Is the display on the local machine, or are we being
201 * run remotely? See filer_set_title().
203 ohost = our_host_name();
204 dpy = gdk_get_display();
205 dpyhost = g_strdup(dpy);
206 tmp = strchr(dpyhost, ':');
207 if (tmp)
208 *tmp = '\0';
210 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
212 /* Try the cannonical name for dpyhost (see our_host_name()
213 * in support.c).
215 struct hostent *ent;
217 ent = gethostbyname(dpyhost);
218 if (!ent || strcmp(ohost, ent->h_name) != 0)
219 not_local = TRUE;
222 g_free(dpyhost);
224 load_settings();
227 static gboolean if_deleted(gpointer item, gpointer removed)
229 int i = ((GPtrArray *) removed)->len;
230 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
231 char *leafname = ((DirItem *) item)->leafname;
233 while (i--)
235 if (strcmp(leafname, r[i]->leafname) == 0)
236 return TRUE;
239 return FALSE;
242 #define DECOR_BORDER 32
244 /* Resize the filer window to w x h pixels, plus border (not clamped).
245 * If triggered by a key event, warp the pointer (for SloppyFocus users).
247 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
249 GtkWidget *window;
251 g_return_if_fail(filer_window != NULL);
253 if (filer_window->scrollbar)
254 w += filer_window->scrollbar->allocation.width;
256 if (o_toolbar.int_value != TOOLBAR_NONE)
257 h += filer_window->toolbar->allocation.height;
258 if (filer_window->message)
259 h += filer_window->message->allocation.height;
261 window = filer_window->window;
263 if (GTK_WIDGET_VISIBLE(window))
265 gint x, y, m;
266 GtkRequisition *req = &window->requisition;
267 GdkWindow *gdk_window = window->window;
268 GdkEvent *event;
270 w = MAX(req->width, w);
271 h = MAX(req->height, h);
272 gdk_window_get_pointer(NULL, &x, &y, NULL);
273 m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(),
274 x, y);
275 gdk_window_get_position(gdk_window, &x, &y);
277 if (x + w + DECOR_BORDER >
278 monitor_geom[m].x + monitor_geom[m].width ||
279 y + h + DECOR_BORDER >
280 monitor_geom[m].y + monitor_geom[m].height)
282 if (x + w + DECOR_BORDER >
283 monitor_geom[m].x + monitor_geom[m].width)
285 x = monitor_geom[m].x + monitor_geom[m].width -
286 w - 4 - DECOR_BORDER;
288 if (y + h + DECOR_BORDER >
289 monitor_geom[m].y + monitor_geom[m].height)
291 y = monitor_geom[m].y + monitor_geom[m].height -
292 h - 4 - DECOR_BORDER;
294 gdk_window_move_resize(gdk_window, x, y, w, h);
296 else
297 gdk_window_resize(gdk_window, w, h);
299 /* If the resize was triggered by a key press, keep
300 * the pointer inside the window so that it doesn't
301 * lose focus when using pointer-follows-mouse.
303 event = gtk_get_current_event();
304 if (event && event->type == GDK_KEY_PRESS)
306 int x, y;
307 int nx, ny;
309 GdkWindow *win = filer_window->window->window;
311 gdk_window_get_pointer(filer_window->window->window,
312 &x, &y, NULL);
314 nx = CLAMP(x, 4, w - 4);
315 ny = CLAMP(y, 4, h - 4);
317 if (nx != x || ny != y)
319 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
320 None,
321 gdk_x11_drawable_get_xid(win),
322 0, 0, 0, 0,
323 nx, ny);
326 if (event)
327 gdk_event_free(event);
329 else
330 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
333 /* Called on a timeout while scanning or when scanning ends
334 * (whichever happens first).
336 static gint open_filer_window(FilerWindow *filer_window)
338 Settings *dir_settings;
339 gboolean force_resize;
341 dir_settings = (Settings *) g_hash_table_lookup(settings_table, filer_window->uri);
343 force_resize = !(o_filer_auto_resize.int_value == RESIZE_NEVER &&
344 dir_settings && dir_settings->flags & SET_POSITION);
346 view_style_changed(filer_window->view, 0);
348 if (filer_window->open_timeout)
350 g_source_remove(filer_window->open_timeout);
351 filer_window->open_timeout = 0;
354 if (!GTK_WIDGET_VISIBLE(filer_window->window))
356 display_set_actual_size(filer_window, force_resize);
357 gtk_widget_show(filer_window->window);
360 return FALSE;
363 /* Look through all items we want to display, and queue a recheck on any
364 * that require it.
366 static void queue_interesting(FilerWindow *filer_window)
368 DirItem *item;
369 ViewIter iter;
371 view_get_iter(filer_window->view, &iter, 0);
372 while ((item = iter.next(&iter)))
374 if (item->flags & ITEM_FLAG_NEED_RESCAN_QUEUE)
375 dir_queue_recheck(filer_window->directory, item);
379 static void update_display(Directory *dir,
380 DirAction action,
381 GPtrArray *items,
382 FilerWindow *filer_window)
384 ViewIface *view = (ViewIface *) filer_window->view;
386 switch (action)
388 case DIR_ADD:
389 view_add_items(view, items);
390 /* Open and resize if currently hidden */
391 open_filer_window(filer_window);
392 break;
393 case DIR_REMOVE:
394 view_delete_if(view, if_deleted, items);
395 toolbar_update_info(filer_window);
396 break;
397 case DIR_START_SCAN:
398 set_scanning_display(filer_window, TRUE);
399 toolbar_update_info(filer_window);
400 break;
401 case DIR_END_SCAN:
402 if (filer_window->window->window)
403 gdk_window_set_cursor(
404 filer_window->window->window,
405 NULL);
406 set_scanning_display(filer_window, FALSE);
407 toolbar_update_info(filer_window);
408 open_filer_window(filer_window);
410 if (filer_window->had_cursor &&
411 !view_cursor_visible(view))
413 ViewIter start;
414 view_get_iter(view, &start, 0);
415 if (start.next(&start))
416 view_cursor_to_iter(view, &start);
417 view_show_cursor(view);
418 filer_window->had_cursor = FALSE;
420 if (filer_window->auto_select)
421 display_set_autoselect(filer_window,
422 filer_window->auto_select);
423 null_g_free(&filer_window->auto_select);
425 filer_create_thumbs(filer_window);
427 if (filer_window->thumb_queue)
428 start_thumb_scanning(filer_window);
429 break;
430 case DIR_UPDATE:
431 view_update_items(view, items);
432 break;
433 case DIR_ERROR_CHANGED:
434 filer_set_title(filer_window);
435 break;
436 case DIR_QUEUE_INTERESTING:
437 queue_interesting(filer_window);
438 break;
442 static void attach(FilerWindow *filer_window)
444 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
445 view_clear(filer_window->view);
446 filer_window->scanning = TRUE;
447 dir_attach(filer_window->directory, (DirCallback) update_display,
448 filer_window);
449 filer_set_title(filer_window);
450 bookmarks_add_history(filer_window->uri);
452 if (filer_window->directory->error)
454 if (spring_in_progress)
455 g_printerr(_("Error scanning '%s':\n%s\n"),
456 filer_window->sym_path,
457 filer_window->directory->error);
458 else
459 delayed_error(_("Error scanning '%s':\n%s"),
460 filer_window->sym_path,
461 filer_window->directory->error);
465 static void detach(FilerWindow *filer_window)
467 g_return_if_fail(filer_window->directory != NULL);
469 dir_detach(filer_window->directory,
470 (DirCallback) update_display, filer_window);
471 g_object_unref(filer_window->directory);
472 filer_window->directory = NULL;
475 /* If 'start' was mounted by ROX-Filer, return it. Otherwise, try the
476 * parents up the tree.
477 * NULL if we're not in a user mount point.
478 * g_free() the result.
480 static char *get_ancestor_user_mount_point(const char *start)
482 char *path;
484 path = strdup(start);
486 while (1)
488 char *slash;
490 if (mount_is_user_mounted(path))
491 return path;
493 if (!path[1])
495 g_free(path);
496 return NULL;
499 slash = strrchr(path + 1, '/');
500 if (!slash)
501 slash = path + 1;
502 *slash = '\0';
506 static void umount_dialog_response(GtkWidget *dialog, int response, char *mount)
508 GList *list;
510 switch (response)
512 case GTK_RESPONSE_OK:
513 list = g_list_prepend(NULL, mount);
514 action_mount(list, FALSE, FALSE, TRUE);
515 g_list_free(list);
516 break;
518 case ROX_RESPONSE_EJECT:
519 list = g_list_prepend(NULL, mount);
520 action_eject(list);
521 g_list_free(list);
522 break;
524 default:
525 break;
528 g_free(mount);
530 gtk_widget_destroy(dialog);
532 one_less_window();
535 /* 'filer_window' shows a directory under 'mount'. If no other window also
536 * shows a directory under it, display a non-modal dialog offering to
537 * unmount the directory.
538 * 'mount' is freed by this function, either directly, or after the dialog
539 * closes.
541 static void may_offer_unmount(FilerWindow *filer_window, char *mount)
543 GtkWidget *dialog, *button;
544 GList *next;
545 int len;
547 len = strlen(mount);
549 for (next = all_filer_windows; next; next = next->next)
551 FilerWindow *other = (FilerWindow *) next->data;
553 if (other == filer_window)
554 continue;
556 if (strncmp(filer_window->real_path, other->real_path,
557 len) != 0)
558 continue;
560 g_return_if_fail(
561 filer_window->real_path[len] != '/' ||
562 filer_window->real_path[len] != '\0');
564 if (other->real_path[len] != '/' &&
565 other->real_path[len] != '\0')
566 continue;
568 /* Found another window. Don't offer to unmount. */
569 g_free(mount);
570 return;
573 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
574 GTK_BUTTONS_NONE,
575 _("Do you want to unmount this device?\n\n"
576 "Unmounting a device makes it safe to remove "
577 "the disk."));
579 button = button_new_mixed(ROX_STOCK_MOUNTED, _("No change"));
580 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
581 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
582 GTK_RESPONSE_CANCEL);
583 gtk_widget_show(button);
585 button = button_new_mixed(ROX_STOCK_MOUNT, _("Unmount"));
586 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
587 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
588 GTK_RESPONSE_OK);
589 gtk_widget_show(button);
591 /* We need a better icon, but I can't draw */
592 button = button_new_mixed(GTK_STOCK_UNDO, _("Eject"));
593 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
594 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
595 ROX_RESPONSE_EJECT);
596 gtk_widget_show(button);
598 g_signal_connect(G_OBJECT(dialog), "response",
599 G_CALLBACK(umount_dialog_response), mount);
601 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
602 GTK_RESPONSE_OK);
604 number_of_windows++;
605 gtk_widget_show(dialog);
608 /* Returns TRUE to prevent closing the window. May offer to unmount a
609 * device.
611 gboolean filer_window_delete(GtkWidget *window,
612 GdkEvent *unused, /* (may be NULL) */
613 FilerWindow *filer_window)
615 char *mount = NULL;
617 if (filer_window->real_path)
618 mount = get_ancestor_user_mount_point(filer_window->real_path);
620 if (mount)
621 may_offer_unmount(filer_window, mount);
623 return FALSE;
626 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
628 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
630 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
632 if (window_with_primary == filer_window)
633 window_with_primary = NULL;
635 if (window_with_focus == filer_window)
637 menu_popdown();
638 window_with_focus = NULL;
641 if (filer_window->directory)
642 detach(filer_window);
644 if (filer_window->open_timeout)
646 g_source_remove(filer_window->open_timeout);
647 filer_window->open_timeout = 0;
650 if (filer_window->auto_scroll != -1)
652 g_source_remove(filer_window->auto_scroll);
653 filer_window->auto_scroll = -1;
656 if (filer_window->thumb_queue)
657 destroy_glist(&filer_window->thumb_queue);
659 tooltip_show(NULL);
661 filer_set_id(filer_window, NULL);
663 if(filer_window->filter_string)
664 g_free(filer_window->filter_string);
665 if(filer_window->regexp)
666 g_free(filer_window->regexp);
668 g_free(filer_window->uri);
669 g_free(filer_window->auto_select);
670 g_free(filer_window->real_path);
671 g_free(filer_window->sym_path);
672 g_free(filer_window);
674 one_less_window();
677 /* Returns TRUE iff the directory still exists. */
678 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
680 Directory *dir;
682 g_return_val_if_fail(filer_window != NULL, FALSE);
684 /* We do a fresh lookup (rather than update) because the inode may
685 * have changed.
687 dir = dir_lookup(filer_window->uri);
688 if (!dir)
690 if (warning)
691 info_message(_("Directory missing/deleted"));
692 gtk_widget_destroy(filer_window->window);
693 return FALSE;
695 if (dir == filer_window->directory)
696 g_object_unref(dir);
697 else
699 detach(filer_window);
700 filer_window->directory = dir;
701 attach(filer_window);
704 return TRUE;
707 /* No items are now selected. This might be because another app claimed
708 * the selection or because the user unselected all the items.
710 void filer_lost_selection(FilerWindow *filer_window, guint time)
712 if (window_with_primary == filer_window)
714 window_with_primary = NULL;
715 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
719 /* Another app has claimed the primary selection */
720 static void filer_lost_primary(GtkWidget *window,
721 GdkEventSelection *event,
722 gpointer user_data)
724 FilerWindow *filer_window = (FilerWindow *) user_data;
726 if (window_with_primary && window_with_primary == filer_window)
728 window_with_primary = NULL;
729 set_selection_state(filer_window, FALSE);
733 /* Someone wants us to send them the selection */
734 static void selection_get(GtkWidget *widget,
735 GtkSelectionData *selection_data,
736 guint info,
737 guint time,
738 gpointer data)
740 GString *reply, *header;
741 FilerWindow *filer_window = (FilerWindow *) data;
742 ViewIter iter;
743 DirItem *item;
745 reply = g_string_new(NULL);
746 header = g_string_new(NULL);
748 switch (info)
750 case TARGET_STRING:
751 g_string_printf(header, " %s",
752 make_path(filer_window->sym_path, ""));
753 break;
754 case TARGET_URI_LIST:
755 g_string_printf(header, " file://%s%s",
756 our_host_name_for_dnd(),
757 make_path(filer_window->sym_path, ""));
758 break;
761 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
763 while ((item = iter.next(&iter)))
765 g_string_append(reply, header->str);
766 g_string_append(reply, item->leafname);
769 if (reply->len > 0)
770 gtk_selection_data_set_text(selection_data,
771 reply->str + 1, reply->len - 1);
772 else
774 g_warning("Attempt to paste empty selection!");
775 gtk_selection_data_set_text(selection_data, "", 0);
778 g_string_free(reply, TRUE);
779 g_string_free(header, TRUE);
782 /* Selection has been changed -- try to grab the primary selection
783 * if we don't have it. Also called when clicking on an insensitive selection
784 * to regain primary.
785 * Also updates toolbar info.
787 void filer_selection_changed(FilerWindow *filer_window, gint time)
789 g_return_if_fail(filer_window != NULL);
791 toolbar_update_info(filer_window);
793 if (window_with_primary == filer_window)
794 return; /* Already got primary */
796 if (!view_count_selected(filer_window->view))
797 return; /* Nothing selected */
799 if (filer_window->temp_item_selected == FALSE &&
800 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
801 GDK_SELECTION_PRIMARY,
802 time))
804 window_with_primary = filer_window;
805 set_selection_state(filer_window, TRUE);
807 else
808 set_selection_state(filer_window, FALSE);
811 /* Open the item (or add it to the shell command minibuffer) */
812 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
814 gboolean shift = (flags & OPEN_SHIFT) != 0;
815 gboolean close_mini = flags & OPEN_FROM_MINI;
816 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
817 DirItem *item;
818 gboolean wink = TRUE;
819 Directory *old_dir;
820 GFile *child;
821 gboolean success;
823 g_return_if_fail(filer_window != NULL && iter != NULL);
825 item = iter->peek(iter);
827 g_return_if_fail(item != NULL);
829 if (filer_window->mini_type == MINI_SHELL)
831 minibuffer_add(filer_window, item->leafname);
832 return;
835 if (item->base_type == TYPE_UNKNOWN)
836 dir_update_item(filer_window->directory, item->leafname);
838 if (item->base_type == TYPE_DIRECTORY)
840 /* Never close a filer window when opening a directory
841 * (click on a dir or click on an app with shift).
843 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
844 close_window = FALSE;
847 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
848 wink = FALSE;
850 old_dir = filer_window->directory;
852 child = g_file_get_child(filer_window->gfile, item->leafname);
853 success = run_diritem_gfile(child, item,
854 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
855 filer_window,
856 shift);
857 g_object_unref(child);
859 if (success)
861 if (old_dir != filer_window->directory)
862 return;
864 if (close_window)
865 gtk_widget_destroy(filer_window->window);
866 else
868 if (wink)
869 view_wink_item(filer_window->view, iter);
870 if (close_mini)
871 minibuffer_hide(filer_window);
876 static gint pointer_in(GtkWidget *widget,
877 GdkEventCrossing *event,
878 FilerWindow *filer_window)
880 may_rescan(filer_window, TRUE);
881 return FALSE;
884 static gint pointer_out(GtkWidget *widget,
885 GdkEventCrossing *event,
886 FilerWindow *filer_window)
888 tooltip_show(NULL);
889 return FALSE;
892 /* Move the cursor to the next selected item in direction 'dir'
893 * (+1 or -1).
895 void filer_next_selected(FilerWindow *filer_window, int dir)
897 ViewIter iter, cursor;
898 gboolean have_cursor;
899 ViewIface *view = filer_window->view;
901 g_return_if_fail(dir == 1 || dir == -1);
903 view_get_cursor(view, &cursor);
904 have_cursor = cursor.peek(&cursor) != NULL;
906 view_get_iter(view, &iter,
907 VIEW_ITER_SELECTED |
908 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
909 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
911 if (have_cursor && view_get_selected(view, &cursor))
912 iter.next(&iter); /* Skip the cursor itself */
914 if (iter.next(&iter))
915 view_cursor_to_iter(view, &iter);
916 else
917 gdk_beep();
919 return;
922 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
924 TargetFunc cb = filer_window->target_cb;
925 gpointer data = filer_window->target_data;
926 OpenFlags flags = 0;
927 ViewIter iter;
929 filer_target_mode(filer_window, NULL, NULL, NULL);
931 view_get_cursor(filer_window->view, &iter);
932 if (!iter.peek(&iter))
933 return;
935 if (cb)
937 cb(filer_window, &iter, data);
938 return;
941 if (event->state & GDK_SHIFT_MASK)
942 flags |= OPEN_SHIFT;
943 if (event->state & GDK_MOD1_MASK)
944 flags |= OPEN_CLOSE_WINDOW;
945 else
946 flags |= OPEN_SAME_WINDOW;
948 filer_openitem(filer_window, &iter, flags);
951 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
952 * changed. If no groups were loaded and there is no file then initialised
953 * groups to an empty document.
954 * Return the node for the 'name' group.
956 static xmlNode *group_find(char *name)
958 xmlNode *node;
959 gchar *path;
961 /* Update the groups, if possible */
962 path = choices_find_xdg_path_load("Groups.xml", PROJECT, SITE);
963 if (path)
965 XMLwrapper *wrapper;
966 wrapper = xml_cache_load(path);
967 if (wrapper)
969 if (groups)
970 g_object_unref(groups);
971 groups = wrapper;
974 g_free(path);
977 if (!groups)
979 groups = xml_new(NULL);
980 groups->doc = xmlNewDoc("1.0");
982 xmlDocSetRootElement(groups->doc,
983 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
984 return NULL;
987 node = xmlDocGetRootElement(groups->doc);
989 for (node = node->xmlChildrenNode; node; node = node->next)
991 guchar *gid;
993 gid = xmlGetProp(node, "name");
995 if (!gid)
996 continue;
998 if (strcmp(name, gid) != 0)
999 continue;
1001 g_free(gid);
1003 return node;
1006 return NULL;
1009 static void group_save(FilerWindow *filer_window, char *name)
1011 xmlNode *group;
1012 guchar *save_path;
1013 DirItem *item;
1014 ViewIter iter;
1016 group = group_find(name);
1017 if (group)
1019 xmlUnlinkNode(group);
1020 xmlFreeNode(group);
1022 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
1023 NULL, "group", NULL);
1024 xmlSetProp(group, "name", name);
1026 xmlNewTextChild(group, NULL, "directory", filer_window->sym_path);
1028 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1030 while ((item = iter.next(&iter)))
1031 xmlNewTextChild(group, NULL, "item", item->leafname);
1033 save_path = choices_find_xdg_path_save("Groups.xml", PROJECT, SITE,
1034 TRUE);
1035 if (save_path)
1037 save_xml_file(groups->doc, save_path);
1038 g_free(save_path);
1042 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
1044 GHashTable *in_group = (GHashTable *) data;
1046 return g_hash_table_lookup(in_group,
1047 iter->peek(iter)->leafname) != NULL;
1050 static void group_restore(FilerWindow *filer_window, char *name)
1052 GHashTable *in_group;
1053 char *path;
1054 xmlNode *group, *node;
1056 group = group_find(name);
1058 if (!group)
1060 report_error(_("Group %s is not set. Select some files "
1061 "and press Ctrl+%s to set the group. Press %s "
1062 "on its own to reselect the files later.\n"
1063 "Make sure NumLock is on if you use the keypad."),
1064 name, name, name);
1065 return;
1068 node = get_subnode(group, NULL, "directory");
1069 g_return_if_fail(node != NULL);
1070 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
1071 g_return_if_fail(path != NULL);
1073 if (strcmp(path, filer_window->sym_path) != 0)
1074 filer_change_to(filer_window, path, NULL);
1075 g_free(path);
1077 in_group = g_hash_table_new(g_str_hash, g_str_equal);
1078 for (node = group->xmlChildrenNode; node; node = node->next)
1080 gchar *leaf;
1081 if (node->type != XML_ELEMENT_NODE)
1082 continue;
1083 if (strcmp(node->name, "item") != 0)
1084 continue;
1086 leaf = xmlNodeListGetString(groups->doc,
1087 node->xmlChildrenNode, 1);
1088 if (!leaf)
1089 g_warning("Missing leafname!\n");
1090 else
1091 g_hash_table_insert(in_group, leaf, filer_window);
1094 view_select_if(filer_window->view, &group_restore_cb, in_group);
1096 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1097 g_hash_table_destroy(in_group);
1100 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
1102 ViewIter iter;
1103 GdkEvent *event;
1105 view_get_cursor(filer_window->view, &iter);
1107 event = gtk_get_current_event();
1108 show_filer_menu(filer_window, event, &iter);
1109 if (event)
1110 gdk_event_free(event);
1112 return TRUE;
1115 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
1117 ViewIface *view = filer_window->view;
1118 ViewIter iter;
1120 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
1121 if (!iter.next(&iter))
1122 return; /* No cursor */
1124 if (view_get_selected(view, &iter))
1125 view_set_selected(view, &iter, FALSE);
1126 else
1127 view_set_selected(view, &iter, TRUE);
1129 if (iter.next(&iter))
1130 view_cursor_to_iter(view, &iter);
1133 gint filer_key_press_event(GtkWidget *widget,
1134 GdkEventKey *event,
1135 FilerWindow *filer_window)
1137 ViewIface *view = filer_window->view;
1138 ViewIter cursor;
1139 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
1140 guint key = event->keyval;
1141 char group[2] = "1";
1143 window_with_focus = filer_window;
1145 /* Delay setting up the keys until now to speed loading... */
1146 if (ensure_filer_menu())
1148 /* Gtk updates in an idle-handler, so force a recheck now */
1149 g_signal_emit_by_name(widget, "keys_changed");
1152 if (focus && focus == filer_window->minibuffer)
1153 if (gtk_widget_event(focus, (GdkEvent *) event))
1154 return TRUE; /* Handled */
1156 if (!focus)
1157 gtk_widget_grab_focus(GTK_WIDGET(view));
1159 view_get_cursor(view, &cursor);
1160 if (!cursor.peek(&cursor) && (key == GDK_Up || key == GDK_Down))
1162 ViewIter iter;
1163 view_get_iter(view, &iter, 0);
1164 if (iter.next(&iter))
1165 view_cursor_to_iter(view, &iter);
1166 gtk_widget_grab_focus(GTK_WIDGET(view)); /* Needed? */
1167 return TRUE;
1170 switch (key)
1172 case GDK_Escape:
1173 filer_target_mode(filer_window, NULL, NULL, NULL);
1174 view_cursor_to_iter(filer_window->view, NULL);
1175 view_clear_selection(filer_window->view);
1176 return FALSE;
1177 case GDK_Return:
1178 return_pressed(filer_window, event);
1179 break;
1180 case GDK_ISO_Left_Tab:
1181 filer_next_selected(filer_window, -1);
1182 break;
1183 case GDK_Tab:
1184 filer_next_selected(filer_window, 1);
1185 break;
1186 case GDK_BackSpace:
1187 change_to_parent(filer_window);
1188 break;
1189 case GDK_backslash:
1191 ViewIter iter;
1193 tooltip_show(NULL);
1195 view_get_cursor(filer_window->view, &iter);
1196 show_filer_menu(filer_window,
1197 (GdkEvent *) event, &iter);
1198 break;
1200 case ' ':
1201 filer_window_toggle_cursor_item_selected(filer_window);
1202 break;
1203 default:
1204 if (key >= GDK_0 && key <= GDK_9)
1205 group[0] = key - GDK_0 + '0';
1206 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1207 group[0] = key - GDK_KP_0 + '0';
1208 else
1210 if (focus && focus != widget &&
1211 gtk_widget_get_toplevel(focus) == widget)
1212 if (gtk_widget_event(focus,
1213 (GdkEvent *) event))
1214 return TRUE; /* Handled */
1215 return FALSE;
1218 if (event->state & GDK_CONTROL_MASK)
1219 group_save(filer_window, group);
1220 else
1221 group_restore(filer_window, group);
1224 return TRUE;
1227 void filer_open_parent(FilerWindow *filer_window)
1229 char *dir;
1230 const char *current = filer_window->sym_path;
1232 if (current[0] == '/' && current[1] == '\0')
1233 return; /* Already in the root */
1235 dir = g_path_get_dirname(current);
1236 filer_opendir(dir, filer_window, NULL);
1237 g_free(dir);
1240 void change_to_parent(FilerWindow *filer_window)
1242 GFile *parent;
1244 parent = g_file_get_parent(filer_window->gfile);
1246 if (!parent)
1247 return; /* Already in the root */
1249 // TODO
1250 if (mount_is_user_mounted(filer_window->uri))
1251 may_offer_unmount(filer_window,
1252 g_strdup(filer_window->uri));
1254 filer_change_to_gfile(filer_window, parent, g_basename(filer_window->uri));
1255 g_object_unref(parent);
1258 /* Removes trailing /s from path (modified in place) */
1259 static void tidy_sympath(gchar *path)
1261 int l;
1263 g_return_if_fail(path != NULL);
1265 l = strlen(path);
1266 while (l > 1 && path[l - 1] == '/')
1268 l--;
1269 path[l] = '\0';
1273 /* Make filer_window display path. When finished, highlight item 'from', or
1274 * the first item if from is NULL. If there is currently no cursor then
1275 * simply wink 'from' (if not NULL).
1276 * If the cause was a key event and we resize, warp the pointer.
1278 void filer_change_to(FilerWindow *filer_window, const char *path, const char *from)
1280 g_return_if_fail(filer_window != NULL);
1281 g_return_if_fail(path != NULL);
1283 GFile *gfile;
1285 gfile = g_file_new_for_path(path);
1286 filer_change_to_gfile(filer_window, gfile, from);
1287 g_object_unref(gfile);
1290 void filer_change_to_gfile(FilerWindow *filer_window, GFile *path, const char *from)
1292 char *from_dup;
1293 char *sym_path, *real_path, *uri;
1294 Directory *new_dir;
1296 g_return_if_fail(filer_window != NULL);
1298 filer_cancel_thumbnails(filer_window);
1300 tooltip_show(NULL);
1302 uri = g_file_get_uri(path);
1303 sym_path = g_file_get_path(path);
1304 real_path = pathdup(sym_path);
1305 new_dir = dir_lookup(uri);
1307 if (!new_dir)
1309 delayed_error(_("Directory '%s' is not accessible"),
1310 sym_path);
1311 g_free(real_path);
1312 g_free(sym_path);
1313 g_free(uri);
1314 return;
1317 if (o_unique_filer_windows.int_value && !spring_in_progress)
1319 FilerWindow *fw;
1321 fw = find_filer_window(sym_path, filer_window);
1322 if (fw)
1323 gtk_widget_destroy(fw->window);
1326 from_dup = from && *from ? g_strdup(from) : NULL;
1328 detach(filer_window);
1329 g_free(filer_window->real_path);
1330 g_free(filer_window->sym_path);
1331 g_free(filer_window->uri);
1332 g_object_unref(filer_window->gfile);
1333 g_object_ref(path);
1334 filer_window->gfile = path;
1335 filer_window->uri = uri;
1336 filer_window->real_path = real_path; // XXX
1337 filer_window->sym_path = sym_path;
1338 tidy_sympath(filer_window->sym_path);
1340 filer_window->directory = new_dir;
1342 g_free(filer_window->auto_select);
1343 filer_window->auto_select = from_dup;
1345 filer_window->had_cursor = filer_window->had_cursor ||
1346 view_cursor_visible(filer_window->view);
1348 filer_set_title(filer_window);
1349 if (filer_window->window->window)
1350 gdk_window_set_role(filer_window->window->window,
1351 filer_window->sym_path);
1352 view_cursor_to_iter(filer_window->view, NULL);
1354 attach(filer_window);
1356 check_settings(filer_window);
1358 display_set_actual_size(filer_window, FALSE);
1360 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1361 view_autosize(filer_window->view);
1363 if (filer_window->mini_type == MINI_PATH)
1364 g_idle_add((GSourceFunc) minibuffer_show_cb, filer_window);
1367 /* Returns a list containing the full (sym) pathname of every selected item.
1368 * You must g_free() each item in the list.
1370 GList *filer_selected_items(FilerWindow *filer_window)
1372 GList *retval = NULL;
1373 guchar *dir = filer_window->sym_path;
1374 ViewIter iter;
1375 DirItem *item;
1377 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1378 while ((item = iter.next(&iter)))
1380 retval = g_list_prepend(retval,
1381 g_strdup(make_path(dir, item->leafname)));
1384 return g_list_reverse(retval);
1387 /* Return the single selected item. Error if nothing is selected. */
1388 DirItem *filer_selected_item(FilerWindow *filer_window)
1390 ViewIter iter;
1391 DirItem *item;
1393 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1395 item = iter.next(&iter);
1396 g_return_val_if_fail(item != NULL, NULL);
1397 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1399 return item;
1402 /* Creates and shows a new filer window.
1403 * If src_win != NULL then display options can be taken from that source window.
1404 * Returns the new filer window, or NULL on error.
1405 * Note: if unique windows is in use, may return an existing window.
1407 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win, const gchar *wm_class)
1409 GFile *gfile;
1410 FilerWindow *retval;
1412 gfile = g_file_new_for_path(path);
1413 retval = filer_opendir_gfile(gfile, src_win, wm_class);
1414 g_object_unref(gfile);
1416 return retval;
1419 FilerWindow *filer_opendir_gfile(GFile *gfile, FilerWindow *src_win, const gchar *wm_class)
1421 FilerWindow *filer_window;
1422 gchar *real_path;
1423 DisplayStyle dstyle;
1424 DetailsType dtype;
1425 SortType s_type;
1426 GtkSortType s_order;
1427 Settings *dir_settings = NULL;
1428 gboolean force_resize = TRUE;
1429 char *path;
1431 /* Get the real pathname of the directory and copy it */
1432 path = g_file_get_path(gfile); /// XXX: free
1433 real_path = path ? pathdup(path) : NULL;
1435 if (o_unique_filer_windows.int_value && !spring_in_progress)
1437 FilerWindow *same_dir_window;
1439 same_dir_window = find_filer_window(path, NULL);
1441 if (same_dir_window)
1443 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1444 return same_dir_window;
1448 g_object_ref(gfile);
1450 filer_window = g_new(FilerWindow, 1);
1451 filer_window->message = NULL;
1452 filer_window->minibuffer = NULL;
1453 filer_window->minibuffer_label = NULL;
1454 filer_window->minibuffer_area = NULL;
1455 filer_window->temp_show_hidden = FALSE;
1456 filer_window->gfile = gfile;
1457 filer_window->uri = g_file_get_uri(gfile);
1458 filer_window->sym_path = g_file_get_path(gfile);
1459 filer_window->real_path = real_path;
1460 filer_window->scanning = FALSE;
1461 filer_window->had_cursor = FALSE;
1462 filer_window->auto_select = NULL;
1463 filer_window->toolbar_text = NULL;
1464 filer_window->target_cb = NULL;
1465 filer_window->mini_type = MINI_NONE;
1466 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1467 filer_window->toolbar = NULL;
1468 filer_window->toplevel_vbox = NULL;
1469 filer_window->view_hbox = NULL;
1470 filer_window->view = NULL;
1471 filer_window->scrollbar = NULL;
1472 filer_window->auto_scroll = -1;
1473 filer_window->window_id = NULL;
1475 // TODO: tidy_sympath(filer_window->sym_path);
1477 /* Finds the entry for this directory in the dir cache, creating
1478 * a new one if needed. This does not cause a scan to start,
1479 * so if a new entry is created then it will be empty.
1481 filer_window->directory = dir_lookup(filer_window->uri);
1482 if (!filer_window->directory)
1484 delayed_error(_("Directory '%s' not found."), filer_window->uri);
1485 g_object_unref(filer_window->gfile);
1486 g_free(filer_window->uri);
1487 g_free(filer_window->real_path);
1488 g_free(filer_window->sym_path);
1489 g_free(filer_window);
1490 return NULL;
1493 filer_window->temp_item_selected = FALSE;
1494 filer_window->flags = (FilerFlags) 0;
1495 filer_window->details_type = DETAILS_TIMES;
1496 filer_window->display_style = UNKNOWN_STYLE;
1497 filer_window->display_style_wanted = UNKNOWN_STYLE;
1498 filer_window->thumb_queue = NULL;
1499 filer_window->max_thumbs = 0;
1500 filer_window->sort_type = -1;
1502 filer_window->filter = FILER_SHOW_ALL;
1503 filer_window->filter_string = NULL;
1504 filer_window->regexp = NULL;
1505 filer_window->filter_directories = FALSE;
1507 if (src_win && o_display_inherit_options.int_value)
1509 s_type = src_win->sort_type;
1510 s_order = src_win->sort_order;
1511 dstyle = src_win->display_style_wanted;
1512 dtype = src_win->details_type;
1513 filer_window->show_hidden = src_win->show_hidden;
1514 filer_window->show_thumbs = src_win->show_thumbs;
1515 filer_window->view_type = src_win->view_type;
1517 filer_window->filter_directories = src_win->filter_directories;
1518 filer_set_filter(filer_window, src_win->filter,
1519 src_win->filter_string);
1521 else
1523 s_type = o_display_sort_by.int_value;
1524 s_order = GTK_SORT_ASCENDING;
1525 dstyle = o_display_size.int_value;
1526 dtype = o_display_details.int_value;
1527 filer_window->show_hidden = o_display_show_hidden.int_value;
1528 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1529 filer_window->view_type = o_filer_view_type.int_value;
1532 dir_settings = (Settings *) g_hash_table_lookup(settings_table, filer_window->uri);
1533 if (dir_settings)
1535 /* Override the current defaults with the per-directory
1536 * user settings.
1538 if (dir_settings->flags & SET_HIDDEN)
1539 filer_window->show_hidden = dir_settings->show_hidden;
1541 if (dir_settings->flags & SET_STYLE)
1542 dstyle = dir_settings->display_style;
1544 if (dir_settings->flags & SET_DETAILS)
1546 dtype = dir_settings->details_type;
1547 filer_window->view_type = dir_settings->view_type;
1550 if (dir_settings->flags & SET_SORT)
1552 s_type = dir_settings->sort_type;
1553 s_order = dir_settings->sort_order;
1556 if (dir_settings->flags & SET_THUMBS)
1557 filer_window->show_thumbs = dir_settings->show_thumbs;
1559 if (dir_settings->flags & SET_FILTER)
1561 filer_set_filter(filer_window,
1562 dir_settings->filter_type,
1563 dir_settings->filter);
1564 filer_set_filter_directories(filer_window,
1565 dir_settings->filter_directories);
1569 /* Add all the user-interface elements & realise */
1570 filer_add_widgets(filer_window, wm_class);
1571 if (src_win)
1572 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1573 GTK_WIN_POS_MOUSE);
1575 if (dir_settings)
1577 if (dir_settings->flags & SET_POSITION)
1579 gtk_window_move(GTK_WINDOW(filer_window->window),
1580 dir_settings->x, dir_settings->y);
1582 if (dir_settings->flags & SET_SIZE)
1584 filer_window_set_size(filer_window,
1585 dir_settings->width,
1586 dir_settings->height);
1587 force_resize = o_filer_auto_resize.int_value != RESIZE_NEVER;
1591 /* Connect to all the signal handlers */
1592 filer_add_signals(filer_window);
1594 display_set_layout(filer_window, dstyle, dtype, force_resize);
1595 display_set_sort_type(filer_window, s_type, s_order);
1597 /* Open the window after a timeout, or when scanning stops.
1598 * Do this before attaching, because attach() might tell us to
1599 * stop scanning (if a scan isn't needed).
1601 filer_window->open_timeout = g_timeout_add(500,
1602 (GSourceFunc) open_filer_window,
1603 filer_window);
1605 /* The view is created empty and then attach() is called, which
1606 * links the filer window to the entry in the directory cache we
1607 * looked up / created above.
1609 * The attach() function will immediately callback to the filer window
1610 * to deliver a list of all known entries in the directory (so,
1611 * the number of items will be known after attach() returns).
1613 * If the directory was not in the cache (because it hadn't been
1614 * opened it before) then the types and icons for the entries are
1615 * not know, but the list of names is.
1618 attach(filer_window);
1620 number_of_windows++;
1621 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1623 return filer_window;
1626 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1628 GtkWidget *view = NULL;
1629 Directory *dir = NULL;
1630 GHashTable *selected = NULL;
1632 g_return_if_fail(filer_window != NULL);
1634 motion_window = NULL;
1636 if (filer_window->view)
1638 /* Save the current selection */
1639 ViewIter iter;
1640 DirItem *item;
1642 selected = g_hash_table_new(g_str_hash, g_str_equal);
1643 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1644 while ((item = iter.next(&iter)))
1645 g_hash_table_insert(selected, item->leafname, "yes");
1647 /* Destroy the old view */
1648 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1649 filer_window->view = NULL;
1651 dir = filer_window->directory;
1652 g_object_ref(dir);
1653 detach(filer_window);
1656 switch (type)
1658 case VIEW_TYPE_COLLECTION:
1659 view = view_collection_new(filer_window);
1660 break;
1661 case VIEW_TYPE_DETAILS:
1662 view = view_details_new(filer_window);
1663 break;
1666 g_return_if_fail(view != NULL);
1668 filer_window->view = VIEW(view);
1669 filer_window->view_type = type;
1671 gtk_box_pack_start(filer_window->view_hbox, view, TRUE, TRUE, 0);
1672 gtk_widget_show(view);
1674 /* Drag and drop events */
1675 make_drop_target(view, 0);
1676 g_signal_connect(view, "drag_motion",
1677 G_CALLBACK(drag_motion), filer_window);
1678 g_signal_connect(view, "drag_leave",
1679 G_CALLBACK(drag_leave), filer_window);
1680 g_signal_connect(view, "drag_end",
1681 G_CALLBACK(drag_end), filer_window);
1682 /* Dragging from us... */
1683 g_signal_connect(view, "drag_data_get",
1684 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1686 if (dir)
1688 /* Only when changing type. Otherwise, will attach later. */
1689 filer_window->directory = dir;
1690 attach(filer_window);
1692 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
1693 view_autosize(filer_window->view);
1696 if (selected)
1698 ViewIter iter;
1699 DirItem *item;
1701 view_get_iter(filer_window->view, &iter, 0);
1702 while ((item = iter.next(&iter)))
1704 if (g_hash_table_lookup(selected, item->leafname))
1705 view_set_selected(filer_window->view,
1706 &iter, TRUE);
1708 g_hash_table_destroy(selected);
1712 /* This adds all the widgets to a new filer window. It is in a separate
1713 * function because filer_opendir() was getting too long...
1715 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1717 GtkWidget *hbox, *vbox;
1719 /* Create the top-level window widget */
1720 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1721 filer_set_title(filer_window);
1722 gtk_widget_set_name(filer_window->window, "rox-filer");
1724 if (wm_class)
1725 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1726 wm_class, PROJECT);
1728 /* This property is cleared when the window is destroyed.
1729 * You can thus ref filer_window->window and use this to see
1730 * if the window no longer exists.
1732 g_object_set_data(G_OBJECT(filer_window->window),
1733 "filer_window", filer_window);
1735 /* Create this now to make the Adjustment before the View */
1736 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1738 vbox = gtk_vbox_new(FALSE, 0);
1739 gtk_container_add(GTK_CONTAINER(filer_window->window), vbox);
1741 filer_window->toplevel_vbox = GTK_BOX(vbox);
1743 /* If there's a message that should be displayed in each window (eg
1744 * 'Running as root'), add it here.
1746 if (show_user_message)
1748 filer_window->message = gtk_label_new(show_user_message);
1749 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1750 FALSE, TRUE, 0);
1751 gtk_widget_show(filer_window->message);
1754 hbox = gtk_hbox_new(FALSE, 0);
1755 filer_window->view_hbox = GTK_BOX(hbox);
1756 gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
1757 /* Add the main View widget */
1758 filer_set_view_type(filer_window, filer_window->view_type);
1759 /* Put the scrollbar next to the View */
1760 gtk_box_pack_end(GTK_BOX(hbox),
1761 filer_window->scrollbar, FALSE, TRUE, 0);
1762 gtk_widget_show(hbox);
1764 /* If we want a toolbar, create it now */
1765 toolbar_update_toolbar(filer_window);
1767 /* And the minibuffer (hidden to start with) */
1768 create_minibuffer(filer_window);
1769 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1770 FALSE, TRUE, 0);
1772 /* And the thumbnail progress bar (also hidden) */
1774 GtkWidget *cancel;
1776 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1777 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1778 FALSE, TRUE, 0);
1780 filer_window->thumb_progress = gtk_progress_bar_new();
1782 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1783 filer_window->thumb_progress, TRUE, TRUE, 0);
1785 cancel = gtk_button_new_with_label(_("Cancel"));
1786 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1787 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1788 cancel, FALSE, TRUE, 0);
1789 g_signal_connect_swapped(cancel, "clicked",
1790 G_CALLBACK(filer_cancel_thumbnails),
1791 filer_window);
1794 gtk_widget_show(vbox);
1795 gtk_widget_show(filer_window->scrollbar);
1797 gtk_widget_realize(filer_window->window);
1799 gdk_window_set_role(filer_window->window->window,
1800 filer_window->sym_path);
1802 filer_window_set_size(filer_window, 4, 4);
1805 static void filer_add_signals(FilerWindow *filer_window)
1807 GtkTargetEntry target_table[] =
1809 {"text/uri-list", 0, TARGET_URI_LIST},
1810 {"UTF8_STRING", 0, TARGET_STRING},
1811 {"STRING", 0, TARGET_STRING},
1812 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1815 /* Events on the top-level window */
1816 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1817 g_signal_connect(filer_window->window, "enter-notify-event",
1818 G_CALLBACK(pointer_in), filer_window);
1819 g_signal_connect(filer_window->window, "leave-notify-event",
1820 G_CALLBACK(pointer_out), filer_window);
1821 g_signal_connect(filer_window->window, "destroy",
1822 G_CALLBACK(filer_window_destroyed), filer_window);
1823 g_signal_connect(filer_window->window, "delete-event",
1824 G_CALLBACK(filer_window_delete), filer_window);
1826 g_signal_connect(filer_window->window, "selection_clear_event",
1827 G_CALLBACK(filer_lost_primary), filer_window);
1829 g_signal_connect(filer_window->window, "selection_get",
1830 G_CALLBACK(selection_get), filer_window);
1831 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1832 GDK_SELECTION_PRIMARY,
1833 target_table,
1834 sizeof(target_table) / sizeof(*target_table));
1836 g_signal_connect(filer_window->window, "popup-menu",
1837 G_CALLBACK(popup_menu), filer_window);
1838 g_signal_connect(filer_window->window, "key_press_event",
1839 G_CALLBACK(filer_key_press_event), filer_window);
1841 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1842 filer_keys);
1845 static gint clear_scanning_display(FilerWindow *filer_window)
1847 if (filer_exists(filer_window))
1848 filer_set_title(filer_window);
1849 return FALSE;
1852 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1854 if (scanning == filer_window->scanning)
1855 return;
1856 filer_window->scanning = scanning;
1858 if (scanning)
1859 filer_set_title(filer_window);
1860 else
1861 g_timeout_add(300, (GSourceFunc) clear_scanning_display,
1862 filer_window);
1865 /* Note that filer_window may not exist after this call.
1866 * Returns TRUE iff the directory still exists.
1868 gboolean filer_update_dir(FilerWindow *filer_window, gboolean warning)
1870 gboolean still_exists;
1872 still_exists = may_rescan(filer_window, warning);
1874 if (still_exists)
1875 dir_update(filer_window->directory, filer_window->sym_path);
1877 return still_exists;
1880 void filer_update_all(void)
1882 GList *next = all_filer_windows;
1884 while (next)
1886 FilerWindow *filer_window = (FilerWindow *) next->data;
1888 /* Updating directory may remove it from list -- stop sending
1889 * patches to move this line!
1891 next = next->next;
1893 /* Don't trigger a refresh if we're already scanning.
1894 * Otherwise, two views of a single directory will trigger
1895 * two scans.
1897 if (filer_window->directory &&
1898 !filer_window->directory->scanning)
1899 filer_update_dir(filer_window, TRUE);
1903 /* Refresh the various caches even if we don't think we need to */
1904 void full_refresh(void)
1906 mount_update(TRUE);
1907 reread_mime_files(); /* Refreshes all windows */
1910 /* See whether a filer window with a given path already exists
1911 * and is different from diff.
1913 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1915 GList *next;
1917 for (next = all_filer_windows; next; next = next->next)
1919 FilerWindow *filer_window = (FilerWindow *) next->data;
1921 if (filer_window != diff &&
1922 strcmp(sym_path, filer_window->sym_path) == 0)
1923 return filer_window;
1926 return NULL;
1929 /* This path has been mounted/umounted/deleted some files - update all dirs */
1930 void filer_check_mounted(const char *real_path)
1932 GList *next = all_filer_windows;
1933 gchar *parent;
1934 int len;
1935 gboolean resize = o_filer_auto_resize.int_value == RESIZE_ALWAYS;
1937 /* DOS disks, etc, often don't change the mtime of the root directory
1938 * on modification, so force a refresh now.
1940 g_fscache_update(dir_cache, real_path);
1942 len = strlen(real_path);
1944 while (next)
1946 FilerWindow *filer_window = (FilerWindow *) next->data;
1948 next = next->next;
1950 if (strncmp(real_path, filer_window->real_path, len) == 0)
1952 char s = filer_window->real_path[len];
1954 if (s == '/' || s == '\0')
1956 if (filer_update_dir(filer_window, FALSE) &&
1957 resize)
1958 view_autosize(filer_window->view);
1963 parent = g_path_get_dirname(real_path);
1964 refresh_dirs(parent);
1965 g_free(parent);
1967 icons_may_update(real_path);
1970 /* Close all windows displaying 'path' or subdirectories of 'path' */
1971 void filer_close_recursive(const char *path)
1973 GList *next = all_filer_windows;
1974 gchar *real;
1975 int len;
1977 real = pathdup(path);
1978 len = strlen(real);
1980 while (next)
1982 FilerWindow *filer_window = (FilerWindow *) next->data;
1984 next = next->next;
1986 if (strncmp(real, filer_window->real_path, len) == 0)
1988 char s = filer_window->real_path[len];
1990 if (len == 1 || s == '/' || s == '\0')
1991 gtk_widget_destroy(filer_window->window);
1996 /* Like minibuffer_show(), except that:
1997 * - It returns FALSE (to be used from an idle callback)
1998 * - It checks that the filer window still exists.
2000 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
2002 if (filer_exists(filer_window))
2003 minibuffer_show(filer_window, MINI_PATH);
2004 return FALSE;
2007 /* TRUE iff filer_window points to an existing FilerWindow
2008 * structure.
2010 gboolean filer_exists(FilerWindow *filer_window)
2012 GList *next;
2014 for (next = all_filer_windows; next; next = next->next)
2016 FilerWindow *fw = (FilerWindow *) next->data;
2018 if (fw == filer_window)
2019 return TRUE;
2022 return FALSE;
2025 FilerWindow *filer_get_by_id(const char *id)
2027 return g_hash_table_lookup(window_with_id, id);
2030 void filer_set_id(FilerWindow *filer_window, const char *id)
2032 g_return_if_fail(filer_window != NULL);
2034 if (filer_window->window_id)
2036 g_hash_table_remove(window_with_id, filer_window->window_id);
2037 g_free(filer_window->window_id);
2038 filer_window->window_id = NULL;
2041 if (id)
2043 filer_window->window_id = g_strdup(id);
2044 g_hash_table_insert(window_with_id,
2045 filer_window->window_id,
2046 filer_window);
2050 /* Make sure the window title is up-to-date */
2051 void filer_set_title(FilerWindow *filer_window)
2053 gchar *title = NULL;
2054 guchar *flags = "";
2056 if (filer_window->scanning ||
2057 filer_window->filter != FILER_SHOW_ALL ||
2058 filer_window->show_hidden || filer_window->show_thumbs)
2060 if (o_short_flag_names.int_value)
2062 const gchar *hidden = "";
2064 switch(filer_window->filter) {
2065 case FILER_SHOW_ALL:
2066 hidden=filer_window->show_hidden? _("A") : "";
2067 break;
2068 case FILER_SHOW_GLOB: hidden = _("G"); break;
2069 default: break;
2072 flags = g_strconcat(" +",
2073 filer_window->scanning ? _("S") : "",
2074 hidden,
2075 filer_window->show_thumbs ? _("T") : "",
2076 NULL);
2078 else
2080 gchar *hidden = NULL;
2082 switch(filer_window->filter) {
2083 case FILER_SHOW_ALL:
2084 hidden = g_strdup(filer_window->show_hidden
2085 ? _("All, ") : "");
2086 break;
2087 case FILER_SHOW_GLOB:
2088 hidden = g_strdup_printf(_("Glob (%s), "),
2089 filer_window->filter_string);
2090 break;
2091 default:
2092 hidden =g_strdup("");
2093 break;
2095 flags = g_strconcat(" (",
2096 filer_window->scanning ? _("Scanning, ") : "",
2097 hidden,
2098 filer_window->show_thumbs ? _("Thumbs, ") : "",
2099 NULL);
2100 flags[strlen(flags) - 2] = ')';
2101 g_free(hidden);
2105 if (not_local)
2106 title = g_strconcat("//", our_host_name(),
2107 filer_window->sym_path, flags, NULL);
2109 if (!title && home_dir_len > 1 && filer_window->sym_path &&
2110 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
2112 guchar sep = filer_window->sym_path[home_dir_len];
2114 if (sep == '\0' || sep == '/')
2115 title = g_strconcat("~",
2116 filer_window->sym_path + home_dir_len,
2117 flags,
2118 NULL);
2121 if (!title)
2123 if (filer_window->sym_path)
2124 title = g_strconcat(filer_window->sym_path, flags, NULL);
2125 else
2126 title = g_strconcat(filer_window->uri, flags, NULL);
2129 ensure_utf8(&title);
2131 if (filer_window->directory->error)
2133 gchar *old = title;
2134 title = g_strconcat(old, ": ", filer_window->directory->error,
2135 NULL);
2136 g_free(old);
2139 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
2141 g_free(title);
2143 if (flags[0] != '\0')
2144 g_free(flags);
2147 /* Reconnect to the same directory (used when the Show Hidden option is
2148 * toggled). This has the side-effect of updating the window title.
2150 void filer_detach_rescan(FilerWindow *filer_window)
2152 Directory *dir = filer_window->directory;
2154 g_object_ref(dir);
2155 detach(filer_window);
2156 filer_window->directory = dir;
2157 attach(filer_window);
2160 /* Puts the filer window into target mode. When an item is chosen,
2161 * fn(filer_window, iter, data) is called. 'reason' will be displayed
2162 * on the toolbar while target mode is active.
2164 * Use fn == NULL to cancel target mode.
2166 void filer_target_mode(FilerWindow *filer_window,
2167 TargetFunc fn,
2168 gpointer data,
2169 const char *reason)
2171 TargetFunc old_fn = filer_window->target_cb;
2173 if (fn != old_fn)
2174 gdk_window_set_cursor(
2175 GTK_WIDGET(filer_window->view)->window,
2176 fn ? crosshair : NULL);
2178 filer_window->target_cb = fn;
2179 filer_window->target_data = data;
2181 if (filer_window->toolbar_text == NULL)
2182 return;
2184 if (fn)
2185 gtk_label_set_text(
2186 GTK_LABEL(filer_window->toolbar_text), reason);
2187 else if (o_toolbar_info.int_value)
2189 if (old_fn)
2190 toolbar_update_info(filer_window);
2192 else
2193 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2196 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2198 GtkStateType old_state = filer_window->selection_state;
2200 filer_window->selection_state = normal
2201 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2203 if (old_state != filer_window->selection_state
2204 && view_count_selected(filer_window->view))
2205 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
2208 void filer_cancel_thumbnails(FilerWindow *filer_window)
2210 gtk_widget_hide(filer_window->thumb_bar);
2212 destroy_glist(&filer_window->thumb_queue);
2213 filer_window->max_thumbs = 0;
2216 /* Generate the next thumb for this window. The window object is
2217 * unref'd when there is nothing more to do.
2218 * If the window no longer has a filer window, nothing is done.
2220 static gboolean filer_next_thumb_real(GObject *window)
2222 FilerWindow *filer_window;
2223 gchar *path;
2224 int done, total;
2226 filer_window = g_object_get_data(window, "filer_window");
2228 if (!filer_window)
2230 g_object_unref(window);
2231 return FALSE;
2234 if (!filer_window->thumb_queue)
2236 filer_cancel_thumbnails(filer_window);
2237 g_object_unref(window);
2238 return FALSE;
2241 total = filer_window->max_thumbs;
2242 done = total - g_list_length(filer_window->thumb_queue);
2244 path = (gchar *) filer_window->thumb_queue->data;
2246 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2248 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2249 path);
2250 g_free(path);
2252 gtk_progress_bar_set_fraction(
2253 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2254 done / (float) total);
2256 return FALSE;
2259 /* path is the thumb just loaded, if any.
2260 * window is unref'd (eventually).
2262 static void filer_next_thumb(GObject *window, const gchar *path)
2264 if (path)
2265 dir_force_update_path(path);
2267 g_idle_add((GSourceFunc) filer_next_thumb_real, window);
2270 static void start_thumb_scanning(FilerWindow *filer_window)
2272 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2273 return; /* Already scanning */
2275 gtk_widget_show_all(filer_window->thumb_bar);
2277 g_object_ref(G_OBJECT(filer_window->window));
2278 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2281 /* Set this image to be loaded some time in the future */
2282 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2284 if (g_list_find_custom(filer_window->thumb_queue, path,
2285 (GCompareFunc) strcmp))
2286 return;
2288 if (!filer_window->thumb_queue)
2289 filer_window->max_thumbs=0;
2290 filer_window->max_thumbs++;
2292 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2293 g_strdup(path));
2295 if (filer_window->scanning)
2296 return; /* Will start when scan ends */
2298 start_thumb_scanning(filer_window);
2301 /* If thumbnail display is on, look through all the items in this directory
2302 * and start creating or updating the thumbnails as needed.
2304 void filer_create_thumbs(FilerWindow *filer_window)
2306 DirItem *item;
2307 ViewIter iter;
2309 if (!filer_window->show_thumbs)
2310 return;
2312 view_get_iter(filer_window->view, &iter, 0);
2314 while ((item = iter.next(&iter)))
2316 MaskedPixmap *pixmap;
2317 const guchar *path;
2318 gboolean found;
2320 if (item->base_type != TYPE_FILE)
2321 continue;
2323 /*if (strcmp(item->mime_type->media_type, "image") != 0)
2324 continue;*/
2326 path = make_path(filer_window->real_path, item->leafname);
2328 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2329 FSCACHE_LOOKUP_ONLY_NEW, &found);
2330 if (pixmap)
2331 g_object_unref(pixmap);
2333 /* If we didn't get an image, it could be because:
2335 * - We're loading the image now. found is TRUE,
2336 * and we'll update the item later.
2337 * - We tried to load the image and failed. found
2338 * is TRUE.
2339 * - We haven't tried loading the image. found is
2340 * FALSE, and we start creating the thumb here.
2342 if (!found)
2343 filer_create_thumb(filer_window, path);
2347 static void filer_options_changed(void)
2349 if (o_short_flag_names.has_changed)
2351 GList *next;
2353 for (next = all_filer_windows; next; next = next->next)
2355 FilerWindow *filer_window = (FilerWindow *) next->data;
2357 filer_set_title(filer_window);
2362 /* Append interesting information to this GString */
2363 void filer_add_tip_details(FilerWindow *filer_window,
2364 GString *tip, DirItem *item)
2366 const guchar *fullpath = NULL;
2368 fullpath = make_path(filer_window->real_path, item->leafname);
2370 if (item->flags & ITEM_FLAG_SYMLINK)
2372 char *target;
2374 target = readlink_dup(fullpath);
2375 if (target)
2377 ensure_utf8(&target);
2379 g_string_append(tip, _("Symbolic link to "));
2380 g_string_append(tip, target);
2381 g_string_append_c(tip, '\n');
2382 g_free(target);
2386 if (item->flags & ITEM_FLAG_APPDIR)
2388 XMLwrapper *info;
2389 xmlNode *node;
2391 info = appinfo_get(fullpath, item);
2392 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
2394 guchar *str;
2395 str = xmlNodeListGetString(node->doc,
2396 node->xmlChildrenNode, 1);
2397 if (str)
2399 g_string_append(tip, str);
2400 g_string_append_c(tip, '\n');
2401 g_free(str);
2404 if (info)
2405 g_object_unref(info);
2407 else if (item->mime_type == application_x_desktop)
2409 char *summary;
2410 summary = tip_from_desktop_file(fullpath);
2411 if (summary)
2413 g_string_append(tip, summary);
2414 g_string_append_c(tip, '\n');
2415 g_free(summary);
2419 if (!g_utf8_validate(item->leafname, -1, NULL))
2420 g_string_append(tip,
2421 _("This filename is not valid UTF-8. "
2422 "You should rename it.\n"));
2425 /* Return the selection as a text/uri-list.
2426 * g_free() the result.
2428 static guchar *filer_create_uri_list(FilerWindow *filer_window)
2430 GString *string;
2431 GString *leader;
2432 ViewIter iter;
2433 DirItem *item;
2434 guchar *retval;
2436 g_return_val_if_fail(filer_window != NULL, NULL);
2438 string = g_string_new(NULL);
2440 leader = g_string_new(filer_window->sym_path);
2441 if (leader->str[leader->len - 1] != '/')
2442 g_string_append_c(leader, '/');
2444 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
2445 while ((item = iter.next(&iter)))
2447 EscapedPath *uri;
2448 char *path;
2450 path = g_strconcat(leader->str, item->leafname, NULL);
2451 uri = encode_path_as_uri(path);
2452 g_string_append(string, (char *) uri);
2453 g_string_append(string, "\r\n");
2454 g_free(path);
2455 g_free(uri);
2458 g_string_free(leader, TRUE);
2459 retval = string->str;
2460 g_string_free(string, FALSE);
2462 return retval;
2465 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
2467 BindAction action;
2468 ViewIface *view = filer_window->view;
2469 DirItem *item = NULL;
2470 gboolean press = event->type == GDK_BUTTON_PRESS;
2471 ViewIter iter;
2472 OpenFlags flags = 0;
2474 if (event->button > 3)
2475 return;
2477 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2478 item = iter.peek(&iter);
2480 if (item && view_cursor_visible(view))
2481 view_cursor_to_iter(view, &iter);
2483 if (item && event->button == 1 &&
2484 view_get_selected(view, &iter) &&
2485 filer_window->selection_state == GTK_STATE_INSENSITIVE)
2487 /* Possibly a really slow DnD operation? */
2488 filer_window->temp_item_selected = FALSE;
2490 filer_selection_changed(filer_window, event->time);
2491 return;
2494 if (filer_window->target_cb)
2496 dnd_motion_ungrab();
2497 if (item && press && event->button == 1)
2498 filer_window->target_cb(filer_window, &iter,
2499 filer_window->target_data);
2501 filer_target_mode(filer_window, NULL, NULL, NULL);
2503 return;
2506 if (!o_single_click.int_value)
2508 /* Make sure both parts of a double-click fall on
2509 * the same file.
2511 static guchar *first_click = NULL;
2512 static guchar *second_click = NULL;
2514 if (event->type == GDK_BUTTON_PRESS)
2516 g_free(first_click);
2517 first_click = second_click;
2519 if (item)
2520 second_click = g_strdup(item->leafname);
2521 else
2522 second_click = NULL;
2525 if (event->type == GDK_2BUTTON_PRESS)
2527 if (first_click && second_click &&
2528 strcmp(first_click, second_click) != 0)
2529 return;
2530 if ((first_click || second_click) &&
2531 !(first_click && second_click))
2532 return;
2536 action = bind_lookup_bev(
2537 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
2538 event);
2540 switch (action)
2542 case ACT_CLEAR_SELECTION:
2543 view_clear_selection(view);
2544 break;
2545 case ACT_TOGGLE_SELECTED:
2546 view_set_selected(view, &iter,
2547 !view_get_selected(view, &iter));
2548 break;
2549 case ACT_SELECT_EXCL:
2550 view_select_only(view, &iter);
2551 break;
2552 case ACT_EDIT_ITEM:
2553 flags |= OPEN_SHIFT;
2554 /* (no break) */
2555 case ACT_OPEN_ITEM:
2556 if (event->button != 1 || event->state & GDK_MOD1_MASK)
2557 flags |= OPEN_CLOSE_WINDOW;
2558 else
2559 flags |= OPEN_SAME_WINDOW;
2560 if (o_new_button_1.int_value)
2561 flags ^= OPEN_SAME_WINDOW;
2562 if (event->type == GDK_2BUTTON_PRESS)
2563 view_set_selected(view, &iter, FALSE);
2564 dnd_motion_ungrab();
2566 filer_openitem(filer_window, &iter, flags);
2567 break;
2568 case ACT_POPUP_MENU:
2569 dnd_motion_ungrab();
2570 tooltip_show(NULL);
2571 show_filer_menu(filer_window,
2572 (GdkEvent *) event, &iter);
2573 break;
2574 case ACT_PRIME_AND_SELECT:
2575 if (item && !view_get_selected(view, &iter))
2576 view_select_only(view, &iter);
2577 dnd_motion_start(MOTION_READY_FOR_DND);
2578 break;
2579 case ACT_PRIME_AND_TOGGLE:
2580 view_set_selected(view, &iter,
2581 !view_get_selected(view, &iter));
2582 dnd_motion_start(MOTION_READY_FOR_DND);
2583 break;
2584 case ACT_PRIME_FOR_DND:
2585 dnd_motion_start(MOTION_READY_FOR_DND);
2586 break;
2587 case ACT_IGNORE:
2588 if (press && event->button < 4)
2590 if (item)
2591 view_wink_item(view, &iter);
2592 dnd_motion_start(MOTION_NONE);
2594 break;
2595 case ACT_LASSO_CLEAR:
2596 view_clear_selection(view);
2597 /* (no break) */
2598 case ACT_LASSO_MODIFY:
2599 view_start_lasso_box(view, event);
2600 break;
2601 case ACT_RESIZE:
2602 view_autosize(filer_window->view);
2603 break;
2604 default:
2605 g_warning("Unsupported action : %d\n", action);
2606 break;
2610 /* It's time to make the tooltip appear. If we're not over the item any
2611 * more, or the item doesn't need a tooltip, do nothing.
2613 static gboolean tooltip_activate(GtkWidget *window)
2615 FilerWindow *filer_window;
2616 ViewIface *view;
2617 ViewIter iter;
2618 gint x, y;
2619 DirItem *item = NULL;
2620 GString *tip = NULL;
2622 g_return_val_if_fail(tip_item != NULL, 0);
2624 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2626 if (!motion_window || !filer_window)
2627 return FALSE; /* Window has been destroyed */
2629 view = filer_window->view;
2631 tooltip_show(NULL);
2633 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2634 view_get_iter_at_point(view, &iter, motion_window, x, y);
2636 item = iter.peek(&iter);
2637 if (item != tip_item)
2638 return FALSE; /* Not still under the pointer */
2640 /* OK, the filer window still exists and the pointer is still
2641 * over the same item. Do we need to show a tip?
2644 tip = g_string_new(NULL);
2646 view_extend_tip(filer_window->view, &iter, tip);
2648 filer_add_tip_details(filer_window, tip, tip_item);
2650 if (tip->len > 1)
2652 g_string_truncate(tip, tip->len - 1);
2654 tooltip_show(tip->str);
2657 g_string_free(tip, TRUE);
2659 return FALSE;
2662 /* Motion detected on the View widget */
2663 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2665 ViewIface *view = filer_window->view;
2666 ViewIter iter;
2667 DirItem *item;
2669 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2670 item = iter.peek(&iter);
2672 if (item)
2674 if (item != tip_item)
2676 tooltip_show(NULL);
2678 tip_item = item;
2679 motion_window = event->window;
2680 tooltip_prime((GtkFunction) tooltip_activate,
2681 G_OBJECT(filer_window->window));
2684 else
2686 tooltip_show(NULL);
2687 tip_item = NULL;
2690 if (motion_state != MOTION_READY_FOR_DND)
2691 return FALSE;
2693 if (!dnd_motion_moved(event))
2694 return FALSE;
2696 view_get_iter_at_point(view, &iter,
2697 event->window,
2698 event->x - (event->x_root - drag_start_x),
2699 event->y - (event->y_root - drag_start_y));
2700 item = iter.peek(&iter);
2701 if (!item)
2702 return FALSE;
2704 view_wink_item(view, NULL);
2706 if (!view_get_selected(view, &iter))
2708 if (event->state & GDK_BUTTON1_MASK)
2710 /* Select just this one */
2711 filer_window->temp_item_selected = TRUE;
2712 view_select_only(view, &iter);
2714 else
2716 if (view_count_selected(view) == 0)
2717 filer_window->temp_item_selected = TRUE;
2718 view_set_selected(view, &iter, TRUE);
2722 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2724 if (view_count_selected(view) == 1)
2726 if (item->base_type == TYPE_UNKNOWN)
2727 item = dir_update_item(filer_window->directory,
2728 item->leafname);
2730 if (!item)
2732 report_error(_("Item no longer exists!"));
2733 return FALSE;
2736 drag_one_item(GTK_WIDGET(view), event,
2737 make_path(filer_window->sym_path, item->leafname),
2738 item, di_image(item));
2739 #if 0
2740 /* XXX: Use thumbnail */
2741 item, view ? view->image : NULL);
2742 #endif
2744 else
2746 guchar *uris;
2748 uris = filer_create_uri_list(filer_window);
2749 drag_selection(GTK_WIDGET(view), event, uris);
2750 g_free(uris);
2753 return FALSE;
2756 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2757 FilerWindow *filer_window)
2759 filer_set_autoscroll(filer_window, FALSE);
2761 if (filer_window->temp_item_selected)
2763 view_clear_selection(filer_window->view);
2764 filer_window->temp_item_selected = FALSE;
2768 /* Remove highlights */
2769 static void drag_leave(GtkWidget *widget,
2770 GdkDragContext *context,
2771 guint32 time,
2772 FilerWindow *filer_window)
2774 dnd_spring_abort();
2777 /* Called during the drag when the mouse is in a widget registered
2778 * as a drop target. Returns TRUE if we can accept the drop.
2780 static gboolean drag_motion(GtkWidget *widget,
2781 GdkDragContext *context,
2782 gint x,
2783 gint y,
2784 guint time,
2785 FilerWindow *filer_window)
2787 DirItem *item;
2788 ViewIface *view = filer_window->view;
2789 ViewIter iter;
2790 GdkDragAction action = context->suggested_action;
2791 const guchar *new_path = NULL;
2792 const char *type = NULL;
2793 gboolean retval = FALSE;
2794 gboolean same_window;
2796 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value)
2798 guint state;
2799 gdk_window_get_pointer(NULL, NULL, NULL, &state);
2800 if (state & GDK_BUTTON1_MASK)
2801 action = GDK_ACTION_ASK;
2804 same_window = gtk_drag_get_source_widget(context) == widget;
2806 filer_set_autoscroll(filer_window, TRUE);
2808 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2810 GdkWindow *bin;
2811 int bin_y;
2812 /* Correct for position of bin window */
2813 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2814 gdk_window_get_position(bin, NULL, &bin_y);
2815 y -= bin_y;
2818 if (o_dnd_drag_to_icons.int_value)
2820 view_get_iter_at_point(view, &iter, widget->window, x, y);
2821 item = iter.peek(&iter);
2823 else
2824 item = NULL;
2826 if (item && same_window && view_get_selected(view, &iter))
2827 type = NULL;
2828 else
2829 type = dnd_motion_item(context, &item);
2831 if (!type)
2832 item = NULL;
2834 /* Don't allow drops to non-writeable directories. BUT, still
2835 * allow drops on non-writeable SUBdirectories so that we can
2836 * do the spring-open thing.
2838 if (item && type == drop_dest_dir &&
2839 !(item->flags & ITEM_FLAG_APPDIR))
2841 dnd_spring_load(context, filer_window);
2843 else
2844 dnd_spring_abort();
2846 if (item)
2847 view_cursor_to_iter(view, &iter);
2848 else
2850 view_cursor_to_iter(view, NULL);
2852 /* Disallow background drops within a single window */
2853 if (type && same_window)
2854 type = NULL;
2857 if (type)
2859 if (item)
2860 new_path = make_path(filer_window->sym_path,
2861 item->leafname);
2862 else
2863 new_path = filer_window->sym_path;
2866 /* Don't ask about dragging to an application! */
2867 if (type == drop_dest_prog && action == GDK_ACTION_ASK)
2868 action = GDK_ACTION_COPY;
2870 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2871 if (type)
2873 gdk_drag_status(context, action, time);
2874 g_dataset_set_data_full(context, "drop_dest_path",
2875 g_strdup(new_path), g_free);
2876 retval = TRUE;
2879 return retval;
2882 static gboolean as_timeout(FilerWindow *filer_window)
2884 gboolean retval;
2886 retval = view_auto_scroll_callback(filer_window->view);
2888 if (!retval)
2889 filer_window->auto_scroll = -1;
2891 return retval;
2894 /* When autoscroll is on, a timer keeps track of the pointer position.
2895 * While it's near the top or bottom of the window, the window scrolls.
2897 * If the mouse buttons are released, the pointer leaves the window, or
2898 * a drag-and-drop operation finishes, auto_scroll is turned off.
2900 void filer_set_autoscroll(FilerWindow *filer_window, gboolean auto_scroll)
2902 g_return_if_fail(filer_window != NULL);
2904 if (auto_scroll)
2906 if (filer_window->auto_scroll != -1)
2907 return; /* Already on! */
2909 filer_window->auto_scroll = g_timeout_add(50,
2910 (GSourceFunc) as_timeout,
2911 filer_window);
2913 else
2915 if (filer_window->auto_scroll == -1)
2916 return; /* Already off! */
2918 g_source_remove(filer_window->auto_scroll);
2919 filer_window->auto_scroll = -1;
2923 #define ZERO_MNT "/uri/0install"
2925 static void refresh_done(FilerWindow *filer_window)
2927 if (filer_exists(filer_window))
2928 filer_update_dir(filer_window, TRUE);
2931 void filer_refresh(FilerWindow *filer_window)
2933 if (!strncmp(ZERO_MNT "/", filer_window->real_path, sizeof(ZERO_MNT)))
2935 /* Try to run 0refresh */
2936 gint pid;
2937 gchar *argv[] = {"0refresh", NULL, NULL};
2938 const char *host = filer_window->real_path + sizeof(ZERO_MNT);
2939 const char *slash;
2941 slash = strchr(host, '/');
2942 if (slash)
2943 argv[1] = g_strndup(host, slash - host);
2944 else
2945 argv[1] = g_strdup(host);
2946 pid = rox_spawn(filer_window->real_path, (const char **) argv);
2947 g_free(argv[1]);
2948 if (pid)
2949 on_child_death(pid, (CallbackFn) refresh_done,
2950 filer_window);
2953 full_refresh();
2956 static inline gboolean is_hidden(const char *dir, DirItem *item)
2958 /* If the leaf name starts with '.' then the item is hidden */
2959 if(item->leafname[0]=='.')
2960 return TRUE;
2962 /*** Test disabled for now. The flags aren't set on the first pass...
2964 #if 0
2965 /* Most files will not have extended attributes, so this should
2966 * be quick. */
2967 if(!o_xattr_ignore.int_value && (item->flags & ITEM_FLAG_HAS_XATTR)) {
2968 gchar *path, *val;
2969 int len;
2970 gboolean hidden=FALSE;
2972 path=g_build_filename(dir, item->leafname, NULL);
2973 val=xattr_get(path, XATTR_HIDDEN, &len);
2974 if(val) {
2975 hidden=atoi(val) || (strcmp(val, "true")==0);
2976 g_free(val);
2978 g_free(path);
2980 if(hidden)
2981 return TRUE;
2983 #endif
2985 /* Otherwise not hidden */
2986 return FALSE;
2989 gboolean filer_match_filter(FilerWindow *filer_window, DirItem *item)
2991 g_return_val_if_fail(item != NULL, FALSE);
2993 if(is_hidden(filer_window->real_path, item) &&
2994 (!filer_window->temp_show_hidden && !filer_window->show_hidden))
2995 return FALSE;
2997 switch(filer_window->filter) {
2998 case FILER_SHOW_GLOB:
2999 return fnmatch(filer_window->filter_string,
3000 item->leafname, 0)==0 ||
3001 (item->base_type==TYPE_DIRECTORY &&
3002 !filer_window->filter_directories);
3004 case FILER_SHOW_ALL:
3005 default:
3006 break;
3008 return TRUE;
3011 /* Provided to hide the implementation */
3012 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
3014 filer_window->show_hidden=hidden;
3017 /* Provided to hide the implementation */
3018 void filer_set_filter_directories(FilerWindow *filer_window, gboolean filter_directories)
3020 filer_window->filter_directories=filter_directories;
3023 /* Set the filter type. Returns TRUE if the type has changed
3024 * (must call filer_detach_rescan).
3026 gboolean filer_set_filter(FilerWindow *filer_window, FilterType type,
3027 const gchar *filter_string)
3029 /* Is this new filter the same as the old one? */
3030 if (filer_window->filter == type)
3032 switch(filer_window->filter)
3034 case FILER_SHOW_ALL:
3035 return FALSE;
3036 case FILER_SHOW_GLOB:
3037 if (strcmp(filer_window->filter_string,
3038 filter_string) == 0)
3039 return FALSE;
3040 break;
3044 /* Clean up old filter */
3045 if (filer_window->filter_string)
3047 g_free(filer_window->filter_string);
3048 filer_window->filter_string = NULL;
3050 /* Also clean up compiled regexp when implemented */
3052 filer_window->filter = type;
3054 switch(type)
3056 case FILER_SHOW_ALL:
3057 /* No extra work */
3058 break;
3060 case FILER_SHOW_GLOB:
3061 filer_window->filter_string = g_strdup(filter_string);
3062 break;
3064 default:
3065 /* oops */
3066 filer_window->filter = FILER_SHOW_ALL;
3067 g_warning("Impossible: filter type %d", type);
3068 break;
3071 return TRUE;
3074 /* Setting stuff */
3075 static Settings *settings_new(const char *path)
3077 Settings *set;
3079 set=g_new(Settings, 1);
3080 memset(set, 0, sizeof(Settings));
3081 if(path)
3082 set->path=g_strdup(path);
3084 return set;
3087 static void settings_free(Settings *set)
3089 g_free(set->path);
3090 if(set->filter)
3091 g_free(set->filter);
3092 g_free(set);
3095 static void store_settings(Settings *set)
3097 Settings *old;
3099 old=g_hash_table_lookup(settings_table, set->path);
3100 if(old)
3102 g_hash_table_remove(settings_table, set->path);
3103 settings_free(old);
3106 g_hash_table_insert(settings_table, set->path, set);
3109 /* TODO: use symbolic names in the XML file where possible */
3110 static void load_from_node(Settings *set, xmlDocPtr doc, xmlNodePtr node)
3112 xmlChar *str=NULL;
3114 str=xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
3116 if(strcmp(node->name, "X") == 0) {
3117 set->x=atoi(str);
3118 set->flags|=SET_POSITION;
3119 } else if(strcmp(node->name, "Y") == 0) {
3120 set->y=atoi(str);
3121 set->flags|=SET_POSITION;
3122 } else if(strcmp(node->name, "Width") == 0) {
3123 set->width=atoi(str);
3124 set->flags|=SET_SIZE;
3125 } else if(strcmp(node->name, "Height") == 0) {
3126 set->height=atoi(str);
3127 set->flags|=SET_SIZE;
3128 } else if(strcmp(node->name, "ShowHidden") == 0) {
3129 set->show_hidden=atoi(str);
3130 set->flags|=SET_HIDDEN;
3131 } else if(strcmp(node->name, "ViewType") == 0) {
3132 set->view_type=atoi(str);
3133 set->flags|=SET_DETAILS;
3134 } else if(strcmp(node->name, "DetailsType") == 0) {
3135 set->details_type=atoi(str);
3136 set->flags|=SET_DETAILS;
3137 } else if(strcmp(node->name, "SortType") == 0) {
3138 set->sort_type=atoi(str);
3139 set->flags|=SET_SORT;
3140 } else if(strcmp(node->name, "SortOrder") == 0) {
3141 set->sort_order=atoi(str);
3142 set->flags|=SET_SORT;
3143 } else if(strcmp(node->name, "DisplayStyle") == 0) {
3144 set->display_style=atoi(str);
3145 set->flags|=SET_STYLE;
3146 } else if(strcmp(node->name, "ShowThumbs") == 0) {
3147 set->show_thumbs=atoi(str);
3148 set->flags|=SET_THUMBS;
3149 } else if(strcmp(node->name, "FilterType") == 0) {
3150 set->filter_type=atoi(str);
3151 set->flags|=SET_FILTER;
3152 } else if(strcmp(node->name, "Filter") == 0) {
3153 set->filter=g_strdup(str);
3154 set->flags|=SET_FILTER;
3155 } else if(strcmp(node->name, "FilterDirectories") == 0) {
3156 set->filter_directories=atoi(str);
3157 set->flags|=SET_FILTER;
3160 if(str)
3161 xmlFree(str);
3164 static void load_settings(void)
3166 gchar *path;
3167 XMLwrapper *settings_doc=NULL;
3169 path=choices_find_xdg_path_load("Settings.xml", PROJECT, SITE);
3170 if(path) {
3171 settings_doc=xml_new(path);
3172 g_free(path);
3175 if(!settings_table)
3176 settings_table=g_hash_table_new(g_str_hash, g_str_equal);
3178 if(settings_doc) {
3179 xmlNodePtr node, subnode;
3181 node = xmlDocGetRootElement(settings_doc->doc);
3183 for (node = node->xmlChildrenNode; node; node = node->next)
3185 Settings *set;
3186 xmlChar *path;
3188 if (node->type != XML_ELEMENT_NODE)
3189 continue;
3190 if (strcmp(node->name, "FilerWindow") != 0)
3191 continue;
3193 path=xmlGetProp(node, "path");
3194 set=settings_new(path);
3195 xmlFree(path);
3197 for (subnode=node->xmlChildrenNode; subnode;
3198 subnode=subnode->next) {
3200 if (subnode->type != XML_ELEMENT_NODE)
3201 continue;
3202 load_from_node(set, settings_doc->doc,
3203 subnode);
3206 store_settings(set);
3208 g_object_unref(settings_doc);
3212 static void add_nodes(gpointer key, gpointer value, gpointer data)
3214 xmlNodePtr node=(xmlNodePtr) data;
3215 xmlNodePtr sub;
3216 Settings *set=(Settings *) value;
3217 char *tmp;
3219 sub=xmlNewChild(node, NULL, "FilerWindow", NULL);
3221 xmlSetProp(sub, "path", set->path);
3223 if(set->flags & SET_POSITION) {
3224 tmp=g_strdup_printf("%d", set->x);
3225 xmlNewChild(sub, NULL, "X", tmp);
3226 g_free(tmp);
3227 tmp=g_strdup_printf("%d", set->y);
3228 xmlNewChild(sub, NULL, "Y", tmp);
3229 g_free(tmp);
3231 if(set->flags & SET_SIZE) {
3232 tmp=g_strdup_printf("%d", set->width);
3233 xmlNewChild(sub, NULL, "Width", tmp);
3234 g_free(tmp);
3235 tmp=g_strdup_printf("%d", set->height);
3236 xmlNewChild(sub, NULL, "Height", tmp);
3237 g_free(tmp);
3239 if(set->flags & SET_HIDDEN) {
3240 tmp=g_strdup_printf("%d", set->show_hidden);
3241 xmlNewChild(sub, NULL, "ShowHidden", tmp);
3242 g_free(tmp);
3244 if(set->flags & SET_STYLE) {
3245 tmp=g_strdup_printf("%d", set->display_style);
3246 xmlNewChild(sub, NULL, "DisplayStyle", tmp);
3247 g_free(tmp);
3249 if(set->flags & SET_SORT) {
3250 tmp=g_strdup_printf("%d", set->sort_type);
3251 xmlNewChild(sub, NULL, "SortType", tmp);
3252 g_free(tmp);
3253 tmp=g_strdup_printf("%d", set->sort_order);
3254 xmlNewChild(sub, NULL, "SortOrder", tmp);
3255 g_free(tmp);
3257 if(set->flags & SET_DETAILS) {
3258 tmp=g_strdup_printf("%d", set->view_type);
3259 xmlNewChild(sub, NULL, "ViewType", tmp);
3260 g_free(tmp);
3261 tmp=g_strdup_printf("%d", set->details_type);
3262 xmlNewChild(sub, NULL, "DetailsType", tmp);
3263 g_free(tmp);
3265 if(set->flags & SET_STYLE) {
3266 tmp=g_strdup_printf("%d", set->show_thumbs);
3267 xmlNewChild(sub, NULL, "ShowThumbs", tmp);
3268 g_free(tmp);
3270 if(set->flags & SET_FILTER) {
3271 tmp=g_strdup_printf("%d", set->filter_type);
3272 xmlNewChild(sub, NULL, "FilterType", tmp);
3273 g_free(tmp);
3274 if(set->filter && set->filter[0])
3275 xmlNewChild(sub, NULL, "Filter", set->filter);
3276 tmp=g_strdup_printf("%d", set->filter_directories);
3277 xmlNewChild(sub, NULL, "FilterDirectories", tmp);
3281 static void save_settings(void)
3283 gchar *path;
3285 path=choices_find_xdg_path_save("Settings.xml", PROJECT, SITE, TRUE);
3286 if(path) {
3287 xmlDocPtr doc = xmlNewDoc("1.0");
3288 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL,
3289 "Settings", NULL));
3291 g_hash_table_foreach(settings_table, add_nodes,
3292 xmlDocGetRootElement(doc));
3294 save_xml_file(doc, path);
3296 g_free(path);
3297 xmlFreeDoc(doc);
3302 static void check_settings(FilerWindow *filer_window)
3304 Settings *set;
3306 set=(Settings *) g_hash_table_lookup(settings_table, filer_window->uri);
3308 if(set) {
3309 if(set->flags & SET_POSITION)
3310 gtk_window_move(GTK_WINDOW(filer_window->window),
3311 set->x, set->y);
3312 if(set->flags & SET_SIZE)
3313 filer_window_set_size(filer_window, set->width,
3314 set->height);
3315 if(set->flags & SET_HIDDEN)
3316 filer_set_hidden(filer_window, set->show_hidden);
3318 if(set->flags & (SET_STYLE|SET_DETAILS)) {
3319 DisplayStyle style=filer_window->display_style;
3320 DetailsType details=filer_window->details_type;
3322 if(set->flags & SET_STYLE)
3323 style=set->display_style;
3325 if(set->flags & SET_DETAILS) {
3326 details=set->details_type;
3328 filer_set_view_type(filer_window,
3329 set->view_type);
3332 display_set_layout(filer_window, style,
3333 details, FALSE);
3336 if(set->flags & SET_SORT)
3337 display_set_sort_type(filer_window,
3338 set->sort_type,
3339 set->sort_order);
3341 if(set->flags & SET_THUMBS)
3342 display_set_thumbs(filer_window,
3343 set->show_thumbs);
3345 if(set->flags & SET_FILTER)
3347 display_set_filter(filer_window,
3348 set->filter_type,
3349 set->filter);
3350 display_set_filter_directories(filer_window,
3351 set->filter_directories);
3357 typedef struct settings_window {
3358 GtkWidget *window;
3360 GtkWidget *pos, *size, *hidden, *style, *sort, *details,
3361 *thumbs, *filter;
3363 Settings *set;
3364 } SettingsWindow;
3367 static void settings_response(GtkWidget *window, gint response,
3368 SettingsWindow *set_win)
3370 if(response==GTK_RESPONSE_OK) {
3371 gint flags=0;
3373 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->pos)))
3374 flags|=SET_POSITION;
3375 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->size)))
3376 flags|=SET_SIZE;
3377 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->hidden)))
3378 flags|=SET_HIDDEN;
3379 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->style)))
3380 flags|=SET_STYLE;
3381 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->sort)))
3382 flags|=SET_SORT;
3383 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->details)))
3384 flags|=SET_DETAILS;
3385 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->thumbs)))
3386 flags|=SET_THUMBS;
3387 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->filter)))
3388 flags|=SET_FILTER;
3390 set_win->set->flags=flags;
3391 store_settings(set_win->set);
3392 save_settings();
3395 gtk_widget_destroy(window);
3398 void filer_save_settings(FilerWindow *fwin)
3400 SettingsWindow *set_win;
3401 GtkWidget *vbox, *frame;
3402 GtkWidget *path, *lbl;
3403 gint x, y;
3405 Settings *set=settings_new(fwin->sym_path);
3407 gtk_window_get_position(GTK_WINDOW(fwin->window),&x, &y);
3408 set->flags|=SET_POSITION;
3409 set->x=x;
3410 set->y=y;
3412 gtk_window_get_size(GTK_WINDOW(fwin->window),&x, &y);
3413 set->flags|=SET_SIZE;
3414 set->width=x;
3415 set->height=y;
3417 set->flags|=SET_HIDDEN;
3418 set->show_hidden=fwin->show_hidden;
3420 set->flags|=SET_STYLE;
3421 set->display_style=fwin->display_style;
3423 set->flags|=SET_SORT;
3424 set->sort_type=fwin->sort_type;
3425 set->sort_order=fwin->sort_order;
3427 set->flags|=SET_DETAILS;
3428 set->view_type=fwin->view_type;
3429 set->details_type=fwin->details_type;
3431 set->flags|=SET_THUMBS;
3432 set->show_thumbs=fwin->show_thumbs;
3434 set->flags|=SET_FILTER;
3435 set->filter_type=fwin->filter;
3436 if(fwin->filter_string)
3437 set->filter=g_strdup(fwin->filter_string);
3438 set->filter_directories=fwin->filter_directories;
3440 /* Store other parameters
3442 set_win=g_new(SettingsWindow, 1);
3444 set_win->window=gtk_dialog_new();
3445 number_of_windows++;
3447 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3448 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
3449 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3450 GTK_STOCK_OK, GTK_RESPONSE_OK);
3452 g_signal_connect(set_win->window, "destroy",
3453 G_CALLBACK(one_less_window), NULL);
3454 g_signal_connect_swapped(set_win->window, "destroy",
3455 G_CALLBACK(g_free), set_win);
3457 gtk_window_set_title(GTK_WINDOW(set_win->window),
3458 _("Select display properties to save"));
3460 vbox=GTK_DIALOG(set_win->window)->vbox;
3462 lbl=gtk_label_new(_("<b>Save display settings for directory</b>"));
3463 gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);
3464 gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 2);
3466 path=gtk_label_new(set->path);
3467 gtk_box_pack_start(GTK_BOX(vbox), path, FALSE, FALSE, 2);
3469 frame=gtk_frame_new(_("Select settings to save"));
3470 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 2);
3472 /*Make new vbox to go in the frame */
3473 vbox=gtk_vbox_new(FALSE, 2);
3474 gtk_container_add(GTK_CONTAINER(frame), vbox);
3476 set_win->pos=gtk_check_button_new_with_label(_("Position"));
3477 gtk_box_pack_start(GTK_BOX(vbox), set_win->pos, FALSE, FALSE, 2);
3478 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->pos),
3479 set->flags & SET_POSITION);
3481 set_win->size=gtk_check_button_new_with_label(_("Size"));
3482 gtk_box_pack_start(GTK_BOX(vbox), set_win->size, FALSE, FALSE, 2);
3483 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->size),
3484 set->flags & SET_SIZE);
3486 set_win->hidden=gtk_check_button_new_with_label(_("Show hidden"));
3487 gtk_box_pack_start(GTK_BOX(vbox), set_win->hidden,
3488 FALSE, FALSE, 2);
3489 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->hidden),
3490 set->flags & SET_HIDDEN);
3492 set_win->style=gtk_check_button_new_with_label(_("Display style"));
3493 gtk_box_pack_start(GTK_BOX(vbox), set_win->style,
3494 FALSE, FALSE, 2);
3495 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->style),
3496 set->flags & SET_STYLE);
3498 set_win->sort=gtk_check_button_new_with_label(_("Sort type and order"));
3499 gtk_box_pack_start(GTK_BOX(vbox), set_win->sort, FALSE, FALSE, 2);
3500 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->sort),
3501 set->flags & SET_SORT);
3503 set_win->details=gtk_check_button_new_with_label(_("Details"));
3504 gtk_box_pack_start(GTK_BOX(vbox), set_win->details, FALSE, FALSE, 2);
3505 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->details),
3506 set->flags & SET_DETAILS);
3508 set_win->thumbs=gtk_check_button_new_with_label(_("Thumbnails"));
3509 gtk_box_pack_start(GTK_BOX(vbox), set_win->thumbs,
3510 FALSE, FALSE, 2);
3511 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->thumbs),
3512 set->flags & SET_THUMBS);
3514 set_win->filter=gtk_check_button_new_with_label(_("Filter"));
3515 gtk_box_pack_start(GTK_BOX(vbox), set_win->filter,
3516 FALSE, FALSE, 2);
3517 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->filter),
3518 set->flags & SET_FILTER);
3520 set_win->set=set;
3521 g_signal_connect(set_win->window, "response",
3522 G_CALLBACK(settings_response), set_win);
3524 gtk_widget_show_all(set_win->window);
3527 static char *tip_from_desktop_file(const char *full_path)
3529 GError *error = NULL;
3530 char *comment = NULL;
3532 comment = get_value_from_desktop_file(full_path,
3533 "Desktop Entry", "Comment", &error);
3534 if (error)
3536 delayed_error("Failed to parse .desktop file '%s':\n%s",
3537 full_path, error->message);
3538 goto err;
3541 err:
3542 if (error != NULL)
3543 g_error_free(error);
3545 return comment;