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"
65 #define PANEL_BORDER 2
67 static XMLwrapper
*groups
= NULL
;
69 FilerWindow
*window_with_focus
= NULL
;
70 GList
*all_filer_windows
= NULL
;
72 static FilerWindow
*window_with_primary
= NULL
;
74 /* Item we are about to display a tooltip for */
75 static DirItem
*tip_item
= NULL
;
76 static GtkWidget
*tip_widget
= NULL
;
77 static gint tip_timeout
= 0;
78 static time_t tip_time
= 0; /* Time tip widget last closed */
80 /* Static prototypes */
81 static void attach(FilerWindow
*filer_window
);
82 static void detach(FilerWindow
*filer_window
);
83 static void filer_window_destroyed(GtkWidget
*widget
,
84 FilerWindow
*filer_window
);
85 static void add_item(FilerWindow
*filer_window
, DirItem
*item
);
86 static void update_display(Directory
*dir
,
89 FilerWindow
*filer_window
);
90 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
);
91 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
);
92 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
);
93 static FilerWindow
*find_filer_window(char *path
, FilerWindow
*diff
);
94 static gint
coll_button_release(GtkWidget
*widget
,
95 GdkEventButton
*event
,
96 FilerWindow
*filer_window
);
97 static gint
coll_button_press(GtkWidget
*widget
,
98 GdkEventButton
*event
,
99 FilerWindow
*filer_window
);
100 static gint
coll_motion_notify(GtkWidget
*widget
,
101 GdkEventMotion
*event
,
102 FilerWindow
*filer_window
);
103 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
);
104 static void filer_add_widgets(FilerWindow
*filer_window
);
105 static void filer_add_signals(FilerWindow
*filer_window
);
106 static void filer_tooltip_prime(FilerWindow
*filer_window
, DirItem
*item
);
107 static void show_tooltip(guchar
*text
);
108 static void filer_size_for(FilerWindow
*filer_window
,
109 int w
, int h
, int n
, gboolean allow_shrink
);
111 static void set_selection_state(FilerWindow
*collection
, gboolean normal
);
112 static void filer_next_thumb(GtkObject
*window
, gchar
*path
);
114 static void start_thumb_scanning(FilerWindow
*filer_window
);
116 static GdkCursor
*busy_cursor
= NULL
;
117 static GdkCursor
*crosshair
= NULL
;
119 /* Indicates whether the filer's display is different to the machine it
120 * is actually running on.
122 static gboolean not_local
=FALSE
;
124 static Option o_filer_size_limit
;
125 Option o_filer_auto_resize
, o_unique_filer_windows
;
127 void filer_init(void)
130 gchar
*dpyhost
, *dpy
;
133 option_add_int(&o_filer_size_limit
, "filer_size_limit", 75);
134 option_add_int(&o_filer_auto_resize
, "filer_auto_resize",
136 option_add_int(&o_unique_filer_windows
, "filer_unique_windows", 0);
138 busy_cursor
= gdk_cursor_new(GDK_WATCH
);
139 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
141 /* Is the display on the local machine, or are we being
142 * run remotely? See filer_set_title().
144 ohost
= our_host_name();
145 dpy
= gdk_get_display();
146 dpyhost
= g_strdup(dpy
);
147 tmp
= strchr(dpyhost
, ':');
151 if (dpyhost
[0] && strcmp(ohost
, dpyhost
) != 0)
153 /* Try the cannonical name for dpyhost (see our_host_name()
158 ent
= gethostbyname(dpyhost
);
159 if (!ent
|| strcmp(ohost
, ent
->h_name
) != 0)
166 static gboolean
if_deleted(gpointer item
, gpointer removed
)
168 int i
= ((GPtrArray
*) removed
)->len
;
169 DirItem
**r
= (DirItem
**) ((GPtrArray
*) removed
)->pdata
;
170 char *leafname
= ((DirItem
*) item
)->leafname
;
174 if (strcmp(leafname
, r
[i
]->leafname
) == 0)
181 static void update_item(FilerWindow
*filer_window
, DirItem
*item
)
184 char *leafname
= item
->leafname
;
185 int old_w
= filer_window
->collection
->item_width
;
186 int old_h
= filer_window
->collection
->item_height
;
188 CollectionItem
*colitem
;
190 if (leafname
[0] == '.' && filer_window
->show_hidden
== FALSE
)
193 i
= collection_find_item(filer_window
->collection
, item
, dir_item_cmp
);
197 g_warning("Failed to find '%s'\n", item
->leafname
);
201 colitem
= &filer_window
->collection
->items
[i
];
203 display_update_view(filer_window
,
204 (DirItem
*) colitem
->data
,
205 (ViewData
*) colitem
->view_data
);
207 calc_size(filer_window
, colitem
, &w
, &h
);
208 if (w
> old_w
|| h
> old_h
)
209 collection_set_item_size(filer_window
->collection
,
213 collection_draw_item(filer_window
->collection
, i
, TRUE
);
216 /* Resize the filer window to w x h pixels, plus border (not clamped) */
217 static void filer_window_set_size(FilerWindow
*filer_window
,
219 gboolean allow_shrink
)
221 g_return_if_fail(filer_window
!= NULL
);
223 if (filer_window
->scrollbar
)
224 w
+= filer_window
->scrollbar
->allocation
.width
;
226 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
227 h
+= filer_window
->toolbar_frame
->allocation
.height
;
228 if (filer_window
->message
)
229 h
+= filer_window
->message
->allocation
.height
;
231 if (GTK_WIDGET_VISIBLE(filer_window
->window
))
234 GtkRequisition
*req
= &filer_window
->window
->requisition
;
236 w
= MAX(req
->width
, w
);
237 h
= MAX(req
->height
, h
);
238 gdk_window_get_position(filer_window
->window
->window
,
244 gdk_window_get_size(filer_window
->window
->window
,
249 if (w
== old_w
&& h
== old_h
)
253 if (x
+ w
> screen_width
|| y
+ h
> screen_height
)
255 if (x
+ w
> screen_width
)
256 x
= screen_width
- w
- 4;
257 if (y
+ h
> screen_height
)
258 y
= screen_height
- h
- 4;
259 gdk_window_move_resize(filer_window
->window
->window
,
263 gdk_window_resize(filer_window
->window
->window
, w
, h
);
266 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
270 /* Resize the window to fit the items currently in the Directory.
271 * This should be used once the Directory has been fully scanned, otherwise
272 * the window will appear too small. When opening a directory for the first
273 * time, the names will be known but not the types and images. We can
274 * still make a good estimate of the size.
276 void filer_window_autosize(FilerWindow
*filer_window
, gboolean allow_shrink
)
278 Collection
*collection
= filer_window
->collection
;
281 n
= collection
->number_of_items
;
284 filer_size_for(filer_window
,
285 collection
->item_width
,
286 collection
->item_height
,
290 /* Choose a good size for this window, assuming n items of size (w, h) */
291 static void filer_size_for(FilerWindow
*filer_window
,
292 int w
, int h
, int n
, gboolean allow_shrink
)
302 size_limit
= o_filer_size_limit
.int_value
;
304 /* Get the extra height required for the toolbar and minibuffer,
307 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
308 t
= filer_window
->toolbar_frame
->allocation
.height
;
309 if (filer_window
->message
)
310 t
+= filer_window
->message
->allocation
.height
;
311 if (GTK_WIDGET_VISIBLE(filer_window
->minibuffer_area
))
315 gtk_widget_size_request(filer_window
->minibuffer_area
, &req
);
316 space
= req
.height
+ 2;
320 max_x
= (size_limit
* screen_width
) / 100;
321 max_rows
= (size_limit
* screen_height
) / (h
* 100);
323 /* Aim for a size where
324 * x = r(y + t + h), (1)
325 * unless that's too wide.
327 * t = toolbar (and minibuffer) height
328 * r = desired (width / height) ratio
330 * Want to display all items:
333 * => x(x/r - t - h) = nwh (from 1)
334 * => xx - x.rt - hr(1 - nw) = 0
335 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
339 * sqrt(rt.rt + ...) > rt
341 * So, the +/- must be +:
343 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
345 * ( + w - 1 to round up)
347 x
= (r
* t
+ sqrt(r
*r
*t
*t
+ 4*h
*r
* (n
*w
- 1))) / 2 + w
- 1;
356 /* Choose rows to display all items given our chosen x.
357 * Don't make the window *too* big!
359 rows
= (n
+ cols
- 1) / cols
;
363 /* Leave some room for extra icons, but only in Small Icons mode
364 * otherwise it takes up too much space.
365 * Also, don't add space if the minibuffer is open.
368 space
= filer_window
->display_style
== SMALL_ICONS
? h
: 2;
370 filer_window_set_size(filer_window
,
372 h
* MAX(rows
, 1) + space
,
376 /* Called on a timeout while scanning or when scanning ends
377 * (whichever happens first).
379 static gint
open_filer_window(FilerWindow
*filer_window
)
381 shrink_grid(filer_window
);
383 if (filer_window
->open_timeout
)
385 gtk_timeout_remove(filer_window
->open_timeout
);
386 filer_window
->open_timeout
= 0;
389 if (!GTK_WIDGET_VISIBLE(filer_window
->window
))
391 filer_window_autosize(filer_window
, TRUE
);
392 gtk_widget_show(filer_window
->window
);
398 static void update_display(Directory
*dir
,
401 FilerWindow
*filer_window
)
405 Collection
*collection
= filer_window
->collection
;
410 old_num
= collection
->number_of_items
;
411 for (i
= 0; i
< items
->len
; i
++)
413 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
415 add_item(filer_window
, item
);
418 if (old_num
!= collection
->number_of_items
)
419 collection_qsort(filer_window
->collection
,
420 filer_window
->sort_fn
);
422 /* Open and resize if currently hidden */
423 open_filer_window(filer_window
);
426 collection_delete_if(filer_window
->collection
,
431 set_scanning_display(filer_window
, TRUE
);
432 toolbar_update_info(filer_window
);
435 if (filer_window
->window
->window
)
436 gdk_window_set_cursor(
437 filer_window
->window
->window
,
439 set_scanning_display(filer_window
, FALSE
);
440 toolbar_update_info(filer_window
);
441 open_filer_window(filer_window
);
443 if (filer_window
->had_cursor
&&
444 collection
->cursor_item
== -1)
446 collection_set_cursor_item(collection
, 0);
447 filer_window
->had_cursor
= FALSE
;
449 if (filer_window
->auto_select
)
450 display_set_autoselect(filer_window
,
451 filer_window
->auto_select
);
452 g_free(filer_window
->auto_select
);
453 filer_window
->auto_select
= NULL
;
455 filer_create_thumbs(filer_window
);
457 if (filer_window
->thumb_queue
)
458 start_thumb_scanning(filer_window
);
461 for (i
= 0; i
< items
->len
; i
++)
463 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
465 update_item(filer_window
, item
);
467 collection_qsort(filer_window
->collection
,
468 filer_window
->sort_fn
);
473 static void attach(FilerWindow
*filer_window
)
475 gdk_window_set_cursor(filer_window
->window
->window
, busy_cursor
);
476 collection_clear(filer_window
->collection
);
477 filer_window
->scanning
= TRUE
;
478 dir_attach(filer_window
->directory
, (DirCallback
) update_display
,
480 filer_set_title(filer_window
);
483 static void detach(FilerWindow
*filer_window
)
485 g_return_if_fail(filer_window
->directory
!= NULL
);
487 dir_detach(filer_window
->directory
,
488 (DirCallback
) update_display
, filer_window
);
489 g_fscache_data_unref(dir_cache
, filer_window
->directory
);
490 filer_window
->directory
= NULL
;
493 static void filer_window_destroyed(GtkWidget
*widget
,
494 FilerWindow
*filer_window
)
496 all_filer_windows
= g_list_remove(all_filer_windows
, filer_window
);
498 gtk_object_set_data(GTK_OBJECT(widget
), "filer_window", NULL
);
500 if (window_with_primary
== filer_window
)
501 window_with_primary
= NULL
;
503 if (window_with_focus
== filer_window
)
506 gtk_menu_popdown(GTK_MENU(popup_menu
));
507 window_with_focus
= NULL
;
510 if (filer_window
->directory
)
511 detach(filer_window
);
513 if (filer_window
->open_timeout
)
515 gtk_timeout_remove(filer_window
->open_timeout
);
516 filer_window
->open_timeout
= 0;
519 if (filer_window
->thumb_queue
)
521 g_list_foreach(filer_window
->thumb_queue
, (GFunc
) g_free
, NULL
);
522 g_list_free(filer_window
->thumb_queue
);
525 filer_tooltip_prime(NULL
, NULL
);
527 g_free(filer_window
->auto_select
);
528 g_free(filer_window
->path
);
529 g_free(filer_window
);
531 if (--number_of_windows
< 1)
535 /* Add a single object to a directory display */
536 static void add_item(FilerWindow
*filer_window
, DirItem
*item
)
538 char *leafname
= item
->leafname
;
539 int old_w
= filer_window
->collection
->item_width
;
540 int old_h
= filer_window
->collection
->item_height
;
544 if (leafname
[0] == '.')
546 if (filer_window
->show_hidden
== FALSE
|| leafname
[1] == '\0'
547 || (leafname
[1] == '.' && leafname
[2] == '\0'))
551 i
= collection_insert(filer_window
->collection
,
553 display_create_viewdata(filer_window
, item
));
555 calc_size(filer_window
, &filer_window
->collection
->items
[i
], &w
, &h
);
557 if (w
> old_w
|| h
> old_h
)
558 collection_set_item_size(filer_window
->collection
,
563 /* Returns TRUE iff the directory still exists. */
564 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
)
568 g_return_val_if_fail(filer_window
!= NULL
, FALSE
);
570 /* We do a fresh lookup (rather than update) because the inode may
573 dir
= g_fscache_lookup(dir_cache
, filer_window
->path
);
577 delayed_error(_("Directory missing/deleted"));
578 gtk_widget_destroy(filer_window
->window
);
581 if (dir
== filer_window
->directory
)
582 g_fscache_data_unref(dir_cache
, dir
);
585 detach(filer_window
);
586 filer_window
->directory
= dir
;
587 attach(filer_window
);
593 /* The collection widget has lost the primary selection */
594 static gint
collection_lose_selection(GtkWidget
*widget
,
595 GdkEventSelection
*event
)
597 if (window_with_primary
&&
598 window_with_primary
->collection
== COLLECTION(widget
))
600 FilerWindow
*filer_window
= window_with_primary
;
601 window_with_primary
= NULL
;
602 set_selection_state(filer_window
, FALSE
);
608 /* Someone wants us to send them the selection */
609 static void selection_get(GtkWidget
*widget
,
610 GtkSelectionData
*selection_data
,
615 GString
*reply
, *header
;
616 FilerWindow
*filer_window
;
618 Collection
*collection
;
620 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
622 reply
= g_string_new(NULL
);
623 header
= g_string_new(NULL
);
628 g_string_sprintf(header
, " %s",
629 make_path(filer_window
->path
, "")->str
);
631 case TARGET_URI_LIST
:
632 g_string_sprintf(header
, " file://%s%s",
633 our_host_name_for_dnd(),
634 make_path(filer_window
->path
, "")->str
);
638 collection
= filer_window
->collection
;
639 for (i
= 0; i
< collection
->number_of_items
; i
++)
641 if (collection
->items
[i
].selected
)
644 (DirItem
*) collection
->items
[i
].data
;
646 g_string_append(reply
, header
->str
);
647 g_string_append(reply
, item
->leafname
);
650 /* This works, but I don't think I like it... */
651 /* g_string_append_c(reply, ' '); */
654 gtk_selection_data_set(selection_data
, xa_string
,
655 8, reply
->str
+ 1, reply
->len
- 1);
658 g_warning("Attempt to paste empty selection!");
659 gtk_selection_data_set(selection_data
, xa_string
, 8, "", 0);
662 g_string_free(reply
, TRUE
);
663 g_string_free(header
, TRUE
);
666 /* No items are now selected. This might be because another app claimed
667 * the selection or because the user unselected all the items.
669 static void lose_selection(Collection
*collection
,
673 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
675 if (window_with_primary
== filer_window
)
677 window_with_primary
= NULL
;
678 gtk_selection_owner_set(NULL
,
679 GDK_SELECTION_PRIMARY
,
684 static void selection_changed(Collection
*collection
,
688 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
690 /* Selection has been changed -- try to grab the primary selection
691 * if we don't have it.
693 if (window_with_primary
== filer_window
)
694 return; /* Already got it */
696 if (!collection
->number_selected
)
697 return; /* Nothing selected */
699 if (filer_window
->temp_item_selected
== FALSE
&&
700 gtk_selection_owner_set(GTK_WIDGET(collection
),
701 GDK_SELECTION_PRIMARY
,
704 window_with_primary
= filer_window
;
705 set_selection_state(filer_window
, TRUE
);
708 set_selection_state(filer_window
, FALSE
);
711 /* Open the item (or add it to the shell command minibuffer) */
712 void filer_openitem(FilerWindow
*filer_window
, int item_number
, OpenFlags flags
)
714 gboolean shift
= (flags
& OPEN_SHIFT
) != 0;
715 gboolean close_mini
= flags
& OPEN_FROM_MINI
;
716 gboolean close_window
= (flags
& OPEN_CLOSE_WINDOW
) != 0;
718 DirItem
*item
= (DirItem
*)
719 filer_window
->collection
->items
[item_number
].data
;
721 gboolean wink
= TRUE
;
724 widget
= filer_window
->window
;
725 if (filer_window
->mini_type
== MINI_SHELL
)
727 minibuffer_add(filer_window
, item
->leafname
);
732 dir_update_item(filer_window
->directory
, item
->leafname
);
734 if (item
->base_type
== TYPE_DIRECTORY
)
736 /* Never close a filer window when opening a directory
737 * (click on a dir or click on an app with shift).
739 if (shift
|| !(item
->flags
& ITEM_FLAG_APPDIR
))
740 close_window
= FALSE
;
743 full_path
= make_path(filer_window
->path
, item
->leafname
)->str
;
744 if (shift
&& (item
->flags
& ITEM_FLAG_SYMLINK
))
747 old_dir
= filer_window
->directory
;
748 if (run_diritem(full_path
, item
,
749 flags
& OPEN_SAME_WINDOW
? filer_window
: NULL
,
753 if (old_dir
!= filer_window
->directory
)
757 gtk_widget_destroy(filer_window
->window
);
761 collection_wink_item(filer_window
->collection
,
764 minibuffer_hide(filer_window
);
769 static gint
pointer_in(GtkWidget
*widget
,
770 GdkEventCrossing
*event
,
771 FilerWindow
*filer_window
)
773 may_rescan(filer_window
, TRUE
);
777 static gint
pointer_out(GtkWidget
*widget
,
778 GdkEventCrossing
*event
,
779 FilerWindow
*filer_window
)
781 filer_tooltip_prime(NULL
, NULL
);
785 /* Move the cursor to the next selected item in direction 'dir'
788 static void next_selected(FilerWindow
*filer_window
, int dir
)
790 Collection
*collection
= filer_window
->collection
;
791 int to_check
= collection
->number_of_items
;
792 int item
= collection
->cursor_item
;
794 g_return_if_fail(dir
== 1 || dir
== -1);
796 if (to_check
> 0 && item
== -1)
798 /* Cursor not currently on */
802 item
= collection
->number_of_items
- 1;
804 if (collection
->items
[item
].selected
)
808 while (--to_check
> 0)
812 if (item
>= collection
->number_of_items
)
815 item
= collection
->number_of_items
- 1;
817 if (collection
->items
[item
].selected
)
824 collection_set_cursor_item(collection
, item
);
827 static void return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
829 Collection
*collection
= filer_window
->collection
;
830 int item
= collection
->cursor_item
;
831 TargetFunc cb
= filer_window
->target_cb
;
832 gpointer data
= filer_window
->target_data
;
833 OpenFlags flags
= OPEN_SAME_WINDOW
;
835 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
836 if (item
< 0 || item
>= collection
->number_of_items
)
841 cb(filer_window
, item
, data
);
845 if (event
->state
& GDK_SHIFT_MASK
)
848 filer_openitem(filer_window
, item
, flags
);
851 /* Makes sure that 'groups' is up-to-date, reloading from file if it has
852 * changed. If no groups were loaded and there is no file then initialised
853 * groups to an empty document.
854 * Return the node for the 'name' group.
856 static xmlNode
*group_find(char *name
)
861 /* Update the groups, if possible */
862 path
= choices_find_path_load("Groups.xml", PROJECT
);
866 wrapper
= xml_cache_load(path
);
870 xml_cache_unref(groups
);
879 groups
= g_new(XMLwrapper
, 1);
880 groups
->doc
= xmlNewDoc("1.0");
882 xmlDocSetRootElement(groups
->doc
,
883 xmlNewDocNode(groups
->doc
, NULL
, "groups", NULL
));
887 node
= xmlDocGetRootElement(groups
->doc
);
889 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
893 gid
= xmlGetProp(node
, "name");
898 if (strcmp(name
, gid
) != 0)
909 static void group_save(FilerWindow
*filer_window
, char *name
)
911 Collection
*collection
= filer_window
->collection
;
916 group
= group_find(name
);
919 xmlUnlinkNode(group
);
922 group
= xmlNewChild(xmlDocGetRootElement(groups
->doc
),
923 NULL
, "group", NULL
);
924 xmlSetProp(group
, "name", name
);
927 xmlNewChild(group
, NULL
, "directory", filer_window
->path
);
931 u8_path
= to_utf8(filer_window
->path
);
932 xmlNewChild(group
, NULL
, "directory", u8_path
);
937 for (i
= 0; i
< collection
->number_of_items
; i
++)
939 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
940 gchar
*u8_leaf
= item
->leafname
;
942 if (!collection
->items
[i
].selected
)
946 xmlNewChild(group
, NULL
, "item", u8_leaf
);
948 u8_leaf
= to_utf8(u8_leaf
);
949 xmlNewChild(group
, NULL
, "item", u8_leaf
);
954 save_path
= choices_find_path_save("Groups.xml", PROJECT
, TRUE
);
957 save_xml_file(groups
->doc
, save_path
);
962 static void group_restore(FilerWindow
*filer_window
, char *name
)
964 GHashTable
*in_group
;
965 Collection
*collection
= filer_window
->collection
;
968 xmlNode
*group
, *node
;
970 group
= group_find(name
);
974 report_error(_("Group %s is not set. Select some files "
975 "and press Ctrl+%s to set the group. Press %s "
976 "on its own to reselect the files later."),
981 node
= get_subnode(group
, NULL
, "directory");
982 g_return_if_fail(node
!= NULL
);
983 path
= xmlNodeListGetString(groups
->doc
, node
->xmlChildrenNode
, 1);
984 g_return_if_fail(path
!= NULL
);
989 path
= from_utf8(old
);
994 if (strcmp(path
, filer_window
->path
) != 0)
995 filer_change_to(filer_window
, path
, NULL
);
998 /* If an item at the start is selected then we could lose the
999 * primary selection after checking that item and then need to
1000 * gain it again at the end. Therefore, if anything is selected
1001 * then select the last item until the end of the search.
1003 n
= collection
->number_of_items
;
1004 if (collection
->number_selected
)
1005 collection_select_item(collection
, n
- 1);
1007 in_group
= g_hash_table_new(g_str_hash
, g_str_equal
);
1008 for (node
= group
->xmlChildrenNode
; node
; node
= node
->next
)
1011 if (node
->type
!= XML_ELEMENT_NODE
)
1013 if (strcmp(node
->name
, "item") != 0)
1016 leaf
= xmlNodeListGetString(groups
->doc
,
1017 node
->xmlChildrenNode
, 1);
1019 g_warning("Missing leafname!\n");
1023 g_hash_table_insert(in_group
, leaf
, filer_window
);
1025 g_hash_table_insert(in_group
, from_utf8(leaf
),
1032 for (j
= 0; j
< n
; j
++)
1034 DirItem
*item
= (DirItem
*) collection
->items
[j
].data
;
1036 if (g_hash_table_lookup(in_group
, item
->leafname
))
1037 collection_select_item(collection
, j
);
1039 collection_unselect_item(collection
, j
);
1042 g_hash_table_foreach(in_group
, (GHFunc
) g_free
, NULL
);
1043 g_hash_table_destroy(in_group
);
1046 /* Handle keys that can't be bound with the menu */
1047 static gint
key_press_event(GtkWidget
*widget
,
1049 FilerWindow
*filer_window
)
1052 guint key
= event
->keyval
;
1053 char group
[2] = "1";
1055 window_with_focus
= filer_window
;
1057 /* Note: Not convinced this is the way Gtk's key system is supposed
1060 gtk_window_add_accel_group(GTK_WINDOW(filer_window
->window
),
1063 handled
= gtk_accel_groups_activate(G_OBJECT(filer_window
->window
),
1064 event
->keyval
, event
->state
);
1066 handled
= gtk_accel_groups_activate(GTK_OBJECT(filer_window
->window
),
1067 event
->keyval
, event
->state
);
1069 if (window_with_focus
)
1070 gtk_window_remove_accel_group(GTK_WINDOW(filer_window
->window
),
1073 return TRUE
; /* Window no longer exists */
1080 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1083 return_pressed(filer_window
, event
);
1085 case GDK_ISO_Left_Tab
:
1086 next_selected(filer_window
, -1);
1089 next_selected(filer_window
, 1);
1092 change_to_parent(filer_window
);
1095 filer_tooltip_prime(NULL
, NULL
);
1096 show_filer_menu(filer_window
, (GdkEvent
*) event
,
1097 filer_window
->collection
->cursor_item
);
1100 if (key
>= GDK_0
&& key
<= GDK_9
)
1101 group
[0] = key
- GDK_0
+ '0';
1102 else if (key
>= GDK_KP_0
&& key
<= GDK_KP_9
)
1103 group
[0] = key
- GDK_KP_0
+ '0';
1108 if (event
->state
& GDK_CONTROL_MASK
)
1109 group_save(filer_window
, group
);
1111 group_restore(filer_window
, group
);
1115 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget
), "key_press_event");
1120 void change_to_parent(FilerWindow
*filer_window
)
1125 if (filer_window
->path
[0] == '/' && filer_window
->path
[1] == '\0')
1126 return; /* Already in the root */
1128 copy
= g_strdup(filer_window
->path
);
1129 slash
= strrchr(copy
, '/');
1134 filer_change_to(filer_window
,
1139 g_warning("No / in directory path!\n");
1145 /* Make filer_window display path. When finished, highlight item 'from', or
1146 * the first item if from is NULL. If there is currently no cursor then
1147 * simply wink 'from' (if not NULL).
1149 void filer_change_to(FilerWindow
*filer_window
, char *path
, char *from
)
1155 g_return_if_fail(filer_window
!= NULL
);
1157 filer_cancel_thumbnails(filer_window
);
1159 filer_tooltip_prime(NULL
, NULL
);
1161 real_path
= pathdup(path
);
1162 new_dir
= g_fscache_lookup(dir_cache
, real_path
);
1166 delayed_error(_("Directory '%s' is not accessible"),
1172 if (o_unique_filer_windows
.int_value
)
1176 fw
= find_filer_window(real_path
, filer_window
);
1178 gtk_widget_destroy(fw
->window
);
1181 from_dup
= from
&& *from
? g_strdup(from
) : NULL
;
1183 detach(filer_window
);
1184 g_free(filer_window
->path
);
1185 filer_window
->path
= real_path
;
1187 filer_window
->directory
= new_dir
;
1189 g_free(filer_window
->auto_select
);
1190 filer_window
->had_cursor
= filer_window
->collection
->cursor_item
!= -1
1191 || filer_window
->had_cursor
;
1192 filer_window
->auto_select
= from_dup
;
1194 filer_set_title(filer_window
);
1195 if (filer_window
->window
->window
)
1196 gdk_window_set_role(filer_window
->window
->window
,
1197 filer_window
->path
);
1198 collection_set_cursor_item(filer_window
->collection
, -1);
1200 attach(filer_window
);
1201 if (o_filer_auto_resize
.int_value
== RESIZE_ALWAYS
)
1202 filer_window_autosize(filer_window
, TRUE
);
1204 if (filer_window
->mini_type
== MINI_PATH
)
1205 gtk_idle_add((GtkFunction
) minibuffer_show_cb
,
1209 void filer_open_parent(FilerWindow
*filer_window
)
1214 if (filer_window
->path
[0] == '/' && filer_window
->path
[1] == '\0')
1215 return; /* Already in the root */
1217 copy
= g_strdup(filer_window
->path
);
1218 slash
= strrchr(copy
, '/');
1223 filer_opendir(*copy
? copy
: "/", filer_window
);
1226 g_warning("No / in directory path!\n");
1231 /* Returns a list containing the full pathname of every selected item.
1232 * You must g_free() each item in the list.
1234 GList
*filer_selected_items(FilerWindow
*filer_window
)
1236 Collection
*collection
= filer_window
->collection
;
1237 GList
*retval
= NULL
;
1238 guchar
*dir
= filer_window
->path
;
1241 for (i
= 0; i
< collection
->number_of_items
; i
++)
1243 if (collection
->items
[i
].selected
)
1245 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1247 retval
= g_list_prepend(retval
,
1248 g_strdup(make_path(dir
, item
->leafname
)->str
));
1252 return g_list_reverse(retval
);
1255 int selected_item_number(Collection
*collection
)
1259 g_return_val_if_fail(collection
!= NULL
, -1);
1260 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1261 g_return_val_if_fail(collection
->number_selected
== 1, -1);
1263 for (i
= 0; i
< collection
->number_of_items
; i
++)
1264 if (collection
->items
[i
].selected
)
1267 g_warning("selected_item: number_selected is wrong\n");
1272 DirItem
*selected_item(Collection
*collection
)
1276 item
= selected_item_number(collection
);
1279 return (DirItem
*) collection
->items
[item
].data
;
1283 /* Append all the URIs in the selection to the string */
1284 static void create_uri_list(FilerWindow
*filer_window
, GString
*string
)
1286 Collection
*collection
= filer_window
->collection
;
1288 int i
, num_selected
;
1290 leader
= g_string_new("file://");
1291 g_string_append(leader
, our_host_name_for_dnd());
1292 g_string_append(leader
, filer_window
->path
);
1293 if (leader
->str
[leader
->len
- 1] != '/')
1294 g_string_append_c(leader
, '/');
1296 num_selected
= collection
->number_selected
;
1298 for (i
= 0; num_selected
> 0; i
++)
1300 if (collection
->items
[i
].selected
)
1302 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1304 g_string_append(string
, leader
->str
);
1305 g_string_append(string
, item
->leafname
);
1306 g_string_append(string
, "\r\n");
1311 g_string_free(leader
, TRUE
);
1314 /* Creates and shows a new filer window.
1315 * If src_win != NULL then display options can be taken from that source window.
1316 * Returns the new filer window, or NULL on error.
1317 * Note: if unique windows is in use, may return an existing window.
1319 FilerWindow
*filer_opendir(char *path
, FilerWindow
*src_win
)
1321 FilerWindow
*filer_window
;
1323 DisplayStyle dstyle
;
1325 FilerWindow
*same_dir_window
= NULL
;
1327 /* Get the real pathname of the directory and copy it */
1328 real_path
= pathdup(path
);
1330 if (o_unique_filer_windows
.int_value
)
1331 same_dir_window
= find_filer_window(real_path
, NULL
);
1334 if (same_dir_window
)
1336 gtk_window_present(GTK_WINDOW(same_dir_window
->window
));
1337 return same_dir_window
;
1341 filer_window
= g_new(FilerWindow
, 1);
1342 filer_window
->message
= NULL
;
1343 filer_window
->minibuffer
= NULL
;
1344 filer_window
->minibuffer_label
= NULL
;
1345 filer_window
->minibuffer_area
= NULL
;
1346 filer_window
->temp_show_hidden
= FALSE
;
1347 filer_window
->path
= real_path
;
1348 filer_window
->scanning
= FALSE
;
1349 filer_window
->had_cursor
= FALSE
;
1350 filer_window
->auto_select
= NULL
;
1351 filer_window
->toolbar_text
= NULL
;
1352 filer_window
->target_cb
= NULL
;
1353 filer_window
->mini_type
= MINI_NONE
;
1354 filer_window
->selection_state
= GTK_STATE_INSENSITIVE
;
1356 /* Finds the entry for this directory in the dir cache, creating
1357 * a new one if needed. This does not cause a scan to start,
1358 * so if a new entry is created then it will be empty.
1360 filer_window
->directory
= g_fscache_lookup(dir_cache
,
1361 filer_window
->path
);
1362 if (!filer_window
->directory
)
1364 delayed_error(_("Directory '%s' not found."), path
);
1365 g_free(filer_window
->path
);
1366 g_free(filer_window
);
1370 filer_window
->temp_item_selected
= FALSE
;
1371 filer_window
->flags
= (FilerFlags
) 0;
1372 filer_window
->details_type
= DETAILS_SUMMARY
;
1373 filer_window
->display_style
= UNKNOWN_STYLE
;
1374 filer_window
->thumb_queue
= NULL
;
1375 filer_window
->max_thumbs
= 0;
1377 if (src_win
&& o_display_inherit_options
.int_value
)
1379 filer_window
->sort_fn
= src_win
->sort_fn
;
1380 dstyle
= src_win
->display_style
;
1381 dtype
= src_win
->details_type
;
1382 filer_window
->show_hidden
= src_win
->show_hidden
;
1383 filer_window
->show_thumbs
= src_win
->show_thumbs
;
1387 int i
= o_display_sort_by
.int_value
;
1388 filer_window
->sort_fn
= i
== 0 ? sort_by_name
:
1389 i
== 1 ? sort_by_type
:
1390 i
== 2 ? sort_by_date
:
1393 dstyle
= o_display_size
.int_value
;
1394 dtype
= o_display_details
.int_value
;
1395 filer_window
->show_hidden
=
1396 o_display_show_hidden
.int_value
;
1397 filer_window
->show_thumbs
=
1398 o_display_show_thumbs
.int_value
;
1401 /* Add all the user-interface elements & realise */
1402 filer_add_widgets(filer_window
);
1404 gtk_window_set_position(GTK_WINDOW(filer_window
->window
),
1407 /* Connect to all the signal handlers */
1408 filer_add_signals(filer_window
);
1410 display_set_layout(filer_window
, dstyle
, dtype
);
1412 /* Open the window after a timeout, or when scanning stops.
1413 * Do this before attaching, because attach() might tell us to
1414 * stop scanning (if a scan isn't needed).
1416 filer_window
->open_timeout
= gtk_timeout_add(500,
1417 (GtkFunction
) open_filer_window
,
1420 /* The collection is created empty and then attach() is called, which
1421 * links the filer window to the entry in the directory cache we
1422 * looked up / created above.
1424 * The attach() function will immediately callback to the filer window
1425 * to deliver a list of all known entries in the directory (so,
1426 * collection->number_of_items may be valid after the call to
1427 * attach() returns).
1429 * BUT, if the directory was not in the cache (because it hadn't been
1430 * opened it before) then the cached dir will be empty and nothing gets
1431 * added until a while later when some entries are actually available.
1434 attach(filer_window
);
1436 /* Update number_of_windows BEFORE destroying the old window! */
1437 number_of_windows
++;
1439 /* If the user doesn't want duplicate windows then check
1440 * for an existing one and close it if found.
1444 if (same_dir_window
)
1445 gtk_widget_destroy(same_dir_window
->window
);
1448 all_filer_windows
= g_list_prepend(all_filer_windows
, filer_window
);
1450 return filer_window
;
1453 /* This adds all the widgets to a new filer window. It is in a separate
1454 * function because filer_opendir() was getting too long...
1456 static void filer_add_widgets(FilerWindow
*filer_window
)
1458 GtkWidget
*hbox
, *vbox
, *collection
;
1460 /* Create the top-level window widget */
1461 filer_window
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1462 filer_set_title(filer_window
);
1464 /* The collection is the area that actually displays the files */
1465 collection
= collection_new();
1467 /* This property is cleared when the window is destroyed.
1468 * You can thus ref filer_window->window and use this to see
1469 * if the window no longer exists.
1471 gtk_object_set_data(GTK_OBJECT(filer_window
->window
),
1472 "filer_window", filer_window
);
1474 gtk_object_set_data(GTK_OBJECT(collection
),
1475 "filer_window", filer_window
);
1476 filer_window
->collection
= COLLECTION(collection
);
1478 filer_window
->collection
->free_item
= display_free_colitem
;
1480 /* Scrollbar on the right, everything else on the left */
1481 hbox
= gtk_hbox_new(FALSE
, 0);
1482 gtk_container_add(GTK_CONTAINER(filer_window
->window
), hbox
);
1484 vbox
= gtk_vbox_new(FALSE
, 0);
1485 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, TRUE
, TRUE
, 0);
1487 /* Create a frame for the toolbar, but don't show it unless we actually
1489 * (allows us to change the toolbar later)
1491 filer_window
->toolbar_frame
= gtk_frame_new(NULL
);
1492 gtk_frame_set_shadow_type(GTK_FRAME(filer_window
->toolbar_frame
),
1494 gtk_box_pack_start(GTK_BOX(vbox
),
1495 filer_window
->toolbar_frame
, FALSE
, TRUE
, 0);
1497 /* If we want a toolbar, create it and put it in the frame */
1498 if (o_toolbar
.int_value
!= TOOLBAR_NONE
)
1502 toolbar
= toolbar_new(filer_window
);
1503 gtk_container_add(GTK_CONTAINER(filer_window
->toolbar_frame
),
1505 gtk_widget_show_all(filer_window
->toolbar_frame
);
1508 /* If there's a message that should be displayed in each window (eg
1509 * 'Running as root'), add it here.
1511 if (show_user_message
)
1513 filer_window
->message
= gtk_label_new(show_user_message
);
1514 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->message
,
1516 gtk_widget_show(filer_window
->message
);
1519 /* Now add the area for displaying the files.
1520 * If we've got Gtk+-2.0 then the collection is one huge window
1521 * that goes in a Viewport. Otherwise, the collection handles
1526 GtkWidget
*viewport
;
1529 adj
= filer_window
->collection
->vadj
;
1530 viewport
= gtk_viewport_new(NULL
, adj
);
1531 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport
),
1533 gtk_container_add(GTK_CONTAINER(viewport
), collection
);
1534 gtk_widget_show_all(viewport
);
1535 gtk_box_pack_start(GTK_BOX(vbox
), viewport
, TRUE
, TRUE
, 0);
1536 filer_window
->scrollbar
= gtk_vscrollbar_new(adj
);
1537 gtk_widget_set_usize(viewport
, 4, 4);
1539 gtk_container_set_resize_mode(GTK_CONTAINER(viewport
),
1540 GTK_RESIZE_IMMEDIATE
);
1543 gtk_box_pack_start(GTK_BOX(vbox
), collection
, TRUE
, TRUE
, 0);
1544 filer_window
->scrollbar
=
1545 gtk_vscrollbar_new(COLLECTION(collection
)->vadj
);
1548 /* And the minibuffer (hidden to start with) */
1549 create_minibuffer(filer_window
);
1550 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->minibuffer_area
,
1553 /* And the thumbnail progress bar (also hidden) */
1557 filer_window
->thumb_bar
= gtk_hbox_new(FALSE
, 2);
1558 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->thumb_bar
,
1561 filer_window
->thumb_progress
= gtk_progress_bar_new();
1562 gtk_box_pack_start(GTK_BOX(filer_window
->thumb_bar
),
1563 filer_window
->thumb_progress
, TRUE
, TRUE
, 0);
1565 cancel
= gtk_button_new_with_label(_("Cancel"));
1566 GTK_WIDGET_UNSET_FLAGS(cancel
, GTK_CAN_FOCUS
);
1567 gtk_box_pack_start(GTK_BOX(filer_window
->thumb_bar
),
1568 cancel
, FALSE
, TRUE
, 0);
1569 gtk_signal_connect_object(GTK_OBJECT(cancel
), "clicked",
1570 GTK_SIGNAL_FUNC(filer_cancel_thumbnails
),
1571 (GtkObject
*) filer_window
);
1574 /* Put the scrollbar on the left of everything else... */
1575 gtk_box_pack_start(GTK_BOX(hbox
),
1576 filer_window
->scrollbar
, FALSE
, TRUE
, 0);
1578 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
), collection
);
1580 gtk_widget_show(hbox
);
1581 gtk_widget_show(vbox
);
1582 gtk_widget_show(filer_window
->scrollbar
);
1583 gtk_widget_show(collection
);
1585 gtk_widget_realize(filer_window
->window
);
1587 gdk_window_set_role(filer_window
->window
->window
, filer_window
->path
);
1589 filer_window_set_size(filer_window
, 4, 4, TRUE
);
1592 static void filer_add_signals(FilerWindow
*filer_window
)
1594 GtkObject
*collection
= GTK_OBJECT(filer_window
->collection
);
1595 GtkTargetEntry target_table
[] =
1597 {"text/uri-list", 0, TARGET_URI_LIST
},
1598 {"STRING", 0, TARGET_STRING
},
1599 {"COMPOUND_TEXT", 0, TARGET_STRING
},/* XXX: Treats as STRING */
1602 /* Events on the top-level window */
1603 gtk_widget_add_events(filer_window
->window
, GDK_ENTER_NOTIFY
);
1604 gtk_signal_connect(GTK_OBJECT(filer_window
->window
),
1605 "enter-notify-event",
1606 GTK_SIGNAL_FUNC(pointer_in
), filer_window
);
1607 gtk_signal_connect(GTK_OBJECT(filer_window
->window
),
1608 "leave-notify-event",
1609 GTK_SIGNAL_FUNC(pointer_out
), filer_window
);
1610 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "destroy",
1611 GTK_SIGNAL_FUNC(filer_window_destroyed
), filer_window
);
1613 /* Events on the collection widget */
1614 gtk_widget_set_events(GTK_WIDGET(collection
),
1615 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
1616 GDK_BUTTON3_MOTION_MASK
| GDK_POINTER_MOTION_MASK
|
1617 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
);
1619 gtk_signal_connect(collection
, "lose_selection",
1620 GTK_SIGNAL_FUNC(lose_selection
), filer_window
);
1621 gtk_signal_connect(collection
, "selection_changed",
1622 GTK_SIGNAL_FUNC(selection_changed
), filer_window
);
1623 gtk_signal_connect(collection
, "selection_clear_event",
1624 GTK_SIGNAL_FUNC(collection_lose_selection
), NULL
);
1625 gtk_signal_connect(collection
, "selection_get",
1626 GTK_SIGNAL_FUNC(selection_get
), NULL
);
1627 gtk_selection_add_targets(GTK_WIDGET(collection
), GDK_SELECTION_PRIMARY
,
1629 sizeof(target_table
) / sizeof(*target_table
));
1631 gtk_signal_connect(collection
, "key_press_event",
1632 GTK_SIGNAL_FUNC(key_press_event
), filer_window
);
1633 gtk_signal_connect(collection
, "button-release-event",
1634 GTK_SIGNAL_FUNC(coll_button_release
), filer_window
);
1635 gtk_signal_connect(collection
, "button-press-event",
1636 GTK_SIGNAL_FUNC(coll_button_press
), filer_window
);
1637 gtk_signal_connect(collection
, "motion-notify-event",
1638 GTK_SIGNAL_FUNC(coll_motion_notify
), filer_window
);
1640 /* Drag and drop events */
1641 gtk_signal_connect(collection
, "drag_data_get",
1642 GTK_SIGNAL_FUNC(drag_data_get
), NULL
);
1643 drag_set_dest(filer_window
);
1646 static gint
clear_scanning_display(FilerWindow
*filer_window
)
1648 if (filer_exists(filer_window
))
1649 filer_set_title(filer_window
);
1653 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
)
1655 if (scanning
== filer_window
->scanning
)
1657 filer_window
->scanning
= scanning
;
1660 filer_set_title(filer_window
);
1662 gtk_timeout_add(300, (GtkFunction
) clear_scanning_display
,
1666 /* Note that filer_window may not exist after this call. */
1667 void filer_update_dir(FilerWindow
*filer_window
, gboolean warning
)
1669 if (may_rescan(filer_window
, warning
))
1670 dir_update(filer_window
->directory
, filer_window
->path
);
1673 void filer_update_all(void)
1675 GList
*next
= all_filer_windows
;
1679 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1683 filer_update_dir(filer_window
, TRUE
);
1687 /* Refresh the various caches even if we don't think we need to */
1688 void full_refresh(void)
1693 /* See whether a filer window with a given path already exists
1694 * and is different from diff.
1696 static FilerWindow
*find_filer_window(char *path
, FilerWindow
*diff
)
1698 GList
*next
= all_filer_windows
;
1702 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1704 if (filer_window
!= diff
&&
1705 strcmp(path
, filer_window
->path
) == 0)
1707 return filer_window
;
1716 /* This path has been mounted/umounted/deleted some files - update all dirs */
1717 void filer_check_mounted(char *path
)
1719 GList
*next
= all_filer_windows
;
1727 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1731 if (strncmp(path
, filer_window
->path
, len
) == 0)
1733 char s
= filer_window
->path
[len
];
1735 if (s
== '/' || s
== '\0')
1736 filer_update_dir(filer_window
, FALSE
);
1740 slash
= strrchr(path
, '/');
1741 if (slash
&& slash
!= path
)
1745 parent
= g_strndup(path
, slash
- path
);
1747 refresh_dirs(parent
);
1754 icons_may_update(path
);
1757 /* Close all windows displaying 'path' or subdirectories of 'path' */
1758 void filer_close_recursive(char *path
)
1760 GList
*next
= all_filer_windows
;
1767 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1771 if (strncmp(path
, filer_window
->path
, len
) == 0)
1773 char s
= filer_window
->path
[len
];
1775 if (len
== 1 || s
== '/' || s
== '\0')
1776 gtk_widget_destroy(filer_window
->window
);
1781 /* Like minibuffer_show(), except that:
1782 * - It returns FALSE (to be used from an idle callback)
1783 * - It checks that the filer window still exists.
1785 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
)
1787 if (filer_exists(filer_window
))
1788 minibuffer_show(filer_window
, MINI_PATH
);
1792 /* TRUE iff filer_window points to an existing FilerWindow
1795 gboolean
filer_exists(FilerWindow
*filer_window
)
1799 for (next
= all_filer_windows
; next
; next
= next
->next
)
1801 FilerWindow
*fw
= (FilerWindow
*) next
->data
;
1803 if (fw
== filer_window
)
1810 /* Make sure the window title is up-to-date */
1811 void filer_set_title(FilerWindow
*filer_window
)
1813 guchar
*title
= NULL
;
1816 if (filer_window
->scanning
|| filer_window
->show_hidden
||
1817 filer_window
->show_thumbs
)
1819 flags
= g_strconcat(" (",
1820 filer_window
->scanning
? _("Scanning, ") : "",
1821 filer_window
->show_hidden
? _("All, ") : "",
1822 filer_window
->show_thumbs
? _("Thumbs, ") : "",
1824 flags
[strlen(flags
) - 2] = ')';
1829 title
= g_strconcat("//", our_host_name(),
1830 filer_window
->path
, flags
, NULL
);
1833 if (!title
&& home_dir_len
> 1 &&
1834 strncmp(filer_window
->path
, home_dir
, home_dir_len
) == 0)
1836 guchar sep
= filer_window
->path
[home_dir_len
];
1838 if (sep
== '\0' || sep
== '/')
1839 title
= g_strconcat("~",
1840 filer_window
->path
+ home_dir_len
,
1846 title
= g_strconcat(filer_window
->path
, flags
, NULL
);
1848 gtk_window_set_title(GTK_WINDOW(filer_window
->window
), title
);
1851 if (flags
[0] != '\0')
1855 /* Reconnect to the same directory (used when the Show Hidden option is
1858 void filer_detach_rescan(FilerWindow
*filer_window
)
1860 Directory
*dir
= filer_window
->directory
;
1862 g_fscache_data_ref(dir_cache
, dir
);
1863 detach(filer_window
);
1864 filer_window
->directory
= dir
;
1865 attach(filer_window
);
1868 static gint
coll_button_release(GtkWidget
*widget
,
1869 GdkEventButton
*event
,
1870 FilerWindow
*filer_window
)
1872 if (dnd_motion_release(event
))
1874 if (motion_buttons_pressed
== 0 &&
1875 filer_window
->collection
->lasso_box
)
1877 collection_end_lasso(filer_window
->collection
,
1878 event
->button
== 1 ? GDK_SET
: GDK_INVERT
);
1883 perform_action(filer_window
, event
);
1888 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
)
1890 Collection
*collection
= filer_window
->collection
;
1894 gboolean press
= event
->type
== GDK_BUTTON_PRESS
;
1895 gboolean selected
= FALSE
;
1896 OpenFlags flags
= 0;
1898 if (event
->button
> 3)
1901 item
= collection_get_item(collection
, event
->x
, event
->y
);
1903 if (item
!= -1 && event
->button
== 1 &&
1904 collection
->items
[item
].selected
&&
1905 filer_window
->selection_state
== GTK_STATE_INSENSITIVE
)
1907 selection_changed(filer_window
->collection
,
1908 event
->time
, filer_window
);
1912 if (filer_window
->target_cb
)
1914 dnd_motion_ungrab();
1915 if (item
!= -1 && press
&& event
->button
== 1)
1916 filer_window
->target_cb(filer_window
, item
,
1917 filer_window
->target_data
);
1918 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1923 action
= bind_lookup_bev(
1924 item
== -1 ? BIND_DIRECTORY
: BIND_DIRECTORY_ICON
,
1929 dir_item
= (DirItem
*) collection
->items
[item
].data
;
1930 selected
= collection
->items
[item
].selected
;
1937 case ACT_CLEAR_SELECTION
:
1938 collection_clear_selection(collection
);
1940 case ACT_TOGGLE_SELECTED
:
1941 collection_toggle_item(collection
, item
);
1943 case ACT_SELECT_EXCL
:
1944 collection_clear_except(collection
, item
);
1947 flags
|= OPEN_SHIFT
;
1950 if (event
->button
!= 1)
1951 flags
|= OPEN_CLOSE_WINDOW
;
1953 flags
|= OPEN_SAME_WINDOW
;
1954 if (o_new_button_1
.int_value
)
1955 flags
^= OPEN_SAME_WINDOW
;
1956 if (event
->type
== GDK_2BUTTON_PRESS
)
1957 collection_unselect_item(collection
, item
);
1958 dnd_motion_ungrab();
1959 filer_openitem(filer_window
, item
, flags
);
1961 case ACT_POPUP_MENU
:
1962 dnd_motion_ungrab();
1963 filer_tooltip_prime(NULL
, NULL
);
1964 show_filer_menu(filer_window
, (GdkEvent
*) event
, item
);
1966 case ACT_PRIME_AND_SELECT
:
1968 collection_clear_except(collection
, item
);
1969 dnd_motion_start(MOTION_READY_FOR_DND
);
1971 case ACT_PRIME_AND_TOGGLE
:
1972 collection_toggle_item(collection
, item
);
1973 dnd_motion_start(MOTION_READY_FOR_DND
);
1975 case ACT_PRIME_FOR_DND
:
1976 dnd_motion_start(MOTION_READY_FOR_DND
);
1979 if (press
&& event
->button
< 4)
1982 collection_wink_item(collection
, item
);
1983 dnd_motion_start(MOTION_NONE
);
1986 case ACT_LASSO_CLEAR
:
1987 collection_clear_selection(collection
);
1989 case ACT_LASSO_MODIFY
:
1990 collection_lasso_box(collection
, event
->x
, event
->y
);
1993 filer_window_autosize(filer_window
, TRUE
);
1996 g_warning("Unsupported action : %d\n", action
);
2001 static gint
coll_button_press(GtkWidget
*widget
,
2002 GdkEventButton
*event
,
2003 FilerWindow
*filer_window
)
2005 collection_set_cursor_item(filer_window
->collection
, -1);
2007 if (dnd_motion_press(widget
, event
))
2008 perform_action(filer_window
, event
);
2013 static gint
coll_motion_notify(GtkWidget
*widget
,
2014 GdkEventMotion
*event
,
2015 FilerWindow
*filer_window
)
2017 Collection
*collection
= filer_window
->collection
;
2020 i
= collection_get_item(collection
, event
->x
, event
->y
);
2023 filer_tooltip_prime(NULL
, NULL
);
2025 filer_tooltip_prime(filer_window
,
2026 (DirItem
*) collection
->items
[i
].data
);
2028 if (motion_state
!= MOTION_READY_FOR_DND
)
2031 if (!dnd_motion_moved(event
))
2034 i
= collection_get_item(collection
,
2035 event
->x
- (event
->x_root
- drag_start_x
),
2036 event
->y
- (event
->y_root
- drag_start_y
));
2040 collection_wink_item(collection
, -1);
2042 if (!collection
->items
[i
].selected
)
2044 if (event
->state
& GDK_BUTTON1_MASK
)
2046 /* Select just this one */
2047 filer_window
->temp_item_selected
= TRUE
;
2048 collection_clear_except(collection
, i
);
2052 if (collection
->number_selected
== 0)
2053 filer_window
->temp_item_selected
= TRUE
;
2054 collection_select_item(collection
, i
);
2058 g_return_val_if_fail(collection
->number_selected
> 0, TRUE
);
2060 if (collection
->number_selected
== 1)
2062 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
2063 ViewData
*view
= (ViewData
*) collection
->items
[i
].view_data
;
2066 item
= dir_update_item(filer_window
->directory
,
2071 report_error(_("Item no longer exists!"));
2075 drag_one_item(widget
, event
,
2076 make_path(filer_window
->path
, item
->leafname
)->str
,
2077 item
, view
? view
->image
: NULL
);
2083 uris
= g_string_new(NULL
);
2084 create_uri_list(filer_window
, uris
);
2085 drag_selection(widget
, event
, uris
->str
);
2086 g_string_free(uris
, TRUE
);
2092 /* Puts the filer window into target mode. When an item is chosen,
2093 * fn(filer_window, item, data) is called. 'reason' will be displayed
2094 * on the toolbar while target mode is active.
2096 * Use fn == NULL to cancel target mode.
2098 void filer_target_mode(FilerWindow
*filer_window
,
2103 TargetFunc old_fn
= filer_window
->target_cb
;
2106 gdk_window_set_cursor(
2107 GTK_WIDGET(filer_window
->collection
)->window
,
2108 fn
? crosshair
: NULL
);
2110 filer_window
->target_cb
= fn
;
2111 filer_window
->target_data
= data
;
2113 if (filer_window
->toolbar_text
== NULL
)
2118 GTK_LABEL(filer_window
->toolbar_text
), reason
);
2119 else if (o_toolbar_info
.int_value
)
2122 toolbar_update_info(filer_window
);
2125 gtk_label_set_text(GTK_LABEL(filer_window
->toolbar_text
), "");
2129 /* Draw the black border */
2130 static gint
filer_tooltip_draw(GtkWidget
*w
)
2132 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
2133 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
2138 /* When the tips window closed, record the time. If we try to open another
2139 * tip soon, it will appear more quickly.
2141 static void tip_destroyed(gpointer data
)
2146 /* It's time to make the tooltip appear. If we're not over the item any
2147 * more, or the item doesn't need a tooltip, do nothing.
2149 static gboolean
filer_tooltip_activate(FilerWindow
*filer_window
)
2151 Collection
*collection
;
2154 GString
*tip
= NULL
;
2155 guchar
*fullpath
= NULL
;
2157 g_return_val_if_fail(tip_item
!= NULL
, 0);
2163 if (!filer_exists(filer_window
))
2166 collection
= filer_window
->collection
;
2167 gdk_window_get_pointer(GTK_WIDGET(collection
)->window
, &x
, &y
, NULL
);
2168 i
= collection_get_item(filer_window
->collection
, x
, y
);
2169 if (i
== -1 || ((DirItem
*) collection
->items
[i
].data
) != tip_item
)
2170 return FALSE
; /* Not still under the pointer */
2172 /* OK, the filer window still exists and the pointer is still
2173 * over the same item. Do we need to show a tip?
2176 tip
= g_string_new(NULL
);
2178 if (display_is_truncated(filer_window
, i
))
2180 g_string_append(tip
, tip_item
->leafname
);
2181 g_string_append_c(tip
, '\n');
2184 fullpath
= make_path(filer_window
->path
, tip_item
->leafname
)->str
;
2186 if (tip_item
->flags
& ITEM_FLAG_SYMLINK
)
2190 target
= readlink_dup(fullpath
);
2193 g_string_append(tip
, _("Symbolic link to "));
2194 g_string_append(tip
, target
);
2195 g_string_append_c(tip
, '\n');
2200 if (tip_item
->flags
& ITEM_FLAG_APPDIR
)
2205 info
= appinfo_get(fullpath
, tip_item
);
2206 if (info
&& ((node
= appinfo_get_section(info
, "Summary"))))
2209 str
= xmlNodeListGetString(node
->doc
,
2210 node
->xmlChildrenNode
, 1);
2213 g_string_append(tip
, str
);
2214 g_string_append_c(tip
, '\n');
2222 g_string_truncate(tip
, tip
->len
- 1);
2223 show_tooltip(tip
->str
);
2226 g_string_free(tip
, TRUE
);
2231 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
2232 * NULL, close any current tooltip.
2234 static void show_tooltip(guchar
*text
)
2242 gtk_widget_destroy(tip_widget
);
2250 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
2251 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
2252 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
2254 gtk_signal_connect_object(GTK_OBJECT(tip_widget
), "expose_event",
2255 GTK_SIGNAL_FUNC(filer_tooltip_draw
),
2256 (GtkObject
*) tip_widget
);
2258 gtk_signal_connect_object(GTK_OBJECT(tip_widget
), "draw",
2259 GTK_SIGNAL_FUNC(filer_tooltip_draw
),
2260 (GtkObject
*) tip_widget
);
2263 label
= gtk_label_new(text
);
2264 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
2265 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
2266 gtk_widget_show(label
);
2267 gtk_widget_realize(tip_widget
);
2269 w
= tip_widget
->allocation
.width
;
2270 h
= tip_widget
->allocation
.height
;
2271 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
2274 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
2276 /* Now check for screen boundaries */
2277 x
= CLAMP(x
, 0, screen_width
- w
);
2278 y
= CLAMP(y
, 0, screen_height
- h
);
2280 /* And again test if pointer is over the tooltip window */
2281 if (py
>= y
&& py
<= y
+ h
)
2283 gtk_widget_set_uposition(tip_widget
, x
, y
);
2284 gtk_widget_show(tip_widget
);
2286 gtk_signal_connect_object(GTK_OBJECT(tip_widget
), "destroy",
2287 GTK_SIGNAL_FUNC(tip_destroyed
), NULL
);
2291 /* Display a tooltip for 'item' after a while (if item is not NULL).
2292 * Cancel any previous tooltip.
2294 static void filer_tooltip_prime(FilerWindow
*filer_window
, DirItem
*item
)
2300 if (item
== tip_item
)
2305 gtk_timeout_remove(tip_timeout
);
2311 gtk_widget_destroy(tip_widget
);
2316 if (filer_window
&& item
)
2318 int delay
= now
- tip_time
> 2 ? 1000 : 200;
2320 tip_timeout
= gtk_timeout_add(delay
,
2321 (GtkFunction
) filer_tooltip_activate
,
2326 static void set_selection_state(FilerWindow
*filer_window
, gboolean normal
)
2328 GtkStateType old_state
;
2330 old_state
= filer_window
->selection_state
;
2331 filer_window
->selection_state
= normal
2332 ? GTK_STATE_SELECTED
: GTK_STATE_INSENSITIVE
;
2334 if (old_state
!= filer_window
->selection_state
2335 && filer_window
->collection
->number_selected
)
2336 gtk_widget_queue_draw(GTK_WIDGET(filer_window
->collection
));
2339 void filer_cancel_thumbnails(FilerWindow
*filer_window
)
2341 gtk_widget_hide(filer_window
->thumb_bar
);
2343 g_list_foreach(filer_window
->thumb_queue
, (GFunc
) g_free
, NULL
);
2344 g_list_free(filer_window
->thumb_queue
);
2345 filer_window
->thumb_queue
= NULL
;
2346 filer_window
->max_thumbs
= 0;
2349 /* Generate the next thumb for this window. The collection object is
2350 * unref'd when there is nothing more to do.
2351 * If the collection no longer has a filer window, nothing is done.
2353 static gboolean
filer_next_thumb_real(GtkObject
*window
)
2355 FilerWindow
*filer_window
;
2359 filer_window
= gtk_object_get_data(window
, "filer_window");
2363 gtk_object_unref(window
);
2367 if (!filer_window
->thumb_queue
)
2369 filer_cancel_thumbnails(filer_window
);
2370 gtk_object_unref(window
);
2374 total
= filer_window
->max_thumbs
;
2375 done
= total
- g_list_length(filer_window
->thumb_queue
);
2377 path
= (gchar
*) filer_window
->thumb_queue
->data
;
2379 pixmap_background_thumb(path
, (GFunc
) filer_next_thumb
, window
);
2381 filer_window
->thumb_queue
= g_list_remove(filer_window
->thumb_queue
,
2385 gtk_progress_set_percentage(GTK_PROGRESS(filer_window
->thumb_progress
),
2386 done
/ (float) total
);
2391 /* path is the thumb just loaded, if any.
2392 * collection is unref'd (eventually).
2394 static void filer_next_thumb(GtkObject
*window
, gchar
*path
)
2397 dir_force_update_path(path
);
2399 gtk_idle_add((GtkFunction
) filer_next_thumb_real
, window
);
2402 static void start_thumb_scanning(FilerWindow
*filer_window
)
2404 if (GTK_WIDGET_VISIBLE(filer_window
->thumb_bar
))
2405 return; /* Already scanning */
2407 gtk_widget_show_all(filer_window
->thumb_bar
);
2409 gtk_object_ref(GTK_OBJECT(filer_window
->window
));
2410 filer_next_thumb(GTK_OBJECT(filer_window
->window
), NULL
);
2413 /* Set this image to be loaded some time in the future */
2414 void filer_create_thumb(FilerWindow
*filer_window
, gchar
*path
)
2416 filer_window
->max_thumbs
++;
2418 filer_window
->thumb_queue
= g_list_append(filer_window
->thumb_queue
,
2421 if (filer_window
->scanning
)
2422 return; /* Will start when scan ends */
2424 start_thumb_scanning(filer_window
);
2427 /* If thumbnail display is on, look through all the items in this directory
2428 * and start creating or updating the thumbnails as needed.
2430 void filer_create_thumbs(FilerWindow
*filer_window
)
2432 Collection
*collection
= filer_window
->collection
;
2435 if (!filer_window
->show_thumbs
)
2438 for (i
= 0; i
< collection
->number_of_items
; i
++)
2440 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
2444 if (item
->base_type
!= TYPE_FILE
)
2447 if (strcmp(item
->mime_type
->media_type
, "image") != 0)
2450 path
= make_path(filer_window
->path
, item
->leafname
)->str
;
2452 g_fscache_lookup_full(pixmap_cache
, path
,
2453 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
2455 /* If we didn't get an image, it could be because:
2457 * - We're loading the image now. found is TRUE,
2458 * and we'll update the item later.
2459 * - We tried to load the image and failed. found
2461 * - We haven't tried loading the image. found is
2462 * FALSE, and we start creating the thumb here.
2465 filer_create_thumb(filer_window
, path
);