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)
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
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 */
32 #include <sys/param.h>
36 #include <gdk/gdkkeysyms.h>
44 #include "gui_support.h"
55 #include "minibuffer.h"
62 #include "view_iface.h"
63 #include "view_collection.h"
64 #include "view_details.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
,
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
,
110 FilerWindow
*filer_window
);
111 static gboolean
drag_motion(GtkWidget
*widget
,
112 GdkDragContext
*context
,
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)
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",
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
, ':');
162 if (dpyhost
[0] && strcmp(ohost
, dpyhost
) != 0)
164 /* Try the cannonical name for dpyhost (see our_host_name()
169 ent
= gethostbyname(dpyhost
);
170 if (!ent
|| strcmp(ohost
, ent
->h_name
) != 0)
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
;
185 if (strcmp(leafname
, r
[i
]->leafname
) == 0)
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
)
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
))
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
);
231 gdk_window_resize(gdk_window
, w
, h
);
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
),
243 gdk_x11_drawable_get_xid(win
),
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
);
271 static void update_display(Directory
*dir
,
274 FilerWindow
*filer_window
)
276 ViewIface
*view
= (ViewIface
*) filer_window
->view
;
281 view_add_items(view
, items
);
282 /* Open and resize if currently hidden */
283 open_filer_window(filer_window
);
286 view_delete_if(view
, if_deleted
, items
);
287 toolbar_update_info(filer_window
);
290 set_scanning_display(filer_window
, TRUE
);
291 toolbar_update_info(filer_window
);
294 if (filer_window
->window
->window
)
295 gdk_window_set_cursor(
296 filer_window
->window
->window
,
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
))
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
);
323 view_update_items(view
, items
);
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
,
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
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
))
359 action
= get_choice(PROJECT
,
360 _("Do you want to unmount this device?\n\n"
361 "Unmounting a device makes it safe to remove "
363 GTK_STOCK_CANCEL
, NULL
,
364 GTK_STOCK_CLOSE
, NULL
,
365 ROX_STOCK_MOUNT
, _("Unmount"));
368 return TRUE
; /* Cancel close operation */
374 list
= g_list_prepend(NULL
, filer_window
->sym_path
);
375 action_mount(list
, FALSE
, TRUE
);
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
)
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
);
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
);
420 /* Returns TRUE iff the directory still exists. */
421 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
)
425 g_return_val_if_fail(filer_window
!= NULL
, FALSE
);
427 /* We do a fresh lookup (rather than update) because the inode may
430 dir
= g_fscache_lookup(dir_cache
, filer_window
->real_path
);
434 info_message(_("Directory missing/deleted"));
435 gtk_widget_destroy(filer_window
->window
);
438 if (dir
== filer_window
->directory
)
442 detach(filer_window
);
443 filer_window
->directory
= dir
;
444 attach(filer_window
);
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
,
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
,
483 GString
*reply
, *header
;
484 FilerWindow
*filer_window
= (FilerWindow
*) data
;
488 reply
= g_string_new(NULL
);
489 header
= g_string_new(NULL
);
494 g_string_printf(header
, " %s",
495 make_path(filer_window
->sym_path
, ""));
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
, ""));
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
);
513 gtk_selection_data_set(selection_data
, xa_string
,
514 8, reply
->str
+ 1, reply
->len
- 1);
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
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
,
545 window_with_primary
= filer_window
;
546 set_selection_state(filer_window
, TRUE
);
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;
559 const guchar
*full_path
;
560 gboolean wink
= TRUE
;
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
);
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
))
591 old_dir
= filer_window
->directory
;
592 if (run_diritem(full_path
, item
,
593 flags
& OPEN_SAME_WINDOW
? filer_window
: NULL
,
597 if (old_dir
!= filer_window
->directory
)
601 gtk_widget_destroy(filer_window
->window
);
605 view_wink_item(filer_window
->view
, iter
);
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
);
620 static gint
pointer_out(GtkWidget
*widget
,
621 GdkEventCrossing
*event
,
622 FilerWindow
*filer_window
)
628 /* Move the cursor to the next selected item in direction 'dir'
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
,
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
);
658 static void return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
660 TargetFunc cb
= filer_window
->target_cb
;
661 gpointer data
= filer_window
->target_data
;
665 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
667 view_get_cursor(filer_window
->view
, &iter
);
668 if (!iter
.peek(&iter
))
673 cb(filer_window
, &iter
, data
);
677 if (event
->state
& GDK_SHIFT_MASK
)
679 if (event
->state
& GDK_MOD1_MASK
)
680 flags
|= OPEN_CLOSE_WINDOW
;
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
)
697 /* Update the groups, if possible */
698 path
= choices_find_path_load("Groups.xml", PROJECT
);
702 wrapper
= xml_cache_load(path
);
706 g_object_unref(groups
);
715 groups
= xml_new(NULL
);
716 groups
->doc
= xmlNewDoc("1.0");
718 xmlDocSetRootElement(groups
->doc
,
719 xmlNewDocNode(groups
->doc
, NULL
, "groups", NULL
));
723 node
= xmlDocGetRootElement(groups
->doc
);
725 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
729 gid
= xmlGetProp(node
, "name");
734 if (strcmp(name
, gid
) != 0)
745 static void group_save(FilerWindow
*filer_window
, char *name
)
752 group
= group_find(name
);
755 xmlUnlinkNode(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
);
772 save_xml_file(groups
->doc
, 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
;
789 xmlNode
*group
, *node
;
791 group
= group_find(name
);
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."),
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
);
812 in_group
= g_hash_table_new(g_str_hash
, g_str_equal
);
813 for (node
= group
->xmlChildrenNode
; node
; node
= node
->next
)
816 if (node
->type
!= XML_ELEMENT_NODE
)
818 if (strcmp(node
->name
, "item") != 0)
821 leaf
= xmlNodeListGetString(groups
->doc
,
822 node
->xmlChildrenNode
, 1);
824 g_warning("Missing leafname!\n");
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
)
839 view_get_cursor(filer_window
->view
, &iter
);
841 show_filer_menu(filer_window
, NULL
, &iter
);
846 void filer_window_toggle_cursor_item_selected(FilerWindow
*filer_window
)
848 ViewIface
*view
= filer_window
->view
;
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
);
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
,
867 FilerWindow
*filer_window
)
869 GtkWidget
*focus
= GTK_WINDOW(widget
)->focus_widget
;
870 guint key
= event
->keyval
;
873 window_with_focus
= filer_window
;
875 /* Delay setting up the keys until now to speed loading... */
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
),
883 if (focus
&& focus
!= widget
&&
884 gtk_widget_get_toplevel(focus
) == widget
)
885 if (gtk_widget_event(focus
, (GdkEvent
*) event
))
886 return TRUE
; /* Handled */
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
);
896 return_pressed(filer_window
, event
);
898 case GDK_ISO_Left_Tab
:
899 next_selected(filer_window
, -1);
902 next_selected(filer_window
, 1);
905 change_to_parent(filer_window
);
913 view_get_cursor(filer_window
->view
, &iter
);
914 show_filer_menu(filer_window
,
915 (GdkEvent
*) event
, &iter
);
919 filer_window_toggle_cursor_item_selected(filer_window
);
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';
930 if (event
->state
& GDK_CONTROL_MASK
)
931 group_save(filer_window
, group
);
933 group_restore(filer_window
, group
);
939 void filer_open_parent(FilerWindow
*filer_window
)
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
);
952 void change_to_parent(FilerWindow
*filer_window
)
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
));
965 /* Removes trailing /s from path (modified in place) */
966 static void tidy_sympath(gchar
*path
)
970 g_return_if_fail(path
!= NULL
);
973 while (l
> 1 && path
[l
- 1] == '/')
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
)
989 char *sym_path
, *real_path
;
992 g_return_if_fail(filer_window
!= NULL
);
994 filer_cancel_thumbnails(filer_window
);
998 sym_path
= g_strdup(path
);
999 real_path
= pathdup(path
);
1000 new_dir
= g_fscache_lookup(dir_cache
, real_path
);
1004 delayed_error(_("Directory '%s' is not accessible"),
1011 if (o_unique_filer_windows
.int_value
)
1015 fw
= find_filer_window(sym_path
, filer_window
);
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
,
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
;
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
)
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
);
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
;
1100 DisplayStyle dstyle
;
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
);
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
;
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
:
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
);
1189 gtk_window_set_position(GTK_WINDOW(filer_window
->window
),
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
,
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
;
1240 detach(filer_window
);
1245 case VIEW_TYPE_COLLECTION
:
1246 view
= view_collection_new(filer_window
);
1248 case VIEW_TYPE_DETAILS
:
1249 view
= view_details_new(filer_window
);
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
);
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
);
1294 gtk_window_set_wmclass(GTK_WINDOW(filer_window
->window
),
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
,
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
,
1336 /* And the thumbnail progress bar (also hidden) */
1340 filer_window
->thumb_bar
= gtk_hbox_new(FALSE
, 2);
1341 gtk_box_pack_end(GTK_BOX(vbox
), filer_window
->thumb_bar
,
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
),
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
,
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
);
1417 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
)
1419 if (scanning
== filer_window
->scanning
)
1421 filer_window
->scanning
= scanning
;
1424 filer_set_title(filer_window
);
1426 gtk_timeout_add(300, (GtkFunction
) clear_scanning_display
,
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
;
1443 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1445 /* Updating directory may remove it from list -- stop sending
1446 * patches to move this line!
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)
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
)
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
;
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
;
1487 len
= strlen(real_path
);
1491 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
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
);
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
;
1518 real
= pathdup(path
);
1523 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
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
);
1548 /* TRUE iff filer_window points to an existing FilerWindow
1551 gboolean
filer_exists(FilerWindow
*filer_window
)
1555 for (next
= all_filer_windows
; next
; next
= next
->next
)
1557 FilerWindow
*fw
= (FilerWindow
*) next
->data
;
1559 if (fw
== filer_window
)
1566 /* Make sure the window title is up-to-date */
1567 void filer_set_title(FilerWindow
*filer_window
)
1569 gchar
*title
= NULL
;
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") : "",
1585 flags
= g_strconcat(" (",
1586 filer_window
->scanning
? _("Scanning, ") : "",
1587 filer_window
->show_hidden
? _("All, ") : "",
1588 filer_window
->show_thumbs
? _("Thumbs, ") : "",
1590 flags
[strlen(flags
) - 2] = ')';
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
,
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
);
1619 if (flags
[0] != '\0')
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
;
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
,
1647 TargetFunc old_fn
= filer_window
->target_cb
;
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
)
1662 GTK_LABEL(filer_window
->toolbar_text
), reason
);
1663 else if (o_toolbar_info
.int_value
)
1666 toolbar_update_info(filer_window
);
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
;
1702 filer_window
= g_object_get_data(window
, "filer_window");
1706 g_object_unref(window
);
1710 if (!filer_window
->thumb_queue
)
1712 filer_cancel_thumbnails(filer_window
);
1713 g_object_unref(window
);
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
,
1728 gtk_progress_bar_set_fraction(
1729 GTK_PROGRESS_BAR(filer_window
->thumb_progress
),
1730 done
/ (float) total
);
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
)
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
,
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
)
1779 if (!filer_window
->show_thumbs
)
1782 view_get_iter(filer_window
->view
, &iter
, 0);
1784 while ((item
= iter
.next(&iter
)))
1786 MaskedPixmap
*pixmap
;
1790 if (item
->base_type
!= TYPE_FILE
)
1793 if (strcmp(item
->mime_type
->media_type
, "image") != 0)
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
);
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
1809 * - We haven't tried loading the image. found is
1810 * FALSE, and we start creating the thumb here.
1813 filer_create_thumb(filer_window
, path
);
1817 static void filer_options_changed(void)
1819 if (o_short_flag_names
.has_changed
)
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
)
1844 target
= readlink_dup(fullpath
);
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');
1856 if (item
->flags
& ITEM_FLAG_APPDIR
)
1861 info
= appinfo_get(fullpath
, item
);
1862 if (info
&& ((node
= xml_get_section(info
, NULL
, "Summary"))))
1865 str
= xmlNodeListGetString(node
->doc
,
1866 node
->xmlChildrenNode
, 1);
1869 g_string_append(tip
, str
);
1870 g_string_append_c(tip
, '\n');
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
)
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
);
1920 void filer_perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
)
1923 ViewIface
*view
= filer_window
->view
;
1924 DirItem
*item
= NULL
;
1925 gboolean press
= event
->type
== GDK_BUTTON_PRESS
;
1927 OpenFlags flags
= 0;
1929 if (event
->button
> 3)
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
);
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
);
1958 action
= bind_lookup_bev(
1959 item
? BIND_DIRECTORY_ICON
: BIND_DIRECTORY
,
1964 case ACT_CLEAR_SELECTION
:
1965 view_clear_selection(view
);
1967 case ACT_TOGGLE_SELECTED
:
1968 view_set_selected(view
, &iter
,
1969 !view_get_selected(view
, &iter
));
1971 case ACT_SELECT_EXCL
:
1972 view_select_only(view
, &iter
);
1975 flags
|= OPEN_SHIFT
;
1978 if (event
->button
!= 1 || event
->state
& GDK_MOD1_MASK
)
1979 flags
|= OPEN_CLOSE_WINDOW
;
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
);
1990 case ACT_POPUP_MENU
:
1991 dnd_motion_ungrab();
1993 show_filer_menu(filer_window
,
1994 (GdkEvent
*) event
, &iter
);
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
);
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
);
2006 case ACT_PRIME_FOR_DND
:
2007 dnd_motion_start(MOTION_READY_FOR_DND
);
2010 if (press
&& event
->button
< 4)
2013 view_wink_item(view
, &iter
);
2014 dnd_motion_start(MOTION_NONE
);
2017 case ACT_LASSO_CLEAR
:
2018 view_clear_selection(view
);
2020 case ACT_LASSO_MODIFY
:
2021 view_start_lasso_box(view
, event
);
2024 view_autosize(filer_window
->view
);
2027 g_warning("Unsupported action : %d\n", action
);
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
;
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");
2050 g_print("[ window destroyed ]\n");
2051 return FALSE
; /* Window has been destroyed */
2054 view
= filer_window
->view
;
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
);
2077 g_string_truncate(tip
, tip
->len
- 1);
2079 tooltip_show(tip
->str
);
2082 g_string_free(tip
, TRUE
);
2087 /* Motion detected on the View widget */
2088 gint
filer_motion_notify(FilerWindow
*filer_window
, GdkEventMotion
*event
)
2090 ViewIface
*view
= filer_window
->view
;
2094 view_get_iter_at_point(view
, &iter
, event
->x
, event
->y
);
2095 item
= iter
.peek(&iter
);
2099 if (item
!= tip_item
)
2104 motion_window
= event
->window
;
2105 tooltip_prime((GtkFunction
) tooltip_activate
,
2106 G_OBJECT(filer_window
->window
));
2115 if (motion_state
!= MOTION_READY_FOR_DND
)
2118 if (!dnd_motion_moved(event
))
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
);
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
);
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)
2151 item
= dir_update_item(filer_window
->directory
,
2156 report_error(_("Item no longer exists!"));
2160 drag_one_item(GTK_WIDGET(view
), event
,
2161 make_path(filer_window
->sym_path
, item
->leafname
),
2164 /* XXX: Use thumbnail */
2165 item
, view
? view
->image
: NULL
);
2172 uris
= filer_create_uri_list(filer_window
);
2173 drag_selection(GTK_WIDGET(view
), event
, uris
);
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
,
2194 FilerWindow
*filer_window
)
2200 g_signal_handler_disconnect(scrolled_adj
, scrolled_signal
);
2201 scrolled_adj
= NULL
;
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
,
2214 FilerWindow
*filer_window
)
2217 ViewIface
*view
= filer_window
->view
;
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
)
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
);
2234 if (o_dnd_drag_to_icons
.int_value
)
2236 view_get_iter_at_point(view
, &iter
, x
, y
);
2237 item
= iter
.peek(&iter
);
2242 if (item
&& view_get_selected(view
, &iter
))
2245 type
= dnd_motion_item(context
, &item
);
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
))
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
)
2273 gtk_signal_disconnect(scrolled_adj
,
2275 scrolled_adj
= vadj
;
2276 scrolled_signal
= gtk_signal_connect(
2279 GTK_SIGNAL_FUNC(scrolled
),
2283 dnd_spring_load(context
, filer_window
);
2289 view_cursor_to_iter(view
, &iter
);
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
)
2302 new_path
= make_path(filer_window
->sym_path
,
2305 new_path
= filer_window
->sym_path
;
2308 g_dataset_set_data(context
, "drop_dest_type", (gpointer
) 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
);