r2219: Removed warning message from --new. It can be useful for a session manager...
[rox-filer.git] / ROX-Filer / src / filer.c
blob400551af11963b44fe168d55d21d97882093d9ac
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 <netdb.h>
32 #include <sys/param.h>
34 #include <gtk/gtk.h>
35 #include <gdk/gdkx.h>
36 #include <gdk/gdkkeysyms.h>
38 #include "global.h"
40 #include "display.h"
41 #include "main.h"
42 #include "fscache.h"
43 #include "support.h"
44 #include "gui_support.h"
45 #include "filer.h"
46 #include "choices.h"
47 #include "pixmaps.h"
48 #include "menu.h"
49 #include "dnd.h"
50 #include "dir.h"
51 #include "diritem.h"
52 #include "run.h"
53 #include "type.h"
54 #include "options.h"
55 #include "minibuffer.h"
56 #include "icon.h"
57 #include "toolbar.h"
58 #include "bind.h"
59 #include "appinfo.h"
60 #include "mount.h"
61 #include "xml.h"
62 #include "view_iface.h"
63 #include "view_collection.h"
64 #include "view_details.h"
65 #include "action.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, const gchar *wm_class);
92 static void filer_add_signals(FilerWindow *filer_window);
94 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
95 static void filer_next_thumb(GObject *window, const gchar *path);
96 static void start_thumb_scanning(FilerWindow *filer_window);
97 static void filer_options_changed(void);
98 static void set_style_by_number_of_items(FilerWindow *filer_window);
100 static GdkCursor *busy_cursor = NULL;
101 static GdkCursor *crosshair = NULL;
103 /* Indicates whether the filer's display is different to the machine it
104 * is actually running on.
106 static gboolean not_local = FALSE;
108 static Option o_filer_change_size, o_filer_change_size_num;
109 static Option o_short_flag_names;
110 Option o_filer_auto_resize, o_unique_filer_windows;
111 Option o_filer_size_limit;
113 void filer_init(void)
115 const gchar *ohost;
116 const gchar *dpy;
117 gchar *dpyhost, *tmp;
119 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
120 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
121 RESIZE_ALWAYS);
122 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
124 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
126 option_add_int(&o_filer_change_size, "filer_change_size", TRUE);
127 option_add_int(&o_filer_change_size_num, "filer_change_size_num", 30);
129 option_add_notify(filer_options_changed);
131 busy_cursor = gdk_cursor_new(GDK_WATCH);
132 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
134 /* Is the display on the local machine, or are we being
135 * run remotely? See filer_set_title().
137 ohost = our_host_name();
138 dpy = gdk_get_display();
139 dpyhost = g_strdup(dpy);
140 tmp = strchr(dpyhost, ':');
141 if (tmp)
142 *tmp = '\0';
144 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
146 /* Try the cannonical name for dpyhost (see our_host_name()
147 * in support.c).
149 struct hostent *ent;
151 ent = gethostbyname(dpyhost);
152 if (!ent || strcmp(ohost, ent->h_name) != 0)
153 not_local = TRUE;
156 g_free(dpyhost);
159 static gboolean if_deleted(gpointer item, gpointer removed)
161 int i = ((GPtrArray *) removed)->len;
162 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
163 char *leafname = ((DirItem *) item)->leafname;
165 while (i--)
167 if (strcmp(leafname, r[i]->leafname) == 0)
168 return TRUE;
171 return FALSE;
174 /* Resize the filer window to w x h pixels, plus border (not clamped).
175 * If triggered by a key event, warp the pointer (for SloppyFocus users).
177 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
179 GdkEvent *event;
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 (x + w > screen_width || y + h > screen_height)
206 if (x + w > screen_width)
207 x = screen_width - w - 4;
208 if (y + h > screen_height)
209 y = screen_height - h - 4;
210 gdk_window_move_resize(gdk_window, x, y, w, h);
212 else
213 gdk_window_resize(gdk_window, w, h);
215 else
216 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
218 event = gtk_get_current_event();
219 if (event && event->type == GDK_KEY_PRESS)
221 GdkWindow *win = filer_window->window->window;
223 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
224 None,
225 gdk_x11_drawable_get_xid(win),
226 0, 0, 0, 0,
227 w - 4, h - 4);
231 /* Resize the window to fit the items currently in the Directory.
232 * When opening a directory for the first time, the names will be known but not
233 * the types and images. We can still make a good estimate of the size.
235 void filer_window_autosize(FilerWindow *filer_window)
237 view_autosize(filer_window->view);
240 /* Called on a timeout while scanning or when scanning ends
241 * (whichever happens first).
243 static gint open_filer_window(FilerWindow *filer_window)
245 view_style_changed(filer_window->view, 0);
247 if (filer_window->open_timeout)
249 gtk_timeout_remove(filer_window->open_timeout);
250 filer_window->open_timeout = 0;
253 if (!GTK_WIDGET_VISIBLE(filer_window->window))
255 set_style_by_number_of_items(filer_window);
256 filer_window_autosize(filer_window);
257 gtk_widget_show(filer_window->window);
260 return FALSE;
263 static void update_display(Directory *dir,
264 DirAction action,
265 GPtrArray *items,
266 FilerWindow *filer_window)
268 ViewIface *view = (ViewIface *) filer_window->view;
270 switch (action)
272 case DIR_ADD:
273 view_add_items(view, items);
274 /* Open and resize if currently hidden */
275 open_filer_window(filer_window);
276 break;
277 case DIR_REMOVE:
278 view_delete_if(view, if_deleted, items);
279 toolbar_update_info(filer_window);
280 break;
281 case DIR_START_SCAN:
282 set_scanning_display(filer_window, TRUE);
283 toolbar_update_info(filer_window);
284 break;
285 case DIR_END_SCAN:
286 if (filer_window->window->window)
287 gdk_window_set_cursor(
288 filer_window->window->window,
289 NULL);
290 set_scanning_display(filer_window, FALSE);
291 toolbar_update_info(filer_window);
292 open_filer_window(filer_window);
294 if (filer_window->had_cursor &&
295 !view_cursor_visible(view))
297 ViewIter start;
298 view_get_iter(view, &start, 0);
299 if (start.next(&start))
300 view_cursor_to_iter(view, &start);
301 view_show_cursor(view);
302 filer_window->had_cursor = FALSE;
304 if (filer_window->auto_select)
305 display_set_autoselect(filer_window,
306 filer_window->auto_select);
307 null_g_free(&filer_window->auto_select);
309 filer_create_thumbs(filer_window);
311 if (filer_window->thumb_queue)
312 start_thumb_scanning(filer_window);
313 break;
314 case DIR_UPDATE:
315 view_update_items(view, items);
316 break;
320 static void attach(FilerWindow *filer_window)
322 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
323 view_clear(filer_window->view);
324 filer_window->scanning = TRUE;
325 dir_attach(filer_window->directory, (DirCallback) update_display,
326 filer_window);
327 filer_set_title(filer_window);
330 static void detach(FilerWindow *filer_window)
332 g_return_if_fail(filer_window->directory != NULL);
334 dir_detach(filer_window->directory,
335 (DirCallback) update_display, filer_window);
336 g_object_unref(filer_window->directory);
337 filer_window->directory = NULL;
340 /* Returns TRUE to prevent closing the window. May offer to unmount a
341 * device.
343 gboolean filer_window_delete(GtkWidget *window,
344 GdkEvent *unused, /* (may be NULL) */
345 FilerWindow *filer_window)
347 if (mount_is_user_mounted(filer_window->real_path))
349 int action;
351 action = get_choice(PROJECT,
352 _("Do you want to unmount this device?\n\n"
353 "Unmounting a device makes it safe to remove "
354 "the disk."), 3,
355 GTK_STOCK_CANCEL, NULL,
356 GTK_STOCK_CLOSE, NULL,
357 ROX_STOCK_MOUNT, _("Unmount"));
359 if (action == 0)
360 return TRUE; /* Cancel close operation */
362 if (action == 2)
364 GList *list;
366 list = g_list_prepend(NULL, filer_window->sym_path);
367 action_mount(list, FALSE, TRUE);
368 g_list_free(list);
372 return FALSE;
375 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
377 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
379 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
381 if (window_with_primary == filer_window)
382 window_with_primary = NULL;
384 if (window_with_focus == filer_window)
386 menu_popdown();
387 window_with_focus = NULL;
390 if (filer_window->directory)
391 detach(filer_window);
393 if (filer_window->open_timeout)
395 gtk_timeout_remove(filer_window->open_timeout);
396 filer_window->open_timeout = 0;
399 if (filer_window->thumb_queue)
400 destroy_glist(&filer_window->thumb_queue);
402 tooltip_show(NULL);
404 g_free(filer_window->auto_select);
405 g_free(filer_window->real_path);
406 g_free(filer_window->sym_path);
407 g_free(filer_window);
409 one_less_window();
412 /* Returns TRUE iff the directory still exists. */
413 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
415 Directory *dir;
417 g_return_val_if_fail(filer_window != NULL, FALSE);
419 /* We do a fresh lookup (rather than update) because the inode may
420 * have changed.
422 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
423 if (!dir)
425 if (warning)
426 info_message(_("Directory missing/deleted"));
427 gtk_widget_destroy(filer_window->window);
428 return FALSE;
430 if (dir == filer_window->directory)
431 g_object_unref(dir);
432 else
434 detach(filer_window);
435 filer_window->directory = dir;
436 attach(filer_window);
439 return TRUE;
442 /* No items are now selected. This might be because another app claimed
443 * the selection or because the user unselected all the items.
445 void filer_lost_selection(FilerWindow *filer_window, guint time)
447 if (window_with_primary == filer_window)
449 window_with_primary = NULL;
450 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
454 /* Another app has claimed the primary selection */
455 static void filer_lost_primary(GtkWidget *window,
456 GdkEventSelection *event,
457 gpointer user_data)
459 FilerWindow *filer_window = (FilerWindow *) user_data;
461 if (window_with_primary && window_with_primary == filer_window)
463 window_with_primary = NULL;
464 set_selection_state(filer_window, FALSE);
468 /* Someone wants us to send them the selection */
469 static void selection_get(GtkWidget *widget,
470 GtkSelectionData *selection_data,
471 guint info,
472 guint time,
473 gpointer data)
475 GString *reply, *header;
476 FilerWindow *filer_window = (FilerWindow *) data;
477 ViewIter iter;
478 DirItem *item;
480 reply = g_string_new(NULL);
481 header = g_string_new(NULL);
483 switch (info)
485 case TARGET_STRING:
486 g_string_printf(header, " %s",
487 make_path(filer_window->sym_path, ""));
488 break;
489 case TARGET_URI_LIST:
490 g_string_printf(header, " file://%s%s",
491 our_host_name_for_dnd(),
492 make_path(filer_window->sym_path, ""));
493 break;
496 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
498 while ((item = iter.next(&iter)))
500 g_string_append(reply, header->str);
501 g_string_append(reply, item->leafname);
504 if (reply->len > 0)
505 gtk_selection_data_set(selection_data, xa_string,
506 8, reply->str + 1, reply->len - 1);
507 else
509 g_warning("Attempt to paste empty selection!");
510 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
513 g_string_free(reply, TRUE);
514 g_string_free(header, TRUE);
517 /* Selection has been changed -- try to grab the primary selection
518 * if we don't have it. Also called when clicking on an insensitive selection
519 * to regain primary.
520 * Also updates toolbar info.
522 void filer_selection_changed(FilerWindow *filer_window, gint time)
524 toolbar_update_info(filer_window);
526 if (window_with_primary == filer_window)
527 return; /* Already got primary */
529 if (!view_count_selected(filer_window->view))
530 return; /* Nothing selected */
532 if (filer_window->temp_item_selected == FALSE &&
533 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
534 GDK_SELECTION_PRIMARY,
535 time))
537 window_with_primary = filer_window;
538 set_selection_state(filer_window, TRUE);
540 else
541 set_selection_state(filer_window, FALSE);
544 /* Open the item (or add it to the shell command minibuffer) */
545 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
547 gboolean shift = (flags & OPEN_SHIFT) != 0;
548 gboolean close_mini = flags & OPEN_FROM_MINI;
549 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
550 DirItem *item;
551 const guchar *full_path;
552 gboolean wink = TRUE;
553 Directory *old_dir;
555 g_return_if_fail(filer_window != NULL && iter != NULL);
557 item = iter->peek(iter);
559 g_return_if_fail(item != NULL);
561 if (filer_window->mini_type == MINI_SHELL)
563 minibuffer_add(filer_window, item->leafname);
564 return;
567 if (!item->image)
568 dir_update_item(filer_window->directory, item->leafname);
570 if (item->base_type == TYPE_DIRECTORY)
572 /* Never close a filer window when opening a directory
573 * (click on a dir or click on an app with shift).
575 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
576 close_window = FALSE;
579 full_path = make_path(filer_window->sym_path, item->leafname);
580 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
581 wink = FALSE;
583 old_dir = filer_window->directory;
584 if (run_diritem(full_path, item,
585 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
586 filer_window,
587 shift))
589 if (old_dir != filer_window->directory)
590 return;
592 if (close_window)
593 gtk_widget_destroy(filer_window->window);
594 else
596 if (wink)
597 view_wink_item(filer_window->view, iter);
598 if (close_mini)
599 minibuffer_hide(filer_window);
604 static gint pointer_in(GtkWidget *widget,
605 GdkEventCrossing *event,
606 FilerWindow *filer_window)
608 may_rescan(filer_window, TRUE);
609 return FALSE;
612 static gint pointer_out(GtkWidget *widget,
613 GdkEventCrossing *event,
614 FilerWindow *filer_window)
616 tooltip_show(NULL);
617 return FALSE;
620 /* Move the cursor to the next selected item in direction 'dir'
621 * (+1 or -1).
623 static void next_selected(FilerWindow *filer_window, int dir)
625 ViewIter iter, cursor;
626 gboolean have_cursor;
627 ViewIface *view = filer_window->view;
629 g_return_if_fail(dir == 1 || dir == -1);
631 view_get_cursor(view, &cursor);
632 have_cursor = cursor.peek(&cursor) != NULL;
634 view_get_iter(view, &iter,
635 VIEW_ITER_SELECTED |
636 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
637 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
639 if (have_cursor && view_get_selected(view, &cursor))
640 iter.next(&iter); /* Skip the cursor itself */
642 if (iter.next(&iter))
643 view_cursor_to_iter(view, &iter);
644 else
645 gdk_beep();
647 return;
650 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
652 TargetFunc cb = filer_window->target_cb;
653 gpointer data = filer_window->target_data;
654 OpenFlags flags = 0;
655 ViewIter iter;
657 filer_target_mode(filer_window, NULL, NULL, NULL);
659 view_get_cursor(filer_window->view, &iter);
660 if (!iter.peek(&iter))
661 return;
663 if (cb)
665 cb(filer_window, &iter, data);
666 return;
669 if (event->state & GDK_SHIFT_MASK)
670 flags |= OPEN_SHIFT;
671 if (event->state & GDK_MOD1_MASK)
672 flags |= OPEN_CLOSE_WINDOW;
673 else
674 flags |= OPEN_SAME_WINDOW;
676 filer_openitem(filer_window, &iter, flags);
679 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
680 * changed. If no groups were loaded and there is no file then initialised
681 * groups to an empty document.
682 * Return the node for the 'name' group.
684 static xmlNode *group_find(char *name)
686 xmlNode *node;
687 gchar *path;
689 /* Update the groups, if possible */
690 path = choices_find_path_load("Groups.xml", PROJECT);
691 if (path)
693 XMLwrapper *wrapper;
694 wrapper = xml_cache_load(path);
695 if (wrapper)
697 if (groups)
698 g_object_unref(groups);
699 groups = wrapper;
702 g_free(path);
705 if (!groups)
707 groups = xml_new(NULL);
708 groups->doc = xmlNewDoc("1.0");
710 xmlDocSetRootElement(groups->doc,
711 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
712 return NULL;
715 node = xmlDocGetRootElement(groups->doc);
717 for (node = node->xmlChildrenNode; node; node = node->next)
719 guchar *gid;
721 gid = xmlGetProp(node, "name");
723 if (!gid)
724 continue;
726 if (strcmp(name, gid) != 0)
727 continue;
729 g_free(gid);
731 return node;
734 return NULL;
737 static void group_save(FilerWindow *filer_window, char *name)
739 xmlNode *group;
740 guchar *save_path;
741 DirItem *item;
742 ViewIter iter;
744 group = group_find(name);
745 if (group)
747 xmlUnlinkNode(group);
748 xmlFreeNode(group);
750 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
751 NULL, "group", NULL);
752 xmlSetProp(group, "name", name);
754 xmlNewChild(group, NULL, "directory", filer_window->sym_path);
756 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
758 while ((item = iter.next(&iter)))
759 xmlNewChild(group, NULL, "item", item->leafname);
761 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
762 if (save_path)
764 save_xml_file(groups->doc, save_path);
765 g_free(save_path);
769 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
771 GHashTable *in_group = (GHashTable *) data;
773 return g_hash_table_lookup(in_group,
774 iter->peek(iter)->leafname) != NULL;
777 static void group_restore(FilerWindow *filer_window, char *name)
779 GHashTable *in_group;
780 char *path;
781 xmlNode *group, *node;
783 group = group_find(name);
785 if (!group)
787 report_error(_("Group %s is not set. Select some files "
788 "and press Ctrl+%s to set the group. Press %s "
789 "on its own to reselect the files later.\n"
790 "Make sure NumLock is on if you use the keypad."),
791 name, name, name);
792 return;
795 node = get_subnode(group, NULL, "directory");
796 g_return_if_fail(node != NULL);
797 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
798 g_return_if_fail(path != NULL);
800 if (strcmp(path, filer_window->sym_path) != 0)
801 filer_change_to(filer_window, path, NULL);
802 g_free(path);
804 in_group = g_hash_table_new(g_str_hash, g_str_equal);
805 for (node = group->xmlChildrenNode; node; node = node->next)
807 gchar *leaf;
808 if (node->type != XML_ELEMENT_NODE)
809 continue;
810 if (strcmp(node->name, "item") != 0)
811 continue;
813 leaf = xmlNodeListGetString(groups->doc,
814 node->xmlChildrenNode, 1);
815 if (!leaf)
816 g_warning("Missing leafname!\n");
817 else
818 g_hash_table_insert(in_group, leaf, filer_window);
821 view_select_if(filer_window->view, &group_restore_cb, in_group);
823 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
824 g_hash_table_destroy(in_group);
827 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
829 ViewIter iter;
831 view_get_cursor(filer_window->view, &iter);
833 show_filer_menu(filer_window, NULL, &iter);
835 return TRUE;
838 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
840 ViewIface *view = filer_window->view;
841 ViewIter iter;
843 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
844 if (!iter.next(&iter))
845 return; /* No cursor */
847 if (view_get_selected(view, &iter))
848 view_set_selected(view, &iter, FALSE);
849 else
850 view_set_selected(view, &iter, TRUE);
852 if (iter.next(&iter))
853 view_cursor_to_iter(view, &iter);
856 /* Handle keys that can't be bound with the menu */
857 static gint key_press_event(GtkWidget *widget,
858 GdkEventKey *event,
859 FilerWindow *filer_window)
861 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
862 guint key = event->keyval;
863 char group[2] = "1";
865 window_with_focus = filer_window;
867 /* Delay setting up the keys until now to speed loading... */
868 if (!filer_keys)
869 ensure_filer_menu(); /* Gets the keys working... */
871 if (!g_slist_find(filer_keys->acceleratables, widget))
872 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
873 filer_keys);
875 if (focus && focus != widget &&
876 gtk_widget_get_toplevel(focus) == widget)
877 if (gtk_widget_event(focus, (GdkEvent *) event))
878 return TRUE; /* Handled */
880 switch (key)
882 case GDK_Escape:
883 filer_target_mode(filer_window, NULL, NULL, NULL);
884 view_cursor_to_iter(filer_window->view, NULL);
885 view_clear_selection(filer_window->view);
886 return FALSE;
887 case GDK_Return:
888 return_pressed(filer_window, event);
889 break;
890 case GDK_ISO_Left_Tab:
891 next_selected(filer_window, -1);
892 break;
893 case GDK_Tab:
894 next_selected(filer_window, 1);
895 break;
896 case GDK_BackSpace:
897 change_to_parent(filer_window);
898 break;
899 case GDK_backslash:
901 ViewIter iter;
903 tooltip_show(NULL);
905 view_get_cursor(filer_window->view, &iter);
906 show_filer_menu(filer_window,
907 (GdkEvent *) event, &iter);
908 break;
910 case ' ':
911 filer_window_toggle_cursor_item_selected(filer_window);
912 break;
913 default:
914 if (key >= GDK_0 && key <= GDK_9)
915 group[0] = key - GDK_0 + '0';
916 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
917 group[0] = key - GDK_KP_0 + '0';
918 else
919 return FALSE;
922 if (event->state & GDK_CONTROL_MASK)
923 group_save(filer_window, group);
924 else
925 group_restore(filer_window, group);
928 return TRUE;
931 void filer_open_parent(FilerWindow *filer_window)
933 char *dir;
934 const char *current = filer_window->sym_path;
936 if (current[0] == '/' && current[1] == '\0')
937 return; /* Already in the root */
939 dir = g_path_get_dirname(current);
940 filer_opendir(dir, filer_window, NULL);
941 g_free(dir);
944 void change_to_parent(FilerWindow *filer_window)
946 char *dir;
947 const char *current = filer_window->sym_path;
949 if (current[0] == '/' && current[1] == '\0')
950 return; /* Already in the root */
952 dir = g_path_get_dirname(current);
953 filer_change_to(filer_window, dir, g_basename(current));
954 g_free(dir);
957 /* Removes trailing /s from path (modified in place) */
958 static void tidy_sympath(gchar *path)
960 int l;
962 g_return_if_fail(path != NULL);
964 l = strlen(path);
965 while (l > 1 && path[l - 1] == '/')
967 l--;
968 path[l] = '\0';
972 /* Make filer_window display path. When finished, highlight item 'from', or
973 * the first item if from is NULL. If there is currently no cursor then
974 * simply wink 'from' (if not NULL).
975 * If the cause was a key event and we resize, warp the pointer.
977 void filer_change_to(FilerWindow *filer_window,
978 const char *path, const char *from)
980 char *from_dup;
981 char *sym_path, *real_path;
982 Directory *new_dir;
984 g_return_if_fail(filer_window != NULL);
986 filer_cancel_thumbnails(filer_window);
988 tooltip_show(NULL);
990 sym_path = g_strdup(path);
991 real_path = pathdup(path);
992 new_dir = g_fscache_lookup(dir_cache, real_path);
994 if (!new_dir)
996 delayed_error(_("Directory '%s' is not accessible"),
997 sym_path);
998 g_free(real_path);
999 g_free(sym_path);
1000 return;
1003 if (o_unique_filer_windows.int_value)
1005 FilerWindow *fw;
1007 fw = find_filer_window(sym_path, filer_window);
1008 if (fw)
1009 gtk_widget_destroy(fw->window);
1012 from_dup = from && *from ? g_strdup(from) : NULL;
1014 detach(filer_window);
1015 g_free(filer_window->real_path);
1016 g_free(filer_window->sym_path);
1017 filer_window->real_path = real_path;
1018 filer_window->sym_path = sym_path;
1019 tidy_sympath(filer_window->sym_path);
1021 filer_window->directory = new_dir;
1023 g_free(filer_window->auto_select);
1024 filer_window->auto_select = from_dup;
1026 filer_window->had_cursor = filer_window->had_cursor ||
1027 view_cursor_visible(filer_window->view);
1029 filer_set_title(filer_window);
1030 if (filer_window->window->window)
1031 gdk_window_set_role(filer_window->window->window,
1032 filer_window->sym_path);
1033 view_cursor_to_iter(filer_window->view, NULL);
1035 attach(filer_window);
1037 set_style_by_number_of_items(filer_window);
1039 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1040 filer_window_autosize(filer_window);
1042 if (filer_window->mini_type == MINI_PATH)
1043 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1044 filer_window);
1047 /* Returns a list containing the full (sym) pathname of every selected item.
1048 * You must g_free() each item in the list.
1050 GList *filer_selected_items(FilerWindow *filer_window)
1052 GList *retval = NULL;
1053 guchar *dir = filer_window->sym_path;
1054 ViewIter iter;
1055 DirItem *item;
1057 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1058 while ((item = iter.next(&iter)))
1060 retval = g_list_prepend(retval,
1061 g_strdup(make_path(dir, item->leafname)));
1064 return g_list_reverse(retval);
1067 /* Return the single selected item. Error if nothing is selected. */
1068 DirItem *filer_selected_item(FilerWindow *filer_window)
1070 ViewIter iter;
1071 DirItem *item;
1073 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1075 item = iter.next(&iter);
1076 g_return_val_if_fail(item != NULL, NULL);
1077 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1079 return item;
1082 /* Creates and shows a new filer window.
1083 * If src_win != NULL then display options can be taken from that source window.
1084 * Returns the new filer window, or NULL on error.
1085 * Note: if unique windows is in use, may return an existing window.
1087 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1088 const gchar *wm_class)
1090 FilerWindow *filer_window;
1091 char *real_path;
1092 DisplayStyle dstyle;
1093 DetailsType dtype;
1095 /* Get the real pathname of the directory and copy it */
1096 real_path = pathdup(path);
1098 if (o_unique_filer_windows.int_value)
1100 FilerWindow *same_dir_window;
1102 same_dir_window = find_filer_window(path, NULL);
1104 if (same_dir_window)
1106 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1107 return same_dir_window;
1111 filer_window = g_new(FilerWindow, 1);
1112 filer_window->message = NULL;
1113 filer_window->minibuffer = NULL;
1114 filer_window->minibuffer_label = NULL;
1115 filer_window->minibuffer_area = NULL;
1116 filer_window->temp_show_hidden = FALSE;
1117 filer_window->sym_path = g_strdup(path);
1118 filer_window->real_path = real_path;
1119 filer_window->scanning = FALSE;
1120 filer_window->had_cursor = FALSE;
1121 filer_window->auto_select = NULL;
1122 filer_window->toolbar_text = NULL;
1123 filer_window->target_cb = NULL;
1124 filer_window->mini_type = MINI_NONE;
1125 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1126 filer_window->toolbar = NULL;
1127 filer_window->toplevel_vbox = NULL;
1128 filer_window->view = NULL;
1129 filer_window->scrollbar = NULL;
1131 tidy_sympath(filer_window->sym_path);
1133 /* Finds the entry for this directory in the dir cache, creating
1134 * a new one if needed. This does not cause a scan to start,
1135 * so if a new entry is created then it will be empty.
1137 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1138 if (!filer_window->directory)
1140 delayed_error(_("Directory '%s' not found."), path);
1141 g_free(filer_window->real_path);
1142 g_free(filer_window->sym_path);
1143 g_free(filer_window);
1144 return NULL;
1147 filer_window->temp_item_selected = FALSE;
1148 filer_window->flags = (FilerFlags) 0;
1149 filer_window->details_type = DETAILS_SUMMARY;
1150 filer_window->display_style = UNKNOWN_STYLE;
1151 filer_window->thumb_queue = NULL;
1152 filer_window->max_thumbs = 0;
1154 if (src_win && o_display_inherit_options.int_value)
1156 filer_window->sort_fn = src_win->sort_fn;
1157 dstyle = src_win->display_style;
1158 dtype = src_win->details_type;
1159 filer_window->show_hidden = src_win->show_hidden;
1160 filer_window->show_thumbs = src_win->show_thumbs;
1162 else
1164 int i = o_display_sort_by.int_value;
1165 filer_window->sort_fn = i == 0 ? sort_by_name :
1166 i == 1 ? sort_by_type :
1167 i == 2 ? sort_by_date :
1168 sort_by_size;
1170 dstyle = o_display_size.int_value;
1171 dtype = o_display_details.int_value;
1172 filer_window->show_hidden =
1173 o_display_show_hidden.int_value;
1174 filer_window->show_thumbs =
1175 o_display_show_thumbs.int_value;
1178 /* Add all the user-interface elements & realise */
1179 filer_add_widgets(filer_window, wm_class);
1180 if (src_win)
1181 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1182 GTK_WIN_POS_MOUSE);
1184 /* Connect to all the signal handlers */
1185 filer_add_signals(filer_window);
1187 display_set_layout(filer_window, dstyle, dtype);
1189 /* Open the window after a timeout, or when scanning stops.
1190 * Do this before attaching, because attach() might tell us to
1191 * stop scanning (if a scan isn't needed).
1193 filer_window->open_timeout = gtk_timeout_add(500,
1194 (GtkFunction) open_filer_window,
1195 filer_window);
1197 /* The view is created empty and then attach() is called, which
1198 * links the filer window to the entry in the directory cache we
1199 * looked up / created above.
1201 * The attach() function will immediately callback to the filer window
1202 * to deliver a list of all known entries in the directory (so,
1203 * the number of items will be known after attach() returns).
1205 * If the directory was not in the cache (because it hadn't been
1206 * opened it before) then the types and icons for the entries are
1207 * not know, but the list of names is.
1210 attach(filer_window);
1212 number_of_windows++;
1213 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1215 return filer_window;
1218 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1220 GtkWidget *view = NULL;
1221 Directory *dir = NULL;
1223 g_return_if_fail(filer_window != NULL);
1225 if (filer_window->view)
1227 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1228 filer_window->view = NULL;
1230 dir = filer_window->directory;
1231 g_object_ref(dir);
1232 detach(filer_window);
1235 switch (type)
1237 case VIEW_TYPE_COLLECTION:
1238 view = view_collection_new(filer_window);
1239 break;
1240 case VIEW_TYPE_DETAILS:
1241 view = view_details_new(filer_window);
1242 break;
1245 g_return_if_fail(view != NULL);
1247 filer_window->view = VIEW(view);
1248 filer_window->view_type = type;
1250 gtk_box_pack_start(filer_window->toplevel_vbox, view, TRUE, TRUE, 0);
1251 gtk_widget_show(view);
1253 if (dir)
1255 /* Only when changing type. Otherwise, will attach later. */
1256 filer_window->directory = dir;
1257 attach(filer_window);
1261 /* This adds all the widgets to a new filer window. It is in a separate
1262 * function because filer_opendir() was getting too long...
1264 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1266 GtkWidget *hbox, *vbox;
1268 /* Create the top-level window widget */
1269 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1270 filer_set_title(filer_window);
1271 if (wm_class)
1272 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1273 wm_class, PROJECT);
1275 /* This property is cleared when the window is destroyed.
1276 * You can thus ref filer_window->window and use this to see
1277 * if the window no longer exists.
1279 g_object_set_data(G_OBJECT(filer_window->window),
1280 "filer_window", filer_window);
1282 /* Create this now to make the Adjustment before the View */
1283 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1285 /* Scrollbar on the right, everything else on the left */
1286 hbox = gtk_hbox_new(FALSE, 0);
1287 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1289 vbox = gtk_vbox_new(FALSE, 0);
1290 gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
1291 filer_window->toplevel_vbox = GTK_BOX(vbox);
1293 filer_set_view_type(filer_window, VIEW_TYPE_COLLECTION);
1295 /* If we want a toolbar, create it now */
1296 toolbar_update_toolbar(filer_window);
1298 /* If there's a message that should be displayed in each window (eg
1299 * 'Running as root'), add it here.
1301 if (show_user_message)
1303 filer_window->message = gtk_label_new(show_user_message);
1304 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1305 FALSE, TRUE, 0);
1306 gtk_widget_show(filer_window->message);
1309 /* And the minibuffer (hidden to start with) */
1310 create_minibuffer(filer_window);
1311 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1312 FALSE, TRUE, 0);
1314 /* And the thumbnail progress bar (also hidden) */
1316 GtkWidget *cancel;
1318 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1319 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1320 FALSE, TRUE, 0);
1322 filer_window->thumb_progress = gtk_progress_bar_new();
1324 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1325 filer_window->thumb_progress, TRUE, TRUE, 0);
1327 cancel = gtk_button_new_with_label(_("Cancel"));
1328 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1329 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1330 cancel, FALSE, TRUE, 0);
1331 g_signal_connect_swapped(cancel, "clicked",
1332 G_CALLBACK(filer_cancel_thumbnails),
1333 filer_window);
1336 /* Put the scrollbar on the left of everything else... */
1337 gtk_box_pack_start(GTK_BOX(hbox),
1338 filer_window->scrollbar, FALSE, TRUE, 0);
1340 gtk_widget_show(hbox);
1341 gtk_widget_show(vbox);
1342 gtk_widget_show(filer_window->scrollbar);
1344 gtk_widget_realize(filer_window->window);
1346 gdk_window_set_role(filer_window->window->window,
1347 filer_window->sym_path);
1349 filer_window_set_size(filer_window, 4, 4);
1352 static void filer_add_signals(FilerWindow *filer_window)
1354 GtkTargetEntry target_table[] =
1356 {"text/uri-list", 0, TARGET_URI_LIST},
1357 {"STRING", 0, TARGET_STRING},
1358 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1361 /* Events on the top-level window */
1362 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1363 g_signal_connect(filer_window->window, "enter-notify-event",
1364 G_CALLBACK(pointer_in), filer_window);
1365 g_signal_connect(filer_window->window, "leave-notify-event",
1366 G_CALLBACK(pointer_out), filer_window);
1367 g_signal_connect(filer_window->window, "destroy",
1368 G_CALLBACK(filer_window_destroyed), filer_window);
1369 g_signal_connect(filer_window->window, "delete-event",
1370 G_CALLBACK(filer_window_delete), filer_window);
1372 g_signal_connect(filer_window->window, "selection_clear_event",
1373 G_CALLBACK(filer_lost_primary), filer_window);
1375 g_signal_connect(filer_window->window, "selection_get",
1376 G_CALLBACK(selection_get), filer_window);
1377 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1378 GDK_SELECTION_PRIMARY,
1379 target_table,
1380 sizeof(target_table) / sizeof(*target_table));
1382 g_signal_connect(filer_window->window, "popup-menu",
1383 G_CALLBACK(popup_menu), filer_window);
1384 g_signal_connect(filer_window->window, "key_press_event",
1385 G_CALLBACK(key_press_event), filer_window);
1388 static gint clear_scanning_display(FilerWindow *filer_window)
1390 if (filer_exists(filer_window))
1391 filer_set_title(filer_window);
1392 return FALSE;
1395 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1397 if (scanning == filer_window->scanning)
1398 return;
1399 filer_window->scanning = scanning;
1401 if (scanning)
1402 filer_set_title(filer_window);
1403 else
1404 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1405 filer_window);
1408 /* Note that filer_window may not exist after this call */
1409 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1411 if (may_rescan(filer_window, warning))
1412 dir_update(filer_window->directory, filer_window->sym_path);
1415 void filer_update_all(void)
1417 GList *next = all_filer_windows;
1419 while (next)
1421 FilerWindow *filer_window = (FilerWindow *) next->data;
1423 /* Updating directory may remove it from list -- stop sending
1424 * patches to move this line!
1426 next = next->next;
1428 filer_update_dir(filer_window, TRUE);
1432 /* Refresh the various caches even if we don't think we need to */
1433 void full_refresh(void)
1435 mount_update(TRUE);
1436 reread_mime_files();
1439 /* See whether a filer window with a given path already exists
1440 * and is different from diff.
1442 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1444 GList *next;
1446 for (next = all_filer_windows; next; next = next->next)
1448 FilerWindow *filer_window = (FilerWindow *) next->data;
1450 if (filer_window != diff &&
1451 strcmp(sym_path, filer_window->sym_path) == 0)
1452 return filer_window;
1455 return NULL;
1458 /* This path has been mounted/umounted/deleted some files - update all dirs */
1459 void filer_check_mounted(const char *real_path)
1461 GList *next = all_filer_windows;
1462 gchar *parent;
1463 int len;
1465 len = strlen(real_path);
1467 while (next)
1469 FilerWindow *filer_window = (FilerWindow *) next->data;
1471 next = next->next;
1473 if (strncmp(real_path, filer_window->real_path, len) == 0)
1475 char s = filer_window->real_path[len];
1477 if (s == '/' || s == '\0')
1478 filer_update_dir(filer_window, FALSE);
1482 parent = g_path_get_dirname(real_path);
1483 refresh_dirs(parent);
1484 g_free(parent);
1486 icons_may_update(real_path);
1489 /* Close all windows displaying 'path' or subdirectories of 'path' */
1490 void filer_close_recursive(const char *path)
1492 GList *next = all_filer_windows;
1493 gchar *real;
1494 int len;
1496 real = pathdup(path);
1497 len = strlen(real);
1499 while (next)
1501 FilerWindow *filer_window = (FilerWindow *) next->data;
1503 next = next->next;
1505 if (strncmp(real, filer_window->real_path, len) == 0)
1507 char s = filer_window->real_path[len];
1509 if (len == 1 || s == '/' || s == '\0')
1510 gtk_widget_destroy(filer_window->window);
1515 /* Like minibuffer_show(), except that:
1516 * - It returns FALSE (to be used from an idle callback)
1517 * - It checks that the filer window still exists.
1519 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1521 if (filer_exists(filer_window))
1522 minibuffer_show(filer_window, MINI_PATH);
1523 return FALSE;
1526 /* TRUE iff filer_window points to an existing FilerWindow
1527 * structure.
1529 gboolean filer_exists(FilerWindow *filer_window)
1531 GList *next;
1533 for (next = all_filer_windows; next; next = next->next)
1535 FilerWindow *fw = (FilerWindow *) next->data;
1537 if (fw == filer_window)
1538 return TRUE;
1541 return FALSE;
1544 /* Make sure the window title is up-to-date */
1545 void filer_set_title(FilerWindow *filer_window)
1547 gchar *title = NULL;
1548 guchar *flags = "";
1550 if (filer_window->scanning || filer_window->show_hidden ||
1551 filer_window->show_thumbs)
1553 if (o_short_flag_names.int_value)
1555 flags = g_strconcat(" +",
1556 filer_window->scanning ? _("S") : "",
1557 filer_window->show_hidden ? _("A") : "",
1558 filer_window->show_thumbs ? _("T") : "",
1559 NULL);
1561 else
1563 flags = g_strconcat(" (",
1564 filer_window->scanning ? _("Scanning, ") : "",
1565 filer_window->show_hidden ? _("All, ") : "",
1566 filer_window->show_thumbs ? _("Thumbs, ") : "",
1567 NULL);
1568 flags[strlen(flags) - 2] = ')';
1572 if (not_local)
1573 title = g_strconcat("//", our_host_name(),
1574 filer_window->sym_path, flags, NULL);
1576 if (!title && home_dir_len > 1 &&
1577 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1579 guchar sep = filer_window->sym_path[home_dir_len];
1581 if (sep == '\0' || sep == '/')
1582 title = g_strconcat("~",
1583 filer_window->sym_path + home_dir_len,
1584 flags,
1585 NULL);
1588 if (!title)
1589 title = g_strconcat(filer_window->sym_path, flags, NULL);
1591 ensure_utf8(&title);
1593 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1595 g_free(title);
1597 if (flags[0] != '\0')
1598 g_free(flags);
1601 /* Reconnect to the same directory (used when the Show Hidden option is
1602 * toggled). This has the side-effect of updating the window title.
1604 void filer_detach_rescan(FilerWindow *filer_window)
1606 Directory *dir = filer_window->directory;
1608 g_object_ref(dir);
1609 detach(filer_window);
1610 filer_window->directory = dir;
1611 attach(filer_window);
1614 /* Puts the filer window into target mode. When an item is chosen,
1615 * fn(filer_window, iter, data) is called. 'reason' will be displayed
1616 * on the toolbar while target mode is active.
1618 * Use fn == NULL to cancel target mode.
1620 void filer_target_mode(FilerWindow *filer_window,
1621 TargetFunc fn,
1622 gpointer data,
1623 const char *reason)
1625 TargetFunc old_fn = filer_window->target_cb;
1627 if (fn != old_fn)
1628 gdk_window_set_cursor(
1629 GTK_WIDGET(filer_window->view)->window,
1630 fn ? crosshair : NULL);
1632 filer_window->target_cb = fn;
1633 filer_window->target_data = data;
1635 if (filer_window->toolbar_text == NULL)
1636 return;
1638 if (fn)
1639 gtk_label_set_text(
1640 GTK_LABEL(filer_window->toolbar_text), reason);
1641 else if (o_toolbar_info.int_value)
1643 if (old_fn)
1644 toolbar_update_info(filer_window);
1646 else
1647 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1650 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
1652 GtkStateType old_state = filer_window->selection_state;
1654 filer_window->selection_state = normal
1655 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
1657 if (old_state != filer_window->selection_state
1658 && view_count_selected(filer_window->view))
1659 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
1662 void filer_cancel_thumbnails(FilerWindow *filer_window)
1664 gtk_widget_hide(filer_window->thumb_bar);
1666 destroy_glist(&filer_window->thumb_queue);
1667 filer_window->max_thumbs = 0;
1670 /* Generate the next thumb for this window. The window object is
1671 * unref'd when there is nothing more to do.
1672 * If the window no longer has a filer window, nothing is done.
1674 static gboolean filer_next_thumb_real(GObject *window)
1676 FilerWindow *filer_window;
1677 gchar *path;
1678 int done, total;
1680 filer_window = g_object_get_data(window, "filer_window");
1682 if (!filer_window)
1684 g_object_unref(window);
1685 return FALSE;
1688 if (!filer_window->thumb_queue)
1690 filer_cancel_thumbnails(filer_window);
1691 g_object_unref(window);
1692 return FALSE;
1695 total = filer_window->max_thumbs;
1696 done = total - g_list_length(filer_window->thumb_queue);
1698 path = (gchar *) filer_window->thumb_queue->data;
1700 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
1702 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
1703 path);
1704 g_free(path);
1706 gtk_progress_bar_set_fraction(
1707 GTK_PROGRESS_BAR(filer_window->thumb_progress),
1708 done / (float) total);
1710 return FALSE;
1713 /* path is the thumb just loaded, if any.
1714 * window is unref'd (eventually).
1716 static void filer_next_thumb(GObject *window, const gchar *path)
1718 if (path)
1719 dir_force_update_path(path);
1721 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
1724 static void start_thumb_scanning(FilerWindow *filer_window)
1726 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
1727 return; /* Already scanning */
1729 gtk_widget_show_all(filer_window->thumb_bar);
1731 g_object_ref(G_OBJECT(filer_window->window));
1732 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
1735 /* Set this image to be loaded some time in the future */
1736 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
1738 filer_window->max_thumbs++;
1740 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
1741 g_strdup(path));
1743 if (filer_window->scanning)
1744 return; /* Will start when scan ends */
1746 start_thumb_scanning(filer_window);
1749 /* If thumbnail display is on, look through all the items in this directory
1750 * and start creating or updating the thumbnails as needed.
1752 void filer_create_thumbs(FilerWindow *filer_window)
1754 DirItem *item;
1755 ViewIter iter;
1757 if (!filer_window->show_thumbs)
1758 return;
1760 view_get_iter(filer_window->view, &iter, 0);
1762 while ((item = iter.next(&iter)))
1764 MaskedPixmap *pixmap;
1765 const guchar *path;
1766 gboolean found;
1768 if (item->base_type != TYPE_FILE)
1769 continue;
1771 if (strcmp(item->mime_type->media_type, "image") != 0)
1772 continue;
1774 path = make_path(filer_window->real_path, item->leafname);
1776 pixmap = g_fscache_lookup_full(pixmap_cache, path,
1777 FSCACHE_LOOKUP_ONLY_NEW, &found);
1778 if (pixmap)
1779 g_object_unref(pixmap);
1781 /* If we didn't get an image, it could be because:
1783 * - We're loading the image now. found is TRUE,
1784 * and we'll update the item later.
1785 * - We tried to load the image and failed. found
1786 * is TRUE.
1787 * - We haven't tried loading the image. found is
1788 * FALSE, and we start creating the thumb here.
1790 if (!found)
1791 filer_create_thumb(filer_window, path);
1795 static void filer_options_changed(void)
1797 if (o_short_flag_names.has_changed)
1799 GList *next;
1801 for (next = all_filer_windows; next; next = next->next)
1803 FilerWindow *filer_window = (FilerWindow *) next->data;
1805 filer_set_title(filer_window);
1810 /* Change to Large or Small icons depending on the number of items
1811 * in the directory, subject to options.
1813 static void set_style_by_number_of_items(FilerWindow *filer_window)
1815 int n;
1817 g_return_if_fail(filer_window != NULL);
1819 if (!o_filer_change_size.int_value)
1820 return; /* Don't auto-set style */
1822 if (filer_window->display_style != LARGE_ICONS &&
1823 filer_window->display_style != SMALL_ICONS)
1824 return; /* Only change between these two styles */
1826 n = view_count_items(filer_window->view);
1828 if (n >= o_filer_change_size_num.int_value)
1829 display_set_layout(filer_window, SMALL_ICONS,
1830 filer_window->details_type);
1831 else
1832 display_set_layout(filer_window, LARGE_ICONS,
1833 filer_window->details_type);
1836 /* Append interesting information to this GString */
1837 void filer_add_tip_details(FilerWindow *filer_window,
1838 GString *tip, DirItem *item)
1840 const guchar *fullpath = NULL;
1842 fullpath = make_path(filer_window->real_path, item->leafname);
1844 if (item->flags & ITEM_FLAG_SYMLINK)
1846 char *target;
1848 target = readlink_dup(fullpath);
1849 if (target)
1851 ensure_utf8(&target);
1853 g_string_append(tip, _("Symbolic link to "));
1854 g_string_append(tip, target);
1855 g_string_append_c(tip, '\n');
1856 g_free(target);
1860 if (item->flags & ITEM_FLAG_APPDIR)
1862 XMLwrapper *info;
1863 xmlNode *node;
1865 info = appinfo_get(fullpath, item);
1866 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
1868 guchar *str;
1869 str = xmlNodeListGetString(node->doc,
1870 node->xmlChildrenNode, 1);
1871 if (str)
1873 g_string_append(tip, str);
1874 g_string_append_c(tip, '\n');
1875 g_free(str);
1878 if (info)
1879 g_object_unref(info);
1882 if (!g_utf8_validate(item->leafname, -1, NULL))
1883 g_string_append(tip,
1884 _("This filename is not valid UTF-8. "
1885 "You should rename it.\n"));