r2918: Doing a Refresh in a directory under /uri/0install triggers a remote refresh.
[rox-filer.git] / ROX-Filer / src / filer.c
blob6d447b147eed8d0aae2f2d87498c8d7c6482e0a7
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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"
66 #include "bookmarks.h"
68 static XMLwrapper *groups = NULL;
70 /* Item we are about to display a tooltip for */
71 static DirItem *tip_item = NULL;
73 /* The window which the motion event for the tooltip came from. Use this
74 * to get the correct widget for finding the item under the pointer.
76 static GdkWindow *motion_window = NULL;
78 /* This is rather badly named. It's actually the filer window which received
79 * the last key press or Menu click event.
81 FilerWindow *window_with_focus = NULL;
83 GList *all_filer_windows = NULL;
85 static FilerWindow *window_with_primary = NULL;
87 /* Static prototypes */
88 static void attach(FilerWindow *filer_window);
89 static void detach(FilerWindow *filer_window);
90 static void filer_window_destroyed(GtkWidget *widget,
91 FilerWindow *filer_window);
92 static void update_display(Directory *dir,
93 DirAction action,
94 GPtrArray *items,
95 FilerWindow *filer_window);
96 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
97 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
98 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
99 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff);
100 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class);
101 static void filer_add_signals(FilerWindow *filer_window);
103 static void set_selection_state(FilerWindow *filer_window, gboolean normal);
104 static void filer_next_thumb(GObject *window, const gchar *path);
105 static void start_thumb_scanning(FilerWindow *filer_window);
106 static void filer_options_changed(void);
107 static void drag_end(GtkWidget *widget, GdkDragContext *context,
108 FilerWindow *filer_window);
109 static void drag_leave(GtkWidget *widget,
110 GdkDragContext *context,
111 guint32 time,
112 FilerWindow *filer_window);
113 static gboolean drag_motion(GtkWidget *widget,
114 GdkDragContext *context,
115 gint x,
116 gint y,
117 guint time,
118 FilerWindow *filer_window);
120 static GdkCursor *busy_cursor = NULL;
121 static GdkCursor *crosshair = NULL;
123 /* Indicates whether the filer's display is different to the machine it
124 * is actually running on.
126 static gboolean not_local = FALSE;
128 static Option o_short_flag_names;
129 static Option o_filer_view_type;
130 Option o_filer_auto_resize, o_unique_filer_windows;
131 Option o_filer_size_limit;
133 void filer_init(void)
135 const gchar *ohost;
136 const gchar *dpy;
137 gchar *dpyhost, *tmp;
139 option_add_int(&o_filer_size_limit, "filer_size_limit", 75);
140 option_add_int(&o_filer_auto_resize, "filer_auto_resize",
141 RESIZE_ALWAYS);
142 option_add_int(&o_unique_filer_windows, "filer_unique_windows", 0);
144 option_add_int(&o_short_flag_names, "filer_short_flag_names", FALSE);
146 option_add_int(&o_filer_view_type, "filer_view_type",
147 VIEW_TYPE_COLLECTION);
149 option_add_notify(filer_options_changed);
151 busy_cursor = gdk_cursor_new(GDK_WATCH);
152 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
154 /* Is the display on the local machine, or are we being
155 * run remotely? See filer_set_title().
157 ohost = our_host_name();
158 dpy = gdk_get_display();
159 dpyhost = g_strdup(dpy);
160 tmp = strchr(dpyhost, ':');
161 if (tmp)
162 *tmp = '\0';
164 if (dpyhost[0] && strcmp(ohost, dpyhost) != 0)
166 /* Try the cannonical name for dpyhost (see our_host_name()
167 * in support.c).
169 struct hostent *ent;
171 ent = gethostbyname(dpyhost);
172 if (!ent || strcmp(ohost, ent->h_name) != 0)
173 not_local = TRUE;
176 g_free(dpyhost);
179 static gboolean if_deleted(gpointer item, gpointer removed)
181 int i = ((GPtrArray *) removed)->len;
182 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
183 char *leafname = ((DirItem *) item)->leafname;
185 while (i--)
187 if (strcmp(leafname, r[i]->leafname) == 0)
188 return TRUE;
191 return FALSE;
194 /* Resize the filer window to w x h pixels, plus border (not clamped).
195 * If triggered by a key event, warp the pointer (for SloppyFocus users).
197 void filer_window_set_size(FilerWindow *filer_window, int w, int h)
199 GdkEvent *event;
200 GtkWidget *window;
202 g_return_if_fail(filer_window != NULL);
204 if (filer_window->scrollbar)
205 w += filer_window->scrollbar->allocation.width;
207 if (o_toolbar.int_value != TOOLBAR_NONE)
208 h += filer_window->toolbar->allocation.height;
209 if (filer_window->message)
210 h += filer_window->message->allocation.height;
212 window = filer_window->window;
214 if (GTK_WIDGET_VISIBLE(window))
216 gint x, y;
217 GtkRequisition *req = &window->requisition;
218 GdkWindow *gdk_window = window->window;
220 w = MAX(req->width, w);
221 h = MAX(req->height, h);
222 gdk_window_get_position(gdk_window, &x, &y);
224 if (x + w > screen_width || y + h > screen_height)
226 if (x + w > screen_width)
227 x = screen_width - w - 4;
228 if (y + h > screen_height)
229 y = screen_height - h - 4;
230 gdk_window_move_resize(gdk_window, x, y, w, h);
232 else
233 gdk_window_resize(gdk_window, w, h);
235 else
236 gtk_window_set_default_size(GTK_WINDOW(window), w, h);
238 event = gtk_get_current_event();
239 if (event && event->type == GDK_KEY_PRESS)
241 GdkWindow *win = filer_window->window->window;
243 XWarpPointer(gdk_x11_drawable_get_xdisplay(win),
244 None,
245 gdk_x11_drawable_get_xid(win),
246 0, 0, 0, 0,
247 w - 4, h - 4);
251 /* Called on a timeout while scanning or when scanning ends
252 * (whichever happens first).
254 static gint open_filer_window(FilerWindow *filer_window)
256 view_style_changed(filer_window->view, 0);
258 if (filer_window->open_timeout)
260 gtk_timeout_remove(filer_window->open_timeout);
261 filer_window->open_timeout = 0;
264 if (!GTK_WIDGET_VISIBLE(filer_window->window))
266 display_set_actual_size(filer_window, TRUE);
267 gtk_widget_show(filer_window->window);
270 return FALSE;
273 static void update_display(Directory *dir,
274 DirAction action,
275 GPtrArray *items,
276 FilerWindow *filer_window)
278 ViewIface *view = (ViewIface *) filer_window->view;
280 switch (action)
282 case DIR_ADD:
283 view_add_items(view, items);
284 /* Open and resize if currently hidden */
285 open_filer_window(filer_window);
286 break;
287 case DIR_REMOVE:
288 view_delete_if(view, if_deleted, items);
289 toolbar_update_info(filer_window);
290 break;
291 case DIR_START_SCAN:
292 set_scanning_display(filer_window, TRUE);
293 toolbar_update_info(filer_window);
294 break;
295 case DIR_END_SCAN:
296 if (filer_window->window->window)
297 gdk_window_set_cursor(
298 filer_window->window->window,
299 NULL);
300 set_scanning_display(filer_window, FALSE);
301 toolbar_update_info(filer_window);
302 open_filer_window(filer_window);
304 if (filer_window->had_cursor &&
305 !view_cursor_visible(view))
307 ViewIter start;
308 view_get_iter(view, &start, 0);
309 if (start.next(&start))
310 view_cursor_to_iter(view, &start);
311 view_show_cursor(view);
312 filer_window->had_cursor = FALSE;
314 if (filer_window->auto_select)
315 display_set_autoselect(filer_window,
316 filer_window->auto_select);
317 null_g_free(&filer_window->auto_select);
319 filer_create_thumbs(filer_window);
321 if (filer_window->thumb_queue)
322 start_thumb_scanning(filer_window);
323 break;
324 case DIR_UPDATE:
325 view_update_items(view, items);
326 break;
327 case DIR_ERROR_CHANGED:
328 filer_set_title(filer_window);
329 break;
333 static void attach(FilerWindow *filer_window)
335 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
336 view_clear(filer_window->view);
337 filer_window->scanning = TRUE;
338 dir_attach(filer_window->directory, (DirCallback) update_display,
339 filer_window);
340 filer_set_title(filer_window);
341 bookmarks_add_history(filer_window->sym_path);
343 if (filer_window->directory->error)
345 if (spring_in_progress)
346 g_printerr(_("Error scanning '%s':\n%s\n"),
347 filer_window->sym_path,
348 filer_window->directory->error);
349 else
350 delayed_error(_("Error scanning '%s':\n%s"),
351 filer_window->sym_path,
352 filer_window->directory->error);
356 static void detach(FilerWindow *filer_window)
358 g_return_if_fail(filer_window->directory != NULL);
360 dir_detach(filer_window->directory,
361 (DirCallback) update_display, filer_window);
362 g_object_unref(filer_window->directory);
363 filer_window->directory = NULL;
366 /* Returns TRUE to prevent closing the window. May offer to unmount a
367 * device.
369 gboolean filer_window_delete(GtkWidget *window,
370 GdkEvent *unused, /* (may be NULL) */
371 FilerWindow *filer_window)
373 if (mount_is_user_mounted(filer_window->real_path))
375 int action;
377 action = get_choice(PROJECT,
378 _("Do you want to unmount this device?\n\n"
379 "Unmounting a device makes it safe to remove "
380 "the disk."), 3,
381 GTK_STOCK_CANCEL, NULL,
382 GTK_STOCK_CLOSE, NULL,
383 ROX_STOCK_MOUNT, _("Unmount"));
385 if (action == 0)
386 return TRUE; /* Cancel close operation */
388 if (action == 2)
390 GList *list;
392 list = g_list_prepend(NULL, filer_window->sym_path);
393 action_mount(list, FALSE, TRUE);
394 g_list_free(list);
396 return TRUE;
400 return FALSE;
403 static void filer_window_destroyed(GtkWidget *widget, FilerWindow *filer_window)
405 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
407 g_object_set_data(G_OBJECT(widget), "filer_window", NULL);
409 if (window_with_primary == filer_window)
410 window_with_primary = NULL;
412 if (window_with_focus == filer_window)
414 menu_popdown();
415 window_with_focus = NULL;
418 if (filer_window->directory)
419 detach(filer_window);
421 if (filer_window->open_timeout)
423 gtk_timeout_remove(filer_window->open_timeout);
424 filer_window->open_timeout = 0;
427 if (filer_window->auto_scroll != -1)
429 gtk_timeout_remove(filer_window->auto_scroll);
430 filer_window->auto_scroll = -1;
433 if (filer_window->thumb_queue)
434 destroy_glist(&filer_window->thumb_queue);
436 tooltip_show(NULL);
438 g_free(filer_window->auto_select);
439 g_free(filer_window->real_path);
440 g_free(filer_window->sym_path);
441 g_free(filer_window);
443 one_less_window();
446 /* Returns TRUE iff the directory still exists. */
447 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
449 Directory *dir;
451 g_return_val_if_fail(filer_window != NULL, FALSE);
453 /* We do a fresh lookup (rather than update) because the inode may
454 * have changed.
456 dir = g_fscache_lookup(dir_cache, filer_window->real_path);
457 if (!dir)
459 if (warning)
460 info_message(_("Directory missing/deleted"));
461 gtk_widget_destroy(filer_window->window);
462 return FALSE;
464 if (dir == filer_window->directory)
465 g_object_unref(dir);
466 else
468 detach(filer_window);
469 filer_window->directory = dir;
470 attach(filer_window);
473 return TRUE;
476 /* No items are now selected. This might be because another app claimed
477 * the selection or because the user unselected all the items.
479 void filer_lost_selection(FilerWindow *filer_window, guint time)
481 if (window_with_primary == filer_window)
483 window_with_primary = NULL;
484 gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, time);
488 /* Another app has claimed the primary selection */
489 static void filer_lost_primary(GtkWidget *window,
490 GdkEventSelection *event,
491 gpointer user_data)
493 FilerWindow *filer_window = (FilerWindow *) user_data;
495 if (window_with_primary && window_with_primary == filer_window)
497 window_with_primary = NULL;
498 set_selection_state(filer_window, FALSE);
502 /* Someone wants us to send them the selection */
503 static void selection_get(GtkWidget *widget,
504 GtkSelectionData *selection_data,
505 guint info,
506 guint time,
507 gpointer data)
509 GString *reply, *header;
510 FilerWindow *filer_window = (FilerWindow *) data;
511 ViewIter iter;
512 DirItem *item;
514 reply = g_string_new(NULL);
515 header = g_string_new(NULL);
517 switch (info)
519 case TARGET_STRING:
520 g_string_printf(header, " %s",
521 make_path(filer_window->sym_path, ""));
522 break;
523 case TARGET_URI_LIST:
524 g_string_printf(header, " file://%s%s",
525 our_host_name_for_dnd(),
526 make_path(filer_window->sym_path, ""));
527 break;
530 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
532 while ((item = iter.next(&iter)))
534 g_string_append(reply, header->str);
535 g_string_append(reply, item->leafname);
538 if (reply->len > 0)
539 gtk_selection_data_set(selection_data, xa_string,
540 8, reply->str + 1, reply->len - 1);
541 else
543 g_warning("Attempt to paste empty selection!");
544 gtk_selection_data_set(selection_data, xa_string, 8, "", 0);
547 g_string_free(reply, TRUE);
548 g_string_free(header, TRUE);
551 /* Selection has been changed -- try to grab the primary selection
552 * if we don't have it. Also called when clicking on an insensitive selection
553 * to regain primary.
554 * Also updates toolbar info.
556 void filer_selection_changed(FilerWindow *filer_window, gint time)
558 toolbar_update_info(filer_window);
560 if (window_with_primary == filer_window)
561 return; /* Already got primary */
563 if (!view_count_selected(filer_window->view))
564 return; /* Nothing selected */
566 if (filer_window->temp_item_selected == FALSE &&
567 gtk_selection_owner_set(GTK_WIDGET(filer_window->window),
568 GDK_SELECTION_PRIMARY,
569 time))
571 window_with_primary = filer_window;
572 set_selection_state(filer_window, TRUE);
574 else
575 set_selection_state(filer_window, FALSE);
578 /* Open the item (or add it to the shell command minibuffer) */
579 void filer_openitem(FilerWindow *filer_window, ViewIter *iter, OpenFlags flags)
581 gboolean shift = (flags & OPEN_SHIFT) != 0;
582 gboolean close_mini = flags & OPEN_FROM_MINI;
583 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
584 DirItem *item;
585 const guchar *full_path;
586 gboolean wink = TRUE;
587 Directory *old_dir;
589 g_return_if_fail(filer_window != NULL && iter != NULL);
591 item = iter->peek(iter);
593 g_return_if_fail(item != NULL);
595 if (filer_window->mini_type == MINI_SHELL)
597 minibuffer_add(filer_window, item->leafname);
598 return;
601 if (!item->image)
602 dir_update_item(filer_window->directory, item->leafname);
604 if (item->base_type == TYPE_DIRECTORY)
606 /* Never close a filer window when opening a directory
607 * (click on a dir or click on an app with shift).
609 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
610 close_window = FALSE;
613 full_path = make_path(filer_window->sym_path, item->leafname);
614 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
615 wink = FALSE;
617 old_dir = filer_window->directory;
618 if (run_diritem(full_path, item,
619 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
620 filer_window,
621 shift))
623 if (old_dir != filer_window->directory)
624 return;
626 if (close_window)
627 gtk_widget_destroy(filer_window->window);
628 else
630 if (wink)
631 view_wink_item(filer_window->view, iter);
632 if (close_mini)
633 minibuffer_hide(filer_window);
638 static gint pointer_in(GtkWidget *widget,
639 GdkEventCrossing *event,
640 FilerWindow *filer_window)
642 may_rescan(filer_window, TRUE);
643 return FALSE;
646 static gint pointer_out(GtkWidget *widget,
647 GdkEventCrossing *event,
648 FilerWindow *filer_window)
650 tooltip_show(NULL);
651 return FALSE;
654 /* Move the cursor to the next selected item in direction 'dir'
655 * (+1 or -1).
657 static void next_selected(FilerWindow *filer_window, int dir)
659 ViewIter iter, cursor;
660 gboolean have_cursor;
661 ViewIface *view = filer_window->view;
663 g_return_if_fail(dir == 1 || dir == -1);
665 view_get_cursor(view, &cursor);
666 have_cursor = cursor.peek(&cursor) != NULL;
668 view_get_iter(view, &iter,
669 VIEW_ITER_SELECTED |
670 (have_cursor ? VIEW_ITER_FROM_CURSOR : 0) |
671 (dir < 0 ? VIEW_ITER_BACKWARDS : 0));
673 if (have_cursor && view_get_selected(view, &cursor))
674 iter.next(&iter); /* Skip the cursor itself */
676 if (iter.next(&iter))
677 view_cursor_to_iter(view, &iter);
678 else
679 gdk_beep();
681 return;
684 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
686 TargetFunc cb = filer_window->target_cb;
687 gpointer data = filer_window->target_data;
688 OpenFlags flags = 0;
689 ViewIter iter;
691 filer_target_mode(filer_window, NULL, NULL, NULL);
693 view_get_cursor(filer_window->view, &iter);
694 if (!iter.peek(&iter))
695 return;
697 if (cb)
699 cb(filer_window, &iter, data);
700 return;
703 if (event->state & GDK_SHIFT_MASK)
704 flags |= OPEN_SHIFT;
705 if (event->state & GDK_MOD1_MASK)
706 flags |= OPEN_CLOSE_WINDOW;
707 else
708 flags |= OPEN_SAME_WINDOW;
710 filer_openitem(filer_window, &iter, flags);
713 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
714 * changed. If no groups were loaded and there is no file then initialised
715 * groups to an empty document.
716 * Return the node for the 'name' group.
718 static xmlNode *group_find(char *name)
720 xmlNode *node;
721 gchar *path;
723 /* Update the groups, if possible */
724 path = choices_find_path_load("Groups.xml", PROJECT);
725 if (path)
727 XMLwrapper *wrapper;
728 wrapper = xml_cache_load(path);
729 if (wrapper)
731 if (groups)
732 g_object_unref(groups);
733 groups = wrapper;
736 g_free(path);
739 if (!groups)
741 groups = xml_new(NULL);
742 groups->doc = xmlNewDoc("1.0");
744 xmlDocSetRootElement(groups->doc,
745 xmlNewDocNode(groups->doc, NULL, "groups", NULL));
746 return NULL;
749 node = xmlDocGetRootElement(groups->doc);
751 for (node = node->xmlChildrenNode; node; node = node->next)
753 guchar *gid;
755 gid = xmlGetProp(node, "name");
757 if (!gid)
758 continue;
760 if (strcmp(name, gid) != 0)
761 continue;
763 g_free(gid);
765 return node;
768 return NULL;
771 static void group_save(FilerWindow *filer_window, char *name)
773 xmlNode *group;
774 guchar *save_path;
775 DirItem *item;
776 ViewIter iter;
778 group = group_find(name);
779 if (group)
781 xmlUnlinkNode(group);
782 xmlFreeNode(group);
784 group = xmlNewChild(xmlDocGetRootElement(groups->doc),
785 NULL, "group", NULL);
786 xmlSetProp(group, "name", name);
788 xmlNewChild(group, NULL, "directory", filer_window->sym_path);
790 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
792 while ((item = iter.next(&iter)))
793 xmlNewChild(group, NULL, "item", item->leafname);
795 save_path = choices_find_path_save("Groups.xml", PROJECT, TRUE);
796 if (save_path)
798 save_xml_file(groups->doc, save_path);
799 g_free(save_path);
803 static gboolean group_restore_cb(ViewIter *iter, gpointer data)
805 GHashTable *in_group = (GHashTable *) data;
807 return g_hash_table_lookup(in_group,
808 iter->peek(iter)->leafname) != NULL;
811 static void group_restore(FilerWindow *filer_window, char *name)
813 GHashTable *in_group;
814 char *path;
815 xmlNode *group, *node;
817 group = group_find(name);
819 if (!group)
821 report_error(_("Group %s is not set. Select some files "
822 "and press Ctrl+%s to set the group. Press %s "
823 "on its own to reselect the files later.\n"
824 "Make sure NumLock is on if you use the keypad."),
825 name, name, name);
826 return;
829 node = get_subnode(group, NULL, "directory");
830 g_return_if_fail(node != NULL);
831 path = xmlNodeListGetString(groups->doc, node->xmlChildrenNode, 1);
832 g_return_if_fail(path != NULL);
834 if (strcmp(path, filer_window->sym_path) != 0)
835 filer_change_to(filer_window, path, NULL);
836 g_free(path);
838 in_group = g_hash_table_new(g_str_hash, g_str_equal);
839 for (node = group->xmlChildrenNode; node; node = node->next)
841 gchar *leaf;
842 if (node->type != XML_ELEMENT_NODE)
843 continue;
844 if (strcmp(node->name, "item") != 0)
845 continue;
847 leaf = xmlNodeListGetString(groups->doc,
848 node->xmlChildrenNode, 1);
849 if (!leaf)
850 g_warning("Missing leafname!\n");
851 else
852 g_hash_table_insert(in_group, leaf, filer_window);
855 view_select_if(filer_window->view, &group_restore_cb, in_group);
857 g_hash_table_foreach(in_group, (GHFunc) g_free, NULL);
858 g_hash_table_destroy(in_group);
861 static gboolean popup_menu(GtkWidget *widget, FilerWindow *filer_window)
863 ViewIter iter;
865 view_get_cursor(filer_window->view, &iter);
867 show_filer_menu(filer_window, NULL, &iter);
869 return TRUE;
872 void filer_window_toggle_cursor_item_selected(FilerWindow *filer_window)
874 ViewIface *view = filer_window->view;
875 ViewIter iter;
877 view_get_iter(view, &iter, VIEW_ITER_FROM_CURSOR);
878 if (!iter.next(&iter))
879 return; /* No cursor */
881 if (view_get_selected(view, &iter))
882 view_set_selected(view, &iter, FALSE);
883 else
884 view_set_selected(view, &iter, TRUE);
886 if (iter.next(&iter))
887 view_cursor_to_iter(view, &iter);
890 gint filer_key_press_event(GtkWidget *widget,
891 GdkEventKey *event,
892 FilerWindow *filer_window)
894 ViewIface *view = filer_window->view;
895 ViewIter cursor;
896 GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
897 guint key = event->keyval;
898 char group[2] = "1";
900 window_with_focus = filer_window;
902 /* Delay setting up the keys until now to speed loading... */
903 if (ensure_filer_menu())
905 /* Gtk updates in an idle-handler, so force a recheck now */
906 g_signal_emit_by_name(widget, "keys_changed");
909 if (focus && focus == filer_window->minibuffer)
910 if (gtk_widget_event(focus, (GdkEvent *) event))
911 return TRUE; /* Handled */
913 if (!focus)
914 gtk_widget_grab_focus(GTK_WIDGET(view));
916 view_get_cursor(view, &cursor);
917 if (!cursor.peek(&cursor) && (key == GDK_Up || key == GDK_Down))
919 ViewIter iter;
920 view_get_iter(view, &iter, 0);
921 if (iter.next(&iter))
922 view_cursor_to_iter(view, &iter);
923 gtk_widget_grab_focus(GTK_WIDGET(view)); /* Needed? */
924 return TRUE;
927 switch (key)
929 case GDK_Escape:
930 filer_target_mode(filer_window, NULL, NULL, NULL);
931 view_cursor_to_iter(filer_window->view, NULL);
932 view_clear_selection(filer_window->view);
933 return FALSE;
934 case GDK_Return:
935 return_pressed(filer_window, event);
936 break;
937 case GDK_ISO_Left_Tab:
938 next_selected(filer_window, -1);
939 break;
940 case GDK_Tab:
941 next_selected(filer_window, 1);
942 break;
943 case GDK_BackSpace:
944 change_to_parent(filer_window);
945 break;
946 case GDK_backslash:
948 ViewIter iter;
950 tooltip_show(NULL);
952 view_get_cursor(filer_window->view, &iter);
953 show_filer_menu(filer_window,
954 (GdkEvent *) event, &iter);
955 break;
957 case ' ':
958 filer_window_toggle_cursor_item_selected(filer_window);
959 break;
960 default:
961 if (key >= GDK_0 && key <= GDK_9)
962 group[0] = key - GDK_0 + '0';
963 else if (key >= GDK_KP_0 && key <= GDK_KP_9)
964 group[0] = key - GDK_KP_0 + '0';
965 else
967 if (focus && focus != widget &&
968 gtk_widget_get_toplevel(focus) == widget)
969 if (gtk_widget_event(focus,
970 (GdkEvent *) event))
971 return TRUE; /* Handled */
972 return FALSE;
975 if (event->state & GDK_CONTROL_MASK)
976 group_save(filer_window, group);
977 else
978 group_restore(filer_window, group);
981 return TRUE;
984 void filer_open_parent(FilerWindow *filer_window)
986 char *dir;
987 const char *current = filer_window->sym_path;
989 if (current[0] == '/' && current[1] == '\0')
990 return; /* Already in the root */
992 dir = g_path_get_dirname(current);
993 filer_opendir(dir, filer_window, NULL);
994 g_free(dir);
997 void change_to_parent(FilerWindow *filer_window)
999 char *dir;
1000 const char *current = filer_window->sym_path;
1002 if (current[0] == '/' && current[1] == '\0')
1003 return; /* Already in the root */
1005 dir = g_path_get_dirname(current);
1006 filer_change_to(filer_window, dir, g_basename(current));
1007 g_free(dir);
1010 /* Removes trailing /s from path (modified in place) */
1011 static void tidy_sympath(gchar *path)
1013 int l;
1015 g_return_if_fail(path != NULL);
1017 l = strlen(path);
1018 while (l > 1 && path[l - 1] == '/')
1020 l--;
1021 path[l] = '\0';
1025 /* Make filer_window display path. When finished, highlight item 'from', or
1026 * the first item if from is NULL. If there is currently no cursor then
1027 * simply wink 'from' (if not NULL).
1028 * If the cause was a key event and we resize, warp the pointer.
1030 void filer_change_to(FilerWindow *filer_window,
1031 const char *path, const char *from)
1033 char *from_dup;
1034 char *sym_path, *real_path;
1035 Directory *new_dir;
1037 g_return_if_fail(filer_window != NULL);
1039 filer_cancel_thumbnails(filer_window);
1041 tooltip_show(NULL);
1043 sym_path = g_strdup(path);
1044 real_path = pathdup(path);
1045 new_dir = g_fscache_lookup(dir_cache, real_path);
1047 if (!new_dir)
1049 delayed_error(_("Directory '%s' is not accessible"),
1050 sym_path);
1051 g_free(real_path);
1052 g_free(sym_path);
1053 return;
1056 if (o_unique_filer_windows.int_value && !spring_in_progress)
1058 FilerWindow *fw;
1060 fw = find_filer_window(sym_path, filer_window);
1061 if (fw)
1062 gtk_widget_destroy(fw->window);
1065 from_dup = from && *from ? g_strdup(from) : NULL;
1067 detach(filer_window);
1068 g_free(filer_window->real_path);
1069 g_free(filer_window->sym_path);
1070 filer_window->real_path = real_path;
1071 filer_window->sym_path = sym_path;
1072 tidy_sympath(filer_window->sym_path);
1074 filer_window->directory = new_dir;
1076 g_free(filer_window->auto_select);
1077 filer_window->auto_select = from_dup;
1079 filer_window->had_cursor = filer_window->had_cursor ||
1080 view_cursor_visible(filer_window->view);
1082 filer_set_title(filer_window);
1083 if (filer_window->window->window)
1084 gdk_window_set_role(filer_window->window->window,
1085 filer_window->sym_path);
1086 view_cursor_to_iter(filer_window->view, NULL);
1088 attach(filer_window);
1090 display_set_actual_size(filer_window, FALSE);
1092 if (o_filer_auto_resize.int_value == RESIZE_ALWAYS)
1093 view_autosize(filer_window->view);
1095 if (filer_window->mini_type == MINI_PATH)
1096 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1097 filer_window);
1100 /* Returns a list containing the full (sym) pathname of every selected item.
1101 * You must g_free() each item in the list.
1103 GList *filer_selected_items(FilerWindow *filer_window)
1105 GList *retval = NULL;
1106 guchar *dir = filer_window->sym_path;
1107 ViewIter iter;
1108 DirItem *item;
1110 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1111 while ((item = iter.next(&iter)))
1113 retval = g_list_prepend(retval,
1114 g_strdup(make_path(dir, item->leafname)));
1117 return g_list_reverse(retval);
1120 /* Return the single selected item. Error if nothing is selected. */
1121 DirItem *filer_selected_item(FilerWindow *filer_window)
1123 ViewIter iter;
1124 DirItem *item;
1126 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1128 item = iter.next(&iter);
1129 g_return_val_if_fail(item != NULL, NULL);
1130 g_return_val_if_fail(iter.next(&iter) == NULL, NULL);
1132 return item;
1135 /* Creates and shows a new filer window.
1136 * If src_win != NULL then display options can be taken from that source window.
1137 * Returns the new filer window, or NULL on error.
1138 * Note: if unique windows is in use, may return an existing window.
1140 FilerWindow *filer_opendir(const char *path, FilerWindow *src_win,
1141 const gchar *wm_class)
1143 FilerWindow *filer_window;
1144 char *real_path;
1145 DisplayStyle dstyle;
1146 DetailsType dtype;
1147 SortType s_type;
1148 GtkSortType s_order;
1150 /* Get the real pathname of the directory and copy it */
1151 real_path = pathdup(path);
1153 if (o_unique_filer_windows.int_value && !spring_in_progress)
1155 FilerWindow *same_dir_window;
1157 same_dir_window = find_filer_window(path, NULL);
1159 if (same_dir_window)
1161 gtk_window_present(GTK_WINDOW(same_dir_window->window));
1162 return same_dir_window;
1166 filer_window = g_new(FilerWindow, 1);
1167 filer_window->message = NULL;
1168 filer_window->minibuffer = NULL;
1169 filer_window->minibuffer_label = NULL;
1170 filer_window->minibuffer_area = NULL;
1171 filer_window->temp_show_hidden = FALSE;
1172 filer_window->sym_path = g_strdup(path);
1173 filer_window->real_path = real_path;
1174 filer_window->scanning = FALSE;
1175 filer_window->had_cursor = FALSE;
1176 filer_window->auto_select = NULL;
1177 filer_window->toolbar_text = NULL;
1178 filer_window->target_cb = NULL;
1179 filer_window->mini_type = MINI_NONE;
1180 filer_window->selection_state = GTK_STATE_INSENSITIVE;
1181 filer_window->toolbar = NULL;
1182 filer_window->toplevel_vbox = NULL;
1183 filer_window->view_hbox = NULL;
1184 filer_window->view = NULL;
1185 filer_window->scrollbar = NULL;
1186 filer_window->auto_scroll = -1;
1188 tidy_sympath(filer_window->sym_path);
1190 /* Finds the entry for this directory in the dir cache, creating
1191 * a new one if needed. This does not cause a scan to start,
1192 * so if a new entry is created then it will be empty.
1194 filer_window->directory = g_fscache_lookup(dir_cache, real_path);
1195 if (!filer_window->directory)
1197 delayed_error(_("Directory '%s' not found."), path);
1198 g_free(filer_window->real_path);
1199 g_free(filer_window->sym_path);
1200 g_free(filer_window);
1201 return NULL;
1204 filer_window->temp_item_selected = FALSE;
1205 filer_window->flags = (FilerFlags) 0;
1206 filer_window->details_type = DETAILS_TIMES;
1207 filer_window->display_style = UNKNOWN_STYLE;
1208 filer_window->display_style_wanted = UNKNOWN_STYLE;
1209 filer_window->thumb_queue = NULL;
1210 filer_window->max_thumbs = 0;
1211 filer_window->sort_type = -1;
1213 if (src_win && o_display_inherit_options.int_value)
1215 s_type = src_win->sort_type;
1216 s_order = src_win->sort_order;
1217 dstyle = src_win->display_style_wanted;
1218 dtype = src_win->details_type;
1219 filer_window->show_hidden = src_win->show_hidden;
1220 filer_window->show_thumbs = src_win->show_thumbs;
1221 filer_window->view_type = src_win->view_type;
1223 else
1225 s_type = o_display_sort_by.int_value;
1226 s_order = GTK_SORT_ASCENDING;
1227 dstyle = o_display_size.int_value;
1228 dtype = o_display_details.int_value;
1229 filer_window->show_hidden = o_display_show_hidden.int_value;
1230 filer_window->show_thumbs = o_display_show_thumbs.int_value;
1231 filer_window->view_type = o_filer_view_type.int_value;
1234 /* Add all the user-interface elements & realise */
1235 filer_add_widgets(filer_window, wm_class);
1236 if (src_win)
1237 gtk_window_set_position(GTK_WINDOW(filer_window->window),
1238 GTK_WIN_POS_MOUSE);
1240 /* Connect to all the signal handlers */
1241 filer_add_signals(filer_window);
1243 display_set_layout(filer_window, dstyle, dtype, TRUE);
1244 display_set_sort_type(filer_window, s_type, s_order);
1246 /* Open the window after a timeout, or when scanning stops.
1247 * Do this before attaching, because attach() might tell us to
1248 * stop scanning (if a scan isn't needed).
1250 filer_window->open_timeout = gtk_timeout_add(500,
1251 (GtkFunction) open_filer_window,
1252 filer_window);
1254 /* The view is created empty and then attach() is called, which
1255 * links the filer window to the entry in the directory cache we
1256 * looked up / created above.
1258 * The attach() function will immediately callback to the filer window
1259 * to deliver a list of all known entries in the directory (so,
1260 * the number of items will be known after attach() returns).
1262 * If the directory was not in the cache (because it hadn't been
1263 * opened it before) then the types and icons for the entries are
1264 * not know, but the list of names is.
1267 attach(filer_window);
1269 number_of_windows++;
1270 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1272 return filer_window;
1275 void filer_set_view_type(FilerWindow *filer_window, ViewType type)
1277 GtkWidget *view = NULL;
1278 Directory *dir = NULL;
1279 GHashTable *selected = NULL;
1281 g_return_if_fail(filer_window != NULL);
1283 motion_window = NULL;
1285 if (filer_window->view)
1287 /* Save the current selection */
1288 ViewIter iter;
1289 DirItem *item;
1291 selected = g_hash_table_new(g_str_hash, g_str_equal);
1292 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
1293 while ((item = iter.next(&iter)))
1294 g_hash_table_insert(selected, item->leafname, "yes");
1296 /* Destroy the old view */
1297 gtk_widget_destroy(GTK_WIDGET(filer_window->view));
1298 filer_window->view = NULL;
1300 dir = filer_window->directory;
1301 g_object_ref(dir);
1302 detach(filer_window);
1305 switch (type)
1307 case VIEW_TYPE_COLLECTION:
1308 view = view_collection_new(filer_window);
1309 break;
1310 case VIEW_TYPE_DETAILS:
1311 view = view_details_new(filer_window);
1312 break;
1315 g_return_if_fail(view != NULL);
1317 filer_window->view = VIEW(view);
1318 filer_window->view_type = type;
1320 gtk_box_pack_start(filer_window->view_hbox, view, TRUE, TRUE, 0);
1321 gtk_widget_show(view);
1323 /* Drag and drop events */
1324 make_drop_target(view, 0);
1325 g_signal_connect(view, "drag_motion",
1326 G_CALLBACK(drag_motion), filer_window);
1327 g_signal_connect(view, "drag_leave",
1328 G_CALLBACK(drag_leave), filer_window);
1329 g_signal_connect(view, "drag_end",
1330 G_CALLBACK(drag_end), filer_window);
1331 /* Dragging from us... */
1332 g_signal_connect(view, "drag_data_get",
1333 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1335 if (dir)
1337 /* Only when changing type. Otherwise, will attach later. */
1338 filer_window->directory = dir;
1339 attach(filer_window);
1341 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
1342 view_autosize(filer_window->view);
1345 if (selected)
1347 ViewIter iter;
1348 DirItem *item;
1350 view_get_iter(filer_window->view, &iter, 0);
1351 while ((item = iter.next(&iter)))
1353 if (g_hash_table_lookup(selected, item->leafname))
1354 view_set_selected(filer_window->view,
1355 &iter, TRUE);
1357 g_hash_table_destroy(selected);
1361 /* This adds all the widgets to a new filer window. It is in a separate
1362 * function because filer_opendir() was getting too long...
1364 static void filer_add_widgets(FilerWindow *filer_window, const gchar *wm_class)
1366 GtkWidget *hbox, *vbox;
1368 /* Create the top-level window widget */
1369 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1370 filer_set_title(filer_window);
1371 if (wm_class)
1372 gtk_window_set_wmclass(GTK_WINDOW(filer_window->window),
1373 wm_class, PROJECT);
1375 /* This property is cleared when the window is destroyed.
1376 * You can thus ref filer_window->window and use this to see
1377 * if the window no longer exists.
1379 g_object_set_data(G_OBJECT(filer_window->window),
1380 "filer_window", filer_window);
1382 /* Create this now to make the Adjustment before the View */
1383 filer_window->scrollbar = gtk_vscrollbar_new(NULL);
1385 vbox = gtk_vbox_new(FALSE, 0);
1386 gtk_container_add(GTK_CONTAINER(filer_window->window), vbox);
1388 filer_window->toplevel_vbox = GTK_BOX(vbox);
1390 /* If there's a message that should be displayed in each window (eg
1391 * 'Running as root'), add it here.
1393 if (show_user_message)
1395 filer_window->message = gtk_label_new(show_user_message);
1396 gtk_box_pack_start(GTK_BOX(vbox), filer_window->message,
1397 FALSE, TRUE, 0);
1398 gtk_widget_show(filer_window->message);
1401 hbox = gtk_hbox_new(FALSE, 0);
1402 filer_window->view_hbox = GTK_BOX(hbox);
1403 gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
1404 /* Add the main View widget */
1405 filer_set_view_type(filer_window, filer_window->view_type);
1406 /* Put the scrollbar next to the View */
1407 gtk_box_pack_end(GTK_BOX(hbox),
1408 filer_window->scrollbar, FALSE, TRUE, 0);
1409 gtk_widget_show(hbox);
1411 /* If we want a toolbar, create it now */
1412 toolbar_update_toolbar(filer_window);
1414 /* And the minibuffer (hidden to start with) */
1415 create_minibuffer(filer_window);
1416 gtk_box_pack_end(GTK_BOX(vbox), filer_window->minibuffer_area,
1417 FALSE, TRUE, 0);
1419 /* And the thumbnail progress bar (also hidden) */
1421 GtkWidget *cancel;
1423 filer_window->thumb_bar = gtk_hbox_new(FALSE, 2);
1424 gtk_box_pack_end(GTK_BOX(vbox), filer_window->thumb_bar,
1425 FALSE, TRUE, 0);
1427 filer_window->thumb_progress = gtk_progress_bar_new();
1429 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1430 filer_window->thumb_progress, TRUE, TRUE, 0);
1432 cancel = gtk_button_new_with_label(_("Cancel"));
1433 GTK_WIDGET_UNSET_FLAGS(cancel, GTK_CAN_FOCUS);
1434 gtk_box_pack_start(GTK_BOX(filer_window->thumb_bar),
1435 cancel, FALSE, TRUE, 0);
1436 g_signal_connect_swapped(cancel, "clicked",
1437 G_CALLBACK(filer_cancel_thumbnails),
1438 filer_window);
1441 gtk_widget_show(vbox);
1442 gtk_widget_show(filer_window->scrollbar);
1444 gtk_widget_realize(filer_window->window);
1446 gdk_window_set_role(filer_window->window->window,
1447 filer_window->sym_path);
1449 filer_window_set_size(filer_window, 4, 4);
1452 static void filer_add_signals(FilerWindow *filer_window)
1454 GtkTargetEntry target_table[] =
1456 {"text/uri-list", 0, TARGET_URI_LIST},
1457 {"STRING", 0, TARGET_STRING},
1458 {"COMPOUND_TEXT", 0, TARGET_STRING},/* XXX: Treats as STRING */
1459 {"UTF8_STRING", 0, TARGET_STRING},
1462 /* Events on the top-level window */
1463 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1464 g_signal_connect(filer_window->window, "enter-notify-event",
1465 G_CALLBACK(pointer_in), filer_window);
1466 g_signal_connect(filer_window->window, "leave-notify-event",
1467 G_CALLBACK(pointer_out), filer_window);
1468 g_signal_connect(filer_window->window, "destroy",
1469 G_CALLBACK(filer_window_destroyed), filer_window);
1470 g_signal_connect(filer_window->window, "delete-event",
1471 G_CALLBACK(filer_window_delete), filer_window);
1473 g_signal_connect(filer_window->window, "selection_clear_event",
1474 G_CALLBACK(filer_lost_primary), filer_window);
1476 g_signal_connect(filer_window->window, "selection_get",
1477 G_CALLBACK(selection_get), filer_window);
1478 gtk_selection_add_targets(GTK_WIDGET(filer_window->window),
1479 GDK_SELECTION_PRIMARY,
1480 target_table,
1481 sizeof(target_table) / sizeof(*target_table));
1483 g_signal_connect(filer_window->window, "popup-menu",
1484 G_CALLBACK(popup_menu), filer_window);
1485 g_signal_connect(filer_window->window, "key_press_event",
1486 G_CALLBACK(filer_key_press_event), filer_window);
1488 gtk_window_add_accel_group(GTK_WINDOW(filer_window->window),
1489 filer_keys);
1492 static gint clear_scanning_display(FilerWindow *filer_window)
1494 if (filer_exists(filer_window))
1495 filer_set_title(filer_window);
1496 return FALSE;
1499 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1501 if (scanning == filer_window->scanning)
1502 return;
1503 filer_window->scanning = scanning;
1505 if (scanning)
1506 filer_set_title(filer_window);
1507 else
1508 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1509 filer_window);
1512 /* Note that filer_window may not exist after this call.
1513 * Returns TRUE iff the directory still exists.
1515 gboolean filer_update_dir(FilerWindow *filer_window, gboolean warning)
1517 gboolean still_exists;
1519 still_exists = may_rescan(filer_window, warning);
1521 if (still_exists)
1522 dir_update(filer_window->directory, filer_window->sym_path);
1524 return still_exists;
1527 void filer_update_all(void)
1529 GList *next = all_filer_windows;
1531 while (next)
1533 FilerWindow *filer_window = (FilerWindow *) next->data;
1535 /* Updating directory may remove it from list -- stop sending
1536 * patches to move this line!
1538 next = next->next;
1540 filer_update_dir(filer_window, TRUE);
1544 /* Refresh the various caches even if we don't think we need to */
1545 void full_refresh(void)
1547 mount_update(TRUE);
1548 reread_mime_files(); /* Refreshes all windows */
1551 /* See whether a filer window with a given path already exists
1552 * and is different from diff.
1554 static FilerWindow *find_filer_window(const char *sym_path, FilerWindow *diff)
1556 GList *next;
1558 for (next = all_filer_windows; next; next = next->next)
1560 FilerWindow *filer_window = (FilerWindow *) next->data;
1562 if (filer_window != diff &&
1563 strcmp(sym_path, filer_window->sym_path) == 0)
1564 return filer_window;
1567 return NULL;
1570 /* This path has been mounted/umounted/deleted some files - update all dirs */
1571 void filer_check_mounted(const char *real_path)
1573 GList *next = all_filer_windows;
1574 gchar *parent;
1575 int len;
1576 gboolean resize = o_filer_auto_resize.int_value == RESIZE_ALWAYS;
1578 /* DOS disks, etc, often don't change the mtime of the root directory
1579 * on modification, so force a refresh now.
1581 g_fscache_update(dir_cache, real_path);
1583 len = strlen(real_path);
1585 while (next)
1587 FilerWindow *filer_window = (FilerWindow *) next->data;
1589 next = next->next;
1591 if (strncmp(real_path, filer_window->real_path, len) == 0)
1593 char s = filer_window->real_path[len];
1595 if (s == '/' || s == '\0')
1597 if (filer_update_dir(filer_window, FALSE) &&
1598 resize)
1599 view_autosize(filer_window->view);
1604 parent = g_path_get_dirname(real_path);
1605 refresh_dirs(parent);
1606 g_free(parent);
1608 icons_may_update(real_path);
1611 /* Close all windows displaying 'path' or subdirectories of 'path' */
1612 void filer_close_recursive(const char *path)
1614 GList *next = all_filer_windows;
1615 gchar *real;
1616 int len;
1618 real = pathdup(path);
1619 len = strlen(real);
1621 while (next)
1623 FilerWindow *filer_window = (FilerWindow *) next->data;
1625 next = next->next;
1627 if (strncmp(real, filer_window->real_path, len) == 0)
1629 char s = filer_window->real_path[len];
1631 if (len == 1 || s == '/' || s == '\0')
1632 gtk_widget_destroy(filer_window->window);
1637 /* Like minibuffer_show(), except that:
1638 * - It returns FALSE (to be used from an idle callback)
1639 * - It checks that the filer window still exists.
1641 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1643 if (filer_exists(filer_window))
1644 minibuffer_show(filer_window, MINI_PATH);
1645 return FALSE;
1648 /* TRUE iff filer_window points to an existing FilerWindow
1649 * structure.
1651 gboolean filer_exists(FilerWindow *filer_window)
1653 GList *next;
1655 for (next = all_filer_windows; next; next = next->next)
1657 FilerWindow *fw = (FilerWindow *) next->data;
1659 if (fw == filer_window)
1660 return TRUE;
1663 return FALSE;
1666 /* Make sure the window title is up-to-date */
1667 void filer_set_title(FilerWindow *filer_window)
1669 gchar *title = NULL;
1670 guchar *flags = "";
1672 if (filer_window->scanning || filer_window->show_hidden ||
1673 filer_window->show_thumbs)
1675 if (o_short_flag_names.int_value)
1677 flags = g_strconcat(" +",
1678 filer_window->scanning ? _("S") : "",
1679 filer_window->show_hidden ? _("A") : "",
1680 filer_window->show_thumbs ? _("T") : "",
1681 NULL);
1683 else
1685 flags = g_strconcat(" (",
1686 filer_window->scanning ? _("Scanning, ") : "",
1687 filer_window->show_hidden ? _("All, ") : "",
1688 filer_window->show_thumbs ? _("Thumbs, ") : "",
1689 NULL);
1690 flags[strlen(flags) - 2] = ')';
1694 if (not_local)
1695 title = g_strconcat("//", our_host_name(),
1696 filer_window->sym_path, flags, NULL);
1698 if (!title && home_dir_len > 1 &&
1699 strncmp(filer_window->sym_path, home_dir, home_dir_len) == 0)
1701 guchar sep = filer_window->sym_path[home_dir_len];
1703 if (sep == '\0' || sep == '/')
1704 title = g_strconcat("~",
1705 filer_window->sym_path + home_dir_len,
1706 flags,
1707 NULL);
1710 if (!title)
1711 title = g_strconcat(filer_window->sym_path, flags, NULL);
1713 ensure_utf8(&title);
1715 if (filer_window->directory->error)
1717 gchar *old = title;
1718 title = g_strconcat(old, ": ", filer_window->directory->error,
1719 NULL);
1720 g_free(old);
1723 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1725 g_free(title);
1727 if (flags[0] != '\0')
1728 g_free(flags);
1731 /* Reconnect to the same directory (used when the Show Hidden option is
1732 * toggled). This has the side-effect of updating the window title.
1734 void filer_detach_rescan(FilerWindow *filer_window)
1736 Directory *dir = filer_window->directory;
1738 g_object_ref(dir);
1739 detach(filer_window);
1740 filer_window->directory = dir;
1741 attach(filer_window);
1744 /* Puts the filer window into target mode. When an item is chosen,
1745 * fn(filer_window, iter, data) is called. 'reason' will be displayed
1746 * on the toolbar while target mode is active.
1748 * Use fn == NULL to cancel target mode.
1750 void filer_target_mode(FilerWindow *filer_window,
1751 TargetFunc fn,
1752 gpointer data,
1753 const char *reason)
1755 TargetFunc old_fn = filer_window->target_cb;
1757 if (fn != old_fn)
1758 gdk_window_set_cursor(
1759 GTK_WIDGET(filer_window->view)->window,
1760 fn ? crosshair : NULL);
1762 filer_window->target_cb = fn;
1763 filer_window->target_data = data;
1765 if (filer_window->toolbar_text == NULL)
1766 return;
1768 if (fn)
1769 gtk_label_set_text(
1770 GTK_LABEL(filer_window->toolbar_text), reason);
1771 else if (o_toolbar_info.int_value)
1773 if (old_fn)
1774 toolbar_update_info(filer_window);
1776 else
1777 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1780 static void set_selection_state(FilerWindow *filer_window, gboolean normal)
1782 GtkStateType old_state = filer_window->selection_state;
1784 filer_window->selection_state = normal
1785 ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE;
1787 if (old_state != filer_window->selection_state
1788 && view_count_selected(filer_window->view))
1789 gtk_widget_queue_draw(GTK_WIDGET(filer_window->view));
1792 void filer_cancel_thumbnails(FilerWindow *filer_window)
1794 gtk_widget_hide(filer_window->thumb_bar);
1796 destroy_glist(&filer_window->thumb_queue);
1797 filer_window->max_thumbs = 0;
1800 /* Generate the next thumb for this window. The window object is
1801 * unref'd when there is nothing more to do.
1802 * If the window no longer has a filer window, nothing is done.
1804 static gboolean filer_next_thumb_real(GObject *window)
1806 FilerWindow *filer_window;
1807 gchar *path;
1808 int done, total;
1810 filer_window = g_object_get_data(window, "filer_window");
1812 if (!filer_window)
1814 g_object_unref(window);
1815 return FALSE;
1818 if (!filer_window->thumb_queue)
1820 filer_cancel_thumbnails(filer_window);
1821 g_object_unref(window);
1822 return FALSE;
1825 total = filer_window->max_thumbs;
1826 done = total - g_list_length(filer_window->thumb_queue);
1828 path = (gchar *) filer_window->thumb_queue->data;
1830 pixmap_background_thumb(path, (GFunc) filer_next_thumb, window);
1832 filer_window->thumb_queue = g_list_remove(filer_window->thumb_queue,
1833 path);
1834 g_free(path);
1836 gtk_progress_bar_set_fraction(
1837 GTK_PROGRESS_BAR(filer_window->thumb_progress),
1838 done / (float) total);
1840 return FALSE;
1843 /* path is the thumb just loaded, if any.
1844 * window is unref'd (eventually).
1846 static void filer_next_thumb(GObject *window, const gchar *path)
1848 if (path)
1849 dir_force_update_path(path);
1851 gtk_idle_add((GtkFunction) filer_next_thumb_real, window);
1854 static void start_thumb_scanning(FilerWindow *filer_window)
1856 if (GTK_WIDGET_VISIBLE(filer_window->thumb_bar))
1857 return; /* Already scanning */
1859 gtk_widget_show_all(filer_window->thumb_bar);
1861 g_object_ref(G_OBJECT(filer_window->window));
1862 filer_next_thumb(G_OBJECT(filer_window->window), NULL);
1865 /* Set this image to be loaded some time in the future */
1866 void filer_create_thumb(FilerWindow *filer_window, const gchar *path)
1868 filer_window->max_thumbs++;
1870 filer_window->thumb_queue = g_list_append(filer_window->thumb_queue,
1871 g_strdup(path));
1873 if (filer_window->scanning)
1874 return; /* Will start when scan ends */
1876 start_thumb_scanning(filer_window);
1879 /* If thumbnail display is on, look through all the items in this directory
1880 * and start creating or updating the thumbnails as needed.
1882 void filer_create_thumbs(FilerWindow *filer_window)
1884 DirItem *item;
1885 ViewIter iter;
1887 if (!filer_window->show_thumbs)
1888 return;
1890 view_get_iter(filer_window->view, &iter, 0);
1892 while ((item = iter.next(&iter)))
1894 MaskedPixmap *pixmap;
1895 const guchar *path;
1896 gboolean found;
1898 if (item->base_type != TYPE_FILE)
1899 continue;
1901 if (strcmp(item->mime_type->media_type, "image") != 0)
1902 continue;
1904 path = make_path(filer_window->real_path, item->leafname);
1906 pixmap = g_fscache_lookup_full(pixmap_cache, path,
1907 FSCACHE_LOOKUP_ONLY_NEW, &found);
1908 if (pixmap)
1909 g_object_unref(pixmap);
1911 /* If we didn't get an image, it could be because:
1913 * - We're loading the image now. found is TRUE,
1914 * and we'll update the item later.
1915 * - We tried to load the image and failed. found
1916 * is TRUE.
1917 * - We haven't tried loading the image. found is
1918 * FALSE, and we start creating the thumb here.
1920 if (!found)
1921 filer_create_thumb(filer_window, path);
1925 static void filer_options_changed(void)
1927 if (o_short_flag_names.has_changed)
1929 GList *next;
1931 for (next = all_filer_windows; next; next = next->next)
1933 FilerWindow *filer_window = (FilerWindow *) next->data;
1935 filer_set_title(filer_window);
1940 /* Append interesting information to this GString */
1941 void filer_add_tip_details(FilerWindow *filer_window,
1942 GString *tip, DirItem *item)
1944 const guchar *fullpath = NULL;
1946 fullpath = make_path(filer_window->real_path, item->leafname);
1948 if (item->flags & ITEM_FLAG_SYMLINK)
1950 char *target;
1952 target = readlink_dup(fullpath);
1953 if (target)
1955 ensure_utf8(&target);
1957 g_string_append(tip, _("Symbolic link to "));
1958 g_string_append(tip, target);
1959 g_string_append_c(tip, '\n');
1960 g_free(target);
1964 if (item->flags & ITEM_FLAG_APPDIR)
1966 XMLwrapper *info;
1967 xmlNode *node;
1969 info = appinfo_get(fullpath, item);
1970 if (info && ((node = xml_get_section(info, NULL, "Summary"))))
1972 guchar *str;
1973 str = xmlNodeListGetString(node->doc,
1974 node->xmlChildrenNode, 1);
1975 if (str)
1977 g_string_append(tip, str);
1978 g_string_append_c(tip, '\n');
1979 g_free(str);
1982 if (info)
1983 g_object_unref(info);
1986 if (!g_utf8_validate(item->leafname, -1, NULL))
1987 g_string_append(tip,
1988 _("This filename is not valid UTF-8. "
1989 "You should rename it.\n"));
1992 /* Return the selection as a text/uri-list.
1993 * g_free() the result.
1995 static guchar *filer_create_uri_list(FilerWindow *filer_window)
1997 GString *string;
1998 GString *leader;
1999 ViewIter iter;
2000 DirItem *item;
2001 guchar *retval;
2003 g_return_val_if_fail(filer_window != NULL, NULL);
2005 string = g_string_new(NULL);
2007 leader = g_string_new(filer_window->sym_path);
2008 if (leader->str[leader->len - 1] != '/')
2009 g_string_append_c(leader, '/');
2011 view_get_iter(filer_window->view, &iter, VIEW_ITER_SELECTED);
2012 while ((item = iter.next(&iter)))
2014 gchar *uri, *path;
2016 path = g_strconcat(leader->str, item->leafname, NULL);
2017 uri = encode_path_as_uri(path);
2018 g_string_append(string, uri);
2019 g_string_append(string, "\r\n");
2020 g_free(path);
2021 g_free(uri);
2024 g_string_free(leader, TRUE);
2025 retval = string->str;
2026 g_string_free(string, FALSE);
2028 return retval;
2031 void filer_perform_action(FilerWindow *filer_window, GdkEventButton *event)
2033 BindAction action;
2034 ViewIface *view = filer_window->view;
2035 DirItem *item = NULL;
2036 gboolean press = event->type == GDK_BUTTON_PRESS;
2037 ViewIter iter;
2038 OpenFlags flags = 0;
2040 if (event->button > 3)
2041 return;
2043 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2044 item = iter.peek(&iter);
2046 if (item && event->button == 1 &&
2047 view_get_selected(view, &iter) &&
2048 filer_window->selection_state == GTK_STATE_INSENSITIVE)
2050 /* Possibly a really slow DnD operation? */
2051 filer_window->temp_item_selected = FALSE;
2053 filer_selection_changed(filer_window, event->time);
2054 return;
2057 if (filer_window->target_cb)
2059 dnd_motion_ungrab();
2060 if (item && press && event->button == 1)
2061 filer_window->target_cb(filer_window, &iter,
2062 filer_window->target_data);
2064 filer_target_mode(filer_window, NULL, NULL, NULL);
2066 return;
2069 if (!o_single_click.int_value)
2071 /* Make sure both parts of a double-click fall on
2072 * the same file.
2074 static guchar *first_click = NULL;
2075 static guchar *second_click = NULL;
2077 if (event->type == GDK_BUTTON_PRESS)
2079 g_free(first_click);
2080 first_click = second_click;
2082 if (item)
2083 second_click = g_strdup(item->leafname);
2084 else
2085 second_click = NULL;
2088 if (event->type == GDK_2BUTTON_PRESS)
2090 if (first_click && second_click &&
2091 strcmp(first_click, second_click) != 0)
2092 return;
2093 if ((first_click || second_click) &&
2094 !(first_click && second_click))
2095 return;
2099 action = bind_lookup_bev(
2100 item ? BIND_DIRECTORY_ICON : BIND_DIRECTORY,
2101 event);
2103 switch (action)
2105 case ACT_CLEAR_SELECTION:
2106 view_clear_selection(view);
2107 break;
2108 case ACT_TOGGLE_SELECTED:
2109 view_set_selected(view, &iter,
2110 !view_get_selected(view, &iter));
2111 break;
2112 case ACT_SELECT_EXCL:
2113 view_select_only(view, &iter);
2114 break;
2115 case ACT_EDIT_ITEM:
2116 flags |= OPEN_SHIFT;
2117 /* (no break) */
2118 case ACT_OPEN_ITEM:
2119 if (event->button != 1 || event->state & GDK_MOD1_MASK)
2120 flags |= OPEN_CLOSE_WINDOW;
2121 else
2122 flags |= OPEN_SAME_WINDOW;
2123 if (o_new_button_1.int_value)
2124 flags ^= OPEN_SAME_WINDOW;
2125 if (event->type == GDK_2BUTTON_PRESS)
2126 view_set_selected(view, &iter, FALSE);
2127 dnd_motion_ungrab();
2129 filer_openitem(filer_window, &iter, flags);
2130 break;
2131 case ACT_POPUP_MENU:
2132 dnd_motion_ungrab();
2133 tooltip_show(NULL);
2134 show_filer_menu(filer_window,
2135 (GdkEvent *) event, &iter);
2136 break;
2137 case ACT_PRIME_AND_SELECT:
2138 if (item && !view_get_selected(view, &iter))
2139 view_select_only(view, &iter);
2140 dnd_motion_start(MOTION_READY_FOR_DND);
2141 break;
2142 case ACT_PRIME_AND_TOGGLE:
2143 view_set_selected(view, &iter,
2144 !view_get_selected(view, &iter));
2145 dnd_motion_start(MOTION_READY_FOR_DND);
2146 break;
2147 case ACT_PRIME_FOR_DND:
2148 dnd_motion_start(MOTION_READY_FOR_DND);
2149 break;
2150 case ACT_IGNORE:
2151 if (press && event->button < 4)
2153 if (item)
2154 view_wink_item(view, &iter);
2155 dnd_motion_start(MOTION_NONE);
2157 break;
2158 case ACT_LASSO_CLEAR:
2159 view_clear_selection(view);
2160 /* (no break) */
2161 case ACT_LASSO_MODIFY:
2162 view_start_lasso_box(view, event);
2163 break;
2164 case ACT_RESIZE:
2165 view_autosize(filer_window->view);
2166 break;
2167 default:
2168 g_warning("Unsupported action : %d\n", action);
2169 break;
2173 /* It's time to make the tooltip appear. If we're not over the item any
2174 * more, or the item doesn't need a tooltip, do nothing.
2176 static gboolean tooltip_activate(GtkWidget *window)
2178 FilerWindow *filer_window;
2179 ViewIface *view;
2180 ViewIter iter;
2181 gint x, y;
2182 DirItem *item = NULL;
2183 GString *tip = NULL;
2185 g_return_val_if_fail(tip_item != NULL, 0);
2187 filer_window = g_object_get_data(G_OBJECT(window), "filer_window");
2189 if (!motion_window || !filer_window)
2190 return FALSE; /* Window has been destroyed */
2192 view = filer_window->view;
2194 tooltip_show(NULL);
2196 gdk_window_get_pointer(motion_window, &x, &y, NULL);
2197 view_get_iter_at_point(view, &iter, motion_window, x, y);
2199 item = iter.peek(&iter);
2200 if (item != tip_item)
2201 return FALSE; /* Not still under the pointer */
2203 /* OK, the filer window still exists and the pointer is still
2204 * over the same item. Do we need to show a tip?
2207 tip = g_string_new(NULL);
2209 view_extend_tip(filer_window->view, &iter, tip);
2211 filer_add_tip_details(filer_window, tip, tip_item);
2213 if (tip->len > 1)
2215 g_string_truncate(tip, tip->len - 1);
2217 tooltip_show(tip->str);
2220 g_string_free(tip, TRUE);
2222 return FALSE;
2225 /* Motion detected on the View widget */
2226 gint filer_motion_notify(FilerWindow *filer_window, GdkEventMotion *event)
2228 ViewIface *view = filer_window->view;
2229 ViewIter iter;
2230 DirItem *item;
2232 view_get_iter_at_point(view, &iter, event->window, event->x, event->y);
2233 item = iter.peek(&iter);
2235 if (item)
2237 if (item != tip_item)
2239 tooltip_show(NULL);
2241 tip_item = item;
2242 motion_window = event->window;
2243 tooltip_prime((GtkFunction) tooltip_activate,
2244 G_OBJECT(filer_window->window));
2247 else
2249 tooltip_show(NULL);
2250 tip_item = NULL;
2253 if (motion_state != MOTION_READY_FOR_DND)
2254 return FALSE;
2256 if (!dnd_motion_moved(event))
2257 return FALSE;
2259 view_get_iter_at_point(view, &iter,
2260 event->window,
2261 event->x - (event->x_root - drag_start_x),
2262 event->y - (event->y_root - drag_start_y));
2263 item = iter.peek(&iter);
2264 if (!item)
2265 return FALSE;
2267 view_wink_item(view, NULL);
2269 if (!view_get_selected(view, &iter))
2271 if (event->state & GDK_BUTTON1_MASK)
2273 /* Select just this one */
2274 filer_window->temp_item_selected = TRUE;
2275 view_select_only(view, &iter);
2277 else
2279 if (view_count_selected(view) == 0)
2280 filer_window->temp_item_selected = TRUE;
2281 view_set_selected(view, &iter, TRUE);
2285 g_return_val_if_fail(view_count_selected(view) > 0, TRUE);
2287 if (view_count_selected(view) == 1)
2289 if (!item->image)
2290 item = dir_update_item(filer_window->directory,
2291 item->leafname);
2293 if (!item)
2295 report_error(_("Item no longer exists!"));
2296 return FALSE;
2299 drag_one_item(GTK_WIDGET(view), event,
2300 make_path(filer_window->sym_path, item->leafname),
2301 item, item->image);
2302 #if 0
2303 /* XXX: Use thumbnail */
2304 item, view ? view->image : NULL);
2305 #endif
2307 else
2309 guchar *uris;
2311 uris = filer_create_uri_list(filer_window);
2312 drag_selection(GTK_WIDGET(view), event, uris);
2313 g_free(uris);
2316 return FALSE;
2319 static void drag_end(GtkWidget *widget, GdkDragContext *context,
2320 FilerWindow *filer_window)
2322 filer_set_autoscroll(filer_window, FALSE);
2324 if (filer_window->temp_item_selected)
2326 view_clear_selection(filer_window->view);
2327 filer_window->temp_item_selected = FALSE;
2331 /* Remove highlights */
2332 static void drag_leave(GtkWidget *widget,
2333 GdkDragContext *context,
2334 guint32 time,
2335 FilerWindow *filer_window)
2337 dnd_spring_abort();
2340 /* Called during the drag when the mouse is in a widget registered
2341 * as a drop target. Returns TRUE if we can accept the drop.
2343 static gboolean drag_motion(GtkWidget *widget,
2344 GdkDragContext *context,
2345 gint x,
2346 gint y,
2347 guint time,
2348 FilerWindow *filer_window)
2350 DirItem *item;
2351 ViewIface *view = filer_window->view;
2352 ViewIter iter;
2353 GdkDragAction action = context->suggested_action;
2354 const guchar *new_path = NULL;
2355 const char *type = NULL;
2356 gboolean retval = FALSE;
2357 gboolean same_window;
2359 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value)
2361 guint state;
2362 gdk_window_get_pointer(NULL, NULL, NULL, &state);
2363 if (state & GDK_BUTTON1_MASK)
2364 action = GDK_ACTION_ASK;
2367 same_window = gtk_drag_get_source_widget(context) == widget;
2369 filer_set_autoscroll(filer_window, TRUE);
2371 if (filer_window->view_type == VIEW_TYPE_DETAILS)
2373 GdkWindow *bin;
2374 int bin_y;
2375 /* Correct for position of bin window */
2376 bin = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
2377 gdk_window_get_position(bin, NULL, &bin_y);
2378 y -= bin_y;
2381 if (o_dnd_drag_to_icons.int_value)
2383 view_get_iter_at_point(view, &iter, widget->window, x, y);
2384 item = iter.peek(&iter);
2386 else
2387 item = NULL;
2389 if (item && same_window && view_get_selected(view, &iter))
2390 type = NULL;
2391 else
2392 type = dnd_motion_item(context, &item);
2394 if (!type)
2395 item = NULL;
2397 /* Don't allow drops to non-writeable directories. BUT, still
2398 * allow drops on non-writeable SUBdirectories so that we can
2399 * do the spring-open thing.
2401 if (item && type == drop_dest_dir &&
2402 !(item->flags & ITEM_FLAG_APPDIR))
2404 dnd_spring_load(context, filer_window);
2406 else
2407 dnd_spring_abort();
2409 if (item)
2410 view_cursor_to_iter(view, &iter);
2411 else
2413 view_cursor_to_iter(view, NULL);
2415 /* Disallow background drops within a single window */
2416 if (type && same_window)
2417 type = NULL;
2420 if (type)
2422 if (item)
2423 new_path = make_path(filer_window->sym_path,
2424 item->leafname);
2425 else
2426 new_path = filer_window->sym_path;
2429 /* Don't ask about dragging to an application! */
2430 if (type == drop_dest_prog && action == GDK_ACTION_ASK)
2431 action = GDK_ACTION_COPY;
2433 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
2434 if (type)
2436 gdk_drag_status(context, action, time);
2437 g_dataset_set_data_full(context, "drop_dest_path",
2438 g_strdup(new_path), g_free);
2439 retval = TRUE;
2442 return retval;
2445 static gboolean as_timeout(FilerWindow *filer_window)
2447 gboolean retval;
2449 retval = view_auto_scroll_callback(filer_window->view);
2451 if (!retval)
2452 filer_window->auto_scroll = -1;
2454 return retval;
2457 /* When autoscroll is on, a timer keeps track of the pointer position.
2458 * While it's near the top or bottom of the window, the window scrolls.
2460 * If the mouse buttons are released, the pointer leaves the window, or
2461 * a drag-and-drop operation finishes, auto_scroll is turned off.
2463 void filer_set_autoscroll(FilerWindow *filer_window, gboolean auto_scroll)
2465 g_return_if_fail(filer_window != NULL);
2467 if (auto_scroll)
2469 if (filer_window->auto_scroll != -1)
2470 return; /* Already on! */
2472 filer_window->auto_scroll = gtk_timeout_add(50,
2473 (GtkFunction) as_timeout,
2474 filer_window);
2476 else
2478 if (filer_window->auto_scroll == -1)
2479 return; /* Already off! */
2481 gtk_timeout_remove(filer_window->auto_scroll);
2482 filer_window->auto_scroll = -1;
2486 #define ZERO_MNT "/uri/0install"
2488 static void refresh_done(FilerWindow *filer_window)
2490 if (filer_exists(filer_window))
2491 filer_update_dir(filer_window, TRUE);
2494 void filer_refresh(FilerWindow *filer_window)
2496 if (!strncmp(ZERO_MNT "/", filer_window->real_path, sizeof(ZERO_MNT)))
2498 /* Try to run 0refresh */
2499 gint pid;
2500 const gchar *argv[] = {"0refresh",
2501 filer_window->real_path, NULL};
2502 pid = rox_spawn(filer_window->real_path, argv);
2503 if (pid)
2504 on_child_death(pid, (CallbackFn) refresh_done,
2505 filer_window);
2508 full_refresh();