r1553: Created View interface and started moving collection specific stuff
[rox-filer.git] / ROX-Filer / src / filer.c
blob8cdf997faf30b305453431065bceb0d6a2f8ac0d
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 FilerWindow *window_with_focus = NULL;
70 GList *all_filer_windows = NULL;
72 static FilerWindow *window_with_primary = NULL;
74 /* Static prototypes */
75 static void attach(FilerWindow *filer_window);
76 static void detach(FilerWindow *filer_window);
77 static void filer_window_destroyed(GtkWidget *widget,
78 FilerWindow *filer_window);
79 static void update_display(Directory *dir,
80 DirAction action,
81 GPtrArray *items,
82 FilerWindow *filer_window);
83 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
84 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
85 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
86 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
87 static void filer_add_widgets(FilerWindow *filer_window);
88 static void filer_add_signals(FilerWindow *filer_window);
89 static void filer_size_for(FilerWindow *filer_window,
90 int w, int h, int n, gboolean allow_shrink);
92 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
93 static void filer_next_thumb(GObject *window, const gchar *path);
94 static void start_thumb_scanning(FilerWindow *filer_window);
95 static void filer_options_changed(void);
96 static void set_style_by_number_of_items(FilerWindow *filer_window);
98 static GdkCursor *busy_cursor = NULL;
99 static GdkCursor *crosshair = NULL;
101 /* Indicates whether the filer's display is different to the machine it
102 * is actually running on.
104 static gboolean not_local = FALSE;
106 static Option o_filer_change_size, o_filer_change_size_num;
107 static Option o_filer_size_limit, o_short_flag_names;
108 Option o_filer_auto_resize, o_unique_filer_windows;
110 void filer_init(void)
112 const gchar *ohost;
113 const gchar *dpy;
114 gchar *dpyhost, *tmp;
116 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
117 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
118 RESIZE_ALWAYS);
119 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
121 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
123 option_add_int(&o_filer_change_size, "filer_change_size", TRUE);
124 option_add_int(&o_filer_change_size_num, "filer_change_size_num", 30);
126 option_add_notify(filer_options_changed);
128 busy_cursor = gdk_cursor_new(GDK_WATCH);
129 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
131 /* Is the display on the local machine, or are we being
132 * run remotely? See filer_set_title().
134 ohost = our_host_name();
135 dpy = gdk_get_display();
136 dpyhost = g_strdup(dpy);
137 tmp = strchr(dpyhost, ':');
138 if (tmp)
139 *tmp = '\0';
141 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
143 /* Try the cannonical name for dpyhost (see our_host_name()
144 * in support.c).
146 struct hostent *ent;
148 ent = gethostbyname(dpyhost);
149 if (!ent || strcmp(ohost, ent->h_name) != 0)
150 not_local = TRUE;
153 g_free(dpyhost);
156 static gboolean if_deleted(gpointer item, gpointer removed)
158 int i = ((GPtrArray *) removed)->len;
159 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
160 char *leafname = ((DirItem *) item)->leafname;
162 while (i--)
164 if (strcmp(leafname, r[i]->leafname) == 0)
165 return TRUE;
168 return FALSE;
171 /* Resize the filer window to w x h pixels, plus border (not clamped) */
172 static void filer_window_set_size(FilerWindow *filer_window,
173 int w, int h,
174 gboolean allow_shrink)
176 GtkWidget *window;
178 g_return_if_fail(filer_window != NULL);
180 if (filer_window->scrollbar)
181 w += filer_window->scrollbar->allocation.width;
183 if (o_toolbar.int_value != TOOLBAR_NONE)
184 h += filer_window->toolbar->allocation.height;
185 if (filer_window->message)
186 h += filer_window->message->allocation.height;
188 window = filer_window->window;
190 if (GTK_WIDGET_VISIBLE(window))
192 gint x, y;
193 GtkRequisition *req = &window->requisition;
194 GdkWindow *gdk_window = window->window;
196 w = MAX(req->width, w);
197 h = MAX(req->height, h);
198 gdk_window_get_position(gdk_window, &x, &y);
200 if (!allow_shrink)
202 gint old_w, old_h;
204 gdk_drawable_get_size(gdk_window, &old_w, &old_h);
205 w = MAX(w, old_w);
206 h = MAX(h, old_h);
208 if (w == old_w && h == old_h)
209 return;
212 if (x + w > screen_width || y + h > screen_height)
214 if (x + w > screen_width)
215 x = screen_width - w - 4;
216 if (y + h > screen_height)
217 y = screen_height - h - 4;
218 gdk_window_move_resize(gdk_window, x, y, w, h);
220 else
221 gdk_window_resize(gdk_window, w, h);
223 else
224 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
227 /* Resize the window to fit the items currently in the Directory.
228 * This should be used once the Directory has been fully scanned, otherwise
229 * the window will appear too small. When opening a directory for the first
230 * time, the names will be known but not the types and images. We can
231 * still make a good estimate of the size.
233 void filer_window_autosize(FilerWindow *filer_window, gboolean allow_shrink)
235 Collection *collection = filer_window->collection;
236 int n;
238 n = collection->number_of_items;
239 n = MAX(n, 2);
241 filer_size_for(filer_window,
242 collection->item_width,
243 collection->item_height,
244 n, allow_shrink);
247 /* Choose a good size for this window, assuming n items of size (w, h) */
248 static void filer_size_for(FilerWindow *filer_window,
249 int w, int h, int n, gboolean allow_shrink)
251 int x;
252 int rows, cols;
253 int max_x, max_rows;
254 const float r = 2.5;
255 int t = 0;
256 int size_limit;
257 int space = 0;
259 size_limit = o_filer_size_limit.int_value;
261 /* Get the extra height required for the toolbar and minibuffer,
262 * if visible.
264 if (o_toolbar.int_value != TOOLBAR_NONE)
265 t = filer_window->toolbar->allocation.height;
266 if (filer_window->message)
267 t += filer_window->message->allocation.height;
268 if (GTK_WIDGET_VISIBLE(filer_window->minibuffer_area))
270 GtkRequisition req;
272 gtk_widget_size_request(filer_window->minibuffer_area, &req);
273 space = req.height + 2;
274 t += space;
277 max_x = (size_limit * screen_width) / 100;
278 max_rows = (size_limit * screen_height) / (h * 100);
280 /* Aim for a size where
281 * x = r(y + t + h), (1)
282 * unless that's too wide.
284 * t = toolbar (and minibuffer) height
285 * r = desired (width / height) ratio
287 * Want to display all items:
288 * (x/w)(y/h) = n
289 * => xy = nwh
290 * => x(x/r - t - h) = nwh (from 1)
291 * => xx - x.rt - hr(1 - nw) = 0
292 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
293 * Now,
294 * 4hr(nw - 1) > 0
295 * so
296 * sqrt(rt.rt + ...) > rt
298 * So, the +/- must be +:
300 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
302 * ( + w - 1 to round up)
304 x = (r * t + sqrt(r*r*t*t + 4*h*r * (n*w - 1))) / 2 + w - 1;
306 /* Limit x */
307 if (x > max_x)
308 x = max_x;
310 cols = x / w;
311 cols = MAX(cols, 1);
313 /* Choose rows to display all items given our chosen x.
314 * Don't make the window *too* big!
316 rows = (n + cols - 1) / cols;
317 if (rows > max_rows)
318 rows = max_rows;
320 /* Leave some room for extra icons, but only in Small Icons mode
321 * otherwise it takes up too much space.
322 * Also, don't add space if the minibuffer is open.
324 if (space == 0)
325 space = filer_window->display_style == SMALL_ICONS ? h : 2;
327 filer_window_set_size(filer_window,
328 w * MAX(cols, 1),
329 h * MAX(rows, 1) + space,
330 allow_shrink);
333 /* Called on a timeout while scanning or when scanning ends
334 * (whichever happens first).
336 static gint open_filer_window(FilerWindow *filer_window)
338 view_style_changed(VIEW(filer_window->view), 0);
340 if (filer_window->open_timeout)
342 gtk_timeout_remove(filer_window->open_timeout);
343 filer_window->open_timeout = 0;
346 if (!GTK_WIDGET_VISIBLE(filer_window->window))
348 set_style_by_number_of_items(filer_window);
349 filer_window_autosize(filer_window, TRUE);
350 gtk_widget_show(filer_window->window);
353 return FALSE;
356 static void update_display(Directory *dir,
357 DirAction action,
358 GPtrArray *items,
359 FilerWindow *filer_window)
361 Collection *collection = filer_window->collection;
362 ViewIface *view = (ViewIface *) filer_window->view;
364 switch (action)
366 case DIR_ADD:
367 view_add_items(view, items);
368 /* Open and resize if currently hidden */
369 open_filer_window(filer_window);
370 break;
371 case DIR_REMOVE:
372 view_delete_if(view, if_deleted, items);
373 break;
374 case DIR_START_SCAN:
375 set_scanning_display(filer_window, TRUE);
376 toolbar_update_info(filer_window);
377 break;
378 case DIR_END_SCAN:
379 if (filer_window->window->window)
380 gdk_window_set_cursor(
381 filer_window->window->window,
382 NULL);
383 set_scanning_display(filer_window, FALSE);
384 toolbar_update_info(filer_window);
385 open_filer_window(filer_window);
387 if (filer_window->had_cursor &&
388 collection->cursor_item == -1)
390 collection_set_cursor_item(collection, 0);
391 filer_window->had_cursor = FALSE;
393 if (filer_window->auto_select)
394 display_set_autoselect(filer_window,
395 filer_window->auto_select);
396 g_free(filer_window->auto_select);
397 filer_window->auto_select = NULL;
399 filer_create_thumbs(filer_window);
401 if (filer_window->thumb_queue)
402 start_thumb_scanning(filer_window);
403 break;
404 case DIR_UPDATE:
405 view_update_items(view, items);
406 break;
410 static void attach(FilerWindow *filer_window)
412 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
413 view_clear(VIEW(filer_window->view));
414 filer_window->scanning = TRUE;
415 dir_attach(filer_window->directory, (DirCallback) update_display,
416 filer_window);
417 filer_set_title(filer_window);
420 static void detach(FilerWindow *filer_window)
422 g_return_if_fail(filer_window->directory != NULL);
424 dir_detach(filer_window->directory,
425 (DirCallback) update_display, filer_window);
426 g_object_unref(filer_window->directory);
427 filer_window->directory = NULL;
430 static void filer_window_destroyed(GtkWidget *widget,
431 FilerWindow *filer_window)
433 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
435 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
437 if (window_with_primary == filer_window)
438 window_with_primary = NULL;
440 if (window_with_focus == filer_window)
442 menu_popdown();
443 window_with_focus = NULL;
446 if (filer_window->directory)
447 detach(filer_window);
449 if (filer_window->open_timeout)
451 gtk_timeout_remove(filer_window->open_timeout);
452 filer_window->open_timeout = 0;
455 if (filer_window->thumb_queue)
457 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
458 g_list_free(filer_window->thumb_queue);
461 tooltip_show(NULL);
463 g_free(filer_window->auto_select);
464 g_free(filer_window->real_path);
465 g_free(filer_window->sym_path);
466 g_free(filer_window);
468 one_less_window();
471 /* Returns TRUE iff the directory still exists. */
472 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
474 Directory *dir;
476 g_return_val_if_fail(filer_window != NULL, FALSE);
478 /* We do a fresh lookup (rather than update) because the inode may
479 * have changed.
481 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
482 if (!dir)
484 if (warning)
485 info_message(_("Directory missing/deleted"));
486 gtk_widget_destroy(filer_window->window);
487 return FALSE;
489 if (dir == filer_window->directory)
490 g_object_unref(dir);
491 else
493 detach(filer_window);
494 filer_window->directory = dir;
495 attach(filer_window);
498 return TRUE;
501 /* No items are now selected. This might be because another app claimed
502 * the selection or because the user unselected all the items.
504 void filer_lost_selection(FilerWindow *filer_window, gint time)
506 if (window_with_primary == filer_window)
508 window_with_primary = NULL;
509 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
513 /* Another app has claimed the primary selection */
514 void filer_lost_primary(FilerWindow *filer_window)
516 if (window_with_primary && window_with_primary == filer_window)
518 window_with_primary = NULL;
519 set_selection_state(filer_window, FALSE);
523 /* Someone wants us to send them the selection */
524 static void selection_get(GtkWidget *widget,
525 GtkSelectionData *selection_data,
526 guint info,
527 guint time,
528 gpointer data)
530 GString *reply, *header;
531 FilerWindow *filer_window;
532 int i;
533 Collection *collection;
535 filer_window = g_object_get_data(G_OBJECT(widget), "filer_window");
537 reply = g_string_new(NULL);
538 header = g_string_new(NULL);
540 switch (info)
542 case TARGET_STRING:
543 g_string_sprintf(header, " %s",
544 make_path(filer_window->sym_path, "")->str);
545 break;
546 case TARGET_URI_LIST:
547 g_string_sprintf(header, " file://%s%s",
548 our_host_name_for_dnd(),
549 make_path(filer_window->sym_path, "")->str);
550 break;
553 collection = filer_window->collection;
554 for (i = 0; i < collection->number_of_items; i++)
556 if (collection->items[i].selected)
558 DirItem *item =
559 (DirItem *) collection->items[i].data;
561 g_string_append(reply, header->str);
562 g_string_append(reply, item->leafname);
565 /* This works, but I don't think I like it... */
566 /* g_string_append_c(reply, ' '); */
568 if (reply->len > 0)
569 gtk_selection_data_set(selection_data, xa_string,
570 8, reply->str + 1, reply->len - 1);
571 else
573 g_warning("Attempt to paste empty selection!");
574 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
577 g_string_free(reply, TRUE);
578 g_string_free(header, TRUE);
581 void filer_selection_changed(FilerWindow *filer_window, gint time)
583 /* Selection has been changed -- try to grab the primary selection
584 * if we don't have it.
586 if (window_with_primary == filer_window)
587 return; /* Already got it */
589 if (!filer_window->collection->number_selected)
590 return; /* Nothing selected */
592 if (filer_window->temp_item_selected == FALSE &&
593 gtk_selection_owner_set(GTK_WIDGET(filer_window->collection),
594 GDK_SELECTION_PRIMARY,
595 time))
597 window_with_primary = filer_window;
598 set_selection_state(filer_window, TRUE);
600 else
601 set_selection_state(filer_window, FALSE);
604 /* Open the item (or add it to the shell command minibuffer) */
605 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
607 gboolean shift = (flags & OPEN_SHIFT) != 0;
608 gboolean close_mini = flags & OPEN_FROM_MINI;
609 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
610 DirItem *item = (DirItem *)
611 filer_window->collection->items[item_number].data;
612 guchar *full_path;
613 gboolean wink = TRUE;
614 Directory *old_dir;
616 if (filer_window->mini_type == MINI_SHELL)
618 minibuffer_add(filer_window, item->leafname);
619 return;
622 if (!item->image)
623 dir_update_item(filer_window->directory, item->leafname);
625 if (item->base_type == TYPE_DIRECTORY)
627 /* Never close a filer window when opening a directory
628 * (click on a dir or click on an app with shift).
630 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
631 close_window = FALSE;
634 full_path = make_path(filer_window->sym_path, item->leafname)->str;
635 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
636 wink = FALSE;
638 old_dir = filer_window->directory;
639 if (run_diritem(full_path, item,
640 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
641 filer_window,
642 shift))
644 if (old_dir != filer_window->directory)
645 return;
647 if (close_window)
648 gtk_widget_destroy(filer_window->window);
649 else
651 if (wink)
652 collection_wink_item(filer_window->collection,
653 item_number);
654 if (close_mini)
655 minibuffer_hide(filer_window);
660 static gint pointer_in(GtkWidget *widget,
661 GdkEventCrossing *event,
662 FilerWindow *filer_window)
664 may_rescan(filer_window, TRUE);
665 return FALSE;
668 static gint pointer_out(GtkWidget *widget,
669 GdkEventCrossing *event,
670 FilerWindow *filer_window)
672 tooltip_show(NULL);
673 return FALSE;
676 /* Move the cursor to the next selected item in direction 'dir'
677 * (+1 or -1).
679 static void next_selected(FilerWindow *filer_window, int dir)
681 Collection *collection = filer_window->collection;
682 int to_check = collection->number_of_items;
683 int item = collection->cursor_item;
685 g_return_if_fail(dir == 1 || dir == -1);
687 if (to_check > 0 && item == -1)
689 /* Cursor not currently on */
690 if (dir == 1)
691 item = 0;
692 else
693 item = collection->number_of_items - 1;
695 if (collection->items[item].selected)
696 goto found;
699 while (--to_check > 0)
701 item += dir;
703 if (item >= collection->number_of_items)
704 item = 0;
705 else if (item < 0)
706 item = collection->number_of_items - 1;
708 if (collection->items[item].selected)
709 goto found;
712 gdk_beep();
713 return;
714 found:
715 collection_set_cursor_item(collection, item);
718 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
720 Collection *collection = filer_window->collection;
721 int item = collection->cursor_item;
722 TargetFunc cb = filer_window->target_cb;
723 gpointer data = filer_window->target_data;
724 OpenFlags flags = OPEN_SAME_WINDOW;
726 filer_target_mode(filer_window, NULL, NULL, NULL);
727 if (item < 0 || item >= collection->number_of_items)
728 return;
730 if (cb)
732 cb(filer_window, item, data);
733 return;
736 if (event->state & GDK_SHIFT_MASK)
737 flags |= OPEN_SHIFT;
739 filer_openitem(filer_window, item, flags);
742 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
743 * changed. If no groups were loaded and there is no file then initialised
744 * groups to an empty document.
745 * Return the node for the 'name' group.
747 static xmlNode *group_find(char *name)
749 xmlNode *node;
750 gchar *path;
752 /* Update the groups, if possible */
753 path = choices_find_path_load("Groups.xml", PROJECT);
754 if (path)
756 XMLwrapper *wrapper;
757 wrapper = xml_cache_load(path);
758 if (wrapper)
760 if (groups)
761 g_object_unref(groups);
762 groups = wrapper;
765 g_free(path);
768 if (!groups)
770 groups = xml_new(NULL);
771 groups->doc = xmlNewDoc("1.0");
773 xmlDocSetRootElement(groups->doc,
774 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
775 return NULL;
778 node = xmlDocGetRootElement(groups->doc);
780 for (node = node->xmlChildrenNode; node; node = node->next)
782 guchar *gid;
784 gid = xmlGetProp(node, "name");
786 if (!gid)
787 continue;
789 if (strcmp(name, gid) != 0)
790 continue;
792 g_free(gid);
794 return node;
797 return NULL;
800 static void group_save(FilerWindow *filer_window, char *name)
802 Collection *collection = filer_window->collection;
803 xmlNode *group;
804 guchar *save_path;
805 int i;
807 group = group_find(name);
808 if (group)
810 xmlUnlinkNode(group);
811 xmlFreeNode(group);
813 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
814 NULL, "group", NULL);
815 xmlSetProp(group, "name", name);
817 xmlNewChild(group, NULL, "directory", filer_window->sym_path);
819 for (i = 0; i < collection->number_of_items; i++)
821 DirItem *item = (DirItem *) collection->items[i].data;
822 gchar *u8_leaf = item->leafname;
824 if (!collection->items[i].selected)
825 continue;
827 xmlNewChild(group, NULL, "item", u8_leaf);
830 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
831 if (save_path)
833 save_xml_file(groups->doc, save_path);
834 g_free(save_path);
838 static void group_restore(FilerWindow *filer_window, char *name)
840 GHashTable *in_group;
841 Collection *collection = filer_window->collection;
842 int j, n;
843 char *path;
844 xmlNode *group, *node;
846 group = group_find(name);
848 if (!group)
850 report_error(_("Group %s is not set. Select some files "
851 "and press Ctrl+%s to set the group. Press %s "
852 "on its own to reselect the files later."),
853 name, name, name);
854 return;
857 node = get_subnode(group, NULL, "directory");
858 g_return_if_fail(node != NULL);
859 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
860 g_return_if_fail(path != NULL);
862 if (strcmp(path, filer_window->sym_path) != 0)
863 filer_change_to(filer_window, path, NULL);
864 g_free(path);
866 /* If an item at the start is selected then we could lose the
867 * primary selection after checking that item and then need to
868 * gain it again at the end. Therefore, if anything is selected
869 * then select the last item until the end of the search.
871 n = collection->number_of_items;
872 if (collection->number_selected)
873 collection_select_item(collection, n - 1);
875 in_group = g_hash_table_new(g_str_hash, g_str_equal);
876 for (node = group->xmlChildrenNode; node; node = node->next)
878 gchar *leaf;
879 if (node->type != XML_ELEMENT_NODE)
880 continue;
881 if (strcmp(node->name, "item") != 0)
882 continue;
884 leaf = xmlNodeListGetString(groups->doc,
885 node->xmlChildrenNode, 1);
886 if (!leaf)
887 g_warning("Missing leafname!\n");
888 else
889 g_hash_table_insert(in_group, leaf, filer_window);
892 for (j = 0; j < n; j++)
894 DirItem *item = (DirItem *) collection->items[j].data;
896 if (g_hash_table_lookup(in_group, item->leafname))
897 collection_select_item(collection, j);
898 else
899 collection_unselect_item(collection, j);
902 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
903 g_hash_table_destroy(in_group);
906 /* Handle keys that can't be bound with the menu */
907 static gint key_press_event(GtkWidget *widget,
908 GdkEventKey *event,
909 FilerWindow *filer_window)
911 gboolean handled;
912 guint key = event->keyval;
913 char group[2] = "1";
915 window_with_focus = filer_window;
917 /* Note: Not convinced this is the way Gtk's key system is supposed
918 * to be used...
920 if (!filer_keys)
921 ensure_filer_menu(); /* Gets the keys working... */
922 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
923 filer_keys);
924 handled = gtk_accel_groups_activate(G_OBJECT(filer_window->window),
925 event->keyval, event->state);
926 if (window_with_focus)
927 gtk_window_remove_accel_group(GTK_WINDOW(filer_window->window),
928 filer_keys);
929 else
930 return TRUE; /* Window no longer exists */
931 if (handled)
932 return TRUE;
934 switch (key)
936 case GDK_Escape:
937 filer_target_mode(filer_window, NULL, NULL, NULL);
938 return FALSE;
939 case GDK_Return:
940 return_pressed(filer_window, event);
941 break;
942 case GDK_ISO_Left_Tab:
943 next_selected(filer_window, -1);
944 break;
945 case GDK_Tab:
946 next_selected(filer_window, 1);
947 break;
948 case GDK_BackSpace:
949 change_to_parent(filer_window);
950 break;
951 case GDK_backslash:
952 tooltip_show(NULL);
953 show_filer_menu(filer_window, (GdkEvent *) event,
954 filer_window->collection->cursor_item);
955 break;
956 default:
957 if (key >= GDK_0 && key <= GDK_9)
958 group[0] = key - GDK_0 + '0';
959 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
960 group[0] = key - GDK_KP_0 + '0';
961 else
962 return FALSE;
965 if (event->state & GDK_CONTROL_MASK)
966 group_save(filer_window, group);
967 else
968 group_restore(filer_window, group);
971 return TRUE;
974 void filer_open_parent(FilerWindow *filer_window)
976 char *dir;
977 const char *current = filer_window->sym_path;
979 if (current[0] == '/' && current[1] == '\0')
980 return; /* Already in the root */
982 dir = g_dirname(current);
983 filer_opendir(dir, filer_window);
984 g_free(dir);
987 void change_to_parent(FilerWindow *filer_window)
989 char *dir;
990 const char *current = filer_window->sym_path;
992 if (current[0] == '/' && current[1] == '\0')
993 return; /* Already in the root */
995 dir = g_dirname(current);
996 filer_change_to(filer_window, dir, g_basename(current));
997 g_free(dir);
1000 /* Removes trailing /s from path (modified in place) */
1001 static void tidy_sympath(gchar *path)
1003 int l;
1005 g_return_if_fail(path != NULL);
1007 l = strlen(path);
1008 while (l > 1 && path[l - 1] == '/')
1010 l--;
1011 path[l] = '\0';
1015 /* Make filer_window display path. When finished, highlight item 'from', or
1016 * the first item if from is NULL. If there is currently no cursor then
1017 * simply wink 'from' (if not NULL).
1019 void filer_change_to(FilerWindow *filer_window,
1020 const char *path, const char *from)
1022 char *from_dup;
1023 char *sym_path, *real_path;
1024 Directory *new_dir;
1026 g_return_if_fail(filer_window != NULL);
1028 filer_cancel_thumbnails(filer_window);
1030 tooltip_show(NULL);
1032 sym_path = g_strdup(path);
1033 real_path = pathdup(path);
1034 new_dir = g_fscache_lookup(dir_cache, real_path);
1036 if (!new_dir)
1038 delayed_error(_("Directory '%s' is not accessible"),
1039 sym_path);
1040 g_free(real_path);
1041 g_free(sym_path);
1042 return;
1045 if (o_unique_filer_windows.int_value)
1047 FilerWindow *fw;
1049 fw = find_filer_window(sym_path, filer_window);
1050 if (fw)
1051 gtk_widget_destroy(fw->window);
1054 from_dup = from && *from ? g_strdup(from) : NULL;
1056 detach(filer_window);
1057 g_free(filer_window->real_path);
1058 g_free(filer_window->sym_path);
1059 filer_window->real_path = real_path;
1060 filer_window->sym_path = sym_path;
1061 tidy_sympath(filer_window->sym_path);
1063 filer_window->directory = new_dir;
1065 g_free(filer_window->auto_select);
1066 filer_window->had_cursor = filer_window->collection->cursor_item != -1
1067 || filer_window->had_cursor;
1068 filer_window->auto_select = from_dup;
1070 filer_set_title(filer_window);
1071 if (filer_window->window->window)
1072 gdk_window_set_role(filer_window->window->window,
1073 filer_window->sym_path);
1074 collection_set_cursor_item(filer_window->collection, -1);
1076 attach(filer_window);
1078 set_style_by_number_of_items(filer_window);
1080 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1081 filer_window_autosize(filer_window, TRUE);
1083 if (filer_window->mini_type == MINI_PATH)
1084 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1085 filer_window);
1088 /* Returns a list containing the full (sym) pathname of every selected item.
1089 * You must g_free() each item in the list.
1091 GList *filer_selected_items(FilerWindow *filer_window)
1093 Collection *collection = filer_window->collection;
1094 GList *retval = NULL;
1095 guchar *dir = filer_window->sym_path;
1096 int i;
1098 for (i = 0; i < collection->number_of_items; i++)
1100 if (collection->items[i].selected)
1102 DirItem *item = (DirItem *) collection->items[i].data;
1104 retval = g_list_prepend(retval,
1105 g_strdup(make_path(dir, item->leafname)->str));
1109 return g_list_reverse(retval);
1112 DirItem *filer_selected_item(FilerWindow *filer_window)
1114 Collection *collection = filer_window->collection;
1115 int item;
1117 item = collection_selected_item_number(collection);
1119 if (item > -1)
1120 return (DirItem *) collection->items[item].data;
1121 return NULL;
1124 /* Creates and shows a new filer window.
1125 * If src_win != NULL then display options can be taken from that source window.
1126 * Returns the new filer window, or NULL on error.
1127 * Note: if unique windows is in use, may return an existing window.
1129 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win)
1131 FilerWindow *filer_window;
1132 char *real_path;
1133 DisplayStyle dstyle;
1134 DetailsType dtype;
1136 /* Get the real pathname of the directory and copy it */
1137 real_path = pathdup(path);
1139 if (o_unique_filer_windows.int_value)
1141 FilerWindow *same_dir_window;
1143 same_dir_window = find_filer_window(path, NULL);
1145 if (same_dir_window)
1147 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1148 return same_dir_window;
1152 filer_window = g_new(FilerWindow, 1);
1153 filer_window->message = NULL;
1154 filer_window->minibuffer = NULL;
1155 filer_window->minibuffer_label = NULL;
1156 filer_window->minibuffer_area = NULL;
1157 filer_window->temp_show_hidden = FALSE;
1158 filer_window->sym_path = g_strdup(path);
1159 filer_window->real_path = real_path;
1160 filer_window->scanning = FALSE;
1161 filer_window->had_cursor = FALSE;
1162 filer_window->auto_select = NULL;
1163 filer_window->toolbar_text = NULL;
1164 filer_window->target_cb = NULL;
1165 filer_window->mini_type = MINI_NONE;
1166 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1167 filer_window->toolbar = NULL;
1168 filer_window->toplevel_vbox = NULL;
1170 tidy_sympath(filer_window->sym_path);
1172 /* Finds the entry for this directory in the dir cache, creating
1173 * a new one if needed. This does not cause a scan to start,
1174 * so if a new entry is created then it will be empty.
1176 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1177 if (!filer_window->directory)
1179 delayed_error(_("Directory '%s' not found."), path);
1180 g_free(filer_window->real_path);
1181 g_free(filer_window->sym_path);
1182 g_free(filer_window);
1183 return NULL;
1186 filer_window->temp_item_selected = FALSE;
1187 filer_window->flags = (FilerFlags) 0;
1188 filer_window->details_type = DETAILS_SUMMARY;
1189 filer_window->display_style = UNKNOWN_STYLE;
1190 filer_window->thumb_queue = NULL;
1191 filer_window->max_thumbs = 0;
1193 if (src_win && o_display_inherit_options.int_value)
1195 filer_window->sort_fn = src_win->sort_fn;
1196 dstyle = src_win->display_style;
1197 dtype = src_win->details_type;
1198 filer_window->show_hidden = src_win->show_hidden;
1199 filer_window->show_thumbs = src_win->show_thumbs;
1201 else
1203 int i = o_display_sort_by.int_value;
1204 filer_window->sort_fn = i == 0 ? sort_by_name :
1205 i == 1 ? sort_by_type :
1206 i == 2 ? sort_by_date :
1207 sort_by_size;
1209 dstyle = o_display_size.int_value;
1210 dtype = o_display_details.int_value;
1211 filer_window->show_hidden =
1212 o_display_show_hidden.int_value;
1213 filer_window->show_thumbs =
1214 o_display_show_thumbs.int_value;
1217 /* Add all the user-interface elements & realise */
1218 filer_add_widgets(filer_window);
1219 if (src_win)
1220 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1221 GTK_WIN_POS_MOUSE);
1223 /* Connect to all the signal handlers */
1224 filer_add_signals(filer_window);
1226 display_set_layout(filer_window, dstyle, dtype);
1228 /* Open the window after a timeout, or when scanning stops.
1229 * Do this before attaching, because attach() might tell us to
1230 * stop scanning (if a scan isn't needed).
1232 filer_window->open_timeout = gtk_timeout_add(500,
1233 (GtkFunction) open_filer_window,
1234 filer_window);
1236 /* The collection is created empty and then attach() is called, which
1237 * links the filer window to the entry in the directory cache we
1238 * looked up / created above.
1240 * The attach() function will immediately callback to the filer window
1241 * to deliver a list of all known entries in the directory (so,
1242 * collection->number_of_items may be valid after the call to
1243 * attach() returns).
1245 * If the directory was not in the cache (because it hadn't been
1246 * opened it before) then the types and icons for the entries are
1247 * not know, but the list of names is.
1250 attach(filer_window);
1252 number_of_windows++;
1253 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1255 return filer_window;
1258 /* This adds all the widgets to a new filer window. It is in a separate
1259 * function because filer_opendir() was getting too long...
1261 static void filer_add_widgets(FilerWindow *filer_window)
1263 GtkWidget *hbox, *vbox, *collection;
1265 /* Create the top-level window widget */
1266 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1267 filer_set_title(filer_window);
1269 /* This property is cleared when the window is destroyed.
1270 * You can thus ref filer_window->window and use this to see
1271 * if the window no longer exists.
1273 g_object_set_data(G_OBJECT(filer_window->window),
1274 "filer_window", filer_window);
1276 /* The view is the area that actually displays the files */
1277 filer_window->view = view_collection_new(filer_window);
1278 gtk_widget_show(filer_window->view);
1279 collection = GTK_WIDGET(filer_window->collection); /* XXX */
1280 g_object_set_data(G_OBJECT(collection), "filer_window", filer_window);
1282 /* Scrollbar on the right, everything else on the left */
1283 hbox = gtk_hbox_new(FALSE, 0);
1284 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1286 vbox = gtk_vbox_new(FALSE, 0);
1287 gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
1288 filer_window->toplevel_vbox = GTK_BOX(vbox);
1290 /* If we want a toolbar, create it now */
1291 toolbar_update_toolbar(filer_window);
1293 /* If there's a message that should be displayed in each window (eg
1294 * 'Running as root'), add it here.
1296 if (show_user_message)
1298 filer_window->message = gtk_label_new(show_user_message);
1299 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1300 FALSE, TRUE, 0);
1301 gtk_widget_show(filer_window->message);
1304 /* Now add the area for displaying the files.
1305 * The collection is one huge window that goes in a Viewport.
1307 gtk_box_pack_start_defaults(GTK_BOX(vbox), filer_window->view);
1308 filer_window->scrollbar =
1309 gtk_vscrollbar_new(filer_window->collection->vadj);
1311 /* And the minibuffer (hidden to start with) */
1312 create_minibuffer(filer_window);
1313 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1314 FALSE, TRUE, 0);
1316 /* And the thumbnail progress bar (also hidden) */
1318 GtkWidget *cancel;
1320 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1321 gtk_box_pack_start(GTK_BOX(vbox), filer_window->thumb_bar,
1322 FALSE, TRUE, 0);
1324 filer_window->thumb_progress = gtk_progress_bar_new();
1326 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1327 filer_window->thumb_progress, TRUE, TRUE, 0);
1329 cancel = gtk_button_new_with_label(_("Cancel"));
1330 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1331 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1332 cancel, FALSE, TRUE, 0);
1333 g_signal_connect_swapped(cancel, "clicked",
1334 G_CALLBACK(filer_cancel_thumbnails),
1335 filer_window);
1338 /* Put the scrollbar on the left of everything else... */
1339 gtk_box_pack_start(GTK_BOX(hbox),
1340 filer_window->scrollbar, FALSE, TRUE, 0);
1342 gtk_window_set_focus(GTK_WINDOW(filer_window->window), collection);
1344 gtk_widget_show(hbox);
1345 gtk_widget_show(vbox);
1346 gtk_widget_show(filer_window->scrollbar);
1347 gtk_widget_show(collection);
1349 gtk_widget_realize(filer_window->window);
1351 gdk_window_set_role(filer_window->window->window,
1352 filer_window->sym_path);
1354 filer_window_set_size(filer_window, 4, 4, TRUE);
1357 static void filer_add_signals(FilerWindow *filer_window)
1359 GtkObject *collection = GTK_OBJECT(filer_window->collection);
1360 GtkTargetEntry target_table[] =
1362 {"text/uri-list", 0, TARGET_URI_LIST},
1363 {"STRING", 0, TARGET_STRING},
1364 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1367 /* Events on the top-level window */
1368 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1369 g_signal_connect(filer_window->window, "enter-notify-event",
1370 G_CALLBACK(pointer_in), filer_window);
1371 g_signal_connect(filer_window->window, "leave-notify-event",
1372 G_CALLBACK(pointer_out), filer_window);
1373 g_signal_connect(filer_window->window, "destroy",
1374 G_CALLBACK(filer_window_destroyed), filer_window);
1376 /* Events on the collection widget */
1377 gtk_widget_set_events(GTK_WIDGET(collection),
1378 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
1379 GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_MASK |
1380 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1382 g_signal_connect(collection, "selection_get",
1383 G_CALLBACK(selection_get), NULL);
1384 gtk_selection_add_targets(GTK_WIDGET(collection), GDK_SELECTION_PRIMARY,
1385 target_table,
1386 sizeof(target_table) / sizeof(*target_table));
1388 g_signal_connect(collection, "key_press_event",
1389 G_CALLBACK(key_press_event), filer_window);
1391 /* Drag and drop events */
1392 g_signal_connect(collection, "drag_data_get",
1393 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1394 drag_set_dest(filer_window);
1397 static gint clear_scanning_display(FilerWindow *filer_window)
1399 if (filer_exists(filer_window))
1400 filer_set_title(filer_window);
1401 return FALSE;
1404 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1406 if (scanning == filer_window->scanning)
1407 return;
1408 filer_window->scanning = scanning;
1410 if (scanning)
1411 filer_set_title(filer_window);
1412 else
1413 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1414 filer_window);
1417 /* Note that filer_window may not exist after this call */
1418 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1420 if (may_rescan(filer_window, warning))
1421 dir_update(filer_window->directory, filer_window->sym_path);
1424 void filer_update_all(void)
1426 GList *next = all_filer_windows;
1428 while (next)
1430 FilerWindow *filer_window = (FilerWindow *) next->data;
1432 /* Updating directory may remove it from list -- stop sending
1433 * patches to move this line!
1435 next = next->next;
1437 filer_update_dir(filer_window, TRUE);
1441 /* Refresh the various caches even if we don't think we need to */
1442 void full_refresh(void)
1444 mount_update(TRUE);
1447 /* See whether a filer window with a given path already exists
1448 * and is different from diff.
1450 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1452 GList *next;
1454 for (next = all_filer_windows; next; next = next->next)
1456 FilerWindow *filer_window = (FilerWindow *) next->data;
1458 if (filer_window != diff &&
1459 strcmp(sym_path, filer_window->sym_path) == 0)
1460 return filer_window;
1463 return NULL;
1466 /* This path has been mounted/umounted/deleted some files - update all dirs */
1467 void filer_check_mounted(const char *real_path)
1469 GList *next = all_filer_windows;
1470 gchar *parent;
1471 int len;
1473 len = strlen(real_path);
1475 while (next)
1477 FilerWindow *filer_window = (FilerWindow *) next->data;
1479 next = next->next;
1481 if (strncmp(real_path, filer_window->real_path, len) == 0)
1483 char s = filer_window->real_path[len];
1485 if (s == '/' || s == '\0')
1486 filer_update_dir(filer_window, FALSE);
1490 parent = g_dirname(real_path);
1491 refresh_dirs(parent);
1492 g_free(parent);
1494 icons_may_update(real_path);
1497 /* Close all windows displaying 'path' or subdirectories of 'path' */
1498 void filer_close_recursive(const char *path)
1500 GList *next = all_filer_windows;
1501 gchar *real;
1502 int len;
1504 real = pathdup(path);
1505 len = strlen(real);
1507 while (next)
1509 FilerWindow *filer_window = (FilerWindow *) next->data;
1511 next = next->next;
1513 if (strncmp(real, filer_window->real_path, len) == 0)
1515 char s = filer_window->real_path[len];
1517 if (len == 1 || s == '/' || s == '\0')
1518 gtk_widget_destroy(filer_window->window);
1523 /* Like minibuffer_show(), except that:
1524 * - It returns FALSE (to be used from an idle callback)
1525 * - It checks that the filer window still exists.
1527 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1529 if (filer_exists(filer_window))
1530 minibuffer_show(filer_window, MINI_PATH);
1531 return FALSE;
1534 /* TRUE iff filer_window points to an existing FilerWindow
1535 * structure.
1537 gboolean filer_exists(FilerWindow *filer_window)
1539 GList *next;
1541 for (next = all_filer_windows; next; next = next->next)
1543 FilerWindow *fw = (FilerWindow *) next->data;
1545 if (fw == filer_window)
1546 return TRUE;
1549 return FALSE;
1552 /* Make sure the window title is up-to-date */
1553 void filer_set_title(FilerWindow *filer_window)
1555 guchar *title = NULL;
1556 guchar *flags = "";
1558 if (filer_window->scanning || filer_window->show_hidden ||
1559 filer_window->show_thumbs)
1561 if (o_short_flag_names.int_value)
1563 flags = g_strconcat(" +",
1564 filer_window->scanning ? _("S") : "",
1565 filer_window->show_hidden ? _("A") : "",
1566 filer_window->show_thumbs ? _("T") : "",
1567 NULL);
1569 else
1571 flags = g_strconcat(" (",
1572 filer_window->scanning ? _("Scanning, ") : "",
1573 filer_window->show_hidden ? _("All, ") : "",
1574 filer_window->show_thumbs ? _("Thumbs, ") : "",
1575 NULL);
1576 flags[strlen(flags) - 2] = ')';
1580 if (not_local)
1581 title = g_strconcat("//", our_host_name(),
1582 filer_window->sym_path, flags, NULL);
1584 if (!title && home_dir_len > 1 &&
1585 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1587 guchar sep = filer_window->sym_path[home_dir_len];
1589 if (sep == '\0' || sep == '/')
1590 title = g_strconcat("~",
1591 filer_window->sym_path + home_dir_len,
1592 flags,
1593 NULL);
1596 if (!title)
1597 title = g_strconcat(filer_window->sym_path, flags, NULL);
1599 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1600 g_free(title);
1602 if (flags[0] != '\0')
1603 g_free(flags);
1606 /* Reconnect to the same directory (used when the Show Hidden option is
1607 * toggled). This has the side-effect of updating the window title.
1609 void filer_detach_rescan(FilerWindow *filer_window)
1611 Directory *dir = filer_window->directory;
1613 g_object_ref(dir);
1614 detach(filer_window);
1615 filer_window->directory = dir;
1616 attach(filer_window);
1619 /* Puts the filer window into target mode. When an item is chosen,
1620 * fn(filer_window, item, data) is called. 'reason' will be displayed
1621 * on the toolbar while target mode is active.
1623 * Use fn == NULL to cancel target mode.
1625 void filer_target_mode(FilerWindow *filer_window,
1626 TargetFunc fn,
1627 gpointer data,
1628 const char *reason)
1630 TargetFunc old_fn = filer_window->target_cb;
1632 if (fn != old_fn)
1633 gdk_window_set_cursor(
1634 GTK_WIDGET(filer_window->collection)->window,
1635 fn ? crosshair : NULL);
1637 filer_window->target_cb = fn;
1638 filer_window->target_data = data;
1640 if (filer_window->toolbar_text == NULL)
1641 return;
1643 if (fn)
1644 gtk_label_set_text(
1645 GTK_LABEL(filer_window->toolbar_text), reason);
1646 else if (o_toolbar_info.int_value)
1648 if (old_fn)
1649 toolbar_update_info(filer_window);
1651 else
1652 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1655 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
1657 GtkStateType old_state = filer_window->selection_state;
1659 filer_window->selection_state = normal
1660 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
1662 if (old_state != filer_window->selection_state
1663 && filer_window->collection->number_selected)
1664 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
1667 void filer_cancel_thumbnails(FilerWindow *filer_window)
1669 gtk_widget_hide(filer_window->thumb_bar);
1671 g_list_foreach(filer_window->thumb_queue, (GFunc) g_free, NULL);
1672 g_list_free(filer_window->thumb_queue);
1673 filer_window->thumb_queue = NULL;
1674 filer_window->max_thumbs = 0;
1677 /* Generate the next thumb for this window. The collection object is
1678 * unref'd when there is nothing more to do.
1679 * If the collection no longer has a filer window, nothing is done.
1681 static gboolean filer_next_thumb_real(GObject *window)
1683 FilerWindow *filer_window;
1684 gchar *path;
1685 int done, total;
1687 filer_window = g_object_get_data(window, "filer_window");
1689 if (!filer_window)
1691 g_object_unref(window);
1692 return FALSE;
1695 if (!filer_window->thumb_queue)
1697 filer_cancel_thumbnails(filer_window);
1698 g_object_unref(window);
1699 return FALSE;
1702 total = filer_window->max_thumbs;
1703 done = total - g_list_length(filer_window->thumb_queue);
1705 path = (gchar *) filer_window->thumb_queue->data;
1707 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
1709 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
1710 path);
1711 g_free(path);
1713 gtk_progress_bar_set_fraction(
1714 GTK_PROGRESS_BAR(filer_window->thumb_progress),
1715 done / (float) total);
1717 return FALSE;
1720 /* path is the thumb just loaded, if any.
1721 * collection is unref'd (eventually).
1723 static void filer_next_thumb(GObject *window, const gchar *path)
1725 if (path)
1726 dir_force_update_path(path);
1728 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
1731 static void start_thumb_scanning(FilerWindow *filer_window)
1733 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
1734 return; /* Already scanning */
1736 gtk_widget_show_all(filer_window->thumb_bar);
1738 g_object_ref(G_OBJECT(filer_window->window));
1739 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
1742 /* Set this image to be loaded some time in the future */
1743 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
1745 filer_window->max_thumbs++;
1747 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
1748 g_strdup(path));
1750 if (filer_window->scanning)
1751 return; /* Will start when scan ends */
1753 start_thumb_scanning(filer_window);
1756 /* If thumbnail display is on, look through all the items in this directory
1757 * and start creating or updating the thumbnails as needed.
1759 void filer_create_thumbs(FilerWindow *filer_window)
1761 Collection *collection = filer_window->collection;
1762 int i;
1764 if (!filer_window->show_thumbs)
1765 return;
1767 for (i = 0; i < collection->number_of_items; i++)
1769 MaskedPixmap *pixmap;
1770 DirItem *item = (DirItem *) collection->items[i].data;
1771 gchar *path;
1772 gboolean found;
1774 if (item->base_type != TYPE_FILE)
1775 continue;
1777 if (strcmp(item->mime_type->media_type, "image") != 0)
1778 continue;
1780 path = make_path(filer_window->real_path, item->leafname)->str;
1782 pixmap = g_fscache_lookup_full(pixmap_cache, path,
1783 FSCACHE_LOOKUP_ONLY_NEW, &found);
1784 if (pixmap)
1785 g_object_unref(pixmap);
1787 /* If we didn't get an image, it could be because:
1789 * - We're loading the image now. found is TRUE,
1790 * and we'll update the item later.
1791 * - We tried to load the image and failed. found
1792 * is TRUE.
1793 * - We haven't tried loading the image. found is
1794 * FALSE, and we start creating the thumb here.
1796 if (!found)
1797 filer_create_thumb(filer_window, path);
1801 static void filer_options_changed(void)
1803 if (o_short_flag_names.has_changed)
1805 GList *next;
1807 for (next = all_filer_windows; next; next = next->next)
1809 FilerWindow *filer_window = (FilerWindow *) next->data;
1811 filer_set_title(filer_window);
1816 /* Change to Large or Small icons depending on the number of items
1817 * in the directory, subject to options.
1819 static void set_style_by_number_of_items(FilerWindow *filer_window)
1821 int n;
1823 g_return_if_fail(filer_window != NULL);
1825 if (!o_filer_change_size.int_value)
1826 return; /* Don't auto-set style */
1828 if (filer_window->display_style != LARGE_ICONS &&
1829 filer_window->display_style != SMALL_ICONS)
1830 return; /* Only change between these two styles */
1832 n = filer_window->collection->number_of_items;
1834 if (n >= o_filer_change_size_num.int_value)
1835 display_set_layout(filer_window, SMALL_ICONS,
1836 filer_window->details_type);
1837 else
1838 display_set_layout(filer_window, LARGE_ICONS,
1839 filer_window->details_type);
1842 /* Append interesting information to this GString */
1843 void filer_add_tip_details(FilerWindow *filer_window,
1844 GString *tip, DirItem *item)
1846 guchar *fullpath = NULL;
1848 fullpath = make_path(filer_window->real_path, item->leafname)->str;
1850 if (item->flags & ITEM_FLAG_SYMLINK)
1852 char *target;
1854 target = readlink_dup(fullpath);
1855 if (target)
1857 g_string_append(tip, _("Symbolic link to "));
1858 g_string_append(tip, target);
1859 g_string_append_c(tip, '\n');
1860 g_free(target);
1864 if (item->flags & ITEM_FLAG_APPDIR)
1866 XMLwrapper *info;
1867 xmlNode *node;
1869 info = appinfo_get(fullpath, item);
1870 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
1872 guchar *str;
1873 str = xmlNodeListGetString(node->doc,
1874 node->xmlChildrenNode, 1);
1875 if (str)
1877 g_string_append(tip, str);
1878 g_string_append_c(tip, '\n');
1879 g_free(str);
1882 if (info)
1883 g_object_unref(info);
1886 if (!g_utf8_validate(item->leafname, -1, NULL))
1887 g_string_append(tip,
1888 _("This filename is not valid UTF-8. "
1889 "You should rename it.\n"));