r1537: Bugfix: Default icon for iconified windows wasn't found (Jimmy Olgeni).
[rox-filer.git] / ROX-Filer / src / filer.c
blob16d2376584e6a90c42398649ca48157716546558
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"
64 #include "xml.h"
66 static XMLwrapper *groups = NULL;
68 FilerWindow *window_with_focus = NULL;
69 GList *all_filer_windows = NULL;
71 static FilerWindow *window_with_primary = NULL;
73 /* Item we are about to display a tooltip for */
74 static DirItem *tip_item = NULL;
75 static GtkWidget *tip_widget = NULL;
76 static gint tip_timeout = 0;
77 static time_t tip_time = 0; /* Time tip widget last closed */
79 /* Static prototypes */
80 static void attach(FilerWindow *filer_window);
81 static void detach(FilerWindow *filer_window);
82 static void filer_window_destroyed(GtkWidget *widget,
83 FilerWindow *filer_window);
84 static void add_item(FilerWindow *filer_window, DirItem *item);
85 static void update_display(Directory *dir,
86 DirAction action,
87 GPtrArray *items,
88 FilerWindow *filer_window);
89 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
90 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
91 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
92 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
93 static gint coll_button_release(GtkWidget *widget,
94 GdkEventButton *event,
95 FilerWindow *filer_window);
96 static gint coll_button_press(GtkWidget *widget,
97 GdkEventButton *event,
98 FilerWindow *filer_window);
99 static gint coll_motion_notify(GtkWidget *widget,
100 GdkEventMotion *event,
101 FilerWindow *filer_window);
102 static void perform_action(FilerWindow *filer_window, GdkEventButton *event);
103 static void filer_add_widgets(FilerWindow *filer_window);
104 static void filer_add_signals(FilerWindow *filer_window);
105 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item);
106 static void show_tooltip(guchar *text);
107 static void filer_size_for(FilerWindow *filer_window,
108 int w, int h, int n, gboolean allow_shrink);
110 static void set_selection_state(FilerWindow *collection, gboolean normal);
111 static void filer_next_thumb(GObject *window, const gchar *path);
112 static void start_thumb_scanning(FilerWindow *filer_window);
113 static void filer_options_changed(void);
115 static GdkCursor *busy_cursor = NULL;
116 static GdkCursor *crosshair = NULL;
118 /* Indicates whether the filer's display is different to the machine it
119 * is actually running on.
121 static gboolean not_local=FALSE;
123 static Option o_filer_size_limit, o_short_flag_names;
124 Option o_filer_auto_resize, o_unique_filer_windows;
126 void filer_init(void)
128 const gchar *ohost;
129 const gchar *dpy;
130 gchar *dpyhost, *tmp;
132 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
133 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
134 RESIZE_ALWAYS);
135 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
137 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
139 option_add_notify(filer_options_changed);
141 busy_cursor = gdk_cursor_new(GDK_WATCH);
142 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
144 /* Is the display on the local machine, or are we being
145 * run remotely? See filer_set_title().
147 ohost = our_host_name();
148 dpy = gdk_get_display();
149 dpyhost = g_strdup(dpy);
150 tmp = strchr(dpyhost, ':');
151 if (tmp)
152 *tmp = '\0';
154 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
156 /* Try the cannonical name for dpyhost (see our_host_name()
157 * in support.c).
159 struct hostent *ent;
161 ent = gethostbyname(dpyhost);
162 if (!ent || strcmp(ohost, ent->h_name) != 0)
163 not_local = TRUE;
166 g_free(dpyhost);
169 static gboolean if_deleted(gpointer item, gpointer removed)
171 int i = ((GPtrArray *) removed)->len;
172 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
173 char *leafname = ((DirItem *) item)->leafname;
175 while (i--)
177 if (strcmp(leafname, r[i]->leafname) == 0)
178 return TRUE;
181 return FALSE;
184 static void update_item(FilerWindow *filer_window, DirItem *item)
186 char *leafname = item->leafname;
187 Collection *collection = filer_window->collection;
188 int old_w = collection->item_width;
189 int old_h = collection->item_height;
190 int w, h, i;
191 CollectionItem *colitem;
193 if (leafname[0] == '.' && filer_window->show_hidden == FALSE)
194 return;
196 i = collection_find_item(collection, item, filer_window->sort_fn);
198 if (i < 0)
200 g_warning("Failed to find '%s'\n", leafname);
201 return;
204 colitem = &collection->items[i];
206 display_update_view(filer_window,
207 (DirItem *) colitem->data,
208 (ViewData *) colitem->view_data,
209 FALSE);
211 calc_size(filer_window, colitem, &w, &h);
212 if (w > old_w || h > old_h)
213 collection_set_item_size(collection,
214 MAX(old_w, w),
215 MAX(old_h, h));
217 collection_draw_item(collection, i, TRUE);
220 /* Resize the filer window to w x h pixels, plus border (not clamped) */
221 static void filer_window_set_size(FilerWindow *filer_window,
222 int w, int h,
223 gboolean allow_shrink)
225 GtkWidget *window;
227 g_return_if_fail(filer_window != NULL);
229 if (filer_window->scrollbar)
230 w += filer_window->scrollbar->allocation.width;
232 if (o_toolbar.int_value != TOOLBAR_NONE)
233 h += filer_window->toolbar->allocation.height;
234 if (filer_window->message)
235 h += filer_window->message->allocation.height;
237 window = filer_window->window;
239 if (GTK_WIDGET_VISIBLE(window))
241 gint x, y;
242 GtkRequisition *req = &window->requisition;
243 GdkWindow *gdk_window = window->window;
245 w = MAX(req->width, w);
246 h = MAX(req->height, h);
247 gdk_window_get_position(gdk_window, &x, &y);
249 if (!allow_shrink)
251 gint old_w, old_h;
253 gdk_drawable_get_size(gdk_window, &old_w, &old_h);
254 w = MAX(w, old_w);
255 h = MAX(h, old_h);
257 if (w == old_w && h == old_h)
258 return;
261 if (x + w > screen_width || y + h > screen_height)
263 if (x + w > screen_width)
264 x = screen_width - w - 4;
265 if (y + h > screen_height)
266 y = screen_height - h - 4;
267 gdk_window_move_resize(gdk_window, x, y, w, h);
269 else
270 gdk_window_resize(gdk_window, w, h);
272 else
273 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
276 /* Resize the window to fit the items currently in the Directory.
277 * This should be used once the Directory has been fully scanned, otherwise
278 * the window will appear too small. When opening a directory for the first
279 * time, the names will be known but not the types and images. We can
280 * still make a good estimate of the size.
282 void filer_window_autosize(FilerWindow *filer_window, gboolean allow_shrink)
284 Collection *collection = filer_window->collection;
285 int n;
287 n = collection->number_of_items;
288 n = MAX(n, 2);
290 filer_size_for(filer_window,
291 collection->item_width,
292 collection->item_height,
293 n, allow_shrink);
296 /* Choose a good size for this window, assuming n items of size (w, h) */
297 static void filer_size_for(FilerWindow *filer_window,
298 int w, int h, int n, gboolean allow_shrink)
300 int x;
301 int rows, cols;
302 int max_x, max_rows;
303 const float r = 2.5;
304 int t = 0;
305 int size_limit;
306 int space = 0;
308 size_limit = o_filer_size_limit.int_value;
310 /* Get the extra height required for the toolbar and minibuffer,
311 * if visible.
313 if (o_toolbar.int_value != TOOLBAR_NONE)
314 t = filer_window->toolbar->allocation.height;
315 if (filer_window->message)
316 t += filer_window->message->allocation.height;
317 if (GTK_WIDGET_VISIBLE(filer_window->minibuffer_area))
319 GtkRequisition req;
321 gtk_widget_size_request(filer_window->minibuffer_area, &req);
322 space = req.height + 2;
323 t += space;
326 max_x = (size_limit * screen_width) / 100;
327 max_rows = (size_limit * screen_height) / (h * 100);
329 /* Aim for a size where
330 * x = r(y + t + h), (1)
331 * unless that's too wide.
333 * t = toolbar (and minibuffer) height
334 * r = desired (width / height) ratio
336 * Want to display all items:
337 * (x/w)(y/h) = n
338 * => xy = nwh
339 * => x(x/r - t - h) = nwh (from 1)
340 * => xx - x.rt - hr(1 - nw) = 0
341 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
342 * Now,
343 * 4hr(nw - 1) > 0
344 * so
345 * sqrt(rt.rt + ...) > rt
347 * So, the +/- must be +:
349 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
351 * ( + w - 1 to round up)
353 x = (r * t + sqrt(r*r*t*t + 4*h*r * (n*w - 1))) / 2 + w - 1;
355 /* Limit x */
356 if (x > max_x)
357 x = max_x;
359 cols = x / w;
360 cols = MAX(cols, 1);
362 /* Choose rows to display all items given our chosen x.
363 * Don't make the window *too* big!
365 rows = (n + cols - 1) / cols;
366 if (rows > max_rows)
367 rows = max_rows;
369 /* Leave some room for extra icons, but only in Small Icons mode
370 * otherwise it takes up too much space.
371 * Also, don't add space if the minibuffer is open.
373 if (space == 0)
374 space = filer_window->display_style == SMALL_ICONS ? h : 2;
376 filer_window_set_size(filer_window,
377 w * MAX(cols, 1),
378 h * MAX(rows, 1) + space,
379 allow_shrink);
382 /* Called on a timeout while scanning or when scanning ends
383 * (whichever happens first).
385 static gint open_filer_window(FilerWindow *filer_window)
387 shrink_grid(filer_window);
389 if (filer_window->open_timeout)
391 gtk_timeout_remove(filer_window->open_timeout);
392 filer_window->open_timeout = 0;
395 if (!GTK_WIDGET_VISIBLE(filer_window->window))
397 filer_window_autosize(filer_window, TRUE);
398 gtk_widget_show(filer_window->window);
401 return FALSE;
404 static void update_display(Directory *dir,
405 DirAction action,
406 GPtrArray *items,
407 FilerWindow *filer_window)
409 int old_num;
410 int i;
411 Collection *collection = filer_window->collection;
413 switch (action)
415 case DIR_ADD:
416 old_num = collection->number_of_items;
417 for (i = 0; i < items->len; i++)
419 DirItem *item = (DirItem *) items->pdata[i];
421 add_item(filer_window, item);
424 if (old_num != collection->number_of_items)
425 collection_qsort(collection,
426 filer_window->sort_fn);
428 /* Open and resize if currently hidden */
429 open_filer_window(filer_window);
430 break;
431 case DIR_REMOVE:
432 collection_delete_if(collection, if_deleted, items);
433 break;
434 case DIR_START_SCAN:
435 set_scanning_display(filer_window, TRUE);
436 toolbar_update_info(filer_window);
437 break;
438 case DIR_END_SCAN:
439 if (filer_window->window->window)
440 gdk_window_set_cursor(
441 filer_window->window->window,
442 NULL);
443 set_scanning_display(filer_window, FALSE);
444 toolbar_update_info(filer_window);
445 open_filer_window(filer_window);
447 if (filer_window->had_cursor &&
448 collection->cursor_item == -1)
450 collection_set_cursor_item(collection, 0);
451 filer_window->had_cursor = FALSE;
453 if (filer_window->auto_select)
454 display_set_autoselect(filer_window,
455 filer_window->auto_select);
456 g_free(filer_window->auto_select);
457 filer_window->auto_select = NULL;
459 filer_create_thumbs(filer_window);
461 if (filer_window->thumb_queue)
462 start_thumb_scanning(filer_window);
463 break;
464 case DIR_UPDATE:
465 collection_qsort(collection, filer_window->sort_fn);
467 for (i = 0; i < items->len; i++)
469 DirItem *item = (DirItem *) items->pdata[i];
471 update_item(filer_window, item);
473 break;
477 static void attach(FilerWindow *filer_window)
479 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
480 collection_clear(filer_window->collection);
481 filer_window->scanning = TRUE;
482 dir_attach(filer_window->directory, (DirCallback) update_display,
483 filer_window);
484 filer_set_title(filer_window);
487 static void detach(FilerWindow *filer_window)
489 g_return_if_fail(filer_window->directory != NULL);
491 dir_detach(filer_window->directory,
492 (DirCallback) update_display, filer_window);
493 g_object_unref(filer_window->directory);
494 filer_window->directory = NULL;
497 static void filer_window_destroyed(GtkWidget *widget,
498 FilerWindow *filer_window)
500 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
502 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
504 if (window_with_primary == filer_window)
505 window_with_primary = NULL;
507 if (window_with_focus == filer_window)
509 menu_popdown();
510 window_with_focus = NULL;
513 if (filer_window->directory)
514 detach(filer_window);
516 if (filer_window->open_timeout)
518 gtk_timeout_remove(filer_window->open_timeout);
519 filer_window->open_timeout = 0;
522 if (filer_window->thumb_queue)
524 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
525 g_list_free(filer_window->thumb_queue);
528 filer_tooltip_prime(NULL, NULL);
530 g_free(filer_window->auto_select);
531 g_free(filer_window->real_path);
532 g_free(filer_window->sym_path);
533 g_free(filer_window);
535 if (--number_of_windows < 1)
536 gtk_main_quit();
539 /* Add a single object to a directory display */
540 static void add_item(FilerWindow *filer_window, DirItem *item)
542 char *leafname = item->leafname;
543 Collection *collection = filer_window->collection;
544 int old_w = collection->item_width;
545 int old_h = collection->item_height;
546 int w, h, i;
548 if (leafname[0] == '.')
550 if (!filer_window->show_hidden)
551 return;
553 if (leafname[1] == '\0')
554 return; /* Never show '.' */
556 if (leafname[1] == '.' && leafname[2] == '\0')
557 return; /* Never show '..' */
560 i = collection_insert(collection, item,
561 display_create_viewdata(filer_window, item));
563 calc_size(filer_window, &collection->items[i], &w, &h);
565 if (w > old_w || h > old_h)
566 collection_set_item_size(collection,
567 MAX(old_w, w),
568 MAX(old_h, h));
571 /* Returns TRUE iff the directory still exists. */
572 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
574 Directory *dir;
576 g_return_val_if_fail(filer_window != NULL, FALSE);
578 /* We do a fresh lookup (rather than update) because the inode may
579 * have changed.
581 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
582 if (!dir)
584 if (warning)
585 info_message(_("Directory missing/deleted"));
586 gtk_widget_destroy(filer_window->window);
587 return FALSE;
589 if (dir == filer_window->directory)
590 g_object_unref(dir);
591 else
593 detach(filer_window);
594 filer_window->directory = dir;
595 attach(filer_window);
598 return TRUE;
601 /* The collection widget has lost the primary selection */
602 static gint collection_lose_selection(GtkWidget *widget,
603 GdkEventSelection *event)
605 if (window_with_primary &&
606 window_with_primary->collection == COLLECTION(widget))
608 FilerWindow *filer_window = window_with_primary;
609 window_with_primary = NULL;
610 set_selection_state(filer_window, FALSE);
613 return FALSE;
616 /* Someone wants us to send them the selection */
617 static void selection_get(GtkWidget *widget,
618 GtkSelectionData *selection_data,
619 guint info,
620 guint time,
621 gpointer data)
623 GString *reply, *header;
624 FilerWindow *filer_window;
625 int i;
626 Collection *collection;
628 filer_window = g_object_get_data(G_OBJECT(widget), "filer_window");
630 reply = g_string_new(NULL);
631 header = g_string_new(NULL);
633 switch (info)
635 case TARGET_STRING:
636 g_string_sprintf(header, " %s",
637 make_path(filer_window->sym_path, "")->str);
638 break;
639 case TARGET_URI_LIST:
640 g_string_sprintf(header, " file://%s%s",
641 our_host_name_for_dnd(),
642 make_path(filer_window->sym_path, "")->str);
643 break;
646 collection = filer_window->collection;
647 for (i = 0; i < collection->number_of_items; i++)
649 if (collection->items[i].selected)
651 DirItem *item =
652 (DirItem *) collection->items[i].data;
654 g_string_append(reply, header->str);
655 g_string_append(reply, item->leafname);
658 /* This works, but I don't think I like it... */
659 /* g_string_append_c(reply, ' '); */
661 if (reply->len > 0)
662 gtk_selection_data_set(selection_data, xa_string,
663 8, reply->str + 1, reply->len - 1);
664 else
666 g_warning("Attempt to paste empty selection!");
667 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
670 g_string_free(reply, TRUE);
671 g_string_free(header, TRUE);
674 /* No items are now selected. This might be because another app claimed
675 * the selection or because the user unselected all the items.
677 static void lose_selection(Collection *collection,
678 guint time,
679 gpointer user_data)
681 FilerWindow *filer_window = (FilerWindow *) user_data;
683 if (window_with_primary == filer_window)
685 window_with_primary = NULL;
686 gtk_selection_owner_set(NULL,
687 GDK_SELECTION_PRIMARY,
688 time);
692 static void selection_changed(Collection *collection,
693 gint time,
694 gpointer user_data)
696 FilerWindow *filer_window = (FilerWindow *) user_data;
698 /* Selection has been changed -- try to grab the primary selection
699 * if we don't have it.
701 if (window_with_primary == filer_window)
702 return; /* Already got it */
704 if (!collection->number_selected)
705 return; /* Nothing selected */
707 if (filer_window->temp_item_selected == FALSE &&
708 gtk_selection_owner_set(GTK_WIDGET(collection),
709 GDK_SELECTION_PRIMARY,
710 time))
712 window_with_primary = filer_window;
713 set_selection_state(filer_window, TRUE);
715 else
716 set_selection_state(filer_window, FALSE);
719 /* Open the item (or add it to the shell command minibuffer) */
720 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
722 gboolean shift = (flags & OPEN_SHIFT) != 0;
723 gboolean close_mini = flags & OPEN_FROM_MINI;
724 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
725 DirItem *item = (DirItem *)
726 filer_window->collection->items[item_number].data;
727 guchar *full_path;
728 gboolean wink = TRUE;
729 Directory *old_dir;
731 if (filer_window->mini_type == MINI_SHELL)
733 minibuffer_add(filer_window, item->leafname);
734 return;
737 if (!item->image)
738 dir_update_item(filer_window->directory, item->leafname);
740 if (item->base_type == TYPE_DIRECTORY)
742 /* Never close a filer window when opening a directory
743 * (click on a dir or click on an app with shift).
745 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
746 close_window = FALSE;
749 full_path = make_path(filer_window->sym_path, item->leafname)->str;
750 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
751 wink = FALSE;
753 old_dir = filer_window->directory;
754 if (run_diritem(full_path, item,
755 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
756 filer_window,
757 shift))
759 if (old_dir != filer_window->directory)
760 return;
762 if (close_window)
763 gtk_widget_destroy(filer_window->window);
764 else
766 if (wink)
767 collection_wink_item(filer_window->collection,
768 item_number);
769 if (close_mini)
770 minibuffer_hide(filer_window);
775 static gint pointer_in(GtkWidget *widget,
776 GdkEventCrossing *event,
777 FilerWindow *filer_window)
779 may_rescan(filer_window, TRUE);
780 return FALSE;
783 static gint pointer_out(GtkWidget *widget,
784 GdkEventCrossing *event,
785 FilerWindow *filer_window)
787 filer_tooltip_prime(NULL, NULL);
788 return FALSE;
791 /* Move the cursor to the next selected item in direction 'dir'
792 * (+1 or -1).
794 static void next_selected(FilerWindow *filer_window, int dir)
796 Collection *collection = filer_window->collection;
797 int to_check = collection->number_of_items;
798 int item = collection->cursor_item;
800 g_return_if_fail(dir == 1 || dir == -1);
802 if (to_check > 0 && item == -1)
804 /* Cursor not currently on */
805 if (dir == 1)
806 item = 0;
807 else
808 item = collection->number_of_items - 1;
810 if (collection->items[item].selected)
811 goto found;
814 while (--to_check > 0)
816 item += dir;
818 if (item >= collection->number_of_items)
819 item = 0;
820 else if (item < 0)
821 item = collection->number_of_items - 1;
823 if (collection->items[item].selected)
824 goto found;
827 gdk_beep();
828 return;
829 found:
830 collection_set_cursor_item(collection, item);
833 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
835 Collection *collection = filer_window->collection;
836 int item = collection->cursor_item;
837 TargetFunc cb = filer_window->target_cb;
838 gpointer data = filer_window->target_data;
839 OpenFlags flags = OPEN_SAME_WINDOW;
841 filer_target_mode(filer_window, NULL, NULL, NULL);
842 if (item < 0 || item >= collection->number_of_items)
843 return;
845 if (cb)
847 cb(filer_window, item, data);
848 return;
851 if (event->state & GDK_SHIFT_MASK)
852 flags |= OPEN_SHIFT;
854 filer_openitem(filer_window, item, flags);
857 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
858 * changed. If no groups were loaded and there is no file then initialised
859 * groups to an empty document.
860 * Return the node for the 'name' group.
862 static xmlNode *group_find(char *name)
864 xmlNode *node;
865 gchar *path;
867 /* Update the groups, if possible */
868 path = choices_find_path_load("Groups.xml", PROJECT);
869 if (path)
871 XMLwrapper *wrapper;
872 wrapper = xml_cache_load(path);
873 if (wrapper)
875 if (groups)
876 g_object_unref(groups);
877 groups = wrapper;
880 g_free(path);
883 if (!groups)
885 groups = xml_new(NULL);
886 groups->doc = xmlNewDoc("1.0");
888 xmlDocSetRootElement(groups->doc,
889 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
890 return NULL;
893 node = xmlDocGetRootElement(groups->doc);
895 for (node = node->xmlChildrenNode; node; node = node->next)
897 guchar *gid;
899 gid = xmlGetProp(node, "name");
901 if (!gid)
902 continue;
904 if (strcmp(name, gid) != 0)
905 continue;
907 g_free(gid);
909 return node;
912 return NULL;
915 static void group_save(FilerWindow *filer_window, char *name)
917 Collection *collection = filer_window->collection;
918 xmlNode *group;
919 guchar *save_path;
920 int i;
922 group = group_find(name);
923 if (group)
925 xmlUnlinkNode(group);
926 xmlFreeNode(group);
928 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
929 NULL, "group", NULL);
930 xmlSetProp(group, "name", name);
932 xmlNewChild(group, NULL, "directory", filer_window->sym_path);
934 for (i = 0; i < collection->number_of_items; i++)
936 DirItem *item = (DirItem *) collection->items[i].data;
937 gchar *u8_leaf = item->leafname;
939 if (!collection->items[i].selected)
940 continue;
942 xmlNewChild(group, NULL, "item", u8_leaf);
945 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
946 if (save_path)
948 save_xml_file(groups->doc, save_path);
949 g_free(save_path);
953 static void group_restore(FilerWindow *filer_window, char *name)
955 GHashTable *in_group;
956 Collection *collection = filer_window->collection;
957 int j, n;
958 char *path;
959 xmlNode *group, *node;
961 group = group_find(name);
963 if (!group)
965 report_error(_("Group %s is not set. Select some files "
966 "and press Ctrl+%s to set the group. Press %s "
967 "on its own to reselect the files later."),
968 name, name, name);
969 return;
972 node = get_subnode(group, NULL, "directory");
973 g_return_if_fail(node != NULL);
974 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
975 g_return_if_fail(path != NULL);
977 if (strcmp(path, filer_window->sym_path) != 0)
978 filer_change_to(filer_window, path, NULL);
979 g_free(path);
981 /* If an item at the start is selected then we could lose the
982 * primary selection after checking that item and then need to
983 * gain it again at the end. Therefore, if anything is selected
984 * then select the last item until the end of the search.
986 n = collection->number_of_items;
987 if (collection->number_selected)
988 collection_select_item(collection, n - 1);
990 in_group = g_hash_table_new(g_str_hash, g_str_equal);
991 for (node = group->xmlChildrenNode; node; node = node->next)
993 gchar *leaf;
994 if (node->type != XML_ELEMENT_NODE)
995 continue;
996 if (strcmp(node->name, "item") != 0)
997 continue;
999 leaf = xmlNodeListGetString(groups->doc,
1000 node->xmlChildrenNode, 1);
1001 if (!leaf)
1002 g_warning("Missing leafname!\n");
1003 else
1004 g_hash_table_insert(in_group, leaf, filer_window);
1007 for (j = 0; j < n; j++)
1009 DirItem *item = (DirItem *) collection->items[j].data;
1011 if (g_hash_table_lookup(in_group, item->leafname))
1012 collection_select_item(collection, j);
1013 else
1014 collection_unselect_item(collection, j);
1017 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1018 g_hash_table_destroy(in_group);
1021 /* Handle keys that can't be bound with the menu */
1022 static gint key_press_event(GtkWidget *widget,
1023 GdkEventKey *event,
1024 FilerWindow *filer_window)
1026 gboolean handled;
1027 guint key = event->keyval;
1028 char group[2] = "1";
1030 window_with_focus = filer_window;
1032 /* Note: Not convinced this is the way Gtk's key system is supposed
1033 * to be used...
1035 if (!filer_keys)
1036 ensure_filer_menu(); /* Gets the keys working... */
1037 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1038 filer_keys);
1039 handled = gtk_accel_groups_activate(G_OBJECT(filer_window->window),
1040 event->keyval, event->state);
1041 if (window_with_focus)
1042 gtk_window_remove_accel_group(GTK_WINDOW(filer_window->window),
1043 filer_keys);
1044 else
1045 return TRUE; /* Window no longer exists */
1046 if (handled)
1047 return TRUE;
1049 switch (key)
1051 case GDK_Escape:
1052 filer_target_mode(filer_window, NULL, NULL, NULL);
1053 return FALSE;
1054 case GDK_Return:
1055 return_pressed(filer_window, event);
1056 break;
1057 case GDK_ISO_Left_Tab:
1058 next_selected(filer_window, -1);
1059 break;
1060 case GDK_Tab:
1061 next_selected(filer_window, 1);
1062 break;
1063 case GDK_BackSpace:
1064 change_to_parent(filer_window);
1065 break;
1066 case GDK_backslash:
1067 filer_tooltip_prime(NULL, NULL);
1068 show_filer_menu(filer_window, (GdkEvent *) event,
1069 filer_window->collection->cursor_item);
1070 break;
1071 default:
1072 if (key >= GDK_0 && key <= GDK_9)
1073 group[0] = key - GDK_0 + '0';
1074 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1075 group[0] = key - GDK_KP_0 + '0';
1076 else
1077 return FALSE;
1080 if (event->state & GDK_CONTROL_MASK)
1081 group_save(filer_window, group);
1082 else
1083 group_restore(filer_window, group);
1086 return TRUE;
1089 void filer_open_parent(FilerWindow *filer_window)
1091 char *dir;
1092 const char *current = filer_window->sym_path;
1094 if (current[0] == '/' && current[1] == '\0')
1095 return; /* Already in the root */
1097 dir = g_dirname(current);
1098 filer_opendir(dir, filer_window);
1099 g_free(dir);
1102 void change_to_parent(FilerWindow *filer_window)
1104 char *dir;
1105 const char *current = filer_window->sym_path;
1107 if (current[0] == '/' && current[1] == '\0')
1108 return; /* Already in the root */
1110 dir = g_dirname(current);
1111 filer_change_to(filer_window, dir, g_basename(current));
1112 g_free(dir);
1115 /* Removes trailing /s from path (modified in place) */
1116 static void tidy_sympath(gchar *path)
1118 int l;
1120 g_return_if_fail(path != NULL);
1122 l = strlen(path);
1123 while (l > 1 && path[l - 1] == '/')
1125 l--;
1126 path[l] = '\0';
1130 /* Make filer_window display path. When finished, highlight item 'from', or
1131 * the first item if from is NULL. If there is currently no cursor then
1132 * simply wink 'from' (if not NULL).
1134 void filer_change_to(FilerWindow *filer_window,
1135 const char *path, const char *from)
1137 char *from_dup;
1138 char *sym_path, *real_path;
1139 Directory *new_dir;
1141 g_return_if_fail(filer_window != NULL);
1143 filer_cancel_thumbnails(filer_window);
1145 filer_tooltip_prime(NULL, NULL);
1147 sym_path = g_strdup(path);
1148 real_path = pathdup(path);
1149 new_dir = g_fscache_lookup(dir_cache, real_path);
1151 if (!new_dir)
1153 delayed_error(_("Directory '%s' is not accessible"),
1154 sym_path);
1155 g_free(real_path);
1156 g_free(sym_path);
1157 return;
1160 if (o_unique_filer_windows.int_value)
1162 FilerWindow *fw;
1164 fw = find_filer_window(sym_path, filer_window);
1165 if (fw)
1166 gtk_widget_destroy(fw->window);
1169 from_dup = from && *from ? g_strdup(from) : NULL;
1171 detach(filer_window);
1172 g_free(filer_window->real_path);
1173 g_free(filer_window->sym_path);
1174 filer_window->real_path = real_path;
1175 filer_window->sym_path = sym_path;
1176 tidy_sympath(filer_window->sym_path);
1178 filer_window->directory = new_dir;
1180 g_free(filer_window->auto_select);
1181 filer_window->had_cursor = filer_window->collection->cursor_item != -1
1182 || filer_window->had_cursor;
1183 filer_window->auto_select = from_dup;
1185 filer_set_title(filer_window);
1186 if (filer_window->window->window)
1187 gdk_window_set_role(filer_window->window->window,
1188 filer_window->sym_path);
1189 collection_set_cursor_item(filer_window->collection, -1);
1191 attach(filer_window);
1192 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1193 filer_window_autosize(filer_window, TRUE);
1195 if (filer_window->mini_type == MINI_PATH)
1196 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1197 filer_window);
1200 /* Returns a list containing the full (sym) pathname of every selected item.
1201 * You must g_free() each item in the list.
1203 GList *filer_selected_items(FilerWindow *filer_window)
1205 Collection *collection = filer_window->collection;
1206 GList *retval = NULL;
1207 guchar *dir = filer_window->sym_path;
1208 int i;
1210 for (i = 0; i < collection->number_of_items; i++)
1212 if (collection->items[i].selected)
1214 DirItem *item = (DirItem *) collection->items[i].data;
1216 retval = g_list_prepend(retval,
1217 g_strdup(make_path(dir, item->leafname)->str));
1221 return g_list_reverse(retval);
1224 int selected_item_number(Collection *collection)
1226 int i;
1228 g_return_val_if_fail(collection != NULL, -1);
1229 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1230 g_return_val_if_fail(collection->number_selected == 1, -1);
1232 for (i = 0; i < collection->number_of_items; i++)
1233 if (collection->items[i].selected)
1234 return i;
1236 g_warning("selected_item: number_selected is wrong\n");
1238 return -1;
1241 DirItem *selected_item(Collection *collection)
1243 int item;
1245 item = selected_item_number(collection);
1247 if (item > -1)
1248 return (DirItem *) collection->items[item].data;
1249 return NULL;
1252 /* Append all the URIs in the selection to the string */
1253 static void create_uri_list(FilerWindow *filer_window, GString *string)
1255 Collection *collection = filer_window->collection;
1256 GString *leader;
1257 int i, num_selected;
1259 leader = g_string_new("file://");
1260 g_string_append(leader, our_host_name_for_dnd());
1261 g_string_append(leader, filer_window->sym_path);
1262 if (leader->str[leader->len - 1] != '/')
1263 g_string_append_c(leader, '/');
1265 num_selected = collection->number_selected;
1267 for (i = 0; num_selected > 0; i++)
1269 if (collection->items[i].selected)
1271 DirItem *item = (DirItem *) collection->items[i].data;
1273 g_string_append(string, leader->str);
1274 g_string_append(string, item->leafname);
1275 g_string_append(string, "\r\n");
1276 num_selected--;
1280 g_string_free(leader, TRUE);
1283 /* Creates and shows a new filer window.
1284 * If src_win != NULL then display options can be taken from that source window.
1285 * Returns the new filer window, or NULL on error.
1286 * Note: if unique windows is in use, may return an existing window.
1288 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win)
1290 FilerWindow *filer_window;
1291 char *real_path;
1292 DisplayStyle dstyle;
1293 DetailsType dtype;
1295 /* Get the real pathname of the directory and copy it */
1296 real_path = pathdup(path);
1298 if (o_unique_filer_windows.int_value)
1300 FilerWindow *same_dir_window;
1302 same_dir_window = find_filer_window(path, NULL);
1304 if (same_dir_window)
1306 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1307 return same_dir_window;
1311 filer_window = g_new(FilerWindow, 1);
1312 filer_window->message = NULL;
1313 filer_window->minibuffer = NULL;
1314 filer_window->minibuffer_label = NULL;
1315 filer_window->minibuffer_area = NULL;
1316 filer_window->temp_show_hidden = FALSE;
1317 filer_window->sym_path = g_strdup(path);
1318 filer_window->real_path = real_path;
1319 filer_window->scanning = FALSE;
1320 filer_window->had_cursor = FALSE;
1321 filer_window->auto_select = NULL;
1322 filer_window->toolbar_text = NULL;
1323 filer_window->target_cb = NULL;
1324 filer_window->mini_type = MINI_NONE;
1325 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1326 filer_window->toolbar = NULL;
1327 filer_window->toplevel_vbox = NULL;
1329 tidy_sympath(filer_window->sym_path);
1331 /* Finds the entry for this directory in the dir cache, creating
1332 * a new one if needed. This does not cause a scan to start,
1333 * so if a new entry is created then it will be empty.
1335 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1336 if (!filer_window->directory)
1338 delayed_error(_("Directory '%s' not found."), path);
1339 g_free(filer_window->real_path);
1340 g_free(filer_window->sym_path);
1341 g_free(filer_window);
1342 return NULL;
1345 filer_window->temp_item_selected = FALSE;
1346 filer_window->flags = (FilerFlags) 0;
1347 filer_window->details_type = DETAILS_SUMMARY;
1348 filer_window->display_style = UNKNOWN_STYLE;
1349 filer_window->thumb_queue = NULL;
1350 filer_window->max_thumbs = 0;
1352 if (src_win && o_display_inherit_options.int_value)
1354 filer_window->sort_fn = src_win->sort_fn;
1355 dstyle = src_win->display_style;
1356 dtype = src_win->details_type;
1357 filer_window->show_hidden = src_win->show_hidden;
1358 filer_window->show_thumbs = src_win->show_thumbs;
1360 else
1362 int i = o_display_sort_by.int_value;
1363 filer_window->sort_fn = i == 0 ? sort_by_name :
1364 i == 1 ? sort_by_type :
1365 i == 2 ? sort_by_date :
1366 sort_by_size;
1368 dstyle = o_display_size.int_value;
1369 dtype = o_display_details.int_value;
1370 filer_window->show_hidden =
1371 o_display_show_hidden.int_value;
1372 filer_window->show_thumbs =
1373 o_display_show_thumbs.int_value;
1376 /* Add all the user-interface elements & realise */
1377 filer_add_widgets(filer_window);
1378 if (src_win)
1379 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1380 GTK_WIN_POS_MOUSE);
1382 /* Connect to all the signal handlers */
1383 filer_add_signals(filer_window);
1385 display_set_layout(filer_window, dstyle, dtype);
1387 /* Open the window after a timeout, or when scanning stops.
1388 * Do this before attaching, because attach() might tell us to
1389 * stop scanning (if a scan isn't needed).
1391 filer_window->open_timeout = gtk_timeout_add(500,
1392 (GtkFunction) open_filer_window,
1393 filer_window);
1395 /* The collection is created empty and then attach() is called, which
1396 * links the filer window to the entry in the directory cache we
1397 * looked up / created above.
1399 * The attach() function will immediately callback to the filer window
1400 * to deliver a list of all known entries in the directory (so,
1401 * collection->number_of_items may be valid after the call to
1402 * attach() returns).
1404 * BUT, if the directory was not in the cache (because it hadn't been
1405 * opened it before) then the cached dir will be empty and nothing gets
1406 * added until a while later when some entries are actually available.
1409 attach(filer_window);
1411 number_of_windows++;
1412 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1414 return filer_window;
1417 /* This adds all the widgets to a new filer window. It is in a separate
1418 * function because filer_opendir() was getting too long...
1420 static void filer_add_widgets(FilerWindow *filer_window)
1422 GtkWidget *hbox, *vbox, *collection;
1424 /* Create the top-level window widget */
1425 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1426 filer_set_title(filer_window);
1428 /* The collection is the area that actually displays the files */
1429 collection = collection_new();
1431 /* This property is cleared when the window is destroyed.
1432 * You can thus ref filer_window->window and use this to see
1433 * if the window no longer exists.
1435 g_object_set_data(G_OBJECT(filer_window->window),
1436 "filer_window", filer_window);
1438 g_object_set_data(G_OBJECT(collection), "filer_window", filer_window);
1439 filer_window->collection = COLLECTION(collection);
1441 filer_window->collection->free_item = display_free_colitem;
1443 /* Scrollbar on the right, everything else on the left */
1444 hbox = gtk_hbox_new(FALSE, 0);
1445 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1447 vbox = gtk_vbox_new(FALSE, 0);
1448 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1449 filer_window->toplevel_vbox = GTK_BOX(vbox);
1451 /* If we want a toolbar, create it now */
1452 toolbar_update_toolbar(filer_window);
1454 /* If there's a message that should be displayed in each window (eg
1455 * 'Running as root'), add it here.
1457 if (show_user_message)
1459 filer_window->message = gtk_label_new(show_user_message);
1460 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1461 FALSE, TRUE, 0);
1462 gtk_widget_show(filer_window->message);
1465 /* Now add the area for displaying the files.
1466 * The collection is one huge window that goes in a Viewport.
1469 GtkWidget *viewport;
1470 GtkAdjustment *adj;
1472 adj = filer_window->collection->vadj;
1473 viewport = gtk_viewport_new(NULL, adj);
1474 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport),
1475 GTK_SHADOW_NONE);
1476 gtk_container_add(GTK_CONTAINER(viewport), collection);
1477 gtk_widget_show_all(viewport);
1478 gtk_box_pack_start(GTK_BOX(vbox), viewport, TRUE, TRUE, 0);
1479 filer_window->scrollbar = gtk_vscrollbar_new(adj);
1480 gtk_widget_set_size_request(viewport, 4, 4);
1482 gtk_container_set_resize_mode(GTK_CONTAINER(viewport),
1483 GTK_RESIZE_IMMEDIATE);
1486 /* And the minibuffer (hidden to start with) */
1487 create_minibuffer(filer_window);
1488 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1489 FALSE, TRUE, 0);
1491 /* And the thumbnail progress bar (also hidden) */
1493 GtkWidget *cancel;
1495 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1496 gtk_box_pack_start(GTK_BOX(vbox), filer_window->thumb_bar,
1497 FALSE, TRUE, 0);
1499 filer_window->thumb_progress = gtk_progress_bar_new();
1501 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1502 filer_window->thumb_progress, TRUE, TRUE, 0);
1504 cancel = gtk_button_new_with_label(_("Cancel"));
1505 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1506 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1507 cancel, FALSE, TRUE, 0);
1508 g_signal_connect_swapped(cancel, "clicked",
1509 G_CALLBACK(filer_cancel_thumbnails),
1510 filer_window);
1513 /* Put the scrollbar on the left of everything else... */
1514 gtk_box_pack_start(GTK_BOX(hbox),
1515 filer_window->scrollbar, FALSE, TRUE, 0);
1517 gtk_window_set_focus(GTK_WINDOW(filer_window->window), collection);
1519 gtk_widget_show(hbox);
1520 gtk_widget_show(vbox);
1521 gtk_widget_show(filer_window->scrollbar);
1522 gtk_widget_show(collection);
1524 gtk_widget_realize(filer_window->window);
1526 gdk_window_set_role(filer_window->window->window,
1527 filer_window->sym_path);
1529 filer_window_set_size(filer_window, 4, 4, TRUE);
1532 static void filer_add_signals(FilerWindow *filer_window)
1534 GtkObject *collection = GTK_OBJECT(filer_window->collection);
1535 GtkTargetEntry target_table[] =
1537 {"text/uri-list", 0, TARGET_URI_LIST},
1538 {"STRING", 0, TARGET_STRING},
1539 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1542 /* Events on the top-level window */
1543 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1544 g_signal_connect(filer_window->window, "enter-notify-event",
1545 G_CALLBACK(pointer_in), filer_window);
1546 g_signal_connect(filer_window->window, "leave-notify-event",
1547 G_CALLBACK(pointer_out), filer_window);
1548 g_signal_connect(filer_window->window, "destroy",
1549 G_CALLBACK(filer_window_destroyed), filer_window);
1551 /* Events on the collection widget */
1552 gtk_widget_set_events(GTK_WIDGET(collection),
1553 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
1554 GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_MASK |
1555 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1557 g_signal_connect_swapped(collection, "style_set",
1558 G_CALLBACK(display_update_views), filer_window);
1559 g_signal_connect(collection, "lose_selection",
1560 G_CALLBACK(lose_selection), filer_window);
1561 g_signal_connect(collection, "selection_changed",
1562 G_CALLBACK(selection_changed), filer_window);
1563 g_signal_connect(collection, "selection_clear_event",
1564 G_CALLBACK(collection_lose_selection), NULL);
1565 g_signal_connect(collection, "selection_get",
1566 G_CALLBACK(selection_get), NULL);
1567 gtk_selection_add_targets(GTK_WIDGET(collection), GDK_SELECTION_PRIMARY,
1568 target_table,
1569 sizeof(target_table) / sizeof(*target_table));
1571 g_signal_connect(collection, "key_press_event",
1572 G_CALLBACK(key_press_event), filer_window);
1573 g_signal_connect(collection, "button-release-event",
1574 G_CALLBACK(coll_button_release), filer_window);
1575 g_signal_connect(collection, "button-press-event",
1576 G_CALLBACK(coll_button_press), filer_window);
1577 g_signal_connect(collection, "motion-notify-event",
1578 G_CALLBACK(coll_motion_notify), filer_window);
1580 /* Drag and drop events */
1581 g_signal_connect(collection, "drag_data_get",
1582 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1583 drag_set_dest(filer_window);
1586 static gint clear_scanning_display(FilerWindow *filer_window)
1588 if (filer_exists(filer_window))
1589 filer_set_title(filer_window);
1590 return FALSE;
1593 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1595 if (scanning == filer_window->scanning)
1596 return;
1597 filer_window->scanning = scanning;
1599 if (scanning)
1600 filer_set_title(filer_window);
1601 else
1602 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1603 filer_window);
1606 /* Note that filer_window may not exist after this call */
1607 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1609 if (may_rescan(filer_window, warning))
1610 dir_update(filer_window->directory, filer_window->sym_path);
1613 void filer_update_all(void)
1615 GList *next = all_filer_windows;
1617 while (next)
1619 FilerWindow *filer_window = (FilerWindow *) next->data;
1621 /* Updating directory may remove it from list -- stop sending
1622 * patches to move this line!
1624 next = next->next;
1626 filer_update_dir(filer_window, TRUE);
1630 /* Refresh the various caches even if we don't think we need to */
1631 void full_refresh(void)
1633 mount_update(TRUE);
1636 /* See whether a filer window with a given path already exists
1637 * and is different from diff.
1639 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1641 GList *next;
1643 for (next = all_filer_windows; next; next = next->next)
1645 FilerWindow *filer_window = (FilerWindow *) next->data;
1647 if (filer_window != diff &&
1648 strcmp(sym_path, filer_window->sym_path) == 0)
1649 return filer_window;
1652 return NULL;
1655 /* This path has been mounted/umounted/deleted some files - update all dirs */
1656 void filer_check_mounted(const char *real_path)
1658 GList *next = all_filer_windows;
1659 gchar *parent;
1660 int len;
1662 len = strlen(real_path);
1664 while (next)
1666 FilerWindow *filer_window = (FilerWindow *) next->data;
1668 next = next->next;
1670 if (strncmp(real_path, filer_window->real_path, len) == 0)
1672 char s = filer_window->real_path[len];
1674 if (s == '/' || s == '\0')
1675 filer_update_dir(filer_window, FALSE);
1679 parent = g_dirname(real_path);
1680 refresh_dirs(parent);
1681 g_free(parent);
1683 icons_may_update(real_path);
1686 /* Close all windows displaying 'path' or subdirectories of 'path' */
1687 void filer_close_recursive(const char *path)
1689 GList *next = all_filer_windows;
1690 gchar *real;
1691 int len;
1693 real = pathdup(path);
1694 len = strlen(real);
1696 while (next)
1698 FilerWindow *filer_window = (FilerWindow *) next->data;
1700 next = next->next;
1702 if (strncmp(real, filer_window->real_path, len) == 0)
1704 char s = filer_window->real_path[len];
1706 if (len == 1 || s == '/' || s == '\0')
1707 gtk_widget_destroy(filer_window->window);
1712 /* Like minibuffer_show(), except that:
1713 * - It returns FALSE (to be used from an idle callback)
1714 * - It checks that the filer window still exists.
1716 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1718 if (filer_exists(filer_window))
1719 minibuffer_show(filer_window, MINI_PATH);
1720 return FALSE;
1723 /* TRUE iff filer_window points to an existing FilerWindow
1724 * structure.
1726 gboolean filer_exists(FilerWindow *filer_window)
1728 GList *next;
1730 for (next = all_filer_windows; next; next = next->next)
1732 FilerWindow *fw = (FilerWindow *) next->data;
1734 if (fw == filer_window)
1735 return TRUE;
1738 return FALSE;
1741 /* Make sure the window title is up-to-date */
1742 void filer_set_title(FilerWindow *filer_window)
1744 guchar *title = NULL;
1745 guchar *flags = "";
1747 if (filer_window->scanning || filer_window->show_hidden ||
1748 filer_window->show_thumbs)
1750 if (o_short_flag_names.int_value)
1752 flags = g_strconcat(" +",
1753 filer_window->scanning ? _("S") : "",
1754 filer_window->show_hidden ? _("A") : "",
1755 filer_window->show_thumbs ? _("T") : "",
1756 NULL);
1758 else
1760 flags = g_strconcat(" (",
1761 filer_window->scanning ? _("Scanning, ") : "",
1762 filer_window->show_hidden ? _("All, ") : "",
1763 filer_window->show_thumbs ? _("Thumbs, ") : "",
1764 NULL);
1765 flags[strlen(flags) - 2] = ')';
1769 if (not_local)
1770 title = g_strconcat("//", our_host_name(),
1771 filer_window->sym_path, flags, NULL);
1773 if (!title && home_dir_len > 1 &&
1774 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1776 guchar sep = filer_window->sym_path[home_dir_len];
1778 if (sep == '\0' || sep == '/')
1779 title = g_strconcat("~",
1780 filer_window->sym_path + home_dir_len,
1781 flags,
1782 NULL);
1785 if (!title)
1786 title = g_strconcat(filer_window->sym_path, flags, NULL);
1788 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1789 g_free(title);
1791 if (flags[0] != '\0')
1792 g_free(flags);
1795 /* Reconnect to the same directory (used when the Show Hidden option is
1796 * toggled). This has the side-effect of updating the window title.
1798 void filer_detach_rescan(FilerWindow *filer_window)
1800 Directory *dir = filer_window->directory;
1802 g_object_ref(dir);
1803 detach(filer_window);
1804 filer_window->directory = dir;
1805 attach(filer_window);
1808 static gint coll_button_release(GtkWidget *widget,
1809 GdkEventButton *event,
1810 FilerWindow *filer_window)
1812 if (dnd_motion_release(event))
1814 if (motion_buttons_pressed == 0 &&
1815 filer_window->collection->lasso_box)
1817 collection_end_lasso(filer_window->collection,
1818 event->button == 1 ? GDK_SET : GDK_INVERT);
1820 return FALSE;
1823 perform_action(filer_window, event);
1825 return FALSE;
1828 static void perform_action(FilerWindow *filer_window, GdkEventButton *event)
1830 Collection *collection = filer_window->collection;
1831 DirItem *dir_item;
1832 int item;
1833 BindAction action;
1834 gboolean press = event->type == GDK_BUTTON_PRESS;
1835 gboolean selected = FALSE;
1836 OpenFlags flags = 0;
1838 if (event->button > 3)
1839 return;
1841 item = collection_get_item(collection, event->x, event->y);
1843 if (item != -1 && event->button == 1 &&
1844 collection->items[item].selected &&
1845 filer_window->selection_state == GTK_STATE_INSENSITIVE)
1847 selection_changed(collection, event->time, filer_window);
1848 return;
1851 if (filer_window->target_cb)
1853 dnd_motion_ungrab();
1854 if (item != -1 && press && event->button == 1)
1855 filer_window->target_cb(filer_window, item,
1856 filer_window->target_data);
1857 filer_target_mode(filer_window, NULL, NULL, NULL);
1859 return;
1862 action = bind_lookup_bev(
1863 item == -1 ? BIND_DIRECTORY : BIND_DIRECTORY_ICON,
1864 event);
1866 if (item != -1)
1868 dir_item = (DirItem *) collection->items[item].data;
1869 selected = collection->items[item].selected;
1871 else
1872 dir_item = NULL;
1874 switch (action)
1876 case ACT_CLEAR_SELECTION:
1877 collection_clear_selection(collection);
1878 break;
1879 case ACT_TOGGLE_SELECTED:
1880 collection_toggle_item(collection, item);
1881 break;
1882 case ACT_SELECT_EXCL:
1883 collection_clear_except(collection, item);
1884 break;
1885 case ACT_EDIT_ITEM:
1886 flags |= OPEN_SHIFT;
1887 /* (no break) */
1888 case ACT_OPEN_ITEM:
1889 if (event->button != 1)
1890 flags |= OPEN_CLOSE_WINDOW;
1891 else
1892 flags |= OPEN_SAME_WINDOW;
1893 if (o_new_button_1.int_value)
1894 flags ^= OPEN_SAME_WINDOW;
1895 if (event->type == GDK_2BUTTON_PRESS)
1896 collection_unselect_item(collection, item);
1897 dnd_motion_ungrab();
1898 filer_openitem(filer_window, item, flags);
1899 break;
1900 case ACT_POPUP_MENU:
1901 dnd_motion_ungrab();
1902 filer_tooltip_prime(NULL, NULL);
1903 show_filer_menu(filer_window, (GdkEvent *) event, item);
1904 break;
1905 case ACT_PRIME_AND_SELECT:
1906 if (!selected)
1907 collection_clear_except(collection, item);
1908 dnd_motion_start(MOTION_READY_FOR_DND);
1909 break;
1910 case ACT_PRIME_AND_TOGGLE:
1911 collection_toggle_item(collection, item);
1912 dnd_motion_start(MOTION_READY_FOR_DND);
1913 break;
1914 case ACT_PRIME_FOR_DND:
1915 dnd_motion_start(MOTION_READY_FOR_DND);
1916 break;
1917 case ACT_IGNORE:
1918 if (press && event->button < 4)
1920 if (item)
1921 collection_wink_item(collection, item);
1922 dnd_motion_start(MOTION_NONE);
1924 break;
1925 case ACT_LASSO_CLEAR:
1926 collection_clear_selection(collection);
1927 /* (no break) */
1928 case ACT_LASSO_MODIFY:
1929 collection_lasso_box(collection, event->x, event->y);
1930 break;
1931 case ACT_RESIZE:
1932 filer_window_autosize(filer_window, TRUE);
1933 break;
1934 default:
1935 g_warning("Unsupported action : %d\n", action);
1936 break;
1940 static gint coll_button_press(GtkWidget *widget,
1941 GdkEventButton *event,
1942 FilerWindow *filer_window)
1944 collection_set_cursor_item(filer_window->collection, -1);
1946 if (dnd_motion_press(widget, event))
1947 perform_action(filer_window, event);
1949 return FALSE;
1952 static gint coll_motion_notify(GtkWidget *widget,
1953 GdkEventMotion *event,
1954 FilerWindow *filer_window)
1956 Collection *collection = filer_window->collection;
1957 int i;
1959 i = collection_get_item(collection, event->x, event->y);
1961 if (i == -1)
1962 filer_tooltip_prime(NULL, NULL);
1963 else
1964 filer_tooltip_prime(filer_window,
1965 (DirItem *) collection->items[i].data);
1967 if (motion_state != MOTION_READY_FOR_DND)
1968 return FALSE;
1970 if (!dnd_motion_moved(event))
1971 return FALSE;
1973 i = collection_get_item(collection,
1974 event->x - (event->x_root - drag_start_x),
1975 event->y - (event->y_root - drag_start_y));
1976 if (i == -1)
1977 return FALSE;
1979 collection_wink_item(collection, -1);
1981 if (!collection->items[i].selected)
1983 if (event->state & GDK_BUTTON1_MASK)
1985 /* Select just this one */
1986 filer_window->temp_item_selected = TRUE;
1987 collection_clear_except(collection, i);
1989 else
1991 if (collection->number_selected == 0)
1992 filer_window->temp_item_selected = TRUE;
1993 collection_select_item(collection, i);
1997 g_return_val_if_fail(collection->number_selected > 0, TRUE);
1999 if (collection->number_selected == 1)
2001 DirItem *item = (DirItem *) collection->items[i].data;
2002 ViewData *view = (ViewData *) collection->items[i].view_data;
2004 if (!item->image)
2005 item = dir_update_item(filer_window->directory,
2006 item->leafname);
2008 if (!item)
2010 report_error(_("Item no longer exists!"));
2011 return FALSE;
2014 drag_one_item(widget, event,
2015 make_path(filer_window->sym_path, item->leafname)->str,
2016 item, view ? view->image : NULL);
2018 else
2020 GString *uris;
2022 uris = g_string_new(NULL);
2023 create_uri_list(filer_window, uris);
2024 drag_selection(widget, event, uris->str);
2025 g_string_free(uris, TRUE);
2028 return FALSE;
2031 /* Puts the filer window into target mode. When an item is chosen,
2032 * fn(filer_window, item, data) is called. 'reason' will be displayed
2033 * on the toolbar while target mode is active.
2035 * Use fn == NULL to cancel target mode.
2037 void filer_target_mode(FilerWindow *filer_window,
2038 TargetFunc fn,
2039 gpointer data,
2040 const char *reason)
2042 TargetFunc old_fn = filer_window->target_cb;
2044 if (fn != old_fn)
2045 gdk_window_set_cursor(
2046 GTK_WIDGET(filer_window->collection)->window,
2047 fn ? crosshair : NULL);
2049 filer_window->target_cb = fn;
2050 filer_window->target_data = data;
2052 if (filer_window->toolbar_text == NULL)
2053 return;
2055 if (fn)
2056 gtk_label_set_text(
2057 GTK_LABEL(filer_window->toolbar_text), reason);
2058 else if (o_toolbar_info.int_value)
2060 if (old_fn)
2061 toolbar_update_info(filer_window);
2063 else
2064 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2067 /* Draw the black border */
2068 static gint filer_tooltip_draw(GtkWidget *w)
2070 gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
2071 w->allocation.width - 1, w->allocation.height - 1);
2073 return FALSE;
2076 /* When the tips window closed, record the time. If we try to open another
2077 * tip soon, it will appear more quickly.
2079 static void tip_destroyed(gpointer data)
2081 time(&tip_time);
2084 /* It's time to make the tooltip appear. If we're not over the item any
2085 * more, or the item doesn't need a tooltip, do nothing.
2087 static gboolean filer_tooltip_activate(FilerWindow *filer_window)
2089 Collection *collection;
2090 gint x, y;
2091 int i;
2092 GString *tip = NULL;
2093 guchar *fullpath = NULL;
2095 g_return_val_if_fail(tip_item != NULL, 0);
2097 tip_timeout = 0;
2099 show_tooltip(NULL);
2101 if (!filer_exists(filer_window))
2102 return FALSE;
2104 collection = filer_window->collection;
2105 gdk_window_get_pointer(GTK_WIDGET(collection)->window, &x, &y, NULL);
2106 i = collection_get_item(collection, x, y);
2107 if (i == -1 || ((DirItem *) collection->items[i].data) != tip_item)
2108 return FALSE; /* Not still under the pointer */
2110 /* OK, the filer window still exists and the pointer is still
2111 * over the same item. Do we need to show a tip?
2114 tip = g_string_new(NULL);
2116 if (display_is_truncated(filer_window, i))
2118 g_string_append(tip, tip_item->leafname);
2119 g_string_append_c(tip, '\n');
2122 fullpath = make_path(filer_window->real_path, tip_item->leafname)->str;
2124 if (tip_item->flags & ITEM_FLAG_SYMLINK)
2126 char *target;
2128 target = readlink_dup(fullpath);
2129 if (target)
2131 g_string_append(tip, _("Symbolic link to "));
2132 g_string_append(tip, target);
2133 g_string_append_c(tip, '\n');
2134 g_free(target);
2138 if (tip_item->flags & ITEM_FLAG_APPDIR)
2140 XMLwrapper *info;
2141 xmlNode *node;
2143 info = appinfo_get(fullpath, tip_item);
2144 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
2146 guchar *str;
2147 str = xmlNodeListGetString(node->doc,
2148 node->xmlChildrenNode, 1);
2149 if (str)
2151 g_string_append(tip, str);
2152 g_string_append_c(tip, '\n');
2153 g_free(str);
2156 if (info)
2157 g_object_unref(info);
2160 if (!g_utf8_validate(tip_item->leafname, -1, NULL))
2161 g_string_append(tip,
2162 _("This filename is not valid UTF-8. "
2163 "You should rename it.\n"));
2165 if (tip->len > 1)
2167 g_string_truncate(tip, tip->len - 1);
2168 show_tooltip(tip->str);
2171 g_string_free(tip, TRUE);
2173 return FALSE;
2176 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
2177 * NULL, close any current tooltip.
2179 static void show_tooltip(guchar *text)
2181 GtkWidget *label;
2182 int x, y, py;
2183 int w, h;
2185 if (tip_widget)
2187 gtk_widget_destroy(tip_widget);
2188 tip_widget = NULL;
2191 if (!text)
2192 return;
2194 /* Show the tip */
2195 tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
2196 gtk_widget_set_app_paintable(tip_widget, TRUE);
2197 gtk_widget_set_name(tip_widget, "gtk-tooltips");
2199 g_signal_connect_swapped(tip_widget, "expose_event",
2200 G_CALLBACK(filer_tooltip_draw), tip_widget);
2202 label = gtk_label_new(text);
2203 gtk_misc_set_padding(GTK_MISC(label), 4, 2);
2204 gtk_container_add(GTK_CONTAINER(tip_widget), label);
2205 gtk_widget_show(label);
2206 gtk_widget_realize(tip_widget);
2208 w = tip_widget->allocation.width;
2209 h = tip_widget->allocation.height;
2210 gdk_window_get_pointer(NULL, &x, &py, NULL);
2212 x -= w / 2;
2213 y = py + 12; /* I don't know the pointer height so I use a constant */
2215 /* Now check for screen boundaries */
2216 x = CLAMP(x, 0, screen_width - w);
2217 y = CLAMP(y, 0, screen_height - h);
2219 /* And again test if pointer is over the tooltip window */
2220 if (py >= y && py <= y + h)
2221 y = py - h- 2;
2222 gtk_window_move(GTK_WINDOW(tip_widget), x, y);
2223 gtk_widget_show(tip_widget);
2225 g_signal_connect_swapped(tip_widget, "destroy",
2226 G_CALLBACK(tip_destroyed), NULL);
2227 time(&tip_time);
2230 /* Display a tooltip for 'item' after a while (if item is not NULL).
2231 * Cancel any previous tooltip.
2233 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item)
2235 time_t now;
2237 time(&now);
2239 if (item == tip_item)
2240 return;
2242 if (tip_timeout)
2244 gtk_timeout_remove(tip_timeout);
2245 tip_timeout = 0;
2246 tip_item = NULL;
2248 if (tip_widget)
2250 gtk_widget_destroy(tip_widget);
2251 tip_widget = NULL;
2254 tip_item = item;
2255 if (filer_window && item)
2257 int delay = now - tip_time > 2 ? 1000 : 200;
2259 tip_timeout = gtk_timeout_add(delay,
2260 (GtkFunction) filer_tooltip_activate,
2261 filer_window);
2265 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2267 GtkStateType old_state = filer_window->selection_state;
2269 filer_window->selection_state = normal
2270 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2272 if (old_state != filer_window->selection_state
2273 && filer_window->collection->number_selected)
2274 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
2277 void filer_cancel_thumbnails(FilerWindow *filer_window)
2279 gtk_widget_hide(filer_window->thumb_bar);
2281 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
2282 g_list_free(filer_window->thumb_queue);
2283 filer_window->thumb_queue = NULL;
2284 filer_window->max_thumbs = 0;
2287 /* Generate the next thumb for this window. The collection object is
2288 * unref'd when there is nothing more to do.
2289 * If the collection no longer has a filer window, nothing is done.
2291 static gboolean filer_next_thumb_real(GObject *window)
2293 FilerWindow *filer_window;
2294 gchar *path;
2295 int done, total;
2297 filer_window = g_object_get_data(window, "filer_window");
2299 if (!filer_window)
2301 g_object_unref(window);
2302 return FALSE;
2305 if (!filer_window->thumb_queue)
2307 filer_cancel_thumbnails(filer_window);
2308 g_object_unref(window);
2309 return FALSE;
2312 total = filer_window->max_thumbs;
2313 done = total - g_list_length(filer_window->thumb_queue);
2315 path = (gchar *) filer_window->thumb_queue->data;
2317 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2319 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2320 path);
2321 g_free(path);
2323 gtk_progress_bar_set_fraction(
2324 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2325 done / (float) total);
2327 return FALSE;
2330 /* path is the thumb just loaded, if any.
2331 * collection is unref'd (eventually).
2333 static void filer_next_thumb(GObject *window, const gchar *path)
2335 if (path)
2336 dir_force_update_path(path);
2338 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
2341 static void start_thumb_scanning(FilerWindow *filer_window)
2343 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2344 return; /* Already scanning */
2346 gtk_widget_show_all(filer_window->thumb_bar);
2348 g_object_ref(G_OBJECT(filer_window->window));
2349 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2352 /* Set this image to be loaded some time in the future */
2353 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2355 filer_window->max_thumbs++;
2357 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2358 g_strdup(path));
2360 if (filer_window->scanning)
2361 return; /* Will start when scan ends */
2363 start_thumb_scanning(filer_window);
2366 /* If thumbnail display is on, look through all the items in this directory
2367 * and start creating or updating the thumbnails as needed.
2369 void filer_create_thumbs(FilerWindow *filer_window)
2371 Collection *collection = filer_window->collection;
2372 int i;
2374 if (!filer_window->show_thumbs)
2375 return;
2377 for (i = 0; i < collection->number_of_items; i++)
2379 MaskedPixmap *pixmap;
2380 DirItem *item = (DirItem *) collection->items[i].data;
2381 gchar *path;
2382 gboolean found;
2384 if (item->base_type != TYPE_FILE)
2385 continue;
2387 if (strcmp(item->mime_type->media_type, "image") != 0)
2388 continue;
2390 path = make_path(filer_window->real_path, item->leafname)->str;
2392 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2393 FSCACHE_LOOKUP_ONLY_NEW, &found);
2394 if (pixmap)
2395 g_object_unref(pixmap);
2397 /* If we didn't get an image, it could be because:
2399 * - We're loading the image now. found is TRUE,
2400 * and we'll update the item later.
2401 * - We tried to load the image and failed. found
2402 * is TRUE.
2403 * - We haven't tried loading the image. found is
2404 * FALSE, and we start creating the thumb here.
2406 if (!found)
2407 filer_create_thumb(filer_window, path);
2411 static void filer_options_changed(void)
2413 if (o_short_flag_names.has_changed)
2415 GList *next;
2417 for (next = all_filer_windows; next; next = next->next)
2419 FilerWindow *filer_window = (FilerWindow *) next->data;
2421 filer_set_title(filer_window);