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(char *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
)
187 char *leafname
= item
->leafname
;
188 int old_w
= filer_window
->collection
->item_width
;
189 int old_h
= filer_window
->collection
->item_height
;
191 CollectionItem
*colitem
;
193 if (leafname
[0] == '.' && filer_window
->show_hidden
== FALSE
)
196 i
= collection_find_item(filer_window
->collection
, item
,
197 filer_window
->sort_fn
);
201 g_warning("Failed to find '%s'\n", item
->leafname
);
205 colitem
= &filer_window
->collection
->items
[i
];
207 display_update_view(filer_window
,
208 (DirItem
*) colitem
->data
,
209 (ViewData
*) colitem
->view_data
,
212 calc_size(filer_window
, colitem
, &w
, &h
);
213 if (w
> old_w
|| h
> old_h
)
214 collection_set_item_size(filer_window
->collection
,
218 collection_draw_item(filer_window
->collection
, i
, TRUE
);
221 /* Resize the filer window to w x h pixels, plus border (not clamped) */
222 static void filer_window_set_size(FilerWindow
*filer_window
,
224 gboolean allow_shrink
)
226 g_return_if_fail(filer_window
!= NULL
);
228 if (filer_window
->scrollbar
)
229 w
+= filer_window
->scrollbar
->allocation
.width
;
231 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
232 h
+= filer_window
->toolbar_frame
->allocation
.height
;
233 if (filer_window
->message
)
234 h
+= filer_window
->message
->allocation
.height
;
236 if (GTK_WIDGET_VISIBLE(filer_window
->window
))
239 GtkRequisition
*req
= &filer_window
->window
->requisition
;
241 w
= MAX(req
->width
, w
);
242 h
= MAX(req
->height
, h
);
243 gdk_window_get_position(filer_window
->window
->window
,
249 gdk_drawable_get_size(filer_window
->window
->window
,
254 if (w
== old_w
&& h
== old_h
)
258 if (x
+ w
> screen_width
|| y
+ h
> screen_height
)
260 if (x
+ w
> screen_width
)
261 x
= screen_width
- w
- 4;
262 if (y
+ h
> screen_height
)
263 y
= screen_height
- h
- 4;
264 gdk_window_move_resize(filer_window
->window
->window
,
268 gdk_window_resize(filer_window
->window
->window
, w
, h
);
271 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
275 /* Resize the window to fit the items currently in the Directory.
276 * This should be used once the Directory has been fully scanned, otherwise
277 * the window will appear too small. When opening a directory for the first
278 * time, the names will be known but not the types and images. We can
279 * still make a good estimate of the size.
281 void filer_window_autosize(FilerWindow
*filer_window
, gboolean allow_shrink
)
283 Collection
*collection
= filer_window
->collection
;
286 n
= collection
->number_of_items
;
289 filer_size_for(filer_window
,
290 collection
->item_width
,
291 collection
->item_height
,
295 /* Choose a good size for this window, assuming n items of size (w, h) */
296 static void filer_size_for(FilerWindow
*filer_window
,
297 int w
, int h
, int n
, gboolean allow_shrink
)
307 size_limit
= o_filer_size_limit
.int_value
;
309 /* Get the extra height required for the toolbar and minibuffer,
312 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
313 t
= filer_window
->toolbar_frame
->allocation
.height
;
314 if (filer_window
->message
)
315 t
+= filer_window
->message
->allocation
.height
;
316 if (GTK_WIDGET_VISIBLE(filer_window
->minibuffer_area
))
320 gtk_widget_size_request(filer_window
->minibuffer_area
, &req
);
321 space
= req
.height
+ 2;
325 max_x
= (size_limit
* screen_width
) / 100;
326 max_rows
= (size_limit
* screen_height
) / (h
* 100);
328 /* Aim for a size where
329 * x = r(y + t + h), (1)
330 * unless that's too wide.
332 * t = toolbar (and minibuffer) height
333 * r = desired (width / height) ratio
335 * Want to display all items:
338 * => x(x/r - t - h) = nwh (from 1)
339 * => xx - x.rt - hr(1 - nw) = 0
340 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
344 * sqrt(rt.rt + ...) > rt
346 * So, the +/- must be +:
348 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
350 * ( + w - 1 to round up)
352 x
= (r
* t
+ sqrt(r
*r
*t
*t
+ 4*h
*r
* (n
*w
- 1))) / 2 + w
- 1;
361 /* Choose rows to display all items given our chosen x.
362 * Don't make the window *too* big!
364 rows
= (n
+ cols
- 1) / cols
;
368 /* Leave some room for extra icons, but only in Small Icons mode
369 * otherwise it takes up too much space.
370 * Also, don't add space if the minibuffer is open.
373 space
= filer_window
->display_style
== SMALL_ICONS
? h
: 2;
375 filer_window_set_size(filer_window
,
377 h
* MAX(rows
, 1) + space
,
381 /* Called on a timeout while scanning or when scanning ends
382 * (whichever happens first).
384 static gint
open_filer_window(FilerWindow
*filer_window
)
386 shrink_grid(filer_window
);
388 if (filer_window
->open_timeout
)
390 gtk_timeout_remove(filer_window
->open_timeout
);
391 filer_window
->open_timeout
= 0;
394 if (!GTK_WIDGET_VISIBLE(filer_window
->window
))
396 filer_window_autosize(filer_window
, TRUE
);
397 gtk_widget_show(filer_window
->window
);
403 static void update_display(Directory
*dir
,
406 FilerWindow
*filer_window
)
410 Collection
*collection
= filer_window
->collection
;
415 old_num
= collection
->number_of_items
;
416 for (i
= 0; i
< items
->len
; i
++)
418 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
420 add_item(filer_window
, item
);
423 if (old_num
!= collection
->number_of_items
)
424 collection_qsort(filer_window
->collection
,
425 filer_window
->sort_fn
);
427 /* Open and resize if currently hidden */
428 open_filer_window(filer_window
);
431 collection_delete_if(filer_window
->collection
,
436 set_scanning_display(filer_window
, TRUE
);
437 toolbar_update_info(filer_window
);
440 if (filer_window
->window
->window
)
441 gdk_window_set_cursor(
442 filer_window
->window
->window
,
444 set_scanning_display(filer_window
, FALSE
);
445 toolbar_update_info(filer_window
);
446 open_filer_window(filer_window
);
448 if (filer_window
->had_cursor
&&
449 collection
->cursor_item
== -1)
451 collection_set_cursor_item(collection
, 0);
452 filer_window
->had_cursor
= FALSE
;
454 if (filer_window
->auto_select
)
455 display_set_autoselect(filer_window
,
456 filer_window
->auto_select
);
457 g_free(filer_window
->auto_select
);
458 filer_window
->auto_select
= NULL
;
460 filer_create_thumbs(filer_window
);
462 if (filer_window
->thumb_queue
)
463 start_thumb_scanning(filer_window
);
466 collection_qsort(filer_window
->collection
,
467 filer_window
->sort_fn
);
468 for (i
= 0; i
< items
->len
; i
++)
470 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
472 update_item(filer_window
, item
);
478 static void attach(FilerWindow
*filer_window
)
480 gdk_window_set_cursor(filer_window
->window
->window
, busy_cursor
);
481 collection_clear(filer_window
->collection
);
482 filer_window
->scanning
= TRUE
;
483 dir_attach(filer_window
->directory
, (DirCallback
) update_display
,
485 filer_set_title(filer_window
);
488 static void detach(FilerWindow
*filer_window
)
490 g_return_if_fail(filer_window
->directory
!= NULL
);
492 dir_detach(filer_window
->directory
,
493 (DirCallback
) update_display
, filer_window
);
494 g_object_unref(filer_window
->directory
);
495 filer_window
->directory
= NULL
;
498 static void filer_window_destroyed(GtkWidget
*widget
,
499 FilerWindow
*filer_window
)
501 all_filer_windows
= g_list_remove(all_filer_windows
, filer_window
);
503 g_object_set_data(G_OBJECT(widget
), "filer_window", NULL
);
505 if (window_with_primary
== filer_window
)
506 window_with_primary
= NULL
;
508 if (window_with_focus
== filer_window
)
511 window_with_focus
= NULL
;
514 if (filer_window
->directory
)
515 detach(filer_window
);
517 if (filer_window
->open_timeout
)
519 gtk_timeout_remove(filer_window
->open_timeout
);
520 filer_window
->open_timeout
= 0;
523 if (filer_window
->thumb_queue
)
525 g_list_foreach(filer_window
->thumb_queue
, (GFunc
) g_free
, NULL
);
526 g_list_free(filer_window
->thumb_queue
);
529 filer_tooltip_prime(NULL
, NULL
);
531 g_free(filer_window
->auto_select
);
532 g_free(filer_window
->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 int old_w
= filer_window
->collection
->item_width
;
544 int old_h
= filer_window
->collection
->item_height
;
548 if (leafname
[0] == '.')
550 if (filer_window
->show_hidden
== FALSE
|| leafname
[1] == '\0'
551 || (leafname
[1] == '.' && leafname
[2] == '\0'))
555 i
= collection_insert(filer_window
->collection
,
557 display_create_viewdata(filer_window
, item
));
559 calc_size(filer_window
, &filer_window
->collection
->items
[i
], &w
, &h
);
561 if (w
> old_w
|| h
> old_h
)
562 collection_set_item_size(filer_window
->collection
,
567 /* Returns TRUE iff the directory still exists. */
568 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
)
572 g_return_val_if_fail(filer_window
!= NULL
, FALSE
);
574 /* We do a fresh lookup (rather than update) because the inode may
577 dir
= g_fscache_lookup(dir_cache
, filer_window
->path
);
581 info_message(_("Directory missing/deleted"));
582 gtk_widget_destroy(filer_window
->window
);
585 if (dir
== filer_window
->directory
)
589 detach(filer_window
);
590 filer_window
->directory
= dir
;
591 attach(filer_window
);
597 /* The collection widget has lost the primary selection */
598 static gint
collection_lose_selection(GtkWidget
*widget
,
599 GdkEventSelection
*event
)
601 if (window_with_primary
&&
602 window_with_primary
->collection
== COLLECTION(widget
))
604 FilerWindow
*filer_window
= window_with_primary
;
605 window_with_primary
= NULL
;
606 set_selection_state(filer_window
, FALSE
);
612 /* Someone wants us to send them the selection */
613 static void selection_get(GtkWidget
*widget
,
614 GtkSelectionData
*selection_data
,
619 GString
*reply
, *header
;
620 FilerWindow
*filer_window
;
622 Collection
*collection
;
624 filer_window
= g_object_get_data(G_OBJECT(widget
), "filer_window");
626 reply
= g_string_new(NULL
);
627 header
= g_string_new(NULL
);
632 g_string_sprintf(header
, " %s",
633 make_path(filer_window
->path
, "")->str
);
635 case TARGET_URI_LIST
:
636 g_string_sprintf(header
, " file://%s%s",
637 our_host_name_for_dnd(),
638 make_path(filer_window
->path
, "")->str
);
642 collection
= filer_window
->collection
;
643 for (i
= 0; i
< collection
->number_of_items
; i
++)
645 if (collection
->items
[i
].selected
)
648 (DirItem
*) collection
->items
[i
].data
;
650 g_string_append(reply
, header
->str
);
651 g_string_append(reply
, item
->leafname
);
654 /* This works, but I don't think I like it... */
655 /* g_string_append_c(reply, ' '); */
658 gtk_selection_data_set(selection_data
, xa_string
,
659 8, reply
->str
+ 1, reply
->len
- 1);
662 g_warning("Attempt to paste empty selection!");
663 gtk_selection_data_set(selection_data
, xa_string
, 8, "", 0);
666 g_string_free(reply
, TRUE
);
667 g_string_free(header
, TRUE
);
670 /* No items are now selected. This might be because another app claimed
671 * the selection or because the user unselected all the items.
673 static void lose_selection(Collection
*collection
,
677 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
679 if (window_with_primary
== filer_window
)
681 window_with_primary
= NULL
;
682 gtk_selection_owner_set(NULL
,
683 GDK_SELECTION_PRIMARY
,
688 static void selection_changed(Collection
*collection
,
692 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
694 /* Selection has been changed -- try to grab the primary selection
695 * if we don't have it.
697 if (window_with_primary
== filer_window
)
698 return; /* Already got it */
700 if (!collection
->number_selected
)
701 return; /* Nothing selected */
703 if (filer_window
->temp_item_selected
== FALSE
&&
704 gtk_selection_owner_set(GTK_WIDGET(collection
),
705 GDK_SELECTION_PRIMARY
,
708 window_with_primary
= filer_window
;
709 set_selection_state(filer_window
, TRUE
);
712 set_selection_state(filer_window
, FALSE
);
715 /* Open the item (or add it to the shell command minibuffer) */
716 void filer_openitem(FilerWindow
*filer_window
, int item_number
, OpenFlags flags
)
718 gboolean shift
= (flags
& OPEN_SHIFT
) != 0;
719 gboolean close_mini
= flags
& OPEN_FROM_MINI
;
720 gboolean close_window
= (flags
& OPEN_CLOSE_WINDOW
) != 0;
722 DirItem
*item
= (DirItem
*)
723 filer_window
->collection
->items
[item_number
].data
;
725 gboolean wink
= TRUE
;
728 widget
= filer_window
->window
;
729 if (filer_window
->mini_type
== MINI_SHELL
)
731 minibuffer_add(filer_window
, item
->leafname
);
736 dir_update_item(filer_window
->directory
, item
->leafname
);
738 if (item
->base_type
== TYPE_DIRECTORY
)
740 /* Never close a filer window when opening a directory
741 * (click on a dir or click on an app with shift).
743 if (shift
|| !(item
->flags
& ITEM_FLAG_APPDIR
))
744 close_window
= FALSE
;
747 full_path
= make_path(filer_window
->path
, item
->leafname
)->str
;
748 if (shift
&& (item
->flags
& ITEM_FLAG_SYMLINK
))
751 old_dir
= filer_window
->directory
;
752 if (run_diritem(full_path
, item
,
753 flags
& OPEN_SAME_WINDOW
? filer_window
: NULL
,
757 if (old_dir
!= filer_window
->directory
)
761 gtk_widget_destroy(filer_window
->window
);
765 collection_wink_item(filer_window
->collection
,
768 minibuffer_hide(filer_window
);
773 static gint
pointer_in(GtkWidget
*widget
,
774 GdkEventCrossing
*event
,
775 FilerWindow
*filer_window
)
777 may_rescan(filer_window
, TRUE
);
781 static gint
pointer_out(GtkWidget
*widget
,
782 GdkEventCrossing
*event
,
783 FilerWindow
*filer_window
)
785 filer_tooltip_prime(NULL
, NULL
);
789 /* Move the cursor to the next selected item in direction 'dir'
792 static void next_selected(FilerWindow
*filer_window
, int dir
)
794 Collection
*collection
= filer_window
->collection
;
795 int to_check
= collection
->number_of_items
;
796 int item
= collection
->cursor_item
;
798 g_return_if_fail(dir
== 1 || dir
== -1);
800 if (to_check
> 0 && item
== -1)
802 /* Cursor not currently on */
806 item
= collection
->number_of_items
- 1;
808 if (collection
->items
[item
].selected
)
812 while (--to_check
> 0)
816 if (item
>= collection
->number_of_items
)
819 item
= collection
->number_of_items
- 1;
821 if (collection
->items
[item
].selected
)
828 collection_set_cursor_item(collection
, item
);
831 static void return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
833 Collection
*collection
= filer_window
->collection
;
834 int item
= collection
->cursor_item
;
835 TargetFunc cb
= filer_window
->target_cb
;
836 gpointer data
= filer_window
->target_data
;
837 OpenFlags flags
= OPEN_SAME_WINDOW
;
839 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
840 if (item
< 0 || item
>= collection
->number_of_items
)
845 cb(filer_window
, item
, data
);
849 if (event
->state
& GDK_SHIFT_MASK
)
852 filer_openitem(filer_window
, item
, flags
);
855 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
856 * changed. If no groups were loaded and there is no file then initialised
857 * groups to an empty document.
858 * Return the node for the 'name' group.
860 static xmlNode
*group_find(char *name
)
865 /* Update the groups, if possible */
866 path
= choices_find_path_load("Groups.xml", PROJECT
);
870 wrapper
= xml_cache_load(path
);
874 g_object_unref(groups
);
883 groups
= xml_new(NULL
);
884 groups
->doc
= xmlNewDoc("1.0");
886 xmlDocSetRootElement(groups
->doc
,
887 xmlNewDocNode(groups
->doc
, NULL
, "groups", NULL
));
891 node
= xmlDocGetRootElement(groups
->doc
);
893 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
897 gid
= xmlGetProp(node
, "name");
902 if (strcmp(name
, gid
) != 0)
913 static void group_save(FilerWindow
*filer_window
, char *name
)
915 Collection
*collection
= filer_window
->collection
;
920 group
= group_find(name
);
923 xmlUnlinkNode(group
);
926 group
= xmlNewChild(xmlDocGetRootElement(groups
->doc
),
927 NULL
, "group", NULL
);
928 xmlSetProp(group
, "name", name
);
930 xmlNewChild(group
, NULL
, "directory", filer_window
->path
);
932 for (i
= 0; i
< collection
->number_of_items
; i
++)
934 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
935 gchar
*u8_leaf
= item
->leafname
;
937 if (!collection
->items
[i
].selected
)
940 xmlNewChild(group
, NULL
, "item", u8_leaf
);
943 save_path
= choices_find_path_save("Groups.xml", PROJECT
, TRUE
);
946 save_xml_file(groups
->doc
, save_path
);
951 static void group_restore(FilerWindow
*filer_window
, char *name
)
953 GHashTable
*in_group
;
954 Collection
*collection
= filer_window
->collection
;
957 xmlNode
*group
, *node
;
959 group
= group_find(name
);
963 report_error(_("Group %s is not set. Select some files "
964 "and press Ctrl+%s to set the group. Press %s "
965 "on its own to reselect the files later."),
970 node
= get_subnode(group
, NULL
, "directory");
971 g_return_if_fail(node
!= NULL
);
972 path
= xmlNodeListGetString(groups
->doc
, node
->xmlChildrenNode
, 1);
973 g_return_if_fail(path
!= NULL
);
975 if (strcmp(path
, filer_window
->path
) != 0)
976 filer_change_to(filer_window
, path
, NULL
);
979 /* If an item at the start is selected then we could lose the
980 * primary selection after checking that item and then need to
981 * gain it again at the end. Therefore, if anything is selected
982 * then select the last item until the end of the search.
984 n
= collection
->number_of_items
;
985 if (collection
->number_selected
)
986 collection_select_item(collection
, n
- 1);
988 in_group
= g_hash_table_new(g_str_hash
, g_str_equal
);
989 for (node
= group
->xmlChildrenNode
; node
; node
= node
->next
)
992 if (node
->type
!= XML_ELEMENT_NODE
)
994 if (strcmp(node
->name
, "item") != 0)
997 leaf
= xmlNodeListGetString(groups
->doc
,
998 node
->xmlChildrenNode
, 1);
1000 g_warning("Missing leafname!\n");
1002 g_hash_table_insert(in_group
, leaf
, filer_window
);
1005 for (j
= 0; j
< n
; j
++)
1007 DirItem
*item
= (DirItem
*) collection
->items
[j
].data
;
1009 if (g_hash_table_lookup(in_group
, item
->leafname
))
1010 collection_select_item(collection
, j
);
1012 collection_unselect_item(collection
, j
);
1015 g_hash_table_foreach(in_group
, (GHFunc
) g_free
, NULL
);
1016 g_hash_table_destroy(in_group
);
1019 /* Handle keys that can't be bound with the menu */
1020 static gint
key_press_event(GtkWidget
*widget
,
1022 FilerWindow
*filer_window
)
1025 guint key
= event
->keyval
;
1026 char group
[2] = "1";
1028 window_with_focus
= filer_window
;
1030 /* Note: Not convinced this is the way Gtk's key system is supposed
1034 ensure_filer_menu(); /* Gets the keys working... */
1035 gtk_window_add_accel_group(GTK_WINDOW(filer_window
->window
),
1037 handled
= gtk_accel_groups_activate(G_OBJECT(filer_window
->window
),
1038 event
->keyval
, event
->state
);
1039 if (window_with_focus
)
1040 gtk_window_remove_accel_group(GTK_WINDOW(filer_window
->window
),
1043 return TRUE
; /* Window no longer exists */
1050 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1053 return_pressed(filer_window
, event
);
1055 case GDK_ISO_Left_Tab
:
1056 next_selected(filer_window
, -1);
1059 next_selected(filer_window
, 1);
1062 change_to_parent(filer_window
);
1065 filer_tooltip_prime(NULL
, NULL
);
1066 show_filer_menu(filer_window
, (GdkEvent
*) event
,
1067 filer_window
->collection
->cursor_item
);
1070 if (key
>= GDK_0
&& key
<= GDK_9
)
1071 group
[0] = key
- GDK_0
+ '0';
1072 else if (key
>= GDK_KP_0
&& key
<= GDK_KP_9
)
1073 group
[0] = key
- GDK_KP_0
+ '0';
1078 if (event
->state
& GDK_CONTROL_MASK
)
1079 group_save(filer_window
, group
);
1081 group_restore(filer_window
, group
);
1087 void change_to_parent(FilerWindow
*filer_window
)
1092 if (filer_window
->path
[0] == '/' && filer_window
->path
[1] == '\0')
1093 return; /* Already in the root */
1095 copy
= g_strdup(filer_window
->path
);
1096 slash
= strrchr(copy
, '/');
1101 filer_change_to(filer_window
,
1106 g_warning("No / in directory path!\n");
1112 /* Make filer_window display path. When finished, highlight item 'from', or
1113 * the first item if from is NULL. If there is currently no cursor then
1114 * simply wink 'from' (if not NULL).
1116 void filer_change_to(FilerWindow
*filer_window
,
1117 const char *path
, const char *from
)
1123 g_return_if_fail(filer_window
!= NULL
);
1125 filer_cancel_thumbnails(filer_window
);
1127 filer_tooltip_prime(NULL
, NULL
);
1129 real_path
= pathdup(path
);
1130 new_dir
= g_fscache_lookup(dir_cache
, real_path
);
1134 delayed_error(_("Directory '%s' is not accessible"),
1140 if (o_unique_filer_windows
.int_value
)
1144 fw
= find_filer_window(real_path
, filer_window
);
1146 gtk_widget_destroy(fw
->window
);
1149 from_dup
= from
&& *from
? g_strdup(from
) : NULL
;
1151 detach(filer_window
);
1152 g_free(filer_window
->path
);
1153 filer_window
->path
= real_path
;
1155 filer_window
->directory
= new_dir
;
1157 g_free(filer_window
->auto_select
);
1158 filer_window
->had_cursor
= filer_window
->collection
->cursor_item
!= -1
1159 || filer_window
->had_cursor
;
1160 filer_window
->auto_select
= from_dup
;
1162 filer_set_title(filer_window
);
1163 if (filer_window
->window
->window
)
1164 gdk_window_set_role(filer_window
->window
->window
,
1165 filer_window
->path
);
1166 collection_set_cursor_item(filer_window
->collection
, -1);
1168 attach(filer_window
);
1169 if (o_filer_auto_resize
.int_value
== RESIZE_ALWAYS
)
1170 filer_window_autosize(filer_window
, TRUE
);
1172 if (filer_window
->mini_type
== MINI_PATH
)
1173 gtk_idle_add((GtkFunction
) minibuffer_show_cb
,
1177 void filer_open_parent(FilerWindow
*filer_window
)
1182 if (filer_window
->path
[0] == '/' && filer_window
->path
[1] == '\0')
1183 return; /* Already in the root */
1185 copy
= g_strdup(filer_window
->path
);
1186 slash
= strrchr(copy
, '/');
1191 filer_opendir(*copy
? copy
: "/", filer_window
);
1194 g_warning("No / in directory path!\n");
1199 /* Returns a list containing the full pathname of every selected item.
1200 * You must g_free() each item in the list.
1202 GList
*filer_selected_items(FilerWindow
*filer_window
)
1204 Collection
*collection
= filer_window
->collection
;
1205 GList
*retval
= NULL
;
1206 guchar
*dir
= filer_window
->path
;
1209 for (i
= 0; i
< collection
->number_of_items
; i
++)
1211 if (collection
->items
[i
].selected
)
1213 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1215 retval
= g_list_prepend(retval
,
1216 g_strdup(make_path(dir
, item
->leafname
)->str
));
1220 return g_list_reverse(retval
);
1223 int selected_item_number(Collection
*collection
)
1227 g_return_val_if_fail(collection
!= NULL
, -1);
1228 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1229 g_return_val_if_fail(collection
->number_selected
== 1, -1);
1231 for (i
= 0; i
< collection
->number_of_items
; i
++)
1232 if (collection
->items
[i
].selected
)
1235 g_warning("selected_item: number_selected is wrong\n");
1240 DirItem
*selected_item(Collection
*collection
)
1244 item
= selected_item_number(collection
);
1247 return (DirItem
*) collection
->items
[item
].data
;
1251 /* Append all the URIs in the selection to the string */
1252 static void create_uri_list(FilerWindow
*filer_window
, GString
*string
)
1254 Collection
*collection
= filer_window
->collection
;
1256 int i
, num_selected
;
1258 leader
= g_string_new("file://");
1259 g_string_append(leader
, our_host_name_for_dnd());
1260 g_string_append(leader
, filer_window
->path
);
1261 if (leader
->str
[leader
->len
- 1] != '/')
1262 g_string_append_c(leader
, '/');
1264 num_selected
= collection
->number_selected
;
1266 for (i
= 0; num_selected
> 0; i
++)
1268 if (collection
->items
[i
].selected
)
1270 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1272 g_string_append(string
, leader
->str
);
1273 g_string_append(string
, item
->leafname
);
1274 g_string_append(string
, "\r\n");
1279 g_string_free(leader
, TRUE
);
1282 /* Creates and shows a new filer window.
1283 * If src_win != NULL then display options can be taken from that source window.
1284 * Returns the new filer window, or NULL on error.
1285 * Note: if unique windows is in use, may return an existing window.
1287 FilerWindow
*filer_opendir(const char *path
, FilerWindow
*src_win
)
1289 FilerWindow
*filer_window
;
1291 DisplayStyle dstyle
;
1293 FilerWindow
*same_dir_window
= NULL
;
1295 /* Get the real pathname of the directory and copy it */
1296 real_path
= pathdup(path
);
1298 if (o_unique_filer_windows
.int_value
)
1299 same_dir_window
= find_filer_window(real_path
, NULL
);
1301 if (same_dir_window
)
1303 gtk_window_present(GTK_WINDOW(same_dir_window
->window
));
1304 return same_dir_window
;
1307 filer_window
= g_new(FilerWindow
, 1);
1308 filer_window
->message
= NULL
;
1309 filer_window
->minibuffer
= NULL
;
1310 filer_window
->minibuffer_label
= NULL
;
1311 filer_window
->minibuffer_area
= NULL
;
1312 filer_window
->temp_show_hidden
= FALSE
;
1313 filer_window
->path
= real_path
;
1314 filer_window
->scanning
= FALSE
;
1315 filer_window
->had_cursor
= FALSE
;
1316 filer_window
->auto_select
= NULL
;
1317 filer_window
->toolbar_text
= NULL
;
1318 filer_window
->target_cb
= NULL
;
1319 filer_window
->mini_type
= MINI_NONE
;
1320 filer_window
->selection_state
= GTK_STATE_INSENSITIVE
;
1322 /* Finds the entry for this directory in the dir cache, creating
1323 * a new one if needed. This does not cause a scan to start,
1324 * so if a new entry is created then it will be empty.
1326 filer_window
->directory
= g_fscache_lookup(dir_cache
,
1327 filer_window
->path
);
1328 if (!filer_window
->directory
)
1330 delayed_error(_("Directory '%s' not found."), path
);
1331 g_free(filer_window
->path
);
1332 g_free(filer_window
);
1336 filer_window
->temp_item_selected
= FALSE
;
1337 filer_window
->flags
= (FilerFlags
) 0;
1338 filer_window
->details_type
= DETAILS_SUMMARY
;
1339 filer_window
->display_style
= UNKNOWN_STYLE
;
1340 filer_window
->thumb_queue
= NULL
;
1341 filer_window
->max_thumbs
= 0;
1343 if (src_win
&& o_display_inherit_options
.int_value
)
1345 filer_window
->sort_fn
= src_win
->sort_fn
;
1346 dstyle
= src_win
->display_style
;
1347 dtype
= src_win
->details_type
;
1348 filer_window
->show_hidden
= src_win
->show_hidden
;
1349 filer_window
->show_thumbs
= src_win
->show_thumbs
;
1353 int i
= o_display_sort_by
.int_value
;
1354 filer_window
->sort_fn
= i
== 0 ? sort_by_name
:
1355 i
== 1 ? sort_by_type
:
1356 i
== 2 ? sort_by_date
:
1359 dstyle
= o_display_size
.int_value
;
1360 dtype
= o_display_details
.int_value
;
1361 filer_window
->show_hidden
=
1362 o_display_show_hidden
.int_value
;
1363 filer_window
->show_thumbs
=
1364 o_display_show_thumbs
.int_value
;
1367 /* Add all the user-interface elements & realise */
1368 filer_add_widgets(filer_window
);
1370 gtk_window_set_position(GTK_WINDOW(filer_window
->window
),
1373 /* Connect to all the signal handlers */
1374 filer_add_signals(filer_window
);
1376 display_set_layout(filer_window
, dstyle
, dtype
);
1378 /* Open the window after a timeout, or when scanning stops.
1379 * Do this before attaching, because attach() might tell us to
1380 * stop scanning (if a scan isn't needed).
1382 filer_window
->open_timeout
= gtk_timeout_add(500,
1383 (GtkFunction
) open_filer_window
,
1386 /* The collection is created empty and then attach() is called, which
1387 * links the filer window to the entry in the directory cache we
1388 * looked up / created above.
1390 * The attach() function will immediately callback to the filer window
1391 * to deliver a list of all known entries in the directory (so,
1392 * collection->number_of_items may be valid after the call to
1393 * attach() returns).
1395 * BUT, if the directory was not in the cache (because it hadn't been
1396 * opened it before) then the cached dir will be empty and nothing gets
1397 * added until a while later when some entries are actually available.
1400 attach(filer_window
);
1402 number_of_windows
++;
1403 all_filer_windows
= g_list_prepend(all_filer_windows
, filer_window
);
1405 return filer_window
;
1408 /* This adds all the widgets to a new filer window. It is in a separate
1409 * function because filer_opendir() was getting too long...
1411 static void filer_add_widgets(FilerWindow
*filer_window
)
1413 GtkWidget
*hbox
, *vbox
, *collection
;
1415 /* Create the top-level window widget */
1416 filer_window
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1417 filer_set_title(filer_window
);
1419 /* The collection is the area that actually displays the files */
1420 collection
= collection_new();
1422 /* This property is cleared when the window is destroyed.
1423 * You can thus ref filer_window->window and use this to see
1424 * if the window no longer exists.
1426 g_object_set_data(G_OBJECT(filer_window
->window
),
1427 "filer_window", filer_window
);
1429 g_object_set_data(G_OBJECT(collection
), "filer_window", filer_window
);
1430 filer_window
->collection
= COLLECTION(collection
);
1432 filer_window
->collection
->free_item
= display_free_colitem
;
1434 /* Scrollbar on the right, everything else on the left */
1435 hbox
= gtk_hbox_new(FALSE
, 0);
1436 gtk_container_add(GTK_CONTAINER(filer_window
->window
), hbox
);
1438 vbox
= gtk_vbox_new(FALSE
, 0);
1439 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, TRUE
, TRUE
, 0);
1441 /* Create a frame for the toolbar, but don't show it unless we actually
1443 * (allows us to change the toolbar later)
1445 filer_window
->toolbar_frame
= gtk_frame_new(NULL
);
1446 gtk_frame_set_shadow_type(GTK_FRAME(filer_window
->toolbar_frame
),
1448 gtk_box_pack_start(GTK_BOX(vbox
),
1449 filer_window
->toolbar_frame
, FALSE
, TRUE
, 0);
1451 /* If we want a toolbar, create it and put it in the frame */
1452 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
1456 toolbar
= toolbar_new(filer_window
);
1457 gtk_container_add(GTK_CONTAINER(filer_window
->toolbar_frame
),
1459 gtk_widget_show_all(filer_window
->toolbar_frame
);
1462 /* If there's a message that should be displayed in each window (eg
1463 * 'Running as root'), add it here.
1465 if (show_user_message
)
1467 filer_window
->message
= gtk_label_new(show_user_message
);
1468 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->message
,
1470 gtk_widget_show(filer_window
->message
);
1473 /* Now add the area for displaying the files.
1474 * The collection is one huge window that goes in a Viewport.
1477 GtkWidget
*viewport
;
1480 adj
= filer_window
->collection
->vadj
;
1481 viewport
= gtk_viewport_new(NULL
, adj
);
1482 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport
),
1484 gtk_container_add(GTK_CONTAINER(viewport
), collection
);
1485 gtk_widget_show_all(viewport
);
1486 gtk_box_pack_start(GTK_BOX(vbox
), viewport
, TRUE
, TRUE
, 0);
1487 filer_window
->scrollbar
= gtk_vscrollbar_new(adj
);
1488 gtk_widget_set_size_request(viewport
, 4, 4);
1490 gtk_container_set_resize_mode(GTK_CONTAINER(viewport
),
1491 GTK_RESIZE_IMMEDIATE
);
1494 /* And the minibuffer (hidden to start with) */
1495 create_minibuffer(filer_window
);
1496 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->minibuffer_area
,
1499 /* And the thumbnail progress bar (also hidden) */
1503 filer_window
->thumb_bar
= gtk_hbox_new(FALSE
, 2);
1504 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->thumb_bar
,
1507 filer_window
->thumb_progress
= gtk_progress_bar_new();
1509 gtk_box_pack_start(GTK_BOX(filer_window
->thumb_bar
),
1510 filer_window
->thumb_progress
, TRUE
, TRUE
, 0);
1512 cancel
= gtk_button_new_with_label(_("Cancel"));
1513 GTK_WIDGET_UNSET_FLAGS(cancel
, GTK_CAN_FOCUS
);
1514 gtk_box_pack_start(GTK_BOX(filer_window
->thumb_bar
),
1515 cancel
, FALSE
, TRUE
, 0);
1516 g_signal_connect_swapped(cancel
, "clicked",
1517 G_CALLBACK(filer_cancel_thumbnails
),
1521 /* Put the scrollbar on the left of everything else... */
1522 gtk_box_pack_start(GTK_BOX(hbox
),
1523 filer_window
->scrollbar
, FALSE
, TRUE
, 0);
1525 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
), collection
);
1527 gtk_widget_show(hbox
);
1528 gtk_widget_show(vbox
);
1529 gtk_widget_show(filer_window
->scrollbar
);
1530 gtk_widget_show(collection
);
1532 gtk_widget_realize(filer_window
->window
);
1534 gdk_window_set_role(filer_window
->window
->window
, filer_window
->path
);
1536 filer_window_set_size(filer_window
, 4, 4, TRUE
);
1539 static void filer_add_signals(FilerWindow
*filer_window
)
1541 GtkObject
*collection
= GTK_OBJECT(filer_window
->collection
);
1542 GtkTargetEntry target_table
[] =
1544 {"text/uri-list", 0, TARGET_URI_LIST
},
1545 {"STRING", 0, TARGET_STRING
},
1546 {"COMPOUND_TEXT", 0, TARGET_STRING
},/* XXX: Treats as STRING */
1549 /* Events on the top-level window */
1550 gtk_widget_add_events(filer_window
->window
, GDK_ENTER_NOTIFY
);
1551 g_signal_connect(filer_window
->window
, "enter-notify-event",
1552 G_CALLBACK(pointer_in
), filer_window
);
1553 g_signal_connect(filer_window
->window
, "leave-notify-event",
1554 G_CALLBACK(pointer_out
), filer_window
);
1555 g_signal_connect(filer_window
->window
, "destroy",
1556 G_CALLBACK(filer_window_destroyed
), filer_window
);
1558 /* Events on the collection widget */
1559 gtk_widget_set_events(GTK_WIDGET(collection
),
1560 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
1561 GDK_BUTTON3_MOTION_MASK
| GDK_POINTER_MOTION_MASK
|
1562 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
);
1564 g_signal_connect(collection
, "lose_selection",
1565 G_CALLBACK(lose_selection
), filer_window
);
1566 g_signal_connect(collection
, "selection_changed",
1567 G_CALLBACK(selection_changed
), filer_window
);
1568 g_signal_connect(collection
, "selection_clear_event",
1569 G_CALLBACK(collection_lose_selection
), NULL
);
1570 g_signal_connect(collection
, "selection_get",
1571 G_CALLBACK(selection_get
), NULL
);
1572 gtk_selection_add_targets(GTK_WIDGET(collection
), GDK_SELECTION_PRIMARY
,
1574 sizeof(target_table
) / sizeof(*target_table
));
1576 g_signal_connect(collection
, "key_press_event",
1577 G_CALLBACK(key_press_event
), filer_window
);
1578 g_signal_connect(collection
, "button-release-event",
1579 G_CALLBACK(coll_button_release
), filer_window
);
1580 g_signal_connect(collection
, "button-press-event",
1581 G_CALLBACK(coll_button_press
), filer_window
);
1582 g_signal_connect(collection
, "motion-notify-event",
1583 G_CALLBACK(coll_motion_notify
), filer_window
);
1585 /* Drag and drop events */
1586 g_signal_connect(collection
, "drag_data_get",
1587 GTK_SIGNAL_FUNC(drag_data_get
), NULL
);
1588 drag_set_dest(filer_window
);
1591 static gint
clear_scanning_display(FilerWindow
*filer_window
)
1593 if (filer_exists(filer_window
))
1594 filer_set_title(filer_window
);
1598 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
)
1600 if (scanning
== filer_window
->scanning
)
1602 filer_window
->scanning
= scanning
;
1605 filer_set_title(filer_window
);
1607 gtk_timeout_add(300, (GtkFunction
) clear_scanning_display
,
1611 /* Note that filer_window may not exist after this call. */
1612 void filer_update_dir(FilerWindow
*filer_window
, gboolean warning
)
1614 if (may_rescan(filer_window
, warning
))
1615 dir_update(filer_window
->directory
, filer_window
->path
);
1618 void filer_update_all(void)
1620 GList
*next
= all_filer_windows
;
1624 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1628 filer_update_dir(filer_window
, TRUE
);
1632 /* Refresh the various caches even if we don't think we need to */
1633 void full_refresh(void)
1638 /* See whether a filer window with a given path already exists
1639 * and is different from diff.
1641 static FilerWindow
*find_filer_window(char *path
, FilerWindow
*diff
)
1643 GList
*next
= all_filer_windows
;
1647 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1649 if (filer_window
!= diff
&&
1650 strcmp(path
, filer_window
->path
) == 0)
1652 return filer_window
;
1661 /* This path has been mounted/umounted/deleted some files - update all dirs */
1662 void filer_check_mounted(const char *path
)
1664 GList
*next
= all_filer_windows
;
1672 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1676 if (strncmp(path
, filer_window
->path
, len
) == 0)
1678 char s
= filer_window
->path
[len
];
1680 if (s
== '/' || s
== '\0')
1681 filer_update_dir(filer_window
, FALSE
);
1685 slash
= strrchr(path
, '/');
1686 if (slash
&& slash
!= path
)
1690 parent
= g_strndup(path
, slash
- path
);
1692 refresh_dirs(parent
);
1699 icons_may_update(path
);
1702 /* Close all windows displaying 'path' or subdirectories of 'path' */
1703 void filer_close_recursive(const char *path
)
1705 GList
*next
= all_filer_windows
;
1712 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1716 if (strncmp(path
, filer_window
->path
, len
) == 0)
1718 char s
= filer_window
->path
[len
];
1720 if (len
== 1 || s
== '/' || s
== '\0')
1721 gtk_widget_destroy(filer_window
->window
);
1726 /* Like minibuffer_show(), except that:
1727 * - It returns FALSE (to be used from an idle callback)
1728 * - It checks that the filer window still exists.
1730 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
)
1732 if (filer_exists(filer_window
))
1733 minibuffer_show(filer_window
, MINI_PATH
);
1737 /* TRUE iff filer_window points to an existing FilerWindow
1740 gboolean
filer_exists(FilerWindow
*filer_window
)
1744 for (next
= all_filer_windows
; next
; next
= next
->next
)
1746 FilerWindow
*fw
= (FilerWindow
*) next
->data
;
1748 if (fw
== filer_window
)
1755 /* Make sure the window title is up-to-date */
1756 void filer_set_title(FilerWindow
*filer_window
)
1758 guchar
*title
= NULL
;
1761 if (filer_window
->scanning
|| filer_window
->show_hidden
||
1762 filer_window
->show_thumbs
)
1764 if (o_short_flag_names
.int_value
)
1766 flags
= g_strconcat(" +",
1767 filer_window
->scanning
? _("S") : "",
1768 filer_window
->show_hidden
? _("A") : "",
1769 filer_window
->show_thumbs
? _("T") : "",
1774 flags
= g_strconcat(" (",
1775 filer_window
->scanning
? _("Scanning, ") : "",
1776 filer_window
->show_hidden
? _("All, ") : "",
1777 filer_window
->show_thumbs
? _("Thumbs, ") : "",
1779 flags
[strlen(flags
) - 2] = ')';
1785 title
= g_strconcat("//", our_host_name(),
1786 filer_window
->path
, flags
, NULL
);
1789 if (!title
&& home_dir_len
> 1 &&
1790 strncmp(filer_window
->path
, home_dir
, home_dir_len
) == 0)
1792 guchar sep
= filer_window
->path
[home_dir_len
];
1794 if (sep
== '\0' || sep
== '/')
1795 title
= g_strconcat("~",
1796 filer_window
->path
+ home_dir_len
,
1802 title
= g_strconcat(filer_window
->path
, flags
, NULL
);
1804 gtk_window_set_title(GTK_WINDOW(filer_window
->window
), title
);
1807 if (flags
[0] != '\0')
1811 /* Reconnect to the same directory (used when the Show Hidden option is
1814 void filer_detach_rescan(FilerWindow
*filer_window
)
1816 Directory
*dir
= filer_window
->directory
;
1819 detach(filer_window
);
1820 filer_window
->directory
= dir
;
1821 attach(filer_window
);
1824 static gint
coll_button_release(GtkWidget
*widget
,
1825 GdkEventButton
*event
,
1826 FilerWindow
*filer_window
)
1828 if (dnd_motion_release(event
))
1830 if (motion_buttons_pressed
== 0 &&
1831 filer_window
->collection
->lasso_box
)
1833 collection_end_lasso(filer_window
->collection
,
1834 event
->button
== 1 ? GDK_SET
: GDK_INVERT
);
1839 perform_action(filer_window
, event
);
1844 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
)
1846 Collection
*collection
= filer_window
->collection
;
1850 gboolean press
= event
->type
== GDK_BUTTON_PRESS
;
1851 gboolean selected
= FALSE
;
1852 OpenFlags flags
= 0;
1854 if (event
->button
> 3)
1857 item
= collection_get_item(collection
, event
->x
, event
->y
);
1859 if (item
!= -1 && event
->button
== 1 &&
1860 collection
->items
[item
].selected
&&
1861 filer_window
->selection_state
== GTK_STATE_INSENSITIVE
)
1863 selection_changed(filer_window
->collection
,
1864 event
->time
, filer_window
);
1868 if (filer_window
->target_cb
)
1870 dnd_motion_ungrab();
1871 if (item
!= -1 && press
&& event
->button
== 1)
1872 filer_window
->target_cb(filer_window
, item
,
1873 filer_window
->target_data
);
1874 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1879 action
= bind_lookup_bev(
1880 item
== -1 ? BIND_DIRECTORY
: BIND_DIRECTORY_ICON
,
1885 dir_item
= (DirItem
*) collection
->items
[item
].data
;
1886 selected
= collection
->items
[item
].selected
;
1893 case ACT_CLEAR_SELECTION
:
1894 collection_clear_selection(collection
);
1896 case ACT_TOGGLE_SELECTED
:
1897 collection_toggle_item(collection
, item
);
1899 case ACT_SELECT_EXCL
:
1900 collection_clear_except(collection
, item
);
1903 flags
|= OPEN_SHIFT
;
1906 if (event
->button
!= 1)
1907 flags
|= OPEN_CLOSE_WINDOW
;
1909 flags
|= OPEN_SAME_WINDOW
;
1910 if (o_new_button_1
.int_value
)
1911 flags
^= OPEN_SAME_WINDOW
;
1912 if (event
->type
== GDK_2BUTTON_PRESS
)
1913 collection_unselect_item(collection
, item
);
1914 dnd_motion_ungrab();
1915 filer_openitem(filer_window
, item
, flags
);
1917 case ACT_POPUP_MENU
:
1918 dnd_motion_ungrab();
1919 filer_tooltip_prime(NULL
, NULL
);
1920 show_filer_menu(filer_window
, (GdkEvent
*) event
, item
);
1922 case ACT_PRIME_AND_SELECT
:
1924 collection_clear_except(collection
, item
);
1925 dnd_motion_start(MOTION_READY_FOR_DND
);
1927 case ACT_PRIME_AND_TOGGLE
:
1928 collection_toggle_item(collection
, item
);
1929 dnd_motion_start(MOTION_READY_FOR_DND
);
1931 case ACT_PRIME_FOR_DND
:
1932 dnd_motion_start(MOTION_READY_FOR_DND
);
1935 if (press
&& event
->button
< 4)
1938 collection_wink_item(collection
, item
);
1939 dnd_motion_start(MOTION_NONE
);
1942 case ACT_LASSO_CLEAR
:
1943 collection_clear_selection(collection
);
1945 case ACT_LASSO_MODIFY
:
1946 collection_lasso_box(collection
, event
->x
, event
->y
);
1949 filer_window_autosize(filer_window
, TRUE
);
1952 g_warning("Unsupported action : %d\n", action
);
1957 static gint
coll_button_press(GtkWidget
*widget
,
1958 GdkEventButton
*event
,
1959 FilerWindow
*filer_window
)
1961 collection_set_cursor_item(filer_window
->collection
, -1);
1963 if (dnd_motion_press(widget
, event
))
1964 perform_action(filer_window
, event
);
1969 static gint
coll_motion_notify(GtkWidget
*widget
,
1970 GdkEventMotion
*event
,
1971 FilerWindow
*filer_window
)
1973 Collection
*collection
= filer_window
->collection
;
1976 i
= collection_get_item(collection
, event
->x
, event
->y
);
1979 filer_tooltip_prime(NULL
, NULL
);
1981 filer_tooltip_prime(filer_window
,
1982 (DirItem
*) collection
->items
[i
].data
);
1984 if (motion_state
!= MOTION_READY_FOR_DND
)
1987 if (!dnd_motion_moved(event
))
1990 i
= collection_get_item(collection
,
1991 event
->x
- (event
->x_root
- drag_start_x
),
1992 event
->y
- (event
->y_root
- drag_start_y
));
1996 collection_wink_item(collection
, -1);
1998 if (!collection
->items
[i
].selected
)
2000 if (event
->state
& GDK_BUTTON1_MASK
)
2002 /* Select just this one */
2003 filer_window
->temp_item_selected
= TRUE
;
2004 collection_clear_except(collection
, i
);
2008 if (collection
->number_selected
== 0)
2009 filer_window
->temp_item_selected
= TRUE
;
2010 collection_select_item(collection
, i
);
2014 g_return_val_if_fail(collection
->number_selected
> 0, TRUE
);
2016 if (collection
->number_selected
== 1)
2018 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
2019 ViewData
*view
= (ViewData
*) collection
->items
[i
].view_data
;
2022 item
= dir_update_item(filer_window
->directory
,
2027 report_error(_("Item no longer exists!"));
2031 drag_one_item(widget
, event
,
2032 make_path(filer_window
->path
, item
->leafname
)->str
,
2033 item
, view
? view
->image
: NULL
);
2039 uris
= g_string_new(NULL
);
2040 create_uri_list(filer_window
, uris
);
2041 drag_selection(widget
, event
, uris
->str
);
2042 g_string_free(uris
, TRUE
);
2048 /* Puts the filer window into target mode. When an item is chosen,
2049 * fn(filer_window, item, data) is called. 'reason' will be displayed
2050 * on the toolbar while target mode is active.
2052 * Use fn == NULL to cancel target mode.
2054 void filer_target_mode(FilerWindow
*filer_window
,
2059 TargetFunc old_fn
= filer_window
->target_cb
;
2062 gdk_window_set_cursor(
2063 GTK_WIDGET(filer_window
->collection
)->window
,
2064 fn
? crosshair
: NULL
);
2066 filer_window
->target_cb
= fn
;
2067 filer_window
->target_data
= data
;
2069 if (filer_window
->toolbar_text
== NULL
)
2074 GTK_LABEL(filer_window
->toolbar_text
), reason
);
2075 else if (o_toolbar_info
.int_value
)
2078 toolbar_update_info(filer_window
);
2081 gtk_label_set_text(GTK_LABEL(filer_window
->toolbar_text
), "");
2085 /* Draw the black border */
2086 static gint
filer_tooltip_draw(GtkWidget
*w
)
2088 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
2089 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
2094 /* When the tips window closed, record the time. If we try to open another
2095 * tip soon, it will appear more quickly.
2097 static void tip_destroyed(gpointer data
)
2102 /* It's time to make the tooltip appear. If we're not over the item any
2103 * more, or the item doesn't need a tooltip, do nothing.
2105 static gboolean
filer_tooltip_activate(FilerWindow
*filer_window
)
2107 Collection
*collection
;
2110 GString
*tip
= NULL
;
2111 guchar
*fullpath
= NULL
;
2113 g_return_val_if_fail(tip_item
!= NULL
, 0);
2119 if (!filer_exists(filer_window
))
2122 collection
= filer_window
->collection
;
2123 gdk_window_get_pointer(GTK_WIDGET(collection
)->window
, &x
, &y
, NULL
);
2124 i
= collection_get_item(filer_window
->collection
, x
, y
);
2125 if (i
== -1 || ((DirItem
*) collection
->items
[i
].data
) != tip_item
)
2126 return FALSE
; /* Not still under the pointer */
2128 /* OK, the filer window still exists and the pointer is still
2129 * over the same item. Do we need to show a tip?
2132 tip
= g_string_new(NULL
);
2134 if (display_is_truncated(filer_window
, i
))
2136 g_string_append(tip
, tip_item
->leafname
);
2137 g_string_append_c(tip
, '\n');
2140 fullpath
= make_path(filer_window
->path
, tip_item
->leafname
)->str
;
2142 if (tip_item
->flags
& ITEM_FLAG_SYMLINK
)
2146 target
= readlink_dup(fullpath
);
2149 g_string_append(tip
, _("Symbolic link to "));
2150 g_string_append(tip
, target
);
2151 g_string_append_c(tip
, '\n');
2156 if (tip_item
->flags
& ITEM_FLAG_APPDIR
)
2161 info
= appinfo_get(fullpath
, tip_item
);
2162 if (info
&& ((node
= appinfo_get_section(info
, "Summary"))))
2165 str
= xmlNodeListGetString(node
->doc
,
2166 node
->xmlChildrenNode
, 1);
2169 g_string_append(tip
, str
);
2170 g_string_append_c(tip
, '\n');
2175 g_object_unref(info
);
2178 if (!g_utf8_validate(tip_item
->leafname
, -1, NULL
))
2179 g_string_append(tip
,
2180 _("This filename is not valid UTF-8. "
2181 "You should rename it.\n"));
2185 g_string_truncate(tip
, tip
->len
- 1);
2186 show_tooltip(tip
->str
);
2189 g_string_free(tip
, TRUE
);
2194 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
2195 * NULL, close any current tooltip.
2197 static void show_tooltip(guchar
*text
)
2205 gtk_widget_destroy(tip_widget
);
2213 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
2214 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
2215 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
2217 g_signal_connect_swapped(tip_widget
, "expose_event",
2218 G_CALLBACK(filer_tooltip_draw
), tip_widget
);
2220 label
= gtk_label_new(text
);
2221 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
2222 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
2223 gtk_widget_show(label
);
2224 gtk_widget_realize(tip_widget
);
2226 w
= tip_widget
->allocation
.width
;
2227 h
= tip_widget
->allocation
.height
;
2228 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
2231 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
2233 /* Now check for screen boundaries */
2234 x
= CLAMP(x
, 0, screen_width
- w
);
2235 y
= CLAMP(y
, 0, screen_height
- h
);
2237 /* And again test if pointer is over the tooltip window */
2238 if (py
>= y
&& py
<= y
+ h
)
2240 gtk_window_move(GTK_WINDOW(tip_widget
), x
, y
);
2241 gtk_widget_show(tip_widget
);
2243 g_signal_connect_swapped(tip_widget
, "destroy",
2244 G_CALLBACK(tip_destroyed
), NULL
);
2248 /* Display a tooltip for 'item' after a while (if item is not NULL).
2249 * Cancel any previous tooltip.
2251 static void filer_tooltip_prime(FilerWindow
*filer_window
, DirItem
*item
)
2257 if (item
== tip_item
)
2262 gtk_timeout_remove(tip_timeout
);
2268 gtk_widget_destroy(tip_widget
);
2273 if (filer_window
&& item
)
2275 int delay
= now
- tip_time
> 2 ? 1000 : 200;
2277 tip_timeout
= gtk_timeout_add(delay
,
2278 (GtkFunction
) filer_tooltip_activate
,
2283 static void set_selection_state(FilerWindow
*filer_window
, gboolean normal
)
2285 GtkStateType old_state
;
2287 old_state
= filer_window
->selection_state
;
2288 filer_window
->selection_state
= normal
2289 ? GTK_STATE_SELECTED
: GTK_STATE_INSENSITIVE
;
2291 if (old_state
!= filer_window
->selection_state
2292 && filer_window
->collection
->number_selected
)
2293 gtk_widget_queue_draw(GTK_WIDGET(filer_window
->collection
));
2296 void filer_cancel_thumbnails(FilerWindow
*filer_window
)
2298 gtk_widget_hide(filer_window
->thumb_bar
);
2300 g_list_foreach(filer_window
->thumb_queue
, (GFunc
) g_free
, NULL
);
2301 g_list_free(filer_window
->thumb_queue
);
2302 filer_window
->thumb_queue
= NULL
;
2303 filer_window
->max_thumbs
= 0;
2306 /* Generate the next thumb for this window. The collection object is
2307 * unref'd when there is nothing more to do.
2308 * If the collection no longer has a filer window, nothing is done.
2310 static gboolean
filer_next_thumb_real(GObject
*window
)
2312 FilerWindow
*filer_window
;
2316 filer_window
= g_object_get_data(window
, "filer_window");
2320 g_object_unref(window
);
2324 if (!filer_window
->thumb_queue
)
2326 filer_cancel_thumbnails(filer_window
);
2327 g_object_unref(window
);
2331 total
= filer_window
->max_thumbs
;
2332 done
= total
- g_list_length(filer_window
->thumb_queue
);
2334 path
= (gchar
*) filer_window
->thumb_queue
->data
;
2336 pixmap_background_thumb(path
, (GFunc
) filer_next_thumb
, window
);
2338 filer_window
->thumb_queue
= g_list_remove(filer_window
->thumb_queue
,
2342 gtk_progress_bar_set_fraction(
2343 GTK_PROGRESS_BAR(filer_window
->thumb_progress
),
2344 done
/ (float) total
);
2349 /* path is the thumb just loaded, if any.
2350 * collection is unref'd (eventually).
2352 static void filer_next_thumb(GObject
*window
, const gchar
*path
)
2355 dir_force_update_path(path
);
2357 gtk_idle_add((GtkFunction
) filer_next_thumb_real
, window
);
2360 static void start_thumb_scanning(FilerWindow
*filer_window
)
2362 if (GTK_WIDGET_VISIBLE(filer_window
->thumb_bar
))
2363 return; /* Already scanning */
2365 gtk_widget_show_all(filer_window
->thumb_bar
);
2367 g_object_ref(G_OBJECT(filer_window
->window
));
2368 filer_next_thumb(G_OBJECT(filer_window
->window
), NULL
);
2371 /* Set this image to be loaded some time in the future */
2372 void filer_create_thumb(FilerWindow
*filer_window
, const gchar
*path
)
2374 filer_window
->max_thumbs
++;
2376 filer_window
->thumb_queue
= g_list_append(filer_window
->thumb_queue
,
2379 if (filer_window
->scanning
)
2380 return; /* Will start when scan ends */
2382 start_thumb_scanning(filer_window
);
2385 /* If thumbnail display is on, look through all the items in this directory
2386 * and start creating or updating the thumbnails as needed.
2388 void filer_create_thumbs(FilerWindow
*filer_window
)
2390 Collection
*collection
= filer_window
->collection
;
2393 if (!filer_window
->show_thumbs
)
2396 for (i
= 0; i
< collection
->number_of_items
; i
++)
2398 MaskedPixmap
*pixmap
;
2399 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
2403 if (item
->base_type
!= TYPE_FILE
)
2406 if (strcmp(item
->mime_type
->media_type
, "image") != 0)
2409 path
= make_path(filer_window
->path
, item
->leafname
)->str
;
2411 pixmap
= g_fscache_lookup_full(pixmap_cache
, path
,
2412 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
2414 g_object_unref(pixmap
);
2416 /* If we didn't get an image, it could be because:
2418 * - We're loading the image now. found is TRUE,
2419 * and we'll update the item later.
2420 * - We tried to load the image and failed. found
2422 * - We haven't tried loading the image. found is
2423 * FALSE, and we start creating the thumb here.
2426 filer_create_thumb(filer_window
, path
);
2430 static void filer_options_changed(void)
2432 if (o_short_flag_names
.has_changed
)
2436 for (next
= all_filer_windows
; next
; next
= next
->next
)
2438 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
2440 filer_set_title(filer_window
);