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 */
34 #include <sys/param.h>
38 #include <gdk/gdkkeysyms.h>
42 #include "collection.h"
47 #include "gui_support.h"
58 #include "minibuffer.h"
66 static XMLwrapper
*groups
= NULL
;
68 FilerWindow
*window_with_focus
= NULL
;
69 GList
*all_filer_windows
= NULL
;
71 static FilerWindow
*window_with_primary
= NULL
;
73 /* Item we are about to display a tooltip for */
74 static DirItem
*tip_item
= NULL
;
75 static GtkWidget
*tip_widget
= NULL
;
76 static gint tip_timeout
= 0;
77 static time_t tip_time
= 0; /* Time tip widget last closed */
79 /* Static prototypes */
80 static void attach(FilerWindow
*filer_window
);
81 static void detach(FilerWindow
*filer_window
);
82 static void filer_window_destroyed(GtkWidget
*widget
,
83 FilerWindow
*filer_window
);
84 static void add_item(FilerWindow
*filer_window
, DirItem
*item
);
85 static void update_display(Directory
*dir
,
88 FilerWindow
*filer_window
);
89 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
);
90 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
);
91 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
);
92 static FilerWindow
*find_filer_window(const char *sym_path
, FilerWindow
*diff
);
93 static gint
coll_button_release(GtkWidget
*widget
,
94 GdkEventButton
*event
,
95 FilerWindow
*filer_window
);
96 static gint
coll_button_press(GtkWidget
*widget
,
97 GdkEventButton
*event
,
98 FilerWindow
*filer_window
);
99 static gint
coll_motion_notify(GtkWidget
*widget
,
100 GdkEventMotion
*event
,
101 FilerWindow
*filer_window
);
102 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
);
103 static void filer_add_widgets(FilerWindow
*filer_window
);
104 static void filer_add_signals(FilerWindow
*filer_window
);
105 static void filer_tooltip_prime(FilerWindow
*filer_window
, DirItem
*item
);
106 static void show_tooltip(guchar
*text
);
107 static void filer_size_for(FilerWindow
*filer_window
,
108 int w
, int h
, int n
, gboolean allow_shrink
);
110 static void set_selection_state(FilerWindow
*collection
, gboolean normal
);
111 static void filer_next_thumb(GObject
*window
, const gchar
*path
);
112 static void start_thumb_scanning(FilerWindow
*filer_window
);
113 static void filer_options_changed(void);
115 static GdkCursor
*busy_cursor
= NULL
;
116 static GdkCursor
*crosshair
= NULL
;
118 /* Indicates whether the filer's display is different to the machine it
119 * is actually running on.
121 static gboolean not_local
=FALSE
;
123 static Option o_filer_size_limit
, o_short_flag_names
;
124 Option o_filer_auto_resize
, o_unique_filer_windows
;
126 void filer_init(void)
130 gchar
*dpyhost
, *tmp
;
132 option_add_int(&o_filer_size_limit
, "filer_size_limit", 75);
133 option_add_int(&o_filer_auto_resize
, "filer_auto_resize",
135 option_add_int(&o_unique_filer_windows
, "filer_unique_windows", 0);
137 option_add_int(&o_short_flag_names
, "filer_short_flag_names", FALSE
);
139 option_add_notify(filer_options_changed
);
141 busy_cursor
= gdk_cursor_new(GDK_WATCH
);
142 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
144 /* Is the display on the local machine, or are we being
145 * run remotely? See filer_set_title().
147 ohost
= our_host_name();
148 dpy
= gdk_get_display();
149 dpyhost
= g_strdup(dpy
);
150 tmp
= strchr(dpyhost
, ':');
154 if (dpyhost
[0] && strcmp(ohost
, dpyhost
) != 0)
156 /* Try the cannonical name for dpyhost (see our_host_name()
161 ent
= gethostbyname(dpyhost
);
162 if (!ent
|| strcmp(ohost
, ent
->h_name
) != 0)
169 static gboolean
if_deleted(gpointer item
, gpointer removed
)
171 int i
= ((GPtrArray
*) removed
)->len
;
172 DirItem
**r
= (DirItem
**) ((GPtrArray
*) removed
)->pdata
;
173 char *leafname
= ((DirItem
*) item
)->leafname
;
177 if (strcmp(leafname
, r
[i
]->leafname
) == 0)
184 static void update_item(FilerWindow
*filer_window
, DirItem
*item
)
186 char *leafname
= item
->leafname
;
187 Collection
*collection
= filer_window
->collection
;
188 int old_w
= collection
->item_width
;
189 int old_h
= collection
->item_height
;
191 CollectionItem
*colitem
;
193 if (leafname
[0] == '.' && filer_window
->show_hidden
== FALSE
)
196 i
= collection_find_item(collection
, item
, filer_window
->sort_fn
);
200 g_warning("Failed to find '%s'\n", leafname
);
204 colitem
= &collection
->items
[i
];
206 display_update_view(filer_window
,
207 (DirItem
*) colitem
->data
,
208 (ViewData
*) colitem
->view_data
,
211 calc_size(filer_window
, colitem
, &w
, &h
);
212 if (w
> old_w
|| h
> old_h
)
213 collection_set_item_size(collection
,
217 collection_draw_item(collection
, i
, TRUE
);
220 /* Resize the filer window to w x h pixels, plus border (not clamped) */
221 static void filer_window_set_size(FilerWindow
*filer_window
,
223 gboolean allow_shrink
)
227 g_return_if_fail(filer_window
!= NULL
);
229 if (filer_window
->scrollbar
)
230 w
+= filer_window
->scrollbar
->allocation
.width
;
232 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
233 h
+= filer_window
->toolbar
->allocation
.height
;
234 if (filer_window
->message
)
235 h
+= filer_window
->message
->allocation
.height
;
237 window
= filer_window
->window
;
239 if (GTK_WIDGET_VISIBLE(window
))
242 GtkRequisition
*req
= &window
->requisition
;
243 GdkWindow
*gdk_window
= window
->window
;
245 w
= MAX(req
->width
, w
);
246 h
= MAX(req
->height
, h
);
247 gdk_window_get_position(gdk_window
, &x
, &y
);
253 gdk_drawable_get_size(gdk_window
, &old_w
, &old_h
);
257 if (w
== old_w
&& h
== old_h
)
261 if (x
+ w
> screen_width
|| y
+ h
> screen_height
)
263 if (x
+ w
> screen_width
)
264 x
= screen_width
- w
- 4;
265 if (y
+ h
> screen_height
)
266 y
= screen_height
- h
- 4;
267 gdk_window_move_resize(gdk_window
, x
, y
, w
, h
);
270 gdk_window_resize(gdk_window
, w
, h
);
273 gtk_window_set_default_size(GTK_WINDOW(window
), w
, h
);
276 /* Resize the window to fit the items currently in the Directory.
277 * This should be used once the Directory has been fully scanned, otherwise
278 * the window will appear too small. When opening a directory for the first
279 * time, the names will be known but not the types and images. We can
280 * still make a good estimate of the size.
282 void filer_window_autosize(FilerWindow
*filer_window
, gboolean allow_shrink
)
284 Collection
*collection
= filer_window
->collection
;
287 n
= collection
->number_of_items
;
290 filer_size_for(filer_window
,
291 collection
->item_width
,
292 collection
->item_height
,
296 /* Choose a good size for this window, assuming n items of size (w, h) */
297 static void filer_size_for(FilerWindow
*filer_window
,
298 int w
, int h
, int n
, gboolean allow_shrink
)
308 size_limit
= o_filer_size_limit
.int_value
;
310 /* Get the extra height required for the toolbar and minibuffer,
313 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
314 t
= filer_window
->toolbar
->allocation
.height
;
315 if (filer_window
->message
)
316 t
+= filer_window
->message
->allocation
.height
;
317 if (GTK_WIDGET_VISIBLE(filer_window
->minibuffer_area
))
321 gtk_widget_size_request(filer_window
->minibuffer_area
, &req
);
322 space
= req
.height
+ 2;
326 max_x
= (size_limit
* screen_width
) / 100;
327 max_rows
= (size_limit
* screen_height
) / (h
* 100);
329 /* Aim for a size where
330 * x = r(y + t + h), (1)
331 * unless that's too wide.
333 * t = toolbar (and minibuffer) height
334 * r = desired (width / height) ratio
336 * Want to display all items:
339 * => x(x/r - t - h) = nwh (from 1)
340 * => xx - x.rt - hr(1 - nw) = 0
341 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
345 * sqrt(rt.rt + ...) > rt
347 * So, the +/- must be +:
349 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
351 * ( + w - 1 to round up)
353 x
= (r
* t
+ sqrt(r
*r
*t
*t
+ 4*h
*r
* (n
*w
- 1))) / 2 + w
- 1;
362 /* Choose rows to display all items given our chosen x.
363 * Don't make the window *too* big!
365 rows
= (n
+ cols
- 1) / cols
;
369 /* Leave some room for extra icons, but only in Small Icons mode
370 * otherwise it takes up too much space.
371 * Also, don't add space if the minibuffer is open.
374 space
= filer_window
->display_style
== SMALL_ICONS
? h
: 2;
376 filer_window_set_size(filer_window
,
378 h
* MAX(rows
, 1) + space
,
382 /* Called on a timeout while scanning or when scanning ends
383 * (whichever happens first).
385 static gint
open_filer_window(FilerWindow
*filer_window
)
387 shrink_grid(filer_window
);
389 if (filer_window
->open_timeout
)
391 gtk_timeout_remove(filer_window
->open_timeout
);
392 filer_window
->open_timeout
= 0;
395 if (!GTK_WIDGET_VISIBLE(filer_window
->window
))
397 filer_window_autosize(filer_window
, TRUE
);
398 gtk_widget_show(filer_window
->window
);
404 static void update_display(Directory
*dir
,
407 FilerWindow
*filer_window
)
411 Collection
*collection
= filer_window
->collection
;
416 old_num
= collection
->number_of_items
;
417 for (i
= 0; i
< items
->len
; i
++)
419 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
421 add_item(filer_window
, item
);
424 if (old_num
!= collection
->number_of_items
)
425 collection_qsort(collection
,
426 filer_window
->sort_fn
);
428 /* Open and resize if currently hidden */
429 open_filer_window(filer_window
);
432 collection_delete_if(collection
, if_deleted
, items
);
435 set_scanning_display(filer_window
, TRUE
);
436 toolbar_update_info(filer_window
);
439 if (filer_window
->window
->window
)
440 gdk_window_set_cursor(
441 filer_window
->window
->window
,
443 set_scanning_display(filer_window
, FALSE
);
444 toolbar_update_info(filer_window
);
445 open_filer_window(filer_window
);
447 if (filer_window
->had_cursor
&&
448 collection
->cursor_item
== -1)
450 collection_set_cursor_item(collection
, 0);
451 filer_window
->had_cursor
= FALSE
;
453 if (filer_window
->auto_select
)
454 display_set_autoselect(filer_window
,
455 filer_window
->auto_select
);
456 g_free(filer_window
->auto_select
);
457 filer_window
->auto_select
= NULL
;
459 filer_create_thumbs(filer_window
);
461 if (filer_window
->thumb_queue
)
462 start_thumb_scanning(filer_window
);
465 collection_qsort(collection
, filer_window
->sort_fn
);
467 for (i
= 0; i
< items
->len
; i
++)
469 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
471 update_item(filer_window
, item
);
477 static void attach(FilerWindow
*filer_window
)
479 gdk_window_set_cursor(filer_window
->window
->window
, busy_cursor
);
480 collection_clear(filer_window
->collection
);
481 filer_window
->scanning
= TRUE
;
482 dir_attach(filer_window
->directory
, (DirCallback
) update_display
,
484 filer_set_title(filer_window
);
487 static void detach(FilerWindow
*filer_window
)
489 g_return_if_fail(filer_window
->directory
!= NULL
);
491 dir_detach(filer_window
->directory
,
492 (DirCallback
) update_display
, filer_window
);
493 g_object_unref(filer_window
->directory
);
494 filer_window
->directory
= NULL
;
497 static void filer_window_destroyed(GtkWidget
*widget
,
498 FilerWindow
*filer_window
)
500 all_filer_windows
= g_list_remove(all_filer_windows
, filer_window
);
502 g_object_set_data(G_OBJECT(widget
), "filer_window", NULL
);
504 if (window_with_primary
== filer_window
)
505 window_with_primary
= NULL
;
507 if (window_with_focus
== filer_window
)
510 window_with_focus
= NULL
;
513 if (filer_window
->directory
)
514 detach(filer_window
);
516 if (filer_window
->open_timeout
)
518 gtk_timeout_remove(filer_window
->open_timeout
);
519 filer_window
->open_timeout
= 0;
522 if (filer_window
->thumb_queue
)
524 g_list_foreach(filer_window
->thumb_queue
, (GFunc
) g_free
, NULL
);
525 g_list_free(filer_window
->thumb_queue
);
528 filer_tooltip_prime(NULL
, NULL
);
530 g_free(filer_window
->auto_select
);
531 g_free(filer_window
->real_path
);
532 g_free(filer_window
->sym_path
);
533 g_free(filer_window
);
535 if (--number_of_windows
< 1)
539 /* Add a single object to a directory display */
540 static void add_item(FilerWindow
*filer_window
, DirItem
*item
)
542 char *leafname
= item
->leafname
;
543 Collection
*collection
= filer_window
->collection
;
544 int old_w
= collection
->item_width
;
545 int old_h
= collection
->item_height
;
548 if (leafname
[0] == '.')
550 if (!filer_window
->show_hidden
)
553 if (leafname
[1] == '\0')
554 return; /* Never show '.' */
556 if (leafname
[1] == '.' && leafname
[2] == '\0')
557 return; /* Never show '..' */
560 i
= collection_insert(collection
, item
,
561 display_create_viewdata(filer_window
, item
));
563 calc_size(filer_window
, &collection
->items
[i
], &w
, &h
);
565 if (w
> old_w
|| h
> old_h
)
566 collection_set_item_size(collection
,
571 /* Returns TRUE iff the directory still exists. */
572 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
)
576 g_return_val_if_fail(filer_window
!= NULL
, FALSE
);
578 /* We do a fresh lookup (rather than update) because the inode may
581 dir
= g_fscache_lookup(dir_cache
, filer_window
->real_path
);
585 info_message(_("Directory missing/deleted"));
586 gtk_widget_destroy(filer_window
->window
);
589 if (dir
== filer_window
->directory
)
593 detach(filer_window
);
594 filer_window
->directory
= dir
;
595 attach(filer_window
);
601 /* The collection widget has lost the primary selection */
602 static gint
collection_lose_selection(GtkWidget
*widget
,
603 GdkEventSelection
*event
)
605 if (window_with_primary
&&
606 window_with_primary
->collection
== COLLECTION(widget
))
608 FilerWindow
*filer_window
= window_with_primary
;
609 window_with_primary
= NULL
;
610 set_selection_state(filer_window
, FALSE
);
616 /* Someone wants us to send them the selection */
617 static void selection_get(GtkWidget
*widget
,
618 GtkSelectionData
*selection_data
,
623 GString
*reply
, *header
;
624 FilerWindow
*filer_window
;
626 Collection
*collection
;
628 filer_window
= g_object_get_data(G_OBJECT(widget
), "filer_window");
630 reply
= g_string_new(NULL
);
631 header
= g_string_new(NULL
);
636 g_string_sprintf(header
, " %s",
637 make_path(filer_window
->sym_path
, "")->str
);
639 case TARGET_URI_LIST
:
640 g_string_sprintf(header
, " file://%s%s",
641 our_host_name_for_dnd(),
642 make_path(filer_window
->sym_path
, "")->str
);
646 collection
= filer_window
->collection
;
647 for (i
= 0; i
< collection
->number_of_items
; i
++)
649 if (collection
->items
[i
].selected
)
652 (DirItem
*) collection
->items
[i
].data
;
654 g_string_append(reply
, header
->str
);
655 g_string_append(reply
, item
->leafname
);
658 /* This works, but I don't think I like it... */
659 /* g_string_append_c(reply, ' '); */
662 gtk_selection_data_set(selection_data
, xa_string
,
663 8, reply
->str
+ 1, reply
->len
- 1);
666 g_warning("Attempt to paste empty selection!");
667 gtk_selection_data_set(selection_data
, xa_string
, 8, "", 0);
670 g_string_free(reply
, TRUE
);
671 g_string_free(header
, TRUE
);
674 /* No items are now selected. This might be because another app claimed
675 * the selection or because the user unselected all the items.
677 static void lose_selection(Collection
*collection
,
681 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
683 if (window_with_primary
== filer_window
)
685 window_with_primary
= NULL
;
686 gtk_selection_owner_set(NULL
,
687 GDK_SELECTION_PRIMARY
,
692 static void selection_changed(Collection
*collection
,
696 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
698 /* Selection has been changed -- try to grab the primary selection
699 * if we don't have it.
701 if (window_with_primary
== filer_window
)
702 return; /* Already got it */
704 if (!collection
->number_selected
)
705 return; /* Nothing selected */
707 if (filer_window
->temp_item_selected
== FALSE
&&
708 gtk_selection_owner_set(GTK_WIDGET(collection
),
709 GDK_SELECTION_PRIMARY
,
712 window_with_primary
= filer_window
;
713 set_selection_state(filer_window
, TRUE
);
716 set_selection_state(filer_window
, FALSE
);
719 /* Open the item (or add it to the shell command minibuffer) */
720 void filer_openitem(FilerWindow
*filer_window
, int item_number
, OpenFlags flags
)
722 gboolean shift
= (flags
& OPEN_SHIFT
) != 0;
723 gboolean close_mini
= flags
& OPEN_FROM_MINI
;
724 gboolean close_window
= (flags
& OPEN_CLOSE_WINDOW
) != 0;
725 DirItem
*item
= (DirItem
*)
726 filer_window
->collection
->items
[item_number
].data
;
728 gboolean wink
= TRUE
;
731 if (filer_window
->mini_type
== MINI_SHELL
)
733 minibuffer_add(filer_window
, item
->leafname
);
738 dir_update_item(filer_window
->directory
, item
->leafname
);
740 if (item
->base_type
== TYPE_DIRECTORY
)
742 /* Never close a filer window when opening a directory
743 * (click on a dir or click on an app with shift).
745 if (shift
|| !(item
->flags
& ITEM_FLAG_APPDIR
))
746 close_window
= FALSE
;
749 full_path
= make_path(filer_window
->sym_path
, item
->leafname
)->str
;
750 if (shift
&& (item
->flags
& ITEM_FLAG_SYMLINK
))
753 old_dir
= filer_window
->directory
;
754 if (run_diritem(full_path
, item
,
755 flags
& OPEN_SAME_WINDOW
? filer_window
: NULL
,
759 if (old_dir
!= filer_window
->directory
)
763 gtk_widget_destroy(filer_window
->window
);
767 collection_wink_item(filer_window
->collection
,
770 minibuffer_hide(filer_window
);
775 static gint
pointer_in(GtkWidget
*widget
,
776 GdkEventCrossing
*event
,
777 FilerWindow
*filer_window
)
779 may_rescan(filer_window
, TRUE
);
783 static gint
pointer_out(GtkWidget
*widget
,
784 GdkEventCrossing
*event
,
785 FilerWindow
*filer_window
)
787 filer_tooltip_prime(NULL
, NULL
);
791 /* Move the cursor to the next selected item in direction 'dir'
794 static void next_selected(FilerWindow
*filer_window
, int dir
)
796 Collection
*collection
= filer_window
->collection
;
797 int to_check
= collection
->number_of_items
;
798 int item
= collection
->cursor_item
;
800 g_return_if_fail(dir
== 1 || dir
== -1);
802 if (to_check
> 0 && item
== -1)
804 /* Cursor not currently on */
808 item
= collection
->number_of_items
- 1;
810 if (collection
->items
[item
].selected
)
814 while (--to_check
> 0)
818 if (item
>= collection
->number_of_items
)
821 item
= collection
->number_of_items
- 1;
823 if (collection
->items
[item
].selected
)
830 collection_set_cursor_item(collection
, item
);
833 static void return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
835 Collection
*collection
= filer_window
->collection
;
836 int item
= collection
->cursor_item
;
837 TargetFunc cb
= filer_window
->target_cb
;
838 gpointer data
= filer_window
->target_data
;
839 OpenFlags flags
= OPEN_SAME_WINDOW
;
841 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
842 if (item
< 0 || item
>= collection
->number_of_items
)
847 cb(filer_window
, item
, data
);
851 if (event
->state
& GDK_SHIFT_MASK
)
854 filer_openitem(filer_window
, item
, flags
);
857 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
858 * changed. If no groups were loaded and there is no file then initialised
859 * groups to an empty document.
860 * Return the node for the 'name' group.
862 static xmlNode
*group_find(char *name
)
867 /* Update the groups, if possible */
868 path
= choices_find_path_load("Groups.xml", PROJECT
);
872 wrapper
= xml_cache_load(path
);
876 g_object_unref(groups
);
885 groups
= xml_new(NULL
);
886 groups
->doc
= xmlNewDoc("1.0");
888 xmlDocSetRootElement(groups
->doc
,
889 xmlNewDocNode(groups
->doc
, NULL
, "groups", NULL
));
893 node
= xmlDocGetRootElement(groups
->doc
);
895 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
899 gid
= xmlGetProp(node
, "name");
904 if (strcmp(name
, gid
) != 0)
915 static void group_save(FilerWindow
*filer_window
, char *name
)
917 Collection
*collection
= filer_window
->collection
;
922 group
= group_find(name
);
925 xmlUnlinkNode(group
);
928 group
= xmlNewChild(xmlDocGetRootElement(groups
->doc
),
929 NULL
, "group", NULL
);
930 xmlSetProp(group
, "name", name
);
932 xmlNewChild(group
, NULL
, "directory", filer_window
->sym_path
);
934 for (i
= 0; i
< collection
->number_of_items
; i
++)
936 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
937 gchar
*u8_leaf
= item
->leafname
;
939 if (!collection
->items
[i
].selected
)
942 xmlNewChild(group
, NULL
, "item", u8_leaf
);
945 save_path
= choices_find_path_save("Groups.xml", PROJECT
, TRUE
);
948 save_xml_file(groups
->doc
, save_path
);
953 static void group_restore(FilerWindow
*filer_window
, char *name
)
955 GHashTable
*in_group
;
956 Collection
*collection
= filer_window
->collection
;
959 xmlNode
*group
, *node
;
961 group
= group_find(name
);
965 report_error(_("Group %s is not set. Select some files "
966 "and press Ctrl+%s to set the group. Press %s "
967 "on its own to reselect the files later."),
972 node
= get_subnode(group
, NULL
, "directory");
973 g_return_if_fail(node
!= NULL
);
974 path
= xmlNodeListGetString(groups
->doc
, node
->xmlChildrenNode
, 1);
975 g_return_if_fail(path
!= NULL
);
977 if (strcmp(path
, filer_window
->sym_path
) != 0)
978 filer_change_to(filer_window
, path
, NULL
);
981 /* If an item at the start is selected then we could lose the
982 * primary selection after checking that item and then need to
983 * gain it again at the end. Therefore, if anything is selected
984 * then select the last item until the end of the search.
986 n
= collection
->number_of_items
;
987 if (collection
->number_selected
)
988 collection_select_item(collection
, n
- 1);
990 in_group
= g_hash_table_new(g_str_hash
, g_str_equal
);
991 for (node
= group
->xmlChildrenNode
; node
; node
= node
->next
)
994 if (node
->type
!= XML_ELEMENT_NODE
)
996 if (strcmp(node
->name
, "item") != 0)
999 leaf
= xmlNodeListGetString(groups
->doc
,
1000 node
->xmlChildrenNode
, 1);
1002 g_warning("Missing leafname!\n");
1004 g_hash_table_insert(in_group
, leaf
, filer_window
);
1007 for (j
= 0; j
< n
; j
++)
1009 DirItem
*item
= (DirItem
*) collection
->items
[j
].data
;
1011 if (g_hash_table_lookup(in_group
, item
->leafname
))
1012 collection_select_item(collection
, j
);
1014 collection_unselect_item(collection
, j
);
1017 g_hash_table_foreach(in_group
, (GHFunc
) g_free
, NULL
);
1018 g_hash_table_destroy(in_group
);
1021 /* Handle keys that can't be bound with the menu */
1022 static gint
key_press_event(GtkWidget
*widget
,
1024 FilerWindow
*filer_window
)
1027 guint key
= event
->keyval
;
1028 char group
[2] = "1";
1030 window_with_focus
= filer_window
;
1032 /* Note: Not convinced this is the way Gtk's key system is supposed
1036 ensure_filer_menu(); /* Gets the keys working... */
1037 gtk_window_add_accel_group(GTK_WINDOW(filer_window
->window
),
1039 handled
= gtk_accel_groups_activate(G_OBJECT(filer_window
->window
),
1040 event
->keyval
, event
->state
);
1041 if (window_with_focus
)
1042 gtk_window_remove_accel_group(GTK_WINDOW(filer_window
->window
),
1045 return TRUE
; /* Window no longer exists */
1052 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1055 return_pressed(filer_window
, event
);
1057 case GDK_ISO_Left_Tab
:
1058 next_selected(filer_window
, -1);
1061 next_selected(filer_window
, 1);
1064 change_to_parent(filer_window
);
1067 filer_tooltip_prime(NULL
, NULL
);
1068 show_filer_menu(filer_window
, (GdkEvent
*) event
,
1069 filer_window
->collection
->cursor_item
);
1072 if (key
>= GDK_0
&& key
<= GDK_9
)
1073 group
[0] = key
- GDK_0
+ '0';
1074 else if (key
>= GDK_KP_0
&& key
<= GDK_KP_9
)
1075 group
[0] = key
- GDK_KP_0
+ '0';
1080 if (event
->state
& GDK_CONTROL_MASK
)
1081 group_save(filer_window
, group
);
1083 group_restore(filer_window
, group
);
1089 void filer_open_parent(FilerWindow
*filer_window
)
1092 const char *current
= filer_window
->sym_path
;
1094 if (current
[0] == '/' && current
[1] == '\0')
1095 return; /* Already in the root */
1097 dir
= g_dirname(current
);
1098 filer_opendir(dir
, filer_window
);
1102 void change_to_parent(FilerWindow
*filer_window
)
1105 const char *current
= filer_window
->sym_path
;
1107 if (current
[0] == '/' && current
[1] == '\0')
1108 return; /* Already in the root */
1110 dir
= g_dirname(current
);
1111 filer_change_to(filer_window
, dir
, g_basename(current
));
1115 /* Removes trailing /s from path (modified in place) */
1116 static void tidy_sympath(gchar
*path
)
1120 g_return_if_fail(path
!= NULL
);
1123 while (l
> 1 && path
[l
- 1] == '/')
1130 /* Make filer_window display path. When finished, highlight item 'from', or
1131 * the first item if from is NULL. If there is currently no cursor then
1132 * simply wink 'from' (if not NULL).
1134 void filer_change_to(FilerWindow
*filer_window
,
1135 const char *path
, const char *from
)
1138 char *sym_path
, *real_path
;
1141 g_return_if_fail(filer_window
!= NULL
);
1143 filer_cancel_thumbnails(filer_window
);
1145 filer_tooltip_prime(NULL
, NULL
);
1147 sym_path
= g_strdup(path
);
1148 real_path
= pathdup(path
);
1149 new_dir
= g_fscache_lookup(dir_cache
, real_path
);
1153 delayed_error(_("Directory '%s' is not accessible"),
1160 if (o_unique_filer_windows
.int_value
)
1164 fw
= find_filer_window(sym_path
, filer_window
);
1166 gtk_widget_destroy(fw
->window
);
1169 from_dup
= from
&& *from
? g_strdup(from
) : NULL
;
1171 detach(filer_window
);
1172 g_free(filer_window
->real_path
);
1173 g_free(filer_window
->sym_path
);
1174 filer_window
->real_path
= real_path
;
1175 filer_window
->sym_path
= sym_path
;
1176 tidy_sympath(filer_window
->sym_path
);
1178 filer_window
->directory
= new_dir
;
1180 g_free(filer_window
->auto_select
);
1181 filer_window
->had_cursor
= filer_window
->collection
->cursor_item
!= -1
1182 || filer_window
->had_cursor
;
1183 filer_window
->auto_select
= from_dup
;
1185 filer_set_title(filer_window
);
1186 if (filer_window
->window
->window
)
1187 gdk_window_set_role(filer_window
->window
->window
,
1188 filer_window
->sym_path
);
1189 collection_set_cursor_item(filer_window
->collection
, -1);
1191 attach(filer_window
);
1192 if (o_filer_auto_resize
.int_value
== RESIZE_ALWAYS
)
1193 filer_window_autosize(filer_window
, TRUE
);
1195 if (filer_window
->mini_type
== MINI_PATH
)
1196 gtk_idle_add((GtkFunction
) minibuffer_show_cb
,
1200 /* Returns a list containing the full (sym) pathname of every selected item.
1201 * You must g_free() each item in the list.
1203 GList
*filer_selected_items(FilerWindow
*filer_window
)
1205 Collection
*collection
= filer_window
->collection
;
1206 GList
*retval
= NULL
;
1207 guchar
*dir
= filer_window
->sym_path
;
1210 for (i
= 0; i
< collection
->number_of_items
; i
++)
1212 if (collection
->items
[i
].selected
)
1214 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1216 retval
= g_list_prepend(retval
,
1217 g_strdup(make_path(dir
, item
->leafname
)->str
));
1221 return g_list_reverse(retval
);
1224 int selected_item_number(Collection
*collection
)
1228 g_return_val_if_fail(collection
!= NULL
, -1);
1229 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1230 g_return_val_if_fail(collection
->number_selected
== 1, -1);
1232 for (i
= 0; i
< collection
->number_of_items
; i
++)
1233 if (collection
->items
[i
].selected
)
1236 g_warning("selected_item: number_selected is wrong\n");
1241 DirItem
*selected_item(Collection
*collection
)
1245 item
= selected_item_number(collection
);
1248 return (DirItem
*) collection
->items
[item
].data
;
1252 /* Append all the URIs in the selection to the string */
1253 static void create_uri_list(FilerWindow
*filer_window
, GString
*string
)
1255 Collection
*collection
= filer_window
->collection
;
1257 int i
, num_selected
;
1259 leader
= g_string_new("file://");
1260 g_string_append(leader
, our_host_name_for_dnd());
1261 g_string_append(leader
, filer_window
->sym_path
);
1262 if (leader
->str
[leader
->len
- 1] != '/')
1263 g_string_append_c(leader
, '/');
1265 num_selected
= collection
->number_selected
;
1267 for (i
= 0; num_selected
> 0; i
++)
1269 if (collection
->items
[i
].selected
)
1271 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1273 g_string_append(string
, leader
->str
);
1274 g_string_append(string
, item
->leafname
);
1275 g_string_append(string
, "\r\n");
1280 g_string_free(leader
, TRUE
);
1283 /* Creates and shows a new filer window.
1284 * If src_win != NULL then display options can be taken from that source window.
1285 * Returns the new filer window, or NULL on error.
1286 * Note: if unique windows is in use, may return an existing window.
1288 FilerWindow
*filer_opendir(const char *path
, FilerWindow
*src_win
)
1290 FilerWindow
*filer_window
;
1292 DisplayStyle dstyle
;
1295 /* Get the real pathname of the directory and copy it */
1296 real_path
= pathdup(path
);
1298 if (o_unique_filer_windows
.int_value
)
1300 FilerWindow
*same_dir_window
;
1302 same_dir_window
= find_filer_window(path
, NULL
);
1304 if (same_dir_window
)
1306 gtk_window_present(GTK_WINDOW(same_dir_window
->window
));
1307 return same_dir_window
;
1311 filer_window
= g_new(FilerWindow
, 1);
1312 filer_window
->message
= NULL
;
1313 filer_window
->minibuffer
= NULL
;
1314 filer_window
->minibuffer_label
= NULL
;
1315 filer_window
->minibuffer_area
= NULL
;
1316 filer_window
->temp_show_hidden
= FALSE
;
1317 filer_window
->sym_path
= g_strdup(path
);
1318 filer_window
->real_path
= real_path
;
1319 filer_window
->scanning
= FALSE
;
1320 filer_window
->had_cursor
= FALSE
;
1321 filer_window
->auto_select
= NULL
;
1322 filer_window
->toolbar_text
= NULL
;
1323 filer_window
->target_cb
= NULL
;
1324 filer_window
->mini_type
= MINI_NONE
;
1325 filer_window
->selection_state
= GTK_STATE_INSENSITIVE
;
1326 filer_window
->toolbar
= NULL
;
1327 filer_window
->toplevel_vbox
= NULL
;
1329 tidy_sympath(filer_window
->sym_path
);
1331 /* Finds the entry for this directory in the dir cache, creating
1332 * a new one if needed. This does not cause a scan to start,
1333 * so if a new entry is created then it will be empty.
1335 filer_window
->directory
= g_fscache_lookup(dir_cache
, real_path
);
1336 if (!filer_window
->directory
)
1338 delayed_error(_("Directory '%s' not found."), path
);
1339 g_free(filer_window
->real_path
);
1340 g_free(filer_window
->sym_path
);
1341 g_free(filer_window
);
1345 filer_window
->temp_item_selected
= FALSE
;
1346 filer_window
->flags
= (FilerFlags
) 0;
1347 filer_window
->details_type
= DETAILS_SUMMARY
;
1348 filer_window
->display_style
= UNKNOWN_STYLE
;
1349 filer_window
->thumb_queue
= NULL
;
1350 filer_window
->max_thumbs
= 0;
1352 if (src_win
&& o_display_inherit_options
.int_value
)
1354 filer_window
->sort_fn
= src_win
->sort_fn
;
1355 dstyle
= src_win
->display_style
;
1356 dtype
= src_win
->details_type
;
1357 filer_window
->show_hidden
= src_win
->show_hidden
;
1358 filer_window
->show_thumbs
= src_win
->show_thumbs
;
1362 int i
= o_display_sort_by
.int_value
;
1363 filer_window
->sort_fn
= i
== 0 ? sort_by_name
:
1364 i
== 1 ? sort_by_type
:
1365 i
== 2 ? sort_by_date
:
1368 dstyle
= o_display_size
.int_value
;
1369 dtype
= o_display_details
.int_value
;
1370 filer_window
->show_hidden
=
1371 o_display_show_hidden
.int_value
;
1372 filer_window
->show_thumbs
=
1373 o_display_show_thumbs
.int_value
;
1376 /* Add all the user-interface elements & realise */
1377 filer_add_widgets(filer_window
);
1379 gtk_window_set_position(GTK_WINDOW(filer_window
->window
),
1382 /* Connect to all the signal handlers */
1383 filer_add_signals(filer_window
);
1385 display_set_layout(filer_window
, dstyle
, dtype
);
1387 /* Open the window after a timeout, or when scanning stops.
1388 * Do this before attaching, because attach() might tell us to
1389 * stop scanning (if a scan isn't needed).
1391 filer_window
->open_timeout
= gtk_timeout_add(500,
1392 (GtkFunction
) open_filer_window
,
1395 /* The collection is created empty and then attach() is called, which
1396 * links the filer window to the entry in the directory cache we
1397 * looked up / created above.
1399 * The attach() function will immediately callback to the filer window
1400 * to deliver a list of all known entries in the directory (so,
1401 * collection->number_of_items may be valid after the call to
1402 * attach() returns).
1404 * BUT, if the directory was not in the cache (because it hadn't been
1405 * opened it before) then the cached dir will be empty and nothing gets
1406 * added until a while later when some entries are actually available.
1409 attach(filer_window
);
1411 number_of_windows
++;
1412 all_filer_windows
= g_list_prepend(all_filer_windows
, filer_window
);
1414 return filer_window
;
1417 /* This adds all the widgets to a new filer window. It is in a separate
1418 * function because filer_opendir() was getting too long...
1420 static void filer_add_widgets(FilerWindow
*filer_window
)
1422 GtkWidget
*hbox
, *vbox
, *collection
;
1424 /* Create the top-level window widget */
1425 filer_window
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1426 filer_set_title(filer_window
);
1428 /* The collection is the area that actually displays the files */
1429 collection
= collection_new();
1431 /* This property is cleared when the window is destroyed.
1432 * You can thus ref filer_window->window and use this to see
1433 * if the window no longer exists.
1435 g_object_set_data(G_OBJECT(filer_window
->window
),
1436 "filer_window", filer_window
);
1438 g_object_set_data(G_OBJECT(collection
), "filer_window", filer_window
);
1439 filer_window
->collection
= COLLECTION(collection
);
1441 filer_window
->collection
->free_item
= display_free_colitem
;
1443 /* Scrollbar on the right, everything else on the left */
1444 hbox
= gtk_hbox_new(FALSE
, 0);
1445 gtk_container_add(GTK_CONTAINER(filer_window
->window
), hbox
);
1447 vbox
= gtk_vbox_new(FALSE
, 0);
1448 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, TRUE
, TRUE
, 0);
1449 filer_window
->toplevel_vbox
= GTK_BOX(vbox
);
1451 /* If we want a toolbar, create it now */
1452 toolbar_update_toolbar(filer_window
);
1454 /* If there's a message that should be displayed in each window (eg
1455 * 'Running as root'), add it here.
1457 if (show_user_message
)
1459 filer_window
->message
= gtk_label_new(show_user_message
);
1460 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->message
,
1462 gtk_widget_show(filer_window
->message
);
1465 /* Now add the area for displaying the files.
1466 * The collection is one huge window that goes in a Viewport.
1469 GtkWidget
*viewport
;
1472 adj
= filer_window
->collection
->vadj
;
1473 viewport
= gtk_viewport_new(NULL
, adj
);
1474 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport
),
1476 gtk_container_add(GTK_CONTAINER(viewport
), collection
);
1477 gtk_widget_show_all(viewport
);
1478 gtk_box_pack_start(GTK_BOX(vbox
), viewport
, TRUE
, TRUE
, 0);
1479 filer_window
->scrollbar
= gtk_vscrollbar_new(adj
);
1480 gtk_widget_set_size_request(viewport
, 4, 4);
1482 gtk_container_set_resize_mode(GTK_CONTAINER(viewport
),
1483 GTK_RESIZE_IMMEDIATE
);
1486 /* And the minibuffer (hidden to start with) */
1487 create_minibuffer(filer_window
);
1488 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->minibuffer_area
,
1491 /* And the thumbnail progress bar (also hidden) */
1495 filer_window
->thumb_bar
= gtk_hbox_new(FALSE
, 2);
1496 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->thumb_bar
,
1499 filer_window
->thumb_progress
= gtk_progress_bar_new();
1501 gtk_box_pack_start(GTK_BOX(filer_window
->thumb_bar
),
1502 filer_window
->thumb_progress
, TRUE
, TRUE
, 0);
1504 cancel
= gtk_button_new_with_label(_("Cancel"));
1505 GTK_WIDGET_UNSET_FLAGS(cancel
, GTK_CAN_FOCUS
);
1506 gtk_box_pack_start(GTK_BOX(filer_window
->thumb_bar
),
1507 cancel
, FALSE
, TRUE
, 0);
1508 g_signal_connect_swapped(cancel
, "clicked",
1509 G_CALLBACK(filer_cancel_thumbnails
),
1513 /* Put the scrollbar on the left of everything else... */
1514 gtk_box_pack_start(GTK_BOX(hbox
),
1515 filer_window
->scrollbar
, FALSE
, TRUE
, 0);
1517 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
), collection
);
1519 gtk_widget_show(hbox
);
1520 gtk_widget_show(vbox
);
1521 gtk_widget_show(filer_window
->scrollbar
);
1522 gtk_widget_show(collection
);
1524 gtk_widget_realize(filer_window
->window
);
1526 gdk_window_set_role(filer_window
->window
->window
,
1527 filer_window
->sym_path
);
1529 filer_window_set_size(filer_window
, 4, 4, TRUE
);
1532 static void filer_add_signals(FilerWindow
*filer_window
)
1534 GtkObject
*collection
= GTK_OBJECT(filer_window
->collection
);
1535 GtkTargetEntry target_table
[] =
1537 {"text/uri-list", 0, TARGET_URI_LIST
},
1538 {"STRING", 0, TARGET_STRING
},
1539 {"COMPOUND_TEXT", 0, TARGET_STRING
},/* XXX: Treats as STRING */
1542 /* Events on the top-level window */
1543 gtk_widget_add_events(filer_window
->window
, GDK_ENTER_NOTIFY
);
1544 g_signal_connect(filer_window
->window
, "enter-notify-event",
1545 G_CALLBACK(pointer_in
), filer_window
);
1546 g_signal_connect(filer_window
->window
, "leave-notify-event",
1547 G_CALLBACK(pointer_out
), filer_window
);
1548 g_signal_connect(filer_window
->window
, "destroy",
1549 G_CALLBACK(filer_window_destroyed
), filer_window
);
1551 /* Events on the collection widget */
1552 gtk_widget_set_events(GTK_WIDGET(collection
),
1553 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
1554 GDK_BUTTON3_MOTION_MASK
| GDK_POINTER_MOTION_MASK
|
1555 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
);
1557 g_signal_connect_swapped(collection
, "style_set",
1558 G_CALLBACK(display_update_views
), filer_window
);
1559 g_signal_connect(collection
, "lose_selection",
1560 G_CALLBACK(lose_selection
), filer_window
);
1561 g_signal_connect(collection
, "selection_changed",
1562 G_CALLBACK(selection_changed
), filer_window
);
1563 g_signal_connect(collection
, "selection_clear_event",
1564 G_CALLBACK(collection_lose_selection
), NULL
);
1565 g_signal_connect(collection
, "selection_get",
1566 G_CALLBACK(selection_get
), NULL
);
1567 gtk_selection_add_targets(GTK_WIDGET(collection
), GDK_SELECTION_PRIMARY
,
1569 sizeof(target_table
) / sizeof(*target_table
));
1571 g_signal_connect(collection
, "key_press_event",
1572 G_CALLBACK(key_press_event
), filer_window
);
1573 g_signal_connect(collection
, "button-release-event",
1574 G_CALLBACK(coll_button_release
), filer_window
);
1575 g_signal_connect(collection
, "button-press-event",
1576 G_CALLBACK(coll_button_press
), filer_window
);
1577 g_signal_connect(collection
, "motion-notify-event",
1578 G_CALLBACK(coll_motion_notify
), filer_window
);
1580 /* Drag and drop events */
1581 g_signal_connect(collection
, "drag_data_get",
1582 GTK_SIGNAL_FUNC(drag_data_get
), NULL
);
1583 drag_set_dest(filer_window
);
1586 static gint
clear_scanning_display(FilerWindow
*filer_window
)
1588 if (filer_exists(filer_window
))
1589 filer_set_title(filer_window
);
1593 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
)
1595 if (scanning
== filer_window
->scanning
)
1597 filer_window
->scanning
= scanning
;
1600 filer_set_title(filer_window
);
1602 gtk_timeout_add(300, (GtkFunction
) clear_scanning_display
,
1606 /* Note that filer_window may not exist after this call */
1607 void filer_update_dir(FilerWindow
*filer_window
, gboolean warning
)
1609 if (may_rescan(filer_window
, warning
))
1610 dir_update(filer_window
->directory
, filer_window
->sym_path
);
1613 void filer_update_all(void)
1615 GList
*next
= all_filer_windows
;
1619 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1621 /* Updating directory may remove it from list -- stop sending
1622 * patches to move this line!
1626 filer_update_dir(filer_window
, TRUE
);
1630 /* Refresh the various caches even if we don't think we need to */
1631 void full_refresh(void)
1636 /* See whether a filer window with a given path already exists
1637 * and is different from diff.
1639 static FilerWindow
*find_filer_window(const char *sym_path
, FilerWindow
*diff
)
1643 for (next
= all_filer_windows
; next
; next
= next
->next
)
1645 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1647 if (filer_window
!= diff
&&
1648 strcmp(sym_path
, filer_window
->sym_path
) == 0)
1649 return filer_window
;
1655 /* This path has been mounted/umounted/deleted some files - update all dirs */
1656 void filer_check_mounted(const char *real_path
)
1658 GList
*next
= all_filer_windows
;
1662 len
= strlen(real_path
);
1666 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1670 if (strncmp(real_path
, filer_window
->real_path
, len
) == 0)
1672 char s
= filer_window
->real_path
[len
];
1674 if (s
== '/' || s
== '\0')
1675 filer_update_dir(filer_window
, FALSE
);
1679 parent
= g_dirname(real_path
);
1680 refresh_dirs(parent
);
1683 icons_may_update(real_path
);
1686 /* Close all windows displaying 'path' or subdirectories of 'path' */
1687 void filer_close_recursive(const char *path
)
1689 GList
*next
= all_filer_windows
;
1693 real
= pathdup(path
);
1698 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1702 if (strncmp(real
, filer_window
->real_path
, len
) == 0)
1704 char s
= filer_window
->real_path
[len
];
1706 if (len
== 1 || s
== '/' || s
== '\0')
1707 gtk_widget_destroy(filer_window
->window
);
1712 /* Like minibuffer_show(), except that:
1713 * - It returns FALSE (to be used from an idle callback)
1714 * - It checks that the filer window still exists.
1716 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
)
1718 if (filer_exists(filer_window
))
1719 minibuffer_show(filer_window
, MINI_PATH
);
1723 /* TRUE iff filer_window points to an existing FilerWindow
1726 gboolean
filer_exists(FilerWindow
*filer_window
)
1730 for (next
= all_filer_windows
; next
; next
= next
->next
)
1732 FilerWindow
*fw
= (FilerWindow
*) next
->data
;
1734 if (fw
== filer_window
)
1741 /* Make sure the window title is up-to-date */
1742 void filer_set_title(FilerWindow
*filer_window
)
1744 guchar
*title
= NULL
;
1747 if (filer_window
->scanning
|| filer_window
->show_hidden
||
1748 filer_window
->show_thumbs
)
1750 if (o_short_flag_names
.int_value
)
1752 flags
= g_strconcat(" +",
1753 filer_window
->scanning
? _("S") : "",
1754 filer_window
->show_hidden
? _("A") : "",
1755 filer_window
->show_thumbs
? _("T") : "",
1760 flags
= g_strconcat(" (",
1761 filer_window
->scanning
? _("Scanning, ") : "",
1762 filer_window
->show_hidden
? _("All, ") : "",
1763 filer_window
->show_thumbs
? _("Thumbs, ") : "",
1765 flags
[strlen(flags
) - 2] = ')';
1770 title
= g_strconcat("//", our_host_name(),
1771 filer_window
->sym_path
, flags
, NULL
);
1773 if (!title
&& home_dir_len
> 1 &&
1774 strncmp(filer_window
->sym_path
, home_dir
, home_dir_len
) == 0)
1776 guchar sep
= filer_window
->sym_path
[home_dir_len
];
1778 if (sep
== '\0' || sep
== '/')
1779 title
= g_strconcat("~",
1780 filer_window
->sym_path
+ home_dir_len
,
1786 title
= g_strconcat(filer_window
->sym_path
, flags
, NULL
);
1788 gtk_window_set_title(GTK_WINDOW(filer_window
->window
), title
);
1791 if (flags
[0] != '\0')
1795 /* Reconnect to the same directory (used when the Show Hidden option is
1796 * toggled). This has the side-effect of updating the window title.
1798 void filer_detach_rescan(FilerWindow
*filer_window
)
1800 Directory
*dir
= filer_window
->directory
;
1803 detach(filer_window
);
1804 filer_window
->directory
= dir
;
1805 attach(filer_window
);
1808 static gint
coll_button_release(GtkWidget
*widget
,
1809 GdkEventButton
*event
,
1810 FilerWindow
*filer_window
)
1812 if (dnd_motion_release(event
))
1814 if (motion_buttons_pressed
== 0 &&
1815 filer_window
->collection
->lasso_box
)
1817 collection_end_lasso(filer_window
->collection
,
1818 event
->button
== 1 ? GDK_SET
: GDK_INVERT
);
1823 perform_action(filer_window
, event
);
1828 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
)
1830 Collection
*collection
= filer_window
->collection
;
1834 gboolean press
= event
->type
== GDK_BUTTON_PRESS
;
1835 gboolean selected
= FALSE
;
1836 OpenFlags flags
= 0;
1838 if (event
->button
> 3)
1841 item
= collection_get_item(collection
, event
->x
, event
->y
);
1843 if (item
!= -1 && event
->button
== 1 &&
1844 collection
->items
[item
].selected
&&
1845 filer_window
->selection_state
== GTK_STATE_INSENSITIVE
)
1847 selection_changed(collection
, event
->time
, filer_window
);
1851 if (filer_window
->target_cb
)
1853 dnd_motion_ungrab();
1854 if (item
!= -1 && press
&& event
->button
== 1)
1855 filer_window
->target_cb(filer_window
, item
,
1856 filer_window
->target_data
);
1857 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1862 action
= bind_lookup_bev(
1863 item
== -1 ? BIND_DIRECTORY
: BIND_DIRECTORY_ICON
,
1868 dir_item
= (DirItem
*) collection
->items
[item
].data
;
1869 selected
= collection
->items
[item
].selected
;
1876 case ACT_CLEAR_SELECTION
:
1877 collection_clear_selection(collection
);
1879 case ACT_TOGGLE_SELECTED
:
1880 collection_toggle_item(collection
, item
);
1882 case ACT_SELECT_EXCL
:
1883 collection_clear_except(collection
, item
);
1886 flags
|= OPEN_SHIFT
;
1889 if (event
->button
!= 1)
1890 flags
|= OPEN_CLOSE_WINDOW
;
1892 flags
|= OPEN_SAME_WINDOW
;
1893 if (o_new_button_1
.int_value
)
1894 flags
^= OPEN_SAME_WINDOW
;
1895 if (event
->type
== GDK_2BUTTON_PRESS
)
1896 collection_unselect_item(collection
, item
);
1897 dnd_motion_ungrab();
1898 filer_openitem(filer_window
, item
, flags
);
1900 case ACT_POPUP_MENU
:
1901 dnd_motion_ungrab();
1902 filer_tooltip_prime(NULL
, NULL
);
1903 show_filer_menu(filer_window
, (GdkEvent
*) event
, item
);
1905 case ACT_PRIME_AND_SELECT
:
1907 collection_clear_except(collection
, item
);
1908 dnd_motion_start(MOTION_READY_FOR_DND
);
1910 case ACT_PRIME_AND_TOGGLE
:
1911 collection_toggle_item(collection
, item
);
1912 dnd_motion_start(MOTION_READY_FOR_DND
);
1914 case ACT_PRIME_FOR_DND
:
1915 dnd_motion_start(MOTION_READY_FOR_DND
);
1918 if (press
&& event
->button
< 4)
1921 collection_wink_item(collection
, item
);
1922 dnd_motion_start(MOTION_NONE
);
1925 case ACT_LASSO_CLEAR
:
1926 collection_clear_selection(collection
);
1928 case ACT_LASSO_MODIFY
:
1929 collection_lasso_box(collection
, event
->x
, event
->y
);
1932 filer_window_autosize(filer_window
, TRUE
);
1935 g_warning("Unsupported action : %d\n", action
);
1940 static gint
coll_button_press(GtkWidget
*widget
,
1941 GdkEventButton
*event
,
1942 FilerWindow
*filer_window
)
1944 collection_set_cursor_item(filer_window
->collection
, -1);
1946 if (dnd_motion_press(widget
, event
))
1947 perform_action(filer_window
, event
);
1952 static gint
coll_motion_notify(GtkWidget
*widget
,
1953 GdkEventMotion
*event
,
1954 FilerWindow
*filer_window
)
1956 Collection
*collection
= filer_window
->collection
;
1959 i
= collection_get_item(collection
, event
->x
, event
->y
);
1962 filer_tooltip_prime(NULL
, NULL
);
1964 filer_tooltip_prime(filer_window
,
1965 (DirItem
*) collection
->items
[i
].data
);
1967 if (motion_state
!= MOTION_READY_FOR_DND
)
1970 if (!dnd_motion_moved(event
))
1973 i
= collection_get_item(collection
,
1974 event
->x
- (event
->x_root
- drag_start_x
),
1975 event
->y
- (event
->y_root
- drag_start_y
));
1979 collection_wink_item(collection
, -1);
1981 if (!collection
->items
[i
].selected
)
1983 if (event
->state
& GDK_BUTTON1_MASK
)
1985 /* Select just this one */
1986 filer_window
->temp_item_selected
= TRUE
;
1987 collection_clear_except(collection
, i
);
1991 if (collection
->number_selected
== 0)
1992 filer_window
->temp_item_selected
= TRUE
;
1993 collection_select_item(collection
, i
);
1997 g_return_val_if_fail(collection
->number_selected
> 0, TRUE
);
1999 if (collection
->number_selected
== 1)
2001 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
2002 ViewData
*view
= (ViewData
*) collection
->items
[i
].view_data
;
2005 item
= dir_update_item(filer_window
->directory
,
2010 report_error(_("Item no longer exists!"));
2014 drag_one_item(widget
, event
,
2015 make_path(filer_window
->sym_path
, item
->leafname
)->str
,
2016 item
, view
? view
->image
: NULL
);
2022 uris
= g_string_new(NULL
);
2023 create_uri_list(filer_window
, uris
);
2024 drag_selection(widget
, event
, uris
->str
);
2025 g_string_free(uris
, TRUE
);
2031 /* Puts the filer window into target mode. When an item is chosen,
2032 * fn(filer_window, item, data) is called. 'reason' will be displayed
2033 * on the toolbar while target mode is active.
2035 * Use fn == NULL to cancel target mode.
2037 void filer_target_mode(FilerWindow
*filer_window
,
2042 TargetFunc old_fn
= filer_window
->target_cb
;
2045 gdk_window_set_cursor(
2046 GTK_WIDGET(filer_window
->collection
)->window
,
2047 fn
? crosshair
: NULL
);
2049 filer_window
->target_cb
= fn
;
2050 filer_window
->target_data
= data
;
2052 if (filer_window
->toolbar_text
== NULL
)
2057 GTK_LABEL(filer_window
->toolbar_text
), reason
);
2058 else if (o_toolbar_info
.int_value
)
2061 toolbar_update_info(filer_window
);
2064 gtk_label_set_text(GTK_LABEL(filer_window
->toolbar_text
), "");
2067 /* Draw the black border */
2068 static gint
filer_tooltip_draw(GtkWidget
*w
)
2070 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
2071 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
2076 /* When the tips window closed, record the time. If we try to open another
2077 * tip soon, it will appear more quickly.
2079 static void tip_destroyed(gpointer data
)
2084 /* It's time to make the tooltip appear. If we're not over the item any
2085 * more, or the item doesn't need a tooltip, do nothing.
2087 static gboolean
filer_tooltip_activate(FilerWindow
*filer_window
)
2089 Collection
*collection
;
2092 GString
*tip
= NULL
;
2093 guchar
*fullpath
= NULL
;
2095 g_return_val_if_fail(tip_item
!= NULL
, 0);
2101 if (!filer_exists(filer_window
))
2104 collection
= filer_window
->collection
;
2105 gdk_window_get_pointer(GTK_WIDGET(collection
)->window
, &x
, &y
, NULL
);
2106 i
= collection_get_item(collection
, x
, y
);
2107 if (i
== -1 || ((DirItem
*) collection
->items
[i
].data
) != tip_item
)
2108 return FALSE
; /* Not still under the pointer */
2110 /* OK, the filer window still exists and the pointer is still
2111 * over the same item. Do we need to show a tip?
2114 tip
= g_string_new(NULL
);
2116 if (display_is_truncated(filer_window
, i
))
2118 g_string_append(tip
, tip_item
->leafname
);
2119 g_string_append_c(tip
, '\n');
2122 fullpath
= make_path(filer_window
->real_path
, tip_item
->leafname
)->str
;
2124 if (tip_item
->flags
& ITEM_FLAG_SYMLINK
)
2128 target
= readlink_dup(fullpath
);
2131 g_string_append(tip
, _("Symbolic link to "));
2132 g_string_append(tip
, target
);
2133 g_string_append_c(tip
, '\n');
2138 if (tip_item
->flags
& ITEM_FLAG_APPDIR
)
2143 info
= appinfo_get(fullpath
, tip_item
);
2144 if (info
&& ((node
= xml_get_section(info
, NULL
, "Summary"))))
2147 str
= xmlNodeListGetString(node
->doc
,
2148 node
->xmlChildrenNode
, 1);
2151 g_string_append(tip
, str
);
2152 g_string_append_c(tip
, '\n');
2157 g_object_unref(info
);
2160 if (!g_utf8_validate(tip_item
->leafname
, -1, NULL
))
2161 g_string_append(tip
,
2162 _("This filename is not valid UTF-8. "
2163 "You should rename it.\n"));
2167 g_string_truncate(tip
, tip
->len
- 1);
2168 show_tooltip(tip
->str
);
2171 g_string_free(tip
, TRUE
);
2176 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
2177 * NULL, close any current tooltip.
2179 static void show_tooltip(guchar
*text
)
2187 gtk_widget_destroy(tip_widget
);
2195 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
2196 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
2197 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
2199 g_signal_connect_swapped(tip_widget
, "expose_event",
2200 G_CALLBACK(filer_tooltip_draw
), tip_widget
);
2202 label
= gtk_label_new(text
);
2203 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
2204 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
2205 gtk_widget_show(label
);
2206 gtk_widget_realize(tip_widget
);
2208 w
= tip_widget
->allocation
.width
;
2209 h
= tip_widget
->allocation
.height
;
2210 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
2213 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
2215 /* Now check for screen boundaries */
2216 x
= CLAMP(x
, 0, screen_width
- w
);
2217 y
= CLAMP(y
, 0, screen_height
- h
);
2219 /* And again test if pointer is over the tooltip window */
2220 if (py
>= y
&& py
<= y
+ h
)
2222 gtk_window_move(GTK_WINDOW(tip_widget
), x
, y
);
2223 gtk_widget_show(tip_widget
);
2225 g_signal_connect_swapped(tip_widget
, "destroy",
2226 G_CALLBACK(tip_destroyed
), NULL
);
2230 /* Display a tooltip for 'item' after a while (if item is not NULL).
2231 * Cancel any previous tooltip.
2233 static void filer_tooltip_prime(FilerWindow
*filer_window
, DirItem
*item
)
2239 if (item
== tip_item
)
2244 gtk_timeout_remove(tip_timeout
);
2250 gtk_widget_destroy(tip_widget
);
2255 if (filer_window
&& item
)
2257 int delay
= now
- tip_time
> 2 ? 1000 : 200;
2259 tip_timeout
= gtk_timeout_add(delay
,
2260 (GtkFunction
) filer_tooltip_activate
,
2265 static void set_selection_state(FilerWindow
*filer_window
, gboolean normal
)
2267 GtkStateType old_state
= filer_window
->selection_state
;
2269 filer_window
->selection_state
= normal
2270 ? GTK_STATE_SELECTED
: GTK_STATE_INSENSITIVE
;
2272 if (old_state
!= filer_window
->selection_state
2273 && filer_window
->collection
->number_selected
)
2274 gtk_widget_queue_draw(GTK_WIDGET(filer_window
->collection
));
2277 void filer_cancel_thumbnails(FilerWindow
*filer_window
)
2279 gtk_widget_hide(filer_window
->thumb_bar
);
2281 g_list_foreach(filer_window
->thumb_queue
, (GFunc
) g_free
, NULL
);
2282 g_list_free(filer_window
->thumb_queue
);
2283 filer_window
->thumb_queue
= NULL
;
2284 filer_window
->max_thumbs
= 0;
2287 /* Generate the next thumb for this window. The collection object is
2288 * unref'd when there is nothing more to do.
2289 * If the collection no longer has a filer window, nothing is done.
2291 static gboolean
filer_next_thumb_real(GObject
*window
)
2293 FilerWindow
*filer_window
;
2297 filer_window
= g_object_get_data(window
, "filer_window");
2301 g_object_unref(window
);
2305 if (!filer_window
->thumb_queue
)
2307 filer_cancel_thumbnails(filer_window
);
2308 g_object_unref(window
);
2312 total
= filer_window
->max_thumbs
;
2313 done
= total
- g_list_length(filer_window
->thumb_queue
);
2315 path
= (gchar
*) filer_window
->thumb_queue
->data
;
2317 pixmap_background_thumb(path
, (GFunc
) filer_next_thumb
, window
);
2319 filer_window
->thumb_queue
= g_list_remove(filer_window
->thumb_queue
,
2323 gtk_progress_bar_set_fraction(
2324 GTK_PROGRESS_BAR(filer_window
->thumb_progress
),
2325 done
/ (float) total
);
2330 /* path is the thumb just loaded, if any.
2331 * collection is unref'd (eventually).
2333 static void filer_next_thumb(GObject
*window
, const gchar
*path
)
2336 dir_force_update_path(path
);
2338 gtk_idle_add((GtkFunction
) filer_next_thumb_real
, window
);
2341 static void start_thumb_scanning(FilerWindow
*filer_window
)
2343 if (GTK_WIDGET_VISIBLE(filer_window
->thumb_bar
))
2344 return; /* Already scanning */
2346 gtk_widget_show_all(filer_window
->thumb_bar
);
2348 g_object_ref(G_OBJECT(filer_window
->window
));
2349 filer_next_thumb(G_OBJECT(filer_window
->window
), NULL
);
2352 /* Set this image to be loaded some time in the future */
2353 void filer_create_thumb(FilerWindow
*filer_window
, const gchar
*path
)
2355 filer_window
->max_thumbs
++;
2357 filer_window
->thumb_queue
= g_list_append(filer_window
->thumb_queue
,
2360 if (filer_window
->scanning
)
2361 return; /* Will start when scan ends */
2363 start_thumb_scanning(filer_window
);
2366 /* If thumbnail display is on, look through all the items in this directory
2367 * and start creating or updating the thumbnails as needed.
2369 void filer_create_thumbs(FilerWindow
*filer_window
)
2371 Collection
*collection
= filer_window
->collection
;
2374 if (!filer_window
->show_thumbs
)
2377 for (i
= 0; i
< collection
->number_of_items
; i
++)
2379 MaskedPixmap
*pixmap
;
2380 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
2384 if (item
->base_type
!= TYPE_FILE
)
2387 if (strcmp(item
->mime_type
->media_type
, "image") != 0)
2390 path
= make_path(filer_window
->real_path
, item
->leafname
)->str
;
2392 pixmap
= g_fscache_lookup_full(pixmap_cache
, path
,
2393 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
2395 g_object_unref(pixmap
);
2397 /* If we didn't get an image, it could be because:
2399 * - We're loading the image now. found is TRUE,
2400 * and we'll update the item later.
2401 * - We tried to load the image and failed. found
2403 * - We haven't tried loading the image. found is
2404 * FALSE, and we start creating the thumb here.
2407 filer_create_thumb(filer_window
, path
);
2411 static void filer_options_changed(void)
2413 if (o_short_flag_names
.has_changed
)
2417 for (next
= all_filer_windows
; next
; next
= next
->next
)
2419 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
2421 filer_set_title(filer_window
);