r1604: Added reminder about having NumLock on when using groups from the keypad
[rox-filer.git] / ROX-Filer / src / filer.c
blobd213d231989b69f353a6a53072ba88406f1d6552
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 <ctype.h>
31 #include <math.h>
32 #include <netdb.h>
33 #include <sys/param.h>
35 #include <gtk/gtk.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdkkeysyms.h>
39 #include "global.h"
41 #include "collection.h"
42 #include "display.h"
43 #include "main.h"
44 #include "fscache.h"
45 #include "support.h"
46 #include "gui_support.h"
47 #include "filer.h"
48 #include "choices.h"
49 #include "pixmaps.h"
50 #include "menu.h"
51 #include "dnd.h"
52 #include "dir.h"
53 #include "diritem.h"
54 #include "run.h"
55 #include "type.h"
56 #include "options.h"
57 #include "minibuffer.h"
58 #include "icon.h"
59 #include "toolbar.h"
60 #include "bind.h"
61 #include "appinfo.h"
62 #include "mount.h"
63 #include "xml.h"
64 #include "view_iface.h"
65 #include "view_collection.h"
67 static XMLwrapper *groups = NULL;
69 /* This is rather badly named. It's actually the filer window which received
70 * the last key press or Menu click event.
72 FilerWindow *window_with_focus = NULL;
74 GList *all_filer_windows = NULL;
76 static FilerWindow *window_with_primary = NULL;
78 /* Static prototypes */
79 static void attach(FilerWindow *filer_window);
80 static void detach(FilerWindow *filer_window);
81 static void filer_window_destroyed(GtkWidget *widget,
82 FilerWindow *filer_window);
83 static void update_display(Directory *dir,
84 DirAction action,
85 GPtrArray *items,
86 FilerWindow *filer_window);
87 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
88 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
89 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
90 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
91 static void filer_add_widgets(FilerWindow *filer_window);
92 static void filer_add_signals(FilerWindow *filer_window);
93 static void filer_size_for(FilerWindow *filer_window,
94 int w, int h, int n, gboolean allow_shrink);
96 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
97 static void filer_next_thumb(GObject *window, const gchar *path);
98 static void start_thumb_scanning(FilerWindow *filer_window);
99 static void filer_options_changed(void);
100 static void set_style_by_number_of_items(FilerWindow *filer_window);
102 static GdkCursor *busy_cursor = NULL;
103 static GdkCursor *crosshair = NULL;
105 /* Indicates whether the filer's display is different to the machine it
106 * is actually running on.
108 static gboolean not_local = FALSE;
110 static Option o_filer_change_size, o_filer_change_size_num;
111 static Option o_filer_size_limit, o_short_flag_names;
112 Option o_filer_auto_resize, o_unique_filer_windows;
114 void filer_init(void)
116 const gchar *ohost;
117 const gchar *dpy;
118 gchar *dpyhost, *tmp;
120 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
121 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
122 RESIZE_ALWAYS);
123 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
125 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
127 option_add_int(&o_filer_change_size, "filer_change_size", TRUE);
128 option_add_int(&o_filer_change_size_num, "filer_change_size_num", 30);
130 option_add_notify(filer_options_changed);
132 busy_cursor = gdk_cursor_new(GDK_WATCH);
133 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
135 /* Is the display on the local machine, or are we being
136 * run remotely? See filer_set_title().
138 ohost = our_host_name();
139 dpy = gdk_get_display();
140 dpyhost = g_strdup(dpy);
141 tmp = strchr(dpyhost, ':');
142 if (tmp)
143 *tmp = '\0';
145 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
147 /* Try the cannonical name for dpyhost (see our_host_name()
148 * in support.c).
150 struct hostent *ent;
152 ent = gethostbyname(dpyhost);
153 if (!ent || strcmp(ohost, ent->h_name) != 0)
154 not_local = TRUE;
157 g_free(dpyhost);
160 static gboolean if_deleted(gpointer item, gpointer removed)
162 int i = ((GPtrArray *) removed)->len;
163 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
164 char *leafname = ((DirItem *) item)->leafname;
166 while (i--)
168 if (strcmp(leafname, r[i]->leafname) == 0)
169 return TRUE;
172 return FALSE;
175 /* Resize the filer window to w x h pixels, plus border (not clamped) */
176 static void filer_window_set_size(FilerWindow *filer_window,
177 int w, int h,
178 gboolean allow_shrink)
180 GtkWidget *window;
182 g_return_if_fail(filer_window != NULL);
184 if (filer_window->scrollbar)
185 w += filer_window->scrollbar->allocation.width;
187 if (o_toolbar.int_value != TOOLBAR_NONE)
188 h += filer_window->toolbar->allocation.height;
189 if (filer_window->message)
190 h += filer_window->message->allocation.height;
192 window = filer_window->window;
194 if (GTK_WIDGET_VISIBLE(window))
196 gint x, y;
197 GtkRequisition *req = &window->requisition;
198 GdkWindow *gdk_window = window->window;
200 w = MAX(req->width, w);
201 h = MAX(req->height, h);
202 gdk_window_get_position(gdk_window, &x, &y);
204 if (!allow_shrink)
206 gint old_w, old_h;
208 gdk_drawable_get_size(gdk_window, &old_w, &old_h);
209 w = MAX(w, old_w);
210 h = MAX(h, old_h);
212 if (w == old_w && h == old_h)
213 return;
216 if (x + w > screen_width || y + h > screen_height)
218 if (x + w > screen_width)
219 x = screen_width - w - 4;
220 if (y + h > screen_height)
221 y = screen_height - h - 4;
222 gdk_window_move_resize(gdk_window, x, y, w, h);
224 else
225 gdk_window_resize(gdk_window, w, h);
227 else
228 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
231 /* Resize the window to fit the items currently in the Directory.
232 * This should be used once the Directory has been fully scanned, otherwise
233 * the window will appear too small. When opening a directory for the first
234 * time, the names will be known but not the types and images. We can
235 * still make a good estimate of the size.
237 void filer_window_autosize(FilerWindow *filer_window, gboolean allow_shrink)
239 Collection *collection = filer_window->collection;
240 int n;
242 n = collection->number_of_items;
243 n = MAX(n, 2);
245 filer_size_for(filer_window,
246 collection->item_width,
247 collection->item_height,
248 n, allow_shrink);
251 /* Choose a good size for this window, assuming n items of size (w, h) */
252 static void filer_size_for(FilerWindow *filer_window,
253 int w, int h, int n, gboolean allow_shrink)
255 int x;
256 int rows, cols;
257 int max_x, max_rows;
258 const float r = 2.5;
259 int t = 0;
260 int size_limit;
261 int space = 0;
263 size_limit = o_filer_size_limit.int_value;
265 /* Get the extra height required for the toolbar and minibuffer,
266 * if visible.
268 if (o_toolbar.int_value != TOOLBAR_NONE)
269 t = filer_window->toolbar->allocation.height;
270 if (filer_window->message)
271 t += filer_window->message->allocation.height;
272 if (GTK_WIDGET_VISIBLE(filer_window->minibuffer_area))
274 GtkRequisition req;
276 gtk_widget_size_request(filer_window->minibuffer_area, &req);
277 space = req.height + 2;
278 t += space;
281 max_x = (size_limit * screen_width) / 100;
282 max_rows = (size_limit * screen_height) / (h * 100);
284 /* Aim for a size where
285 * x = r(y + t + h), (1)
286 * unless that's too wide.
288 * t = toolbar (and minibuffer) height
289 * r = desired (width / height) ratio
291 * Want to display all items:
292 * (x/w)(y/h) = n
293 * => xy = nwh
294 * => x(x/r - t - h) = nwh (from 1)
295 * => xx - x.rt - hr(1 - nw) = 0
296 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
297 * Now,
298 * 4hr(nw - 1) > 0
299 * so
300 * sqrt(rt.rt + ...) > rt
302 * So, the +/- must be +:
304 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
306 * ( + w - 1 to round up)
308 x = (r * t + sqrt(r*r*t*t + 4*h*r * (n*w - 1))) / 2 + w - 1;
310 /* Limit x */
311 if (x > max_x)
312 x = max_x;
314 cols = x / w;
315 cols = MAX(cols, 1);
317 /* Choose rows to display all items given our chosen x.
318 * Don't make the window *too* big!
320 rows = (n + cols - 1) / cols;
321 if (rows > max_rows)
322 rows = max_rows;
324 /* Leave some room for extra icons, but only in Small Icons mode
325 * otherwise it takes up too much space.
326 * Also, don't add space if the minibuffer is open.
328 if (space == 0)
329 space = filer_window->display_style == SMALL_ICONS ? h : 2;
331 filer_window_set_size(filer_window,
332 w * MAX(cols, 1),
333 h * MAX(rows, 1) + space,
334 allow_shrink);
337 /* Called on a timeout while scanning or when scanning ends
338 * (whichever happens first).
340 static gint open_filer_window(FilerWindow *filer_window)
342 view_style_changed(filer_window->view, 0);
344 if (filer_window->open_timeout)
346 gtk_timeout_remove(filer_window->open_timeout);
347 filer_window->open_timeout = 0;
350 if (!GTK_WIDGET_VISIBLE(filer_window->window))
352 set_style_by_number_of_items(filer_window);
353 filer_window_autosize(filer_window, TRUE);
354 gtk_widget_show(filer_window->window);
357 return FALSE;
360 static void update_display(Directory *dir,
361 DirAction action,
362 GPtrArray *items,
363 FilerWindow *filer_window)
365 Collection *collection = filer_window->collection;
366 ViewIface *view = (ViewIface *) filer_window->view;
368 switch (action)
370 case DIR_ADD:
371 view_add_items(view, items);
372 /* Open and resize if currently hidden */
373 open_filer_window(filer_window);
374 break;
375 case DIR_REMOVE:
376 view_delete_if(view, if_deleted, items);
377 break;
378 case DIR_START_SCAN:
379 set_scanning_display(filer_window, TRUE);
380 toolbar_update_info(filer_window);
381 break;
382 case DIR_END_SCAN:
383 if (filer_window->window->window)
384 gdk_window_set_cursor(
385 filer_window->window->window,
386 NULL);
387 set_scanning_display(filer_window, FALSE);
388 toolbar_update_info(filer_window);
389 open_filer_window(filer_window);
391 if (filer_window->had_cursor &&
392 collection->cursor_item == -1)
394 collection_set_cursor_item(collection, 0);
395 filer_window->had_cursor = FALSE;
397 if (filer_window->auto_select)
398 display_set_autoselect(filer_window,
399 filer_window->auto_select);
400 g_free(filer_window->auto_select);
401 filer_window->auto_select = NULL;
403 filer_create_thumbs(filer_window);
405 if (filer_window->thumb_queue)
406 start_thumb_scanning(filer_window);
407 break;
408 case DIR_UPDATE:
409 view_update_items(view, items);
410 break;
414 static void attach(FilerWindow *filer_window)
416 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
417 view_clear(filer_window->view);
418 filer_window->scanning = TRUE;
419 dir_attach(filer_window->directory, (DirCallback) update_display,
420 filer_window);
421 filer_set_title(filer_window);
424 static void detach(FilerWindow *filer_window)
426 g_return_if_fail(filer_window->directory != NULL);
428 dir_detach(filer_window->directory,
429 (DirCallback) update_display, filer_window);
430 g_object_unref(filer_window->directory);
431 filer_window->directory = NULL;
434 static void filer_window_destroyed(GtkWidget *widget,
435 FilerWindow *filer_window)
437 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
439 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
441 if (window_with_primary == filer_window)
442 window_with_primary = NULL;
444 if (window_with_focus == filer_window)
446 menu_popdown();
447 window_with_focus = NULL;
450 if (filer_window->directory)
451 detach(filer_window);
453 if (filer_window->open_timeout)
455 gtk_timeout_remove(filer_window->open_timeout);
456 filer_window->open_timeout = 0;
459 if (filer_window->thumb_queue)
461 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
462 g_list_free(filer_window->thumb_queue);
465 tooltip_show(NULL);
467 g_free(filer_window->auto_select);
468 g_free(filer_window->real_path);
469 g_free(filer_window->sym_path);
470 g_free(filer_window);
472 one_less_window();
475 /* Returns TRUE iff the directory still exists. */
476 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
478 Directory *dir;
480 g_return_val_if_fail(filer_window != NULL, FALSE);
482 /* We do a fresh lookup (rather than update) because the inode may
483 * have changed.
485 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
486 if (!dir)
488 if (warning)
489 info_message(_("Directory missing/deleted"));
490 gtk_widget_destroy(filer_window->window);
491 return FALSE;
493 if (dir == filer_window->directory)
494 g_object_unref(dir);
495 else
497 detach(filer_window);
498 filer_window->directory = dir;
499 attach(filer_window);
502 return TRUE;
505 /* No items are now selected. This might be because another app claimed
506 * the selection or because the user unselected all the items.
508 void filer_lost_selection(FilerWindow *filer_window, gint time)
510 if (window_with_primary == filer_window)
512 window_with_primary = NULL;
513 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
517 /* Another app has claimed the primary selection */
518 void filer_lost_primary(FilerWindow *filer_window)
520 if (window_with_primary && window_with_primary == filer_window)
522 window_with_primary = NULL;
523 set_selection_state(filer_window, FALSE);
527 /* Someone wants us to send them the selection */
528 static void selection_get(GtkWidget *widget,
529 GtkSelectionData *selection_data,
530 guint info,
531 guint time,
532 gpointer data)
534 GString *reply, *header;
535 FilerWindow *filer_window;
536 ViewIter iter;
537 DirItem *item;
539 filer_window = g_object_get_data(G_OBJECT(widget), "filer_window");
541 reply = g_string_new(NULL);
542 header = g_string_new(NULL);
544 switch (info)
546 case TARGET_STRING:
547 g_string_sprintf(header, " %s",
548 make_path(filer_window->sym_path, "")->str);
549 break;
550 case TARGET_URI_LIST:
551 g_string_sprintf(header, " file://%s%s",
552 our_host_name_for_dnd(),
553 make_path(filer_window->sym_path, "")->str);
554 break;
557 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
559 while ((item = iter.next(&iter)))
561 g_string_append(reply, header->str);
562 g_string_append(reply, item->leafname);
565 if (reply->len > 0)
566 gtk_selection_data_set(selection_data, xa_string,
567 8, reply->str + 1, reply->len - 1);
568 else
570 g_warning("Attempt to paste empty selection!");
571 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
574 g_string_free(reply, TRUE);
575 g_string_free(header, TRUE);
578 void filer_selection_changed(FilerWindow *filer_window, gint time)
580 /* Selection has been changed -- try to grab the primary selection
581 * if we don't have it.
583 if (window_with_primary == filer_window)
584 return; /* Already got it */
586 if (!filer_window->collection->number_selected)
587 return; /* Nothing selected */
589 if (filer_window->temp_item_selected == FALSE &&
590 gtk_selection_owner_set(GTK_WIDGET(filer_window->collection),
591 GDK_SELECTION_PRIMARY,
592 time))
594 window_with_primary = filer_window;
595 set_selection_state(filer_window, TRUE);
597 else
598 set_selection_state(filer_window, FALSE);
601 /* Open the item (or add it to the shell command minibuffer) */
602 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
604 gboolean shift = (flags & OPEN_SHIFT) != 0;
605 gboolean close_mini = flags & OPEN_FROM_MINI;
606 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
607 DirItem *item = (DirItem *)
608 filer_window->collection->items[item_number].data;
609 guchar *full_path;
610 gboolean wink = TRUE;
611 Directory *old_dir;
613 if (filer_window->mini_type == MINI_SHELL)
615 minibuffer_add(filer_window, item->leafname);
616 return;
619 if (!item->image)
620 dir_update_item(filer_window->directory, item->leafname);
622 if (item->base_type == TYPE_DIRECTORY)
624 /* Never close a filer window when opening a directory
625 * (click on a dir or click on an app with shift).
627 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
628 close_window = FALSE;
631 full_path = make_path(filer_window->sym_path, item->leafname)->str;
632 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
633 wink = FALSE;
635 old_dir = filer_window->directory;
636 if (run_diritem(full_path, item,
637 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
638 filer_window,
639 shift))
641 if (old_dir != filer_window->directory)
642 return;
644 if (close_window)
645 gtk_widget_destroy(filer_window->window);
646 else
648 if (wink)
649 collection_wink_item(filer_window->collection,
650 item_number);
651 if (close_mini)
652 minibuffer_hide(filer_window);
657 static gint pointer_in(GtkWidget *widget,
658 GdkEventCrossing *event,
659 FilerWindow *filer_window)
661 may_rescan(filer_window, TRUE);
662 return FALSE;
665 static gint pointer_out(GtkWidget *widget,
666 GdkEventCrossing *event,
667 FilerWindow *filer_window)
669 tooltip_show(NULL);
670 return FALSE;
673 /* Move the cursor to the next selected item in direction 'dir'
674 * (+1 or -1).
676 static void next_selected(FilerWindow *filer_window, int dir)
678 Collection *collection = filer_window->collection;
679 int to_check = collection->number_of_items;
680 int item = collection->cursor_item;
682 g_return_if_fail(dir == 1 || dir == -1);
684 if (to_check > 0 && item == -1)
686 /* Cursor not currently on */
687 if (dir == 1)
688 item = 0;
689 else
690 item = collection->number_of_items - 1;
692 if (collection->items[item].selected)
693 goto found;
696 while (--to_check > 0)
698 item += dir;
700 if (item >= collection->number_of_items)
701 item = 0;
702 else if (item < 0)
703 item = collection->number_of_items - 1;
705 if (collection->items[item].selected)
706 goto found;
709 gdk_beep();
710 return;
711 found:
712 collection_set_cursor_item(collection, item);
715 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
717 Collection *collection = filer_window->collection;
718 int item = collection->cursor_item;
719 TargetFunc cb = filer_window->target_cb;
720 gpointer data = filer_window->target_data;
721 OpenFlags flags = OPEN_SAME_WINDOW;
723 filer_target_mode(filer_window, NULL, NULL, NULL);
724 if (item < 0 || item >= collection->number_of_items)
725 return;
727 if (cb)
729 cb(filer_window, item, data);
730 return;
733 if (event->state & GDK_SHIFT_MASK)
734 flags |= OPEN_SHIFT;
736 filer_openitem(filer_window, item, flags);
739 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
740 * changed. If no groups were loaded and there is no file then initialised
741 * groups to an empty document.
742 * Return the node for the 'name' group.
744 static xmlNode *group_find(char *name)
746 xmlNode *node;
747 gchar *path;
749 /* Update the groups, if possible */
750 path = choices_find_path_load("Groups.xml", PROJECT);
751 if (path)
753 XMLwrapper *wrapper;
754 wrapper = xml_cache_load(path);
755 if (wrapper)
757 if (groups)
758 g_object_unref(groups);
759 groups = wrapper;
762 g_free(path);
765 if (!groups)
767 groups = xml_new(NULL);
768 groups->doc = xmlNewDoc("1.0");
770 xmlDocSetRootElement(groups->doc,
771 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
772 return NULL;
775 node = xmlDocGetRootElement(groups->doc);
777 for (node = node->xmlChildrenNode; node; node = node->next)
779 guchar *gid;
781 gid = xmlGetProp(node, "name");
783 if (!gid)
784 continue;
786 if (strcmp(name, gid) != 0)
787 continue;
789 g_free(gid);
791 return node;
794 return NULL;
797 static void group_save(FilerWindow *filer_window, char *name)
799 xmlNode *group;
800 guchar *save_path;
801 DirItem *item;
802 ViewIter iter;
804 group = group_find(name);
805 if (group)
807 xmlUnlinkNode(group);
808 xmlFreeNode(group);
810 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
811 NULL, "group", NULL);
812 xmlSetProp(group, "name", name);
814 xmlNewChild(group, NULL, "directory", filer_window->sym_path);
816 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
818 while ((item = iter.next(&iter)))
819 xmlNewChild(group, NULL, "item", item->leafname);
821 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
822 if (save_path)
824 save_xml_file(groups->doc, save_path);
825 g_free(save_path);
829 static void group_restore(FilerWindow *filer_window, char *name)
831 GHashTable *in_group;
832 Collection *collection = filer_window->collection;
833 int j, n;
834 char *path;
835 xmlNode *group, *node;
837 group = group_find(name);
839 if (!group)
841 report_error(_("Group %s is not set. Select some files "
842 "and press Ctrl+%s to set the group. Press %s "
843 "on its own to reselect the files later.\n"
844 "Make sure NumLock is on if you use the keypad."),
845 name, name, name);
846 return;
849 node = get_subnode(group, NULL, "directory");
850 g_return_if_fail(node != NULL);
851 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
852 g_return_if_fail(path != NULL);
854 if (strcmp(path, filer_window->sym_path) != 0)
855 filer_change_to(filer_window, path, NULL);
856 g_free(path);
858 /* If an item at the start is selected then we could lose the
859 * primary selection after checking that item and then need to
860 * gain it again at the end. Therefore, if anything is selected
861 * then select the last item until the end of the search.
863 n = collection->number_of_items;
864 if (collection->number_selected)
865 collection_select_item(collection, n - 1);
867 in_group = g_hash_table_new(g_str_hash, g_str_equal);
868 for (node = group->xmlChildrenNode; node; node = node->next)
870 gchar *leaf;
871 if (node->type != XML_ELEMENT_NODE)
872 continue;
873 if (strcmp(node->name, "item") != 0)
874 continue;
876 leaf = xmlNodeListGetString(groups->doc,
877 node->xmlChildrenNode, 1);
878 if (!leaf)
879 g_warning("Missing leafname!\n");
880 else
881 g_hash_table_insert(in_group, leaf, filer_window);
884 for (j = 0; j < n; j++)
886 DirItem *item = (DirItem *) collection->items[j].data;
888 if (g_hash_table_lookup(in_group, item->leafname))
889 collection_select_item(collection, j);
890 else
891 collection_unselect_item(collection, j);
894 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
895 g_hash_table_destroy(in_group);
898 /* Handle keys that can't be bound with the menu */
899 static gint key_press_event(GtkWidget *widget,
900 GdkEventKey *event,
901 FilerWindow *filer_window)
903 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
904 guint key = event->keyval;
905 char group[2] = "1";
907 window_with_focus = filer_window;
909 /* Delay setting up the keys until now to speed loading... */
910 if (!filer_keys)
911 ensure_filer_menu(); /* Gets the keys working... */
913 if (!g_slist_find(filer_keys->acceleratables, widget))
914 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
915 filer_keys);
917 if (focus && focus != widget &&
918 gtk_widget_get_toplevel(focus) == widget)
919 if (gtk_widget_event(focus, (GdkEvent *) event))
920 return TRUE; /* Handled */
922 switch (key)
924 case GDK_Escape:
925 filer_target_mode(filer_window, NULL, NULL, NULL);
926 return FALSE;
927 case GDK_Return:
928 return_pressed(filer_window, event);
929 break;
930 case GDK_ISO_Left_Tab:
931 next_selected(filer_window, -1);
932 break;
933 case GDK_Tab:
934 next_selected(filer_window, 1);
935 break;
936 case GDK_BackSpace:
937 change_to_parent(filer_window);
938 break;
939 case GDK_backslash:
940 tooltip_show(NULL);
941 show_filer_menu(filer_window, (GdkEvent *) event,
942 filer_window->collection->cursor_item);
943 break;
944 default:
945 if (key >= GDK_0 && key <= GDK_9)
946 group[0] = key - GDK_0 + '0';
947 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
948 group[0] = key - GDK_KP_0 + '0';
949 else
950 return FALSE;
953 if (event->state & GDK_CONTROL_MASK)
954 group_save(filer_window, group);
955 else
956 group_restore(filer_window, group);
959 return TRUE;
962 void filer_open_parent(FilerWindow *filer_window)
964 char *dir;
965 const char *current = filer_window->sym_path;
967 if (current[0] == '/' && current[1] == '\0')
968 return; /* Already in the root */
970 dir = g_dirname(current);
971 filer_opendir(dir, filer_window);
972 g_free(dir);
975 void change_to_parent(FilerWindow *filer_window)
977 char *dir;
978 const char *current = filer_window->sym_path;
980 if (current[0] == '/' && current[1] == '\0')
981 return; /* Already in the root */
983 dir = g_dirname(current);
984 filer_change_to(filer_window, dir, g_basename(current));
985 g_free(dir);
988 /* Removes trailing /s from path (modified in place) */
989 static void tidy_sympath(gchar *path)
991 int l;
993 g_return_if_fail(path != NULL);
995 l = strlen(path);
996 while (l > 1 && path[l - 1] == '/')
998 l--;
999 path[l] = '\0';
1003 /* Make filer_window display path. When finished, highlight item 'from', or
1004 * the first item if from is NULL. If there is currently no cursor then
1005 * simply wink 'from' (if not NULL).
1007 void filer_change_to(FilerWindow *filer_window,
1008 const char *path, const char *from)
1010 char *from_dup;
1011 char *sym_path, *real_path;
1012 Directory *new_dir;
1014 g_return_if_fail(filer_window != NULL);
1016 filer_cancel_thumbnails(filer_window);
1018 tooltip_show(NULL);
1020 sym_path = g_strdup(path);
1021 real_path = pathdup(path);
1022 new_dir = g_fscache_lookup(dir_cache, real_path);
1024 if (!new_dir)
1026 delayed_error(_("Directory '%s' is not accessible"),
1027 sym_path);
1028 g_free(real_path);
1029 g_free(sym_path);
1030 return;
1033 if (o_unique_filer_windows.int_value)
1035 FilerWindow *fw;
1037 fw = find_filer_window(sym_path, filer_window);
1038 if (fw)
1039 gtk_widget_destroy(fw->window);
1042 from_dup = from && *from ? g_strdup(from) : NULL;
1044 detach(filer_window);
1045 g_free(filer_window->real_path);
1046 g_free(filer_window->sym_path);
1047 filer_window->real_path = real_path;
1048 filer_window->sym_path = sym_path;
1049 tidy_sympath(filer_window->sym_path);
1051 filer_window->directory = new_dir;
1053 g_free(filer_window->auto_select);
1054 filer_window->had_cursor = filer_window->collection->cursor_item != -1
1055 || filer_window->had_cursor;
1056 filer_window->auto_select = from_dup;
1058 filer_set_title(filer_window);
1059 if (filer_window->window->window)
1060 gdk_window_set_role(filer_window->window->window,
1061 filer_window->sym_path);
1062 collection_set_cursor_item(filer_window->collection, -1);
1064 attach(filer_window);
1066 set_style_by_number_of_items(filer_window);
1068 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1069 filer_window_autosize(filer_window, TRUE);
1071 if (filer_window->mini_type == MINI_PATH)
1072 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1073 filer_window);
1076 /* Returns a list containing the full (sym) pathname of every selected item.
1077 * You must g_free() each item in the list.
1079 GList *filer_selected_items(FilerWindow *filer_window)
1081 GList *retval = NULL;
1082 guchar *dir = filer_window->sym_path;
1083 ViewIter iter;
1084 DirItem *item;
1086 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1087 while ((item = iter.next(&iter)))
1089 retval = g_list_prepend(retval,
1090 g_strdup(make_path(dir, item->leafname)->str));
1093 return g_list_reverse(retval);
1096 DirItem *filer_selected_item(FilerWindow *filer_window)
1098 Collection *collection = filer_window->collection;
1099 int item;
1101 item = collection_selected_item_number(collection);
1103 if (item > -1)
1104 return (DirItem *) collection->items[item].data;
1105 return NULL;
1108 /* Creates and shows a new filer window.
1109 * If src_win != NULL then display options can be taken from that source window.
1110 * Returns the new filer window, or NULL on error.
1111 * Note: if unique windows is in use, may return an existing window.
1113 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win)
1115 FilerWindow *filer_window;
1116 char *real_path;
1117 DisplayStyle dstyle;
1118 DetailsType dtype;
1120 /* Get the real pathname of the directory and copy it */
1121 real_path = pathdup(path);
1123 if (o_unique_filer_windows.int_value)
1125 FilerWindow *same_dir_window;
1127 same_dir_window = find_filer_window(path, NULL);
1129 if (same_dir_window)
1131 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1132 return same_dir_window;
1136 filer_window = g_new(FilerWindow, 1);
1137 filer_window->message = NULL;
1138 filer_window->minibuffer = NULL;
1139 filer_window->minibuffer_label = NULL;
1140 filer_window->minibuffer_area = NULL;
1141 filer_window->temp_show_hidden = FALSE;
1142 filer_window->sym_path = g_strdup(path);
1143 filer_window->real_path = real_path;
1144 filer_window->scanning = FALSE;
1145 filer_window->had_cursor = FALSE;
1146 filer_window->auto_select = NULL;
1147 filer_window->toolbar_text = NULL;
1148 filer_window->target_cb = NULL;
1149 filer_window->mini_type = MINI_NONE;
1150 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1151 filer_window->toolbar = NULL;
1152 filer_window->toplevel_vbox = NULL;
1154 tidy_sympath(filer_window->sym_path);
1156 /* Finds the entry for this directory in the dir cache, creating
1157 * a new one if needed. This does not cause a scan to start,
1158 * so if a new entry is created then it will be empty.
1160 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1161 if (!filer_window->directory)
1163 delayed_error(_("Directory '%s' not found."), path);
1164 g_free(filer_window->real_path);
1165 g_free(filer_window->sym_path);
1166 g_free(filer_window);
1167 return NULL;
1170 filer_window->temp_item_selected = FALSE;
1171 filer_window->flags = (FilerFlags) 0;
1172 filer_window->details_type = DETAILS_SUMMARY;
1173 filer_window->display_style = UNKNOWN_STYLE;
1174 filer_window->thumb_queue = NULL;
1175 filer_window->max_thumbs = 0;
1177 if (src_win && o_display_inherit_options.int_value)
1179 filer_window->sort_fn = src_win->sort_fn;
1180 dstyle = src_win->display_style;
1181 dtype = src_win->details_type;
1182 filer_window->show_hidden = src_win->show_hidden;
1183 filer_window->show_thumbs = src_win->show_thumbs;
1185 else
1187 int i = o_display_sort_by.int_value;
1188 filer_window->sort_fn = i == 0 ? sort_by_name :
1189 i == 1 ? sort_by_type :
1190 i == 2 ? sort_by_date :
1191 sort_by_size;
1193 dstyle = o_display_size.int_value;
1194 dtype = o_display_details.int_value;
1195 filer_window->show_hidden =
1196 o_display_show_hidden.int_value;
1197 filer_window->show_thumbs =
1198 o_display_show_thumbs.int_value;
1201 /* Add all the user-interface elements & realise */
1202 filer_add_widgets(filer_window);
1203 if (src_win)
1204 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1205 GTK_WIN_POS_MOUSE);
1207 /* Connect to all the signal handlers */
1208 filer_add_signals(filer_window);
1210 display_set_layout(filer_window, dstyle, dtype);
1212 /* Open the window after a timeout, or when scanning stops.
1213 * Do this before attaching, because attach() might tell us to
1214 * stop scanning (if a scan isn't needed).
1216 filer_window->open_timeout = gtk_timeout_add(500,
1217 (GtkFunction) open_filer_window,
1218 filer_window);
1220 /* The collection is created empty and then attach() is called, which
1221 * links the filer window to the entry in the directory cache we
1222 * looked up / created above.
1224 * The attach() function will immediately callback to the filer window
1225 * to deliver a list of all known entries in the directory (so,
1226 * collection->number_of_items may be valid after the call to
1227 * attach() returns).
1229 * If the directory was not in the cache (because it hadn't been
1230 * opened it before) then the types and icons for the entries are
1231 * not know, but the list of names is.
1234 attach(filer_window);
1236 number_of_windows++;
1237 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1239 return filer_window;
1242 /* This adds all the widgets to a new filer window. It is in a separate
1243 * function because filer_opendir() was getting too long...
1245 static void filer_add_widgets(FilerWindow *filer_window)
1247 GtkWidget *hbox, *vbox, *collection;
1249 /* Create the top-level window widget */
1250 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1251 filer_set_title(filer_window);
1253 /* This property is cleared when the window is destroyed.
1254 * You can thus ref filer_window->window and use this to see
1255 * if the window no longer exists.
1257 g_object_set_data(G_OBJECT(filer_window->window),
1258 "filer_window", filer_window);
1260 /* The view is the area that actually displays the files */
1261 filer_window->view = VIEW(view_collection_new(filer_window));
1262 gtk_widget_show(GTK_WIDGET(filer_window->view));
1263 collection = GTK_WIDGET(filer_window->collection); /* XXX */
1264 g_object_set_data(G_OBJECT(collection), "filer_window", filer_window);
1266 /* Scrollbar on the right, everything else on the left */
1267 hbox = gtk_hbox_new(FALSE, 0);
1268 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1270 vbox = gtk_vbox_new(FALSE, 0);
1271 gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
1272 filer_window->toplevel_vbox = GTK_BOX(vbox);
1274 /* If we want a toolbar, create it now */
1275 toolbar_update_toolbar(filer_window);
1277 /* If there's a message that should be displayed in each window (eg
1278 * 'Running as root'), add it here.
1280 if (show_user_message)
1282 filer_window->message = gtk_label_new(show_user_message);
1283 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1284 FALSE, TRUE, 0);
1285 gtk_widget_show(filer_window->message);
1288 /* Now add the area for displaying the files.
1289 * The collection is one huge window that goes in a Viewport.
1291 gtk_box_pack_start_defaults(GTK_BOX(vbox),
1292 GTK_WIDGET(filer_window->view));
1293 filer_window->scrollbar =
1294 gtk_vscrollbar_new(filer_window->collection->vadj);
1296 /* And the minibuffer (hidden to start with) */
1297 create_minibuffer(filer_window);
1298 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1299 FALSE, TRUE, 0);
1301 /* And the thumbnail progress bar (also hidden) */
1303 GtkWidget *cancel;
1305 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1306 gtk_box_pack_start(GTK_BOX(vbox), filer_window->thumb_bar,
1307 FALSE, TRUE, 0);
1309 filer_window->thumb_progress = gtk_progress_bar_new();
1311 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1312 filer_window->thumb_progress, TRUE, TRUE, 0);
1314 cancel = gtk_button_new_with_label(_("Cancel"));
1315 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1316 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1317 cancel, FALSE, TRUE, 0);
1318 g_signal_connect_swapped(cancel, "clicked",
1319 G_CALLBACK(filer_cancel_thumbnails),
1320 filer_window);
1323 /* Put the scrollbar on the left of everything else... */
1324 gtk_box_pack_start(GTK_BOX(hbox),
1325 filer_window->scrollbar, FALSE, TRUE, 0);
1327 gtk_window_set_focus(GTK_WINDOW(filer_window->window), collection);
1329 gtk_widget_show(hbox);
1330 gtk_widget_show(vbox);
1331 gtk_widget_show(filer_window->scrollbar);
1332 gtk_widget_show(collection);
1334 gtk_widget_realize(filer_window->window);
1336 gdk_window_set_role(filer_window->window->window,
1337 filer_window->sym_path);
1339 filer_window_set_size(filer_window, 4, 4, TRUE);
1342 static void filer_add_signals(FilerWindow *filer_window)
1344 GtkObject *collection = GTK_OBJECT(filer_window->collection);
1345 GtkTargetEntry target_table[] =
1347 {"text/uri-list", 0, TARGET_URI_LIST},
1348 {"STRING", 0, TARGET_STRING},
1349 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1352 /* Events on the top-level window */
1353 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1354 g_signal_connect(filer_window->window, "enter-notify-event",
1355 G_CALLBACK(pointer_in), filer_window);
1356 g_signal_connect(filer_window->window, "leave-notify-event",
1357 G_CALLBACK(pointer_out), filer_window);
1358 g_signal_connect(filer_window->window, "destroy",
1359 G_CALLBACK(filer_window_destroyed), filer_window);
1361 /* Events on the collection widget */
1362 gtk_widget_set_events(GTK_WIDGET(collection),
1363 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
1364 GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_MASK |
1365 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1367 g_signal_connect(collection, "selection_get",
1368 G_CALLBACK(selection_get), NULL);
1369 gtk_selection_add_targets(GTK_WIDGET(collection), GDK_SELECTION_PRIMARY,
1370 target_table,
1371 sizeof(target_table) / sizeof(*target_table));
1373 g_signal_connect(filer_window->window, "key_press_event",
1374 G_CALLBACK(key_press_event), filer_window);
1376 /* Drag and drop events */
1377 g_signal_connect(collection, "drag_data_get",
1378 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1379 drag_set_dest(filer_window);
1382 static gint clear_scanning_display(FilerWindow *filer_window)
1384 if (filer_exists(filer_window))
1385 filer_set_title(filer_window);
1386 return FALSE;
1389 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1391 if (scanning == filer_window->scanning)
1392 return;
1393 filer_window->scanning = scanning;
1395 if (scanning)
1396 filer_set_title(filer_window);
1397 else
1398 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1399 filer_window);
1402 /* Note that filer_window may not exist after this call */
1403 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1405 if (may_rescan(filer_window, warning))
1406 dir_update(filer_window->directory, filer_window->sym_path);
1409 void filer_update_all(void)
1411 GList *next = all_filer_windows;
1413 while (next)
1415 FilerWindow *filer_window = (FilerWindow *) next->data;
1417 /* Updating directory may remove it from list -- stop sending
1418 * patches to move this line!
1420 next = next->next;
1422 filer_update_dir(filer_window, TRUE);
1426 /* Refresh the various caches even if we don't think we need to */
1427 void full_refresh(void)
1429 mount_update(TRUE);
1432 /* See whether a filer window with a given path already exists
1433 * and is different from diff.
1435 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1437 GList *next;
1439 for (next = all_filer_windows; next; next = next->next)
1441 FilerWindow *filer_window = (FilerWindow *) next->data;
1443 if (filer_window != diff &&
1444 strcmp(sym_path, filer_window->sym_path) == 0)
1445 return filer_window;
1448 return NULL;
1451 /* This path has been mounted/umounted/deleted some files - update all dirs */
1452 void filer_check_mounted(const char *real_path)
1454 GList *next = all_filer_windows;
1455 gchar *parent;
1456 int len;
1458 len = strlen(real_path);
1460 while (next)
1462 FilerWindow *filer_window = (FilerWindow *) next->data;
1464 next = next->next;
1466 if (strncmp(real_path, filer_window->real_path, len) == 0)
1468 char s = filer_window->real_path[len];
1470 if (s == '/' || s == '\0')
1471 filer_update_dir(filer_window, FALSE);
1475 parent = g_dirname(real_path);
1476 refresh_dirs(parent);
1477 g_free(parent);
1479 icons_may_update(real_path);
1482 /* Close all windows displaying 'path' or subdirectories of 'path' */
1483 void filer_close_recursive(const char *path)
1485 GList *next = all_filer_windows;
1486 gchar *real;
1487 int len;
1489 real = pathdup(path);
1490 len = strlen(real);
1492 while (next)
1494 FilerWindow *filer_window = (FilerWindow *) next->data;
1496 next = next->next;
1498 if (strncmp(real, filer_window->real_path, len) == 0)
1500 char s = filer_window->real_path[len];
1502 if (len == 1 || s == '/' || s == '\0')
1503 gtk_widget_destroy(filer_window->window);
1508 /* Like minibuffer_show(), except that:
1509 * - It returns FALSE (to be used from an idle callback)
1510 * - It checks that the filer window still exists.
1512 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1514 if (filer_exists(filer_window))
1515 minibuffer_show(filer_window, MINI_PATH);
1516 return FALSE;
1519 /* TRUE iff filer_window points to an existing FilerWindow
1520 * structure.
1522 gboolean filer_exists(FilerWindow *filer_window)
1524 GList *next;
1526 for (next = all_filer_windows; next; next = next->next)
1528 FilerWindow *fw = (FilerWindow *) next->data;
1530 if (fw == filer_window)
1531 return TRUE;
1534 return FALSE;
1537 /* Make sure the window title is up-to-date */
1538 void filer_set_title(FilerWindow *filer_window)
1540 guchar *title = NULL;
1541 guchar *flags = "";
1543 if (filer_window->scanning || filer_window->show_hidden ||
1544 filer_window->show_thumbs)
1546 if (o_short_flag_names.int_value)
1548 flags = g_strconcat(" +",
1549 filer_window->scanning ? _("S") : "",
1550 filer_window->show_hidden ? _("A") : "",
1551 filer_window->show_thumbs ? _("T") : "",
1552 NULL);
1554 else
1556 flags = g_strconcat(" (",
1557 filer_window->scanning ? _("Scanning, ") : "",
1558 filer_window->show_hidden ? _("All, ") : "",
1559 filer_window->show_thumbs ? _("Thumbs, ") : "",
1560 NULL);
1561 flags[strlen(flags) - 2] = ')';
1565 if (not_local)
1566 title = g_strconcat("//", our_host_name(),
1567 filer_window->sym_path, flags, NULL);
1569 if (!title && home_dir_len > 1 &&
1570 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1572 guchar sep = filer_window->sym_path[home_dir_len];
1574 if (sep == '\0' || sep == '/')
1575 title = g_strconcat("~",
1576 filer_window->sym_path + home_dir_len,
1577 flags,
1578 NULL);
1581 if (!title)
1582 title = g_strconcat(filer_window->sym_path, flags, NULL);
1584 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1585 g_free(title);
1587 if (flags[0] != '\0')
1588 g_free(flags);
1591 /* Reconnect to the same directory (used when the Show Hidden option is
1592 * toggled). This has the side-effect of updating the window title.
1594 void filer_detach_rescan(FilerWindow *filer_window)
1596 Directory *dir = filer_window->directory;
1598 g_object_ref(dir);
1599 detach(filer_window);
1600 filer_window->directory = dir;
1601 attach(filer_window);
1604 /* Puts the filer window into target mode. When an item is chosen,
1605 * fn(filer_window, item, data) is called. 'reason' will be displayed
1606 * on the toolbar while target mode is active.
1608 * Use fn == NULL to cancel target mode.
1610 void filer_target_mode(FilerWindow *filer_window,
1611 TargetFunc fn,
1612 gpointer data,
1613 const char *reason)
1615 TargetFunc old_fn = filer_window->target_cb;
1617 if (fn != old_fn)
1618 gdk_window_set_cursor(
1619 GTK_WIDGET(filer_window->collection)->window,
1620 fn ? crosshair : NULL);
1622 filer_window->target_cb = fn;
1623 filer_window->target_data = data;
1625 if (filer_window->toolbar_text == NULL)
1626 return;
1628 if (fn)
1629 gtk_label_set_text(
1630 GTK_LABEL(filer_window->toolbar_text), reason);
1631 else if (o_toolbar_info.int_value)
1633 if (old_fn)
1634 toolbar_update_info(filer_window);
1636 else
1637 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1640 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
1642 GtkStateType old_state = filer_window->selection_state;
1644 filer_window->selection_state = normal
1645 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
1647 if (old_state != filer_window->selection_state
1648 && filer_window->collection->number_selected)
1649 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
1652 void filer_cancel_thumbnails(FilerWindow *filer_window)
1654 gtk_widget_hide(filer_window->thumb_bar);
1656 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
1657 g_list_free(filer_window->thumb_queue);
1658 filer_window->thumb_queue = NULL;
1659 filer_window->max_thumbs = 0;
1662 /* Generate the next thumb for this window. The collection object is
1663 * unref'd when there is nothing more to do.
1664 * If the collection no longer has a filer window, nothing is done.
1666 static gboolean filer_next_thumb_real(GObject *window)
1668 FilerWindow *filer_window;
1669 gchar *path;
1670 int done, total;
1672 filer_window = g_object_get_data(window, "filer_window");
1674 if (!filer_window)
1676 g_object_unref(window);
1677 return FALSE;
1680 if (!filer_window->thumb_queue)
1682 filer_cancel_thumbnails(filer_window);
1683 g_object_unref(window);
1684 return FALSE;
1687 total = filer_window->max_thumbs;
1688 done = total - g_list_length(filer_window->thumb_queue);
1690 path = (gchar *) filer_window->thumb_queue->data;
1692 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
1694 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
1695 path);
1696 g_free(path);
1698 gtk_progress_bar_set_fraction(
1699 GTK_PROGRESS_BAR(filer_window->thumb_progress),
1700 done / (float) total);
1702 return FALSE;
1705 /* path is the thumb just loaded, if any.
1706 * collection is unref'd (eventually).
1708 static void filer_next_thumb(GObject *window, const gchar *path)
1710 if (path)
1711 dir_force_update_path(path);
1713 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
1716 static void start_thumb_scanning(FilerWindow *filer_window)
1718 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
1719 return; /* Already scanning */
1721 gtk_widget_show_all(filer_window->thumb_bar);
1723 g_object_ref(G_OBJECT(filer_window->window));
1724 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
1727 /* Set this image to be loaded some time in the future */
1728 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
1730 filer_window->max_thumbs++;
1732 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
1733 g_strdup(path));
1735 if (filer_window->scanning)
1736 return; /* Will start when scan ends */
1738 start_thumb_scanning(filer_window);
1741 /* If thumbnail display is on, look through all the items in this directory
1742 * and start creating or updating the thumbnails as needed.
1744 void filer_create_thumbs(FilerWindow *filer_window)
1746 DirItem *item;
1747 ViewIter iter;
1749 if (!filer_window->show_thumbs)
1750 return;
1752 view_get_iter(filer_window->view, &iter, 0);
1754 while ((item = iter.next(&iter)))
1756 MaskedPixmap *pixmap;
1757 gchar *path;
1758 gboolean found;
1760 if (item->base_type != TYPE_FILE)
1761 continue;
1763 if (strcmp(item->mime_type->media_type, "image") != 0)
1764 continue;
1766 path = make_path(filer_window->real_path, item->leafname)->str;
1768 pixmap = g_fscache_lookup_full(pixmap_cache, path,
1769 FSCACHE_LOOKUP_ONLY_NEW, &found);
1770 if (pixmap)
1771 g_object_unref(pixmap);
1773 /* If we didn't get an image, it could be because:
1775 * - We're loading the image now. found is TRUE,
1776 * and we'll update the item later.
1777 * - We tried to load the image and failed. found
1778 * is TRUE.
1779 * - We haven't tried loading the image. found is
1780 * FALSE, and we start creating the thumb here.
1782 if (!found)
1783 filer_create_thumb(filer_window, path);
1787 static void filer_options_changed(void)
1789 if (o_short_flag_names.has_changed)
1791 GList *next;
1793 for (next = all_filer_windows; next; next = next->next)
1795 FilerWindow *filer_window = (FilerWindow *) next->data;
1797 filer_set_title(filer_window);
1802 /* Change to Large or Small icons depending on the number of items
1803 * in the directory, subject to options.
1805 static void set_style_by_number_of_items(FilerWindow *filer_window)
1807 int n;
1809 g_return_if_fail(filer_window != NULL);
1811 if (!o_filer_change_size.int_value)
1812 return; /* Don't auto-set style */
1814 if (filer_window->display_style != LARGE_ICONS &&
1815 filer_window->display_style != SMALL_ICONS)
1816 return; /* Only change between these two styles */
1818 n = filer_window->collection->number_of_items;
1820 if (n >= o_filer_change_size_num.int_value)
1821 display_set_layout(filer_window, SMALL_ICONS,
1822 filer_window->details_type);
1823 else
1824 display_set_layout(filer_window, LARGE_ICONS,
1825 filer_window->details_type);
1828 /* Append interesting information to this GString */
1829 void filer_add_tip_details(FilerWindow *filer_window,
1830 GString *tip, DirItem *item)
1832 guchar *fullpath = NULL;
1834 fullpath = make_path(filer_window->real_path, item->leafname)->str;
1836 if (item->flags & ITEM_FLAG_SYMLINK)
1838 char *target;
1840 target = readlink_dup(fullpath);
1841 if (target)
1843 g_string_append(tip, _("Symbolic link to "));
1844 g_string_append(tip, target);
1845 g_string_append_c(tip, '\n');
1846 g_free(target);
1850 if (item->flags & ITEM_FLAG_APPDIR)
1852 XMLwrapper *info;
1853 xmlNode *node;
1855 info = appinfo_get(fullpath, item);
1856 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
1858 guchar *str;
1859 str = xmlNodeListGetString(node->doc,
1860 node->xmlChildrenNode, 1);
1861 if (str)
1863 g_string_append(tip, str);
1864 g_string_append_c(tip, '\n');
1865 g_free(str);
1868 if (info)
1869 g_object_unref(info);
1872 if (!g_utf8_validate(item->leafname, -1, NULL))
1873 g_string_append(tip,
1874 _("This filename is not valid UTF-8. "
1875 "You should rename it.\n"));