4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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 */
36 #include <gdk/gdkkeysyms.h>
37 #include "collection.h"
43 #include "gui_support.h"
53 #include "minibuffer.h"
58 #define PANEL_BORDER 2
60 FilerWindow
*window_with_focus
= NULL
;
61 GList
*all_filer_windows
= NULL
;
63 static FilerWindow
*window_with_selection
= NULL
;
65 /* Static prototypes */
66 static void attach(FilerWindow
*filer_window
);
67 static void detach(FilerWindow
*filer_window
);
68 static void filer_window_destroyed(GtkWidget
*widget
,
69 FilerWindow
*filer_window
);
70 static gint
focus_in(GtkWidget
*widget
,
72 FilerWindow
*filer_window
);
73 static void add_item(FilerWindow
*filer_window
, DirItem
*item
);
74 static void update_display(Directory
*dir
,
77 FilerWindow
*filer_window
);
78 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
);
79 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
);
80 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
);
81 static FilerWindow
*find_filer_window(char *path
, FilerWindow
*diff
);
82 static void filer_set_title(FilerWindow
*filer_window
);
83 static gint
coll_button_release(GtkWidget
*widget
,
84 GdkEventButton
*event
,
85 FilerWindow
*filer_window
);
86 static gint
coll_button_press(GtkWidget
*widget
,
87 GdkEventButton
*event
,
88 FilerWindow
*filer_window
);
89 static gint
coll_motion_notify(GtkWidget
*widget
,
90 GdkEventMotion
*event
,
91 FilerWindow
*filer_window
);
92 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
);
93 static void filer_add_widgets(FilerWindow
*filer_window
);
94 static void filer_add_signals(FilerWindow
*filer_window
);
96 static void set_unique(guchar
*unique
);
98 static GdkAtom xa_string
;
100 static GdkCursor
*busy_cursor
= NULL
;
101 static GdkCursor
*crosshair
= NULL
;
103 gboolean o_unique_filer_windows
= FALSE
;
105 void filer_init(void)
107 xa_string
= gdk_atom_intern("STRING", FALSE
);
109 option_add_int("filer_unique_windows", o_unique_filer_windows
,
112 busy_cursor
= gdk_cursor_new(GDK_WATCH
);
113 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
116 static void set_unique(guchar
*unique
)
118 o_unique_filer_windows
= atoi(unique
);
121 static gboolean
if_deleted(gpointer item
, gpointer removed
)
123 int i
= ((GPtrArray
*) removed
)->len
;
124 DirItem
**r
= (DirItem
**) ((GPtrArray
*) removed
)->pdata
;
125 char *leafname
= ((DirItem
*) item
)->leafname
;
129 if (strcmp(leafname
, r
[i
]->leafname
) == 0)
136 static void update_item(FilerWindow
*filer_window
, DirItem
*item
)
139 char *leafname
= item
->leafname
;
141 if (leafname
[0] == '.' && filer_window
->show_hidden
== FALSE
)
144 i
= collection_find_item(filer_window
->collection
, item
, dir_item_cmp
);
147 collection_draw_item(filer_window
->collection
, i
, TRUE
);
149 g_warning("Failed to find '%s'\n", item
->leafname
);
152 /* Resize the filer window to w x h icons (not clamped) */
153 static void filer_window_set_size(FilerWindow
*filer_window
, int w
, int h
)
155 g_return_if_fail(filer_window
!= NULL
);
157 w
= filer_window
->collection
->item_width
* MAX(w
, 1);
158 h
= filer_window
->collection
->item_height
* MAX(h
, 1);
160 if (filer_window
->scrollbar
)
161 w
+= filer_window
->scrollbar
->allocation
.width
;
163 if (o_toolbar
!= TOOLBAR_NONE
)
164 h
+= filer_window
->toolbar_frame
->allocation
.height
;
166 if (GTK_WIDGET_VISIBLE(filer_window
->window
))
168 GtkRequisition
*req
= &filer_window
->window
->requisition
;
170 gdk_window_resize(filer_window
->window
->window
,
172 MAX(req
->height
, h
));
175 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
179 void filer_window_autosize(FilerWindow
*filer_window
)
181 Collection
*collection
= filer_window
->collection
;
182 int n
= collection
->number_of_items
;
185 int w
= collection
->item_width
;
186 int h
= collection
->item_height
;
191 if (o_toolbar
!= TOOLBAR_NONE
)
192 t
= filer_window
->toolbar_frame
->allocation
.height
;
194 /* Include items that are about to be added... */
195 if (filer_window
->scanning
)
196 n
+= filer_window
->directory
->new_items
->len
;
200 max_x
= (3 * screen_width
) / 4;
201 max_rows
= (3 * screen_height
) / (h
* 4);
203 /* Aim for a size where
204 * x = r(y + t + h), (1)
205 * unless that's too wide.
208 * r = desired (width / height) ratio
210 * Want to display all items:
213 * => x(x/r - t - h) = nwh (from 1)
214 * => xx - x.rt - hr(1 - nw) = 0
215 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
219 * sqrt(rt.rt + ...) > rt
221 * So, the +/- must be +:
223 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
225 * ( + w - 1 to round up)
227 x
= (r
* t
+ sqrt(r
*r
*t
*t
+ 4*h
*r
* (n
*w
- 1))) / 2 + w
- 1;
236 /* Choose rows to display all items given our chosen x.
237 * Don't make the window *too* big!
239 rows
= (n
+ cols
- 1) / cols
;
243 filer_window_set_size(filer_window
, cols
, rows
+ 1);
246 /* Called on a timeout while scanning or when scanning ends
247 * (whichever happens first).
249 static gint
open_filer_window(FilerWindow
*filer_window
)
251 if (filer_window
->open_timeout
)
253 gtk_timeout_remove(filer_window
->open_timeout
);
254 filer_window
->open_timeout
= 0;
257 if (!GTK_WIDGET_VISIBLE(filer_window
->window
))
259 filer_window_autosize(filer_window
);
260 gtk_widget_show(filer_window
->window
);
266 static void update_display(Directory
*dir
,
269 FilerWindow
*filer_window
)
273 int cursor
= filer_window
->collection
->cursor_item
;
275 Collection
*collection
= filer_window
->collection
;
280 as
= filer_window
->auto_select
;
282 old_num
= collection
->number_of_items
;
283 for (i
= 0; i
< items
->len
; i
++)
285 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
287 add_item(filer_window
, item
);
289 if (cursor
!= -1 || !as
)
292 if (strcmp(as
, item
->leafname
) != 0)
295 cursor
= collection
->number_of_items
- 1;
296 if (filer_window
->had_cursor
)
298 collection_set_cursor_item(collection
,
300 filer_window
->mini_cursor_base
= cursor
;
303 collection_wink_item(collection
,
307 if (old_num
!= collection
->number_of_items
)
308 collection_qsort(filer_window
->collection
,
309 filer_window
->sort_fn
);
312 collection_delete_if(filer_window
->collection
,
317 set_scanning_display(filer_window
, TRUE
);
320 if (filer_window
->window
->window
)
321 gdk_window_set_cursor(
322 filer_window
->window
->window
,
324 shrink_width(filer_window
);
325 if (filer_window
->had_cursor
&&
326 collection
->cursor_item
== -1)
328 collection_set_cursor_item(collection
, 0);
329 filer_window
->had_cursor
= FALSE
;
331 set_scanning_display(filer_window
, FALSE
);
332 open_filer_window(filer_window
);
335 for (i
= 0; i
< items
->len
; i
++)
337 DirItem
*item
= (DirItem
*) items
->pdata
[i
];
339 update_item(filer_window
, item
);
341 collection_qsort(filer_window
->collection
,
342 filer_window
->sort_fn
);
347 static void attach(FilerWindow
*filer_window
)
349 gdk_window_set_cursor(filer_window
->window
->window
, busy_cursor
);
350 collection_clear(filer_window
->collection
);
351 filer_window
->scanning
= TRUE
;
352 dir_attach(filer_window
->directory
, (DirCallback
) update_display
,
354 filer_set_title(filer_window
);
357 static void detach(FilerWindow
*filer_window
)
359 g_return_if_fail(filer_window
->directory
!= NULL
);
361 dir_detach(filer_window
->directory
,
362 (DirCallback
) update_display
, filer_window
);
363 g_fscache_data_unref(dir_cache
, filer_window
->directory
);
364 filer_window
->directory
= NULL
;
367 static void filer_window_destroyed(GtkWidget
*widget
,
368 FilerWindow
*filer_window
)
370 all_filer_windows
= g_list_remove(all_filer_windows
, filer_window
);
372 if (window_with_selection
== filer_window
)
373 window_with_selection
= NULL
;
375 if (window_with_focus
== filer_window
)
378 gtk_menu_popdown(GTK_MENU(popup_menu
));
379 window_with_focus
= NULL
;
382 if (filer_window
->directory
)
383 detach(filer_window
);
385 if (filer_window
->open_timeout
)
387 gtk_timeout_remove(filer_window
->open_timeout
);
388 filer_window
->open_timeout
= 0;
391 g_free(filer_window
->auto_select
);
392 g_free(filer_window
->path
);
393 g_free(filer_window
);
395 if (--number_of_windows
< 1)
399 /* Add a single object to a directory display */
400 static void add_item(FilerWindow
*filer_window
, DirItem
*item
)
402 char *leafname
= item
->leafname
;
405 if (leafname
[0] == '.')
407 if (filer_window
->show_hidden
== FALSE
|| leafname
[1] == '\0'
408 || (leafname
[1] == '.' && leafname
[2] == '\0'))
412 item_width
= calc_width(filer_window
, item
);
413 if (item_width
> filer_window
->collection
->item_width
)
414 collection_set_item_size(filer_window
->collection
,
416 filer_window
->collection
->item_height
);
417 collection_insert(filer_window
->collection
, item
);
420 /* Returns TRUE iff the directory still exists. */
421 static gboolean
may_rescan(FilerWindow
*filer_window
, gboolean warning
)
425 g_return_val_if_fail(filer_window
!= NULL
, FALSE
);
427 /* We do a fresh lookup (rather than update) because the inode may
430 dir
= g_fscache_lookup(dir_cache
, filer_window
->path
);
434 delayed_error(PROJECT
, _("Directory missing/deleted"));
435 gtk_widget_destroy(filer_window
->window
);
438 if (dir
== filer_window
->directory
)
439 g_fscache_data_unref(dir_cache
, dir
);
442 detach(filer_window
);
443 filer_window
->directory
= dir
;
444 attach(filer_window
);
450 /* Another app has grabbed the selection */
451 static gint
collection_lose_selection(GtkWidget
*widget
,
452 GdkEventSelection
*event
)
454 if (window_with_selection
&&
455 window_with_selection
->collection
== COLLECTION(widget
))
457 FilerWindow
*filer_window
= window_with_selection
;
458 window_with_selection
= NULL
;
459 collection_clear_selection(filer_window
->collection
);
465 /* Someone wants us to send them the selection */
466 static void selection_get(GtkWidget
*widget
,
467 GtkSelectionData
*selection_data
,
472 GString
*reply
, *header
;
473 FilerWindow
*filer_window
;
475 Collection
*collection
;
477 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
479 reply
= g_string_new(NULL
);
480 header
= g_string_new(NULL
);
485 g_string_sprintf(header
, " %s",
486 make_path(filer_window
->path
, "")->str
);
488 case TARGET_URI_LIST
:
489 g_string_sprintf(header
, " file://%s%s",
491 make_path(filer_window
->path
, "")->str
);
495 collection
= filer_window
->collection
;
496 for (i
= 0; i
< collection
->number_of_items
; i
++)
498 if (collection
->items
[i
].selected
)
501 (DirItem
*) collection
->items
[i
].data
;
503 g_string_append(reply
, header
->str
);
504 g_string_append(reply
, item
->leafname
);
507 /* This works, but I don't think I like it... */
508 /* g_string_append_c(reply, ' '); */
510 gtk_selection_data_set(selection_data
, xa_string
,
511 8, reply
->str
+ 1, reply
->len
- 1);
512 g_string_free(reply
, TRUE
);
513 g_string_free(header
, TRUE
);
516 /* No items are now selected. This might be because another app claimed
517 * the selection or because the user unselected all the items.
519 static void lose_selection(Collection
*collection
,
523 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
525 if (window_with_selection
== filer_window
)
527 window_with_selection
= NULL
;
528 gtk_selection_owner_set(NULL
,
529 GDK_SELECTION_PRIMARY
,
534 static void gain_selection(Collection
*collection
,
538 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
540 if (gtk_selection_owner_set(GTK_WIDGET(collection
),
541 GDK_SELECTION_PRIMARY
,
544 window_with_selection
= filer_window
;
547 collection_clear_selection(filer_window
->collection
);
550 /* Open the item (or add it to the shell command minibuffer) */
551 void filer_openitem(FilerWindow
*filer_window
, int item_number
, OpenFlags flags
)
553 gboolean shift
= (flags
& OPEN_SHIFT
) != 0;
554 gboolean close_mini
= flags
& OPEN_FROM_MINI
;
555 gboolean close_window
= (flags
& OPEN_CLOSE_WINDOW
) != 0;
557 DirItem
*item
= (DirItem
*)
558 filer_window
->collection
->items
[item_number
].data
;
560 gboolean wink
= TRUE
;
563 widget
= filer_window
->window
;
564 if (filer_window
->mini_type
== MINI_SHELL
)
566 minibuffer_add(filer_window
, item
->leafname
);
570 if (item
->base_type
== TYPE_DIRECTORY
)
572 /* Never close a filer window when opening a directory
573 * (click on a dir or click on an app with shift).
575 if (shift
|| !(item
->flags
& ITEM_FLAG_APPDIR
))
576 close_window
= FALSE
;
579 full_path
= make_path(filer_window
->path
, item
->leafname
)->str
;
580 if (shift
&& (item
->flags
& ITEM_FLAG_SYMLINK
))
583 old_dir
= filer_window
->directory
;
584 if (run_diritem(full_path
, item
,
585 flags
& OPEN_SAME_WINDOW
? filer_window
: NULL
,
588 if (old_dir
!= filer_window
->directory
)
592 gtk_widget_destroy(filer_window
->window
);
596 collection_wink_item(filer_window
->collection
,
599 minibuffer_hide(filer_window
);
604 static gint
pointer_in(GtkWidget
*widget
,
605 GdkEventCrossing
*event
,
606 FilerWindow
*filer_window
)
608 may_rescan(filer_window
, TRUE
);
612 static gint
focus_in(GtkWidget
*widget
,
613 GdkEventFocus
*event
,
614 FilerWindow
*filer_window
)
616 window_with_focus
= filer_window
;
621 /* Move the cursor to the next selected item in direction 'dir'
624 static void next_selected(FilerWindow
*filer_window
, int dir
)
626 Collection
*collection
= filer_window
->collection
;
627 int to_check
= collection
->number_of_items
;
628 int item
= collection
->cursor_item
;
630 g_return_if_fail(dir
== 1 || dir
== -1);
632 if (to_check
> 0 && item
== -1)
634 /* Cursor not currently on */
638 item
= collection
->number_of_items
- 1;
640 if (collection
->items
[item
].selected
)
644 while (--to_check
> 0)
648 if (item
>= collection
->number_of_items
)
651 item
= collection
->number_of_items
- 1;
653 if (collection
->items
[item
].selected
)
660 collection_set_cursor_item(collection
, item
);
663 static void return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
665 Collection
*collection
= filer_window
->collection
;
666 int item
= collection
->cursor_item
;
667 TargetFunc cb
= filer_window
->target_cb
;
668 gpointer data
= filer_window
->target_data
;
669 OpenFlags flags
= OPEN_SAME_WINDOW
;
671 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
672 if (item
< 0 || item
>= collection
->number_of_items
)
677 cb(filer_window
, item
, data
);
681 if (event
->state
& GDK_SHIFT_MASK
)
684 filer_openitem(filer_window
, item
, flags
);
687 /* Handle keys that can't be bound with the menu */
688 static gint
key_press_event(GtkWidget
*widget
,
690 FilerWindow
*filer_window
)
692 switch (event
->keyval
)
695 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
698 return_pressed(filer_window
, event
);
700 case GDK_ISO_Left_Tab
:
701 next_selected(filer_window
, -1);
704 next_selected(filer_window
, 1);
707 change_to_parent(filer_window
);
713 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget
), "key_press_event");
717 void change_to_parent(FilerWindow
*filer_window
)
722 if (filer_window
->path
[0] == '/' && filer_window
->path
[1] == '\0')
723 return; /* Already in the root */
725 copy
= g_strdup(filer_window
->path
);
726 slash
= strrchr(copy
, '/');
731 filer_change_to(filer_window
,
736 g_warning("No / in directory path!\n");
742 /* Make filer_window display path. When finished, highlight item 'from', or
743 * the first item if from is NULL. If there is currently no cursor then
744 * simply wink 'from' (if not NULL).
746 void filer_change_to(FilerWindow
*filer_window
, char *path
, char *from
)
752 g_return_if_fail(filer_window
!= NULL
);
754 real_path
= pathdup(path
);
755 new_dir
= g_fscache_lookup(dir_cache
, real_path
);
761 error
= g_strdup_printf(_("Directory '%s' is not accessible"),
764 delayed_error(PROJECT
, error
);
769 if (o_unique_filer_windows
)
773 fw
= find_filer_window(real_path
, filer_window
);
775 gtk_widget_destroy(fw
->window
);
778 from_dup
= from
&& *from
? g_strdup(from
) : NULL
;
780 detach(filer_window
);
781 g_free(filer_window
->path
);
782 filer_window
->path
= real_path
;
784 filer_window
->directory
= new_dir
;
786 g_free(filer_window
->auto_select
);
787 filer_window
->had_cursor
= filer_window
->collection
->cursor_item
!= -1
788 || filer_window
->had_cursor
;
789 filer_window
->auto_select
= from_dup
;
791 filer_set_title(filer_window
);
792 collection_set_cursor_item(filer_window
->collection
, -1);
793 attach(filer_window
);
795 if (filer_window
->mini_type
== MINI_PATH
)
796 gtk_idle_add((GtkFunction
) minibuffer_show_cb
,
800 void filer_open_parent(FilerWindow
*filer_window
)
805 if (filer_window
->path
[0] == '/' && filer_window
->path
[1] == '\0')
806 return; /* Already in the root */
808 copy
= g_strdup(filer_window
->path
);
809 slash
= strrchr(copy
, '/');
814 filer_opendir(*copy
? copy
: "/");
817 g_warning("No / in directory path!\n");
822 int selected_item_number(Collection
*collection
)
826 g_return_val_if_fail(collection
!= NULL
, -1);
827 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
828 g_return_val_if_fail(collection
->number_selected
== 1, -1);
830 for (i
= 0; i
< collection
->number_of_items
; i
++)
831 if (collection
->items
[i
].selected
)
834 g_warning("selected_item: number_selected is wrong\n");
839 DirItem
*selected_item(Collection
*collection
)
843 item
= selected_item_number(collection
);
846 return (DirItem
*) collection
->items
[item
].data
;
850 /* Append all the URIs in the selection to the string */
851 static void create_uri_list(FilerWindow
*filer_window
, GString
*string
)
853 Collection
*collection
= filer_window
->collection
;
857 leader
= g_string_new("file://");
858 g_string_append(leader
, our_host_name());
859 g_string_append(leader
, filer_window
->path
);
860 if (leader
->str
[leader
->len
- 1] != '/')
861 g_string_append_c(leader
, '/');
863 num_selected
= collection
->number_selected
;
865 for (i
= 0; num_selected
> 0; i
++)
867 if (collection
->items
[i
].selected
)
869 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
871 g_string_append(string
, leader
->str
);
872 g_string_append(string
, item
->leafname
);
873 g_string_append(string
, "\r\n");
878 g_string_free(leader
, TRUE
);
881 /* Creates and shows a new filer window.
882 * Returns the new filer window, or NULL on error.
884 FilerWindow
*filer_opendir(char *path
)
886 FilerWindow
*filer_window
;
890 /* Get the real pathname of the directory and copy it */
891 real_path
= pathdup(path
);
893 filer_window
= g_new(FilerWindow
, 1);
894 filer_window
->minibuffer
= NULL
;
895 filer_window
->minibuffer_label
= NULL
;
896 filer_window
->minibuffer_area
= NULL
;
897 filer_window
->temp_show_hidden
= FALSE
;
898 filer_window
->path
= real_path
;
899 filer_window
->scanning
= FALSE
;
900 filer_window
->had_cursor
= FALSE
;
901 filer_window
->auto_select
= NULL
;
902 filer_window
->toolbar_text
= NULL
;
903 filer_window
->target_cb
= NULL
;
904 filer_window
->mini_type
= MINI_NONE
;
906 /* Finds the entry for this directory in the dir cache, creating
907 * a new one if needed. This does not cause a scan to start,
908 * so if a new entry is created then it will be empty.
910 filer_window
->directory
= g_fscache_lookup(dir_cache
,
912 if (!filer_window
->directory
)
916 error
= g_strdup_printf(_("Directory '%s' not found."), path
);
917 delayed_error(PROJECT
, error
);
919 g_free(filer_window
->path
);
920 g_free(filer_window
);
924 filer_window
->show_hidden
= FALSE
;
925 filer_window
->temp_item_selected
= FALSE
;
927 i
= option_get_int("display_sort_by");
928 filer_window
->sort_fn
= i
== 0 ? sort_by_name
:
929 i
== 1 ? sort_by_type
:
930 i
== 2 ? sort_by_date
:
933 filer_window
->flags
= (FilerFlags
) 0;
934 filer_window
->details_type
= DETAILS_SUMMARY
;
935 filer_window
->display_style
= UNKNOWN_STYLE
;
937 /* Add all the user-interface elements & realise */
938 filer_add_widgets(filer_window
);
940 /* Connect to all the signal handlers */
941 filer_add_signals(filer_window
);
943 display_set_layout(filer_window
,
944 option_get_int("display_size"),
945 option_get_int("display_details"));
947 /* Open the window after a timeout, or when scanning stops.
948 * Do this before attaching, because attach() might tell us to
949 * stop scanning (if a scan isn't needed).
951 filer_window
->open_timeout
= gtk_timeout_add(500,
952 (GtkFunction
) open_filer_window
,
955 /* The collection is created empty and then attach() is called, which
956 * links the filer window to the entry in the directory cache we
957 * looked up / created above.
959 * The attach() function will immediately callback to the filer window
960 * to deliver a list of all known entries in the directory (so,
961 * collection->number_of_items may be valid after the call to
964 * BUT, if the directory was not in the cache (because it hadn't been
965 * opened it before) then the cached dir will be empty and nothing gets
966 * added until a while later when some entries are actually available.
969 attach(filer_window
);
971 /* Make the window visible. Update number_of_windows BEFORE destroying
976 /* If the user doesn't want duplicate windows then check
977 * for an existing one and close it if found.
980 if (o_unique_filer_windows
)
984 fw
= find_filer_window(filer_window
->path
, NULL
);
987 gtk_widget_destroy(fw
->window
);
990 all_filer_windows
= g_list_prepend(all_filer_windows
, filer_window
);
995 /* This adds all the widgets to a new filer window. It is in a separate
996 * function because filer_opendir() was getting too long...
998 static void filer_add_widgets(FilerWindow
*filer_window
)
1000 GtkWidget
*hbox
, *vbox
, *collection
;
1002 /* Create the top-level window widget */
1003 filer_window
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1004 filer_set_title(filer_window
);
1006 /* The collection is the area that actually displays the files */
1007 collection
= collection_new(NULL
);
1009 gtk_object_set_data(GTK_OBJECT(collection
),
1010 "filer_window", filer_window
);
1011 filer_window
->collection
= COLLECTION(collection
);
1013 /* Scrollbar on the right, everything else on the left */
1014 hbox
= gtk_hbox_new(FALSE
, 0);
1015 gtk_container_add(GTK_CONTAINER(filer_window
->window
), hbox
);
1017 vbox
= gtk_vbox_new(FALSE
, 0);
1018 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, TRUE
, TRUE
, 0);
1020 /* If there's a message that should go at the top of every
1021 * window (eg 'Running as root'), add it here.
1023 if (show_user_message
)
1027 label
= gtk_label_new(show_user_message
);
1028 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, TRUE
, 0);
1029 gtk_widget_show(label
);
1032 /* Create a frame for the toolbar, but don't show it unless we actually
1034 * (allows us to change the toolbar later)
1036 filer_window
->toolbar_frame
= gtk_frame_new(NULL
);
1037 gtk_frame_set_shadow_type(GTK_FRAME(filer_window
->toolbar_frame
),
1039 gtk_box_pack_start(GTK_BOX(vbox
),
1040 filer_window
->toolbar_frame
, FALSE
, TRUE
, 0);
1042 /* If we want a toolbar, create it and put it in the frame */
1043 if (o_toolbar
!= TOOLBAR_NONE
)
1047 toolbar
= toolbar_new(filer_window
);
1048 gtk_container_add(GTK_CONTAINER(filer_window
->toolbar_frame
),
1050 gtk_widget_show_all(filer_window
->toolbar_frame
);
1053 /* Now add the area for displaying the files... */
1054 gtk_box_pack_start(GTK_BOX(vbox
), collection
, TRUE
, TRUE
, 0);
1056 /* And the minibuffer (hidden by default)... */
1057 create_minibuffer(filer_window
);
1058 gtk_box_pack_start(GTK_BOX(vbox
), filer_window
->minibuffer_area
,
1061 /* Put the scrollbar on the left of everything else... */
1062 filer_window
->scrollbar
=
1063 gtk_vscrollbar_new(COLLECTION(collection
)->vadj
);
1064 gtk_box_pack_start(GTK_BOX(hbox
),
1065 filer_window
->scrollbar
, FALSE
, TRUE
, 0);
1067 /* Connect the menu's accelerator group to the window */
1068 gtk_accel_group_attach(filer_keys
, GTK_OBJECT(filer_window
->window
));
1070 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
), collection
);
1072 gtk_widget_show(hbox
);
1073 gtk_widget_show(vbox
);
1074 gtk_widget_show(filer_window
->scrollbar
);
1075 gtk_widget_show(collection
);
1077 gtk_widget_realize(filer_window
->window
);
1079 filer_window_set_size(filer_window
, 4, 4);
1082 static void filer_add_signals(FilerWindow
*filer_window
)
1084 GtkObject
*collection
= GTK_OBJECT(filer_window
->collection
);
1085 GtkTargetEntry target_table
[] =
1087 {"text/uri-list", 0, TARGET_URI_LIST
},
1088 {"STRING", 0, TARGET_STRING
},
1091 /* Events on the top-level window */
1092 gtk_widget_add_events(filer_window
->window
, GDK_ENTER_NOTIFY
);
1093 gtk_signal_connect(GTK_OBJECT(filer_window
->window
),
1094 "enter-notify-event",
1095 GTK_SIGNAL_FUNC(pointer_in
), filer_window
);
1096 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "focus_in_event",
1097 GTK_SIGNAL_FUNC(focus_in
), filer_window
);
1098 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "destroy",
1099 filer_window_destroyed
, filer_window
);
1101 /* Events on the collection widget */
1102 gtk_widget_set_events(GTK_WIDGET(collection
),
1103 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
1104 GDK_BUTTON3_MOTION_MASK
|
1105 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
);
1107 gtk_signal_connect(collection
, "gain_selection",
1108 gain_selection
, filer_window
);
1109 gtk_signal_connect(collection
, "lose_selection",
1110 lose_selection
, filer_window
);
1111 gtk_signal_connect(collection
, "selection_clear_event",
1112 GTK_SIGNAL_FUNC(collection_lose_selection
), NULL
);
1113 gtk_signal_connect(collection
, "selection_get",
1114 GTK_SIGNAL_FUNC(selection_get
), NULL
);
1115 gtk_selection_add_targets(GTK_WIDGET(collection
), GDK_SELECTION_PRIMARY
,
1117 sizeof(target_table
) / sizeof(*target_table
));
1119 gtk_signal_connect(collection
, "key_press_event",
1120 GTK_SIGNAL_FUNC(key_press_event
), filer_window
);
1121 gtk_signal_connect(collection
, "button-release-event",
1122 GTK_SIGNAL_FUNC(coll_button_release
), filer_window
);
1123 gtk_signal_connect(collection
, "button-press-event",
1124 GTK_SIGNAL_FUNC(coll_button_press
), filer_window
);
1125 gtk_signal_connect(collection
, "motion-notify-event",
1126 GTK_SIGNAL_FUNC(coll_motion_notify
), filer_window
);
1128 /* Drag and drop events */
1129 gtk_signal_connect(collection
, "drag_data_get", drag_data_get
, NULL
);
1130 drag_set_dest(filer_window
);
1133 static gint
clear_scanning_display(FilerWindow
*filer_window
)
1135 if (filer_exists(filer_window
))
1136 filer_set_title(filer_window
);
1140 static void set_scanning_display(FilerWindow
*filer_window
, gboolean scanning
)
1142 if (scanning
== filer_window
->scanning
)
1144 filer_window
->scanning
= scanning
;
1147 filer_set_title(filer_window
);
1149 gtk_timeout_add(300, (GtkFunction
) clear_scanning_display
,
1153 /* Note that filer_window may not exist after this call. */
1154 void filer_update_dir(FilerWindow
*filer_window
, gboolean warning
)
1156 if (may_rescan(filer_window
, warning
))
1157 dir_update(filer_window
->directory
, filer_window
->path
);
1160 /* Refresh the various caches even if we don't think we need to */
1161 void full_refresh(void)
1166 /* See whether a filer window with a given path already exists
1167 * and is different from diff.
1169 static FilerWindow
*find_filer_window(char *path
, FilerWindow
*diff
)
1171 GList
*next
= all_filer_windows
;
1175 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1177 if (filer_window
!= diff
&&
1178 strcmp(path
, filer_window
->path
) == 0)
1180 return filer_window
;
1189 /* This path has been mounted/umounted/deleted some files - update all dirs */
1190 void filer_check_mounted(char *path
)
1192 GList
*next
= all_filer_windows
;
1200 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
1204 if (strncmp(path
, filer_window
->path
, len
) == 0)
1206 char s
= filer_window
->path
[len
];
1208 if (s
== '/' || s
== '\0')
1209 filer_update_dir(filer_window
, FALSE
);
1213 slash
= strrchr(path
, '/');
1214 if (slash
&& slash
!= path
)
1218 parent
= g_strndup(path
, slash
- path
);
1220 refresh_dirs(parent
);
1227 icons_may_update(path
);
1230 /* Like minibuffer_show(), except that:
1231 * - It returns FALSE (to be used from an idle callback)
1232 * - It checks that the filer window still exists.
1234 static gboolean
minibuffer_show_cb(FilerWindow
*filer_window
)
1236 if (filer_exists(filer_window
))
1237 minibuffer_show(filer_window
, MINI_PATH
);
1241 gboolean
filer_exists(FilerWindow
*filer_window
)
1245 for (next
= all_filer_windows
; next
; next
= next
->next
)
1247 FilerWindow
*fw
= (FilerWindow
*) next
->data
;
1249 if (fw
== filer_window
)
1256 static void filer_set_title(FilerWindow
*filer_window
)
1258 guchar
*title
= NULL
;
1259 guchar
*scanning
= filer_window
->scanning
? _(" (Scanning)") : "";
1261 if (home_dir_len
> 1 &&
1262 strncmp(filer_window
->path
, home_dir
, home_dir_len
) == 0)
1264 guchar sep
= filer_window
->path
[home_dir_len
];
1266 if (sep
== '\0' || sep
== '/')
1267 title
= g_strconcat("~",
1268 filer_window
->path
+ home_dir_len
,
1274 title
= g_strconcat(filer_window
->path
, scanning
, NULL
);
1276 gtk_window_set_title(GTK_WINDOW(filer_window
->window
), title
);
1280 /* Reconnect to the same directory (used when the Show Hidden option is
1283 void filer_detach_rescan(FilerWindow
*filer_window
)
1285 Directory
*dir
= filer_window
->directory
;
1287 g_fscache_data_ref(dir_cache
, dir
);
1288 detach(filer_window
);
1289 filer_window
->directory
= dir
;
1290 attach(filer_window
);
1293 static gint
coll_button_release(GtkWidget
*widget
,
1294 GdkEventButton
*event
,
1295 FilerWindow
*filer_window
)
1297 if (dnd_motion_release(event
))
1299 if (motion_buttons_pressed
== 0 &&
1300 filer_window
->collection
->lasso_box
)
1301 collection_end_lasso(filer_window
->collection
, TRUE
);
1305 perform_action(filer_window
, event
);
1310 static void perform_action(FilerWindow
*filer_window
, GdkEventButton
*event
)
1312 Collection
*collection
= filer_window
->collection
;
1316 gboolean press
= event
->type
== GDK_BUTTON_PRESS
;
1317 gboolean selected
= FALSE
;
1318 OpenFlags flags
= 0;
1320 if (event
->button
> 3)
1323 item
= collection_get_item(collection
, event
->x
, event
->y
);
1325 if (filer_window
->target_cb
)
1327 dnd_motion_ungrab();
1328 if (item
!= -1 && press
&& event
->button
== 1)
1329 filer_window
->target_cb(filer_window
, item
,
1330 filer_window
->target_data
);
1331 filer_target_mode(filer_window
, NULL
, NULL
, NULL
);
1336 action
= bind_lookup_bev(
1337 item
== -1 ? BIND_DIRECTORY
: BIND_DIRECTORY_ICON
,
1342 dir_item
= (DirItem
*) collection
->items
[item
].data
;
1343 selected
= collection
->items
[item
].selected
;
1350 case ACT_CLEAR_SELECTION
:
1351 collection_clear_selection(collection
);
1353 case ACT_TOGGLE_SELECTED
:
1354 collection_toggle_item(collection
, item
);
1356 case ACT_SELECT_EXCL
:
1357 collection_clear_except(collection
, item
);
1360 flags
|= OPEN_SHIFT
;
1363 if (event
->button
!= 1)
1364 flags
|= OPEN_CLOSE_WINDOW
;
1366 flags
|= OPEN_SAME_WINDOW
;
1367 if (o_new_window_on_1
)
1368 flags
^= OPEN_SAME_WINDOW
;
1369 if (event
->type
== GDK_2BUTTON_PRESS
)
1370 collection_unselect_item(collection
, item
);
1371 dnd_motion_ungrab();
1372 filer_openitem(filer_window
, item
, flags
);
1374 case ACT_POPUP_MENU
:
1375 dnd_motion_ungrab();
1376 show_filer_menu(filer_window
, event
, item
);
1378 case ACT_PRIME_AND_SELECT
:
1380 collection_clear_except(collection
, item
);
1381 dnd_motion_start(MOTION_READY_FOR_DND
);
1383 case ACT_PRIME_AND_TOGGLE
:
1384 collection_toggle_item(collection
, item
);
1385 dnd_motion_start(MOTION_READY_FOR_DND
);
1387 case ACT_PRIME_FOR_DND
:
1388 collection_wink_item(collection
, item
);
1389 dnd_motion_start(MOTION_READY_FOR_DND
);
1392 if (press
&& event
->button
< 4)
1395 collection_wink_item(collection
, item
);
1396 dnd_motion_start(MOTION_NONE
);
1399 case ACT_LASSO_CLEAR
:
1400 collection_clear_selection(collection
);
1402 case ACT_LASSO_MODIFY
:
1403 collection_lasso_box(collection
, event
->x
, event
->y
);
1406 g_warning("Unsupported action : %d\n", action
);
1411 static gint
coll_button_press(GtkWidget
*widget
,
1412 GdkEventButton
*event
,
1413 FilerWindow
*filer_window
)
1415 collection_set_cursor_item(filer_window
->collection
, -1);
1417 if (dnd_motion_press(widget
, event
))
1418 perform_action(filer_window
, event
);
1423 static gint
coll_motion_notify(GtkWidget
*widget
,
1424 GdkEventMotion
*event
,
1425 FilerWindow
*filer_window
)
1427 Collection
*collection
= filer_window
->collection
;
1430 if (motion_state
!= MOTION_READY_FOR_DND
)
1433 if (!dnd_motion_moved(event
))
1436 i
= collection_get_item(collection
,
1437 event
->x
- (event
->x_root
- drag_start_x
),
1438 event
->y
- (event
->y_root
- drag_start_y
));
1442 collection_wink_item(collection
, -1);
1444 if (!collection
->items
[i
].selected
)
1446 if (event
->state
& GDK_BUTTON1_MASK
)
1448 /* Select just this one */
1449 collection_clear_except(collection
, i
);
1450 filer_window
->temp_item_selected
= TRUE
;
1454 if (collection
->number_selected
== 0)
1455 filer_window
->temp_item_selected
= TRUE
;
1456 collection_select_item(collection
, i
);
1460 g_return_val_if_fail(collection
->number_selected
> 0, TRUE
);
1462 if (collection
->number_selected
== 1)
1464 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
1466 drag_one_item(widget
, event
,
1467 make_path(filer_window
->path
, item
->leafname
)->str
,
1474 uris
= g_string_new(NULL
);
1475 create_uri_list(filer_window
, uris
);
1476 drag_selection(widget
, event
, uris
->str
);
1477 g_string_free(uris
, TRUE
);
1483 /* Puts the filer window into target mode. When an item is chosen,
1484 * fn(filer_window, item, data) is called. 'reason' will be displayed
1485 * on the toolbar while target mode is active.
1487 * Use fn == NULL to cancel target mode.
1489 void filer_target_mode(FilerWindow
*filer_window
,
1494 if (fn
!= filer_window
->target_cb
)
1495 gdk_window_set_cursor(
1496 GTK_WIDGET(filer_window
->collection
)->window
,
1497 fn
? crosshair
: NULL
);
1499 filer_window
->target_cb
= fn
;
1500 filer_window
->target_data
= data
;
1502 if (filer_window
->toolbar_text
)
1503 gtk_label_set_text(GTK_LABEL(filer_window
->toolbar_text
),