r1219: Internal changes to the options system. See text at start of options.c.
[rox-filer.git] / ROX-Filer / src / filer.c
bloba1af89153cf8ada1f41e56053df53417f3f1d939
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 <time.h>
31 #include <ctype.h>
32 #include <math.h>
33 #include <netdb.h>
34 #include <sys/param.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdkx.h>
38 #include <gdk/gdkkeysyms.h>
40 #include "global.h"
42 #include "collection.h"
43 #include "display.h"
44 #include "main.h"
45 #include "fscache.h"
46 #include "support.h"
47 #include "gui_support.h"
48 #include "filer.h"
49 #include "choices.h"
50 #include "pixmaps.h"
51 #include "menu.h"
52 #include "dnd.h"
53 #include "dir.h"
54 #include "diritem.h"
55 #include "run.h"
56 #include "type.h"
57 #include "options.h"
58 #include "minibuffer.h"
59 #include "icon.h"
60 #include "toolbar.h"
61 #include "bind.h"
62 #include "appinfo.h"
63 #include "mount.h"
65 #define PANEL_BORDER 2
67 static XMLwrapper *groups = NULL;
69 FilerWindow *window_with_focus = NULL;
70 GList *all_filer_windows = NULL;
72 static FilerWindow *window_with_primary = NULL;
74 /* Item we are about to display a tooltip for */
75 static DirItem *tip_item = NULL;
76 static GtkWidget *tip_widget = NULL;
77 static gint tip_timeout = 0;
78 static time_t tip_time = 0; /* Time tip widget last closed */
80 /* Static prototypes */
81 static void attach(FilerWindow *filer_window);
82 static void detach(FilerWindow *filer_window);
83 static void filer_window_destroyed(GtkWidget *widget,
84 FilerWindow *filer_window);
85 static void add_item(FilerWindow *filer_window, DirItem *item);
86 static void update_display(Directory *dir,
87 DirAction action,
88 GPtrArray *items,
89 FilerWindow *filer_window);
90 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
91 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
92 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
93 static FilerWindow *find_filer_window(char *path, FilerWindow *diff);
94 static gint coll_button_release(GtkWidget *widget,
95 GdkEventButton *event,
96 FilerWindow *filer_window);
97 static gint coll_button_press(GtkWidget *widget,
98 GdkEventButton *event,
99 FilerWindow *filer_window);
100 static gint coll_motion_notify(GtkWidget *widget,
101 GdkEventMotion *event,
102 FilerWindow *filer_window);
103 static void perform_action(FilerWindow *filer_window, GdkEventButton *event);
104 static void filer_add_widgets(FilerWindow *filer_window);
105 static void filer_add_signals(FilerWindow *filer_window);
106 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item);
107 static void show_tooltip(guchar *text);
108 static void filer_size_for(FilerWindow *filer_window,
109 int w, int h, int n, gboolean allow_shrink);
111 static void set_selection_state(FilerWindow *collection, gboolean normal);
112 static void filer_next_thumb(GtkObject *window, gchar *path);
114 static void start_thumb_scanning(FilerWindow *filer_window);
116 static GdkCursor *busy_cursor = NULL;
117 static GdkCursor *crosshair = NULL;
119 /* Indicates whether the filer's display is different to the machine it
120 * is actually running on.
122 static gboolean not_local=FALSE;
124 static Option o_filer_size_limit;
125 Option o_filer_auto_resize, o_unique_filer_windows;
127 void filer_init(void)
129 gchar *ohost;
130 gchar *dpyhost, *dpy;
131 gchar *tmp;
133 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
134 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
135 RESIZE_ALWAYS);
136 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
138 busy_cursor = gdk_cursor_new(GDK_WATCH);
139 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
141 /* Is the display on the local machine, or are we being
142 * run remotely? See filer_set_title().
144 ohost = our_host_name();
145 dpy = gdk_get_display();
146 dpyhost = g_strdup(dpy);
147 tmp = strchr(dpyhost, ':');
148 if (tmp)
149 *tmp = '\0';
151 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
153 /* Try the cannonical name for dpyhost (see our_host_name()
154 * in support.c).
156 struct hostent *ent;
158 ent = gethostbyname(dpyhost);
159 if (!ent || strcmp(ohost, ent->h_name) != 0)
160 not_local = TRUE;
163 g_free(dpyhost);
166 static gboolean if_deleted(gpointer item, gpointer removed)
168 int i = ((GPtrArray *) removed)->len;
169 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
170 char *leafname = ((DirItem *) item)->leafname;
172 while (i--)
174 if (strcmp(leafname, r[i]->leafname) == 0)
175 return TRUE;
178 return FALSE;
181 static void update_item(FilerWindow *filer_window, DirItem *item)
183 int i;
184 char *leafname = item->leafname;
185 int old_w = filer_window->collection->item_width;
186 int old_h = filer_window->collection->item_height;
187 int w, h;
188 CollectionItem *colitem;
190 if (leafname[0] == '.' && filer_window->show_hidden == FALSE)
191 return;
193 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
195 if (i < 0)
197 g_warning("Failed to find '%s'\n", item->leafname);
198 return;
201 colitem = &filer_window->collection->items[i];
203 display_update_view(filer_window,
204 (DirItem *) colitem->data,
205 (ViewData *) colitem->view_data);
207 calc_size(filer_window, colitem, &w, &h);
208 if (w > old_w || h > old_h)
209 collection_set_item_size(filer_window->collection,
210 MAX(old_w, w),
211 MAX(old_h, h));
213 collection_draw_item(filer_window->collection, i, TRUE);
216 /* Resize the filer window to w x h pixels, plus border (not clamped) */
217 static void filer_window_set_size(FilerWindow *filer_window,
218 int w, int h,
219 gboolean allow_shrink)
221 g_return_if_fail(filer_window != NULL);
223 if (filer_window->scrollbar)
224 w += filer_window->scrollbar->allocation.width;
226 if (o_toolbar.int_value != TOOLBAR_NONE)
227 h += filer_window->toolbar_frame->allocation.height;
228 if (filer_window->message)
229 h += filer_window->message->allocation.height;
231 if (GTK_WIDGET_VISIBLE(filer_window->window))
233 gint x, y;
234 GtkRequisition *req = &filer_window->window->requisition;
236 w = MAX(req->width, w);
237 h = MAX(req->height, h);
238 gdk_window_get_position(filer_window->window->window,
239 &x, &y);
240 if (!allow_shrink)
242 gint old_w, old_h;
244 gdk_window_get_size(filer_window->window->window,
245 &old_w, &old_h);
246 w = MAX(w, old_w);
247 h = MAX(h, old_h);
249 if (w == old_w && h == old_h)
250 return;
253 if (x + w > screen_width || y + h > screen_height)
255 if (x + w > screen_width)
256 x = screen_width - w - 4;
257 if (y + h > screen_height)
258 y = screen_height - h - 4;
259 gdk_window_move_resize(filer_window->window->window,
260 x, y, w, h);
262 else
263 gdk_window_resize(filer_window->window->window, w, h);
265 else
266 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
267 w, h);
270 /* Resize the window to fit the items currently in the Directory.
271 * This should be used once the Directory has been fully scanned, otherwise
272 * the window will appear too small. When opening a directory for the first
273 * time, the names will be known but not the types and images. We can
274 * still make a good estimate of the size.
276 void filer_window_autosize(FilerWindow *filer_window, gboolean allow_shrink)
278 Collection *collection = filer_window->collection;
279 int n;
281 n = collection->number_of_items;
282 n = MAX(n, 2);
284 filer_size_for(filer_window,
285 collection->item_width,
286 collection->item_height,
287 n, allow_shrink);
290 /* Choose a good size for this window, assuming n items of size (w, h) */
291 static void filer_size_for(FilerWindow *filer_window,
292 int w, int h, int n, gboolean allow_shrink)
294 int x;
295 int rows, cols;
296 int max_x, max_rows;
297 const float r = 2.5;
298 int t = 0;
299 int size_limit;
300 int space = 0;
302 size_limit = o_filer_size_limit.int_value;
304 /* Get the extra height required for the toolbar and minibuffer,
305 * if visible.
307 if (o_toolbar.int_value != TOOLBAR_NONE)
308 t = filer_window->toolbar_frame->allocation.height;
309 if (filer_window->message)
310 t += filer_window->message->allocation.height;
311 if (GTK_WIDGET_VISIBLE(filer_window->minibuffer_area))
313 GtkRequisition req;
315 gtk_widget_size_request(filer_window->minibuffer_area, &req);
316 space = req.height + 2;
317 t += space;
320 max_x = (size_limit * screen_width) / 100;
321 max_rows = (size_limit * screen_height) / (h * 100);
323 /* Aim for a size where
324 * x = r(y + t + h), (1)
325 * unless that's too wide.
327 * t = toolbar (and minibuffer) height
328 * r = desired (width / height) ratio
330 * Want to display all items:
331 * (x/w)(y/h) = n
332 * => xy = nwh
333 * => x(x/r - t - h) = nwh (from 1)
334 * => xx - x.rt - hr(1 - nw) = 0
335 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
336 * Now,
337 * 4hr(nw - 1) > 0
338 * so
339 * sqrt(rt.rt + ...) > rt
341 * So, the +/- must be +:
343 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
345 * ( + w - 1 to round up)
347 x = (r * t + sqrt(r*r*t*t + 4*h*r * (n*w - 1))) / 2 + w - 1;
349 /* Limit x */
350 if (x > max_x)
351 x = max_x;
353 cols = x / w;
354 cols = MAX(cols, 1);
356 /* Choose rows to display all items given our chosen x.
357 * Don't make the window *too* big!
359 rows = (n + cols - 1) / cols;
360 if (rows > max_rows)
361 rows = max_rows;
363 /* Leave some room for extra icons, but only in Small Icons mode
364 * otherwise it takes up too much space.
365 * Also, don't add space if the minibuffer is open.
367 if (space == 0)
368 space = filer_window->display_style == SMALL_ICONS ? h : 2;
370 filer_window_set_size(filer_window,
371 w * MAX(cols, 1),
372 h * MAX(rows, 1) + space,
373 allow_shrink);
376 /* Called on a timeout while scanning or when scanning ends
377 * (whichever happens first).
379 static gint open_filer_window(FilerWindow *filer_window)
381 shrink_grid(filer_window);
383 if (filer_window->open_timeout)
385 gtk_timeout_remove(filer_window->open_timeout);
386 filer_window->open_timeout = 0;
389 if (!GTK_WIDGET_VISIBLE(filer_window->window))
391 filer_window_autosize(filer_window, TRUE);
392 gtk_widget_show(filer_window->window);
395 return FALSE;
398 static void update_display(Directory *dir,
399 DirAction action,
400 GPtrArray *items,
401 FilerWindow *filer_window)
403 int old_num;
404 int i;
405 Collection *collection = filer_window->collection;
407 switch (action)
409 case DIR_ADD:
410 old_num = collection->number_of_items;
411 for (i = 0; i < items->len; i++)
413 DirItem *item = (DirItem *) items->pdata[i];
415 add_item(filer_window, item);
418 if (old_num != collection->number_of_items)
419 collection_qsort(filer_window->collection,
420 filer_window->sort_fn);
422 /* Open and resize if currently hidden */
423 open_filer_window(filer_window);
424 break;
425 case DIR_REMOVE:
426 collection_delete_if(filer_window->collection,
427 if_deleted,
428 items);
429 break;
430 case DIR_START_SCAN:
431 set_scanning_display(filer_window, TRUE);
432 toolbar_update_info(filer_window);
433 break;
434 case DIR_END_SCAN:
435 if (filer_window->window->window)
436 gdk_window_set_cursor(
437 filer_window->window->window,
438 NULL);
439 set_scanning_display(filer_window, FALSE);
440 toolbar_update_info(filer_window);
441 open_filer_window(filer_window);
443 if (filer_window->had_cursor &&
444 collection->cursor_item == -1)
446 collection_set_cursor_item(collection, 0);
447 filer_window->had_cursor = FALSE;
449 if (filer_window->auto_select)
450 display_set_autoselect(filer_window,
451 filer_window->auto_select);
452 g_free(filer_window->auto_select);
453 filer_window->auto_select = NULL;
455 filer_create_thumbs(filer_window);
457 if (filer_window->thumb_queue)
458 start_thumb_scanning(filer_window);
459 break;
460 case DIR_UPDATE:
461 for (i = 0; i < items->len; i++)
463 DirItem *item = (DirItem *) items->pdata[i];
465 update_item(filer_window, item);
467 collection_qsort(filer_window->collection,
468 filer_window->sort_fn);
469 break;
473 static void attach(FilerWindow *filer_window)
475 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
476 collection_clear(filer_window->collection);
477 filer_window->scanning = TRUE;
478 dir_attach(filer_window->directory, (DirCallback) update_display,
479 filer_window);
480 filer_set_title(filer_window);
483 static void detach(FilerWindow *filer_window)
485 g_return_if_fail(filer_window->directory != NULL);
487 dir_detach(filer_window->directory,
488 (DirCallback) update_display, filer_window);
489 g_fscache_data_unref(dir_cache, filer_window->directory);
490 filer_window->directory = NULL;
493 static void filer_window_destroyed(GtkWidget *widget,
494 FilerWindow *filer_window)
496 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
498 gtk_object_set_data(GTK_OBJECT(widget), "filer_window", NULL);
500 if (window_with_primary == filer_window)
501 window_with_primary = NULL;
503 if (window_with_focus == filer_window)
505 if (popup_menu)
506 gtk_menu_popdown(GTK_MENU(popup_menu));
507 window_with_focus = NULL;
510 if (filer_window->directory)
511 detach(filer_window);
513 if (filer_window->open_timeout)
515 gtk_timeout_remove(filer_window->open_timeout);
516 filer_window->open_timeout = 0;
519 if (filer_window->thumb_queue)
521 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
522 g_list_free(filer_window->thumb_queue);
525 filer_tooltip_prime(NULL, NULL);
527 g_free(filer_window->auto_select);
528 g_free(filer_window->path);
529 g_free(filer_window);
531 if (--number_of_windows < 1)
532 gtk_main_quit();
535 /* Add a single object to a directory display */
536 static void add_item(FilerWindow *filer_window, DirItem *item)
538 char *leafname = item->leafname;
539 int old_w = filer_window->collection->item_width;
540 int old_h = filer_window->collection->item_height;
541 int w, h;
542 int i;
544 if (leafname[0] == '.')
546 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
547 || (leafname[1] == '.' && leafname[2] == '\0'))
548 return;
551 i = collection_insert(filer_window->collection,
552 item,
553 display_create_viewdata(filer_window, item));
555 calc_size(filer_window, &filer_window->collection->items[i], &w, &h);
557 if (w > old_w || h > old_h)
558 collection_set_item_size(filer_window->collection,
559 MAX(old_w, w),
560 MAX(old_h, h));
563 /* Returns TRUE iff the directory still exists. */
564 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
566 Directory *dir;
568 g_return_val_if_fail(filer_window != NULL, FALSE);
570 /* We do a fresh lookup (rather than update) because the inode may
571 * have changed.
573 dir = g_fscache_lookup(dir_cache, filer_window->path);
574 if (!dir)
576 if (warning)
577 delayed_error(_("Directory missing/deleted"));
578 gtk_widget_destroy(filer_window->window);
579 return FALSE;
581 if (dir == filer_window->directory)
582 g_fscache_data_unref(dir_cache, dir);
583 else
585 detach(filer_window);
586 filer_window->directory = dir;
587 attach(filer_window);
590 return TRUE;
593 /* The collection widget has lost the primary selection */
594 static gint collection_lose_selection(GtkWidget *widget,
595 GdkEventSelection *event)
597 if (window_with_primary &&
598 window_with_primary->collection == COLLECTION(widget))
600 FilerWindow *filer_window = window_with_primary;
601 window_with_primary = NULL;
602 set_selection_state(filer_window, FALSE);
605 return FALSE;
608 /* Someone wants us to send them the selection */
609 static void selection_get(GtkWidget *widget,
610 GtkSelectionData *selection_data,
611 guint info,
612 guint time,
613 gpointer data)
615 GString *reply, *header;
616 FilerWindow *filer_window;
617 int i;
618 Collection *collection;
620 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
622 reply = g_string_new(NULL);
623 header = g_string_new(NULL);
625 switch (info)
627 case TARGET_STRING:
628 g_string_sprintf(header, " %s",
629 make_path(filer_window->path, "")->str);
630 break;
631 case TARGET_URI_LIST:
632 g_string_sprintf(header, " file://%s%s",
633 our_host_name_for_dnd(),
634 make_path(filer_window->path, "")->str);
635 break;
638 collection = filer_window->collection;
639 for (i = 0; i < collection->number_of_items; i++)
641 if (collection->items[i].selected)
643 DirItem *item =
644 (DirItem *) collection->items[i].data;
646 g_string_append(reply, header->str);
647 g_string_append(reply, item->leafname);
650 /* This works, but I don't think I like it... */
651 /* g_string_append_c(reply, ' '); */
653 if (reply->len > 0)
654 gtk_selection_data_set(selection_data, xa_string,
655 8, reply->str + 1, reply->len - 1);
656 else
658 g_warning("Attempt to paste empty selection!");
659 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
662 g_string_free(reply, TRUE);
663 g_string_free(header, TRUE);
666 /* No items are now selected. This might be because another app claimed
667 * the selection or because the user unselected all the items.
669 static void lose_selection(Collection *collection,
670 guint time,
671 gpointer user_data)
673 FilerWindow *filer_window = (FilerWindow *) user_data;
675 if (window_with_primary == filer_window)
677 window_with_primary = NULL;
678 gtk_selection_owner_set(NULL,
679 GDK_SELECTION_PRIMARY,
680 time);
684 static void selection_changed(Collection *collection,
685 gint time,
686 gpointer user_data)
688 FilerWindow *filer_window = (FilerWindow *) user_data;
690 /* Selection has been changed -- try to grab the primary selection
691 * if we don't have it.
693 if (window_with_primary == filer_window)
694 return; /* Already got it */
696 if (!collection->number_selected)
697 return; /* Nothing selected */
699 if (filer_window->temp_item_selected == FALSE &&
700 gtk_selection_owner_set(GTK_WIDGET(collection),
701 GDK_SELECTION_PRIMARY,
702 time))
704 window_with_primary = filer_window;
705 set_selection_state(filer_window, TRUE);
707 else
708 set_selection_state(filer_window, FALSE);
711 /* Open the item (or add it to the shell command minibuffer) */
712 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
714 gboolean shift = (flags & OPEN_SHIFT) != 0;
715 gboolean close_mini = flags & OPEN_FROM_MINI;
716 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
717 GtkWidget *widget;
718 DirItem *item = (DirItem *)
719 filer_window->collection->items[item_number].data;
720 guchar *full_path;
721 gboolean wink = TRUE;
722 Directory *old_dir;
724 widget = filer_window->window;
725 if (filer_window->mini_type == MINI_SHELL)
727 minibuffer_add(filer_window, item->leafname);
728 return;
731 if (!item->image)
732 dir_update_item(filer_window->directory, item->leafname);
734 if (item->base_type == TYPE_DIRECTORY)
736 /* Never close a filer window when opening a directory
737 * (click on a dir or click on an app with shift).
739 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
740 close_window = FALSE;
743 full_path = make_path(filer_window->path, item->leafname)->str;
744 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
745 wink = FALSE;
747 old_dir = filer_window->directory;
748 if (run_diritem(full_path, item,
749 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
750 filer_window,
751 shift))
753 if (old_dir != filer_window->directory)
754 return;
756 if (close_window)
757 gtk_widget_destroy(filer_window->window);
758 else
760 if (wink)
761 collection_wink_item(filer_window->collection,
762 item_number);
763 if (close_mini)
764 minibuffer_hide(filer_window);
769 static gint pointer_in(GtkWidget *widget,
770 GdkEventCrossing *event,
771 FilerWindow *filer_window)
773 may_rescan(filer_window, TRUE);
774 return FALSE;
777 static gint pointer_out(GtkWidget *widget,
778 GdkEventCrossing *event,
779 FilerWindow *filer_window)
781 filer_tooltip_prime(NULL, NULL);
782 return FALSE;
785 /* Move the cursor to the next selected item in direction 'dir'
786 * (+1 or -1).
788 static void next_selected(FilerWindow *filer_window, int dir)
790 Collection *collection = filer_window->collection;
791 int to_check = collection->number_of_items;
792 int item = collection->cursor_item;
794 g_return_if_fail(dir == 1 || dir == -1);
796 if (to_check > 0 && item == -1)
798 /* Cursor not currently on */
799 if (dir == 1)
800 item = 0;
801 else
802 item = collection->number_of_items - 1;
804 if (collection->items[item].selected)
805 goto found;
808 while (--to_check > 0)
810 item += dir;
812 if (item >= collection->number_of_items)
813 item = 0;
814 else if (item < 0)
815 item = collection->number_of_items - 1;
817 if (collection->items[item].selected)
818 goto found;
821 gdk_beep();
822 return;
823 found:
824 collection_set_cursor_item(collection, item);
827 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
829 Collection *collection = filer_window->collection;
830 int item = collection->cursor_item;
831 TargetFunc cb = filer_window->target_cb;
832 gpointer data = filer_window->target_data;
833 OpenFlags flags = OPEN_SAME_WINDOW;
835 filer_target_mode(filer_window, NULL, NULL, NULL);
836 if (item < 0 || item >= collection->number_of_items)
837 return;
839 if (cb)
841 cb(filer_window, item, data);
842 return;
845 if (event->state & GDK_SHIFT_MASK)
846 flags |= OPEN_SHIFT;
848 filer_openitem(filer_window, item, flags);
851 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
852 * changed. If no groups were loaded and there is no file then initialised
853 * groups to an empty document.
854 * Return the node for the 'name' group.
856 static xmlNode *group_find(char *name)
858 xmlNode *node;
859 gchar *path;
861 /* Update the groups, if possible */
862 path = choices_find_path_load("Groups.xml", PROJECT);
863 if (path)
865 XMLwrapper *wrapper;
866 wrapper = xml_cache_load(path);
867 if (wrapper)
869 if (groups)
870 xml_cache_unref(groups);
871 groups = wrapper;
874 g_free(path);
877 if (!groups)
879 groups = g_new(XMLwrapper, 1);
880 groups->doc = xmlNewDoc("1.0");
881 groups->ref = 1;
882 xmlDocSetRootElement(groups->doc,
883 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
884 return NULL;
887 node = xmlDocGetRootElement(groups->doc);
889 for (node = node->xmlChildrenNode; node; node = node->next)
891 guchar *gid;
893 gid = xmlGetProp(node, "name");
895 if (!gid)
896 continue;
898 if (strcmp(name, gid) != 0)
899 continue;
901 g_free(gid);
903 return node;
906 return NULL;
909 static void group_save(FilerWindow *filer_window, char *name)
911 Collection *collection = filer_window->collection;
912 xmlNode *group;
913 guchar *save_path;
914 int i;
916 group = group_find(name);
917 if (group)
919 xmlUnlinkNode(group);
920 xmlFreeNode(group);
922 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
923 NULL, "group", NULL);
924 xmlSetProp(group, "name", name);
926 #ifdef GTK2
927 xmlNewChild(group, NULL, "directory", filer_window->path);
928 #else
930 gchar *u8_path;
931 u8_path = to_utf8(filer_window->path);
932 xmlNewChild(group, NULL, "directory", u8_path);
933 g_free(u8_path);
935 #endif
937 for (i = 0; i < collection->number_of_items; i++)
939 DirItem *item = (DirItem *) collection->items[i].data;
940 gchar *u8_leaf = item->leafname;
942 if (!collection->items[i].selected)
943 continue;
945 #ifdef GTK2
946 xmlNewChild(group, NULL, "item", u8_leaf);
947 #else
948 u8_leaf = to_utf8(u8_leaf);
949 xmlNewChild(group, NULL, "item", u8_leaf);
950 g_free(u8_leaf);
951 #endif
954 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
955 if (save_path)
957 save_xml_file(groups->doc, save_path);
958 g_free(save_path);
962 static void group_restore(FilerWindow *filer_window, char *name)
964 GHashTable *in_group;
965 Collection *collection = filer_window->collection;
966 int j, n;
967 char *path;
968 xmlNode *group, *node;
970 group = group_find(name);
972 if (!group)
974 report_error(_("Group %s is not set. Select some files "
975 "and press Ctrl+%s to set the group. Press %s "
976 "on its own to reselect the files later."),
977 name, name, name);
978 return;
981 node = get_subnode(group, NULL, "directory");
982 g_return_if_fail(node != NULL);
983 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
984 g_return_if_fail(path != NULL);
986 #ifndef GTK2
988 gchar *old = path;
989 path = from_utf8(old);
990 g_free(old);
992 #endif
994 if (strcmp(path, filer_window->path) != 0)
995 filer_change_to(filer_window, path, NULL);
996 g_free(path);
998 /* If an item at the start is selected then we could lose the
999 * primary selection after checking that item and then need to
1000 * gain it again at the end. Therefore, if anything is selected
1001 * then select the last item until the end of the search.
1003 n = collection->number_of_items;
1004 if (collection->number_selected)
1005 collection_select_item(collection, n - 1);
1007 in_group = g_hash_table_new(g_str_hash, g_str_equal);
1008 for (node = group->xmlChildrenNode; node; node = node->next)
1010 gchar *leaf;
1011 if (node->type != XML_ELEMENT_NODE)
1012 continue;
1013 if (strcmp(node->name, "item") != 0)
1014 continue;
1016 leaf = xmlNodeListGetString(groups->doc,
1017 node->xmlChildrenNode, 1);
1018 if (!leaf)
1019 g_warning("Missing leafname!\n");
1020 else
1022 #ifdef GTK2
1023 g_hash_table_insert(in_group, leaf, filer_window);
1024 #else
1025 g_hash_table_insert(in_group, from_utf8(leaf),
1026 filer_window);
1027 g_free(leaf);
1028 #endif
1032 for (j = 0; j < n; j++)
1034 DirItem *item = (DirItem *) collection->items[j].data;
1036 if (g_hash_table_lookup(in_group, item->leafname))
1037 collection_select_item(collection, j);
1038 else
1039 collection_unselect_item(collection, j);
1042 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1043 g_hash_table_destroy(in_group);
1046 /* Handle keys that can't be bound with the menu */
1047 static gint key_press_event(GtkWidget *widget,
1048 GdkEventKey *event,
1049 FilerWindow *filer_window)
1051 gboolean handled;
1052 guint key = event->keyval;
1053 char group[2] = "1";
1055 window_with_focus = filer_window;
1057 /* Note: Not convinced this is the way Gtk's key system is supposed
1058 * to be used...
1060 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1061 filer_keys);
1062 #ifdef GTK2
1063 handled = gtk_accel_groups_activate(G_OBJECT(filer_window->window),
1064 event->keyval, event->state);
1065 #else
1066 handled = gtk_accel_groups_activate(GTK_OBJECT(filer_window->window),
1067 event->keyval, event->state);
1068 #endif
1069 if (window_with_focus)
1070 gtk_window_remove_accel_group(GTK_WINDOW(filer_window->window),
1071 filer_keys);
1072 else
1073 return TRUE; /* Window no longer exists */
1074 if (handled)
1075 return TRUE;
1077 switch (key)
1079 case GDK_Escape:
1080 filer_target_mode(filer_window, NULL, NULL, NULL);
1081 return FALSE;
1082 case GDK_Return:
1083 return_pressed(filer_window, event);
1084 break;
1085 case GDK_ISO_Left_Tab:
1086 next_selected(filer_window, -1);
1087 break;
1088 case GDK_Tab:
1089 next_selected(filer_window, 1);
1090 break;
1091 case GDK_BackSpace:
1092 change_to_parent(filer_window);
1093 break;
1094 case GDK_backslash:
1095 filer_tooltip_prime(NULL, NULL);
1096 show_filer_menu(filer_window, (GdkEvent *) event,
1097 filer_window->collection->cursor_item);
1098 break;
1099 default:
1100 if (key >= GDK_0 && key <= GDK_9)
1101 group[0] = key - GDK_0 + '0';
1102 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1103 group[0] = key - GDK_KP_0 + '0';
1104 else
1105 return FALSE;
1108 if (event->state & GDK_CONTROL_MASK)
1109 group_save(filer_window, group);
1110 else
1111 group_restore(filer_window, group);
1114 #ifndef GTK2
1115 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
1116 #endif
1117 return TRUE;
1120 void change_to_parent(FilerWindow *filer_window)
1122 char *copy;
1123 char *slash;
1125 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
1126 return; /* Already in the root */
1128 copy = g_strdup(filer_window->path);
1129 slash = strrchr(copy, '/');
1131 if (slash)
1133 *slash = '\0';
1134 filer_change_to(filer_window,
1135 *copy ? copy : "/",
1136 slash + 1);
1138 else
1139 g_warning("No / in directory path!\n");
1141 g_free(copy);
1145 /* Make filer_window display path. When finished, highlight item 'from', or
1146 * the first item if from is NULL. If there is currently no cursor then
1147 * simply wink 'from' (if not NULL).
1149 void filer_change_to(FilerWindow *filer_window, char *path, char *from)
1151 char *from_dup;
1152 char *real_path;
1153 Directory *new_dir;
1155 g_return_if_fail(filer_window != NULL);
1157 filer_cancel_thumbnails(filer_window);
1159 filer_tooltip_prime(NULL, NULL);
1161 real_path = pathdup(path);
1162 new_dir = g_fscache_lookup(dir_cache, real_path);
1164 if (!new_dir)
1166 delayed_error(_("Directory '%s' is not accessible"),
1167 real_path);
1168 g_free(real_path);
1169 return;
1172 if (o_unique_filer_windows.int_value)
1174 FilerWindow *fw;
1176 fw = find_filer_window(real_path, filer_window);
1177 if (fw)
1178 gtk_widget_destroy(fw->window);
1181 from_dup = from && *from ? g_strdup(from) : NULL;
1183 detach(filer_window);
1184 g_free(filer_window->path);
1185 filer_window->path = real_path;
1187 filer_window->directory = new_dir;
1189 g_free(filer_window->auto_select);
1190 filer_window->had_cursor = filer_window->collection->cursor_item != -1
1191 || filer_window->had_cursor;
1192 filer_window->auto_select = from_dup;
1194 filer_set_title(filer_window);
1195 if (filer_window->window->window)
1196 gdk_window_set_role(filer_window->window->window,
1197 filer_window->path);
1198 collection_set_cursor_item(filer_window->collection, -1);
1200 attach(filer_window);
1201 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1202 filer_window_autosize(filer_window, TRUE);
1204 if (filer_window->mini_type == MINI_PATH)
1205 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1206 filer_window);
1209 void filer_open_parent(FilerWindow *filer_window)
1211 char *copy;
1212 char *slash;
1214 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
1215 return; /* Already in the root */
1217 copy = g_strdup(filer_window->path);
1218 slash = strrchr(copy, '/');
1220 if (slash)
1222 *slash = '\0';
1223 filer_opendir(*copy ? copy : "/", filer_window);
1225 else
1226 g_warning("No / in directory path!\n");
1228 g_free(copy);
1231 /* Returns a list containing the full pathname of every selected item.
1232 * You must g_free() each item in the list.
1234 GList *filer_selected_items(FilerWindow *filer_window)
1236 Collection *collection = filer_window->collection;
1237 GList *retval = NULL;
1238 guchar *dir = filer_window->path;
1239 int i;
1241 for (i = 0; i < collection->number_of_items; i++)
1243 if (collection->items[i].selected)
1245 DirItem *item = (DirItem *) collection->items[i].data;
1247 retval = g_list_prepend(retval,
1248 g_strdup(make_path(dir, item->leafname)->str));
1252 return g_list_reverse(retval);
1255 int selected_item_number(Collection *collection)
1257 int i;
1259 g_return_val_if_fail(collection != NULL, -1);
1260 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1261 g_return_val_if_fail(collection->number_selected == 1, -1);
1263 for (i = 0; i < collection->number_of_items; i++)
1264 if (collection->items[i].selected)
1265 return i;
1267 g_warning("selected_item: number_selected is wrong\n");
1269 return -1;
1272 DirItem *selected_item(Collection *collection)
1274 int item;
1276 item = selected_item_number(collection);
1278 if (item > -1)
1279 return (DirItem *) collection->items[item].data;
1280 return NULL;
1283 /* Append all the URIs in the selection to the string */
1284 static void create_uri_list(FilerWindow *filer_window, GString *string)
1286 Collection *collection = filer_window->collection;
1287 GString *leader;
1288 int i, num_selected;
1290 leader = g_string_new("file://");
1291 g_string_append(leader, our_host_name_for_dnd());
1292 g_string_append(leader, filer_window->path);
1293 if (leader->str[leader->len - 1] != '/')
1294 g_string_append_c(leader, '/');
1296 num_selected = collection->number_selected;
1298 for (i = 0; num_selected > 0; i++)
1300 if (collection->items[i].selected)
1302 DirItem *item = (DirItem *) collection->items[i].data;
1304 g_string_append(string, leader->str);
1305 g_string_append(string, item->leafname);
1306 g_string_append(string, "\r\n");
1307 num_selected--;
1311 g_string_free(leader, TRUE);
1314 /* Creates and shows a new filer window.
1315 * If src_win != NULL then display options can be taken from that source window.
1316 * Returns the new filer window, or NULL on error.
1317 * Note: if unique windows is in use, may return an existing window.
1319 FilerWindow *filer_opendir(char *path, FilerWindow *src_win)
1321 FilerWindow *filer_window;
1322 char *real_path;
1323 DisplayStyle dstyle;
1324 DetailsType dtype;
1325 FilerWindow *same_dir_window = NULL;
1327 /* Get the real pathname of the directory and copy it */
1328 real_path = pathdup(path);
1330 if (o_unique_filer_windows.int_value)
1331 same_dir_window = find_filer_window(real_path, NULL);
1333 #ifdef GTK2
1334 if (same_dir_window)
1336 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1337 return same_dir_window;
1339 #endif
1341 filer_window = g_new(FilerWindow, 1);
1342 filer_window->message = NULL;
1343 filer_window->minibuffer = NULL;
1344 filer_window->minibuffer_label = NULL;
1345 filer_window->minibuffer_area = NULL;
1346 filer_window->temp_show_hidden = FALSE;
1347 filer_window->path = real_path;
1348 filer_window->scanning = FALSE;
1349 filer_window->had_cursor = FALSE;
1350 filer_window->auto_select = NULL;
1351 filer_window->toolbar_text = NULL;
1352 filer_window->target_cb = NULL;
1353 filer_window->mini_type = MINI_NONE;
1354 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1356 /* Finds the entry for this directory in the dir cache, creating
1357 * a new one if needed. This does not cause a scan to start,
1358 * so if a new entry is created then it will be empty.
1360 filer_window->directory = g_fscache_lookup(dir_cache,
1361 filer_window->path);
1362 if (!filer_window->directory)
1364 delayed_error(_("Directory '%s' not found."), path);
1365 g_free(filer_window->path);
1366 g_free(filer_window);
1367 return NULL;
1370 filer_window->temp_item_selected = FALSE;
1371 filer_window->flags = (FilerFlags) 0;
1372 filer_window->details_type = DETAILS_SUMMARY;
1373 filer_window->display_style = UNKNOWN_STYLE;
1374 filer_window->thumb_queue = NULL;
1375 filer_window->max_thumbs = 0;
1377 if (src_win && o_display_inherit_options.int_value)
1379 filer_window->sort_fn = src_win->sort_fn;
1380 dstyle = src_win->display_style;
1381 dtype = src_win->details_type;
1382 filer_window->show_hidden = src_win->show_hidden;
1383 filer_window->show_thumbs = src_win->show_thumbs;
1385 else
1387 int i = o_display_sort_by.int_value;
1388 filer_window->sort_fn = i == 0 ? sort_by_name :
1389 i == 1 ? sort_by_type :
1390 i == 2 ? sort_by_date :
1391 sort_by_size;
1393 dstyle = o_display_size.int_value;
1394 dtype = o_display_details.int_value;
1395 filer_window->show_hidden =
1396 o_display_show_hidden.int_value;
1397 filer_window->show_thumbs =
1398 o_display_show_thumbs.int_value;
1401 /* Add all the user-interface elements & realise */
1402 filer_add_widgets(filer_window);
1403 if (src_win)
1404 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1405 GTK_WIN_POS_MOUSE);
1407 /* Connect to all the signal handlers */
1408 filer_add_signals(filer_window);
1410 display_set_layout(filer_window, dstyle, dtype);
1412 /* Open the window after a timeout, or when scanning stops.
1413 * Do this before attaching, because attach() might tell us to
1414 * stop scanning (if a scan isn't needed).
1416 filer_window->open_timeout = gtk_timeout_add(500,
1417 (GtkFunction) open_filer_window,
1418 filer_window);
1420 /* The collection is created empty and then attach() is called, which
1421 * links the filer window to the entry in the directory cache we
1422 * looked up / created above.
1424 * The attach() function will immediately callback to the filer window
1425 * to deliver a list of all known entries in the directory (so,
1426 * collection->number_of_items may be valid after the call to
1427 * attach() returns).
1429 * BUT, if the directory was not in the cache (because it hadn't been
1430 * opened it before) then the cached dir will be empty and nothing gets
1431 * added until a while later when some entries are actually available.
1434 attach(filer_window);
1436 /* Update number_of_windows BEFORE destroying the old window! */
1437 number_of_windows++;
1439 /* If the user doesn't want duplicate windows then check
1440 * for an existing one and close it if found.
1443 #ifndef GTK2
1444 if (same_dir_window)
1445 gtk_widget_destroy(same_dir_window->window);
1446 #endif
1448 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1450 return filer_window;
1453 /* This adds all the widgets to a new filer window. It is in a separate
1454 * function because filer_opendir() was getting too long...
1456 static void filer_add_widgets(FilerWindow *filer_window)
1458 GtkWidget *hbox, *vbox, *collection;
1460 /* Create the top-level window widget */
1461 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1462 filer_set_title(filer_window);
1464 /* The collection is the area that actually displays the files */
1465 collection = collection_new();
1467 /* This property is cleared when the window is destroyed.
1468 * You can thus ref filer_window->window and use this to see
1469 * if the window no longer exists.
1471 gtk_object_set_data(GTK_OBJECT(filer_window->window),
1472 "filer_window", filer_window);
1474 gtk_object_set_data(GTK_OBJECT(collection),
1475 "filer_window", filer_window);
1476 filer_window->collection = COLLECTION(collection);
1478 filer_window->collection->free_item = display_free_colitem;
1480 /* Scrollbar on the right, everything else on the left */
1481 hbox = gtk_hbox_new(FALSE, 0);
1482 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1484 vbox = gtk_vbox_new(FALSE, 0);
1485 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1487 /* Create a frame for the toolbar, but don't show it unless we actually
1488 * have a toolbar.
1489 * (allows us to change the toolbar later)
1491 filer_window->toolbar_frame = gtk_frame_new(NULL);
1492 gtk_frame_set_shadow_type(GTK_FRAME(filer_window->toolbar_frame),
1493 GTK_SHADOW_OUT);
1494 gtk_box_pack_start(GTK_BOX(vbox),
1495 filer_window->toolbar_frame, FALSE, TRUE, 0);
1497 /* If we want a toolbar, create it and put it in the frame */
1498 if (o_toolbar.int_value != TOOLBAR_NONE)
1500 GtkWidget *toolbar;
1502 toolbar = toolbar_new(filer_window);
1503 gtk_container_add(GTK_CONTAINER(filer_window->toolbar_frame),
1504 toolbar);
1505 gtk_widget_show_all(filer_window->toolbar_frame);
1508 /* If there's a message that should be displayed in each window (eg
1509 * 'Running as root'), add it here.
1511 if (show_user_message)
1513 filer_window->message = gtk_label_new(show_user_message);
1514 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1515 FALSE, TRUE, 0);
1516 gtk_widget_show(filer_window->message);
1519 /* Now add the area for displaying the files.
1520 * If we've got Gtk+-2.0 then the collection is one huge window
1521 * that goes in a Viewport. Otherwise, the collection handles
1522 * scrolling itself.
1524 #ifdef GTK2
1526 GtkWidget *viewport;
1527 GtkAdjustment *adj;
1529 adj = filer_window->collection->vadj;
1530 viewport = gtk_viewport_new(NULL, adj);
1531 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport),
1532 GTK_SHADOW_NONE);
1533 gtk_container_add(GTK_CONTAINER(viewport), collection);
1534 gtk_widget_show_all(viewport);
1535 gtk_box_pack_start(GTK_BOX(vbox), viewport, TRUE, TRUE, 0);
1536 filer_window->scrollbar = gtk_vscrollbar_new(adj);
1537 gtk_widget_set_usize(viewport, 4, 4);
1539 gtk_container_set_resize_mode(GTK_CONTAINER(viewport),
1540 GTK_RESIZE_IMMEDIATE);
1542 #else
1543 gtk_box_pack_start(GTK_BOX(vbox), collection, TRUE, TRUE, 0);
1544 filer_window->scrollbar =
1545 gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1546 #endif
1548 /* And the minibuffer (hidden to start with) */
1549 create_minibuffer(filer_window);
1550 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1551 FALSE, TRUE, 0);
1553 /* And the thumbnail progress bar (also hidden) */
1555 GtkWidget *cancel;
1557 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1558 gtk_box_pack_start(GTK_BOX(vbox), filer_window->thumb_bar,
1559 FALSE, TRUE, 0);
1561 filer_window->thumb_progress = gtk_progress_bar_new();
1562 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1563 filer_window->thumb_progress, TRUE, TRUE, 0);
1565 cancel = gtk_button_new_with_label(_("Cancel"));
1566 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1567 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1568 cancel, FALSE, TRUE, 0);
1569 gtk_signal_connect_object(GTK_OBJECT(cancel), "clicked",
1570 GTK_SIGNAL_FUNC(filer_cancel_thumbnails),
1571 (GtkObject *) filer_window);
1574 /* Put the scrollbar on the left of everything else... */
1575 gtk_box_pack_start(GTK_BOX(hbox),
1576 filer_window->scrollbar, FALSE, TRUE, 0);
1578 gtk_window_set_focus(GTK_WINDOW(filer_window->window), collection);
1580 gtk_widget_show(hbox);
1581 gtk_widget_show(vbox);
1582 gtk_widget_show(filer_window->scrollbar);
1583 gtk_widget_show(collection);
1585 gtk_widget_realize(filer_window->window);
1587 gdk_window_set_role(filer_window->window->window, filer_window->path);
1589 filer_window_set_size(filer_window, 4, 4, TRUE);
1592 static void filer_add_signals(FilerWindow *filer_window)
1594 GtkObject *collection = GTK_OBJECT(filer_window->collection);
1595 GtkTargetEntry target_table[] =
1597 {"text/uri-list", 0, TARGET_URI_LIST},
1598 {"STRING", 0, TARGET_STRING},
1599 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1602 /* Events on the top-level window */
1603 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1604 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1605 "enter-notify-event",
1606 GTK_SIGNAL_FUNC(pointer_in), filer_window);
1607 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1608 "leave-notify-event",
1609 GTK_SIGNAL_FUNC(pointer_out), filer_window);
1610 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1611 GTK_SIGNAL_FUNC(filer_window_destroyed), filer_window);
1613 /* Events on the collection widget */
1614 gtk_widget_set_events(GTK_WIDGET(collection),
1615 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
1616 GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_MASK |
1617 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1619 gtk_signal_connect(collection, "lose_selection",
1620 GTK_SIGNAL_FUNC(lose_selection), filer_window);
1621 gtk_signal_connect(collection, "selection_changed",
1622 GTK_SIGNAL_FUNC(selection_changed), filer_window);
1623 gtk_signal_connect(collection, "selection_clear_event",
1624 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1625 gtk_signal_connect(collection, "selection_get",
1626 GTK_SIGNAL_FUNC(selection_get), NULL);
1627 gtk_selection_add_targets(GTK_WIDGET(collection), GDK_SELECTION_PRIMARY,
1628 target_table,
1629 sizeof(target_table) / sizeof(*target_table));
1631 gtk_signal_connect(collection, "key_press_event",
1632 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1633 gtk_signal_connect(collection, "button-release-event",
1634 GTK_SIGNAL_FUNC(coll_button_release), filer_window);
1635 gtk_signal_connect(collection, "button-press-event",
1636 GTK_SIGNAL_FUNC(coll_button_press), filer_window);
1637 gtk_signal_connect(collection, "motion-notify-event",
1638 GTK_SIGNAL_FUNC(coll_motion_notify), filer_window);
1640 /* Drag and drop events */
1641 gtk_signal_connect(collection, "drag_data_get",
1642 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1643 drag_set_dest(filer_window);
1646 static gint clear_scanning_display(FilerWindow *filer_window)
1648 if (filer_exists(filer_window))
1649 filer_set_title(filer_window);
1650 return FALSE;
1653 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1655 if (scanning == filer_window->scanning)
1656 return;
1657 filer_window->scanning = scanning;
1659 if (scanning)
1660 filer_set_title(filer_window);
1661 else
1662 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1663 filer_window);
1666 /* Note that filer_window may not exist after this call. */
1667 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1669 if (may_rescan(filer_window, warning))
1670 dir_update(filer_window->directory, filer_window->path);
1673 void filer_update_all(void)
1675 GList *next = all_filer_windows;
1677 while (next)
1679 FilerWindow *filer_window = (FilerWindow *) next->data;
1681 next = next->next;
1683 filer_update_dir(filer_window, TRUE);
1687 /* Refresh the various caches even if we don't think we need to */
1688 void full_refresh(void)
1690 mount_update(TRUE);
1693 /* See whether a filer window with a given path already exists
1694 * and is different from diff.
1696 static FilerWindow *find_filer_window(char *path, FilerWindow *diff)
1698 GList *next = all_filer_windows;
1700 while (next)
1702 FilerWindow *filer_window = (FilerWindow *) next->data;
1704 if (filer_window != diff &&
1705 strcmp(path, filer_window->path) == 0)
1707 return filer_window;
1710 next = next->next;
1713 return NULL;
1716 /* This path has been mounted/umounted/deleted some files - update all dirs */
1717 void filer_check_mounted(char *path)
1719 GList *next = all_filer_windows;
1720 char *slash;
1721 int len;
1723 len = strlen(path);
1725 while (next)
1727 FilerWindow *filer_window = (FilerWindow *) next->data;
1729 next = next->next;
1731 if (strncmp(path, filer_window->path, len) == 0)
1733 char s = filer_window->path[len];
1735 if (s == '/' || s == '\0')
1736 filer_update_dir(filer_window, FALSE);
1740 slash = strrchr(path, '/');
1741 if (slash && slash != path)
1743 guchar *parent;
1745 parent = g_strndup(path, slash - path);
1747 refresh_dirs(parent);
1749 g_free(parent);
1751 else
1752 refresh_dirs("/");
1754 icons_may_update(path);
1757 /* Close all windows displaying 'path' or subdirectories of 'path' */
1758 void filer_close_recursive(char *path)
1760 GList *next = all_filer_windows;
1761 int len;
1763 len = strlen(path);
1765 while (next)
1767 FilerWindow *filer_window = (FilerWindow *) next->data;
1769 next = next->next;
1771 if (strncmp(path, filer_window->path, len) == 0)
1773 char s = filer_window->path[len];
1775 if (len == 1 || s == '/' || s == '\0')
1776 gtk_widget_destroy(filer_window->window);
1781 /* Like minibuffer_show(), except that:
1782 * - It returns FALSE (to be used from an idle callback)
1783 * - It checks that the filer window still exists.
1785 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1787 if (filer_exists(filer_window))
1788 minibuffer_show(filer_window, MINI_PATH);
1789 return FALSE;
1792 /* TRUE iff filer_window points to an existing FilerWindow
1793 * structure.
1795 gboolean filer_exists(FilerWindow *filer_window)
1797 GList *next;
1799 for (next = all_filer_windows; next; next = next->next)
1801 FilerWindow *fw = (FilerWindow *) next->data;
1803 if (fw == filer_window)
1804 return TRUE;
1807 return FALSE;
1810 /* Make sure the window title is up-to-date */
1811 void filer_set_title(FilerWindow *filer_window)
1813 guchar *title = NULL;
1814 guchar *flags = "";
1816 if (filer_window->scanning || filer_window->show_hidden ||
1817 filer_window->show_thumbs)
1819 flags = g_strconcat(" (",
1820 filer_window->scanning ? _("Scanning, ") : "",
1821 filer_window->show_hidden ? _("All, ") : "",
1822 filer_window->show_thumbs ? _("Thumbs, ") : "",
1823 NULL);
1824 flags[strlen(flags) - 2] = ')';
1827 if (not_local)
1829 title = g_strconcat("//", our_host_name(),
1830 filer_window->path, flags, NULL);
1833 if (!title && home_dir_len > 1 &&
1834 strncmp(filer_window->path, home_dir, home_dir_len) == 0)
1836 guchar sep = filer_window->path[home_dir_len];
1838 if (sep == '\0' || sep == '/')
1839 title = g_strconcat("~",
1840 filer_window->path + home_dir_len,
1841 flags,
1842 NULL);
1845 if (!title)
1846 title = g_strconcat(filer_window->path, flags, NULL);
1848 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1849 g_free(title);
1851 if (flags[0] != '\0')
1852 g_free(flags);
1855 /* Reconnect to the same directory (used when the Show Hidden option is
1856 * toggled).
1858 void filer_detach_rescan(FilerWindow *filer_window)
1860 Directory *dir = filer_window->directory;
1862 g_fscache_data_ref(dir_cache, dir);
1863 detach(filer_window);
1864 filer_window->directory = dir;
1865 attach(filer_window);
1868 static gint coll_button_release(GtkWidget *widget,
1869 GdkEventButton *event,
1870 FilerWindow *filer_window)
1872 if (dnd_motion_release(event))
1874 if (motion_buttons_pressed == 0 &&
1875 filer_window->collection->lasso_box)
1877 collection_end_lasso(filer_window->collection,
1878 event->button == 1 ? GDK_SET : GDK_INVERT);
1880 return FALSE;
1883 perform_action(filer_window, event);
1885 return FALSE;
1888 static void perform_action(FilerWindow *filer_window, GdkEventButton *event)
1890 Collection *collection = filer_window->collection;
1891 DirItem *dir_item;
1892 int item;
1893 BindAction action;
1894 gboolean press = event->type == GDK_BUTTON_PRESS;
1895 gboolean selected = FALSE;
1896 OpenFlags flags = 0;
1898 if (event->button > 3)
1899 return;
1901 item = collection_get_item(collection, event->x, event->y);
1903 if (item != -1 && event->button == 1 &&
1904 collection->items[item].selected &&
1905 filer_window->selection_state == GTK_STATE_INSENSITIVE)
1907 selection_changed(filer_window->collection,
1908 event->time, filer_window);
1909 return;
1912 if (filer_window->target_cb)
1914 dnd_motion_ungrab();
1915 if (item != -1 && press && event->button == 1)
1916 filer_window->target_cb(filer_window, item,
1917 filer_window->target_data);
1918 filer_target_mode(filer_window, NULL, NULL, NULL);
1920 return;
1923 action = bind_lookup_bev(
1924 item == -1 ? BIND_DIRECTORY : BIND_DIRECTORY_ICON,
1925 event);
1927 if (item != -1)
1929 dir_item = (DirItem *) collection->items[item].data;
1930 selected = collection->items[item].selected;
1932 else
1933 dir_item = NULL;
1935 switch (action)
1937 case ACT_CLEAR_SELECTION:
1938 collection_clear_selection(collection);
1939 break;
1940 case ACT_TOGGLE_SELECTED:
1941 collection_toggle_item(collection, item);
1942 break;
1943 case ACT_SELECT_EXCL:
1944 collection_clear_except(collection, item);
1945 break;
1946 case ACT_EDIT_ITEM:
1947 flags |= OPEN_SHIFT;
1948 /* (no break) */
1949 case ACT_OPEN_ITEM:
1950 if (event->button != 1)
1951 flags |= OPEN_CLOSE_WINDOW;
1952 else
1953 flags |= OPEN_SAME_WINDOW;
1954 if (o_new_button_1.int_value)
1955 flags ^= OPEN_SAME_WINDOW;
1956 if (event->type == GDK_2BUTTON_PRESS)
1957 collection_unselect_item(collection, item);
1958 dnd_motion_ungrab();
1959 filer_openitem(filer_window, item, flags);
1960 break;
1961 case ACT_POPUP_MENU:
1962 dnd_motion_ungrab();
1963 filer_tooltip_prime(NULL, NULL);
1964 show_filer_menu(filer_window, (GdkEvent *) event, item);
1965 break;
1966 case ACT_PRIME_AND_SELECT:
1967 if (!selected)
1968 collection_clear_except(collection, item);
1969 dnd_motion_start(MOTION_READY_FOR_DND);
1970 break;
1971 case ACT_PRIME_AND_TOGGLE:
1972 collection_toggle_item(collection, item);
1973 dnd_motion_start(MOTION_READY_FOR_DND);
1974 break;
1975 case ACT_PRIME_FOR_DND:
1976 dnd_motion_start(MOTION_READY_FOR_DND);
1977 break;
1978 case ACT_IGNORE:
1979 if (press && event->button < 4)
1981 if (item)
1982 collection_wink_item(collection, item);
1983 dnd_motion_start(MOTION_NONE);
1985 break;
1986 case ACT_LASSO_CLEAR:
1987 collection_clear_selection(collection);
1988 /* (no break) */
1989 case ACT_LASSO_MODIFY:
1990 collection_lasso_box(collection, event->x, event->y);
1991 break;
1992 case ACT_RESIZE:
1993 filer_window_autosize(filer_window, TRUE);
1994 break;
1995 default:
1996 g_warning("Unsupported action : %d\n", action);
1997 break;
2001 static gint coll_button_press(GtkWidget *widget,
2002 GdkEventButton *event,
2003 FilerWindow *filer_window)
2005 collection_set_cursor_item(filer_window->collection, -1);
2007 if (dnd_motion_press(widget, event))
2008 perform_action(filer_window, event);
2010 return FALSE;
2013 static gint coll_motion_notify(GtkWidget *widget,
2014 GdkEventMotion *event,
2015 FilerWindow *filer_window)
2017 Collection *collection = filer_window->collection;
2018 int i;
2020 i = collection_get_item(collection, event->x, event->y);
2022 if (i == -1)
2023 filer_tooltip_prime(NULL, NULL);
2024 else
2025 filer_tooltip_prime(filer_window,
2026 (DirItem *) collection->items[i].data);
2028 if (motion_state != MOTION_READY_FOR_DND)
2029 return FALSE;
2031 if (!dnd_motion_moved(event))
2032 return FALSE;
2034 i = collection_get_item(collection,
2035 event->x - (event->x_root - drag_start_x),
2036 event->y - (event->y_root - drag_start_y));
2037 if (i == -1)
2038 return FALSE;
2040 collection_wink_item(collection, -1);
2042 if (!collection->items[i].selected)
2044 if (event->state & GDK_BUTTON1_MASK)
2046 /* Select just this one */
2047 filer_window->temp_item_selected = TRUE;
2048 collection_clear_except(collection, i);
2050 else
2052 if (collection->number_selected == 0)
2053 filer_window->temp_item_selected = TRUE;
2054 collection_select_item(collection, i);
2058 g_return_val_if_fail(collection->number_selected > 0, TRUE);
2060 if (collection->number_selected == 1)
2062 DirItem *item = (DirItem *) collection->items[i].data;
2063 ViewData *view = (ViewData *) collection->items[i].view_data;
2065 if (!item->image)
2066 item = dir_update_item(filer_window->directory,
2067 item->leafname);
2069 if (!item)
2071 report_error(_("Item no longer exists!"));
2072 return FALSE;
2075 drag_one_item(widget, event,
2076 make_path(filer_window->path, item->leafname)->str,
2077 item, view ? view->image : NULL);
2079 else
2081 GString *uris;
2083 uris = g_string_new(NULL);
2084 create_uri_list(filer_window, uris);
2085 drag_selection(widget, event, uris->str);
2086 g_string_free(uris, TRUE);
2089 return FALSE;
2092 /* Puts the filer window into target mode. When an item is chosen,
2093 * fn(filer_window, item, data) is called. 'reason' will be displayed
2094 * on the toolbar while target mode is active.
2096 * Use fn == NULL to cancel target mode.
2098 void filer_target_mode(FilerWindow *filer_window,
2099 TargetFunc fn,
2100 gpointer data,
2101 char *reason)
2103 TargetFunc old_fn = filer_window->target_cb;
2105 if (fn != old_fn)
2106 gdk_window_set_cursor(
2107 GTK_WIDGET(filer_window->collection)->window,
2108 fn ? crosshair : NULL);
2110 filer_window->target_cb = fn;
2111 filer_window->target_data = data;
2113 if (filer_window->toolbar_text == NULL)
2114 return;
2116 if (fn)
2117 gtk_label_set_text(
2118 GTK_LABEL(filer_window->toolbar_text), reason);
2119 else if (o_toolbar_info.int_value)
2121 if (old_fn)
2122 toolbar_update_info(filer_window);
2124 else
2125 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2129 /* Draw the black border */
2130 static gint filer_tooltip_draw(GtkWidget *w)
2132 gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
2133 w->allocation.width - 1, w->allocation.height - 1);
2135 return FALSE;
2138 /* When the tips window closed, record the time. If we try to open another
2139 * tip soon, it will appear more quickly.
2141 static void tip_destroyed(gpointer data)
2143 time(&tip_time);
2146 /* It's time to make the tooltip appear. If we're not over the item any
2147 * more, or the item doesn't need a tooltip, do nothing.
2149 static gboolean filer_tooltip_activate(FilerWindow *filer_window)
2151 Collection *collection;
2152 gint x, y;
2153 int i;
2154 GString *tip = NULL;
2155 guchar *fullpath = NULL;
2157 g_return_val_if_fail(tip_item != NULL, 0);
2159 tip_timeout = 0;
2161 show_tooltip(NULL);
2163 if (!filer_exists(filer_window))
2164 return FALSE;
2166 collection = filer_window->collection;
2167 gdk_window_get_pointer(GTK_WIDGET(collection)->window, &x, &y, NULL);
2168 i = collection_get_item(filer_window->collection, x, y);
2169 if (i == -1 || ((DirItem *) collection->items[i].data) != tip_item)
2170 return FALSE; /* Not still under the pointer */
2172 /* OK, the filer window still exists and the pointer is still
2173 * over the same item. Do we need to show a tip?
2176 tip = g_string_new(NULL);
2178 if (display_is_truncated(filer_window, i))
2180 g_string_append(tip, tip_item->leafname);
2181 g_string_append_c(tip, '\n');
2184 fullpath = make_path(filer_window->path, tip_item->leafname)->str;
2186 if (tip_item->flags & ITEM_FLAG_SYMLINK)
2188 char *target;
2190 target = readlink_dup(fullpath);
2191 if (target)
2193 g_string_append(tip, _("Symbolic link to "));
2194 g_string_append(tip, target);
2195 g_string_append_c(tip, '\n');
2196 g_free(target);
2200 if (tip_item->flags & ITEM_FLAG_APPDIR)
2202 XMLwrapper *info;
2203 xmlNode *node;
2205 info = appinfo_get(fullpath, tip_item);
2206 if (info && ((node = appinfo_get_section(info, "Summary"))))
2208 guchar *str;
2209 str = xmlNodeListGetString(node->doc,
2210 node->xmlChildrenNode, 1);
2211 if (str)
2213 g_string_append(tip, str);
2214 g_string_append_c(tip, '\n');
2215 g_free(str);
2220 if (tip->len > 1)
2222 g_string_truncate(tip, tip->len - 1);
2223 show_tooltip(tip->str);
2226 g_string_free(tip, TRUE);
2228 return FALSE;
2231 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
2232 * NULL, close any current tooltip.
2234 static void show_tooltip(guchar *text)
2236 GtkWidget *label;
2237 int x, y, py;
2238 int w, h;
2240 if (tip_widget)
2242 gtk_widget_destroy(tip_widget);
2243 tip_widget = NULL;
2246 if (!text)
2247 return;
2249 /* Show the tip */
2250 tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
2251 gtk_widget_set_app_paintable(tip_widget, TRUE);
2252 gtk_widget_set_name(tip_widget, "gtk-tooltips");
2254 gtk_signal_connect_object(GTK_OBJECT(tip_widget), "expose_event",
2255 GTK_SIGNAL_FUNC(filer_tooltip_draw),
2256 (GtkObject *) tip_widget);
2257 #ifndef GTK2
2258 gtk_signal_connect_object(GTK_OBJECT(tip_widget), "draw",
2259 GTK_SIGNAL_FUNC(filer_tooltip_draw),
2260 (GtkObject *) tip_widget);
2261 #endif
2263 label = gtk_label_new(text);
2264 gtk_misc_set_padding(GTK_MISC(label), 4, 2);
2265 gtk_container_add(GTK_CONTAINER(tip_widget), label);
2266 gtk_widget_show(label);
2267 gtk_widget_realize(tip_widget);
2269 w = tip_widget->allocation.width;
2270 h = tip_widget->allocation.height;
2271 gdk_window_get_pointer(NULL, &x, &py, NULL);
2273 x -= w / 2;
2274 y = py + 12; /* I don't know the pointer height so I use a constant */
2276 /* Now check for screen boundaries */
2277 x = CLAMP(x, 0, screen_width - w);
2278 y = CLAMP(y, 0, screen_height - h);
2280 /* And again test if pointer is over the tooltip window */
2281 if (py >= y && py <= y + h)
2282 y = py - h- 2;
2283 gtk_widget_set_uposition(tip_widget, x, y);
2284 gtk_widget_show(tip_widget);
2286 gtk_signal_connect_object(GTK_OBJECT(tip_widget), "destroy",
2287 GTK_SIGNAL_FUNC(tip_destroyed), NULL);
2288 time(&tip_time);
2291 /* Display a tooltip for 'item' after a while (if item is not NULL).
2292 * Cancel any previous tooltip.
2294 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item)
2296 time_t now;
2298 time(&now);
2300 if (item == tip_item)
2301 return;
2303 if (tip_timeout)
2305 gtk_timeout_remove(tip_timeout);
2306 tip_timeout = 0;
2307 tip_item = NULL;
2309 if (tip_widget)
2311 gtk_widget_destroy(tip_widget);
2312 tip_widget = NULL;
2315 tip_item = item;
2316 if (filer_window && item)
2318 int delay = now - tip_time > 2 ? 1000 : 200;
2320 tip_timeout = gtk_timeout_add(delay,
2321 (GtkFunction) filer_tooltip_activate,
2322 filer_window);
2326 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2328 GtkStateType old_state;
2330 old_state = filer_window->selection_state;
2331 filer_window->selection_state = normal
2332 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2334 if (old_state != filer_window->selection_state
2335 && filer_window->collection->number_selected)
2336 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
2339 void filer_cancel_thumbnails(FilerWindow *filer_window)
2341 gtk_widget_hide(filer_window->thumb_bar);
2343 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
2344 g_list_free(filer_window->thumb_queue);
2345 filer_window->thumb_queue = NULL;
2346 filer_window->max_thumbs = 0;
2349 /* Generate the next thumb for this window. The collection object is
2350 * unref'd when there is nothing more to do.
2351 * If the collection no longer has a filer window, nothing is done.
2353 static gboolean filer_next_thumb_real(GtkObject *window)
2355 FilerWindow *filer_window;
2356 gchar *path;
2357 int done, total;
2359 filer_window = gtk_object_get_data(window, "filer_window");
2361 if (!filer_window)
2363 gtk_object_unref(window);
2364 return FALSE;
2367 if (!filer_window->thumb_queue)
2369 filer_cancel_thumbnails(filer_window);
2370 gtk_object_unref(window);
2371 return FALSE;
2374 total = filer_window->max_thumbs;
2375 done = total - g_list_length(filer_window->thumb_queue);
2377 path = (gchar *) filer_window->thumb_queue->data;
2379 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2381 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2382 path);
2383 g_free(path);
2385 gtk_progress_set_percentage(GTK_PROGRESS(filer_window->thumb_progress),
2386 done / (float) total);
2388 return FALSE;
2391 /* path is the thumb just loaded, if any.
2392 * collection is unref'd (eventually).
2394 static void filer_next_thumb(GtkObject *window, gchar *path)
2396 if (path)
2397 dir_force_update_path(path);
2399 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
2402 static void start_thumb_scanning(FilerWindow *filer_window)
2404 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2405 return; /* Already scanning */
2407 gtk_widget_show_all(filer_window->thumb_bar);
2409 gtk_object_ref(GTK_OBJECT(filer_window->window));
2410 filer_next_thumb(GTK_OBJECT(filer_window->window), NULL);
2413 /* Set this image to be loaded some time in the future */
2414 void filer_create_thumb(FilerWindow *filer_window, gchar *path)
2416 filer_window->max_thumbs++;
2418 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2419 g_strdup(path));
2421 if (filer_window->scanning)
2422 return; /* Will start when scan ends */
2424 start_thumb_scanning(filer_window);
2427 /* If thumbnail display is on, look through all the items in this directory
2428 * and start creating or updating the thumbnails as needed.
2430 void filer_create_thumbs(FilerWindow *filer_window)
2432 Collection *collection = filer_window->collection;
2433 int i;
2435 if (!filer_window->show_thumbs)
2436 return;
2438 for (i = 0; i < collection->number_of_items; i++)
2440 DirItem *item = (DirItem *) collection->items[i].data;
2441 gchar *path;
2442 gboolean found;
2444 if (item->base_type != TYPE_FILE)
2445 continue;
2447 if (strcmp(item->mime_type->media_type, "image") != 0)
2448 continue;
2450 path = make_path(filer_window->path, item->leafname)->str;
2452 g_fscache_lookup_full(pixmap_cache, path,
2453 FSCACHE_LOOKUP_ONLY_NEW, &found);
2455 /* If we didn't get an image, it could be because:
2457 * - We're loading the image now. found is TRUE,
2458 * and we'll update the item later.
2459 * - We tried to load the image and failed. found
2460 * is TRUE.
2461 * - We haven't tried loading the image. found is
2462 * FALSE, and we start creating the thumb here.
2464 if (!found)
2465 filer_create_thumb(filer_window, path);