r2228: Made 'Automatic' an icon size, rather than a separate option.
[rox-filer.git] / ROX-Filer / src / filer.c
blob88334a27949ea501c9f51908ae9913e6350b014d
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 /* Item we are about to display a tooltip for */
70 static DirItem *tip_item = NULL;
71 /* The window which the motion event for the tooltip came from. Use this
72 * to get the correct widget for finding the item under the pointer.
74 static GdkWindow *motion_window = NULL;
76 /* This is rather badly named. It's actually the filer window which received
77 * the last key press or Menu click event.
79 FilerWindow *window_with_focus = NULL;
81 GList *all_filer_windows = NULL;
83 static FilerWindow *window_with_primary = NULL;
85 /* Static prototypes */
86 static void attach(FilerWindow *filer_window);
87 static void detach(FilerWindow *filer_window);
88 static void filer_window_destroyed(GtkWidget *widget,
89 FilerWindow *filer_window);
90 static void update_display(Directory *dir,
91 DirAction action,
92 GPtrArray *items,
93 FilerWindow *filer_window);
94 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
95 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
96 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
97 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
98 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class);
99 static void filer_add_signals(FilerWindow *filer_window);
101 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
102 static void filer_next_thumb(GObject *window, const gchar *path);
103 static void start_thumb_scanning(FilerWindow *filer_window);
104 static void filer_options_changed(void);
105 static void drag_end(GtkWidget *widget, GdkDragContext *context,
106 FilerWindow *filer_window);
107 static void drag_leave(GtkWidget *widget,
108 GdkDragContext *context,
109 guint32 time,
110 FilerWindow *filer_window);
111 static gboolean drag_motion(GtkWidget *widget,
112 GdkDragContext *context,
113 gint x,
114 gint y,
115 guint time,
116 FilerWindow *filer_window);
118 static GdkCursor *busy_cursor = NULL;
119 static GdkCursor *crosshair = NULL;
121 /* Indicates whether the filer's display is different to the machine it
122 * is actually running on.
124 static gboolean not_local = FALSE;
126 static Option o_short_flag_names;
127 static Option o_filer_view_type;
128 Option o_filer_auto_resize, o_unique_filer_windows;
129 Option o_filer_size_limit;
131 void filer_init(void)
133 const gchar *ohost;
134 const gchar *dpy;
135 gchar *dpyhost, *tmp;
137 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
138 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
139 RESIZE_ALWAYS);
140 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
142 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
144 option_add_int(&o_filer_view_type, "filer_view_type",
145 VIEW_TYPE_COLLECTION);
147 option_add_notify(filer_options_changed);
149 busy_cursor = gdk_cursor_new(GDK_WATCH);
150 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
152 /* Is the display on the local machine, or are we being
153 * run remotely? See filer_set_title().
155 ohost = our_host_name();
156 dpy = gdk_get_display();
157 dpyhost = g_strdup(dpy);
158 tmp = strchr(dpyhost, ':');
159 if (tmp)
160 *tmp = '\0';
162 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
164 /* Try the cannonical name for dpyhost (see our_host_name()
165 * in support.c).
167 struct hostent *ent;
169 ent = gethostbyname(dpyhost);
170 if (!ent || strcmp(ohost, ent->h_name) != 0)
171 not_local = TRUE;
174 g_free(dpyhost);
177 static gboolean if_deleted(gpointer item, gpointer removed)
179 int i = ((GPtrArray *) removed)->len;
180 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
181 char *leafname = ((DirItem *) item)->leafname;
183 while (i--)
185 if (strcmp(leafname, r[i]->leafname) == 0)
186 return TRUE;
189 return FALSE;
192 /* Resize the filer window to w x h pixels, plus border (not clamped).
193 * If triggered by a key event, warp the pointer (for SloppyFocus users).
195 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
197 GdkEvent *event;
198 GtkWidget *window;
200 g_return_if_fail(filer_window != NULL);
202 if (filer_window->scrollbar)
203 w += filer_window->scrollbar->allocation.width;
205 if (o_toolbar.int_value != TOOLBAR_NONE)
206 h += filer_window->toolbar->allocation.height;
207 if (filer_window->message)
208 h += filer_window->message->allocation.height;
210 window = filer_window->window;
212 if (GTK_WIDGET_VISIBLE(window))
214 gint x, y;
215 GtkRequisition *req = &window->requisition;
216 GdkWindow *gdk_window = window->window;
218 w = MAX(req->width, w);
219 h = MAX(req->height, h);
220 gdk_window_get_position(gdk_window, &x, &y);
222 if (x + w > screen_width || y + h > screen_height)
224 if (x + w > screen_width)
225 x = screen_width - w - 4;
226 if (y + h > screen_height)
227 y = screen_height - h - 4;
228 gdk_window_move_resize(gdk_window, x, y, w, h);
230 else
231 gdk_window_resize(gdk_window, w, h);
233 else
234 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
236 event = gtk_get_current_event();
237 if (event && event->type == GDK_KEY_PRESS)
239 GdkWindow *win = filer_window->window->window;
241 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
242 None,
243 gdk_x11_drawable_get_xid(win),
244 0, 0, 0, 0,
245 w - 4, h - 4);
249 /* Called on a timeout while scanning or when scanning ends
250 * (whichever happens first).
252 static gint open_filer_window(FilerWindow *filer_window)
254 view_style_changed(filer_window->view, 0);
256 if (filer_window->open_timeout)
258 gtk_timeout_remove(filer_window->open_timeout);
259 filer_window->open_timeout = 0;
262 if (!GTK_WIDGET_VISIBLE(filer_window->window))
264 display_set_actual_size(filer_window);
265 gtk_widget_show(filer_window->window);
268 return FALSE;
271 static void update_display(Directory *dir,
272 DirAction action,
273 GPtrArray *items,
274 FilerWindow *filer_window)
276 ViewIface *view = (ViewIface *) filer_window->view;
278 switch (action)
280 case DIR_ADD:
281 view_add_items(view, items);
282 /* Open and resize if currently hidden */
283 open_filer_window(filer_window);
284 break;
285 case DIR_REMOVE:
286 view_delete_if(view, if_deleted, items);
287 toolbar_update_info(filer_window);
288 break;
289 case DIR_START_SCAN:
290 set_scanning_display(filer_window, TRUE);
291 toolbar_update_info(filer_window);
292 break;
293 case DIR_END_SCAN:
294 if (filer_window->window->window)
295 gdk_window_set_cursor(
296 filer_window->window->window,
297 NULL);
298 set_scanning_display(filer_window, FALSE);
299 toolbar_update_info(filer_window);
300 open_filer_window(filer_window);
302 if (filer_window->had_cursor &&
303 !view_cursor_visible(view))
305 ViewIter start;
306 view_get_iter(view, &start, 0);
307 if (start.next(&start))
308 view_cursor_to_iter(view, &start);
309 view_show_cursor(view);
310 filer_window->had_cursor = FALSE;
312 if (filer_window->auto_select)
313 display_set_autoselect(filer_window,
314 filer_window->auto_select);
315 null_g_free(&filer_window->auto_select);
317 filer_create_thumbs(filer_window);
319 if (filer_window->thumb_queue)
320 start_thumb_scanning(filer_window);
321 break;
322 case DIR_UPDATE:
323 view_update_items(view, items);
324 break;
328 static void attach(FilerWindow *filer_window)
330 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
331 view_clear(filer_window->view);
332 filer_window->scanning = TRUE;
333 dir_attach(filer_window->directory, (DirCallback) update_display,
334 filer_window);
335 filer_set_title(filer_window);
338 static void detach(FilerWindow *filer_window)
340 g_return_if_fail(filer_window->directory != NULL);
342 dir_detach(filer_window->directory,
343 (DirCallback) update_display, filer_window);
344 g_object_unref(filer_window->directory);
345 filer_window->directory = NULL;
348 /* Returns TRUE to prevent closing the window. May offer to unmount a
349 * device.
351 gboolean filer_window_delete(GtkWidget *window,
352 GdkEvent *unused, /* (may be NULL) */
353 FilerWindow *filer_window)
355 if (mount_is_user_mounted(filer_window->real_path))
357 int action;
359 action = get_choice(PROJECT,
360 _("Do you want to unmount this device?\n\n"
361 "Unmounting a device makes it safe to remove "
362 "the disk."), 3,
363 GTK_STOCK_CANCEL, NULL,
364 GTK_STOCK_CLOSE, NULL,
365 ROX_STOCK_MOUNT, _("Unmount"));
367 if (action == 0)
368 return TRUE; /* Cancel close operation */
370 if (action == 2)
372 GList *list;
374 list = g_list_prepend(NULL, filer_window->sym_path);
375 action_mount(list, FALSE, TRUE);
376 g_list_free(list);
380 return FALSE;
383 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
385 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
387 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
389 if (window_with_primary == filer_window)
390 window_with_primary = NULL;
392 if (window_with_focus == filer_window)
394 menu_popdown();
395 window_with_focus = NULL;
398 if (filer_window->directory)
399 detach(filer_window);
401 if (filer_window->open_timeout)
403 gtk_timeout_remove(filer_window->open_timeout);
404 filer_window->open_timeout = 0;
407 if (filer_window->thumb_queue)
408 destroy_glist(&filer_window->thumb_queue);
410 tooltip_show(NULL);
412 g_free(filer_window->auto_select);
413 g_free(filer_window->real_path);
414 g_free(filer_window->sym_path);
415 g_free(filer_window);
417 one_less_window();
420 /* Returns TRUE iff the directory still exists. */
421 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
423 Directory *dir;
425 g_return_val_if_fail(filer_window != NULL, FALSE);
427 /* We do a fresh lookup (rather than update) because the inode may
428 * have changed.
430 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
431 if (!dir)
433 if (warning)
434 info_message(_("Directory missing/deleted"));
435 gtk_widget_destroy(filer_window->window);
436 return FALSE;
438 if (dir == filer_window->directory)
439 g_object_unref(dir);
440 else
442 detach(filer_window);
443 filer_window->directory = dir;
444 attach(filer_window);
447 return TRUE;
450 /* No items are now selected. This might be because another app claimed
451 * the selection or because the user unselected all the items.
453 void filer_lost_selection(FilerWindow *filer_window, guint time)
455 if (window_with_primary == filer_window)
457 window_with_primary = NULL;
458 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
462 /* Another app has claimed the primary selection */
463 static void filer_lost_primary(GtkWidget *window,
464 GdkEventSelection *event,
465 gpointer user_data)
467 FilerWindow *filer_window = (FilerWindow *) user_data;
469 if (window_with_primary && window_with_primary == filer_window)
471 window_with_primary = NULL;
472 set_selection_state(filer_window, FALSE);
476 /* Someone wants us to send them the selection */
477 static void selection_get(GtkWidget *widget,
478 GtkSelectionData *selection_data,
479 guint info,
480 guint time,
481 gpointer data)
483 GString *reply, *header;
484 FilerWindow *filer_window = (FilerWindow *) data;
485 ViewIter iter;
486 DirItem *item;
488 reply = g_string_new(NULL);
489 header = g_string_new(NULL);
491 switch (info)
493 case TARGET_STRING:
494 g_string_printf(header, " %s",
495 make_path(filer_window->sym_path, ""));
496 break;
497 case TARGET_URI_LIST:
498 g_string_printf(header, " file://%s%s",
499 our_host_name_for_dnd(),
500 make_path(filer_window->sym_path, ""));
501 break;
504 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
506 while ((item = iter.next(&iter)))
508 g_string_append(reply, header->str);
509 g_string_append(reply, item->leafname);
512 if (reply->len > 0)
513 gtk_selection_data_set(selection_data, xa_string,
514 8, reply->str + 1, reply->len - 1);
515 else
517 g_warning("Attempt to paste empty selection!");
518 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
521 g_string_free(reply, TRUE);
522 g_string_free(header, TRUE);
525 /* Selection has been changed -- try to grab the primary selection
526 * if we don't have it. Also called when clicking on an insensitive selection
527 * to regain primary.
528 * Also updates toolbar info.
530 void filer_selection_changed(FilerWindow *filer_window, gint time)
532 toolbar_update_info(filer_window);
534 if (window_with_primary == filer_window)
535 return; /* Already got primary */
537 if (!view_count_selected(filer_window->view))
538 return; /* Nothing selected */
540 if (filer_window->temp_item_selected == FALSE &&
541 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
542 GDK_SELECTION_PRIMARY,
543 time))
545 window_with_primary = filer_window;
546 set_selection_state(filer_window, TRUE);
548 else
549 set_selection_state(filer_window, FALSE);
552 /* Open the item (or add it to the shell command minibuffer) */
553 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
555 gboolean shift = (flags & OPEN_SHIFT) != 0;
556 gboolean close_mini = flags & OPEN_FROM_MINI;
557 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
558 DirItem *item;
559 const guchar *full_path;
560 gboolean wink = TRUE;
561 Directory *old_dir;
563 g_return_if_fail(filer_window != NULL && iter != NULL);
565 item = iter->peek(iter);
567 g_return_if_fail(item != NULL);
569 if (filer_window->mini_type == MINI_SHELL)
571 minibuffer_add(filer_window, item->leafname);
572 return;
575 if (!item->image)
576 dir_update_item(filer_window->directory, item->leafname);
578 if (item->base_type == TYPE_DIRECTORY)
580 /* Never close a filer window when opening a directory
581 * (click on a dir or click on an app with shift).
583 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
584 close_window = FALSE;
587 full_path = make_path(filer_window->sym_path, item->leafname);
588 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
589 wink = FALSE;
591 old_dir = filer_window->directory;
592 if (run_diritem(full_path, item,
593 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
594 filer_window,
595 shift))
597 if (old_dir != filer_window->directory)
598 return;
600 if (close_window)
601 gtk_widget_destroy(filer_window->window);
602 else
604 if (wink)
605 view_wink_item(filer_window->view, iter);
606 if (close_mini)
607 minibuffer_hide(filer_window);
612 static gint pointer_in(GtkWidget *widget,
613 GdkEventCrossing *event,
614 FilerWindow *filer_window)
616 may_rescan(filer_window, TRUE);
617 return FALSE;
620 static gint pointer_out(GtkWidget *widget,
621 GdkEventCrossing *event,
622 FilerWindow *filer_window)
624 tooltip_show(NULL);
625 return FALSE;
628 /* Move the cursor to the next selected item in direction 'dir'
629 * (+1 or -1).
631 static void next_selected(FilerWindow *filer_window, int dir)
633 ViewIter iter, cursor;
634 gboolean have_cursor;
635 ViewIface *view = filer_window->view;
637 g_return_if_fail(dir == 1 || dir == -1);
639 view_get_cursor(view, &cursor);
640 have_cursor = cursor.peek(&cursor) != NULL;
642 view_get_iter(view, &iter,
643 VIEW_ITER_SELECTED |
644 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
645 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
647 if (have_cursor && view_get_selected(view, &cursor))
648 iter.next(&iter); /* Skip the cursor itself */
650 if (iter.next(&iter))
651 view_cursor_to_iter(view, &iter);
652 else
653 gdk_beep();
655 return;
658 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
660 TargetFunc cb = filer_window->target_cb;
661 gpointer data = filer_window->target_data;
662 OpenFlags flags = 0;
663 ViewIter iter;
665 filer_target_mode(filer_window, NULL, NULL, NULL);
667 view_get_cursor(filer_window->view, &iter);
668 if (!iter.peek(&iter))
669 return;
671 if (cb)
673 cb(filer_window, &iter, data);
674 return;
677 if (event->state & GDK_SHIFT_MASK)
678 flags |= OPEN_SHIFT;
679 if (event->state & GDK_MOD1_MASK)
680 flags |= OPEN_CLOSE_WINDOW;
681 else
682 flags |= OPEN_SAME_WINDOW;
684 filer_openitem(filer_window, &iter, flags);
687 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
688 * changed. If no groups were loaded and there is no file then initialised
689 * groups to an empty document.
690 * Return the node for the 'name' group.
692 static xmlNode *group_find(char *name)
694 xmlNode *node;
695 gchar *path;
697 /* Update the groups, if possible */
698 path = choices_find_path_load("Groups.xml", PROJECT);
699 if (path)
701 XMLwrapper *wrapper;
702 wrapper = xml_cache_load(path);
703 if (wrapper)
705 if (groups)
706 g_object_unref(groups);
707 groups = wrapper;
710 g_free(path);
713 if (!groups)
715 groups = xml_new(NULL);
716 groups->doc = xmlNewDoc("1.0");
718 xmlDocSetRootElement(groups->doc,
719 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
720 return NULL;
723 node = xmlDocGetRootElement(groups->doc);
725 for (node = node->xmlChildrenNode; node; node = node->next)
727 guchar *gid;
729 gid = xmlGetProp(node, "name");
731 if (!gid)
732 continue;
734 if (strcmp(name, gid) != 0)
735 continue;
737 g_free(gid);
739 return node;
742 return NULL;
745 static void group_save(FilerWindow *filer_window, char *name)
747 xmlNode *group;
748 guchar *save_path;
749 DirItem *item;
750 ViewIter iter;
752 group = group_find(name);
753 if (group)
755 xmlUnlinkNode(group);
756 xmlFreeNode(group);
758 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
759 NULL, "group", NULL);
760 xmlSetProp(group, "name", name);
762 xmlNewChild(group, NULL, "directory", filer_window->sym_path);
764 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
766 while ((item = iter.next(&iter)))
767 xmlNewChild(group, NULL, "item", item->leafname);
769 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
770 if (save_path)
772 save_xml_file(groups->doc, save_path);
773 g_free(save_path);
777 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
779 GHashTable *in_group = (GHashTable *) data;
781 return g_hash_table_lookup(in_group,
782 iter->peek(iter)->leafname) != NULL;
785 static void group_restore(FilerWindow *filer_window, char *name)
787 GHashTable *in_group;
788 char *path;
789 xmlNode *group, *node;
791 group = group_find(name);
793 if (!group)
795 report_error(_("Group %s is not set. Select some files "
796 "and press Ctrl+%s to set the group. Press %s "
797 "on its own to reselect the files later.\n"
798 "Make sure NumLock is on if you use the keypad."),
799 name, name, name);
800 return;
803 node = get_subnode(group, NULL, "directory");
804 g_return_if_fail(node != NULL);
805 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
806 g_return_if_fail(path != NULL);
808 if (strcmp(path, filer_window->sym_path) != 0)
809 filer_change_to(filer_window, path, NULL);
810 g_free(path);
812 in_group = g_hash_table_new(g_str_hash, g_str_equal);
813 for (node = group->xmlChildrenNode; node; node = node->next)
815 gchar *leaf;
816 if (node->type != XML_ELEMENT_NODE)
817 continue;
818 if (strcmp(node->name, "item") != 0)
819 continue;
821 leaf = xmlNodeListGetString(groups->doc,
822 node->xmlChildrenNode, 1);
823 if (!leaf)
824 g_warning("Missing leafname!\n");
825 else
826 g_hash_table_insert(in_group, leaf, filer_window);
829 view_select_if(filer_window->view, &group_restore_cb, in_group);
831 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
832 g_hash_table_destroy(in_group);
835 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
837 ViewIter iter;
839 view_get_cursor(filer_window->view, &iter);
841 show_filer_menu(filer_window, NULL, &iter);
843 return TRUE;
846 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
848 ViewIface *view = filer_window->view;
849 ViewIter iter;
851 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
852 if (!iter.next(&iter))
853 return; /* No cursor */
855 if (view_get_selected(view, &iter))
856 view_set_selected(view, &iter, FALSE);
857 else
858 view_set_selected(view, &iter, TRUE);
860 if (iter.next(&iter))
861 view_cursor_to_iter(view, &iter);
864 /* Handle keys that can't be bound with the menu */
865 static gint key_press_event(GtkWidget *widget,
866 GdkEventKey *event,
867 FilerWindow *filer_window)
869 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
870 guint key = event->keyval;
871 char group[2] = "1";
873 window_with_focus = filer_window;
875 /* Delay setting up the keys until now to speed loading... */
876 if (!filer_keys)
877 ensure_filer_menu(); /* Gets the keys working... */
879 if (!g_slist_find(filer_keys->acceleratables, widget))
880 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
881 filer_keys);
883 if (focus && focus != widget &&
884 gtk_widget_get_toplevel(focus) == widget)
885 if (gtk_widget_event(focus, (GdkEvent *) event))
886 return TRUE; /* Handled */
888 switch (key)
890 case GDK_Escape:
891 filer_target_mode(filer_window, NULL, NULL, NULL);
892 view_cursor_to_iter(filer_window->view, NULL);
893 view_clear_selection(filer_window->view);
894 return FALSE;
895 case GDK_Return:
896 return_pressed(filer_window, event);
897 break;
898 case GDK_ISO_Left_Tab:
899 next_selected(filer_window, -1);
900 break;
901 case GDK_Tab:
902 next_selected(filer_window, 1);
903 break;
904 case GDK_BackSpace:
905 change_to_parent(filer_window);
906 break;
907 case GDK_backslash:
909 ViewIter iter;
911 tooltip_show(NULL);
913 view_get_cursor(filer_window->view, &iter);
914 show_filer_menu(filer_window,
915 (GdkEvent *) event, &iter);
916 break;
918 case ' ':
919 filer_window_toggle_cursor_item_selected(filer_window);
920 break;
921 default:
922 if (key >= GDK_0 && key <= GDK_9)
923 group[0] = key - GDK_0 + '0';
924 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
925 group[0] = key - GDK_KP_0 + '0';
926 else
927 return FALSE;
930 if (event->state & GDK_CONTROL_MASK)
931 group_save(filer_window, group);
932 else
933 group_restore(filer_window, group);
936 return TRUE;
939 void filer_open_parent(FilerWindow *filer_window)
941 char *dir;
942 const char *current = filer_window->sym_path;
944 if (current[0] == '/' && current[1] == '\0')
945 return; /* Already in the root */
947 dir = g_path_get_dirname(current);
948 filer_opendir(dir, filer_window, NULL);
949 g_free(dir);
952 void change_to_parent(FilerWindow *filer_window)
954 char *dir;
955 const char *current = filer_window->sym_path;
957 if (current[0] == '/' && current[1] == '\0')
958 return; /* Already in the root */
960 dir = g_path_get_dirname(current);
961 filer_change_to(filer_window, dir, g_basename(current));
962 g_free(dir);
965 /* Removes trailing /s from path (modified in place) */
966 static void tidy_sympath(gchar *path)
968 int l;
970 g_return_if_fail(path != NULL);
972 l = strlen(path);
973 while (l > 1 && path[l - 1] == '/')
975 l--;
976 path[l] = '\0';
980 /* Make filer_window display path. When finished, highlight item 'from', or
981 * the first item if from is NULL. If there is currently no cursor then
982 * simply wink 'from' (if not NULL).
983 * If the cause was a key event and we resize, warp the pointer.
985 void filer_change_to(FilerWindow *filer_window,
986 const char *path, const char *from)
988 char *from_dup;
989 char *sym_path, *real_path;
990 Directory *new_dir;
992 g_return_if_fail(filer_window != NULL);
994 filer_cancel_thumbnails(filer_window);
996 tooltip_show(NULL);
998 sym_path = g_strdup(path);
999 real_path = pathdup(path);
1000 new_dir = g_fscache_lookup(dir_cache, real_path);
1002 if (!new_dir)
1004 delayed_error(_("Directory '%s' is not accessible"),
1005 sym_path);
1006 g_free(real_path);
1007 g_free(sym_path);
1008 return;
1011 if (o_unique_filer_windows.int_value)
1013 FilerWindow *fw;
1015 fw = find_filer_window(sym_path, filer_window);
1016 if (fw)
1017 gtk_widget_destroy(fw->window);
1020 from_dup = from && *from ? g_strdup(from) : NULL;
1022 detach(filer_window);
1023 g_free(filer_window->real_path);
1024 g_free(filer_window->sym_path);
1025 filer_window->real_path = real_path;
1026 filer_window->sym_path = sym_path;
1027 tidy_sympath(filer_window->sym_path);
1029 filer_window->directory = new_dir;
1031 g_free(filer_window->auto_select);
1032 filer_window->auto_select = from_dup;
1034 filer_window->had_cursor = filer_window->had_cursor ||
1035 view_cursor_visible(filer_window->view);
1037 filer_set_title(filer_window);
1038 if (filer_window->window->window)
1039 gdk_window_set_role(filer_window->window->window,
1040 filer_window->sym_path);
1041 view_cursor_to_iter(filer_window->view, NULL);
1043 attach(filer_window);
1045 display_set_actual_size(filer_window);
1047 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1048 view_autosize(filer_window->view);
1050 if (filer_window->mini_type == MINI_PATH)
1051 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1052 filer_window);
1055 /* Returns a list containing the full (sym) pathname of every selected item.
1056 * You must g_free() each item in the list.
1058 GList *filer_selected_items(FilerWindow *filer_window)
1060 GList *retval = NULL;
1061 guchar *dir = filer_window->sym_path;
1062 ViewIter iter;
1063 DirItem *item;
1065 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1066 while ((item = iter.next(&iter)))
1068 retval = g_list_prepend(retval,
1069 g_strdup(make_path(dir, item->leafname)));
1072 return g_list_reverse(retval);
1075 /* Return the single selected item. Error if nothing is selected. */
1076 DirItem *filer_selected_item(FilerWindow *filer_window)
1078 ViewIter iter;
1079 DirItem *item;
1081 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1083 item = iter.next(&iter);
1084 g_return_val_if_fail(item != NULL, NULL);
1085 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1087 return item;
1090 /* Creates and shows a new filer window.
1091 * If src_win != NULL then display options can be taken from that source window.
1092 * Returns the new filer window, or NULL on error.
1093 * Note: if unique windows is in use, may return an existing window.
1095 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1096 const gchar *wm_class)
1098 FilerWindow *filer_window;
1099 char *real_path;
1100 DisplayStyle dstyle;
1101 DetailsType dtype;
1103 /* Get the real pathname of the directory and copy it */
1104 real_path = pathdup(path);
1106 if (o_unique_filer_windows.int_value)
1108 FilerWindow *same_dir_window;
1110 same_dir_window = find_filer_window(path, NULL);
1112 if (same_dir_window)
1114 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1115 return same_dir_window;
1119 filer_window = g_new(FilerWindow, 1);
1120 filer_window->message = NULL;
1121 filer_window->minibuffer = NULL;
1122 filer_window->minibuffer_label = NULL;
1123 filer_window->minibuffer_area = NULL;
1124 filer_window->temp_show_hidden = FALSE;
1125 filer_window->sym_path = g_strdup(path);
1126 filer_window->real_path = real_path;
1127 filer_window->scanning = FALSE;
1128 filer_window->had_cursor = FALSE;
1129 filer_window->auto_select = NULL;
1130 filer_window->toolbar_text = NULL;
1131 filer_window->target_cb = NULL;
1132 filer_window->mini_type = MINI_NONE;
1133 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1134 filer_window->toolbar = NULL;
1135 filer_window->toplevel_vbox = NULL;
1136 filer_window->view = NULL;
1137 filer_window->scrollbar = NULL;
1139 tidy_sympath(filer_window->sym_path);
1141 /* Finds the entry for this directory in the dir cache, creating
1142 * a new one if needed. This does not cause a scan to start,
1143 * so if a new entry is created then it will be empty.
1145 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1146 if (!filer_window->directory)
1148 delayed_error(_("Directory '%s' not found."), path);
1149 g_free(filer_window->real_path);
1150 g_free(filer_window->sym_path);
1151 g_free(filer_window);
1152 return NULL;
1155 filer_window->temp_item_selected = FALSE;
1156 filer_window->flags = (FilerFlags) 0;
1157 filer_window->details_type = DETAILS_TIMES;
1158 filer_window->display_style = UNKNOWN_STYLE;
1159 filer_window->thumb_queue = NULL;
1160 filer_window->max_thumbs = 0;
1162 if (src_win && o_display_inherit_options.int_value)
1164 filer_window->sort_fn = src_win->sort_fn;
1165 dstyle = src_win->display_style_wanted;
1166 dtype = src_win->details_type;
1167 filer_window->show_hidden = src_win->show_hidden;
1168 filer_window->show_thumbs = src_win->show_thumbs;
1169 filer_window->view_type = src_win->view_type;
1171 else
1173 int i = o_display_sort_by.int_value;
1174 filer_window->sort_fn = i == 0 ? sort_by_name :
1175 i == 1 ? sort_by_type :
1176 i == 2 ? sort_by_date :
1177 sort_by_size;
1179 dstyle = o_display_size.int_value;
1180 dtype = o_display_details.int_value;
1181 filer_window->show_hidden = o_display_show_hidden.int_value;
1182 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1183 filer_window->view_type = o_filer_view_type.int_value;
1186 /* Add all the user-interface elements & realise */
1187 filer_add_widgets(filer_window, wm_class);
1188 if (src_win)
1189 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1190 GTK_WIN_POS_MOUSE);
1192 /* Connect to all the signal handlers */
1193 filer_add_signals(filer_window);
1195 display_set_layout(filer_window, dstyle, dtype);
1197 /* Open the window after a timeout, or when scanning stops.
1198 * Do this before attaching, because attach() might tell us to
1199 * stop scanning (if a scan isn't needed).
1201 filer_window->open_timeout = gtk_timeout_add(500,
1202 (GtkFunction) open_filer_window,
1203 filer_window);
1205 /* The view is created empty and then attach() is called, which
1206 * links the filer window to the entry in the directory cache we
1207 * looked up / created above.
1209 * The attach() function will immediately callback to the filer window
1210 * to deliver a list of all known entries in the directory (so,
1211 * the number of items will be known after attach() returns).
1213 * If the directory was not in the cache (because it hadn't been
1214 * opened it before) then the types and icons for the entries are
1215 * not know, but the list of names is.
1218 attach(filer_window);
1220 number_of_windows++;
1221 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1223 return filer_window;
1226 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1228 GtkWidget *view = NULL;
1229 Directory *dir = NULL;
1231 g_return_if_fail(filer_window != NULL);
1233 if (filer_window->view)
1235 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1236 filer_window->view = NULL;
1238 dir = filer_window->directory;
1239 g_object_ref(dir);
1240 detach(filer_window);
1243 switch (type)
1245 case VIEW_TYPE_COLLECTION:
1246 view = view_collection_new(filer_window);
1247 break;
1248 case VIEW_TYPE_DETAILS:
1249 view = view_details_new(filer_window);
1250 break;
1253 g_return_if_fail(view != NULL);
1255 filer_window->view = VIEW(view);
1256 filer_window->view_type = type;
1258 gtk_box_pack_start(filer_window->toplevel_vbox, view, TRUE, TRUE, 0);
1259 gtk_widget_show(view);
1261 /* Drag and drop events */
1262 make_drop_target(view, 0);
1263 g_signal_connect(view, "drag_motion",
1264 G_CALLBACK(drag_motion), filer_window);
1265 g_signal_connect(view, "drag_leave",
1266 G_CALLBACK(drag_leave), filer_window);
1267 g_signal_connect(view, "drag_end",
1268 G_CALLBACK(drag_end), filer_window);
1269 /* Dragging from us... */
1270 g_signal_connect(view, "drag_data_get",
1271 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1273 if (dir)
1275 /* Only when changing type. Otherwise, will attach later. */
1276 filer_window->directory = dir;
1277 attach(filer_window);
1279 view_autosize(filer_window->view);
1283 /* This adds all the widgets to a new filer window. It is in a separate
1284 * function because filer_opendir() was getting too long...
1286 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1288 GtkWidget *hbox, *vbox;
1290 /* Create the top-level window widget */
1291 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1292 filer_set_title(filer_window);
1293 if (wm_class)
1294 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1295 wm_class, PROJECT);
1297 /* This property is cleared when the window is destroyed.
1298 * You can thus ref filer_window->window and use this to see
1299 * if the window no longer exists.
1301 g_object_set_data(G_OBJECT(filer_window->window),
1302 "filer_window", filer_window);
1304 /* Create this now to make the Adjustment before the View */
1305 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1307 /* Scrollbar on the right, everything else on the left */
1308 hbox = gtk_hbox_new(FALSE, 0);
1309 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1311 vbox = gtk_vbox_new(FALSE, 0);
1312 gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
1313 filer_window->toplevel_vbox = GTK_BOX(vbox);
1315 filer_set_view_type(filer_window, filer_window->view_type);
1317 /* If we want a toolbar, create it now */
1318 toolbar_update_toolbar(filer_window);
1320 /* If there's a message that should be displayed in each window (eg
1321 * 'Running as root'), add it here.
1323 if (show_user_message)
1325 filer_window->message = gtk_label_new(show_user_message);
1326 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1327 FALSE, TRUE, 0);
1328 gtk_widget_show(filer_window->message);
1331 /* And the minibuffer (hidden to start with) */
1332 create_minibuffer(filer_window);
1333 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1334 FALSE, TRUE, 0);
1336 /* And the thumbnail progress bar (also hidden) */
1338 GtkWidget *cancel;
1340 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1341 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1342 FALSE, TRUE, 0);
1344 filer_window->thumb_progress = gtk_progress_bar_new();
1346 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1347 filer_window->thumb_progress, TRUE, TRUE, 0);
1349 cancel = gtk_button_new_with_label(_("Cancel"));
1350 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1351 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1352 cancel, FALSE, TRUE, 0);
1353 g_signal_connect_swapped(cancel, "clicked",
1354 G_CALLBACK(filer_cancel_thumbnails),
1355 filer_window);
1358 /* Put the scrollbar on the left of everything else... */
1359 gtk_box_pack_start(GTK_BOX(hbox),
1360 filer_window->scrollbar, FALSE, TRUE, 0);
1362 gtk_widget_show(hbox);
1363 gtk_widget_show(vbox);
1364 gtk_widget_show(filer_window->scrollbar);
1366 gtk_widget_realize(filer_window->window);
1368 gdk_window_set_role(filer_window->window->window,
1369 filer_window->sym_path);
1371 filer_window_set_size(filer_window, 4, 4);
1374 static void filer_add_signals(FilerWindow *filer_window)
1376 GtkTargetEntry target_table[] =
1378 {"text/uri-list", 0, TARGET_URI_LIST},
1379 {"STRING", 0, TARGET_STRING},
1380 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1383 /* Events on the top-level window */
1384 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1385 g_signal_connect(filer_window->window, "enter-notify-event",
1386 G_CALLBACK(pointer_in), filer_window);
1387 g_signal_connect(filer_window->window, "leave-notify-event",
1388 G_CALLBACK(pointer_out), filer_window);
1389 g_signal_connect(filer_window->window, "destroy",
1390 G_CALLBACK(filer_window_destroyed), filer_window);
1391 g_signal_connect(filer_window->window, "delete-event",
1392 G_CALLBACK(filer_window_delete), filer_window);
1394 g_signal_connect(filer_window->window, "selection_clear_event",
1395 G_CALLBACK(filer_lost_primary), filer_window);
1397 g_signal_connect(filer_window->window, "selection_get",
1398 G_CALLBACK(selection_get), filer_window);
1399 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1400 GDK_SELECTION_PRIMARY,
1401 target_table,
1402 sizeof(target_table) / sizeof(*target_table));
1404 g_signal_connect(filer_window->window, "popup-menu",
1405 G_CALLBACK(popup_menu), filer_window);
1406 g_signal_connect(filer_window->window, "key_press_event",
1407 G_CALLBACK(key_press_event), filer_window);
1410 static gint clear_scanning_display(FilerWindow *filer_window)
1412 if (filer_exists(filer_window))
1413 filer_set_title(filer_window);
1414 return FALSE;
1417 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1419 if (scanning == filer_window->scanning)
1420 return;
1421 filer_window->scanning = scanning;
1423 if (scanning)
1424 filer_set_title(filer_window);
1425 else
1426 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1427 filer_window);
1430 /* Note that filer_window may not exist after this call */
1431 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1433 if (may_rescan(filer_window, warning))
1434 dir_update(filer_window->directory, filer_window->sym_path);
1437 void filer_update_all(void)
1439 GList *next = all_filer_windows;
1441 while (next)
1443 FilerWindow *filer_window = (FilerWindow *) next->data;
1445 /* Updating directory may remove it from list -- stop sending
1446 * patches to move this line!
1448 next = next->next;
1450 filer_update_dir(filer_window, TRUE);
1454 /* Refresh the various caches even if we don't think we need to */
1455 void full_refresh(void)
1457 mount_update(TRUE);
1458 reread_mime_files();
1461 /* See whether a filer window with a given path already exists
1462 * and is different from diff.
1464 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1466 GList *next;
1468 for (next = all_filer_windows; next; next = next->next)
1470 FilerWindow *filer_window = (FilerWindow *) next->data;
1472 if (filer_window != diff &&
1473 strcmp(sym_path, filer_window->sym_path) == 0)
1474 return filer_window;
1477 return NULL;
1480 /* This path has been mounted/umounted/deleted some files - update all dirs */
1481 void filer_check_mounted(const char *real_path)
1483 GList *next = all_filer_windows;
1484 gchar *parent;
1485 int len;
1487 len = strlen(real_path);
1489 while (next)
1491 FilerWindow *filer_window = (FilerWindow *) next->data;
1493 next = next->next;
1495 if (strncmp(real_path, filer_window->real_path, len) == 0)
1497 char s = filer_window->real_path[len];
1499 if (s == '/' || s == '\0')
1500 filer_update_dir(filer_window, FALSE);
1504 parent = g_path_get_dirname(real_path);
1505 refresh_dirs(parent);
1506 g_free(parent);
1508 icons_may_update(real_path);
1511 /* Close all windows displaying 'path' or subdirectories of 'path' */
1512 void filer_close_recursive(const char *path)
1514 GList *next = all_filer_windows;
1515 gchar *real;
1516 int len;
1518 real = pathdup(path);
1519 len = strlen(real);
1521 while (next)
1523 FilerWindow *filer_window = (FilerWindow *) next->data;
1525 next = next->next;
1527 if (strncmp(real, filer_window->real_path, len) == 0)
1529 char s = filer_window->real_path[len];
1531 if (len == 1 || s == '/' || s == '\0')
1532 gtk_widget_destroy(filer_window->window);
1537 /* Like minibuffer_show(), except that:
1538 * - It returns FALSE (to be used from an idle callback)
1539 * - It checks that the filer window still exists.
1541 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1543 if (filer_exists(filer_window))
1544 minibuffer_show(filer_window, MINI_PATH);
1545 return FALSE;
1548 /* TRUE iff filer_window points to an existing FilerWindow
1549 * structure.
1551 gboolean filer_exists(FilerWindow *filer_window)
1553 GList *next;
1555 for (next = all_filer_windows; next; next = next->next)
1557 FilerWindow *fw = (FilerWindow *) next->data;
1559 if (fw == filer_window)
1560 return TRUE;
1563 return FALSE;
1566 /* Make sure the window title is up-to-date */
1567 void filer_set_title(FilerWindow *filer_window)
1569 gchar *title = NULL;
1570 guchar *flags = "";
1572 if (filer_window->scanning || filer_window->show_hidden ||
1573 filer_window->show_thumbs)
1575 if (o_short_flag_names.int_value)
1577 flags = g_strconcat(" +",
1578 filer_window->scanning ? _("S") : "",
1579 filer_window->show_hidden ? _("A") : "",
1580 filer_window->show_thumbs ? _("T") : "",
1581 NULL);
1583 else
1585 flags = g_strconcat(" (",
1586 filer_window->scanning ? _("Scanning, ") : "",
1587 filer_window->show_hidden ? _("All, ") : "",
1588 filer_window->show_thumbs ? _("Thumbs, ") : "",
1589 NULL);
1590 flags[strlen(flags) - 2] = ')';
1594 if (not_local)
1595 title = g_strconcat("//", our_host_name(),
1596 filer_window->sym_path, flags, NULL);
1598 if (!title && home_dir_len > 1 &&
1599 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1601 guchar sep = filer_window->sym_path[home_dir_len];
1603 if (sep == '\0' || sep == '/')
1604 title = g_strconcat("~",
1605 filer_window->sym_path + home_dir_len,
1606 flags,
1607 NULL);
1610 if (!title)
1611 title = g_strconcat(filer_window->sym_path, flags, NULL);
1613 ensure_utf8(&title);
1615 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1617 g_free(title);
1619 if (flags[0] != '\0')
1620 g_free(flags);
1623 /* Reconnect to the same directory (used when the Show Hidden option is
1624 * toggled). This has the side-effect of updating the window title.
1626 void filer_detach_rescan(FilerWindow *filer_window)
1628 Directory *dir = filer_window->directory;
1630 g_object_ref(dir);
1631 detach(filer_window);
1632 filer_window->directory = dir;
1633 attach(filer_window);
1636 /* Puts the filer window into target mode. When an item is chosen,
1637 * fn(filer_window, iter, data) is called. 'reason' will be displayed
1638 * on the toolbar while target mode is active.
1640 * Use fn == NULL to cancel target mode.
1642 void filer_target_mode(FilerWindow *filer_window,
1643 TargetFunc fn,
1644 gpointer data,
1645 const char *reason)
1647 TargetFunc old_fn = filer_window->target_cb;
1649 if (fn != old_fn)
1650 gdk_window_set_cursor(
1651 GTK_WIDGET(filer_window->view)->window,
1652 fn ? crosshair : NULL);
1654 filer_window->target_cb = fn;
1655 filer_window->target_data = data;
1657 if (filer_window->toolbar_text == NULL)
1658 return;
1660 if (fn)
1661 gtk_label_set_text(
1662 GTK_LABEL(filer_window->toolbar_text), reason);
1663 else if (o_toolbar_info.int_value)
1665 if (old_fn)
1666 toolbar_update_info(filer_window);
1668 else
1669 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1672 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
1674 GtkStateType old_state = filer_window->selection_state;
1676 filer_window->selection_state = normal
1677 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
1679 if (old_state != filer_window->selection_state
1680 && view_count_selected(filer_window->view))
1681 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
1684 void filer_cancel_thumbnails(FilerWindow *filer_window)
1686 gtk_widget_hide(filer_window->thumb_bar);
1688 destroy_glist(&filer_window->thumb_queue);
1689 filer_window->max_thumbs = 0;
1692 /* Generate the next thumb for this window. The window object is
1693 * unref'd when there is nothing more to do.
1694 * If the window no longer has a filer window, nothing is done.
1696 static gboolean filer_next_thumb_real(GObject *window)
1698 FilerWindow *filer_window;
1699 gchar *path;
1700 int done, total;
1702 filer_window = g_object_get_data(window, "filer_window");
1704 if (!filer_window)
1706 g_object_unref(window);
1707 return FALSE;
1710 if (!filer_window->thumb_queue)
1712 filer_cancel_thumbnails(filer_window);
1713 g_object_unref(window);
1714 return FALSE;
1717 total = filer_window->max_thumbs;
1718 done = total - g_list_length(filer_window->thumb_queue);
1720 path = (gchar *) filer_window->thumb_queue->data;
1722 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
1724 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
1725 path);
1726 g_free(path);
1728 gtk_progress_bar_set_fraction(
1729 GTK_PROGRESS_BAR(filer_window->thumb_progress),
1730 done / (float) total);
1732 return FALSE;
1735 /* path is the thumb just loaded, if any.
1736 * window is unref'd (eventually).
1738 static void filer_next_thumb(GObject *window, const gchar *path)
1740 if (path)
1741 dir_force_update_path(path);
1743 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
1746 static void start_thumb_scanning(FilerWindow *filer_window)
1748 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
1749 return; /* Already scanning */
1751 gtk_widget_show_all(filer_window->thumb_bar);
1753 g_object_ref(G_OBJECT(filer_window->window));
1754 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
1757 /* Set this image to be loaded some time in the future */
1758 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
1760 filer_window->max_thumbs++;
1762 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
1763 g_strdup(path));
1765 if (filer_window->scanning)
1766 return; /* Will start when scan ends */
1768 start_thumb_scanning(filer_window);
1771 /* If thumbnail display is on, look through all the items in this directory
1772 * and start creating or updating the thumbnails as needed.
1774 void filer_create_thumbs(FilerWindow *filer_window)
1776 DirItem *item;
1777 ViewIter iter;
1779 if (!filer_window->show_thumbs)
1780 return;
1782 view_get_iter(filer_window->view, &iter, 0);
1784 while ((item = iter.next(&iter)))
1786 MaskedPixmap *pixmap;
1787 const guchar *path;
1788 gboolean found;
1790 if (item->base_type != TYPE_FILE)
1791 continue;
1793 if (strcmp(item->mime_type->media_type, "image") != 0)
1794 continue;
1796 path = make_path(filer_window->real_path, item->leafname);
1798 pixmap = g_fscache_lookup_full(pixmap_cache, path,
1799 FSCACHE_LOOKUP_ONLY_NEW, &found);
1800 if (pixmap)
1801 g_object_unref(pixmap);
1803 /* If we didn't get an image, it could be because:
1805 * - We're loading the image now. found is TRUE,
1806 * and we'll update the item later.
1807 * - We tried to load the image and failed. found
1808 * is TRUE.
1809 * - We haven't tried loading the image. found is
1810 * FALSE, and we start creating the thumb here.
1812 if (!found)
1813 filer_create_thumb(filer_window, path);
1817 static void filer_options_changed(void)
1819 if (o_short_flag_names.has_changed)
1821 GList *next;
1823 for (next = all_filer_windows; next; next = next->next)
1825 FilerWindow *filer_window = (FilerWindow *) next->data;
1827 filer_set_title(filer_window);
1832 /* Append interesting information to this GString */
1833 void filer_add_tip_details(FilerWindow *filer_window,
1834 GString *tip, DirItem *item)
1836 const guchar *fullpath = NULL;
1838 fullpath = make_path(filer_window->real_path, item->leafname);
1840 if (item->flags & ITEM_FLAG_SYMLINK)
1842 char *target;
1844 target = readlink_dup(fullpath);
1845 if (target)
1847 ensure_utf8(&target);
1849 g_string_append(tip, _("Symbolic link to "));
1850 g_string_append(tip, target);
1851 g_string_append_c(tip, '\n');
1852 g_free(target);
1856 if (item->flags & ITEM_FLAG_APPDIR)
1858 XMLwrapper *info;
1859 xmlNode *node;
1861 info = appinfo_get(fullpath, item);
1862 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
1864 guchar *str;
1865 str = xmlNodeListGetString(node->doc,
1866 node->xmlChildrenNode, 1);
1867 if (str)
1869 g_string_append(tip, str);
1870 g_string_append_c(tip, '\n');
1871 g_free(str);
1874 if (info)
1875 g_object_unref(info);
1878 if (!g_utf8_validate(item->leafname, -1, NULL))
1879 g_string_append(tip,
1880 _("This filename is not valid UTF-8. "
1881 "You should rename it.\n"));
1884 /* Return the selection as a text/uri-list.
1885 * g_free() the result.
1887 static guchar *filer_create_uri_list(FilerWindow *filer_window)
1889 GString *string;
1890 GString *leader;
1891 ViewIter iter;
1892 DirItem *item;
1893 guchar *retval;
1895 g_return_val_if_fail(filer_window != NULL, NULL);
1897 string = g_string_new(NULL);
1899 leader = g_string_new("file://");
1900 g_string_append(leader, our_host_name_for_dnd());
1901 g_string_append(leader, filer_window->sym_path);
1902 if (leader->str[leader->len - 1] != '/')
1903 g_string_append_c(leader, '/');
1905 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1906 while ((item = iter.next(&iter)))
1908 g_string_append(string, leader->str);
1909 g_string_append(string, item->leafname);
1910 g_string_append(string, "\r\n");
1913 g_string_free(leader, TRUE);
1914 retval = string->str;
1915 g_string_free(string, FALSE);
1917 return retval;
1920 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
1922 BindAction action;
1923 ViewIface *view = filer_window->view;
1924 DirItem *item = NULL;
1925 gboolean press = event->type == GDK_BUTTON_PRESS;
1926 ViewIter iter;
1927 OpenFlags flags = 0;
1929 if (event->button > 3)
1930 return;
1932 view_get_iter_at_point(view, &iter, event->x, event->y);
1933 item = iter.peek(&iter);
1935 if (item && event->button == 1 &&
1936 view_get_selected(view, &iter) &&
1937 filer_window->selection_state == GTK_STATE_INSENSITIVE)
1939 /* Possibly a really slow DnD operation? */
1940 filer_window->temp_item_selected = FALSE;
1942 filer_selection_changed(filer_window, event->time);
1943 return;
1946 if (filer_window->target_cb)
1948 dnd_motion_ungrab();
1949 if (item && press && event->button == 1)
1950 filer_window->target_cb(filer_window, &iter,
1951 filer_window->target_data);
1953 filer_target_mode(filer_window, NULL, NULL, NULL);
1955 return;
1958 action = bind_lookup_bev(
1959 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
1960 event);
1962 switch (action)
1964 case ACT_CLEAR_SELECTION:
1965 view_clear_selection(view);
1966 break;
1967 case ACT_TOGGLE_SELECTED:
1968 view_set_selected(view, &iter,
1969 !view_get_selected(view, &iter));
1970 break;
1971 case ACT_SELECT_EXCL:
1972 view_select_only(view, &iter);
1973 break;
1974 case ACT_EDIT_ITEM:
1975 flags |= OPEN_SHIFT;
1976 /* (no break) */
1977 case ACT_OPEN_ITEM:
1978 if (event->button != 1 || event->state & GDK_MOD1_MASK)
1979 flags |= OPEN_CLOSE_WINDOW;
1980 else
1981 flags |= OPEN_SAME_WINDOW;
1982 if (o_new_button_1.int_value)
1983 flags ^= OPEN_SAME_WINDOW;
1984 if (event->type == GDK_2BUTTON_PRESS)
1985 view_set_selected(view, &iter, FALSE);
1986 dnd_motion_ungrab();
1988 filer_openitem(filer_window, &iter, flags);
1989 break;
1990 case ACT_POPUP_MENU:
1991 dnd_motion_ungrab();
1992 tooltip_show(NULL);
1993 show_filer_menu(filer_window,
1994 (GdkEvent *) event, &iter);
1995 break;
1996 case ACT_PRIME_AND_SELECT:
1997 if (item && !view_get_selected(view, &iter))
1998 view_select_only(view, &iter);
1999 dnd_motion_start(MOTION_READY_FOR_DND);
2000 break;
2001 case ACT_PRIME_AND_TOGGLE:
2002 view_set_selected(view, &iter,
2003 !view_get_selected(view, &iter));
2004 dnd_motion_start(MOTION_READY_FOR_DND);
2005 break;
2006 case ACT_PRIME_FOR_DND:
2007 dnd_motion_start(MOTION_READY_FOR_DND);
2008 break;
2009 case ACT_IGNORE:
2010 if (press && event->button < 4)
2012 if (item)
2013 view_wink_item(view, &iter);
2014 dnd_motion_start(MOTION_NONE);
2016 break;
2017 case ACT_LASSO_CLEAR:
2018 view_clear_selection(view);
2019 /* (no break) */
2020 case ACT_LASSO_MODIFY:
2021 view_start_lasso_box(view, event);
2022 break;
2023 case ACT_RESIZE:
2024 view_autosize(filer_window->view);
2025 break;
2026 default:
2027 g_warning("Unsupported action : %d\n", action);
2028 break;
2032 /* It's time to make the tooltip appear. If we're not over the item any
2033 * more, or the item doesn't need a tooltip, do nothing.
2035 static gboolean tooltip_activate(GtkWidget *window)
2037 FilerWindow *filer_window;
2038 ViewIface *view;
2039 ViewIter iter;
2040 gint x, y;
2041 DirItem *item = NULL;
2042 GString *tip = NULL;
2044 g_return_val_if_fail(tip_item != NULL, 0);
2046 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2048 if (!filer_window)
2050 g_print("[ window destroyed ]\n");
2051 return FALSE; /* Window has been destroyed */
2054 view = filer_window->view;
2056 tooltip_show(NULL);
2058 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2059 view_get_iter_at_point(view, &iter, x, y);
2061 item = iter.peek(&iter);
2062 if (item != tip_item)
2063 return FALSE; /* Not still under the pointer */
2065 /* OK, the filer window still exists and the pointer is still
2066 * over the same item. Do we need to show a tip?
2069 tip = g_string_new(NULL);
2071 view_extend_tip(filer_window->view, &iter, tip);
2073 filer_add_tip_details(filer_window, tip, tip_item);
2075 if (tip->len > 1)
2077 g_string_truncate(tip, tip->len - 1);
2079 tooltip_show(tip->str);
2082 g_string_free(tip, TRUE);
2084 return FALSE;
2087 /* Motion detected on the View widget */
2088 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2090 ViewIface *view = filer_window->view;
2091 ViewIter iter;
2092 DirItem *item;
2094 view_get_iter_at_point(view, &iter, event->x, event->y);
2095 item = iter.peek(&iter);
2097 if (item)
2099 if (item != tip_item)
2101 tooltip_show(NULL);
2103 tip_item = item;
2104 motion_window = event->window;
2105 tooltip_prime((GtkFunction) tooltip_activate,
2106 G_OBJECT(filer_window->window));
2109 else
2111 tooltip_show(NULL);
2112 tip_item = NULL;
2115 if (motion_state != MOTION_READY_FOR_DND)
2116 return FALSE;
2118 if (!dnd_motion_moved(event))
2119 return FALSE;
2121 view_get_iter_at_point(view, &iter,
2122 event->x - (event->x_root - drag_start_x),
2123 event->y - (event->y_root - drag_start_y));
2124 item = iter.peek(&iter);
2125 if (!item)
2126 return FALSE;
2128 view_wink_item(view, NULL);
2130 if (!view_get_selected(view, &iter))
2132 if (event->state & GDK_BUTTON1_MASK)
2134 /* Select just this one */
2135 filer_window->temp_item_selected = TRUE;
2136 view_select_only(view, &iter);
2138 else
2140 if (view_count_selected(view) == 0)
2141 filer_window->temp_item_selected = TRUE;
2142 view_set_selected(view, &iter, TRUE);
2146 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2148 if (view_count_selected(view) == 1)
2150 if (!item->image)
2151 item = dir_update_item(filer_window->directory,
2152 item->leafname);
2154 if (!item)
2156 report_error(_("Item no longer exists!"));
2157 return FALSE;
2160 drag_one_item(GTK_WIDGET(view), event,
2161 make_path(filer_window->sym_path, item->leafname),
2162 item, item->image);
2163 #if 0
2164 /* XXX: Use thumbnail */
2165 item, view ? view->image : NULL);
2166 #endif
2168 else
2170 guchar *uris;
2172 uris = filer_create_uri_list(filer_window);
2173 drag_selection(GTK_WIDGET(view), event, uris);
2174 g_free(uris);
2177 return FALSE;
2180 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2181 FilerWindow *filer_window)
2183 if (filer_window->temp_item_selected)
2185 view_clear_selection(filer_window->view);
2186 filer_window->temp_item_selected = FALSE;
2190 /* Remove highlights */
2191 static void drag_leave(GtkWidget *widget,
2192 GdkDragContext *context,
2193 guint32 time,
2194 FilerWindow *filer_window)
2196 dnd_spring_abort();
2197 #if 0
2198 if (scrolled_adj)
2200 g_signal_handler_disconnect(scrolled_adj, scrolled_signal);
2201 scrolled_adj = NULL;
2203 #endif
2206 /* Called during the drag when the mouse is in a widget registered
2207 * as a drop target. Returns TRUE if we can accept the drop.
2209 static gboolean drag_motion(GtkWidget *widget,
2210 GdkDragContext *context,
2211 gint x,
2212 gint y,
2213 guint time,
2214 FilerWindow *filer_window)
2216 DirItem *item;
2217 ViewIface *view = filer_window->view;
2218 ViewIter iter;
2219 GdkDragAction action = context->suggested_action;
2220 const guchar *new_path = NULL;
2221 const char *type = NULL;
2222 gboolean retval = FALSE;
2224 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2226 GdkWindow *bin;
2227 int bin_y;
2228 /* Correct for position of bin window */
2229 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2230 gdk_window_get_position(bin, NULL, &bin_y);
2231 y -= bin_y;
2234 if (o_dnd_drag_to_icons.int_value)
2236 view_get_iter_at_point(view, &iter, x, y);
2237 item = iter.peek(&iter);
2239 else
2240 item = NULL;
2242 if (item && view_get_selected(view, &iter))
2243 type = NULL;
2244 else
2245 type = dnd_motion_item(context, &item);
2247 if (!type)
2248 item = NULL;
2250 /* Don't allow drops to non-writeable directories. BUT, still
2251 * allow drops on non-writeable SUBdirectories so that we can
2252 * do the spring-open thing.
2254 if (item && type == drop_dest_dir &&
2255 !(item->flags & ITEM_FLAG_APPDIR))
2257 #if 0
2258 /* XXX: This is needed so that directories don't
2259 * spring open while we scroll. Should go in
2260 * view_collection.c, I think.
2262 * XXX: Now we ARE in view_collection, maybe fix it?
2264 * XXX: Drat. Now we're in filer.c :-(
2267 GtkObject *vadj = GTK_OBJECT(collection->vadj);
2269 /* Subdir: prepare for spring-open */
2270 if (scrolled_adj != vadj)
2272 if (scrolled_adj)
2273 gtk_signal_disconnect(scrolled_adj,
2274 scrolled_signal);
2275 scrolled_adj = vadj;
2276 scrolled_signal = gtk_signal_connect(
2277 scrolled_adj,
2278 "value_changed",
2279 GTK_SIGNAL_FUNC(scrolled),
2280 collection);
2282 #endif
2283 dnd_spring_load(context, filer_window);
2285 else
2286 dnd_spring_abort();
2288 if (item)
2289 view_cursor_to_iter(view, &iter);
2290 else
2292 view_cursor_to_iter(view, NULL);
2294 /* Disallow background drops within a single window */
2295 if (type && gtk_drag_get_source_widget(context) == widget)
2296 type = NULL;
2299 if (type)
2301 if (item)
2302 new_path = make_path(filer_window->sym_path,
2303 item->leafname);
2304 else
2305 new_path = filer_window->sym_path;
2308 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2309 if (type)
2311 gdk_drag_status(context, action, time);
2312 g_dataset_set_data_full(context, "drop_dest_path",
2313 g_strdup(new_path), g_free);
2314 retval = TRUE;
2317 return retval;