r1353: Various speed improvements. Intelligent sort copes better with adjacent
[rox-filer.git] / ROX-Filer / src / filer.c
blobe4df023c804ba7c58d37de9cad9f760ab4c79df5
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(char *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 int i;
187 char *leafname = item->leafname;
188 int old_w = filer_window->collection->item_width;
189 int old_h = filer_window->collection->item_height;
190 int w, h;
191 CollectionItem *colitem;
193 if (leafname[0] == '.' && filer_window->show_hidden == FALSE)
194 return;
196 i = collection_find_item(filer_window->collection, item,
197 filer_window->sort_fn);
199 if (i < 0)
201 g_warning("Failed to find '%s'\n", item->leafname);
202 return;
205 colitem = &filer_window->collection->items[i];
207 display_update_view(filer_window,
208 (DirItem *) colitem->data,
209 (ViewData *) colitem->view_data,
210 FALSE);
212 calc_size(filer_window, colitem, &w, &h);
213 if (w > old_w || h > old_h)
214 collection_set_item_size(filer_window->collection,
215 MAX(old_w, w),
216 MAX(old_h, h));
218 collection_draw_item(filer_window->collection, i, TRUE);
221 /* Resize the filer window to w x h pixels, plus border (not clamped) */
222 static void filer_window_set_size(FilerWindow *filer_window,
223 int w, int h,
224 gboolean allow_shrink)
226 g_return_if_fail(filer_window != NULL);
228 if (filer_window->scrollbar)
229 w += filer_window->scrollbar->allocation.width;
231 if (o_toolbar.int_value != TOOLBAR_NONE)
232 h += filer_window->toolbar_frame->allocation.height;
233 if (filer_window->message)
234 h += filer_window->message->allocation.height;
236 if (GTK_WIDGET_VISIBLE(filer_window->window))
238 gint x, y;
239 GtkRequisition *req = &filer_window->window->requisition;
241 w = MAX(req->width, w);
242 h = MAX(req->height, h);
243 gdk_window_get_position(filer_window->window->window,
244 &x, &y);
245 if (!allow_shrink)
247 gint old_w, old_h;
249 gdk_drawable_get_size(filer_window->window->window,
250 &old_w, &old_h);
251 w = MAX(w, old_w);
252 h = MAX(h, old_h);
254 if (w == old_w && h == old_h)
255 return;
258 if (x + w > screen_width || y + h > screen_height)
260 if (x + w > screen_width)
261 x = screen_width - w - 4;
262 if (y + h > screen_height)
263 y = screen_height - h - 4;
264 gdk_window_move_resize(filer_window->window->window,
265 x, y, w, h);
267 else
268 gdk_window_resize(filer_window->window->window, w, h);
270 else
271 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
272 w, h);
275 /* Resize the window to fit the items currently in the Directory.
276 * This should be used once the Directory has been fully scanned, otherwise
277 * the window will appear too small. When opening a directory for the first
278 * time, the names will be known but not the types and images. We can
279 * still make a good estimate of the size.
281 void filer_window_autosize(FilerWindow *filer_window, gboolean allow_shrink)
283 Collection *collection = filer_window->collection;
284 int n;
286 n = collection->number_of_items;
287 n = MAX(n, 2);
289 filer_size_for(filer_window,
290 collection->item_width,
291 collection->item_height,
292 n, allow_shrink);
295 /* Choose a good size for this window, assuming n items of size (w, h) */
296 static void filer_size_for(FilerWindow *filer_window,
297 int w, int h, int n, gboolean allow_shrink)
299 int x;
300 int rows, cols;
301 int max_x, max_rows;
302 const float r = 2.5;
303 int t = 0;
304 int size_limit;
305 int space = 0;
307 size_limit = o_filer_size_limit.int_value;
309 /* Get the extra height required for the toolbar and minibuffer,
310 * if visible.
312 if (o_toolbar.int_value != TOOLBAR_NONE)
313 t = filer_window->toolbar_frame->allocation.height;
314 if (filer_window->message)
315 t += filer_window->message->allocation.height;
316 if (GTK_WIDGET_VISIBLE(filer_window->minibuffer_area))
318 GtkRequisition req;
320 gtk_widget_size_request(filer_window->minibuffer_area, &req);
321 space = req.height + 2;
322 t += space;
325 max_x = (size_limit * screen_width) / 100;
326 max_rows = (size_limit * screen_height) / (h * 100);
328 /* Aim for a size where
329 * x = r(y + t + h), (1)
330 * unless that's too wide.
332 * t = toolbar (and minibuffer) height
333 * r = desired (width / height) ratio
335 * Want to display all items:
336 * (x/w)(y/h) = n
337 * => xy = nwh
338 * => x(x/r - t - h) = nwh (from 1)
339 * => xx - x.rt - hr(1 - nw) = 0
340 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
341 * Now,
342 * 4hr(nw - 1) > 0
343 * so
344 * sqrt(rt.rt + ...) > rt
346 * So, the +/- must be +:
348 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
350 * ( + w - 1 to round up)
352 x = (r * t + sqrt(r*r*t*t + 4*h*r * (n*w - 1))) / 2 + w - 1;
354 /* Limit x */
355 if (x > max_x)
356 x = max_x;
358 cols = x / w;
359 cols = MAX(cols, 1);
361 /* Choose rows to display all items given our chosen x.
362 * Don't make the window *too* big!
364 rows = (n + cols - 1) / cols;
365 if (rows > max_rows)
366 rows = max_rows;
368 /* Leave some room for extra icons, but only in Small Icons mode
369 * otherwise it takes up too much space.
370 * Also, don't add space if the minibuffer is open.
372 if (space == 0)
373 space = filer_window->display_style == SMALL_ICONS ? h : 2;
375 filer_window_set_size(filer_window,
376 w * MAX(cols, 1),
377 h * MAX(rows, 1) + space,
378 allow_shrink);
381 /* Called on a timeout while scanning or when scanning ends
382 * (whichever happens first).
384 static gint open_filer_window(FilerWindow *filer_window)
386 shrink_grid(filer_window);
388 if (filer_window->open_timeout)
390 gtk_timeout_remove(filer_window->open_timeout);
391 filer_window->open_timeout = 0;
394 if (!GTK_WIDGET_VISIBLE(filer_window->window))
396 filer_window_autosize(filer_window, TRUE);
397 gtk_widget_show(filer_window->window);
400 return FALSE;
403 static void update_display(Directory *dir,
404 DirAction action,
405 GPtrArray *items,
406 FilerWindow *filer_window)
408 int old_num;
409 int i;
410 Collection *collection = filer_window->collection;
412 switch (action)
414 case DIR_ADD:
415 old_num = collection->number_of_items;
416 for (i = 0; i < items->len; i++)
418 DirItem *item = (DirItem *) items->pdata[i];
420 add_item(filer_window, item);
423 if (old_num != collection->number_of_items)
424 collection_qsort(filer_window->collection,
425 filer_window->sort_fn);
427 /* Open and resize if currently hidden */
428 open_filer_window(filer_window);
429 break;
430 case DIR_REMOVE:
431 collection_delete_if(filer_window->collection,
432 if_deleted,
433 items);
434 break;
435 case DIR_START_SCAN:
436 set_scanning_display(filer_window, TRUE);
437 toolbar_update_info(filer_window);
438 break;
439 case DIR_END_SCAN:
440 if (filer_window->window->window)
441 gdk_window_set_cursor(
442 filer_window->window->window,
443 NULL);
444 set_scanning_display(filer_window, FALSE);
445 toolbar_update_info(filer_window);
446 open_filer_window(filer_window);
448 if (filer_window->had_cursor &&
449 collection->cursor_item == -1)
451 collection_set_cursor_item(collection, 0);
452 filer_window->had_cursor = FALSE;
454 if (filer_window->auto_select)
455 display_set_autoselect(filer_window,
456 filer_window->auto_select);
457 g_free(filer_window->auto_select);
458 filer_window->auto_select = NULL;
460 filer_create_thumbs(filer_window);
462 if (filer_window->thumb_queue)
463 start_thumb_scanning(filer_window);
464 break;
465 case DIR_UPDATE:
466 collection_qsort(filer_window->collection,
467 filer_window->sort_fn);
468 for (i = 0; i < items->len; i++)
470 DirItem *item = (DirItem *) items->pdata[i];
472 update_item(filer_window, item);
474 break;
478 static void attach(FilerWindow *filer_window)
480 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
481 collection_clear(filer_window->collection);
482 filer_window->scanning = TRUE;
483 dir_attach(filer_window->directory, (DirCallback) update_display,
484 filer_window);
485 filer_set_title(filer_window);
488 static void detach(FilerWindow *filer_window)
490 g_return_if_fail(filer_window->directory != NULL);
492 dir_detach(filer_window->directory,
493 (DirCallback) update_display, filer_window);
494 g_object_unref(filer_window->directory);
495 filer_window->directory = NULL;
498 static void filer_window_destroyed(GtkWidget *widget,
499 FilerWindow *filer_window)
501 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
503 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
505 if (window_with_primary == filer_window)
506 window_with_primary = NULL;
508 if (window_with_focus == filer_window)
510 menu_popdown();
511 window_with_focus = NULL;
514 if (filer_window->directory)
515 detach(filer_window);
517 if (filer_window->open_timeout)
519 gtk_timeout_remove(filer_window->open_timeout);
520 filer_window->open_timeout = 0;
523 if (filer_window->thumb_queue)
525 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
526 g_list_free(filer_window->thumb_queue);
529 filer_tooltip_prime(NULL, NULL);
531 g_free(filer_window->auto_select);
532 g_free(filer_window->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 int old_w = filer_window->collection->item_width;
544 int old_h = filer_window->collection->item_height;
545 int w, h;
546 int i;
548 if (leafname[0] == '.')
550 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
551 || (leafname[1] == '.' && leafname[2] == '\0'))
552 return;
555 i = collection_insert(filer_window->collection,
556 item,
557 display_create_viewdata(filer_window, item));
559 calc_size(filer_window, &filer_window->collection->items[i], &w, &h);
561 if (w > old_w || h > old_h)
562 collection_set_item_size(filer_window->collection,
563 MAX(old_w, w),
564 MAX(old_h, h));
567 /* Returns TRUE iff the directory still exists. */
568 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
570 Directory *dir;
572 g_return_val_if_fail(filer_window != NULL, FALSE);
574 /* We do a fresh lookup (rather than update) because the inode may
575 * have changed.
577 dir = g_fscache_lookup(dir_cache, filer_window->path);
578 if (!dir)
580 if (warning)
581 info_message(_("Directory missing/deleted"));
582 gtk_widget_destroy(filer_window->window);
583 return FALSE;
585 if (dir == filer_window->directory)
586 g_object_unref(dir);
587 else
589 detach(filer_window);
590 filer_window->directory = dir;
591 attach(filer_window);
594 return TRUE;
597 /* The collection widget has lost the primary selection */
598 static gint collection_lose_selection(GtkWidget *widget,
599 GdkEventSelection *event)
601 if (window_with_primary &&
602 window_with_primary->collection == COLLECTION(widget))
604 FilerWindow *filer_window = window_with_primary;
605 window_with_primary = NULL;
606 set_selection_state(filer_window, FALSE);
609 return FALSE;
612 /* Someone wants us to send them the selection */
613 static void selection_get(GtkWidget *widget,
614 GtkSelectionData *selection_data,
615 guint info,
616 guint time,
617 gpointer data)
619 GString *reply, *header;
620 FilerWindow *filer_window;
621 int i;
622 Collection *collection;
624 filer_window = g_object_get_data(G_OBJECT(widget), "filer_window");
626 reply = g_string_new(NULL);
627 header = g_string_new(NULL);
629 switch (info)
631 case TARGET_STRING:
632 g_string_sprintf(header, " %s",
633 make_path(filer_window->path, "")->str);
634 break;
635 case TARGET_URI_LIST:
636 g_string_sprintf(header, " file://%s%s",
637 our_host_name_for_dnd(),
638 make_path(filer_window->path, "")->str);
639 break;
642 collection = filer_window->collection;
643 for (i = 0; i < collection->number_of_items; i++)
645 if (collection->items[i].selected)
647 DirItem *item =
648 (DirItem *) collection->items[i].data;
650 g_string_append(reply, header->str);
651 g_string_append(reply, item->leafname);
654 /* This works, but I don't think I like it... */
655 /* g_string_append_c(reply, ' '); */
657 if (reply->len > 0)
658 gtk_selection_data_set(selection_data, xa_string,
659 8, reply->str + 1, reply->len - 1);
660 else
662 g_warning("Attempt to paste empty selection!");
663 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
666 g_string_free(reply, TRUE);
667 g_string_free(header, TRUE);
670 /* No items are now selected. This might be because another app claimed
671 * the selection or because the user unselected all the items.
673 static void lose_selection(Collection *collection,
674 guint time,
675 gpointer user_data)
677 FilerWindow *filer_window = (FilerWindow *) user_data;
679 if (window_with_primary == filer_window)
681 window_with_primary = NULL;
682 gtk_selection_owner_set(NULL,
683 GDK_SELECTION_PRIMARY,
684 time);
688 static void selection_changed(Collection *collection,
689 gint time,
690 gpointer user_data)
692 FilerWindow *filer_window = (FilerWindow *) user_data;
694 /* Selection has been changed -- try to grab the primary selection
695 * if we don't have it.
697 if (window_with_primary == filer_window)
698 return; /* Already got it */
700 if (!collection->number_selected)
701 return; /* Nothing selected */
703 if (filer_window->temp_item_selected == FALSE &&
704 gtk_selection_owner_set(GTK_WIDGET(collection),
705 GDK_SELECTION_PRIMARY,
706 time))
708 window_with_primary = filer_window;
709 set_selection_state(filer_window, TRUE);
711 else
712 set_selection_state(filer_window, FALSE);
715 /* Open the item (or add it to the shell command minibuffer) */
716 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
718 gboolean shift = (flags & OPEN_SHIFT) != 0;
719 gboolean close_mini = flags & OPEN_FROM_MINI;
720 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
721 GtkWidget *widget;
722 DirItem *item = (DirItem *)
723 filer_window->collection->items[item_number].data;
724 guchar *full_path;
725 gboolean wink = TRUE;
726 Directory *old_dir;
728 widget = filer_window->window;
729 if (filer_window->mini_type == MINI_SHELL)
731 minibuffer_add(filer_window, item->leafname);
732 return;
735 if (!item->image)
736 dir_update_item(filer_window->directory, item->leafname);
738 if (item->base_type == TYPE_DIRECTORY)
740 /* Never close a filer window when opening a directory
741 * (click on a dir or click on an app with shift).
743 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
744 close_window = FALSE;
747 full_path = make_path(filer_window->path, item->leafname)->str;
748 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
749 wink = FALSE;
751 old_dir = filer_window->directory;
752 if (run_diritem(full_path, item,
753 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
754 filer_window,
755 shift))
757 if (old_dir != filer_window->directory)
758 return;
760 if (close_window)
761 gtk_widget_destroy(filer_window->window);
762 else
764 if (wink)
765 collection_wink_item(filer_window->collection,
766 item_number);
767 if (close_mini)
768 minibuffer_hide(filer_window);
773 static gint pointer_in(GtkWidget *widget,
774 GdkEventCrossing *event,
775 FilerWindow *filer_window)
777 may_rescan(filer_window, TRUE);
778 return FALSE;
781 static gint pointer_out(GtkWidget *widget,
782 GdkEventCrossing *event,
783 FilerWindow *filer_window)
785 filer_tooltip_prime(NULL, NULL);
786 return FALSE;
789 /* Move the cursor to the next selected item in direction 'dir'
790 * (+1 or -1).
792 static void next_selected(FilerWindow *filer_window, int dir)
794 Collection *collection = filer_window->collection;
795 int to_check = collection->number_of_items;
796 int item = collection->cursor_item;
798 g_return_if_fail(dir == 1 || dir == -1);
800 if (to_check > 0 && item == -1)
802 /* Cursor not currently on */
803 if (dir == 1)
804 item = 0;
805 else
806 item = collection->number_of_items - 1;
808 if (collection->items[item].selected)
809 goto found;
812 while (--to_check > 0)
814 item += dir;
816 if (item >= collection->number_of_items)
817 item = 0;
818 else if (item < 0)
819 item = collection->number_of_items - 1;
821 if (collection->items[item].selected)
822 goto found;
825 gdk_beep();
826 return;
827 found:
828 collection_set_cursor_item(collection, item);
831 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
833 Collection *collection = filer_window->collection;
834 int item = collection->cursor_item;
835 TargetFunc cb = filer_window->target_cb;
836 gpointer data = filer_window->target_data;
837 OpenFlags flags = OPEN_SAME_WINDOW;
839 filer_target_mode(filer_window, NULL, NULL, NULL);
840 if (item < 0 || item >= collection->number_of_items)
841 return;
843 if (cb)
845 cb(filer_window, item, data);
846 return;
849 if (event->state & GDK_SHIFT_MASK)
850 flags |= OPEN_SHIFT;
852 filer_openitem(filer_window, item, flags);
855 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
856 * changed. If no groups were loaded and there is no file then initialised
857 * groups to an empty document.
858 * Return the node for the 'name' group.
860 static xmlNode *group_find(char *name)
862 xmlNode *node;
863 gchar *path;
865 /* Update the groups, if possible */
866 path = choices_find_path_load("Groups.xml", PROJECT);
867 if (path)
869 XMLwrapper *wrapper;
870 wrapper = xml_cache_load(path);
871 if (wrapper)
873 if (groups)
874 g_object_unref(groups);
875 groups = wrapper;
878 g_free(path);
881 if (!groups)
883 groups = xml_new(NULL);
884 groups->doc = xmlNewDoc("1.0");
886 xmlDocSetRootElement(groups->doc,
887 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
888 return NULL;
891 node = xmlDocGetRootElement(groups->doc);
893 for (node = node->xmlChildrenNode; node; node = node->next)
895 guchar *gid;
897 gid = xmlGetProp(node, "name");
899 if (!gid)
900 continue;
902 if (strcmp(name, gid) != 0)
903 continue;
905 g_free(gid);
907 return node;
910 return NULL;
913 static void group_save(FilerWindow *filer_window, char *name)
915 Collection *collection = filer_window->collection;
916 xmlNode *group;
917 guchar *save_path;
918 int i;
920 group = group_find(name);
921 if (group)
923 xmlUnlinkNode(group);
924 xmlFreeNode(group);
926 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
927 NULL, "group", NULL);
928 xmlSetProp(group, "name", name);
930 xmlNewChild(group, NULL, "directory", filer_window->path);
932 for (i = 0; i < collection->number_of_items; i++)
934 DirItem *item = (DirItem *) collection->items[i].data;
935 gchar *u8_leaf = item->leafname;
937 if (!collection->items[i].selected)
938 continue;
940 xmlNewChild(group, NULL, "item", u8_leaf);
943 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
944 if (save_path)
946 save_xml_file(groups->doc, save_path);
947 g_free(save_path);
951 static void group_restore(FilerWindow *filer_window, char *name)
953 GHashTable *in_group;
954 Collection *collection = filer_window->collection;
955 int j, n;
956 char *path;
957 xmlNode *group, *node;
959 group = group_find(name);
961 if (!group)
963 report_error(_("Group %s is not set. Select some files "
964 "and press Ctrl+%s to set the group. Press %s "
965 "on its own to reselect the files later."),
966 name, name, name);
967 return;
970 node = get_subnode(group, NULL, "directory");
971 g_return_if_fail(node != NULL);
972 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
973 g_return_if_fail(path != NULL);
975 if (strcmp(path, filer_window->path) != 0)
976 filer_change_to(filer_window, path, NULL);
977 g_free(path);
979 /* If an item at the start is selected then we could lose the
980 * primary selection after checking that item and then need to
981 * gain it again at the end. Therefore, if anything is selected
982 * then select the last item until the end of the search.
984 n = collection->number_of_items;
985 if (collection->number_selected)
986 collection_select_item(collection, n - 1);
988 in_group = g_hash_table_new(g_str_hash, g_str_equal);
989 for (node = group->xmlChildrenNode; node; node = node->next)
991 gchar *leaf;
992 if (node->type != XML_ELEMENT_NODE)
993 continue;
994 if (strcmp(node->name, "item") != 0)
995 continue;
997 leaf = xmlNodeListGetString(groups->doc,
998 node->xmlChildrenNode, 1);
999 if (!leaf)
1000 g_warning("Missing leafname!\n");
1001 else
1002 g_hash_table_insert(in_group, leaf, filer_window);
1005 for (j = 0; j < n; j++)
1007 DirItem *item = (DirItem *) collection->items[j].data;
1009 if (g_hash_table_lookup(in_group, item->leafname))
1010 collection_select_item(collection, j);
1011 else
1012 collection_unselect_item(collection, j);
1015 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
1016 g_hash_table_destroy(in_group);
1019 /* Handle keys that can't be bound with the menu */
1020 static gint key_press_event(GtkWidget *widget,
1021 GdkEventKey *event,
1022 FilerWindow *filer_window)
1024 gboolean handled;
1025 guint key = event->keyval;
1026 char group[2] = "1";
1028 window_with_focus = filer_window;
1030 /* Note: Not convinced this is the way Gtk's key system is supposed
1031 * to be used...
1033 if (!filer_keys)
1034 ensure_filer_menu(); /* Gets the keys working... */
1035 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1036 filer_keys);
1037 handled = gtk_accel_groups_activate(G_OBJECT(filer_window->window),
1038 event->keyval, event->state);
1039 if (window_with_focus)
1040 gtk_window_remove_accel_group(GTK_WINDOW(filer_window->window),
1041 filer_keys);
1042 else
1043 return TRUE; /* Window no longer exists */
1044 if (handled)
1045 return TRUE;
1047 switch (key)
1049 case GDK_Escape:
1050 filer_target_mode(filer_window, NULL, NULL, NULL);
1051 return FALSE;
1052 case GDK_Return:
1053 return_pressed(filer_window, event);
1054 break;
1055 case GDK_ISO_Left_Tab:
1056 next_selected(filer_window, -1);
1057 break;
1058 case GDK_Tab:
1059 next_selected(filer_window, 1);
1060 break;
1061 case GDK_BackSpace:
1062 change_to_parent(filer_window);
1063 break;
1064 case GDK_backslash:
1065 filer_tooltip_prime(NULL, NULL);
1066 show_filer_menu(filer_window, (GdkEvent *) event,
1067 filer_window->collection->cursor_item);
1068 break;
1069 default:
1070 if (key >= GDK_0 && key <= GDK_9)
1071 group[0] = key - GDK_0 + '0';
1072 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
1073 group[0] = key - GDK_KP_0 + '0';
1074 else
1075 return FALSE;
1078 if (event->state & GDK_CONTROL_MASK)
1079 group_save(filer_window, group);
1080 else
1081 group_restore(filer_window, group);
1084 return TRUE;
1087 void change_to_parent(FilerWindow *filer_window)
1089 char *copy;
1090 char *slash;
1092 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
1093 return; /* Already in the root */
1095 copy = g_strdup(filer_window->path);
1096 slash = strrchr(copy, '/');
1098 if (slash)
1100 *slash = '\0';
1101 filer_change_to(filer_window,
1102 *copy ? copy : "/",
1103 slash + 1);
1105 else
1106 g_warning("No / in directory path!\n");
1108 g_free(copy);
1112 /* Make filer_window display path. When finished, highlight item 'from', or
1113 * the first item if from is NULL. If there is currently no cursor then
1114 * simply wink 'from' (if not NULL).
1116 void filer_change_to(FilerWindow *filer_window,
1117 const char *path, const char *from)
1119 char *from_dup;
1120 char *real_path;
1121 Directory *new_dir;
1123 g_return_if_fail(filer_window != NULL);
1125 filer_cancel_thumbnails(filer_window);
1127 filer_tooltip_prime(NULL, NULL);
1129 real_path = pathdup(path);
1130 new_dir = g_fscache_lookup(dir_cache, real_path);
1132 if (!new_dir)
1134 delayed_error(_("Directory '%s' is not accessible"),
1135 real_path);
1136 g_free(real_path);
1137 return;
1140 if (o_unique_filer_windows.int_value)
1142 FilerWindow *fw;
1144 fw = find_filer_window(real_path, filer_window);
1145 if (fw)
1146 gtk_widget_destroy(fw->window);
1149 from_dup = from && *from ? g_strdup(from) : NULL;
1151 detach(filer_window);
1152 g_free(filer_window->path);
1153 filer_window->path = real_path;
1155 filer_window->directory = new_dir;
1157 g_free(filer_window->auto_select);
1158 filer_window->had_cursor = filer_window->collection->cursor_item != -1
1159 || filer_window->had_cursor;
1160 filer_window->auto_select = from_dup;
1162 filer_set_title(filer_window);
1163 if (filer_window->window->window)
1164 gdk_window_set_role(filer_window->window->window,
1165 filer_window->path);
1166 collection_set_cursor_item(filer_window->collection, -1);
1168 attach(filer_window);
1169 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1170 filer_window_autosize(filer_window, TRUE);
1172 if (filer_window->mini_type == MINI_PATH)
1173 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1174 filer_window);
1177 void filer_open_parent(FilerWindow *filer_window)
1179 char *copy;
1180 char *slash;
1182 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
1183 return; /* Already in the root */
1185 copy = g_strdup(filer_window->path);
1186 slash = strrchr(copy, '/');
1188 if (slash)
1190 *slash = '\0';
1191 filer_opendir(*copy ? copy : "/", filer_window);
1193 else
1194 g_warning("No / in directory path!\n");
1196 g_free(copy);
1199 /* Returns a list containing the full pathname of every selected item.
1200 * You must g_free() each item in the list.
1202 GList *filer_selected_items(FilerWindow *filer_window)
1204 Collection *collection = filer_window->collection;
1205 GList *retval = NULL;
1206 guchar *dir = filer_window->path;
1207 int i;
1209 for (i = 0; i < collection->number_of_items; i++)
1211 if (collection->items[i].selected)
1213 DirItem *item = (DirItem *) collection->items[i].data;
1215 retval = g_list_prepend(retval,
1216 g_strdup(make_path(dir, item->leafname)->str));
1220 return g_list_reverse(retval);
1223 int selected_item_number(Collection *collection)
1225 int i;
1227 g_return_val_if_fail(collection != NULL, -1);
1228 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1229 g_return_val_if_fail(collection->number_selected == 1, -1);
1231 for (i = 0; i < collection->number_of_items; i++)
1232 if (collection->items[i].selected)
1233 return i;
1235 g_warning("selected_item: number_selected is wrong\n");
1237 return -1;
1240 DirItem *selected_item(Collection *collection)
1242 int item;
1244 item = selected_item_number(collection);
1246 if (item > -1)
1247 return (DirItem *) collection->items[item].data;
1248 return NULL;
1251 /* Append all the URIs in the selection to the string */
1252 static void create_uri_list(FilerWindow *filer_window, GString *string)
1254 Collection *collection = filer_window->collection;
1255 GString *leader;
1256 int i, num_selected;
1258 leader = g_string_new("file://");
1259 g_string_append(leader, our_host_name_for_dnd());
1260 g_string_append(leader, filer_window->path);
1261 if (leader->str[leader->len - 1] != '/')
1262 g_string_append_c(leader, '/');
1264 num_selected = collection->number_selected;
1266 for (i = 0; num_selected > 0; i++)
1268 if (collection->items[i].selected)
1270 DirItem *item = (DirItem *) collection->items[i].data;
1272 g_string_append(string, leader->str);
1273 g_string_append(string, item->leafname);
1274 g_string_append(string, "\r\n");
1275 num_selected--;
1279 g_string_free(leader, TRUE);
1282 /* Creates and shows a new filer window.
1283 * If src_win != NULL then display options can be taken from that source window.
1284 * Returns the new filer window, or NULL on error.
1285 * Note: if unique windows is in use, may return an existing window.
1287 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win)
1289 FilerWindow *filer_window;
1290 char *real_path;
1291 DisplayStyle dstyle;
1292 DetailsType dtype;
1293 FilerWindow *same_dir_window = NULL;
1295 /* Get the real pathname of the directory and copy it */
1296 real_path = pathdup(path);
1298 if (o_unique_filer_windows.int_value)
1299 same_dir_window = find_filer_window(real_path, NULL);
1301 if (same_dir_window)
1303 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1304 return same_dir_window;
1307 filer_window = g_new(FilerWindow, 1);
1308 filer_window->message = NULL;
1309 filer_window->minibuffer = NULL;
1310 filer_window->minibuffer_label = NULL;
1311 filer_window->minibuffer_area = NULL;
1312 filer_window->temp_show_hidden = FALSE;
1313 filer_window->path = real_path;
1314 filer_window->scanning = FALSE;
1315 filer_window->had_cursor = FALSE;
1316 filer_window->auto_select = NULL;
1317 filer_window->toolbar_text = NULL;
1318 filer_window->target_cb = NULL;
1319 filer_window->mini_type = MINI_NONE;
1320 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1322 /* Finds the entry for this directory in the dir cache, creating
1323 * a new one if needed. This does not cause a scan to start,
1324 * so if a new entry is created then it will be empty.
1326 filer_window->directory = g_fscache_lookup(dir_cache,
1327 filer_window->path);
1328 if (!filer_window->directory)
1330 delayed_error(_("Directory '%s' not found."), path);
1331 g_free(filer_window->path);
1332 g_free(filer_window);
1333 return NULL;
1336 filer_window->temp_item_selected = FALSE;
1337 filer_window->flags = (FilerFlags) 0;
1338 filer_window->details_type = DETAILS_SUMMARY;
1339 filer_window->display_style = UNKNOWN_STYLE;
1340 filer_window->thumb_queue = NULL;
1341 filer_window->max_thumbs = 0;
1343 if (src_win && o_display_inherit_options.int_value)
1345 filer_window->sort_fn = src_win->sort_fn;
1346 dstyle = src_win->display_style;
1347 dtype = src_win->details_type;
1348 filer_window->show_hidden = src_win->show_hidden;
1349 filer_window->show_thumbs = src_win->show_thumbs;
1351 else
1353 int i = o_display_sort_by.int_value;
1354 filer_window->sort_fn = i == 0 ? sort_by_name :
1355 i == 1 ? sort_by_type :
1356 i == 2 ? sort_by_date :
1357 sort_by_size;
1359 dstyle = o_display_size.int_value;
1360 dtype = o_display_details.int_value;
1361 filer_window->show_hidden =
1362 o_display_show_hidden.int_value;
1363 filer_window->show_thumbs =
1364 o_display_show_thumbs.int_value;
1367 /* Add all the user-interface elements & realise */
1368 filer_add_widgets(filer_window);
1369 if (src_win)
1370 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1371 GTK_WIN_POS_MOUSE);
1373 /* Connect to all the signal handlers */
1374 filer_add_signals(filer_window);
1376 display_set_layout(filer_window, dstyle, dtype);
1378 /* Open the window after a timeout, or when scanning stops.
1379 * Do this before attaching, because attach() might tell us to
1380 * stop scanning (if a scan isn't needed).
1382 filer_window->open_timeout = gtk_timeout_add(500,
1383 (GtkFunction) open_filer_window,
1384 filer_window);
1386 /* The collection is created empty and then attach() is called, which
1387 * links the filer window to the entry in the directory cache we
1388 * looked up / created above.
1390 * The attach() function will immediately callback to the filer window
1391 * to deliver a list of all known entries in the directory (so,
1392 * collection->number_of_items may be valid after the call to
1393 * attach() returns).
1395 * BUT, if the directory was not in the cache (because it hadn't been
1396 * opened it before) then the cached dir will be empty and nothing gets
1397 * added until a while later when some entries are actually available.
1400 attach(filer_window);
1402 number_of_windows++;
1403 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1405 return filer_window;
1408 /* This adds all the widgets to a new filer window. It is in a separate
1409 * function because filer_opendir() was getting too long...
1411 static void filer_add_widgets(FilerWindow *filer_window)
1413 GtkWidget *hbox, *vbox, *collection;
1415 /* Create the top-level window widget */
1416 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1417 filer_set_title(filer_window);
1419 /* The collection is the area that actually displays the files */
1420 collection = collection_new();
1422 /* This property is cleared when the window is destroyed.
1423 * You can thus ref filer_window->window and use this to see
1424 * if the window no longer exists.
1426 g_object_set_data(G_OBJECT(filer_window->window),
1427 "filer_window", filer_window);
1429 g_object_set_data(G_OBJECT(collection), "filer_window", filer_window);
1430 filer_window->collection = COLLECTION(collection);
1432 filer_window->collection->free_item = display_free_colitem;
1434 /* Scrollbar on the right, everything else on the left */
1435 hbox = gtk_hbox_new(FALSE, 0);
1436 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1438 vbox = gtk_vbox_new(FALSE, 0);
1439 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1441 /* Create a frame for the toolbar, but don't show it unless we actually
1442 * have a toolbar.
1443 * (allows us to change the toolbar later)
1445 filer_window->toolbar_frame = gtk_frame_new(NULL);
1446 gtk_frame_set_shadow_type(GTK_FRAME(filer_window->toolbar_frame),
1447 GTK_SHADOW_OUT);
1448 gtk_box_pack_start(GTK_BOX(vbox),
1449 filer_window->toolbar_frame, FALSE, TRUE, 0);
1451 /* If we want a toolbar, create it and put it in the frame */
1452 if (o_toolbar.int_value != TOOLBAR_NONE)
1454 GtkWidget *toolbar;
1456 toolbar = toolbar_new(filer_window);
1457 gtk_container_add(GTK_CONTAINER(filer_window->toolbar_frame),
1458 toolbar);
1459 gtk_widget_show_all(filer_window->toolbar_frame);
1462 /* If there's a message that should be displayed in each window (eg
1463 * 'Running as root'), add it here.
1465 if (show_user_message)
1467 filer_window->message = gtk_label_new(show_user_message);
1468 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1469 FALSE, TRUE, 0);
1470 gtk_widget_show(filer_window->message);
1473 /* Now add the area for displaying the files.
1474 * The collection is one huge window that goes in a Viewport.
1477 GtkWidget *viewport;
1478 GtkAdjustment *adj;
1480 adj = filer_window->collection->vadj;
1481 viewport = gtk_viewport_new(NULL, adj);
1482 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport),
1483 GTK_SHADOW_NONE);
1484 gtk_container_add(GTK_CONTAINER(viewport), collection);
1485 gtk_widget_show_all(viewport);
1486 gtk_box_pack_start(GTK_BOX(vbox), viewport, TRUE, TRUE, 0);
1487 filer_window->scrollbar = gtk_vscrollbar_new(adj);
1488 gtk_widget_set_size_request(viewport, 4, 4);
1490 gtk_container_set_resize_mode(GTK_CONTAINER(viewport),
1491 GTK_RESIZE_IMMEDIATE);
1494 /* And the minibuffer (hidden to start with) */
1495 create_minibuffer(filer_window);
1496 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1497 FALSE, TRUE, 0);
1499 /* And the thumbnail progress bar (also hidden) */
1501 GtkWidget *cancel;
1503 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1504 gtk_box_pack_start(GTK_BOX(vbox), filer_window->thumb_bar,
1505 FALSE, TRUE, 0);
1507 filer_window->thumb_progress = gtk_progress_bar_new();
1509 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1510 filer_window->thumb_progress, TRUE, TRUE, 0);
1512 cancel = gtk_button_new_with_label(_("Cancel"));
1513 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1514 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1515 cancel, FALSE, TRUE, 0);
1516 g_signal_connect_swapped(cancel, "clicked",
1517 G_CALLBACK(filer_cancel_thumbnails),
1518 filer_window);
1521 /* Put the scrollbar on the left of everything else... */
1522 gtk_box_pack_start(GTK_BOX(hbox),
1523 filer_window->scrollbar, FALSE, TRUE, 0);
1525 gtk_window_set_focus(GTK_WINDOW(filer_window->window), collection);
1527 gtk_widget_show(hbox);
1528 gtk_widget_show(vbox);
1529 gtk_widget_show(filer_window->scrollbar);
1530 gtk_widget_show(collection);
1532 gtk_widget_realize(filer_window->window);
1534 gdk_window_set_role(filer_window->window->window, filer_window->path);
1536 filer_window_set_size(filer_window, 4, 4, TRUE);
1539 static void filer_add_signals(FilerWindow *filer_window)
1541 GtkObject *collection = GTK_OBJECT(filer_window->collection);
1542 GtkTargetEntry target_table[] =
1544 {"text/uri-list", 0, TARGET_URI_LIST},
1545 {"STRING", 0, TARGET_STRING},
1546 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1549 /* Events on the top-level window */
1550 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1551 g_signal_connect(filer_window->window, "enter-notify-event",
1552 G_CALLBACK(pointer_in), filer_window);
1553 g_signal_connect(filer_window->window, "leave-notify-event",
1554 G_CALLBACK(pointer_out), filer_window);
1555 g_signal_connect(filer_window->window, "destroy",
1556 G_CALLBACK(filer_window_destroyed), filer_window);
1558 /* Events on the collection widget */
1559 gtk_widget_set_events(GTK_WIDGET(collection),
1560 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
1561 GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_MASK |
1562 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1564 g_signal_connect(collection, "lose_selection",
1565 G_CALLBACK(lose_selection), filer_window);
1566 g_signal_connect(collection, "selection_changed",
1567 G_CALLBACK(selection_changed), filer_window);
1568 g_signal_connect(collection, "selection_clear_event",
1569 G_CALLBACK(collection_lose_selection), NULL);
1570 g_signal_connect(collection, "selection_get",
1571 G_CALLBACK(selection_get), NULL);
1572 gtk_selection_add_targets(GTK_WIDGET(collection), GDK_SELECTION_PRIMARY,
1573 target_table,
1574 sizeof(target_table) / sizeof(*target_table));
1576 g_signal_connect(collection, "key_press_event",
1577 G_CALLBACK(key_press_event), filer_window);
1578 g_signal_connect(collection, "button-release-event",
1579 G_CALLBACK(coll_button_release), filer_window);
1580 g_signal_connect(collection, "button-press-event",
1581 G_CALLBACK(coll_button_press), filer_window);
1582 g_signal_connect(collection, "motion-notify-event",
1583 G_CALLBACK(coll_motion_notify), filer_window);
1585 /* Drag and drop events */
1586 g_signal_connect(collection, "drag_data_get",
1587 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1588 drag_set_dest(filer_window);
1591 static gint clear_scanning_display(FilerWindow *filer_window)
1593 if (filer_exists(filer_window))
1594 filer_set_title(filer_window);
1595 return FALSE;
1598 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1600 if (scanning == filer_window->scanning)
1601 return;
1602 filer_window->scanning = scanning;
1604 if (scanning)
1605 filer_set_title(filer_window);
1606 else
1607 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1608 filer_window);
1611 /* Note that filer_window may not exist after this call. */
1612 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1614 if (may_rescan(filer_window, warning))
1615 dir_update(filer_window->directory, filer_window->path);
1618 void filer_update_all(void)
1620 GList *next = all_filer_windows;
1622 while (next)
1624 FilerWindow *filer_window = (FilerWindow *) next->data;
1626 next = next->next;
1628 filer_update_dir(filer_window, TRUE);
1632 /* Refresh the various caches even if we don't think we need to */
1633 void full_refresh(void)
1635 mount_update(TRUE);
1638 /* See whether a filer window with a given path already exists
1639 * and is different from diff.
1641 static FilerWindow *find_filer_window(char *path, FilerWindow *diff)
1643 GList *next = all_filer_windows;
1645 while (next)
1647 FilerWindow *filer_window = (FilerWindow *) next->data;
1649 if (filer_window != diff &&
1650 strcmp(path, filer_window->path) == 0)
1652 return filer_window;
1655 next = next->next;
1658 return NULL;
1661 /* This path has been mounted/umounted/deleted some files - update all dirs */
1662 void filer_check_mounted(const char *path)
1664 GList *next = all_filer_windows;
1665 char *slash;
1666 int len;
1668 len = strlen(path);
1670 while (next)
1672 FilerWindow *filer_window = (FilerWindow *) next->data;
1674 next = next->next;
1676 if (strncmp(path, filer_window->path, len) == 0)
1678 char s = filer_window->path[len];
1680 if (s == '/' || s == '\0')
1681 filer_update_dir(filer_window, FALSE);
1685 slash = strrchr(path, '/');
1686 if (slash && slash != path)
1688 guchar *parent;
1690 parent = g_strndup(path, slash - path);
1692 refresh_dirs(parent);
1694 g_free(parent);
1696 else
1697 refresh_dirs("/");
1699 icons_may_update(path);
1702 /* Close all windows displaying 'path' or subdirectories of 'path' */
1703 void filer_close_recursive(const char *path)
1705 GList *next = all_filer_windows;
1706 int len;
1708 len = strlen(path);
1710 while (next)
1712 FilerWindow *filer_window = (FilerWindow *) next->data;
1714 next = next->next;
1716 if (strncmp(path, filer_window->path, len) == 0)
1718 char s = filer_window->path[len];
1720 if (len == 1 || s == '/' || s == '\0')
1721 gtk_widget_destroy(filer_window->window);
1726 /* Like minibuffer_show(), except that:
1727 * - It returns FALSE (to be used from an idle callback)
1728 * - It checks that the filer window still exists.
1730 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1732 if (filer_exists(filer_window))
1733 minibuffer_show(filer_window, MINI_PATH);
1734 return FALSE;
1737 /* TRUE iff filer_window points to an existing FilerWindow
1738 * structure.
1740 gboolean filer_exists(FilerWindow *filer_window)
1742 GList *next;
1744 for (next = all_filer_windows; next; next = next->next)
1746 FilerWindow *fw = (FilerWindow *) next->data;
1748 if (fw == filer_window)
1749 return TRUE;
1752 return FALSE;
1755 /* Make sure the window title is up-to-date */
1756 void filer_set_title(FilerWindow *filer_window)
1758 guchar *title = NULL;
1759 guchar *flags = "";
1761 if (filer_window->scanning || filer_window->show_hidden ||
1762 filer_window->show_thumbs)
1764 if (o_short_flag_names.int_value)
1766 flags = g_strconcat(" +",
1767 filer_window->scanning ? _("S") : "",
1768 filer_window->show_hidden ? _("A") : "",
1769 filer_window->show_thumbs ? _("T") : "",
1770 NULL);
1772 else
1774 flags = g_strconcat(" (",
1775 filer_window->scanning ? _("Scanning, ") : "",
1776 filer_window->show_hidden ? _("All, ") : "",
1777 filer_window->show_thumbs ? _("Thumbs, ") : "",
1778 NULL);
1779 flags[strlen(flags) - 2] = ')';
1783 if (not_local)
1785 title = g_strconcat("//", our_host_name(),
1786 filer_window->path, flags, NULL);
1789 if (!title && home_dir_len > 1 &&
1790 strncmp(filer_window->path, home_dir, home_dir_len) == 0)
1792 guchar sep = filer_window->path[home_dir_len];
1794 if (sep == '\0' || sep == '/')
1795 title = g_strconcat("~",
1796 filer_window->path + home_dir_len,
1797 flags,
1798 NULL);
1801 if (!title)
1802 title = g_strconcat(filer_window->path, flags, NULL);
1804 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1805 g_free(title);
1807 if (flags[0] != '\0')
1808 g_free(flags);
1811 /* Reconnect to the same directory (used when the Show Hidden option is
1812 * toggled).
1814 void filer_detach_rescan(FilerWindow *filer_window)
1816 Directory *dir = filer_window->directory;
1818 g_object_ref(dir);
1819 detach(filer_window);
1820 filer_window->directory = dir;
1821 attach(filer_window);
1824 static gint coll_button_release(GtkWidget *widget,
1825 GdkEventButton *event,
1826 FilerWindow *filer_window)
1828 if (dnd_motion_release(event))
1830 if (motion_buttons_pressed == 0 &&
1831 filer_window->collection->lasso_box)
1833 collection_end_lasso(filer_window->collection,
1834 event->button == 1 ? GDK_SET : GDK_INVERT);
1836 return FALSE;
1839 perform_action(filer_window, event);
1841 return FALSE;
1844 static void perform_action(FilerWindow *filer_window, GdkEventButton *event)
1846 Collection *collection = filer_window->collection;
1847 DirItem *dir_item;
1848 int item;
1849 BindAction action;
1850 gboolean press = event->type == GDK_BUTTON_PRESS;
1851 gboolean selected = FALSE;
1852 OpenFlags flags = 0;
1854 if (event->button > 3)
1855 return;
1857 item = collection_get_item(collection, event->x, event->y);
1859 if (item != -1 && event->button == 1 &&
1860 collection->items[item].selected &&
1861 filer_window->selection_state == GTK_STATE_INSENSITIVE)
1863 selection_changed(filer_window->collection,
1864 event->time, filer_window);
1865 return;
1868 if (filer_window->target_cb)
1870 dnd_motion_ungrab();
1871 if (item != -1 && press && event->button == 1)
1872 filer_window->target_cb(filer_window, item,
1873 filer_window->target_data);
1874 filer_target_mode(filer_window, NULL, NULL, NULL);
1876 return;
1879 action = bind_lookup_bev(
1880 item == -1 ? BIND_DIRECTORY : BIND_DIRECTORY_ICON,
1881 event);
1883 if (item != -1)
1885 dir_item = (DirItem *) collection->items[item].data;
1886 selected = collection->items[item].selected;
1888 else
1889 dir_item = NULL;
1891 switch (action)
1893 case ACT_CLEAR_SELECTION:
1894 collection_clear_selection(collection);
1895 break;
1896 case ACT_TOGGLE_SELECTED:
1897 collection_toggle_item(collection, item);
1898 break;
1899 case ACT_SELECT_EXCL:
1900 collection_clear_except(collection, item);
1901 break;
1902 case ACT_EDIT_ITEM:
1903 flags |= OPEN_SHIFT;
1904 /* (no break) */
1905 case ACT_OPEN_ITEM:
1906 if (event->button != 1)
1907 flags |= OPEN_CLOSE_WINDOW;
1908 else
1909 flags |= OPEN_SAME_WINDOW;
1910 if (o_new_button_1.int_value)
1911 flags ^= OPEN_SAME_WINDOW;
1912 if (event->type == GDK_2BUTTON_PRESS)
1913 collection_unselect_item(collection, item);
1914 dnd_motion_ungrab();
1915 filer_openitem(filer_window, item, flags);
1916 break;
1917 case ACT_POPUP_MENU:
1918 dnd_motion_ungrab();
1919 filer_tooltip_prime(NULL, NULL);
1920 show_filer_menu(filer_window, (GdkEvent *) event, item);
1921 break;
1922 case ACT_PRIME_AND_SELECT:
1923 if (!selected)
1924 collection_clear_except(collection, item);
1925 dnd_motion_start(MOTION_READY_FOR_DND);
1926 break;
1927 case ACT_PRIME_AND_TOGGLE:
1928 collection_toggle_item(collection, item);
1929 dnd_motion_start(MOTION_READY_FOR_DND);
1930 break;
1931 case ACT_PRIME_FOR_DND:
1932 dnd_motion_start(MOTION_READY_FOR_DND);
1933 break;
1934 case ACT_IGNORE:
1935 if (press && event->button < 4)
1937 if (item)
1938 collection_wink_item(collection, item);
1939 dnd_motion_start(MOTION_NONE);
1941 break;
1942 case ACT_LASSO_CLEAR:
1943 collection_clear_selection(collection);
1944 /* (no break) */
1945 case ACT_LASSO_MODIFY:
1946 collection_lasso_box(collection, event->x, event->y);
1947 break;
1948 case ACT_RESIZE:
1949 filer_window_autosize(filer_window, TRUE);
1950 break;
1951 default:
1952 g_warning("Unsupported action : %d\n", action);
1953 break;
1957 static gint coll_button_press(GtkWidget *widget,
1958 GdkEventButton *event,
1959 FilerWindow *filer_window)
1961 collection_set_cursor_item(filer_window->collection, -1);
1963 if (dnd_motion_press(widget, event))
1964 perform_action(filer_window, event);
1966 return FALSE;
1969 static gint coll_motion_notify(GtkWidget *widget,
1970 GdkEventMotion *event,
1971 FilerWindow *filer_window)
1973 Collection *collection = filer_window->collection;
1974 int i;
1976 i = collection_get_item(collection, event->x, event->y);
1978 if (i == -1)
1979 filer_tooltip_prime(NULL, NULL);
1980 else
1981 filer_tooltip_prime(filer_window,
1982 (DirItem *) collection->items[i].data);
1984 if (motion_state != MOTION_READY_FOR_DND)
1985 return FALSE;
1987 if (!dnd_motion_moved(event))
1988 return FALSE;
1990 i = collection_get_item(collection,
1991 event->x - (event->x_root - drag_start_x),
1992 event->y - (event->y_root - drag_start_y));
1993 if (i == -1)
1994 return FALSE;
1996 collection_wink_item(collection, -1);
1998 if (!collection->items[i].selected)
2000 if (event->state & GDK_BUTTON1_MASK)
2002 /* Select just this one */
2003 filer_window->temp_item_selected = TRUE;
2004 collection_clear_except(collection, i);
2006 else
2008 if (collection->number_selected == 0)
2009 filer_window->temp_item_selected = TRUE;
2010 collection_select_item(collection, i);
2014 g_return_val_if_fail(collection->number_selected > 0, TRUE);
2016 if (collection->number_selected == 1)
2018 DirItem *item = (DirItem *) collection->items[i].data;
2019 ViewData *view = (ViewData *) collection->items[i].view_data;
2021 if (!item->image)
2022 item = dir_update_item(filer_window->directory,
2023 item->leafname);
2025 if (!item)
2027 report_error(_("Item no longer exists!"));
2028 return FALSE;
2031 drag_one_item(widget, event,
2032 make_path(filer_window->path, item->leafname)->str,
2033 item, view ? view->image : NULL);
2035 else
2037 GString *uris;
2039 uris = g_string_new(NULL);
2040 create_uri_list(filer_window, uris);
2041 drag_selection(widget, event, uris->str);
2042 g_string_free(uris, TRUE);
2045 return FALSE;
2048 /* Puts the filer window into target mode. When an item is chosen,
2049 * fn(filer_window, item, data) is called. 'reason' will be displayed
2050 * on the toolbar while target mode is active.
2052 * Use fn == NULL to cancel target mode.
2054 void filer_target_mode(FilerWindow *filer_window,
2055 TargetFunc fn,
2056 gpointer data,
2057 const char *reason)
2059 TargetFunc old_fn = filer_window->target_cb;
2061 if (fn != old_fn)
2062 gdk_window_set_cursor(
2063 GTK_WIDGET(filer_window->collection)->window,
2064 fn ? crosshair : NULL);
2066 filer_window->target_cb = fn;
2067 filer_window->target_data = data;
2069 if (filer_window->toolbar_text == NULL)
2070 return;
2072 if (fn)
2073 gtk_label_set_text(
2074 GTK_LABEL(filer_window->toolbar_text), reason);
2075 else if (o_toolbar_info.int_value)
2077 if (old_fn)
2078 toolbar_update_info(filer_window);
2080 else
2081 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
2085 /* Draw the black border */
2086 static gint filer_tooltip_draw(GtkWidget *w)
2088 gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
2089 w->allocation.width - 1, w->allocation.height - 1);
2091 return FALSE;
2094 /* When the tips window closed, record the time. If we try to open another
2095 * tip soon, it will appear more quickly.
2097 static void tip_destroyed(gpointer data)
2099 time(&tip_time);
2102 /* It's time to make the tooltip appear. If we're not over the item any
2103 * more, or the item doesn't need a tooltip, do nothing.
2105 static gboolean filer_tooltip_activate(FilerWindow *filer_window)
2107 Collection *collection;
2108 gint x, y;
2109 int i;
2110 GString *tip = NULL;
2111 guchar *fullpath = NULL;
2113 g_return_val_if_fail(tip_item != NULL, 0);
2115 tip_timeout = 0;
2117 show_tooltip(NULL);
2119 if (!filer_exists(filer_window))
2120 return FALSE;
2122 collection = filer_window->collection;
2123 gdk_window_get_pointer(GTK_WIDGET(collection)->window, &x, &y, NULL);
2124 i = collection_get_item(filer_window->collection, x, y);
2125 if (i == -1 || ((DirItem *) collection->items[i].data) != tip_item)
2126 return FALSE; /* Not still under the pointer */
2128 /* OK, the filer window still exists and the pointer is still
2129 * over the same item. Do we need to show a tip?
2132 tip = g_string_new(NULL);
2134 if (display_is_truncated(filer_window, i))
2136 g_string_append(tip, tip_item->leafname);
2137 g_string_append_c(tip, '\n');
2140 fullpath = make_path(filer_window->path, tip_item->leafname)->str;
2142 if (tip_item->flags & ITEM_FLAG_SYMLINK)
2144 char *target;
2146 target = readlink_dup(fullpath);
2147 if (target)
2149 g_string_append(tip, _("Symbolic link to "));
2150 g_string_append(tip, target);
2151 g_string_append_c(tip, '\n');
2152 g_free(target);
2156 if (tip_item->flags & ITEM_FLAG_APPDIR)
2158 XMLwrapper *info;
2159 xmlNode *node;
2161 info = appinfo_get(fullpath, tip_item);
2162 if (info && ((node = appinfo_get_section(info, "Summary"))))
2164 guchar *str;
2165 str = xmlNodeListGetString(node->doc,
2166 node->xmlChildrenNode, 1);
2167 if (str)
2169 g_string_append(tip, str);
2170 g_string_append_c(tip, '\n');
2171 g_free(str);
2174 if (info)
2175 g_object_unref(info);
2178 if (!g_utf8_validate(tip_item->leafname, -1, NULL))
2179 g_string_append(tip,
2180 _("This filename is not valid UTF-8. "
2181 "You should rename it.\n"));
2183 if (tip->len > 1)
2185 g_string_truncate(tip, tip->len - 1);
2186 show_tooltip(tip->str);
2189 g_string_free(tip, TRUE);
2191 return FALSE;
2194 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
2195 * NULL, close any current tooltip.
2197 static void show_tooltip(guchar *text)
2199 GtkWidget *label;
2200 int x, y, py;
2201 int w, h;
2203 if (tip_widget)
2205 gtk_widget_destroy(tip_widget);
2206 tip_widget = NULL;
2209 if (!text)
2210 return;
2212 /* Show the tip */
2213 tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
2214 gtk_widget_set_app_paintable(tip_widget, TRUE);
2215 gtk_widget_set_name(tip_widget, "gtk-tooltips");
2217 g_signal_connect_swapped(tip_widget, "expose_event",
2218 G_CALLBACK(filer_tooltip_draw), tip_widget);
2220 label = gtk_label_new(text);
2221 gtk_misc_set_padding(GTK_MISC(label), 4, 2);
2222 gtk_container_add(GTK_CONTAINER(tip_widget), label);
2223 gtk_widget_show(label);
2224 gtk_widget_realize(tip_widget);
2226 w = tip_widget->allocation.width;
2227 h = tip_widget->allocation.height;
2228 gdk_window_get_pointer(NULL, &x, &py, NULL);
2230 x -= w / 2;
2231 y = py + 12; /* I don't know the pointer height so I use a constant */
2233 /* Now check for screen boundaries */
2234 x = CLAMP(x, 0, screen_width - w);
2235 y = CLAMP(y, 0, screen_height - h);
2237 /* And again test if pointer is over the tooltip window */
2238 if (py >= y && py <= y + h)
2239 y = py - h- 2;
2240 gtk_window_move(GTK_WINDOW(tip_widget), x, y);
2241 gtk_widget_show(tip_widget);
2243 g_signal_connect_swapped(tip_widget, "destroy",
2244 G_CALLBACK(tip_destroyed), NULL);
2245 time(&tip_time);
2248 /* Display a tooltip for 'item' after a while (if item is not NULL).
2249 * Cancel any previous tooltip.
2251 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item)
2253 time_t now;
2255 time(&now);
2257 if (item == tip_item)
2258 return;
2260 if (tip_timeout)
2262 gtk_timeout_remove(tip_timeout);
2263 tip_timeout = 0;
2264 tip_item = NULL;
2266 if (tip_widget)
2268 gtk_widget_destroy(tip_widget);
2269 tip_widget = NULL;
2272 tip_item = item;
2273 if (filer_window && item)
2275 int delay = now - tip_time > 2 ? 1000 : 200;
2277 tip_timeout = gtk_timeout_add(delay,
2278 (GtkFunction) filer_tooltip_activate,
2279 filer_window);
2283 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
2285 GtkStateType old_state;
2287 old_state = filer_window->selection_state;
2288 filer_window->selection_state = normal
2289 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
2291 if (old_state != filer_window->selection_state
2292 && filer_window->collection->number_selected)
2293 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
2296 void filer_cancel_thumbnails(FilerWindow *filer_window)
2298 gtk_widget_hide(filer_window->thumb_bar);
2300 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
2301 g_list_free(filer_window->thumb_queue);
2302 filer_window->thumb_queue = NULL;
2303 filer_window->max_thumbs = 0;
2306 /* Generate the next thumb for this window. The collection object is
2307 * unref'd when there is nothing more to do.
2308 * If the collection no longer has a filer window, nothing is done.
2310 static gboolean filer_next_thumb_real(GObject *window)
2312 FilerWindow *filer_window;
2313 gchar *path;
2314 int done, total;
2316 filer_window = g_object_get_data(window, "filer_window");
2318 if (!filer_window)
2320 g_object_unref(window);
2321 return FALSE;
2324 if (!filer_window->thumb_queue)
2326 filer_cancel_thumbnails(filer_window);
2327 g_object_unref(window);
2328 return FALSE;
2331 total = filer_window->max_thumbs;
2332 done = total - g_list_length(filer_window->thumb_queue);
2334 path = (gchar *) filer_window->thumb_queue->data;
2336 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
2338 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
2339 path);
2340 g_free(path);
2342 gtk_progress_bar_set_fraction(
2343 GTK_PROGRESS_BAR(filer_window->thumb_progress),
2344 done / (float) total);
2346 return FALSE;
2349 /* path is the thumb just loaded, if any.
2350 * collection is unref'd (eventually).
2352 static void filer_next_thumb(GObject *window, const gchar *path)
2354 if (path)
2355 dir_force_update_path(path);
2357 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
2360 static void start_thumb_scanning(FilerWindow *filer_window)
2362 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
2363 return; /* Already scanning */
2365 gtk_widget_show_all(filer_window->thumb_bar);
2367 g_object_ref(G_OBJECT(filer_window->window));
2368 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
2371 /* Set this image to be loaded some time in the future */
2372 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
2374 filer_window->max_thumbs++;
2376 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
2377 g_strdup(path));
2379 if (filer_window->scanning)
2380 return; /* Will start when scan ends */
2382 start_thumb_scanning(filer_window);
2385 /* If thumbnail display is on, look through all the items in this directory
2386 * and start creating or updating the thumbnails as needed.
2388 void filer_create_thumbs(FilerWindow *filer_window)
2390 Collection *collection = filer_window->collection;
2391 int i;
2393 if (!filer_window->show_thumbs)
2394 return;
2396 for (i = 0; i < collection->number_of_items; i++)
2398 MaskedPixmap *pixmap;
2399 DirItem *item = (DirItem *) collection->items[i].data;
2400 gchar *path;
2401 gboolean found;
2403 if (item->base_type != TYPE_FILE)
2404 continue;
2406 if (strcmp(item->mime_type->media_type, "image") != 0)
2407 continue;
2409 path = make_path(filer_window->path, item->leafname)->str;
2411 pixmap = g_fscache_lookup_full(pixmap_cache, path,
2412 FSCACHE_LOOKUP_ONLY_NEW, &found);
2413 if (pixmap)
2414 g_object_unref(pixmap);
2416 /* If we didn't get an image, it could be because:
2418 * - We're loading the image now. found is TRUE,
2419 * and we'll update the item later.
2420 * - We tried to load the image and failed. found
2421 * is TRUE.
2422 * - We haven't tried loading the image. found is
2423 * FALSE, and we start creating the thumb here.
2425 if (!found)
2426 filer_create_thumb(filer_window, path);
2430 static void filer_options_changed(void)
2432 if (o_short_flag_names.has_changed)
2434 GList *next;
2436 for (next = all_filer_windows; next; next = next->next)
2438 FilerWindow *filer_window = (FilerWindow *) next->data;
2440 filer_set_title(filer_window);