r3597: Bugfix: When opening a new directory with saved settings, the filer would
[rox-filer.git] / ROX-Filer / src / filer.c
blobd7311d3fca9db10e0e48507148f4751fcbd4ef31
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* filer.c - code for handling filer windows */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <netdb.h>
32 #include <sys/param.h>
33 #include <fnmatch.h>
35 #include <gtk/gtk.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdkkeysyms.h>
39 #include "global.h"
41 #include "filer.h"
42 #include "display.h"
43 #include "main.h"
44 #include "fscache.h"
45 #include "support.h"
46 #include "gui_support.h"
47 #include "choices.h"
48 #include "pixmaps.h"
49 #include "menu.h"
50 #include "dnd.h"
51 #include "dir.h"
52 #include "diritem.h"
53 #include "run.h"
54 #include "type.h"
55 #include "options.h"
56 #include "minibuffer.h"
57 #include "icon.h"
58 #include "toolbar.h"
59 #include "bind.h"
60 #include "appinfo.h"
61 #include "mount.h"
62 #include "xml.h"
63 #include "view_iface.h"
64 #include "view_collection.h"
65 #include "view_details.h"
66 #include "action.h"
67 #include "bookmarks.h"
69 static XMLwrapper *groups = NULL;
71 /* Item we are about to display a tooltip for */
72 static DirItem *tip_item = NULL;
74 /* The window which the motion event for the tooltip came from. Use this
75 * to get the correct widget for finding the item under the pointer.
77 static GdkWindow *motion_window = NULL;
79 /* This is rather badly named. It's actually the filer window which received
80 * the last key press or Menu click event.
82 FilerWindow *window_with_focus = NULL;
84 GList *all_filer_windows = NULL;
86 static GHashTable *window_with_id = NULL;
88 static FilerWindow *window_with_primary = NULL;
90 static GHashTable *settings_table=NULL;
92 typedef struct {
93 gchar *path;
95 guint flags; /* Which parts are valid, see below */
97 gint x, y;
98 gint width, height;
99 gboolean show_hidden;
100 ViewType view_type;
101 SortType sort_type;
102 GtkSortType sort_order;
103 gboolean show_thumbs;
105 DetailsType details_type;
106 DisplayStyle display_style;
108 FilterType filter_type;
109 char *filter;
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);
160 static GdkCursor *busy_cursor = NULL;
161 static GdkCursor *crosshair = NULL;
163 /* Indicates whether the filer's display is different to the machine it
164 * is actually running on.
166 static gboolean not_local = FALSE;
168 static Option o_short_flag_names;
169 static Option o_filer_view_type;
170 Option o_filer_auto_resize, o_unique_filer_windows;
171 Option o_filer_size_limit;
173 void filer_init(void)
175 const gchar *ohost;
176 const gchar *dpy;
177 gchar *dpyhost, *tmp;
179 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
180 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
181 RESIZE_ALWAYS);
182 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
184 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
186 option_add_int(&o_filer_view_type, "filer_view_type",
187 VIEW_TYPE_COLLECTION);
189 option_add_notify(filer_options_changed);
191 busy_cursor = gdk_cursor_new(GDK_WATCH);
192 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
194 window_with_id = g_hash_table_new_full(g_str_hash, g_str_equal,
195 NULL, NULL);
197 /* Is the display on the local machine, or are we being
198 * run remotely? See filer_set_title().
200 ohost = our_host_name();
201 dpy = gdk_get_display();
202 dpyhost = g_strdup(dpy);
203 tmp = strchr(dpyhost, ':');
204 if (tmp)
205 *tmp = '\0';
207 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
209 /* Try the cannonical name for dpyhost (see our_host_name()
210 * in support.c).
212 struct hostent *ent;
214 ent = gethostbyname(dpyhost);
215 if (!ent || strcmp(ohost, ent->h_name) != 0)
216 not_local = TRUE;
219 g_free(dpyhost);
221 load_settings();
224 static gboolean if_deleted(gpointer item, gpointer removed)
226 int i = ((GPtrArray *) removed)->len;
227 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
228 char *leafname = ((DirItem *) item)->leafname;
230 while (i--)
232 if (strcmp(leafname, r[i]->leafname) == 0)
233 return TRUE;
236 return FALSE;
239 #define DECOR_BORDER 32
241 /* Resize the filer window to w x h pixels, plus border (not clamped).
242 * If triggered by a key event, warp the pointer (for SloppyFocus users).
244 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
246 GdkEvent *event;
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;
267 w = MAX(req->width, w);
268 h = MAX(req->height, h);
269 gdk_window_get_pointer(NULL, &x, &y, NULL);
270 m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(),
271 x, y);
272 gdk_window_get_position(gdk_window, &x, &y);
274 if (x + w + DECOR_BORDER >
275 monitor_geom[m].x + monitor_geom[m].width ||
276 y + h + DECOR_BORDER >
277 monitor_geom[m].y + monitor_geom[m].height)
279 if (x + w + DECOR_BORDER >
280 monitor_geom[m].x + monitor_geom[m].width)
282 x = monitor_geom[m].x + monitor_geom[m].width -
283 w - 4 - DECOR_BORDER;
285 if (y + h + DECOR_BORDER >
286 monitor_geom[m].y + monitor_geom[m].height)
288 y = monitor_geom[m].y + monitor_geom[m].height -
289 h - 4 - DECOR_BORDER;
291 gdk_window_move_resize(gdk_window, x, y, w, h);
293 else
294 gdk_window_resize(gdk_window, w, h);
296 else
297 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
299 event = gtk_get_current_event();
300 if (event && event->type == GDK_KEY_PRESS)
302 GdkWindow *win = filer_window->window->window;
304 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
305 None,
306 gdk_x11_drawable_get_xid(win),
307 0, 0, 0, 0,
308 w - 4, h - 4);
312 /* Called on a timeout while scanning or when scanning ends
313 * (whichever happens first).
315 static gint open_filer_window(FilerWindow *filer_window)
317 view_style_changed(filer_window->view, 0);
319 if (filer_window->open_timeout)
321 gtk_timeout_remove(filer_window->open_timeout);
322 filer_window->open_timeout = 0;
325 if (!GTK_WIDGET_VISIBLE(filer_window->window))
327 display_set_actual_size(filer_window, TRUE);
328 gtk_widget_show(filer_window->window);
331 return FALSE;
334 static void update_display(Directory *dir,
335 DirAction action,
336 GPtrArray *items,
337 FilerWindow *filer_window)
339 ViewIface *view = (ViewIface *) filer_window->view;
341 switch (action)
343 case DIR_ADD:
344 view_add_items(view, items);
345 /* Open and resize if currently hidden */
346 open_filer_window(filer_window);
347 break;
348 case DIR_REMOVE:
349 view_delete_if(view, if_deleted, items);
350 toolbar_update_info(filer_window);
351 break;
352 case DIR_START_SCAN:
353 set_scanning_display(filer_window, TRUE);
354 toolbar_update_info(filer_window);
355 break;
356 case DIR_END_SCAN:
357 if (filer_window->window->window)
358 gdk_window_set_cursor(
359 filer_window->window->window,
360 NULL);
361 set_scanning_display(filer_window, FALSE);
362 toolbar_update_info(filer_window);
363 open_filer_window(filer_window);
365 if (filer_window->had_cursor &&
366 !view_cursor_visible(view))
368 ViewIter start;
369 view_get_iter(view, &start, 0);
370 if (start.next(&start))
371 view_cursor_to_iter(view, &start);
372 view_show_cursor(view);
373 filer_window->had_cursor = FALSE;
375 if (filer_window->auto_select)
376 display_set_autoselect(filer_window,
377 filer_window->auto_select);
378 null_g_free(&filer_window->auto_select);
380 filer_create_thumbs(filer_window);
382 if (filer_window->thumb_queue)
383 start_thumb_scanning(filer_window);
384 break;
385 case DIR_UPDATE:
386 view_update_items(view, items);
387 break;
388 case DIR_ERROR_CHANGED:
389 filer_set_title(filer_window);
390 break;
394 static void attach(FilerWindow *filer_window)
396 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
397 view_clear(filer_window->view);
398 filer_window->scanning = TRUE;
399 dir_attach(filer_window->directory, (DirCallback) update_display,
400 filer_window);
401 filer_set_title(filer_window);
402 bookmarks_add_history(filer_window->sym_path);
404 if (filer_window->directory->error)
406 if (spring_in_progress)
407 g_printerr(_("Error scanning '%s':\n%s\n"),
408 filer_window->sym_path,
409 filer_window->directory->error);
410 else
411 delayed_error(_("Error scanning '%s':\n%s"),
412 filer_window->sym_path,
413 filer_window->directory->error);
417 static void detach(FilerWindow *filer_window)
419 g_return_if_fail(filer_window->directory != NULL);
421 dir_detach(filer_window->directory,
422 (DirCallback) update_display, filer_window);
423 g_object_unref(filer_window->directory);
424 filer_window->directory = NULL;
427 /* Returns TRUE to prevent closing the window. May offer to unmount a
428 * device.
430 gboolean filer_window_delete(GtkWidget *window,
431 GdkEvent *unused, /* (may be NULL) */
432 FilerWindow *filer_window)
434 if (mount_is_user_mounted(filer_window->real_path))
436 int action;
438 action = get_choice(PROJECT,
439 _("Do you want to unmount this device?\n\n"
440 "Unmounting a device makes it safe to remove "
441 "the disk."), 3,
442 GTK_STOCK_CANCEL, NULL,
443 GTK_STOCK_CLOSE, NULL,
444 ROX_STOCK_MOUNT, _("Unmount"));
446 if (action == 0)
447 return TRUE; /* Cancel close operation */
449 if (action == 2)
451 GList *list;
453 list = g_list_prepend(NULL, filer_window->sym_path);
454 action_mount(list, FALSE, TRUE);
455 g_list_free(list);
457 return TRUE;
461 return FALSE;
464 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
466 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
468 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
470 if (window_with_primary == filer_window)
471 window_with_primary = NULL;
473 if (window_with_focus == filer_window)
475 menu_popdown();
476 window_with_focus = NULL;
479 if (filer_window->directory)
480 detach(filer_window);
482 if (filer_window->open_timeout)
484 gtk_timeout_remove(filer_window->open_timeout);
485 filer_window->open_timeout = 0;
488 if (filer_window->auto_scroll != -1)
490 gtk_timeout_remove(filer_window->auto_scroll);
491 filer_window->auto_scroll = -1;
494 if (filer_window->thumb_queue)
495 destroy_glist(&filer_window->thumb_queue);
497 tooltip_show(NULL);
499 filer_set_id(filer_window, NULL);
501 if(filer_window->filter_string)
502 g_free(filer_window->filter_string);
503 if(filer_window->regexp)
504 g_free(filer_window->regexp);
506 g_free(filer_window->auto_select);
507 g_free(filer_window->real_path);
508 g_free(filer_window->sym_path);
509 g_free(filer_window);
511 one_less_window();
514 /* Returns TRUE iff the directory still exists. */
515 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
517 Directory *dir;
519 g_return_val_if_fail(filer_window != NULL, FALSE);
521 /* We do a fresh lookup (rather than update) because the inode may
522 * have changed.
524 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
525 if (!dir)
527 if (warning)
528 info_message(_("Directory missing/deleted"));
529 gtk_widget_destroy(filer_window->window);
530 return FALSE;
532 if (dir == filer_window->directory)
533 g_object_unref(dir);
534 else
536 detach(filer_window);
537 filer_window->directory = dir;
538 attach(filer_window);
541 return TRUE;
544 /* No items are now selected. This might be because another app claimed
545 * the selection or because the user unselected all the items.
547 void filer_lost_selection(FilerWindow *filer_window, guint time)
549 if (window_with_primary == filer_window)
551 window_with_primary = NULL;
552 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
556 /* Another app has claimed the primary selection */
557 static void filer_lost_primary(GtkWidget *window,
558 GdkEventSelection *event,
559 gpointer user_data)
561 FilerWindow *filer_window = (FilerWindow *) user_data;
563 if (window_with_primary && window_with_primary == filer_window)
565 window_with_primary = NULL;
566 set_selection_state(filer_window, FALSE);
570 /* Someone wants us to send them the selection */
571 static void selection_get(GtkWidget *widget,
572 GtkSelectionData *selection_data,
573 guint info,
574 guint time,
575 gpointer data)
577 GString *reply, *header;
578 FilerWindow *filer_window = (FilerWindow *) data;
579 ViewIter iter;
580 DirItem *item;
582 reply = g_string_new(NULL);
583 header = g_string_new(NULL);
585 switch (info)
587 case TARGET_STRING:
588 g_string_printf(header, " %s",
589 make_path(filer_window->sym_path, ""));
590 break;
591 case TARGET_URI_LIST:
592 g_string_printf(header, " file://%s%s",
593 our_host_name_for_dnd(),
594 make_path(filer_window->sym_path, ""));
595 break;
598 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
600 while ((item = iter.next(&iter)))
602 g_string_append(reply, header->str);
603 g_string_append(reply, item->leafname);
606 if (reply->len > 0)
607 gtk_selection_data_set(selection_data, xa_string,
608 8, reply->str + 1, reply->len - 1);
609 else
611 g_warning("Attempt to paste empty selection!");
612 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
615 g_string_free(reply, TRUE);
616 g_string_free(header, TRUE);
619 /* Selection has been changed -- try to grab the primary selection
620 * if we don't have it. Also called when clicking on an insensitive selection
621 * to regain primary.
622 * Also updates toolbar info.
624 void filer_selection_changed(FilerWindow *filer_window, gint time)
626 toolbar_update_info(filer_window);
628 if (window_with_primary == filer_window)
629 return; /* Already got primary */
631 if (!view_count_selected(filer_window->view))
632 return; /* Nothing selected */
634 if (filer_window->temp_item_selected == FALSE &&
635 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
636 GDK_SELECTION_PRIMARY,
637 time))
639 window_with_primary = filer_window;
640 set_selection_state(filer_window, TRUE);
642 else
643 set_selection_state(filer_window, FALSE);
646 /* Open the item (or add it to the shell command minibuffer) */
647 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
649 gboolean shift = (flags & OPEN_SHIFT) != 0;
650 gboolean close_mini = flags & OPEN_FROM_MINI;
651 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
652 DirItem *item;
653 const guchar *full_path;
654 gboolean wink = TRUE;
655 Directory *old_dir;
657 g_return_if_fail(filer_window != NULL && iter != NULL);
659 item = iter->peek(iter);
661 g_return_if_fail(item != NULL);
663 if (filer_window->mini_type == MINI_SHELL)
665 minibuffer_add(filer_window, item->leafname);
666 return;
669 if (!item->image)
670 dir_update_item(filer_window->directory, item->leafname);
672 if (item->base_type == TYPE_DIRECTORY)
674 /* Never close a filer window when opening a directory
675 * (click on a dir or click on an app with shift).
677 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
678 close_window = FALSE;
681 full_path = make_path(filer_window->sym_path, item->leafname);
682 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
683 wink = FALSE;
685 old_dir = filer_window->directory;
686 if (run_diritem(full_path, item,
687 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
688 filer_window,
689 shift))
691 if (old_dir != filer_window->directory)
692 return;
694 if (close_window)
695 gtk_widget_destroy(filer_window->window);
696 else
698 if (wink)
699 view_wink_item(filer_window->view, iter);
700 if (close_mini)
701 minibuffer_hide(filer_window);
706 static gint pointer_in(GtkWidget *widget,
707 GdkEventCrossing *event,
708 FilerWindow *filer_window)
710 may_rescan(filer_window, TRUE);
711 return FALSE;
714 static gint pointer_out(GtkWidget *widget,
715 GdkEventCrossing *event,
716 FilerWindow *filer_window)
718 tooltip_show(NULL);
719 return FALSE;
722 /* Move the cursor to the next selected item in direction 'dir'
723 * (+1 or -1).
725 void filer_next_selected(FilerWindow *filer_window, int dir)
727 ViewIter iter, cursor;
728 gboolean have_cursor;
729 ViewIface *view = filer_window->view;
731 g_return_if_fail(dir == 1 || dir == -1);
733 view_get_cursor(view, &cursor);
734 have_cursor = cursor.peek(&cursor) != NULL;
736 view_get_iter(view, &iter,
737 VIEW_ITER_SELECTED |
738 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
739 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
741 if (have_cursor && view_get_selected(view, &cursor))
742 iter.next(&iter); /* Skip the cursor itself */
744 if (iter.next(&iter))
745 view_cursor_to_iter(view, &iter);
746 else
747 gdk_beep();
749 return;
752 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
754 TargetFunc cb = filer_window->target_cb;
755 gpointer data = filer_window->target_data;
756 OpenFlags flags = 0;
757 ViewIter iter;
759 filer_target_mode(filer_window, NULL, NULL, NULL);
761 view_get_cursor(filer_window->view, &iter);
762 if (!iter.peek(&iter))
763 return;
765 if (cb)
767 cb(filer_window, &iter, data);
768 return;
771 if (event->state & GDK_SHIFT_MASK)
772 flags |= OPEN_SHIFT;
773 if (event->state & GDK_MOD1_MASK)
774 flags |= OPEN_CLOSE_WINDOW;
775 else
776 flags |= OPEN_SAME_WINDOW;
778 filer_openitem(filer_window, &iter, flags);
781 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
782 * changed. If no groups were loaded and there is no file then initialised
783 * groups to an empty document.
784 * Return the node for the 'name' group.
786 static xmlNode *group_find(char *name)
788 xmlNode *node;
789 gchar *path;
791 /* Update the groups, if possible */
792 path = choices_find_path_load("Groups.xml", PROJECT);
793 if (path)
795 XMLwrapper *wrapper;
796 wrapper = xml_cache_load(path);
797 if (wrapper)
799 if (groups)
800 g_object_unref(groups);
801 groups = wrapper;
804 g_free(path);
807 if (!groups)
809 groups = xml_new(NULL);
810 groups->doc = xmlNewDoc("1.0");
812 xmlDocSetRootElement(groups->doc,
813 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
814 return NULL;
817 node = xmlDocGetRootElement(groups->doc);
819 for (node = node->xmlChildrenNode; node; node = node->next)
821 guchar *gid;
823 gid = xmlGetProp(node, "name");
825 if (!gid)
826 continue;
828 if (strcmp(name, gid) != 0)
829 continue;
831 g_free(gid);
833 return node;
836 return NULL;
839 static void group_save(FilerWindow *filer_window, char *name)
841 xmlNode *group;
842 guchar *save_path;
843 DirItem *item;
844 ViewIter iter;
846 group = group_find(name);
847 if (group)
849 xmlUnlinkNode(group);
850 xmlFreeNode(group);
852 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
853 NULL, "group", NULL);
854 xmlSetProp(group, "name", name);
856 xmlNewTextChild(group, NULL, "directory", filer_window->sym_path);
858 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
860 while ((item = iter.next(&iter)))
861 xmlNewTextChild(group, NULL, "item", item->leafname);
863 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
864 if (save_path)
866 save_xml_file(groups->doc, save_path);
867 g_free(save_path);
871 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
873 GHashTable *in_group = (GHashTable *) data;
875 return g_hash_table_lookup(in_group,
876 iter->peek(iter)->leafname) != NULL;
879 static void group_restore(FilerWindow *filer_window, char *name)
881 GHashTable *in_group;
882 char *path;
883 xmlNode *group, *node;
885 group = group_find(name);
887 if (!group)
889 report_error(_("Group %s is not set. Select some files "
890 "and press Ctrl+%s to set the group. Press %s "
891 "on its own to reselect the files later.\n"
892 "Make sure NumLock is on if you use the keypad."),
893 name, name, name);
894 return;
897 node = get_subnode(group, NULL, "directory");
898 g_return_if_fail(node != NULL);
899 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
900 g_return_if_fail(path != NULL);
902 if (strcmp(path, filer_window->sym_path) != 0)
903 filer_change_to(filer_window, path, NULL);
904 g_free(path);
906 in_group = g_hash_table_new(g_str_hash, g_str_equal);
907 for (node = group->xmlChildrenNode; node; node = node->next)
909 gchar *leaf;
910 if (node->type != XML_ELEMENT_NODE)
911 continue;
912 if (strcmp(node->name, "item") != 0)
913 continue;
915 leaf = xmlNodeListGetString(groups->doc,
916 node->xmlChildrenNode, 1);
917 if (!leaf)
918 g_warning("Missing leafname!\n");
919 else
920 g_hash_table_insert(in_group, leaf, filer_window);
923 view_select_if(filer_window->view, &group_restore_cb, in_group);
925 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
926 g_hash_table_destroy(in_group);
929 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
931 ViewIter iter;
933 view_get_cursor(filer_window->view, &iter);
935 show_filer_menu(filer_window, NULL, &iter);
937 return TRUE;
940 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
942 ViewIface *view = filer_window->view;
943 ViewIter iter;
945 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
946 if (!iter.next(&iter))
947 return; /* No cursor */
949 if (view_get_selected(view, &iter))
950 view_set_selected(view, &iter, FALSE);
951 else
952 view_set_selected(view, &iter, TRUE);
954 if (iter.next(&iter))
955 view_cursor_to_iter(view, &iter);
958 gint filer_key_press_event(GtkWidget *widget,
959 GdkEventKey *event,
960 FilerWindow *filer_window)
962 ViewIface *view = filer_window->view;
963 ViewIter cursor;
964 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
965 guint key = event->keyval;
966 char group[2] = "1";
968 window_with_focus = filer_window;
970 /* Delay setting up the keys until now to speed loading... */
971 if (ensure_filer_menu())
973 /* Gtk updates in an idle-handler, so force a recheck now */
974 g_signal_emit_by_name(widget, "keys_changed");
977 if (focus && focus == filer_window->minibuffer)
978 if (gtk_widget_event(focus, (GdkEvent *) event))
979 return TRUE; /* Handled */
981 if (!focus)
982 gtk_widget_grab_focus(GTK_WIDGET(view));
984 view_get_cursor(view, &cursor);
985 if (!cursor.peek(&cursor) && (key == GDK_Up || key == GDK_Down))
987 ViewIter iter;
988 view_get_iter(view, &iter, 0);
989 if (iter.next(&iter))
990 view_cursor_to_iter(view, &iter);
991 gtk_widget_grab_focus(GTK_WIDGET(view)); /* Needed? */
992 return TRUE;
995 switch (key)
997 case GDK_Escape:
998 filer_target_mode(filer_window, NULL, NULL, NULL);
999 view_cursor_to_iter(filer_window->view, NULL);
1000 view_clear_selection(filer_window->view);
1001 return FALSE;
1002 case GDK_Return:
1003 return_pressed(filer_window, event);
1004 break;
1005 case GDK_ISO_Left_Tab:
1006 filer_next_selected(filer_window, -1);
1007 break;
1008 case GDK_Tab:
1009 filer_next_selected(filer_window, 1);
1010 break;
1011 case GDK_BackSpace:
1012 change_to_parent(filer_window);
1013 break;
1014 case GDK_backslash:
1016 ViewIter iter;
1018 tooltip_show(NULL);
1020 view_get_cursor(filer_window->view, &iter);
1021 show_filer_menu(filer_window,
1022 (GdkEvent *) event, &iter);
1023 break;
1025 case ' ':
1026 filer_window_toggle_cursor_item_selected(filer_window);
1027 break;
1028 default:
1029 if (key >= GDK_0 && key <= GDK_9)
1030 group[0] = key - GDK_0 + '0';
1031 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1032 group[0] = key - GDK_KP_0 + '0';
1033 else
1035 if (focus && focus != widget &&
1036 gtk_widget_get_toplevel(focus) == widget)
1037 if (gtk_widget_event(focus,
1038 (GdkEvent *) event))
1039 return TRUE; /* Handled */
1040 return FALSE;
1043 if (event->state & GDK_CONTROL_MASK)
1044 group_save(filer_window, group);
1045 else
1046 group_restore(filer_window, group);
1049 return TRUE;
1052 void filer_open_parent(FilerWindow *filer_window)
1054 char *dir;
1055 const char *current = filer_window->sym_path;
1057 if (current[0] == '/' && current[1] == '\0')
1058 return; /* Already in the root */
1060 dir = g_path_get_dirname(current);
1061 filer_opendir(dir, filer_window, NULL);
1062 g_free(dir);
1065 void change_to_parent(FilerWindow *filer_window)
1067 char *dir;
1068 const char *current = filer_window->sym_path;
1070 if (current[0] == '/' && current[1] == '\0')
1071 return; /* Already in the root */
1073 dir = g_path_get_dirname(current);
1074 filer_change_to(filer_window, dir, g_basename(current));
1075 g_free(dir);
1078 /* Removes trailing /s from path (modified in place) */
1079 static void tidy_sympath(gchar *path)
1081 int l;
1083 g_return_if_fail(path != NULL);
1085 l = strlen(path);
1086 while (l > 1 && path[l - 1] == '/')
1088 l--;
1089 path[l] = '\0';
1093 /* Make filer_window display path. When finished, highlight item 'from', or
1094 * the first item if from is NULL. If there is currently no cursor then
1095 * simply wink 'from' (if not NULL).
1096 * If the cause was a key event and we resize, warp the pointer.
1098 void filer_change_to(FilerWindow *filer_window,
1099 const char *path, const char *from)
1101 char *from_dup;
1102 char *sym_path, *real_path;
1103 Directory *new_dir;
1105 g_return_if_fail(filer_window != NULL);
1107 filer_cancel_thumbnails(filer_window);
1109 tooltip_show(NULL);
1111 sym_path = g_strdup(path);
1112 real_path = pathdup(path);
1113 new_dir = g_fscache_lookup(dir_cache, real_path);
1115 if (!new_dir)
1117 delayed_error(_("Directory '%s' is not accessible"),
1118 sym_path);
1119 g_free(real_path);
1120 g_free(sym_path);
1121 return;
1124 if (o_unique_filer_windows.int_value && !spring_in_progress)
1126 FilerWindow *fw;
1128 fw = find_filer_window(sym_path, filer_window);
1129 if (fw)
1130 gtk_widget_destroy(fw->window);
1133 from_dup = from && *from ? g_strdup(from) : NULL;
1135 detach(filer_window);
1136 g_free(filer_window->real_path);
1137 g_free(filer_window->sym_path);
1138 filer_window->real_path = real_path;
1139 filer_window->sym_path = sym_path;
1140 tidy_sympath(filer_window->sym_path);
1142 filer_window->directory = new_dir;
1144 g_free(filer_window->auto_select);
1145 filer_window->auto_select = from_dup;
1147 filer_window->had_cursor = filer_window->had_cursor ||
1148 view_cursor_visible(filer_window->view);
1150 filer_set_title(filer_window);
1151 if (filer_window->window->window)
1152 gdk_window_set_role(filer_window->window->window,
1153 filer_window->sym_path);
1154 view_cursor_to_iter(filer_window->view, NULL);
1156 attach(filer_window);
1158 check_settings(filer_window);
1160 display_set_actual_size(filer_window, FALSE);
1162 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1163 view_autosize(filer_window->view);
1165 if (filer_window->mini_type == MINI_PATH)
1166 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1167 filer_window);
1170 /* Returns a list containing the full (sym) pathname of every selected item.
1171 * You must g_free() each item in the list.
1173 GList *filer_selected_items(FilerWindow *filer_window)
1175 GList *retval = NULL;
1176 guchar *dir = filer_window->sym_path;
1177 ViewIter iter;
1178 DirItem *item;
1180 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1181 while ((item = iter.next(&iter)))
1183 retval = g_list_prepend(retval,
1184 g_strdup(make_path(dir, item->leafname)));
1187 return g_list_reverse(retval);
1190 /* Return the single selected item. Error if nothing is selected. */
1191 DirItem *filer_selected_item(FilerWindow *filer_window)
1193 ViewIter iter;
1194 DirItem *item;
1196 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1198 item = iter.next(&iter);
1199 g_return_val_if_fail(item != NULL, NULL);
1200 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1202 return item;
1205 /* Creates and shows a new filer window.
1206 * If src_win != NULL then display options can be taken from that source window.
1207 * Returns the new filer window, or NULL on error.
1208 * Note: if unique windows is in use, may return an existing window.
1210 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1211 const gchar *wm_class)
1213 FilerWindow *filer_window;
1214 char *real_path;
1215 DisplayStyle dstyle;
1216 DetailsType dtype;
1217 SortType s_type;
1218 GtkSortType s_order;
1219 Settings *dir_settings = NULL;
1221 /* Get the real pathname of the directory and copy it */
1222 real_path = pathdup(path);
1224 if (o_unique_filer_windows.int_value && !spring_in_progress)
1226 FilerWindow *same_dir_window;
1228 same_dir_window = find_filer_window(path, NULL);
1230 if (same_dir_window)
1232 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1233 return same_dir_window;
1237 filer_window = g_new(FilerWindow, 1);
1238 filer_window->message = NULL;
1239 filer_window->minibuffer = NULL;
1240 filer_window->minibuffer_label = NULL;
1241 filer_window->minibuffer_area = NULL;
1242 filer_window->temp_show_hidden = FALSE;
1243 filer_window->sym_path = g_strdup(path);
1244 filer_window->real_path = real_path;
1245 filer_window->scanning = FALSE;
1246 filer_window->had_cursor = FALSE;
1247 filer_window->auto_select = NULL;
1248 filer_window->toolbar_text = NULL;
1249 filer_window->target_cb = NULL;
1250 filer_window->mini_type = MINI_NONE;
1251 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1252 filer_window->toolbar = NULL;
1253 filer_window->toplevel_vbox = NULL;
1254 filer_window->view_hbox = NULL;
1255 filer_window->view = NULL;
1256 filer_window->scrollbar = NULL;
1257 filer_window->auto_scroll = -1;
1258 filer_window->window_id = NULL;
1260 tidy_sympath(filer_window->sym_path);
1262 /* Finds the entry for this directory in the dir cache, creating
1263 * a new one if needed. This does not cause a scan to start,
1264 * so if a new entry is created then it will be empty.
1266 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1267 if (!filer_window->directory)
1269 delayed_error(_("Directory '%s' not found."), path);
1270 g_free(filer_window->real_path);
1271 g_free(filer_window->sym_path);
1272 g_free(filer_window);
1273 return NULL;
1276 filer_window->temp_item_selected = FALSE;
1277 filer_window->flags = (FilerFlags) 0;
1278 filer_window->details_type = DETAILS_TIMES;
1279 filer_window->display_style = UNKNOWN_STYLE;
1280 filer_window->display_style_wanted = UNKNOWN_STYLE;
1281 filer_window->thumb_queue = NULL;
1282 filer_window->max_thumbs = 0;
1283 filer_window->sort_type = -1;
1285 filer_window->filter = FILER_SHOW_ALL;
1286 filer_window->filter_string = NULL;
1287 filer_window->regexp = NULL;
1289 if (src_win && o_display_inherit_options.int_value)
1291 s_type = src_win->sort_type;
1292 s_order = src_win->sort_order;
1293 dstyle = src_win->display_style_wanted;
1294 dtype = src_win->details_type;
1295 filer_window->show_hidden = src_win->show_hidden;
1296 filer_window->show_thumbs = src_win->show_thumbs;
1297 filer_window->view_type = src_win->view_type;
1299 filer_set_filter(filer_window, src_win->filter,
1300 src_win->filter_string);
1302 else
1304 s_type = o_display_sort_by.int_value;
1305 s_order = GTK_SORT_ASCENDING;
1306 dstyle = o_display_size.int_value;
1307 dtype = o_display_details.int_value;
1308 filer_window->show_hidden = o_display_show_hidden.int_value;
1309 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1310 filer_window->view_type = o_filer_view_type.int_value;
1313 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
1314 filer_window->sym_path);
1315 if (dir_settings)
1317 /* Override the current defaults with the per-directory
1318 * user settings.
1320 if (dir_settings->flags & SET_HIDDEN)
1321 filer_window->show_hidden = dir_settings->show_hidden;
1323 if (dir_settings->flags & SET_STYLE)
1324 filer_window->display_style =
1325 dir_settings->display_style;
1327 if (dir_settings->flags & SET_DETAILS)
1328 filer_window->details_type = dir_settings->details_type;
1330 if (dir_settings->flags & SET_SORT)
1332 s_type = dir_settings->sort_type;
1333 s_order = dir_settings->sort_order;
1336 if (dir_settings->flags & SET_THUMBS)
1337 filer_window->show_thumbs = dir_settings->show_thumbs;
1339 if (dir_settings->flags & SET_FILTER)
1340 filer_set_filter(filer_window,
1341 dir_settings->filter_type,
1342 dir_settings->filter);
1345 /* Add all the user-interface elements & realise */
1346 filer_add_widgets(filer_window, wm_class);
1347 if (src_win)
1348 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1349 GTK_WIN_POS_MOUSE);
1351 if (dir_settings)
1353 if(dir_settings->flags & SET_POSITION)
1354 gtk_window_move(GTK_WINDOW(filer_window->window),
1355 dir_settings->x, dir_settings->y);
1356 if(dir_settings->flags & SET_SIZE)
1357 filer_window_set_size(filer_window,
1358 dir_settings->width,
1359 dir_settings->height);
1362 /* Connect to all the signal handlers */
1363 filer_add_signals(filer_window);
1365 display_set_layout(filer_window, dstyle, dtype, TRUE);
1366 display_set_sort_type(filer_window, s_type, s_order);
1368 /* Open the window after a timeout, or when scanning stops.
1369 * Do this before attaching, because attach() might tell us to
1370 * stop scanning (if a scan isn't needed).
1372 filer_window->open_timeout = gtk_timeout_add(500,
1373 (GtkFunction) open_filer_window,
1374 filer_window);
1376 /* The view is created empty and then attach() is called, which
1377 * links the filer window to the entry in the directory cache we
1378 * looked up / created above.
1380 * The attach() function will immediately callback to the filer window
1381 * to deliver a list of all known entries in the directory (so,
1382 * the number of items will be known after attach() returns).
1384 * If the directory was not in the cache (because it hadn't been
1385 * opened it before) then the types and icons for the entries are
1386 * not know, but the list of names is.
1389 attach(filer_window);
1391 number_of_windows++;
1392 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1394 return filer_window;
1397 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1399 GtkWidget *view = NULL;
1400 Directory *dir = NULL;
1401 GHashTable *selected = NULL;
1403 g_return_if_fail(filer_window != NULL);
1405 motion_window = NULL;
1407 if (filer_window->view)
1409 /* Save the current selection */
1410 ViewIter iter;
1411 DirItem *item;
1413 selected = g_hash_table_new(g_str_hash, g_str_equal);
1414 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1415 while ((item = iter.next(&iter)))
1416 g_hash_table_insert(selected, item->leafname, "yes");
1418 /* Destroy the old view */
1419 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1420 filer_window->view = NULL;
1422 dir = filer_window->directory;
1423 g_object_ref(dir);
1424 detach(filer_window);
1427 switch (type)
1429 case VIEW_TYPE_COLLECTION:
1430 view = view_collection_new(filer_window);
1431 break;
1432 case VIEW_TYPE_DETAILS:
1433 view = view_details_new(filer_window);
1434 break;
1437 g_return_if_fail(view != NULL);
1439 filer_window->view = VIEW(view);
1440 filer_window->view_type = type;
1442 gtk_box_pack_start(filer_window->view_hbox, view, TRUE, TRUE, 0);
1443 gtk_widget_show(view);
1445 /* Drag and drop events */
1446 make_drop_target(view, 0);
1447 g_signal_connect(view, "drag_motion",
1448 G_CALLBACK(drag_motion), filer_window);
1449 g_signal_connect(view, "drag_leave",
1450 G_CALLBACK(drag_leave), filer_window);
1451 g_signal_connect(view, "drag_end",
1452 G_CALLBACK(drag_end), filer_window);
1453 /* Dragging from us... */
1454 g_signal_connect(view, "drag_data_get",
1455 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1457 if (dir)
1459 /* Only when changing type. Otherwise, will attach later. */
1460 filer_window->directory = dir;
1461 attach(filer_window);
1463 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
1464 view_autosize(filer_window->view);
1467 if (selected)
1469 ViewIter iter;
1470 DirItem *item;
1472 view_get_iter(filer_window->view, &iter, 0);
1473 while ((item = iter.next(&iter)))
1475 if (g_hash_table_lookup(selected, item->leafname))
1476 view_set_selected(filer_window->view,
1477 &iter, TRUE);
1479 g_hash_table_destroy(selected);
1483 /* This adds all the widgets to a new filer window. It is in a separate
1484 * function because filer_opendir() was getting too long...
1486 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1488 GtkWidget *hbox, *vbox;
1490 /* Create the top-level window widget */
1491 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1492 filer_set_title(filer_window);
1493 if (wm_class)
1494 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1495 wm_class, PROJECT);
1497 /* This property is cleared when the window is destroyed.
1498 * You can thus ref filer_window->window and use this to see
1499 * if the window no longer exists.
1501 g_object_set_data(G_OBJECT(filer_window->window),
1502 "filer_window", filer_window);
1504 /* Create this now to make the Adjustment before the View */
1505 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1507 vbox = gtk_vbox_new(FALSE, 0);
1508 gtk_container_add(GTK_CONTAINER(filer_window->window), vbox);
1510 filer_window->toplevel_vbox = GTK_BOX(vbox);
1512 /* If there's a message that should be displayed in each window (eg
1513 * 'Running as root'), add it here.
1515 if (show_user_message)
1517 filer_window->message = gtk_label_new(show_user_message);
1518 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1519 FALSE, TRUE, 0);
1520 gtk_widget_show(filer_window->message);
1523 hbox = gtk_hbox_new(FALSE, 0);
1524 filer_window->view_hbox = GTK_BOX(hbox);
1525 gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
1526 /* Add the main View widget */
1527 filer_set_view_type(filer_window, filer_window->view_type);
1528 /* Put the scrollbar next to the View */
1529 gtk_box_pack_end(GTK_BOX(hbox),
1530 filer_window->scrollbar, FALSE, TRUE, 0);
1531 gtk_widget_show(hbox);
1533 /* If we want a toolbar, create it now */
1534 toolbar_update_toolbar(filer_window);
1536 /* And the minibuffer (hidden to start with) */
1537 create_minibuffer(filer_window);
1538 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1539 FALSE, TRUE, 0);
1541 /* And the thumbnail progress bar (also hidden) */
1543 GtkWidget *cancel;
1545 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1546 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1547 FALSE, TRUE, 0);
1549 filer_window->thumb_progress = gtk_progress_bar_new();
1551 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1552 filer_window->thumb_progress, TRUE, TRUE, 0);
1554 cancel = gtk_button_new_with_label(_("Cancel"));
1555 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1556 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1557 cancel, FALSE, TRUE, 0);
1558 g_signal_connect_swapped(cancel, "clicked",
1559 G_CALLBACK(filer_cancel_thumbnails),
1560 filer_window);
1563 gtk_widget_show(vbox);
1564 gtk_widget_show(filer_window->scrollbar);
1566 gtk_widget_realize(filer_window->window);
1568 gdk_window_set_role(filer_window->window->window,
1569 filer_window->sym_path);
1571 filer_window_set_size(filer_window, 4, 4);
1574 static void filer_add_signals(FilerWindow *filer_window)
1576 GtkTargetEntry target_table[] =
1578 {"text/uri-list", 0, TARGET_URI_LIST},
1579 {"STRING", 0, TARGET_STRING},
1580 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1581 {"UTF8_STRING", 0, TARGET_STRING},
1584 /* Events on the top-level window */
1585 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1586 g_signal_connect(filer_window->window, "enter-notify-event",
1587 G_CALLBACK(pointer_in), filer_window);
1588 g_signal_connect(filer_window->window, "leave-notify-event",
1589 G_CALLBACK(pointer_out), filer_window);
1590 g_signal_connect(filer_window->window, "destroy",
1591 G_CALLBACK(filer_window_destroyed), filer_window);
1592 g_signal_connect(filer_window->window, "delete-event",
1593 G_CALLBACK(filer_window_delete), filer_window);
1595 g_signal_connect(filer_window->window, "selection_clear_event",
1596 G_CALLBACK(filer_lost_primary), filer_window);
1598 g_signal_connect(filer_window->window, "selection_get",
1599 G_CALLBACK(selection_get), filer_window);
1600 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1601 GDK_SELECTION_PRIMARY,
1602 target_table,
1603 sizeof(target_table) / sizeof(*target_table));
1605 g_signal_connect(filer_window->window, "popup-menu",
1606 G_CALLBACK(popup_menu), filer_window);
1607 g_signal_connect(filer_window->window, "key_press_event",
1608 G_CALLBACK(filer_key_press_event), filer_window);
1610 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1611 filer_keys);
1614 static gint clear_scanning_display(FilerWindow *filer_window)
1616 if (filer_exists(filer_window))
1617 filer_set_title(filer_window);
1618 return FALSE;
1621 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1623 if (scanning == filer_window->scanning)
1624 return;
1625 filer_window->scanning = scanning;
1627 if (scanning)
1628 filer_set_title(filer_window);
1629 else
1630 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1631 filer_window);
1634 /* Note that filer_window may not exist after this call.
1635 * Returns TRUE iff the directory still exists.
1637 gboolean filer_update_dir(FilerWindow *filer_window, gboolean warning)
1639 gboolean still_exists;
1641 still_exists = may_rescan(filer_window, warning);
1643 if (still_exists)
1644 dir_update(filer_window->directory, filer_window->sym_path);
1646 return still_exists;
1649 void filer_update_all(void)
1651 GList *next = all_filer_windows;
1653 while (next)
1655 FilerWindow *filer_window = (FilerWindow *) next->data;
1657 /* Updating directory may remove it from list -- stop sending
1658 * patches to move this line!
1660 next = next->next;
1662 filer_update_dir(filer_window, TRUE);
1666 /* Refresh the various caches even if we don't think we need to */
1667 void full_refresh(void)
1669 mount_update(TRUE);
1670 reread_mime_files(); /* Refreshes all windows */
1673 /* See whether a filer window with a given path already exists
1674 * and is different from diff.
1676 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1678 GList *next;
1680 for (next = all_filer_windows; next; next = next->next)
1682 FilerWindow *filer_window = (FilerWindow *) next->data;
1684 if (filer_window != diff &&
1685 strcmp(sym_path, filer_window->sym_path) == 0)
1686 return filer_window;
1689 return NULL;
1692 /* This path has been mounted/umounted/deleted some files - update all dirs */
1693 void filer_check_mounted(const char *real_path)
1695 GList *next = all_filer_windows;
1696 gchar *parent;
1697 int len;
1698 gboolean resize = o_filer_auto_resize.int_value == RESIZE_ALWAYS;
1700 /* DOS disks, etc, often don't change the mtime of the root directory
1701 * on modification, so force a refresh now.
1703 g_fscache_update(dir_cache, real_path);
1705 len = strlen(real_path);
1707 while (next)
1709 FilerWindow *filer_window = (FilerWindow *) next->data;
1711 next = next->next;
1713 if (strncmp(real_path, filer_window->real_path, len) == 0)
1715 char s = filer_window->real_path[len];
1717 if (s == '/' || s == '\0')
1719 if (filer_update_dir(filer_window, FALSE) &&
1720 resize)
1721 view_autosize(filer_window->view);
1726 parent = g_path_get_dirname(real_path);
1727 refresh_dirs(parent);
1728 g_free(parent);
1730 icons_may_update(real_path);
1733 /* Close all windows displaying 'path' or subdirectories of 'path' */
1734 void filer_close_recursive(const char *path)
1736 GList *next = all_filer_windows;
1737 gchar *real;
1738 int len;
1740 real = pathdup(path);
1741 len = strlen(real);
1743 while (next)
1745 FilerWindow *filer_window = (FilerWindow *) next->data;
1747 next = next->next;
1749 if (strncmp(real, filer_window->real_path, len) == 0)
1751 char s = filer_window->real_path[len];
1753 if (len == 1 || s == '/' || s == '\0')
1754 gtk_widget_destroy(filer_window->window);
1759 /* Like minibuffer_show(), except that:
1760 * - It returns FALSE (to be used from an idle callback)
1761 * - It checks that the filer window still exists.
1763 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1765 if (filer_exists(filer_window))
1766 minibuffer_show(filer_window, MINI_PATH);
1767 return FALSE;
1770 /* TRUE iff filer_window points to an existing FilerWindow
1771 * structure.
1773 gboolean filer_exists(FilerWindow *filer_window)
1775 GList *next;
1777 for (next = all_filer_windows; next; next = next->next)
1779 FilerWindow *fw = (FilerWindow *) next->data;
1781 if (fw == filer_window)
1782 return TRUE;
1785 return FALSE;
1788 FilerWindow *filer_get_by_id(const char *id)
1790 return g_hash_table_lookup(window_with_id, id);
1793 void filer_set_id(FilerWindow *filer_window, const char *id)
1795 g_return_if_fail(filer_window != NULL);
1797 if (filer_window->window_id)
1799 g_hash_table_remove(window_with_id, filer_window->window_id);
1800 g_free(filer_window->window_id);
1801 filer_window->window_id = NULL;
1804 if (id)
1806 filer_window->window_id = g_strdup(id);
1807 g_hash_table_insert(window_with_id,
1808 filer_window->window_id,
1809 filer_window);
1813 /* Make sure the window title is up-to-date */
1814 void filer_set_title(FilerWindow *filer_window)
1816 gchar *title = NULL;
1817 guchar *flags = "";
1818 gchar *hidden = "";
1820 if (filer_window->scanning ||
1821 filer_window->filter != FILER_SHOW_ALL ||
1822 filer_window->show_hidden || filer_window->show_thumbs)
1824 if (o_short_flag_names.int_value)
1827 switch(filer_window->filter) {
1828 case FILER_SHOW_ALL:
1829 hidden=filer_window->show_hidden? "A": "";
1830 break;
1831 case FILER_SHOW_GLOB: hidden="G"; break;
1832 case FILER_SHOW_REGEXP: hidden="R"; break;
1833 default: break;
1836 flags = g_strconcat(" +",
1837 filer_window->scanning ? _("S") : "",
1838 hidden,
1839 filer_window->show_thumbs ? _("T") : "",
1840 NULL);
1842 else
1844 switch(filer_window->filter) {
1845 case FILER_SHOW_ALL:
1846 hidden=g_strdup(filer_window->show_hidden? "All, ": "");
1847 break;
1848 case FILER_SHOW_GLOB:
1849 hidden=g_strdup_printf("Glob (%s), ",
1850 filer_window->filter_string);
1851 break;
1852 case FILER_SHOW_REGEXP:
1853 hidden=g_strdup_printf("Regexp (%s), ",
1854 filer_window->filter_string);
1855 break;
1856 default:
1857 hidden=g_strdup("");
1858 break;
1860 flags = g_strconcat(" (",
1861 filer_window->scanning ? _("Scanning, ") : "",
1862 hidden,
1863 filer_window->show_thumbs ? _("Thumbs, ") : "",
1864 NULL);
1865 flags[strlen(flags) - 2] = ')';
1866 g_free(hidden);
1870 if (not_local)
1871 title = g_strconcat("//", our_host_name(),
1872 filer_window->sym_path, flags, NULL);
1874 if (!title && home_dir_len > 1 &&
1875 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1877 guchar sep = filer_window->sym_path[home_dir_len];
1879 if (sep == '\0' || sep == '/')
1880 title = g_strconcat("~",
1881 filer_window->sym_path + home_dir_len,
1882 flags,
1883 NULL);
1886 if (!title)
1887 title = g_strconcat(filer_window->sym_path, flags, NULL);
1889 ensure_utf8(&title);
1891 if (filer_window->directory->error)
1893 gchar *old = title;
1894 title = g_strconcat(old, ": ", filer_window->directory->error,
1895 NULL);
1896 g_free(old);
1899 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1901 g_free(title);
1903 if (flags[0] != '\0')
1904 g_free(flags);
1907 /* Reconnect to the same directory (used when the Show Hidden option is
1908 * toggled). This has the side-effect of updating the window title.
1910 void filer_detach_rescan(FilerWindow *filer_window)
1912 Directory *dir = filer_window->directory;
1914 g_object_ref(dir);
1915 detach(filer_window);
1916 filer_window->directory = dir;
1917 attach(filer_window);
1920 /* Puts the filer window into target mode. When an item is chosen,
1921 * fn(filer_window, iter, data) is called. 'reason' will be displayed
1922 * on the toolbar while target mode is active.
1924 * Use fn == NULL to cancel target mode.
1926 void filer_target_mode(FilerWindow *filer_window,
1927 TargetFunc fn,
1928 gpointer data,
1929 const char *reason)
1931 TargetFunc old_fn = filer_window->target_cb;
1933 if (fn != old_fn)
1934 gdk_window_set_cursor(
1935 GTK_WIDGET(filer_window->view)->window,
1936 fn ? crosshair : NULL);
1938 filer_window->target_cb = fn;
1939 filer_window->target_data = data;
1941 if (filer_window->toolbar_text == NULL)
1942 return;
1944 if (fn)
1945 gtk_label_set_text(
1946 GTK_LABEL(filer_window->toolbar_text), reason);
1947 else if (o_toolbar_info.int_value)
1949 if (old_fn)
1950 toolbar_update_info(filer_window);
1952 else
1953 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1956 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
1958 GtkStateType old_state = filer_window->selection_state;
1960 filer_window->selection_state = normal
1961 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
1963 if (old_state != filer_window->selection_state
1964 && view_count_selected(filer_window->view))
1965 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
1968 void filer_cancel_thumbnails(FilerWindow *filer_window)
1970 gtk_widget_hide(filer_window->thumb_bar);
1972 destroy_glist(&filer_window->thumb_queue);
1973 filer_window->max_thumbs = 0;
1976 /* Generate the next thumb for this window. The window object is
1977 * unref'd when there is nothing more to do.
1978 * If the window no longer has a filer window, nothing is done.
1980 static gboolean filer_next_thumb_real(GObject *window)
1982 FilerWindow *filer_window;
1983 gchar *path;
1984 int done, total;
1986 filer_window = g_object_get_data(window, "filer_window");
1988 if (!filer_window)
1990 g_object_unref(window);
1991 return FALSE;
1994 if (!filer_window->thumb_queue)
1996 filer_cancel_thumbnails(filer_window);
1997 g_object_unref(window);
1998 return FALSE;
2001 total = filer_window->max_thumbs;
2002 done = total - g_list_length(filer_window->thumb_queue);
2004 path = (gchar *) filer_window->thumb_queue->data;
2006 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2008 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2009 path);
2010 g_free(path);
2012 gtk_progress_bar_set_fraction(
2013 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2014 done / (float) total);
2016 return FALSE;
2019 /* path is the thumb just loaded, if any.
2020 * window is unref'd (eventually).
2022 static void filer_next_thumb(GObject *window, const gchar *path)
2024 if (path)
2025 dir_force_update_path(path);
2027 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
2030 static void start_thumb_scanning(FilerWindow *filer_window)
2032 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2033 return; /* Already scanning */
2035 gtk_widget_show_all(filer_window->thumb_bar);
2037 g_object_ref(G_OBJECT(filer_window->window));
2038 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2041 /* Set this image to be loaded some time in the future */
2042 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2044 filer_window->max_thumbs++;
2046 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2047 g_strdup(path));
2049 if (filer_window->scanning)
2050 return; /* Will start when scan ends */
2052 start_thumb_scanning(filer_window);
2055 /* If thumbnail display is on, look through all the items in this directory
2056 * and start creating or updating the thumbnails as needed.
2058 void filer_create_thumbs(FilerWindow *filer_window)
2060 DirItem *item;
2061 ViewIter iter;
2063 if (!filer_window->show_thumbs)
2064 return;
2066 view_get_iter(filer_window->view, &iter, 0);
2068 while ((item = iter.next(&iter)))
2070 MaskedPixmap *pixmap;
2071 const guchar *path;
2072 gboolean found;
2074 if (item->base_type != TYPE_FILE)
2075 continue;
2077 /*if (strcmp(item->mime_type->media_type, "image") != 0)
2078 continue;*/
2080 path = make_path(filer_window->real_path, item->leafname);
2082 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2083 FSCACHE_LOOKUP_ONLY_NEW, &found);
2084 if (pixmap)
2085 g_object_unref(pixmap);
2087 /* If we didn't get an image, it could be because:
2089 * - We're loading the image now. found is TRUE,
2090 * and we'll update the item later.
2091 * - We tried to load the image and failed. found
2092 * is TRUE.
2093 * - We haven't tried loading the image. found is
2094 * FALSE, and we start creating the thumb here.
2096 if (!found)
2097 filer_create_thumb(filer_window, path);
2101 static void filer_options_changed(void)
2103 if (o_short_flag_names.has_changed)
2105 GList *next;
2107 for (next = all_filer_windows; next; next = next->next)
2109 FilerWindow *filer_window = (FilerWindow *) next->data;
2111 filer_set_title(filer_window);
2116 /* Append interesting information to this GString */
2117 void filer_add_tip_details(FilerWindow *filer_window,
2118 GString *tip, DirItem *item)
2120 const guchar *fullpath = NULL;
2122 fullpath = make_path(filer_window->real_path, item->leafname);
2124 if (item->flags & ITEM_FLAG_SYMLINK)
2126 char *target;
2128 target = readlink_dup(fullpath);
2129 if (target)
2131 ensure_utf8(&target);
2133 g_string_append(tip, _("Symbolic link to "));
2134 g_string_append(tip, target);
2135 g_string_append_c(tip, '\n');
2136 g_free(target);
2140 if (item->flags & ITEM_FLAG_APPDIR)
2142 XMLwrapper *info;
2143 xmlNode *node;
2145 info = appinfo_get(fullpath, item);
2146 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
2148 guchar *str;
2149 str = xmlNodeListGetString(node->doc,
2150 node->xmlChildrenNode, 1);
2151 if (str)
2153 g_string_append(tip, str);
2154 g_string_append_c(tip, '\n');
2155 g_free(str);
2158 if (info)
2159 g_object_unref(info);
2162 if (!g_utf8_validate(item->leafname, -1, NULL))
2163 g_string_append(tip,
2164 _("This filename is not valid UTF-8. "
2165 "You should rename it.\n"));
2168 /* Return the selection as a text/uri-list.
2169 * g_free() the result.
2171 static guchar *filer_create_uri_list(FilerWindow *filer_window)
2173 GString *string;
2174 GString *leader;
2175 ViewIter iter;
2176 DirItem *item;
2177 guchar *retval;
2179 g_return_val_if_fail(filer_window != NULL, NULL);
2181 string = g_string_new(NULL);
2183 leader = g_string_new(filer_window->sym_path);
2184 if (leader->str[leader->len - 1] != '/')
2185 g_string_append_c(leader, '/');
2187 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
2188 while ((item = iter.next(&iter)))
2190 gchar *uri, *path;
2192 path = g_strconcat(leader->str, item->leafname, NULL);
2193 uri = encode_path_as_uri(path);
2194 g_string_append(string, uri);
2195 g_string_append(string, "\r\n");
2196 g_free(path);
2197 g_free(uri);
2200 g_string_free(leader, TRUE);
2201 retval = string->str;
2202 g_string_free(string, FALSE);
2204 return retval;
2207 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
2209 BindAction action;
2210 ViewIface *view = filer_window->view;
2211 DirItem *item = NULL;
2212 gboolean press = event->type == GDK_BUTTON_PRESS;
2213 ViewIter iter;
2214 OpenFlags flags = 0;
2216 if (event->button > 3)
2217 return;
2219 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2220 item = iter.peek(&iter);
2222 if (item && event->button == 1 &&
2223 view_get_selected(view, &iter) &&
2224 filer_window->selection_state == GTK_STATE_INSENSITIVE)
2226 /* Possibly a really slow DnD operation? */
2227 filer_window->temp_item_selected = FALSE;
2229 filer_selection_changed(filer_window, event->time);
2230 return;
2233 if (filer_window->target_cb)
2235 dnd_motion_ungrab();
2236 if (item && press && event->button == 1)
2237 filer_window->target_cb(filer_window, &iter,
2238 filer_window->target_data);
2240 filer_target_mode(filer_window, NULL, NULL, NULL);
2242 return;
2245 if (!o_single_click.int_value)
2247 /* Make sure both parts of a double-click fall on
2248 * the same file.
2250 static guchar *first_click = NULL;
2251 static guchar *second_click = NULL;
2253 if (event->type == GDK_BUTTON_PRESS)
2255 g_free(first_click);
2256 first_click = second_click;
2258 if (item)
2259 second_click = g_strdup(item->leafname);
2260 else
2261 second_click = NULL;
2264 if (event->type == GDK_2BUTTON_PRESS)
2266 if (first_click && second_click &&
2267 strcmp(first_click, second_click) != 0)
2268 return;
2269 if ((first_click || second_click) &&
2270 !(first_click && second_click))
2271 return;
2275 action = bind_lookup_bev(
2276 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
2277 event);
2279 switch (action)
2281 case ACT_CLEAR_SELECTION:
2282 view_clear_selection(view);
2283 break;
2284 case ACT_TOGGLE_SELECTED:
2285 view_set_selected(view, &iter,
2286 !view_get_selected(view, &iter));
2287 break;
2288 case ACT_SELECT_EXCL:
2289 view_select_only(view, &iter);
2290 break;
2291 case ACT_EDIT_ITEM:
2292 flags |= OPEN_SHIFT;
2293 /* (no break) */
2294 case ACT_OPEN_ITEM:
2295 if (event->button != 1 || event->state & GDK_MOD1_MASK)
2296 flags |= OPEN_CLOSE_WINDOW;
2297 else
2298 flags |= OPEN_SAME_WINDOW;
2299 if (o_new_button_1.int_value)
2300 flags ^= OPEN_SAME_WINDOW;
2301 if (event->type == GDK_2BUTTON_PRESS)
2302 view_set_selected(view, &iter, FALSE);
2303 dnd_motion_ungrab();
2305 filer_openitem(filer_window, &iter, flags);
2306 break;
2307 case ACT_POPUP_MENU:
2308 dnd_motion_ungrab();
2309 tooltip_show(NULL);
2310 show_filer_menu(filer_window,
2311 (GdkEvent *) event, &iter);
2312 break;
2313 case ACT_PRIME_AND_SELECT:
2314 if (item && !view_get_selected(view, &iter))
2315 view_select_only(view, &iter);
2316 dnd_motion_start(MOTION_READY_FOR_DND);
2317 break;
2318 case ACT_PRIME_AND_TOGGLE:
2319 view_set_selected(view, &iter,
2320 !view_get_selected(view, &iter));
2321 dnd_motion_start(MOTION_READY_FOR_DND);
2322 break;
2323 case ACT_PRIME_FOR_DND:
2324 dnd_motion_start(MOTION_READY_FOR_DND);
2325 break;
2326 case ACT_IGNORE:
2327 if (press && event->button < 4)
2329 if (item)
2330 view_wink_item(view, &iter);
2331 dnd_motion_start(MOTION_NONE);
2333 break;
2334 case ACT_LASSO_CLEAR:
2335 view_clear_selection(view);
2336 /* (no break) */
2337 case ACT_LASSO_MODIFY:
2338 view_start_lasso_box(view, event);
2339 break;
2340 case ACT_RESIZE:
2341 view_autosize(filer_window->view);
2342 break;
2343 default:
2344 g_warning("Unsupported action : %d\n", action);
2345 break;
2349 /* It's time to make the tooltip appear. If we're not over the item any
2350 * more, or the item doesn't need a tooltip, do nothing.
2352 static gboolean tooltip_activate(GtkWidget *window)
2354 FilerWindow *filer_window;
2355 ViewIface *view;
2356 ViewIter iter;
2357 gint x, y;
2358 DirItem *item = NULL;
2359 GString *tip = NULL;
2361 g_return_val_if_fail(tip_item != NULL, 0);
2363 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2365 if (!motion_window || !filer_window)
2366 return FALSE; /* Window has been destroyed */
2368 view = filer_window->view;
2370 tooltip_show(NULL);
2372 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2373 view_get_iter_at_point(view, &iter, motion_window, x, y);
2375 item = iter.peek(&iter);
2376 if (item != tip_item)
2377 return FALSE; /* Not still under the pointer */
2379 /* OK, the filer window still exists and the pointer is still
2380 * over the same item. Do we need to show a tip?
2383 tip = g_string_new(NULL);
2385 view_extend_tip(filer_window->view, &iter, tip);
2387 filer_add_tip_details(filer_window, tip, tip_item);
2389 if (tip->len > 1)
2391 g_string_truncate(tip, tip->len - 1);
2393 tooltip_show(tip->str);
2396 g_string_free(tip, TRUE);
2398 return FALSE;
2401 /* Motion detected on the View widget */
2402 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2404 ViewIface *view = filer_window->view;
2405 ViewIter iter;
2406 DirItem *item;
2408 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2409 item = iter.peek(&iter);
2411 if (item)
2413 if (item != tip_item)
2415 tooltip_show(NULL);
2417 tip_item = item;
2418 motion_window = event->window;
2419 tooltip_prime((GtkFunction) tooltip_activate,
2420 G_OBJECT(filer_window->window));
2423 else
2425 tooltip_show(NULL);
2426 tip_item = NULL;
2429 if (motion_state != MOTION_READY_FOR_DND)
2430 return FALSE;
2432 if (!dnd_motion_moved(event))
2433 return FALSE;
2435 view_get_iter_at_point(view, &iter,
2436 event->window,
2437 event->x - (event->x_root - drag_start_x),
2438 event->y - (event->y_root - drag_start_y));
2439 item = iter.peek(&iter);
2440 if (!item)
2441 return FALSE;
2443 view_wink_item(view, NULL);
2445 if (!view_get_selected(view, &iter))
2447 if (event->state & GDK_BUTTON1_MASK)
2449 /* Select just this one */
2450 filer_window->temp_item_selected = TRUE;
2451 view_select_only(view, &iter);
2453 else
2455 if (view_count_selected(view) == 0)
2456 filer_window->temp_item_selected = TRUE;
2457 view_set_selected(view, &iter, TRUE);
2461 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2463 if (view_count_selected(view) == 1)
2465 if (!item->image)
2466 item = dir_update_item(filer_window->directory,
2467 item->leafname);
2469 if (!item)
2471 report_error(_("Item no longer exists!"));
2472 return FALSE;
2475 drag_one_item(GTK_WIDGET(view), event,
2476 make_path(filer_window->sym_path, item->leafname),
2477 item, item->image);
2478 #if 0
2479 /* XXX: Use thumbnail */
2480 item, view ? view->image : NULL);
2481 #endif
2483 else
2485 guchar *uris;
2487 uris = filer_create_uri_list(filer_window);
2488 drag_selection(GTK_WIDGET(view), event, uris);
2489 g_free(uris);
2492 return FALSE;
2495 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2496 FilerWindow *filer_window)
2498 filer_set_autoscroll(filer_window, FALSE);
2500 if (filer_window->temp_item_selected)
2502 view_clear_selection(filer_window->view);
2503 filer_window->temp_item_selected = FALSE;
2507 /* Remove highlights */
2508 static void drag_leave(GtkWidget *widget,
2509 GdkDragContext *context,
2510 guint32 time,
2511 FilerWindow *filer_window)
2513 dnd_spring_abort();
2516 /* Called during the drag when the mouse is in a widget registered
2517 * as a drop target. Returns TRUE if we can accept the drop.
2519 static gboolean drag_motion(GtkWidget *widget,
2520 GdkDragContext *context,
2521 gint x,
2522 gint y,
2523 guint time,
2524 FilerWindow *filer_window)
2526 DirItem *item;
2527 ViewIface *view = filer_window->view;
2528 ViewIter iter;
2529 GdkDragAction action = context->suggested_action;
2530 const guchar *new_path = NULL;
2531 const char *type = NULL;
2532 gboolean retval = FALSE;
2533 gboolean same_window;
2535 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value)
2537 guint state;
2538 gdk_window_get_pointer(NULL, NULL, NULL, &state);
2539 if (state & GDK_BUTTON1_MASK)
2540 action = GDK_ACTION_ASK;
2543 same_window = gtk_drag_get_source_widget(context) == widget;
2545 filer_set_autoscroll(filer_window, TRUE);
2547 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2549 GdkWindow *bin;
2550 int bin_y;
2551 /* Correct for position of bin window */
2552 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2553 gdk_window_get_position(bin, NULL, &bin_y);
2554 y -= bin_y;
2557 if (o_dnd_drag_to_icons.int_value)
2559 view_get_iter_at_point(view, &iter, widget->window, x, y);
2560 item = iter.peek(&iter);
2562 else
2563 item = NULL;
2565 if (item && same_window && view_get_selected(view, &iter))
2566 type = NULL;
2567 else
2568 type = dnd_motion_item(context, &item);
2570 if (!type)
2571 item = NULL;
2573 /* Don't allow drops to non-writeable directories. BUT, still
2574 * allow drops on non-writeable SUBdirectories so that we can
2575 * do the spring-open thing.
2577 if (item && type == drop_dest_dir &&
2578 !(item->flags & ITEM_FLAG_APPDIR))
2580 dnd_spring_load(context, filer_window);
2582 else
2583 dnd_spring_abort();
2585 if (item)
2586 view_cursor_to_iter(view, &iter);
2587 else
2589 view_cursor_to_iter(view, NULL);
2591 /* Disallow background drops within a single window */
2592 if (type && same_window)
2593 type = NULL;
2596 if (type)
2598 if (item)
2599 new_path = make_path(filer_window->sym_path,
2600 item->leafname);
2601 else
2602 new_path = filer_window->sym_path;
2605 /* Don't ask about dragging to an application! */
2606 if (type == drop_dest_prog && action == GDK_ACTION_ASK)
2607 action = GDK_ACTION_COPY;
2609 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2610 if (type)
2612 gdk_drag_status(context, action, time);
2613 g_dataset_set_data_full(context, "drop_dest_path",
2614 g_strdup(new_path), g_free);
2615 retval = TRUE;
2618 return retval;
2621 static gboolean as_timeout(FilerWindow *filer_window)
2623 gboolean retval;
2625 retval = view_auto_scroll_callback(filer_window->view);
2627 if (!retval)
2628 filer_window->auto_scroll = -1;
2630 return retval;
2633 /* When autoscroll is on, a timer keeps track of the pointer position.
2634 * While it's near the top or bottom of the window, the window scrolls.
2636 * If the mouse buttons are released, the pointer leaves the window, or
2637 * a drag-and-drop operation finishes, auto_scroll is turned off.
2639 void filer_set_autoscroll(FilerWindow *filer_window, gboolean auto_scroll)
2641 g_return_if_fail(filer_window != NULL);
2643 if (auto_scroll)
2645 if (filer_window->auto_scroll != -1)
2646 return; /* Already on! */
2648 filer_window->auto_scroll = gtk_timeout_add(50,
2649 (GtkFunction) as_timeout,
2650 filer_window);
2652 else
2654 if (filer_window->auto_scroll == -1)
2655 return; /* Already off! */
2657 gtk_timeout_remove(filer_window->auto_scroll);
2658 filer_window->auto_scroll = -1;
2662 #define ZERO_MNT "/uri/0install"
2664 static void refresh_done(FilerWindow *filer_window)
2666 if (filer_exists(filer_window))
2667 filer_update_dir(filer_window, TRUE);
2670 void filer_refresh(FilerWindow *filer_window)
2672 if (!strncmp(ZERO_MNT "/", filer_window->real_path, sizeof(ZERO_MNT)))
2674 /* Try to run 0refresh */
2675 gint pid;
2676 gchar *argv[] = {"0refresh", NULL, NULL};
2677 const char *host = filer_window->real_path + sizeof(ZERO_MNT);
2678 const char *slash;
2680 slash = strchr(host, '/');
2681 if (slash)
2682 argv[1] = g_strndup(host, slash - host);
2683 else
2684 argv[1] = g_strdup(host);
2685 pid = rox_spawn(filer_window->real_path, (const char **) argv);
2686 g_free(argv[1]);
2687 if (pid)
2688 on_child_death(pid, (CallbackFn) refresh_done,
2689 filer_window);
2692 full_refresh();
2695 gboolean filer_match_filter(FilerWindow *filer_window, const gchar *filename)
2697 if(filename[0]=='.' &&
2698 (!filer_window->temp_show_hidden && !filer_window->show_hidden))
2699 return FALSE;
2701 /*printf("%d %s\n", filer_window->filter, filename);*/
2702 switch(filer_window->filter) {
2703 case FILER_SHOW_GLOB:
2704 return fnmatch(filer_window->filter_string,
2705 filename, 0)==0;
2707 case FILER_SHOW_REGEXP: /* Unimplemented */
2709 case FILER_SHOW_ALL:
2710 default:
2711 break;
2713 return TRUE;
2716 /* Provided to hide the implementation */
2717 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
2719 filer_window->show_hidden=hidden;
2722 void filer_set_filter(FilerWindow *filer_window, FilterType type,
2723 const gchar *filter_string)
2725 /*printf("filer_set_filter(%p, %d, %s)\n", filer_window, type,
2726 filter_string? filter_string: "NULL");*/
2727 /* Is this new filter the same as the old one? */
2728 if(filer_window->filter==type) {
2729 switch(filer_window->filter) {
2730 case FILER_SHOW_ALL:
2731 return;
2732 case FILER_SHOW_GLOB:
2733 case FILER_SHOW_REGEXP:
2734 if(strcmp(filer_window->filter_string,
2735 filter_string)==0)
2736 return;
2737 break;
2741 /* Clean up old filter */
2742 if(filer_window->filter_string) {
2743 g_free(filer_window->filter_string);
2744 filer_window->filter_string=NULL;
2746 /* Also clean up compiled regexp when implemented */
2748 /*printf("set %d %s\n", type,
2749 filter_string? filter_string: "NULL");*/
2750 filer_window->filter=type;
2751 switch(type) {
2752 case FILER_SHOW_ALL:
2753 /* No extra work */
2754 break;
2756 case FILER_SHOW_GLOB:
2757 filer_window->filter_string=g_strdup(filter_string);
2758 break;
2760 case FILER_SHOW_REGEXP:
2761 filer_window->filter_string=g_strdup(filter_string);
2762 /* Compile the pattern */
2763 break;
2765 default:
2766 /* oops */
2767 filer_window->filter=FILER_SHOW_ALL;
2768 report_error("Impossible: filter type %d", type);
2769 break;
2773 /* Setting stuff */
2774 static Settings *settings_new(const char *path)
2776 Settings *set;
2778 set=g_new(Settings, 1);
2779 memset(set, 0, sizeof(Settings));
2780 if(path)
2781 set->path=g_strdup(path);
2783 return set;
2786 static void settings_free(Settings *set)
2788 g_free(set->path);
2789 if(set->filter)
2790 g_free(set->filter);
2791 g_free(set);
2794 static gboolean free_settings(gpointer key, gpointer value, gpointer data)
2796 if(!data || strcmp(data, key)==0)
2797 settings_free(value);
2799 return TRUE;
2802 static void store_settings(Settings *set)
2804 Settings *old;
2806 old=g_hash_table_lookup(settings_table, set->path);
2807 if(old) {
2808 g_hash_table_foreach_remove(settings_table, free_settings,
2809 set->path);
2812 g_hash_table_insert(settings_table, set->path, set);
2815 /* TODO: use symbolic names in the XML file where possible */
2816 static void load_from_node(Settings *set, xmlDocPtr doc, xmlNodePtr node)
2818 xmlChar *str=NULL;
2820 str=xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
2822 if(strcmp(node->name, "X") == 0) {
2823 set->x=atoi(str);
2824 set->flags|=SET_POSITION;
2825 } else if(strcmp(node->name, "Y") == 0) {
2826 set->y=atoi(str);
2827 set->flags|=SET_POSITION;
2828 } else if(strcmp(node->name, "Width") == 0) {
2829 set->width=atoi(str);
2830 set->flags|=SET_SIZE;
2831 } else if(strcmp(node->name, "Height") == 0) {
2832 set->height=atoi(str);
2833 set->flags|=SET_SIZE;
2834 } else if(strcmp(node->name, "ShowHidden") == 0) {
2835 set->show_hidden=atoi(str);
2836 set->flags|=SET_HIDDEN;
2837 } else if(strcmp(node->name, "ViewType") == 0) {
2838 set->view_type=atoi(str);
2839 set->flags|=SET_DETAILS;
2840 } else if(strcmp(node->name, "DetailsType") == 0) {
2841 set->details_type=atoi(str);
2842 set->flags|=SET_DETAILS;
2843 } else if(strcmp(node->name, "SortType") == 0) {
2844 set->sort_type=atoi(str);
2845 set->flags|=SET_SORT;
2846 } else if(strcmp(node->name, "SortOrder") == 0) {
2847 set->sort_order=atoi(str);
2848 set->flags|=SET_SORT;
2849 } else if(strcmp(node->name, "DisplayStyle") == 0) {
2850 set->display_style=atoi(str);
2851 set->flags|=SET_STYLE;
2852 } else if(strcmp(node->name, "ShowThumbs") == 0) {
2853 set->show_thumbs=atoi(str);
2854 set->flags|=SET_THUMBS;
2855 } else if(strcmp(node->name, "FilterType") == 0) {
2856 set->filter_type=atoi(str);
2857 set->flags|=SET_FILTER;
2858 } else if(strcmp(node->name, "Filter") == 0) {
2859 set->filter=g_strdup(str);
2860 set->flags|=SET_FILTER;
2863 if(str)
2864 xmlFree(str);
2867 static void load_settings(void)
2869 gchar *path;
2870 XMLwrapper *settings_doc=NULL;
2872 path=choices_find_path_load("Settings.xml", "ROX-Filer");
2873 if(path) {
2874 settings_doc=xml_new(path);
2875 g_free(path);
2878 if(!settings_table)
2879 settings_table=g_hash_table_new(g_str_hash, g_str_equal);
2881 if(settings_doc) {
2882 xmlNodePtr node, subnode;
2884 node = xmlDocGetRootElement(settings_doc->doc);
2886 for (node = node->xmlChildrenNode; node; node = node->next)
2888 Settings *set;
2889 xmlChar *path;
2891 if (node->type != XML_ELEMENT_NODE)
2892 continue;
2893 if (strcmp(node->name, "FilerWindow") != 0)
2894 continue;
2896 path=xmlGetProp(node, "path");
2897 set=settings_new(path);
2898 xmlFree(path);
2900 for (subnode=node->xmlChildrenNode; subnode;
2901 subnode=subnode->next) {
2903 if (subnode->type != XML_ELEMENT_NODE)
2904 continue;
2905 load_from_node(set, settings_doc->doc,
2906 subnode);
2909 store_settings(set);
2911 g_object_unref(settings_doc);
2915 static void add_nodes(gpointer key, gpointer value, gpointer data)
2917 xmlNodePtr node=(xmlNodePtr) data;
2918 xmlNodePtr sub;
2919 Settings *set=(Settings *) value;
2920 char *tmp;
2922 sub=xmlNewChild(node, NULL, "FilerWindow", NULL);
2924 xmlSetProp(sub, "path", set->path);
2926 if(set->flags & SET_POSITION) {
2927 tmp=g_strdup_printf("%d", set->x);
2928 xmlNewChild(sub, NULL, "X", tmp);
2929 g_free(tmp);
2930 tmp=g_strdup_printf("%d", set->y);
2931 xmlNewChild(sub, NULL, "Y", tmp);
2932 g_free(tmp);
2934 if(set->flags & SET_SIZE) {
2935 tmp=g_strdup_printf("%d", set->width);
2936 xmlNewChild(sub, NULL, "Width", tmp);
2937 g_free(tmp);
2938 tmp=g_strdup_printf("%d", set->height);
2939 xmlNewChild(sub, NULL, "Height", tmp);
2940 g_free(tmp);
2942 if(set->flags & SET_HIDDEN) {
2943 tmp=g_strdup_printf("%d", set->show_hidden);
2944 xmlNewChild(sub, NULL, "ShowHidden", tmp);
2945 g_free(tmp);
2947 if(set->flags & SET_STYLE) {
2948 tmp=g_strdup_printf("%d", set->display_style);
2949 xmlNewChild(sub, NULL, "DisplayStyle", tmp);
2950 g_free(tmp);
2952 if(set->flags & SET_SORT) {
2953 tmp=g_strdup_printf("%d", set->sort_type);
2954 xmlNewChild(sub, NULL, "SortType", tmp);
2955 g_free(tmp);
2956 tmp=g_strdup_printf("%d", set->sort_order);
2957 xmlNewChild(sub, NULL, "SortOrder", tmp);
2958 g_free(tmp);
2960 if(set->flags & SET_DETAILS) {
2961 tmp=g_strdup_printf("%d", set->view_type);
2962 xmlNewChild(sub, NULL, "ViewType", tmp);
2963 g_free(tmp);
2964 tmp=g_strdup_printf("%d", set->details_type);
2965 xmlNewChild(sub, NULL, "DetailsType", tmp);
2966 g_free(tmp);
2968 if(set->flags & SET_STYLE) {
2969 tmp=g_strdup_printf("%d", set->show_thumbs);
2970 xmlNewChild(sub, NULL, "ShowThumbs", tmp);
2971 g_free(tmp);
2973 if(set->flags & SET_FILTER) {
2974 tmp=g_strdup_printf("%d", set->filter_type);
2975 xmlNewChild(sub, NULL, "FilterType", tmp);
2976 g_free(tmp);
2977 if(set->filter && set->filter[0])
2978 xmlNewChild(sub, NULL, "Filter", set->filter);
2982 static void save_settings(void)
2984 gchar *path;
2986 path=choices_find_path_save("Settings.xml", "ROX-Filer", TRUE);
2987 if(path) {
2988 xmlDocPtr doc = xmlNewDoc("1.0");
2989 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL,
2990 "Settings", NULL));
2992 g_hash_table_foreach(settings_table, add_nodes,
2993 xmlDocGetRootElement(doc));
2995 save_xml_file(doc, path);
2997 g_free(path);
2998 xmlFreeDoc(doc);
3003 static void check_settings(FilerWindow *filer_window)
3005 Settings *set;
3007 set=(Settings *) g_hash_table_lookup(settings_table,
3008 filer_window->sym_path);
3010 if(set) {
3011 if(set->flags & SET_POSITION)
3012 gtk_window_move(GTK_WINDOW(filer_window->window),
3013 set->x, set->y);
3014 if(set->flags & SET_SIZE)
3015 filer_window_set_size(filer_window, set->width,
3016 set->height);
3017 if(set->flags & SET_HIDDEN)
3018 filer_set_hidden(filer_window, set->show_hidden);
3020 if(set->flags & (SET_STYLE|SET_DETAILS)) {
3021 DisplayStyle style=filer_window->display_style;
3022 DetailsType details=filer_window->details_type;
3024 if(set->flags & SET_STYLE)
3025 style=set->display_style;
3027 if(set->flags & SET_DETAILS) {
3028 details=set->details_type;
3030 filer_set_view_type(filer_window,
3031 set->view_type);
3034 display_set_layout(filer_window, style,
3035 details, FALSE);
3038 if(set->flags & SET_SORT)
3039 display_set_sort_type(filer_window,
3040 set->sort_type,
3041 set->sort_order);
3043 if(set->flags & SET_THUMBS)
3044 display_set_thumbs(filer_window,
3045 set->show_thumbs);
3047 if(set->flags & SET_FILTER)
3048 display_set_filter(filer_window,
3049 set->filter_type,
3050 set->filter);
3055 typedef struct settings_window {
3056 GtkWidget *window;
3058 GtkWidget *pos, *size, *hidden, *style, *sort, *details,
3059 *thumbs, *filter;
3061 Settings *set;
3062 } SettingsWindow;
3065 static void settings_response(GtkWidget *window, gint response,
3066 SettingsWindow *set_win)
3068 if(response==GTK_RESPONSE_OK) {
3069 gint flags=0;
3071 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->pos)))
3072 flags|=SET_POSITION;
3073 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->size)))
3074 flags|=SET_SIZE;
3075 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->hidden)))
3076 flags|=SET_HIDDEN;
3077 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->style)))
3078 flags|=SET_STYLE;
3079 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->sort)))
3080 flags|=SET_SORT;
3081 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->details)))
3082 flags|=SET_DETAILS;
3083 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->thumbs)))
3084 flags|=SET_THUMBS;
3085 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->filter)))
3086 flags|=SET_FILTER;
3088 set_win->set->flags=flags;
3089 store_settings(set_win->set);
3090 save_settings();
3093 gtk_widget_destroy(window);
3096 void filer_save_settings(FilerWindow *fwin)
3098 SettingsWindow *set_win;
3099 GtkWidget *vbox;
3100 GtkWidget *path;
3101 gint x, y;
3103 Settings *set=settings_new(fwin->sym_path);
3105 gtk_window_get_position(GTK_WINDOW(fwin->window),&x, &y);
3106 set->flags|=SET_POSITION;
3107 set->x=x;
3108 set->y=y;
3110 gtk_window_get_size(GTK_WINDOW(fwin->window),&x, &y);
3111 set->flags|=SET_SIZE;
3112 set->width=x;
3113 set->height=y;
3115 set->flags|=SET_HIDDEN;
3116 set->show_hidden=fwin->show_hidden;
3118 set->flags|=SET_STYLE;
3119 set->display_style=fwin->display_style;
3121 set->flags|=SET_SORT;
3122 set->sort_type=fwin->sort_type;
3123 set->sort_order=fwin->sort_order;
3125 set->flags|=SET_DETAILS;
3126 set->view_type=fwin->view_type;
3127 set->details_type=fwin->details_type;
3129 set->flags|=SET_THUMBS;
3130 set->show_thumbs=fwin->show_thumbs;
3132 set->flags|=SET_FILTER;
3133 set->filter_type=fwin->filter;
3134 if(fwin->filter_string)
3135 set->filter=g_strdup(fwin->filter_string);
3137 /* Store other parameters
3139 set_win=g_new(SettingsWindow, 1);
3141 set_win->window=gtk_dialog_new();
3142 number_of_windows++;
3144 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3145 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
3146 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3147 GTK_STOCK_OK, GTK_RESPONSE_OK);
3149 g_signal_connect(set_win->window, "destroy",
3150 G_CALLBACK(one_less_window), NULL);
3151 g_signal_connect_swapped(set_win->window, "destroy",
3152 G_CALLBACK(g_free), set_win);
3154 gtk_window_set_title(GTK_WINDOW(set_win->window),
3155 _("Select display properties to save"));
3157 vbox=GTK_DIALOG(set_win->window)->vbox;
3159 path=gtk_label_new(set->path);
3160 gtk_box_pack_start(GTK_BOX(vbox), path, FALSE, FALSE, 2);
3162 set_win->pos=gtk_check_button_new_with_label(_("Position"));
3163 gtk_box_pack_start(GTK_BOX(vbox), set_win->pos, FALSE, FALSE, 2);
3164 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->pos),
3165 set->flags & SET_POSITION);
3167 set_win->size=gtk_check_button_new_with_label(_("Size"));
3168 gtk_box_pack_start(GTK_BOX(vbox), set_win->size, FALSE, FALSE, 2);
3169 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->size),
3170 set->flags & SET_SIZE);
3172 set_win->hidden=gtk_check_button_new_with_label(_("Show hidden"));
3173 gtk_box_pack_start(GTK_BOX(vbox), set_win->hidden,
3174 FALSE, FALSE, 2);
3175 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->hidden),
3176 set->flags & SET_HIDDEN);
3178 set_win->style=gtk_check_button_new_with_label(_("Display style"));
3179 gtk_box_pack_start(GTK_BOX(vbox), set_win->style,
3180 FALSE, FALSE, 2);
3181 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->style),
3182 set->flags & SET_STYLE);
3184 set_win->sort=gtk_check_button_new_with_label(_("Sort type and order"));
3185 gtk_box_pack_start(GTK_BOX(vbox), set_win->sort, FALSE, FALSE, 2);
3186 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->sort),
3187 set->flags & SET_SORT);
3189 set_win->details=gtk_check_button_new_with_label(_("Details"));
3190 gtk_box_pack_start(GTK_BOX(vbox), set_win->details, FALSE, FALSE, 2);
3191 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->details),
3192 set->flags & SET_DETAILS);
3194 set_win->thumbs=gtk_check_button_new_with_label(_("Thumbnails"));
3195 gtk_box_pack_start(GTK_BOX(vbox), set_win->thumbs,
3196 FALSE, FALSE, 2);
3197 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->thumbs),
3198 set->flags & SET_THUMBS);
3200 set_win->filter=gtk_check_button_new_with_label(_("Filter"));
3201 gtk_box_pack_start(GTK_BOX(vbox), set_win->filter,
3202 FALSE, FALSE, 2);
3203 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->filter),
3204 set->flags & SET_FILTER);
3206 set_win->set=set;
3207 g_signal_connect(set_win->window, "response",
3208 G_CALLBACK(settings_response), set_win);
3210 gtk_widget_show_all(set_win->window);