r4085: Updated French manual translation (Arnaud Calvo).
[rox-filer.git] / ROX-Filer / src / filer.c
bloba74edbc0fefd7ae05b818abe50deb12e09b3892d
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, 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 GtkWidget *window;
248 g_return_if_fail(filer_window != NULL);
250 if (filer_window->scrollbar)
251 w += filer_window->scrollbar->allocation.width;
253 if (o_toolbar.int_value != TOOLBAR_NONE)
254 h += filer_window->toolbar->allocation.height;
255 if (filer_window->message)
256 h += filer_window->message->allocation.height;
258 window = filer_window->window;
260 if (GTK_WIDGET_VISIBLE(window))
262 gint x, y, m;
263 GtkRequisition *req = &window->requisition;
264 GdkWindow *gdk_window = window->window;
265 GdkEvent *event;
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 /* If the resize was triggered by a key press, keep
297 * the pointer inside the window so that it doesn't
298 * lose focus when using pointer-follows-mouse.
300 event = gtk_get_current_event();
301 if (event && event->type == GDK_KEY_PRESS)
303 int x, y;
304 int nx, ny;
306 gdk_event_free(event);
308 GdkWindow *win = filer_window->window->window;
310 gdk_window_get_pointer(filer_window->window->window,
311 &x, &y, NULL);
313 nx = CLAMP(x, 4, w - 4);
314 ny = CLAMP(y, 4, h - 4);
316 if (nx != x || ny != y)
318 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
319 None,
320 gdk_x11_drawable_get_xid(win),
321 0, 0, 0, 0,
322 nx, ny);
326 else
327 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
330 /* Called on a timeout while scanning or when scanning ends
331 * (whichever happens first).
333 static gint open_filer_window(FilerWindow *filer_window)
335 Settings *dir_settings;
336 gboolean force_resize;
338 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
339 filer_window->sym_path);
341 force_resize = !(o_filer_auto_resize.int_value == RESIZE_NEVER &&
342 dir_settings && dir_settings->flags & SET_POSITION);
344 view_style_changed(filer_window->view, 0);
346 if (filer_window->open_timeout)
348 g_source_remove(filer_window->open_timeout);
349 filer_window->open_timeout = 0;
352 if (!GTK_WIDGET_VISIBLE(filer_window->window))
354 display_set_actual_size(filer_window, force_resize);
355 gtk_widget_show(filer_window->window);
358 return FALSE;
361 /* Look through all items we want to display, and queue a recheck on any
362 * that require it.
364 static void queue_interesting(FilerWindow *filer_window)
366 DirItem *item;
367 ViewIter iter;
369 view_get_iter(filer_window->view, &iter, 0);
370 while ((item = iter.next(&iter)))
372 if (item->flags & ITEM_FLAG_NEED_RESCAN_QUEUE)
373 dir_queue_recheck(filer_window->directory, item);
377 static void update_display(Directory *dir,
378 DirAction action,
379 GPtrArray *items,
380 FilerWindow *filer_window)
382 ViewIface *view = (ViewIface *) filer_window->view;
384 switch (action)
386 case DIR_ADD:
387 view_add_items(view, items);
388 /* Open and resize if currently hidden */
389 open_filer_window(filer_window);
390 break;
391 case DIR_REMOVE:
392 view_delete_if(view, if_deleted, items);
393 toolbar_update_info(filer_window);
394 break;
395 case DIR_START_SCAN:
396 set_scanning_display(filer_window, TRUE);
397 toolbar_update_info(filer_window);
398 break;
399 case DIR_END_SCAN:
400 if (filer_window->window->window)
401 gdk_window_set_cursor(
402 filer_window->window->window,
403 NULL);
404 set_scanning_display(filer_window, FALSE);
405 toolbar_update_info(filer_window);
406 open_filer_window(filer_window);
408 if (filer_window->had_cursor &&
409 !view_cursor_visible(view))
411 ViewIter start;
412 view_get_iter(view, &start, 0);
413 if (start.next(&start))
414 view_cursor_to_iter(view, &start);
415 view_show_cursor(view);
416 filer_window->had_cursor = FALSE;
418 if (filer_window->auto_select)
419 display_set_autoselect(filer_window,
420 filer_window->auto_select);
421 null_g_free(&filer_window->auto_select);
423 filer_create_thumbs(filer_window);
425 if (filer_window->thumb_queue)
426 start_thumb_scanning(filer_window);
427 break;
428 case DIR_UPDATE:
429 view_update_items(view, items);
430 break;
431 case DIR_ERROR_CHANGED:
432 filer_set_title(filer_window);
433 break;
434 case DIR_QUEUE_INTERESTING:
435 queue_interesting(filer_window);
436 break;
440 static void attach(FilerWindow *filer_window)
442 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
443 view_clear(filer_window->view);
444 filer_window->scanning = TRUE;
445 dir_attach(filer_window->directory, (DirCallback) update_display,
446 filer_window);
447 filer_set_title(filer_window);
448 bookmarks_add_history(filer_window->sym_path);
450 if (filer_window->directory->error)
452 if (spring_in_progress)
453 g_printerr(_("Error scanning '%s':\n%s\n"),
454 filer_window->sym_path,
455 filer_window->directory->error);
456 else
457 delayed_error(_("Error scanning '%s':\n%s"),
458 filer_window->sym_path,
459 filer_window->directory->error);
463 static void detach(FilerWindow *filer_window)
465 g_return_if_fail(filer_window->directory != NULL);
467 dir_detach(filer_window->directory,
468 (DirCallback) update_display, filer_window);
469 g_object_unref(filer_window->directory);
470 filer_window->directory = NULL;
473 /* If 'start' was mounted by ROX-Filer, return it. Otherwise, try the
474 * parents up the tree.
475 * NULL if we're not in a user mount point.
476 * g_free() the result.
478 static char *get_ancestor_user_mount_point(const char *start)
480 char *path;
482 path = strdup(start);
484 while (1)
486 char *slash;
488 if (mount_is_user_mounted(path))
489 return path;
491 if (!path[1])
493 g_free(path);
494 return NULL;
497 slash = strrchr(path + 1, '/');
498 if (!slash)
499 slash = path + 1;
500 *slash = '\0';
504 static void umount_dialog_response(GtkWidget *dialog, int response, char *mount)
506 if (response == GTK_RESPONSE_OK)
508 GList *list;
510 list = g_list_prepend(NULL, mount);
511 action_mount(list, FALSE, FALSE, TRUE);
512 g_list_free(list);
515 g_free(mount);
517 gtk_widget_destroy(dialog);
519 one_less_window();
522 /* 'filer_window' shows a directory under 'mount'. If no other window also
523 * shows a directory under it, display a non-modal dialog offering to
524 * unmount the directory.
525 * 'mount' is freed by this function, either directly, or after the dialog
526 * closes.
528 static void may_offer_unmount(FilerWindow *filer_window, char *mount)
530 GtkWidget *dialog, *button;
531 GList *next;
532 int len;
534 len = strlen(mount);
536 for (next = all_filer_windows; next; next = next->next)
538 FilerWindow *other = (FilerWindow *) next->data;
540 if (other == filer_window)
541 continue;
543 if (strncmp(filer_window->real_path, other->real_path,
544 len) != 0)
545 continue;
547 g_return_if_fail(
548 filer_window->real_path[len] != '/' ||
549 filer_window->real_path[len] != '\0');
551 if (other->real_path[len] != '/' &&
552 other->real_path[len] != '\0')
553 continue;
555 /* Found another window. Don't offer to unmount. */
556 g_free(mount);
557 return;
560 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
561 GTK_BUTTONS_NONE,
562 _("Do you want to unmount this device?\n\n"
563 "Unmounting a device makes it safe to remove "
564 "the disk."), mount);
566 button = button_new_mixed(ROX_STOCK_MOUNTED, _("No change"));
567 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
568 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
569 GTK_RESPONSE_CANCEL);
570 gtk_widget_show(button);
572 button = button_new_mixed(ROX_STOCK_MOUNT, _("Unmount"));
573 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
574 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
575 GTK_RESPONSE_OK);
576 gtk_widget_show(button);
578 g_signal_connect(G_OBJECT(dialog), "response",
579 G_CALLBACK(umount_dialog_response), mount);
581 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
582 GTK_RESPONSE_OK);
584 number_of_windows++;
585 gtk_widget_show(dialog);
588 /* Returns TRUE to prevent closing the window. May offer to unmount a
589 * device.
591 gboolean filer_window_delete(GtkWidget *window,
592 GdkEvent *unused, /* (may be NULL) */
593 FilerWindow *filer_window)
595 char *mount;
597 mount = get_ancestor_user_mount_point(filer_window->real_path);
599 if (mount)
600 may_offer_unmount(filer_window, mount);
602 return FALSE;
605 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
607 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
609 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
611 if (window_with_primary == filer_window)
612 window_with_primary = NULL;
614 if (window_with_focus == filer_window)
616 menu_popdown();
617 window_with_focus = NULL;
620 if (filer_window->directory)
621 detach(filer_window);
623 if (filer_window->open_timeout)
625 g_source_remove(filer_window->open_timeout);
626 filer_window->open_timeout = 0;
629 if (filer_window->auto_scroll != -1)
631 g_source_remove(filer_window->auto_scroll);
632 filer_window->auto_scroll = -1;
635 if (filer_window->thumb_queue)
636 destroy_glist(&filer_window->thumb_queue);
638 tooltip_show(NULL);
640 filer_set_id(filer_window, NULL);
642 if(filer_window->filter_string)
643 g_free(filer_window->filter_string);
644 if(filer_window->regexp)
645 g_free(filer_window->regexp);
647 g_free(filer_window->auto_select);
648 g_free(filer_window->real_path);
649 g_free(filer_window->sym_path);
650 g_free(filer_window);
652 one_less_window();
655 /* Returns TRUE iff the directory still exists. */
656 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
658 Directory *dir;
660 g_return_val_if_fail(filer_window != NULL, FALSE);
662 /* We do a fresh lookup (rather than update) because the inode may
663 * have changed.
665 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
666 if (!dir)
668 if (warning)
669 info_message(_("Directory missing/deleted"));
670 gtk_widget_destroy(filer_window->window);
671 return FALSE;
673 if (dir == filer_window->directory)
674 g_object_unref(dir);
675 else
677 detach(filer_window);
678 filer_window->directory = dir;
679 attach(filer_window);
682 return TRUE;
685 /* No items are now selected. This might be because another app claimed
686 * the selection or because the user unselected all the items.
688 void filer_lost_selection(FilerWindow *filer_window, guint time)
690 if (window_with_primary == filer_window)
692 window_with_primary = NULL;
693 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
697 /* Another app has claimed the primary selection */
698 static void filer_lost_primary(GtkWidget *window,
699 GdkEventSelection *event,
700 gpointer user_data)
702 FilerWindow *filer_window = (FilerWindow *) user_data;
704 if (window_with_primary && window_with_primary == filer_window)
706 window_with_primary = NULL;
707 set_selection_state(filer_window, FALSE);
711 /* Someone wants us to send them the selection */
712 static void selection_get(GtkWidget *widget,
713 GtkSelectionData *selection_data,
714 guint info,
715 guint time,
716 gpointer data)
718 GString *reply, *header;
719 FilerWindow *filer_window = (FilerWindow *) data;
720 ViewIter iter;
721 DirItem *item;
723 reply = g_string_new(NULL);
724 header = g_string_new(NULL);
726 switch (info)
728 case TARGET_STRING:
729 g_string_printf(header, " %s",
730 make_path(filer_window->sym_path, ""));
731 break;
732 case TARGET_URI_LIST:
733 g_string_printf(header, " file://%s%s",
734 our_host_name_for_dnd(),
735 make_path(filer_window->sym_path, ""));
736 break;
739 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
741 while ((item = iter.next(&iter)))
743 g_string_append(reply, header->str);
744 g_string_append(reply, item->leafname);
747 if (reply->len > 0)
748 gtk_selection_data_set_text(selection_data,
749 reply->str + 1, reply->len - 1);
750 else
752 g_warning("Attempt to paste empty selection!");
753 gtk_selection_data_set_text(selection_data, "", 0);
756 g_string_free(reply, TRUE);
757 g_string_free(header, TRUE);
760 /* Selection has been changed -- try to grab the primary selection
761 * if we don't have it. Also called when clicking on an insensitive selection
762 * to regain primary.
763 * Also updates toolbar info.
765 void filer_selection_changed(FilerWindow *filer_window, gint time)
767 toolbar_update_info(filer_window);
769 if (window_with_primary == filer_window)
770 return; /* Already got primary */
772 if (!view_count_selected(filer_window->view))
773 return; /* Nothing selected */
775 if (filer_window->temp_item_selected == FALSE &&
776 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
777 GDK_SELECTION_PRIMARY,
778 time))
780 window_with_primary = filer_window;
781 set_selection_state(filer_window, TRUE);
783 else
784 set_selection_state(filer_window, FALSE);
787 /* Open the item (or add it to the shell command minibuffer) */
788 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
790 gboolean shift = (flags & OPEN_SHIFT) != 0;
791 gboolean close_mini = flags & OPEN_FROM_MINI;
792 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
793 DirItem *item;
794 const guchar *full_path;
795 gboolean wink = TRUE;
796 Directory *old_dir;
798 g_return_if_fail(filer_window != NULL && iter != NULL);
800 item = iter->peek(iter);
802 g_return_if_fail(item != NULL);
804 if (filer_window->mini_type == MINI_SHELL)
806 minibuffer_add(filer_window, item->leafname);
807 return;
810 if (item->base_type == TYPE_UNKNOWN)
811 dir_update_item(filer_window->directory, item->leafname);
813 if (item->base_type == TYPE_DIRECTORY)
815 /* Never close a filer window when opening a directory
816 * (click on a dir or click on an app with shift).
818 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
819 close_window = FALSE;
822 full_path = make_path(filer_window->sym_path, item->leafname);
823 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
824 wink = FALSE;
826 old_dir = filer_window->directory;
827 if (run_diritem(full_path, item,
828 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
829 filer_window,
830 shift))
832 if (old_dir != filer_window->directory)
833 return;
835 if (close_window)
836 gtk_widget_destroy(filer_window->window);
837 else
839 if (wink)
840 view_wink_item(filer_window->view, iter);
841 if (close_mini)
842 minibuffer_hide(filer_window);
847 static gint pointer_in(GtkWidget *widget,
848 GdkEventCrossing *event,
849 FilerWindow *filer_window)
851 may_rescan(filer_window, TRUE);
852 return FALSE;
855 static gint pointer_out(GtkWidget *widget,
856 GdkEventCrossing *event,
857 FilerWindow *filer_window)
859 tooltip_show(NULL);
860 return FALSE;
863 /* Move the cursor to the next selected item in direction 'dir'
864 * (+1 or -1).
866 void filer_next_selected(FilerWindow *filer_window, int dir)
868 ViewIter iter, cursor;
869 gboolean have_cursor;
870 ViewIface *view = filer_window->view;
872 g_return_if_fail(dir == 1 || dir == -1);
874 view_get_cursor(view, &cursor);
875 have_cursor = cursor.peek(&cursor) != NULL;
877 view_get_iter(view, &iter,
878 VIEW_ITER_SELECTED |
879 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
880 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
882 if (have_cursor && view_get_selected(view, &cursor))
883 iter.next(&iter); /* Skip the cursor itself */
885 if (iter.next(&iter))
886 view_cursor_to_iter(view, &iter);
887 else
888 gdk_beep();
890 return;
893 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
895 TargetFunc cb = filer_window->target_cb;
896 gpointer data = filer_window->target_data;
897 OpenFlags flags = 0;
898 ViewIter iter;
900 filer_target_mode(filer_window, NULL, NULL, NULL);
902 view_get_cursor(filer_window->view, &iter);
903 if (!iter.peek(&iter))
904 return;
906 if (cb)
908 cb(filer_window, &iter, data);
909 return;
912 if (event->state & GDK_SHIFT_MASK)
913 flags |= OPEN_SHIFT;
914 if (event->state & GDK_MOD1_MASK)
915 flags |= OPEN_CLOSE_WINDOW;
916 else
917 flags |= OPEN_SAME_WINDOW;
919 filer_openitem(filer_window, &iter, flags);
922 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
923 * changed. If no groups were loaded and there is no file then initialised
924 * groups to an empty document.
925 * Return the node for the 'name' group.
927 static xmlNode *group_find(char *name)
929 xmlNode *node;
930 gchar *path;
932 /* Update the groups, if possible */
933 path = choices_find_xdg_path_load("Groups.xml", PROJECT, SITE);
934 if (path)
936 XMLwrapper *wrapper;
937 wrapper = xml_cache_load(path);
938 if (wrapper)
940 if (groups)
941 g_object_unref(groups);
942 groups = wrapper;
945 g_free(path);
948 if (!groups)
950 groups = xml_new(NULL);
951 groups->doc = xmlNewDoc("1.0");
953 xmlDocSetRootElement(groups->doc,
954 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
955 return NULL;
958 node = xmlDocGetRootElement(groups->doc);
960 for (node = node->xmlChildrenNode; node; node = node->next)
962 guchar *gid;
964 gid = xmlGetProp(node, "name");
966 if (!gid)
967 continue;
969 if (strcmp(name, gid) != 0)
970 continue;
972 g_free(gid);
974 return node;
977 return NULL;
980 static void group_save(FilerWindow *filer_window, char *name)
982 xmlNode *group;
983 guchar *save_path;
984 DirItem *item;
985 ViewIter iter;
987 group = group_find(name);
988 if (group)
990 xmlUnlinkNode(group);
991 xmlFreeNode(group);
993 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
994 NULL, "group", NULL);
995 xmlSetProp(group, "name", name);
997 xmlNewTextChild(group, NULL, "directory", filer_window->sym_path);
999 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1001 while ((item = iter.next(&iter)))
1002 xmlNewTextChild(group, NULL, "item", item->leafname);
1004 save_path = choices_find_xdg_path_save("Groups.xml", PROJECT, SITE,
1005 TRUE);
1006 if (save_path)
1008 save_xml_file(groups->doc, save_path);
1009 g_free(save_path);
1013 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
1015 GHashTable *in_group = (GHashTable *) data;
1017 return g_hash_table_lookup(in_group,
1018 iter->peek(iter)->leafname) != NULL;
1021 static void group_restore(FilerWindow *filer_window, char *name)
1023 GHashTable *in_group;
1024 char *path;
1025 xmlNode *group, *node;
1027 group = group_find(name);
1029 if (!group)
1031 report_error(_("Group %s is not set. Select some files "
1032 "and press Ctrl+%s to set the group. Press %s "
1033 "on its own to reselect the files later.\n"
1034 "Make sure NumLock is on if you use the keypad."),
1035 name, name, name);
1036 return;
1039 node = get_subnode(group, NULL, "directory");
1040 g_return_if_fail(node != NULL);
1041 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
1042 g_return_if_fail(path != NULL);
1044 if (strcmp(path, filer_window->sym_path) != 0)
1045 filer_change_to(filer_window, path, NULL);
1046 g_free(path);
1048 in_group = g_hash_table_new(g_str_hash, g_str_equal);
1049 for (node = group->xmlChildrenNode; node; node = node->next)
1051 gchar *leaf;
1052 if (node->type != XML_ELEMENT_NODE)
1053 continue;
1054 if (strcmp(node->name, "item") != 0)
1055 continue;
1057 leaf = xmlNodeListGetString(groups->doc,
1058 node->xmlChildrenNode, 1);
1059 if (!leaf)
1060 g_warning("Missing leafname!\n");
1061 else
1062 g_hash_table_insert(in_group, leaf, filer_window);
1065 view_select_if(filer_window->view, &group_restore_cb, in_group);
1067 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1068 g_hash_table_destroy(in_group);
1071 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
1073 ViewIter iter;
1074 GdkEvent *event;
1076 view_get_cursor(filer_window->view, &iter);
1078 event = gtk_get_current_event();
1079 show_filer_menu(filer_window, NULL, &iter);
1080 if (event)
1081 gdk_event_free(event);
1083 return TRUE;
1086 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
1088 ViewIface *view = filer_window->view;
1089 ViewIter iter;
1091 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
1092 if (!iter.next(&iter))
1093 return; /* No cursor */
1095 if (view_get_selected(view, &iter))
1096 view_set_selected(view, &iter, FALSE);
1097 else
1098 view_set_selected(view, &iter, TRUE);
1100 if (iter.next(&iter))
1101 view_cursor_to_iter(view, &iter);
1104 gint filer_key_press_event(GtkWidget *widget,
1105 GdkEventKey *event,
1106 FilerWindow *filer_window)
1108 ViewIface *view = filer_window->view;
1109 ViewIter cursor;
1110 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
1111 guint key = event->keyval;
1112 char group[2] = "1";
1114 window_with_focus = filer_window;
1116 /* Delay setting up the keys until now to speed loading... */
1117 if (ensure_filer_menu())
1119 /* Gtk updates in an idle-handler, so force a recheck now */
1120 g_signal_emit_by_name(widget, "keys_changed");
1123 if (focus && focus == filer_window->minibuffer)
1124 if (gtk_widget_event(focus, (GdkEvent *) event))
1125 return TRUE; /* Handled */
1127 if (!focus)
1128 gtk_widget_grab_focus(GTK_WIDGET(view));
1130 view_get_cursor(view, &cursor);
1131 if (!cursor.peek(&cursor) && (key == GDK_Up || key == GDK_Down))
1133 ViewIter iter;
1134 view_get_iter(view, &iter, 0);
1135 if (iter.next(&iter))
1136 view_cursor_to_iter(view, &iter);
1137 gtk_widget_grab_focus(GTK_WIDGET(view)); /* Needed? */
1138 return TRUE;
1141 switch (key)
1143 case GDK_Escape:
1144 filer_target_mode(filer_window, NULL, NULL, NULL);
1145 view_cursor_to_iter(filer_window->view, NULL);
1146 view_clear_selection(filer_window->view);
1147 return FALSE;
1148 case GDK_Return:
1149 return_pressed(filer_window, event);
1150 break;
1151 case GDK_ISO_Left_Tab:
1152 filer_next_selected(filer_window, -1);
1153 break;
1154 case GDK_Tab:
1155 filer_next_selected(filer_window, 1);
1156 break;
1157 case GDK_BackSpace:
1158 change_to_parent(filer_window);
1159 break;
1160 case GDK_backslash:
1162 ViewIter iter;
1164 tooltip_show(NULL);
1166 view_get_cursor(filer_window->view, &iter);
1167 show_filer_menu(filer_window,
1168 (GdkEvent *) event, &iter);
1169 break;
1171 case ' ':
1172 filer_window_toggle_cursor_item_selected(filer_window);
1173 break;
1174 default:
1175 if (key >= GDK_0 && key <= GDK_9)
1176 group[0] = key - GDK_0 + '0';
1177 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1178 group[0] = key - GDK_KP_0 + '0';
1179 else
1181 if (focus && focus != widget &&
1182 gtk_widget_get_toplevel(focus) == widget)
1183 if (gtk_widget_event(focus,
1184 (GdkEvent *) event))
1185 return TRUE; /* Handled */
1186 return FALSE;
1189 if (event->state & GDK_CONTROL_MASK)
1190 group_save(filer_window, group);
1191 else
1192 group_restore(filer_window, group);
1195 return TRUE;
1198 void filer_open_parent(FilerWindow *filer_window)
1200 char *dir;
1201 const char *current = filer_window->sym_path;
1203 if (current[0] == '/' && current[1] == '\0')
1204 return; /* Already in the root */
1206 dir = g_path_get_dirname(current);
1207 filer_opendir(dir, filer_window, NULL);
1208 g_free(dir);
1211 void change_to_parent(FilerWindow *filer_window)
1213 char *dir;
1214 const char *current = filer_window->sym_path;
1216 if (current[0] == '/' && current[1] == '\0')
1217 return; /* Already in the root */
1219 if (mount_is_user_mounted(filer_window->real_path))
1220 may_offer_unmount(filer_window,
1221 g_strdup(filer_window->real_path));
1223 dir = g_path_get_dirname(current);
1224 filer_change_to(filer_window, dir, g_basename(current));
1225 g_free(dir);
1228 /* Removes trailing /s from path (modified in place) */
1229 static void tidy_sympath(gchar *path)
1231 int l;
1233 g_return_if_fail(path != NULL);
1235 l = strlen(path);
1236 while (l > 1 && path[l - 1] == '/')
1238 l--;
1239 path[l] = '\0';
1243 /* Make filer_window display path. When finished, highlight item 'from', or
1244 * the first item if from is NULL. If there is currently no cursor then
1245 * simply wink 'from' (if not NULL).
1246 * If the cause was a key event and we resize, warp the pointer.
1248 void filer_change_to(FilerWindow *filer_window,
1249 const char *path, const char *from)
1251 char *from_dup;
1252 char *sym_path, *real_path;
1253 Directory *new_dir;
1255 g_return_if_fail(filer_window != NULL);
1257 filer_cancel_thumbnails(filer_window);
1259 tooltip_show(NULL);
1261 sym_path = g_strdup(path);
1262 real_path = pathdup(path);
1263 new_dir = g_fscache_lookup(dir_cache, real_path);
1265 if (!new_dir)
1267 delayed_error(_("Directory '%s' is not accessible"),
1268 sym_path);
1269 g_free(real_path);
1270 g_free(sym_path);
1271 return;
1274 if (o_unique_filer_windows.int_value && !spring_in_progress)
1276 FilerWindow *fw;
1278 fw = find_filer_window(sym_path, filer_window);
1279 if (fw)
1280 gtk_widget_destroy(fw->window);
1283 from_dup = from && *from ? g_strdup(from) : NULL;
1285 detach(filer_window);
1286 g_free(filer_window->real_path);
1287 g_free(filer_window->sym_path);
1288 filer_window->real_path = real_path;
1289 filer_window->sym_path = sym_path;
1290 tidy_sympath(filer_window->sym_path);
1292 filer_window->directory = new_dir;
1294 g_free(filer_window->auto_select);
1295 filer_window->auto_select = from_dup;
1297 filer_window->had_cursor = filer_window->had_cursor ||
1298 view_cursor_visible(filer_window->view);
1300 filer_set_title(filer_window);
1301 if (filer_window->window->window)
1302 gdk_window_set_role(filer_window->window->window,
1303 filer_window->sym_path);
1304 view_cursor_to_iter(filer_window->view, NULL);
1306 attach(filer_window);
1308 check_settings(filer_window);
1310 display_set_actual_size(filer_window, FALSE);
1312 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1313 view_autosize(filer_window->view);
1315 if (filer_window->mini_type == MINI_PATH)
1316 g_idle_add((GSourceFunc) minibuffer_show_cb, filer_window);
1319 /* Returns a list containing the full (sym) pathname of every selected item.
1320 * You must g_free() each item in the list.
1322 GList *filer_selected_items(FilerWindow *filer_window)
1324 GList *retval = NULL;
1325 guchar *dir = filer_window->sym_path;
1326 ViewIter iter;
1327 DirItem *item;
1329 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1330 while ((item = iter.next(&iter)))
1332 retval = g_list_prepend(retval,
1333 g_strdup(make_path(dir, item->leafname)));
1336 return g_list_reverse(retval);
1339 /* Return the single selected item. Error if nothing is selected. */
1340 DirItem *filer_selected_item(FilerWindow *filer_window)
1342 ViewIter iter;
1343 DirItem *item;
1345 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1347 item = iter.next(&iter);
1348 g_return_val_if_fail(item != NULL, NULL);
1349 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1351 return item;
1354 /* Creates and shows a new filer window.
1355 * If src_win != NULL then display options can be taken from that source window.
1356 * Returns the new filer window, or NULL on error.
1357 * Note: if unique windows is in use, may return an existing window.
1359 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1360 const gchar *wm_class)
1362 FilerWindow *filer_window;
1363 char *real_path;
1364 DisplayStyle dstyle;
1365 DetailsType dtype;
1366 SortType s_type;
1367 GtkSortType s_order;
1368 Settings *dir_settings = NULL;
1369 gboolean force_resize = TRUE;
1371 /* Get the real pathname of the directory and copy it */
1372 real_path = pathdup(path);
1374 if (o_unique_filer_windows.int_value && !spring_in_progress)
1376 FilerWindow *same_dir_window;
1378 same_dir_window = find_filer_window(path, NULL);
1380 if (same_dir_window)
1382 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1383 return same_dir_window;
1387 filer_window = g_new(FilerWindow, 1);
1388 filer_window->message = NULL;
1389 filer_window->minibuffer = NULL;
1390 filer_window->minibuffer_label = NULL;
1391 filer_window->minibuffer_area = NULL;
1392 filer_window->temp_show_hidden = FALSE;
1393 filer_window->sym_path = g_strdup(path);
1394 filer_window->real_path = real_path;
1395 filer_window->scanning = FALSE;
1396 filer_window->had_cursor = FALSE;
1397 filer_window->auto_select = NULL;
1398 filer_window->toolbar_text = NULL;
1399 filer_window->target_cb = NULL;
1400 filer_window->mini_type = MINI_NONE;
1401 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1402 filer_window->toolbar = NULL;
1403 filer_window->toplevel_vbox = NULL;
1404 filer_window->view_hbox = NULL;
1405 filer_window->view = NULL;
1406 filer_window->scrollbar = NULL;
1407 filer_window->auto_scroll = -1;
1408 filer_window->window_id = NULL;
1410 tidy_sympath(filer_window->sym_path);
1412 /* Finds the entry for this directory in the dir cache, creating
1413 * a new one if needed. This does not cause a scan to start,
1414 * so if a new entry is created then it will be empty.
1416 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1417 if (!filer_window->directory)
1419 delayed_error(_("Directory '%s' not found."), path);
1420 g_free(filer_window->real_path);
1421 g_free(filer_window->sym_path);
1422 g_free(filer_window);
1423 return NULL;
1426 filer_window->temp_item_selected = FALSE;
1427 filer_window->flags = (FilerFlags) 0;
1428 filer_window->details_type = DETAILS_TIMES;
1429 filer_window->display_style = UNKNOWN_STYLE;
1430 filer_window->display_style_wanted = UNKNOWN_STYLE;
1431 filer_window->thumb_queue = NULL;
1432 filer_window->max_thumbs = 0;
1433 filer_window->sort_type = -1;
1435 filer_window->filter = FILER_SHOW_ALL;
1436 filer_window->filter_string = NULL;
1437 filer_window->regexp = NULL;
1439 if (src_win && o_display_inherit_options.int_value)
1441 s_type = src_win->sort_type;
1442 s_order = src_win->sort_order;
1443 dstyle = src_win->display_style_wanted;
1444 dtype = src_win->details_type;
1445 filer_window->show_hidden = src_win->show_hidden;
1446 filer_window->show_thumbs = src_win->show_thumbs;
1447 filer_window->view_type = src_win->view_type;
1449 filer_set_filter(filer_window, src_win->filter,
1450 src_win->filter_string);
1452 else
1454 s_type = o_display_sort_by.int_value;
1455 s_order = GTK_SORT_ASCENDING;
1456 dstyle = o_display_size.int_value;
1457 dtype = o_display_details.int_value;
1458 filer_window->show_hidden = o_display_show_hidden.int_value;
1459 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1460 filer_window->view_type = o_filer_view_type.int_value;
1463 dir_settings = (Settings *) g_hash_table_lookup(settings_table,
1464 filer_window->sym_path);
1465 if (dir_settings)
1467 /* Override the current defaults with the per-directory
1468 * user settings.
1470 if (dir_settings->flags & SET_HIDDEN)
1471 filer_window->show_hidden = dir_settings->show_hidden;
1473 if (dir_settings->flags & SET_STYLE)
1474 dstyle = dir_settings->display_style;
1476 if (dir_settings->flags & SET_DETAILS)
1478 dtype = dir_settings->details_type;
1479 filer_window->view_type = dir_settings->view_type;
1482 if (dir_settings->flags & SET_SORT)
1484 s_type = dir_settings->sort_type;
1485 s_order = dir_settings->sort_order;
1488 if (dir_settings->flags & SET_THUMBS)
1489 filer_window->show_thumbs = dir_settings->show_thumbs;
1491 if (dir_settings->flags & SET_FILTER)
1492 filer_set_filter(filer_window,
1493 dir_settings->filter_type,
1494 dir_settings->filter);
1497 /* Add all the user-interface elements & realise */
1498 filer_add_widgets(filer_window, wm_class);
1499 if (src_win)
1500 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1501 GTK_WIN_POS_MOUSE);
1503 if (dir_settings)
1505 if (dir_settings->flags & SET_POSITION)
1507 gtk_window_move(GTK_WINDOW(filer_window->window),
1508 dir_settings->x, dir_settings->y);
1510 if (dir_settings->flags & SET_SIZE)
1512 filer_window_set_size(filer_window,
1513 dir_settings->width,
1514 dir_settings->height);
1515 force_resize = o_filer_auto_resize.int_value != RESIZE_NEVER;
1519 /* Connect to all the signal handlers */
1520 filer_add_signals(filer_window);
1522 display_set_layout(filer_window, dstyle, dtype, force_resize);
1523 display_set_sort_type(filer_window, s_type, s_order);
1525 /* Open the window after a timeout, or when scanning stops.
1526 * Do this before attaching, because attach() might tell us to
1527 * stop scanning (if a scan isn't needed).
1529 filer_window->open_timeout = g_timeout_add(500,
1530 (GSourceFunc) open_filer_window,
1531 filer_window);
1533 /* The view is created empty and then attach() is called, which
1534 * links the filer window to the entry in the directory cache we
1535 * looked up / created above.
1537 * The attach() function will immediately callback to the filer window
1538 * to deliver a list of all known entries in the directory (so,
1539 * the number of items will be known after attach() returns).
1541 * If the directory was not in the cache (because it hadn't been
1542 * opened it before) then the types and icons for the entries are
1543 * not know, but the list of names is.
1546 attach(filer_window);
1548 number_of_windows++;
1549 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1551 return filer_window;
1554 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1556 GtkWidget *view = NULL;
1557 Directory *dir = NULL;
1558 GHashTable *selected = NULL;
1560 g_return_if_fail(filer_window != NULL);
1562 motion_window = NULL;
1564 if (filer_window->view)
1566 /* Save the current selection */
1567 ViewIter iter;
1568 DirItem *item;
1570 selected = g_hash_table_new(g_str_hash, g_str_equal);
1571 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1572 while ((item = iter.next(&iter)))
1573 g_hash_table_insert(selected, item->leafname, "yes");
1575 /* Destroy the old view */
1576 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1577 filer_window->view = NULL;
1579 dir = filer_window->directory;
1580 g_object_ref(dir);
1581 detach(filer_window);
1584 switch (type)
1586 case VIEW_TYPE_COLLECTION:
1587 view = view_collection_new(filer_window);
1588 break;
1589 case VIEW_TYPE_DETAILS:
1590 view = view_details_new(filer_window);
1591 break;
1594 g_return_if_fail(view != NULL);
1596 filer_window->view = VIEW(view);
1597 filer_window->view_type = type;
1599 gtk_box_pack_start(filer_window->view_hbox, view, TRUE, TRUE, 0);
1600 gtk_widget_show(view);
1602 /* Drag and drop events */
1603 make_drop_target(view, 0);
1604 g_signal_connect(view, "drag_motion",
1605 G_CALLBACK(drag_motion), filer_window);
1606 g_signal_connect(view, "drag_leave",
1607 G_CALLBACK(drag_leave), filer_window);
1608 g_signal_connect(view, "drag_end",
1609 G_CALLBACK(drag_end), filer_window);
1610 /* Dragging from us... */
1611 g_signal_connect(view, "drag_data_get",
1612 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1614 if (dir)
1616 /* Only when changing type. Otherwise, will attach later. */
1617 filer_window->directory = dir;
1618 attach(filer_window);
1620 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
1621 view_autosize(filer_window->view);
1624 if (selected)
1626 ViewIter iter;
1627 DirItem *item;
1629 view_get_iter(filer_window->view, &iter, 0);
1630 while ((item = iter.next(&iter)))
1632 if (g_hash_table_lookup(selected, item->leafname))
1633 view_set_selected(filer_window->view,
1634 &iter, TRUE);
1636 g_hash_table_destroy(selected);
1640 /* This adds all the widgets to a new filer window. It is in a separate
1641 * function because filer_opendir() was getting too long...
1643 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1645 GtkWidget *hbox, *vbox;
1647 /* Create the top-level window widget */
1648 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1649 filer_set_title(filer_window);
1650 gtk_widget_set_name(filer_window->window, "rox-filer");
1652 if (wm_class)
1653 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1654 wm_class, PROJECT);
1656 /* This property is cleared when the window is destroyed.
1657 * You can thus ref filer_window->window and use this to see
1658 * if the window no longer exists.
1660 g_object_set_data(G_OBJECT(filer_window->window),
1661 "filer_window", filer_window);
1663 /* Create this now to make the Adjustment before the View */
1664 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1666 vbox = gtk_vbox_new(FALSE, 0);
1667 gtk_container_add(GTK_CONTAINER(filer_window->window), vbox);
1669 filer_window->toplevel_vbox = GTK_BOX(vbox);
1671 /* If there's a message that should be displayed in each window (eg
1672 * 'Running as root'), add it here.
1674 if (show_user_message)
1676 filer_window->message = gtk_label_new(show_user_message);
1677 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1678 FALSE, TRUE, 0);
1679 gtk_widget_show(filer_window->message);
1682 hbox = gtk_hbox_new(FALSE, 0);
1683 filer_window->view_hbox = GTK_BOX(hbox);
1684 gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
1685 /* Add the main View widget */
1686 filer_set_view_type(filer_window, filer_window->view_type);
1687 /* Put the scrollbar next to the View */
1688 gtk_box_pack_end(GTK_BOX(hbox),
1689 filer_window->scrollbar, FALSE, TRUE, 0);
1690 gtk_widget_show(hbox);
1692 /* If we want a toolbar, create it now */
1693 toolbar_update_toolbar(filer_window);
1695 /* And the minibuffer (hidden to start with) */
1696 create_minibuffer(filer_window);
1697 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1698 FALSE, TRUE, 0);
1700 /* And the thumbnail progress bar (also hidden) */
1702 GtkWidget *cancel;
1704 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1705 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1706 FALSE, TRUE, 0);
1708 filer_window->thumb_progress = gtk_progress_bar_new();
1710 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1711 filer_window->thumb_progress, TRUE, TRUE, 0);
1713 cancel = gtk_button_new_with_label(_("Cancel"));
1714 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1715 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1716 cancel, FALSE, TRUE, 0);
1717 g_signal_connect_swapped(cancel, "clicked",
1718 G_CALLBACK(filer_cancel_thumbnails),
1719 filer_window);
1722 gtk_widget_show(vbox);
1723 gtk_widget_show(filer_window->scrollbar);
1725 gtk_widget_realize(filer_window->window);
1727 gdk_window_set_role(filer_window->window->window,
1728 filer_window->sym_path);
1730 filer_window_set_size(filer_window, 4, 4);
1733 static void filer_add_signals(FilerWindow *filer_window)
1735 GtkTargetEntry target_table[] =
1737 {"text/uri-list", 0, TARGET_URI_LIST},
1738 {"UTF8_STRING", 0, TARGET_STRING},
1739 {"STRING", 0, TARGET_STRING},
1740 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1743 /* Events on the top-level window */
1744 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1745 g_signal_connect(filer_window->window, "enter-notify-event",
1746 G_CALLBACK(pointer_in), filer_window);
1747 g_signal_connect(filer_window->window, "leave-notify-event",
1748 G_CALLBACK(pointer_out), filer_window);
1749 g_signal_connect(filer_window->window, "destroy",
1750 G_CALLBACK(filer_window_destroyed), filer_window);
1751 g_signal_connect(filer_window->window, "delete-event",
1752 G_CALLBACK(filer_window_delete), filer_window);
1754 g_signal_connect(filer_window->window, "selection_clear_event",
1755 G_CALLBACK(filer_lost_primary), filer_window);
1757 g_signal_connect(filer_window->window, "selection_get",
1758 G_CALLBACK(selection_get), filer_window);
1759 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1760 GDK_SELECTION_PRIMARY,
1761 target_table,
1762 sizeof(target_table) / sizeof(*target_table));
1764 g_signal_connect(filer_window->window, "popup-menu",
1765 G_CALLBACK(popup_menu), filer_window);
1766 g_signal_connect(filer_window->window, "key_press_event",
1767 G_CALLBACK(filer_key_press_event), filer_window);
1769 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1770 filer_keys);
1773 static gint clear_scanning_display(FilerWindow *filer_window)
1775 if (filer_exists(filer_window))
1776 filer_set_title(filer_window);
1777 return FALSE;
1780 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1782 if (scanning == filer_window->scanning)
1783 return;
1784 filer_window->scanning = scanning;
1786 if (scanning)
1787 filer_set_title(filer_window);
1788 else
1789 g_timeout_add(300, (GSourceFunc) clear_scanning_display,
1790 filer_window);
1793 /* Note that filer_window may not exist after this call.
1794 * Returns TRUE iff the directory still exists.
1796 gboolean filer_update_dir(FilerWindow *filer_window, gboolean warning)
1798 gboolean still_exists;
1800 still_exists = may_rescan(filer_window, warning);
1802 if (still_exists)
1803 dir_update(filer_window->directory, filer_window->sym_path);
1805 return still_exists;
1808 void filer_update_all(void)
1810 GList *next = all_filer_windows;
1812 while (next)
1814 FilerWindow *filer_window = (FilerWindow *) next->data;
1816 /* Updating directory may remove it from list -- stop sending
1817 * patches to move this line!
1819 next = next->next;
1821 /* Don't trigger a refresh if we're already scanning.
1822 * Otherwise, two views of a single directory will trigger
1823 * two scans.
1825 if (filer_window->directory &&
1826 !filer_window->directory->scanning)
1827 filer_update_dir(filer_window, TRUE);
1831 /* Refresh the various caches even if we don't think we need to */
1832 void full_refresh(void)
1834 mount_update(TRUE);
1835 reread_mime_files(); /* Refreshes all windows */
1838 /* See whether a filer window with a given path already exists
1839 * and is different from diff.
1841 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1843 GList *next;
1845 for (next = all_filer_windows; next; next = next->next)
1847 FilerWindow *filer_window = (FilerWindow *) next->data;
1849 if (filer_window != diff &&
1850 strcmp(sym_path, filer_window->sym_path) == 0)
1851 return filer_window;
1854 return NULL;
1857 /* This path has been mounted/umounted/deleted some files - update all dirs */
1858 void filer_check_mounted(const char *real_path)
1860 GList *next = all_filer_windows;
1861 gchar *parent;
1862 int len;
1863 gboolean resize = o_filer_auto_resize.int_value == RESIZE_ALWAYS;
1865 /* DOS disks, etc, often don't change the mtime of the root directory
1866 * on modification, so force a refresh now.
1868 g_fscache_update(dir_cache, real_path);
1870 len = strlen(real_path);
1872 while (next)
1874 FilerWindow *filer_window = (FilerWindow *) next->data;
1876 next = next->next;
1878 if (strncmp(real_path, filer_window->real_path, len) == 0)
1880 char s = filer_window->real_path[len];
1882 if (s == '/' || s == '\0')
1884 if (filer_update_dir(filer_window, FALSE) &&
1885 resize)
1886 view_autosize(filer_window->view);
1891 parent = g_path_get_dirname(real_path);
1892 refresh_dirs(parent);
1893 g_free(parent);
1895 icons_may_update(real_path);
1898 /* Close all windows displaying 'path' or subdirectories of 'path' */
1899 void filer_close_recursive(const char *path)
1901 GList *next = all_filer_windows;
1902 gchar *real;
1903 int len;
1905 real = pathdup(path);
1906 len = strlen(real);
1908 while (next)
1910 FilerWindow *filer_window = (FilerWindow *) next->data;
1912 next = next->next;
1914 if (strncmp(real, filer_window->real_path, len) == 0)
1916 char s = filer_window->real_path[len];
1918 if (len == 1 || s == '/' || s == '\0')
1919 gtk_widget_destroy(filer_window->window);
1924 /* Like minibuffer_show(), except that:
1925 * - It returns FALSE (to be used from an idle callback)
1926 * - It checks that the filer window still exists.
1928 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1930 if (filer_exists(filer_window))
1931 minibuffer_show(filer_window, MINI_PATH);
1932 return FALSE;
1935 /* TRUE iff filer_window points to an existing FilerWindow
1936 * structure.
1938 gboolean filer_exists(FilerWindow *filer_window)
1940 GList *next;
1942 for (next = all_filer_windows; next; next = next->next)
1944 FilerWindow *fw = (FilerWindow *) next->data;
1946 if (fw == filer_window)
1947 return TRUE;
1950 return FALSE;
1953 FilerWindow *filer_get_by_id(const char *id)
1955 return g_hash_table_lookup(window_with_id, id);
1958 void filer_set_id(FilerWindow *filer_window, const char *id)
1960 g_return_if_fail(filer_window != NULL);
1962 if (filer_window->window_id)
1964 g_hash_table_remove(window_with_id, filer_window->window_id);
1965 g_free(filer_window->window_id);
1966 filer_window->window_id = NULL;
1969 if (id)
1971 filer_window->window_id = g_strdup(id);
1972 g_hash_table_insert(window_with_id,
1973 filer_window->window_id,
1974 filer_window);
1978 /* Make sure the window title is up-to-date */
1979 void filer_set_title(FilerWindow *filer_window)
1981 gchar *title = NULL;
1982 guchar *flags = "";
1983 gchar *hidden = "";
1985 if (filer_window->scanning ||
1986 filer_window->filter != FILER_SHOW_ALL ||
1987 filer_window->show_hidden || filer_window->show_thumbs)
1989 if (o_short_flag_names.int_value)
1992 switch(filer_window->filter) {
1993 case FILER_SHOW_ALL:
1994 hidden=filer_window->show_hidden? "A": "";
1995 break;
1996 case FILER_SHOW_GLOB: hidden="G"; break;
1997 case FILER_SHOW_REGEXP: hidden="R"; break;
1998 default: break;
2001 flags = g_strconcat(" +",
2002 filer_window->scanning ? _("S") : "",
2003 hidden,
2004 filer_window->show_thumbs ? _("T") : "",
2005 NULL);
2007 else
2009 switch(filer_window->filter) {
2010 case FILER_SHOW_ALL:
2011 hidden=g_strdup(filer_window->show_hidden? "All, ": "");
2012 break;
2013 case FILER_SHOW_GLOB:
2014 hidden=g_strdup_printf("Glob (%s), ",
2015 filer_window->filter_string);
2016 break;
2017 case FILER_SHOW_REGEXP:
2018 hidden=g_strdup_printf("Regexp (%s), ",
2019 filer_window->filter_string);
2020 break;
2021 default:
2022 hidden=g_strdup("");
2023 break;
2025 flags = g_strconcat(" (",
2026 filer_window->scanning ? _("Scanning, ") : "",
2027 hidden,
2028 filer_window->show_thumbs ? _("Thumbs, ") : "",
2029 NULL);
2030 flags[strlen(flags) - 2] = ')';
2031 g_free(hidden);
2035 if (not_local)
2036 title = g_strconcat("//", our_host_name(),
2037 filer_window->sym_path, flags, NULL);
2039 if (!title && home_dir_len > 1 &&
2040 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
2042 guchar sep = filer_window->sym_path[home_dir_len];
2044 if (sep == '\0' || sep == '/')
2045 title = g_strconcat("~",
2046 filer_window->sym_path + home_dir_len,
2047 flags,
2048 NULL);
2051 if (!title)
2052 title = g_strconcat(filer_window->sym_path, flags, NULL);
2054 ensure_utf8(&title);
2056 if (filer_window->directory->error)
2058 gchar *old = title;
2059 title = g_strconcat(old, ": ", filer_window->directory->error,
2060 NULL);
2061 g_free(old);
2064 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
2066 g_free(title);
2068 if (flags[0] != '\0')
2069 g_free(flags);
2072 /* Reconnect to the same directory (used when the Show Hidden option is
2073 * toggled). This has the side-effect of updating the window title.
2075 void filer_detach_rescan(FilerWindow *filer_window)
2077 Directory *dir = filer_window->directory;
2079 g_object_ref(dir);
2080 detach(filer_window);
2081 filer_window->directory = dir;
2082 attach(filer_window);
2085 /* Puts the filer window into target mode. When an item is chosen,
2086 * fn(filer_window, iter, data) is called. 'reason' will be displayed
2087 * on the toolbar while target mode is active.
2089 * Use fn == NULL to cancel target mode.
2091 void filer_target_mode(FilerWindow *filer_window,
2092 TargetFunc fn,
2093 gpointer data,
2094 const char *reason)
2096 TargetFunc old_fn = filer_window->target_cb;
2098 if (fn != old_fn)
2099 gdk_window_set_cursor(
2100 GTK_WIDGET(filer_window->view)->window,
2101 fn ? crosshair : NULL);
2103 filer_window->target_cb = fn;
2104 filer_window->target_data = data;
2106 if (filer_window->toolbar_text == NULL)
2107 return;
2109 if (fn)
2110 gtk_label_set_text(
2111 GTK_LABEL(filer_window->toolbar_text), reason);
2112 else if (o_toolbar_info.int_value)
2114 if (old_fn)
2115 toolbar_update_info(filer_window);
2117 else
2118 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2121 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2123 GtkStateType old_state = filer_window->selection_state;
2125 filer_window->selection_state = normal
2126 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2128 if (old_state != filer_window->selection_state
2129 && view_count_selected(filer_window->view))
2130 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
2133 void filer_cancel_thumbnails(FilerWindow *filer_window)
2135 gtk_widget_hide(filer_window->thumb_bar);
2137 destroy_glist(&filer_window->thumb_queue);
2138 filer_window->max_thumbs = 0;
2141 /* Generate the next thumb for this window. The window object is
2142 * unref'd when there is nothing more to do.
2143 * If the window no longer has a filer window, nothing is done.
2145 static gboolean filer_next_thumb_real(GObject *window)
2147 FilerWindow *filer_window;
2148 gchar *path;
2149 int done, total;
2151 filer_window = g_object_get_data(window, "filer_window");
2153 if (!filer_window)
2155 g_object_unref(window);
2156 return FALSE;
2159 if (!filer_window->thumb_queue)
2161 filer_cancel_thumbnails(filer_window);
2162 g_object_unref(window);
2163 return FALSE;
2166 total = filer_window->max_thumbs;
2167 done = total - g_list_length(filer_window->thumb_queue);
2169 path = (gchar *) filer_window->thumb_queue->data;
2171 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2173 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2174 path);
2175 g_free(path);
2177 gtk_progress_bar_set_fraction(
2178 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2179 done / (float) total);
2181 return FALSE;
2184 /* path is the thumb just loaded, if any.
2185 * window is unref'd (eventually).
2187 static void filer_next_thumb(GObject *window, const gchar *path)
2189 if (path)
2190 dir_force_update_path(path);
2192 g_idle_add((GSourceFunc) filer_next_thumb_real, window);
2195 static void start_thumb_scanning(FilerWindow *filer_window)
2197 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2198 return; /* Already scanning */
2200 gtk_widget_show_all(filer_window->thumb_bar);
2202 g_object_ref(G_OBJECT(filer_window->window));
2203 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2206 /* Set this image to be loaded some time in the future */
2207 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2209 if (g_list_find_custom(filer_window->thumb_queue, path,
2210 (GCompareFunc) strcmp))
2211 return;
2213 if (!filer_window->thumb_queue)
2214 filer_window->max_thumbs=0;
2215 filer_window->max_thumbs++;
2217 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2218 g_strdup(path));
2220 if (filer_window->scanning)
2221 return; /* Will start when scan ends */
2223 start_thumb_scanning(filer_window);
2226 /* If thumbnail display is on, look through all the items in this directory
2227 * and start creating or updating the thumbnails as needed.
2229 void filer_create_thumbs(FilerWindow *filer_window)
2231 DirItem *item;
2232 ViewIter iter;
2234 if (!filer_window->show_thumbs)
2235 return;
2237 view_get_iter(filer_window->view, &iter, 0);
2239 while ((item = iter.next(&iter)))
2241 MaskedPixmap *pixmap;
2242 const guchar *path;
2243 gboolean found;
2245 if (item->base_type != TYPE_FILE)
2246 continue;
2248 /*if (strcmp(item->mime_type->media_type, "image") != 0)
2249 continue;*/
2251 path = make_path(filer_window->real_path, item->leafname);
2253 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2254 FSCACHE_LOOKUP_ONLY_NEW, &found);
2255 if (pixmap)
2256 g_object_unref(pixmap);
2258 /* If we didn't get an image, it could be because:
2260 * - We're loading the image now. found is TRUE,
2261 * and we'll update the item later.
2262 * - We tried to load the image and failed. found
2263 * is TRUE.
2264 * - We haven't tried loading the image. found is
2265 * FALSE, and we start creating the thumb here.
2267 if (!found)
2268 filer_create_thumb(filer_window, path);
2272 static void filer_options_changed(void)
2274 if (o_short_flag_names.has_changed)
2276 GList *next;
2278 for (next = all_filer_windows; next; next = next->next)
2280 FilerWindow *filer_window = (FilerWindow *) next->data;
2282 filer_set_title(filer_window);
2287 /* Append interesting information to this GString */
2288 void filer_add_tip_details(FilerWindow *filer_window,
2289 GString *tip, DirItem *item)
2291 const guchar *fullpath = NULL;
2293 fullpath = make_path(filer_window->real_path, item->leafname);
2295 if (item->flags & ITEM_FLAG_SYMLINK)
2297 char *target;
2299 target = readlink_dup(fullpath);
2300 if (target)
2302 ensure_utf8(&target);
2304 g_string_append(tip, _("Symbolic link to "));
2305 g_string_append(tip, target);
2306 g_string_append_c(tip, '\n');
2307 g_free(target);
2311 if (item->flags & ITEM_FLAG_APPDIR)
2313 XMLwrapper *info;
2314 xmlNode *node;
2316 info = appinfo_get(fullpath, item);
2317 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
2319 guchar *str;
2320 str = xmlNodeListGetString(node->doc,
2321 node->xmlChildrenNode, 1);
2322 if (str)
2324 g_string_append(tip, str);
2325 g_string_append_c(tip, '\n');
2326 g_free(str);
2329 if (info)
2330 g_object_unref(info);
2333 if (!g_utf8_validate(item->leafname, -1, NULL))
2334 g_string_append(tip,
2335 _("This filename is not valid UTF-8. "
2336 "You should rename it.\n"));
2339 /* Return the selection as a text/uri-list.
2340 * g_free() the result.
2342 static guchar *filer_create_uri_list(FilerWindow *filer_window)
2344 GString *string;
2345 GString *leader;
2346 ViewIter iter;
2347 DirItem *item;
2348 guchar *retval;
2350 g_return_val_if_fail(filer_window != NULL, NULL);
2352 string = g_string_new(NULL);
2354 leader = g_string_new(filer_window->sym_path);
2355 if (leader->str[leader->len - 1] != '/')
2356 g_string_append_c(leader, '/');
2358 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
2359 while ((item = iter.next(&iter)))
2361 EscapedPath *uri;
2362 char *path;
2364 path = g_strconcat(leader->str, item->leafname, NULL);
2365 uri = encode_path_as_uri(path);
2366 g_string_append(string, (char *) uri);
2367 g_string_append(string, "\r\n");
2368 g_free(path);
2369 g_free(uri);
2372 g_string_free(leader, TRUE);
2373 retval = string->str;
2374 g_string_free(string, FALSE);
2376 return retval;
2379 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
2381 BindAction action;
2382 ViewIface *view = filer_window->view;
2383 DirItem *item = NULL;
2384 gboolean press = event->type == GDK_BUTTON_PRESS;
2385 ViewIter iter;
2386 OpenFlags flags = 0;
2388 if (event->button > 3)
2389 return;
2391 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2392 item = iter.peek(&iter);
2394 if (item && event->button == 1 &&
2395 view_get_selected(view, &iter) &&
2396 filer_window->selection_state == GTK_STATE_INSENSITIVE)
2398 /* Possibly a really slow DnD operation? */
2399 filer_window->temp_item_selected = FALSE;
2401 filer_selection_changed(filer_window, event->time);
2402 return;
2405 if (filer_window->target_cb)
2407 dnd_motion_ungrab();
2408 if (item && press && event->button == 1)
2409 filer_window->target_cb(filer_window, &iter,
2410 filer_window->target_data);
2412 filer_target_mode(filer_window, NULL, NULL, NULL);
2414 return;
2417 if (!o_single_click.int_value)
2419 /* Make sure both parts of a double-click fall on
2420 * the same file.
2422 static guchar *first_click = NULL;
2423 static guchar *second_click = NULL;
2425 if (event->type == GDK_BUTTON_PRESS)
2427 g_free(first_click);
2428 first_click = second_click;
2430 if (item)
2431 second_click = g_strdup(item->leafname);
2432 else
2433 second_click = NULL;
2436 if (event->type == GDK_2BUTTON_PRESS)
2438 if (first_click && second_click &&
2439 strcmp(first_click, second_click) != 0)
2440 return;
2441 if ((first_click || second_click) &&
2442 !(first_click && second_click))
2443 return;
2447 action = bind_lookup_bev(
2448 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
2449 event);
2451 switch (action)
2453 case ACT_CLEAR_SELECTION:
2454 view_clear_selection(view);
2455 break;
2456 case ACT_TOGGLE_SELECTED:
2457 view_set_selected(view, &iter,
2458 !view_get_selected(view, &iter));
2459 break;
2460 case ACT_SELECT_EXCL:
2461 view_select_only(view, &iter);
2462 break;
2463 case ACT_EDIT_ITEM:
2464 flags |= OPEN_SHIFT;
2465 /* (no break) */
2466 case ACT_OPEN_ITEM:
2467 if (event->button != 1 || event->state & GDK_MOD1_MASK)
2468 flags |= OPEN_CLOSE_WINDOW;
2469 else
2470 flags |= OPEN_SAME_WINDOW;
2471 if (o_new_button_1.int_value)
2472 flags ^= OPEN_SAME_WINDOW;
2473 if (event->type == GDK_2BUTTON_PRESS)
2474 view_set_selected(view, &iter, FALSE);
2475 dnd_motion_ungrab();
2477 filer_openitem(filer_window, &iter, flags);
2478 break;
2479 case ACT_POPUP_MENU:
2480 dnd_motion_ungrab();
2481 tooltip_show(NULL);
2482 show_filer_menu(filer_window,
2483 (GdkEvent *) event, &iter);
2484 break;
2485 case ACT_PRIME_AND_SELECT:
2486 if (item && !view_get_selected(view, &iter))
2487 view_select_only(view, &iter);
2488 dnd_motion_start(MOTION_READY_FOR_DND);
2489 break;
2490 case ACT_PRIME_AND_TOGGLE:
2491 view_set_selected(view, &iter,
2492 !view_get_selected(view, &iter));
2493 dnd_motion_start(MOTION_READY_FOR_DND);
2494 break;
2495 case ACT_PRIME_FOR_DND:
2496 dnd_motion_start(MOTION_READY_FOR_DND);
2497 break;
2498 case ACT_IGNORE:
2499 if (press && event->button < 4)
2501 if (item)
2502 view_wink_item(view, &iter);
2503 dnd_motion_start(MOTION_NONE);
2505 break;
2506 case ACT_LASSO_CLEAR:
2507 view_clear_selection(view);
2508 /* (no break) */
2509 case ACT_LASSO_MODIFY:
2510 view_start_lasso_box(view, event);
2511 break;
2512 case ACT_RESIZE:
2513 view_autosize(filer_window->view);
2514 break;
2515 default:
2516 g_warning("Unsupported action : %d\n", action);
2517 break;
2521 /* It's time to make the tooltip appear. If we're not over the item any
2522 * more, or the item doesn't need a tooltip, do nothing.
2524 static gboolean tooltip_activate(GtkWidget *window)
2526 FilerWindow *filer_window;
2527 ViewIface *view;
2528 ViewIter iter;
2529 gint x, y;
2530 DirItem *item = NULL;
2531 GString *tip = NULL;
2533 g_return_val_if_fail(tip_item != NULL, 0);
2535 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2537 if (!motion_window || !filer_window)
2538 return FALSE; /* Window has been destroyed */
2540 view = filer_window->view;
2542 tooltip_show(NULL);
2544 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2545 view_get_iter_at_point(view, &iter, motion_window, x, y);
2547 item = iter.peek(&iter);
2548 if (item != tip_item)
2549 return FALSE; /* Not still under the pointer */
2551 /* OK, the filer window still exists and the pointer is still
2552 * over the same item. Do we need to show a tip?
2555 tip = g_string_new(NULL);
2557 view_extend_tip(filer_window->view, &iter, tip);
2559 filer_add_tip_details(filer_window, tip, tip_item);
2561 if (tip->len > 1)
2563 g_string_truncate(tip, tip->len - 1);
2565 tooltip_show(tip->str);
2568 g_string_free(tip, TRUE);
2570 return FALSE;
2573 /* Motion detected on the View widget */
2574 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2576 ViewIface *view = filer_window->view;
2577 ViewIter iter;
2578 DirItem *item;
2580 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2581 item = iter.peek(&iter);
2583 if (item)
2585 if (item != tip_item)
2587 tooltip_show(NULL);
2589 tip_item = item;
2590 motion_window = event->window;
2591 tooltip_prime((GtkFunction) tooltip_activate,
2592 G_OBJECT(filer_window->window));
2595 else
2597 tooltip_show(NULL);
2598 tip_item = NULL;
2601 if (motion_state != MOTION_READY_FOR_DND)
2602 return FALSE;
2604 if (!dnd_motion_moved(event))
2605 return FALSE;
2607 view_get_iter_at_point(view, &iter,
2608 event->window,
2609 event->x - (event->x_root - drag_start_x),
2610 event->y - (event->y_root - drag_start_y));
2611 item = iter.peek(&iter);
2612 if (!item)
2613 return FALSE;
2615 view_wink_item(view, NULL);
2617 if (!view_get_selected(view, &iter))
2619 if (event->state & GDK_BUTTON1_MASK)
2621 /* Select just this one */
2622 filer_window->temp_item_selected = TRUE;
2623 view_select_only(view, &iter);
2625 else
2627 if (view_count_selected(view) == 0)
2628 filer_window->temp_item_selected = TRUE;
2629 view_set_selected(view, &iter, TRUE);
2633 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2635 if (view_count_selected(view) == 1)
2637 if (item->base_type == TYPE_UNKNOWN)
2638 item = dir_update_item(filer_window->directory,
2639 item->leafname);
2641 if (!item)
2643 report_error(_("Item no longer exists!"));
2644 return FALSE;
2647 drag_one_item(GTK_WIDGET(view), event,
2648 make_path(filer_window->sym_path, item->leafname),
2649 item, di_image(item));
2650 #if 0
2651 /* XXX: Use thumbnail */
2652 item, view ? view->image : NULL);
2653 #endif
2655 else
2657 guchar *uris;
2659 uris = filer_create_uri_list(filer_window);
2660 drag_selection(GTK_WIDGET(view), event, uris);
2661 g_free(uris);
2664 return FALSE;
2667 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2668 FilerWindow *filer_window)
2670 filer_set_autoscroll(filer_window, FALSE);
2672 if (filer_window->temp_item_selected)
2674 view_clear_selection(filer_window->view);
2675 filer_window->temp_item_selected = FALSE;
2679 /* Remove highlights */
2680 static void drag_leave(GtkWidget *widget,
2681 GdkDragContext *context,
2682 guint32 time,
2683 FilerWindow *filer_window)
2685 dnd_spring_abort();
2688 /* Called during the drag when the mouse is in a widget registered
2689 * as a drop target. Returns TRUE if we can accept the drop.
2691 static gboolean drag_motion(GtkWidget *widget,
2692 GdkDragContext *context,
2693 gint x,
2694 gint y,
2695 guint time,
2696 FilerWindow *filer_window)
2698 DirItem *item;
2699 ViewIface *view = filer_window->view;
2700 ViewIter iter;
2701 GdkDragAction action = context->suggested_action;
2702 const guchar *new_path = NULL;
2703 const char *type = NULL;
2704 gboolean retval = FALSE;
2705 gboolean same_window;
2707 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value)
2709 guint state;
2710 gdk_window_get_pointer(NULL, NULL, NULL, &state);
2711 if (state & GDK_BUTTON1_MASK)
2712 action = GDK_ACTION_ASK;
2715 same_window = gtk_drag_get_source_widget(context) == widget;
2717 filer_set_autoscroll(filer_window, TRUE);
2719 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2721 GdkWindow *bin;
2722 int bin_y;
2723 /* Correct for position of bin window */
2724 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2725 gdk_window_get_position(bin, NULL, &bin_y);
2726 y -= bin_y;
2729 if (o_dnd_drag_to_icons.int_value)
2731 view_get_iter_at_point(view, &iter, widget->window, x, y);
2732 item = iter.peek(&iter);
2734 else
2735 item = NULL;
2737 if (item && same_window && view_get_selected(view, &iter))
2738 type = NULL;
2739 else
2740 type = dnd_motion_item(context, &item);
2742 if (!type)
2743 item = NULL;
2745 /* Don't allow drops to non-writeable directories. BUT, still
2746 * allow drops on non-writeable SUBdirectories so that we can
2747 * do the spring-open thing.
2749 if (item && type == drop_dest_dir &&
2750 !(item->flags & ITEM_FLAG_APPDIR))
2752 dnd_spring_load(context, filer_window);
2754 else
2755 dnd_spring_abort();
2757 if (item)
2758 view_cursor_to_iter(view, &iter);
2759 else
2761 view_cursor_to_iter(view, NULL);
2763 /* Disallow background drops within a single window */
2764 if (type && same_window)
2765 type = NULL;
2768 if (type)
2770 if (item)
2771 new_path = make_path(filer_window->sym_path,
2772 item->leafname);
2773 else
2774 new_path = filer_window->sym_path;
2777 /* Don't ask about dragging to an application! */
2778 if (type == drop_dest_prog && action == GDK_ACTION_ASK)
2779 action = GDK_ACTION_COPY;
2781 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2782 if (type)
2784 gdk_drag_status(context, action, time);
2785 g_dataset_set_data_full(context, "drop_dest_path",
2786 g_strdup(new_path), g_free);
2787 retval = TRUE;
2790 return retval;
2793 static gboolean as_timeout(FilerWindow *filer_window)
2795 gboolean retval;
2797 retval = view_auto_scroll_callback(filer_window->view);
2799 if (!retval)
2800 filer_window->auto_scroll = -1;
2802 return retval;
2805 /* When autoscroll is on, a timer keeps track of the pointer position.
2806 * While it's near the top or bottom of the window, the window scrolls.
2808 * If the mouse buttons are released, the pointer leaves the window, or
2809 * a drag-and-drop operation finishes, auto_scroll is turned off.
2811 void filer_set_autoscroll(FilerWindow *filer_window, gboolean auto_scroll)
2813 g_return_if_fail(filer_window != NULL);
2815 if (auto_scroll)
2817 if (filer_window->auto_scroll != -1)
2818 return; /* Already on! */
2820 filer_window->auto_scroll = g_timeout_add(50,
2821 (GSourceFunc) as_timeout,
2822 filer_window);
2824 else
2826 if (filer_window->auto_scroll == -1)
2827 return; /* Already off! */
2829 g_source_remove(filer_window->auto_scroll);
2830 filer_window->auto_scroll = -1;
2834 #define ZERO_MNT "/uri/0install"
2836 static void refresh_done(FilerWindow *filer_window)
2838 if (filer_exists(filer_window))
2839 filer_update_dir(filer_window, TRUE);
2842 void filer_refresh(FilerWindow *filer_window)
2844 if (!strncmp(ZERO_MNT "/", filer_window->real_path, sizeof(ZERO_MNT)))
2846 /* Try to run 0refresh */
2847 gint pid;
2848 gchar *argv[] = {"0refresh", NULL, NULL};
2849 const char *host = filer_window->real_path + sizeof(ZERO_MNT);
2850 const char *slash;
2852 slash = strchr(host, '/');
2853 if (slash)
2854 argv[1] = g_strndup(host, slash - host);
2855 else
2856 argv[1] = g_strdup(host);
2857 pid = rox_spawn(filer_window->real_path, (const char **) argv);
2858 g_free(argv[1]);
2859 if (pid)
2860 on_child_death(pid, (CallbackFn) refresh_done,
2861 filer_window);
2864 full_refresh();
2867 gboolean filer_match_filter(FilerWindow *filer_window, const gchar *filename)
2869 g_return_val_if_fail(filename != NULL, FALSE);
2871 if(filename[0]=='.' &&
2872 (!filer_window->temp_show_hidden && !filer_window->show_hidden))
2873 return FALSE;
2875 switch(filer_window->filter) {
2876 case FILER_SHOW_GLOB:
2877 return fnmatch(filer_window->filter_string,
2878 filename, 0)==0;
2880 case FILER_SHOW_REGEXP: /* Unimplemented */
2882 case FILER_SHOW_ALL:
2883 default:
2884 break;
2886 return TRUE;
2889 /* Provided to hide the implementation */
2890 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
2892 filer_window->show_hidden=hidden;
2895 /* Set the filter type. Returns TRUE if the type has changed
2896 * (must call filer_detach_rescan).
2898 gboolean filer_set_filter(FilerWindow *filer_window, FilterType type,
2899 const gchar *filter_string)
2901 /* Is this new filter the same as the old one? */
2902 if (filer_window->filter == type)
2904 switch(filer_window->filter)
2906 case FILER_SHOW_ALL:
2907 return FALSE;
2908 case FILER_SHOW_GLOB:
2909 case FILER_SHOW_REGEXP:
2910 if (strcmp(filer_window->filter_string,
2911 filter_string) == 0)
2912 return FALSE;
2913 break;
2917 /* Clean up old filter */
2918 if (filer_window->filter_string)
2920 g_free(filer_window->filter_string);
2921 filer_window->filter_string = NULL;
2923 /* Also clean up compiled regexp when implemented */
2925 filer_window->filter = type;
2927 switch(type)
2929 case FILER_SHOW_ALL:
2930 /* No extra work */
2931 break;
2933 case FILER_SHOW_GLOB:
2934 filer_window->filter_string = g_strdup(filter_string);
2935 break;
2937 case FILER_SHOW_REGEXP:
2938 filer_window->filter_string = g_strdup(filter_string);
2939 /* Compile the pattern */
2940 break;
2942 default:
2943 /* oops */
2944 filer_window->filter = FILER_SHOW_ALL;
2945 g_warning("Impossible: filter type %d", type);
2946 break;
2949 return TRUE;
2952 /* Setting stuff */
2953 static Settings *settings_new(const char *path)
2955 Settings *set;
2957 set=g_new(Settings, 1);
2958 memset(set, 0, sizeof(Settings));
2959 if(path)
2960 set->path=g_strdup(path);
2962 return set;
2965 static void settings_free(Settings *set)
2967 g_free(set->path);
2968 if(set->filter)
2969 g_free(set->filter);
2970 g_free(set);
2973 static void store_settings(Settings *set)
2975 Settings *old;
2977 old=g_hash_table_lookup(settings_table, set->path);
2978 if(old)
2980 g_hash_table_remove(settings_table, set->path);
2981 settings_free(old);
2984 g_hash_table_insert(settings_table, set->path, set);
2987 /* TODO: use symbolic names in the XML file where possible */
2988 static void load_from_node(Settings *set, xmlDocPtr doc, xmlNodePtr node)
2990 xmlChar *str=NULL;
2992 str=xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
2994 if(strcmp(node->name, "X") == 0) {
2995 set->x=atoi(str);
2996 set->flags|=SET_POSITION;
2997 } else if(strcmp(node->name, "Y") == 0) {
2998 set->y=atoi(str);
2999 set->flags|=SET_POSITION;
3000 } else if(strcmp(node->name, "Width") == 0) {
3001 set->width=atoi(str);
3002 set->flags|=SET_SIZE;
3003 } else if(strcmp(node->name, "Height") == 0) {
3004 set->height=atoi(str);
3005 set->flags|=SET_SIZE;
3006 } else if(strcmp(node->name, "ShowHidden") == 0) {
3007 set->show_hidden=atoi(str);
3008 set->flags|=SET_HIDDEN;
3009 } else if(strcmp(node->name, "ViewType") == 0) {
3010 set->view_type=atoi(str);
3011 set->flags|=SET_DETAILS;
3012 } else if(strcmp(node->name, "DetailsType") == 0) {
3013 set->details_type=atoi(str);
3014 set->flags|=SET_DETAILS;
3015 } else if(strcmp(node->name, "SortType") == 0) {
3016 set->sort_type=atoi(str);
3017 set->flags|=SET_SORT;
3018 } else if(strcmp(node->name, "SortOrder") == 0) {
3019 set->sort_order=atoi(str);
3020 set->flags|=SET_SORT;
3021 } else if(strcmp(node->name, "DisplayStyle") == 0) {
3022 set->display_style=atoi(str);
3023 set->flags|=SET_STYLE;
3024 } else if(strcmp(node->name, "ShowThumbs") == 0) {
3025 set->show_thumbs=atoi(str);
3026 set->flags|=SET_THUMBS;
3027 } else if(strcmp(node->name, "FilterType") == 0) {
3028 set->filter_type=atoi(str);
3029 set->flags|=SET_FILTER;
3030 } else if(strcmp(node->name, "Filter") == 0) {
3031 set->filter=g_strdup(str);
3032 set->flags|=SET_FILTER;
3035 if(str)
3036 xmlFree(str);
3039 static void load_settings(void)
3041 gchar *path;
3042 XMLwrapper *settings_doc=NULL;
3044 path=choices_find_xdg_path_load("Settings.xml", PROJECT, SITE);
3045 if(path) {
3046 settings_doc=xml_new(path);
3047 g_free(path);
3050 if(!settings_table)
3051 settings_table=g_hash_table_new(g_str_hash, g_str_equal);
3053 if(settings_doc) {
3054 xmlNodePtr node, subnode;
3056 node = xmlDocGetRootElement(settings_doc->doc);
3058 for (node = node->xmlChildrenNode; node; node = node->next)
3060 Settings *set;
3061 xmlChar *path;
3063 if (node->type != XML_ELEMENT_NODE)
3064 continue;
3065 if (strcmp(node->name, "FilerWindow") != 0)
3066 continue;
3068 path=xmlGetProp(node, "path");
3069 set=settings_new(path);
3070 xmlFree(path);
3072 for (subnode=node->xmlChildrenNode; subnode;
3073 subnode=subnode->next) {
3075 if (subnode->type != XML_ELEMENT_NODE)
3076 continue;
3077 load_from_node(set, settings_doc->doc,
3078 subnode);
3081 store_settings(set);
3083 g_object_unref(settings_doc);
3087 static void add_nodes(gpointer key, gpointer value, gpointer data)
3089 xmlNodePtr node=(xmlNodePtr) data;
3090 xmlNodePtr sub;
3091 Settings *set=(Settings *) value;
3092 char *tmp;
3094 sub=xmlNewChild(node, NULL, "FilerWindow", NULL);
3096 xmlSetProp(sub, "path", set->path);
3098 if(set->flags & SET_POSITION) {
3099 tmp=g_strdup_printf("%d", set->x);
3100 xmlNewChild(sub, NULL, "X", tmp);
3101 g_free(tmp);
3102 tmp=g_strdup_printf("%d", set->y);
3103 xmlNewChild(sub, NULL, "Y", tmp);
3104 g_free(tmp);
3106 if(set->flags & SET_SIZE) {
3107 tmp=g_strdup_printf("%d", set->width);
3108 xmlNewChild(sub, NULL, "Width", tmp);
3109 g_free(tmp);
3110 tmp=g_strdup_printf("%d", set->height);
3111 xmlNewChild(sub, NULL, "Height", tmp);
3112 g_free(tmp);
3114 if(set->flags & SET_HIDDEN) {
3115 tmp=g_strdup_printf("%d", set->show_hidden);
3116 xmlNewChild(sub, NULL, "ShowHidden", tmp);
3117 g_free(tmp);
3119 if(set->flags & SET_STYLE) {
3120 tmp=g_strdup_printf("%d", set->display_style);
3121 xmlNewChild(sub, NULL, "DisplayStyle", tmp);
3122 g_free(tmp);
3124 if(set->flags & SET_SORT) {
3125 tmp=g_strdup_printf("%d", set->sort_type);
3126 xmlNewChild(sub, NULL, "SortType", tmp);
3127 g_free(tmp);
3128 tmp=g_strdup_printf("%d", set->sort_order);
3129 xmlNewChild(sub, NULL, "SortOrder", tmp);
3130 g_free(tmp);
3132 if(set->flags & SET_DETAILS) {
3133 tmp=g_strdup_printf("%d", set->view_type);
3134 xmlNewChild(sub, NULL, "ViewType", tmp);
3135 g_free(tmp);
3136 tmp=g_strdup_printf("%d", set->details_type);
3137 xmlNewChild(sub, NULL, "DetailsType", tmp);
3138 g_free(tmp);
3140 if(set->flags & SET_STYLE) {
3141 tmp=g_strdup_printf("%d", set->show_thumbs);
3142 xmlNewChild(sub, NULL, "ShowThumbs", tmp);
3143 g_free(tmp);
3145 if(set->flags & SET_FILTER) {
3146 tmp=g_strdup_printf("%d", set->filter_type);
3147 xmlNewChild(sub, NULL, "FilterType", tmp);
3148 g_free(tmp);
3149 if(set->filter && set->filter[0])
3150 xmlNewChild(sub, NULL, "Filter", set->filter);
3154 static void save_settings(void)
3156 gchar *path;
3158 path=choices_find_xdg_path_save("Settings.xml", PROJECT, SITE, TRUE);
3159 if(path) {
3160 xmlDocPtr doc = xmlNewDoc("1.0");
3161 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL,
3162 "Settings", NULL));
3164 g_hash_table_foreach(settings_table, add_nodes,
3165 xmlDocGetRootElement(doc));
3167 save_xml_file(doc, path);
3169 g_free(path);
3170 xmlFreeDoc(doc);
3175 static void check_settings(FilerWindow *filer_window)
3177 Settings *set;
3179 set=(Settings *) g_hash_table_lookup(settings_table,
3180 filer_window->sym_path);
3182 if(set) {
3183 if(set->flags & SET_POSITION)
3184 gtk_window_move(GTK_WINDOW(filer_window->window),
3185 set->x, set->y);
3186 if(set->flags & SET_SIZE)
3187 filer_window_set_size(filer_window, set->width,
3188 set->height);
3189 if(set->flags & SET_HIDDEN)
3190 filer_set_hidden(filer_window, set->show_hidden);
3192 if(set->flags & (SET_STYLE|SET_DETAILS)) {
3193 DisplayStyle style=filer_window->display_style;
3194 DetailsType details=filer_window->details_type;
3196 if(set->flags & SET_STYLE)
3197 style=set->display_style;
3199 if(set->flags & SET_DETAILS) {
3200 details=set->details_type;
3202 filer_set_view_type(filer_window,
3203 set->view_type);
3206 display_set_layout(filer_window, style,
3207 details, FALSE);
3210 if(set->flags & SET_SORT)
3211 display_set_sort_type(filer_window,
3212 set->sort_type,
3213 set->sort_order);
3215 if(set->flags & SET_THUMBS)
3216 display_set_thumbs(filer_window,
3217 set->show_thumbs);
3219 if(set->flags & SET_FILTER)
3220 display_set_filter(filer_window,
3221 set->filter_type,
3222 set->filter);
3227 typedef struct settings_window {
3228 GtkWidget *window;
3230 GtkWidget *pos, *size, *hidden, *style, *sort, *details,
3231 *thumbs, *filter;
3233 Settings *set;
3234 } SettingsWindow;
3237 static void settings_response(GtkWidget *window, gint response,
3238 SettingsWindow *set_win)
3240 if(response==GTK_RESPONSE_OK) {
3241 gint flags=0;
3243 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->pos)))
3244 flags|=SET_POSITION;
3245 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->size)))
3246 flags|=SET_SIZE;
3247 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->hidden)))
3248 flags|=SET_HIDDEN;
3249 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->style)))
3250 flags|=SET_STYLE;
3251 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->sort)))
3252 flags|=SET_SORT;
3253 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->details)))
3254 flags|=SET_DETAILS;
3255 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->thumbs)))
3256 flags|=SET_THUMBS;
3257 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_win->filter)))
3258 flags|=SET_FILTER;
3260 set_win->set->flags=flags;
3261 store_settings(set_win->set);
3262 save_settings();
3265 gtk_widget_destroy(window);
3268 void filer_save_settings(FilerWindow *fwin)
3270 SettingsWindow *set_win;
3271 GtkWidget *vbox;
3272 GtkWidget *path;
3273 gint x, y;
3275 Settings *set=settings_new(fwin->sym_path);
3277 gtk_window_get_position(GTK_WINDOW(fwin->window),&x, &y);
3278 set->flags|=SET_POSITION;
3279 set->x=x;
3280 set->y=y;
3282 gtk_window_get_size(GTK_WINDOW(fwin->window),&x, &y);
3283 set->flags|=SET_SIZE;
3284 set->width=x;
3285 set->height=y;
3287 set->flags|=SET_HIDDEN;
3288 set->show_hidden=fwin->show_hidden;
3290 set->flags|=SET_STYLE;
3291 set->display_style=fwin->display_style;
3293 set->flags|=SET_SORT;
3294 set->sort_type=fwin->sort_type;
3295 set->sort_order=fwin->sort_order;
3297 set->flags|=SET_DETAILS;
3298 set->view_type=fwin->view_type;
3299 set->details_type=fwin->details_type;
3301 set->flags|=SET_THUMBS;
3302 set->show_thumbs=fwin->show_thumbs;
3304 set->flags|=SET_FILTER;
3305 set->filter_type=fwin->filter;
3306 if(fwin->filter_string)
3307 set->filter=g_strdup(fwin->filter_string);
3309 /* Store other parameters
3311 set_win=g_new(SettingsWindow, 1);
3313 set_win->window=gtk_dialog_new();
3314 number_of_windows++;
3316 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3317 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
3318 gtk_dialog_add_button(GTK_DIALOG(set_win->window),
3319 GTK_STOCK_OK, GTK_RESPONSE_OK);
3321 g_signal_connect(set_win->window, "destroy",
3322 G_CALLBACK(one_less_window), NULL);
3323 g_signal_connect_swapped(set_win->window, "destroy",
3324 G_CALLBACK(g_free), set_win);
3326 gtk_window_set_title(GTK_WINDOW(set_win->window),
3327 _("Select display properties to save"));
3329 vbox=GTK_DIALOG(set_win->window)->vbox;
3331 path=gtk_label_new(set->path);
3332 gtk_box_pack_start(GTK_BOX(vbox), path, FALSE, FALSE, 2);
3334 set_win->pos=gtk_check_button_new_with_label(_("Position"));
3335 gtk_box_pack_start(GTK_BOX(vbox), set_win->pos, FALSE, FALSE, 2);
3336 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->pos),
3337 set->flags & SET_POSITION);
3339 set_win->size=gtk_check_button_new_with_label(_("Size"));
3340 gtk_box_pack_start(GTK_BOX(vbox), set_win->size, FALSE, FALSE, 2);
3341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->size),
3342 set->flags & SET_SIZE);
3344 set_win->hidden=gtk_check_button_new_with_label(_("Show hidden"));
3345 gtk_box_pack_start(GTK_BOX(vbox), set_win->hidden,
3346 FALSE, FALSE, 2);
3347 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->hidden),
3348 set->flags & SET_HIDDEN);
3350 set_win->style=gtk_check_button_new_with_label(_("Display style"));
3351 gtk_box_pack_start(GTK_BOX(vbox), set_win->style,
3352 FALSE, FALSE, 2);
3353 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->style),
3354 set->flags & SET_STYLE);
3356 set_win->sort=gtk_check_button_new_with_label(_("Sort type and order"));
3357 gtk_box_pack_start(GTK_BOX(vbox), set_win->sort, FALSE, FALSE, 2);
3358 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->sort),
3359 set->flags & SET_SORT);
3361 set_win->details=gtk_check_button_new_with_label(_("Details"));
3362 gtk_box_pack_start(GTK_BOX(vbox), set_win->details, FALSE, FALSE, 2);
3363 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->details),
3364 set->flags & SET_DETAILS);
3366 set_win->thumbs=gtk_check_button_new_with_label(_("Thumbnails"));
3367 gtk_box_pack_start(GTK_BOX(vbox), set_win->thumbs,
3368 FALSE, FALSE, 2);
3369 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->thumbs),
3370 set->flags & SET_THUMBS);
3372 set_win->filter=gtk_check_button_new_with_label(_("Filter"));
3373 gtk_box_pack_start(GTK_BOX(vbox), set_win->filter,
3374 FALSE, FALSE, 2);
3375 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_win->filter),
3376 set->flags & SET_FILTER);
3378 set_win->set=set;
3379 g_signal_connect(set_win->window, "response",
3380 G_CALLBACK(settings_response), set_win);
3382 gtk_widget_show_all(set_win->window);