r339: The menu key bindings are now only saved if they actually changed.
[rox-filer.git] / ROX-Filer / src / filer.c
blob0a49534c6a05fca178077291704c6287ab10baf3
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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 */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <time.h>
31 #include <ctype.h>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkx.h>
35 #include <gdk/gdkkeysyms.h>
36 #include "collection.h"
38 #include "global.h"
40 #include "main.h"
41 #include "support.h"
42 #include "gui_support.h"
43 #include "filer.h"
44 #include "pixmaps.h"
45 #include "menu.h"
46 #include "dnd.h"
47 #include "dir.h"
48 #include "run.h"
49 #include "mount.h"
50 #include "type.h"
51 #include "options.h"
52 #include "minibuffer.h"
53 #include "pinboard.h"
54 #include "toolbar.h"
56 #define PANEL_BORDER 2
58 extern int collection_menu_button;
59 extern gboolean collection_single_click;
61 FilerWindow *window_with_focus = NULL;
62 GList *all_filer_windows = NULL;
64 static FilerWindow *window_with_selection = NULL;
66 /* Options bits */
67 static GtkWidget *create_options();
68 static void update_options();
69 static void set_options();
70 static void save_options();
71 static char *filer_single_click(char *data);
72 static char *filer_unique_windows(char *data);
73 static char *filer_menu_on_2(char *data);
74 static char *filer_new_window_on_1(char *data);
76 static OptionsSection options =
78 N_("Filer window options"),
79 create_options,
80 update_options,
81 set_options,
82 save_options
85 gboolean o_single_click = TRUE;
86 gboolean o_new_window_on_1 = FALSE; /* Button 1 => New window */
87 gboolean o_unique_filer_windows = FALSE;
88 static GtkWidget *toggle_single_click;
89 static GtkWidget *toggle_new_window_on_1;
90 static GtkWidget *toggle_menu_on_2;
91 static GtkWidget *toggle_unique_filer_windows;
93 /* Static prototypes */
94 static void attach(FilerWindow *filer_window);
95 static void detach(FilerWindow *filer_window);
96 static void filer_window_destroyed(GtkWidget *widget,
97 FilerWindow *filer_window);
98 static void show_menu(Collection *collection, GdkEventButton *event,
99 int number_selected, gpointer user_data);
100 static gint focus_in(GtkWidget *widget,
101 GdkEventFocus *event,
102 FilerWindow *filer_window);
103 static gint focus_out(GtkWidget *widget,
104 GdkEventFocus *event,
105 FilerWindow *filer_window);
106 static void add_item(FilerWindow *filer_window, DirItem *item);
107 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
108 FilerWindow *window);
109 static void update_display(Directory *dir,
110 DirAction action,
111 GPtrArray *items,
112 FilerWindow *filer_window);
113 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
114 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
115 static void open_item(Collection *collection,
116 gpointer item_data, int item_number,
117 gpointer user_data);
118 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
119 static FilerWindow *find_filer_window(char *path, FilerWindow *diff);
120 static void filer_set_title(FilerWindow *filer_window);
122 static GdkAtom xa_string;
123 enum
125 TARGET_STRING,
126 TARGET_URI_LIST,
129 static GdkCursor *busy_cursor = NULL;
131 void filer_init()
133 xa_string = gdk_atom_intern("STRING", FALSE);
135 options_sections = g_slist_prepend(options_sections, &options);
136 option_register("filer_new_window_on_1", filer_new_window_on_1);
137 option_register("filer_menu_on_2", filer_menu_on_2);
138 option_register("filer_single_click", filer_single_click);
139 option_register("filer_unique_windows", filer_unique_windows);
141 busy_cursor = gdk_cursor_new(GDK_WATCH);
144 static gboolean if_deleted(gpointer item, gpointer removed)
146 int i = ((GPtrArray *) removed)->len;
147 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
148 char *leafname = ((DirItem *) item)->leafname;
150 while (i--)
152 if (strcmp(leafname, r[i]->leafname) == 0)
153 return TRUE;
156 return FALSE;
159 static void update_item(FilerWindow *filer_window, DirItem *item)
161 int i;
162 char *leafname = item->leafname;
164 if (leafname[0] == '.')
166 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
167 || (leafname[1] == '.' && leafname[2] == '\0'))
168 return;
171 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
173 if (i >= 0)
174 collection_draw_item(filer_window->collection, i, TRUE);
175 else
176 g_warning("Failed to find '%s'\n", item->leafname);
179 static void update_display(Directory *dir,
180 DirAction action,
181 GPtrArray *items,
182 FilerWindow *filer_window)
184 int old_num;
185 int i;
186 int cursor = filer_window->collection->cursor_item;
187 char *as;
188 Collection *collection = filer_window->collection;
190 switch (action)
192 case DIR_ADD:
193 as = filer_window->auto_select;
195 old_num = collection->number_of_items;
196 for (i = 0; i < items->len; i++)
198 DirItem *item = (DirItem *) items->pdata[i];
200 add_item(filer_window, item);
202 if (cursor != -1 || !as)
203 continue;
205 if (strcmp(as, item->leafname) != 0)
206 continue;
208 cursor = collection->number_of_items - 1;
209 if (filer_window->had_cursor)
211 collection_set_cursor_item(collection,
212 cursor);
213 filer_window->mini_cursor_base = cursor;
215 else
216 collection_wink_item(collection,
217 cursor);
220 if (old_num != collection->number_of_items)
221 collection_qsort(filer_window->collection,
222 filer_window->sort_fn);
223 break;
224 case DIR_REMOVE:
225 collection_delete_if(filer_window->collection,
226 if_deleted,
227 items);
228 break;
229 case DIR_START_SCAN:
230 set_scanning_display(filer_window, TRUE);
231 break;
232 case DIR_END_SCAN:
233 if (filer_window->window->window)
234 gdk_window_set_cursor(
235 filer_window->window->window,
236 NULL);
237 shrink_width(filer_window);
238 if (filer_window->had_cursor &&
239 collection->cursor_item == -1)
241 collection_set_cursor_item(collection, 0);
242 filer_window->had_cursor = FALSE;
244 set_scanning_display(filer_window, FALSE);
245 break;
246 case DIR_UPDATE:
247 for (i = 0; i < items->len; i++)
249 DirItem *item = (DirItem *) items->pdata[i];
251 update_item(filer_window, item);
253 collection_qsort(filer_window->collection,
254 filer_window->sort_fn);
255 break;
259 static void attach(FilerWindow *filer_window)
261 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
262 collection_clear(filer_window->collection);
263 filer_window->scanning = TRUE;
264 dir_attach(filer_window->directory, (DirCallback) update_display,
265 filer_window);
266 filer_set_title(filer_window);
269 static void detach(FilerWindow *filer_window)
271 g_return_if_fail(filer_window->directory != NULL);
273 dir_detach(filer_window->directory,
274 (DirCallback) update_display, filer_window);
275 g_fscache_data_unref(dir_cache, filer_window->directory);
276 filer_window->directory = NULL;
279 static void filer_window_destroyed(GtkWidget *widget,
280 FilerWindow *filer_window)
282 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
284 if (window_with_selection == filer_window)
285 window_with_selection = NULL;
287 if (window_with_focus == filer_window)
289 if (popup_menu)
290 gtk_menu_popdown(GTK_MENU(popup_menu));
291 window_with_focus = NULL;
294 if (filer_window->directory)
295 detach(filer_window);
297 g_free(filer_window->auto_select);
298 g_free(filer_window->path);
299 g_free(filer_window);
301 if (--number_of_windows < 1)
302 gtk_main_quit();
305 /* Add a single object to a directory display */
306 static void add_item(FilerWindow *filer_window, DirItem *item)
308 char *leafname = item->leafname;
309 int item_width;
311 if (leafname[0] == '.')
313 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
314 || (leafname[1] == '.' && leafname[2] == '\0'))
315 return;
318 item_width = calc_width(filer_window, item);
319 if (item_width > filer_window->collection->item_width)
320 collection_set_item_size(filer_window->collection,
321 item_width,
322 filer_window->collection->item_height);
323 collection_insert(filer_window->collection, item);
326 static void show_menu(Collection *collection, GdkEventButton *event,
327 int item, gpointer user_data)
329 show_filer_menu((FilerWindow *) user_data, event, item);
332 /* Returns TRUE iff the directory still exists. */
333 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
335 Directory *dir;
337 g_return_val_if_fail(filer_window != NULL, FALSE);
339 /* We do a fresh lookup (rather than update) because the inode may
340 * have changed.
342 dir = g_fscache_lookup(dir_cache, filer_window->path);
343 if (!dir)
345 if (warning)
346 delayed_error(PROJECT, _("Directory missing/deleted"));
347 gtk_widget_destroy(filer_window->window);
348 return FALSE;
350 if (dir == filer_window->directory)
351 g_fscache_data_unref(dir_cache, dir);
352 else
354 detach(filer_window);
355 filer_window->directory = dir;
356 attach(filer_window);
359 return TRUE;
362 /* Another app has grabbed the selection */
363 static gint collection_lose_selection(GtkWidget *widget,
364 GdkEventSelection *event)
366 if (window_with_selection &&
367 window_with_selection->collection == COLLECTION(widget))
369 FilerWindow *filer_window = window_with_selection;
370 window_with_selection = NULL;
371 collection_clear_selection(filer_window->collection);
374 return TRUE;
377 /* Someone wants us to send them the selection */
378 static void selection_get(GtkWidget *widget,
379 GtkSelectionData *selection_data,
380 guint info,
381 guint time,
382 gpointer data)
384 GString *reply, *header;
385 FilerWindow *filer_window;
386 int i;
387 Collection *collection;
389 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
391 reply = g_string_new(NULL);
392 header = g_string_new(NULL);
394 switch (info)
396 case TARGET_STRING:
397 g_string_sprintf(header, " %s",
398 make_path(filer_window->path, "")->str);
399 break;
400 case TARGET_URI_LIST:
401 g_string_sprintf(header, " file://%s%s",
402 our_host_name(),
403 make_path(filer_window->path, "")->str);
404 break;
407 collection = filer_window->collection;
408 for (i = 0; i < collection->number_of_items; i++)
410 if (collection->items[i].selected)
412 DirItem *item =
413 (DirItem *) collection->items[i].data;
415 g_string_append(reply, header->str);
416 g_string_append(reply, item->leafname);
419 /* This works, but I don't think I like it... */
420 /* g_string_append_c(reply, ' '); */
422 gtk_selection_data_set(selection_data, xa_string,
423 8, reply->str + 1, reply->len - 1);
424 g_string_free(reply, TRUE);
425 g_string_free(header, TRUE);
428 /* No items are now selected. This might be because another app claimed
429 * the selection or because the user unselected all the items.
431 static void lose_selection(Collection *collection,
432 guint time,
433 gpointer user_data)
435 FilerWindow *filer_window = (FilerWindow *) user_data;
437 if (window_with_selection == filer_window)
439 window_with_selection = NULL;
440 gtk_selection_owner_set(NULL,
441 GDK_SELECTION_PRIMARY,
442 time);
446 static void gain_selection(Collection *collection,
447 guint time,
448 gpointer user_data)
450 FilerWindow *filer_window = (FilerWindow *) user_data;
452 if (gtk_selection_owner_set(GTK_WIDGET(collection),
453 GDK_SELECTION_PRIMARY,
454 time))
456 window_with_selection = filer_window;
458 else
459 collection_clear_selection(filer_window->collection);
462 static void open_item(Collection *collection,
463 gpointer item_data, int item_number,
464 gpointer user_data)
466 FilerWindow *filer_window = (FilerWindow *) user_data;
467 GdkEvent *event;
468 GdkEventButton *bevent;
469 GdkEventKey *kevent;
470 OpenFlags flags = 0;
472 event = (GdkEvent *) gtk_get_current_event();
474 bevent = (GdkEventButton *) event;
475 kevent = (GdkEventKey *) event;
477 switch (event->type)
479 case GDK_2BUTTON_PRESS:
480 case GDK_BUTTON_PRESS:
481 case GDK_BUTTON_RELEASE:
482 if (bevent->state & GDK_SHIFT_MASK)
483 flags |= OPEN_SHIFT;
485 if (o_new_window_on_1 ^ (bevent->button == 1))
486 flags |= OPEN_SAME_WINDOW;
488 if (bevent->button != 1)
489 flags |= OPEN_CLOSE_WINDOW;
491 if (o_single_click == FALSE &&
492 (bevent->state & GDK_CONTROL_MASK) != 0)
493 flags ^= OPEN_SAME_WINDOW | OPEN_CLOSE_WINDOW;
494 break;
495 case GDK_KEY_PRESS:
496 flags |= OPEN_SAME_WINDOW;
497 if (kevent->state & GDK_SHIFT_MASK)
498 flags |= OPEN_SHIFT;
499 break;
500 default:
501 break;
504 filer_openitem(filer_window, item_number, flags);
507 /* Open the item (or add it to the shell command minibuffer) */
508 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
510 gboolean shift = (flags & OPEN_SHIFT) != 0;
511 gboolean close_mini = flags & OPEN_FROM_MINI;
512 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0
513 && !filer_window->panel_type;
514 GtkWidget *widget;
515 DirItem *item = (DirItem *)
516 filer_window->collection->items[item_number].data;
517 guchar *full_path;
518 gboolean wink = TRUE;
519 Directory *old_dir;
521 widget = filer_window->window;
522 if (filer_window->mini_type == MINI_SHELL)
524 minibuffer_add(filer_window, item->leafname);
525 return;
528 if (item->base_type == TYPE_DIRECTORY)
530 /* Never close a filer window when opening a directory
531 * (click on a dir or click on an app with shift).
533 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
534 close_window = FALSE;
537 full_path = make_path(filer_window->path, item->leafname)->str;
539 old_dir = filer_window->directory;
540 if (run_diritem(full_path, item,
541 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
542 shift))
544 if (old_dir != filer_window->directory)
545 return;
547 if (close_window)
548 gtk_widget_destroy(filer_window->window);
549 else
551 if (wink)
552 collection_wink_item(filer_window->collection,
553 item_number);
554 if (close_mini)
555 minibuffer_hide(filer_window);
560 static gint pointer_in(GtkWidget *widget,
561 GdkEventCrossing *event,
562 FilerWindow *filer_window)
564 may_rescan(filer_window, TRUE);
565 return FALSE;
568 static gint focus_in(GtkWidget *widget,
569 GdkEventFocus *event,
570 FilerWindow *filer_window)
572 window_with_focus = filer_window;
574 return FALSE;
577 static gint focus_out(GtkWidget *widget,
578 GdkEventFocus *event,
579 FilerWindow *filer_window)
581 /* TODO: Shade the cursor */
583 return FALSE;
586 /* Move the cursor to the next selected item in direction 'dir'
587 * (+1 or -1).
589 static void next_selected(FilerWindow *filer_window, int dir)
591 Collection *collection = filer_window->collection;
592 int to_check = collection->number_of_items;
593 int item = collection->cursor_item;
595 g_return_if_fail(dir == 1 || dir == -1);
597 if (to_check > 0 && item == -1)
599 /* Cursor not currently on */
600 if (dir == 1)
601 item = 0;
602 else
603 item = collection->number_of_items - 1;
605 if (collection->items[item].selected)
606 goto found;
609 while (--to_check > 0)
611 item += dir;
613 if (item >= collection->number_of_items)
614 item = 0;
615 else if (item < 0)
616 item = collection->number_of_items - 1;
618 if (collection->items[item].selected)
619 goto found;
622 gdk_beep();
623 return;
624 found:
625 collection_set_cursor_item(collection, item);
628 /* Handle keys that can't be bound with the menu */
629 static gint key_press_event(GtkWidget *widget,
630 GdkEventKey *event,
631 FilerWindow *filer_window)
633 switch (event->keyval)
635 case GDK_ISO_Left_Tab:
636 next_selected(filer_window, -1);
637 break;
638 case GDK_Tab:
639 next_selected(filer_window, 1);
640 break;
641 case GDK_BackSpace:
642 change_to_parent(filer_window);
643 break;
644 default:
645 return FALSE;
648 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
649 return TRUE;
652 void change_to_parent(FilerWindow *filer_window)
654 char *copy;
655 char *slash;
657 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
658 return; /* Already in the root */
660 copy = g_strdup(filer_window->path);
661 slash = strrchr(copy, '/');
663 if (slash)
665 *slash = '\0';
666 filer_change_to(filer_window,
667 *copy ? copy : "/",
668 slash + 1);
670 else
671 g_warning("No / in directory path!\n");
673 g_free(copy);
677 /* Make filer_window display path. When finished, highlight item 'from', or
678 * the first item if from is NULL. If there is currently no cursor then
679 * simply wink 'from' (if not NULL).
681 void filer_change_to(FilerWindow *filer_window, char *path, char *from)
683 char *from_dup;
684 char *real_path = pathdup(path);
686 g_return_if_fail(filer_window != NULL);
688 if (o_unique_filer_windows)
690 FilerWindow *fw;
692 fw = find_filer_window(real_path, filer_window);
693 if (fw)
694 gtk_widget_destroy(fw->window);
697 from_dup = from && *from ? g_strdup(from) : NULL;
699 detach(filer_window);
700 g_free(filer_window->path);
701 filer_window->path = real_path;
703 filer_window->directory = g_fscache_lookup(dir_cache,
704 filer_window->path);
705 if (filer_window->directory)
707 g_free(filer_window->auto_select);
708 filer_window->had_cursor =
709 filer_window->collection->cursor_item != -1
710 || filer_window->had_cursor;
711 filer_window->auto_select = from_dup;
713 filer_set_title(filer_window);
714 collection_set_cursor_item(filer_window->collection, -1);
715 attach(filer_window);
717 if (filer_window->mini_type == MINI_PATH)
718 gtk_idle_add((GtkFunction) minibuffer_show_cb,
719 filer_window);
721 else
723 char *error;
725 g_free(from_dup);
726 error = g_strdup_printf(_("Directory '%s' is not accessible"),
727 path);
728 delayed_error(PROJECT, error);
729 g_free(error);
730 gtk_widget_destroy(filer_window->window);
734 void filer_open_parent(FilerWindow *filer_window)
736 char *copy;
737 char *slash;
739 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
740 return; /* Already in the root */
742 copy = g_strdup(filer_window->path);
743 slash = strrchr(copy, '/');
745 if (slash)
747 *slash = '\0';
748 filer_opendir(*copy ? copy : "/");
750 else
751 g_warning("No / in directory path!\n");
753 g_free(copy);
756 int selected_item_number(Collection *collection)
758 int i;
760 g_return_val_if_fail(collection != NULL, -1);
761 g_return_val_if_fail(IS_COLLECTION(collection), -1);
762 g_return_val_if_fail(collection->number_selected == 1, -1);
764 for (i = 0; i < collection->number_of_items; i++)
765 if (collection->items[i].selected)
766 return i;
768 g_warning("selected_item: number_selected is wrong\n");
770 return -1;
773 DirItem *selected_item(Collection *collection)
775 int item;
777 item = selected_item_number(collection);
779 if (item > -1)
780 return (DirItem *) collection->items[item].data;
781 return NULL;
784 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
785 FilerWindow *window)
787 /* TODO: We can open lots of these - very irritating! */
788 return get_choice(_("Close panel?"),
789 _("You have tried to close a panel via the window "
790 "manager - I usually find that this is accidental... "
791 "really close?"),
792 2, _("Remove"), _("Cancel")) != 0;
795 /* Append all the URIs in the selection to the string */
796 static void create_uri_list(FilerWindow *filer_window, GString *string)
798 Collection *collection = filer_window->collection;
799 GString *leader;
800 int i, num_selected;
802 leader = g_string_new("file://");
803 if (!o_no_hostnames)
804 g_string_append(leader, our_host_name());
805 g_string_append(leader, filer_window->path);
806 if (leader->str[leader->len - 1] != '/')
807 g_string_append_c(leader, '/');
809 num_selected = collection->number_selected;
811 for (i = 0; num_selected > 0; i++)
813 if (collection->items[i].selected)
815 DirItem *item = (DirItem *) collection->items[i].data;
817 g_string_append(string, leader->str);
818 g_string_append(string, item->leafname);
819 g_string_append(string, "\r\n");
820 num_selected--;
824 g_string_free(leader, TRUE);
827 static void start_drag_selection(Collection *collection,
828 GdkEventMotion *event,
829 int number_selected,
830 FilerWindow *filer_window)
832 GtkWidget *widget = (GtkWidget *) collection;
834 if (number_selected == 1)
836 DirItem *item;
838 item = selected_item(collection);
840 drag_one_item(widget, event,
841 make_path(filer_window->path, item->leafname)->str,
842 item,
843 filer_window->mini_type == MINI_RUN_ACTION);
845 else
847 GString *uris;
849 uris = g_string_new(NULL);
850 create_uri_list(filer_window, uris);
851 drag_selection(widget, event, uris->str);
852 g_string_free(uris, TRUE);
856 /* Creates and shows a new filer window */
857 FilerWindow *filer_opendir(char *path)
859 return filer_openpanel(path, PANEL_NO);
862 /* Creates and shows a new filer window.
863 * panel_type may be PANEL_NO for a normal window.
865 FilerWindow *filer_openpanel(char *path, PanelType panel_type)
867 GtkWidget *hbox, *scrollbar, *collection;
868 FilerWindow *filer_window;
869 GtkTargetEntry target_table[] =
871 {"text/uri-list", 0, TARGET_URI_LIST},
872 {"STRING", 0, TARGET_STRING},
874 char *real_path;
876 /* Get the real pathname of the directory and copy it */
877 real_path = pathdup(path);
879 /* If the user doesn't want duplicate windows then check
880 * for an existing one and close it if found.
882 if (o_unique_filer_windows && panel_type == PANEL_NO)
884 FilerWindow *fw;
886 fw = find_filer_window(real_path, NULL);
888 if (fw)
890 /* TODO: this should bring the window to the front
891 * at the same coordinates.
893 gtk_widget_hide(fw->window);
894 g_free(real_path);
895 gtk_widget_show(fw->window);
896 return fw;
900 filer_window = g_new(FilerWindow, 1);
901 filer_window->minibuffer = NULL;
902 filer_window->minibuffer_label = NULL;
903 filer_window->minibuffer_area = NULL;
904 filer_window->temp_show_hidden = FALSE;
905 filer_window->path = real_path;
906 filer_window->scanning = FALSE;
907 filer_window->had_cursor = FALSE;
908 filer_window->auto_select = NULL;
909 filer_window->mini_type = MINI_NONE;
911 /* Finds the entry for this directory in the dir cache, creating
912 * a new one if needed. This does not cause a scan to start,
913 * so if a new entry is created then it will be empty.
915 filer_window->directory = g_fscache_lookup(dir_cache,
916 filer_window->path);
917 if (!filer_window->directory)
919 char *error;
921 error = g_strdup_printf(_("Directory '%s' not found."), path);
922 delayed_error(PROJECT, error);
923 g_free(error);
924 g_free(filer_window->path);
925 g_free(filer_window);
926 return NULL;
929 filer_window->show_hidden = last_show_hidden;
930 filer_window->panel_type = panel_type;
931 filer_window->temp_item_selected = FALSE;
932 filer_window->sort_fn = last_sort_fn;
933 filer_window->flags = (FilerFlags) 0;
934 filer_window->details_type = DETAILS_SUMMARY;
935 filer_window->display_style = UNKNOWN_STYLE;
937 /* Create the top-level window widget */
938 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
939 filer_set_title(filer_window);
941 /* The collection is the area that actually displays the files */
942 collection = collection_new(NULL);
943 gtk_object_set_data(GTK_OBJECT(collection),
944 "filer_window", filer_window);
945 filer_window->collection = COLLECTION(collection);
947 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
948 gtk_signal_connect(GTK_OBJECT(filer_window->window),
949 "enter-notify-event",
950 GTK_SIGNAL_FUNC(pointer_in), filer_window);
951 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
952 GTK_SIGNAL_FUNC(focus_in), filer_window);
953 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
954 GTK_SIGNAL_FUNC(focus_out), filer_window);
955 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
956 filer_window_destroyed, filer_window);
958 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
959 open_item, filer_window);
960 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
961 show_menu, filer_window);
962 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
963 gain_selection, filer_window);
964 gtk_signal_connect(GTK_OBJECT(collection), "lose_selection",
965 lose_selection, filer_window);
966 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
967 start_drag_selection, filer_window);
968 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
969 drag_data_get, NULL);
970 gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event",
971 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
972 gtk_signal_connect(GTK_OBJECT(collection), "selection_get",
973 GTK_SIGNAL_FUNC(selection_get), NULL);
974 gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY,
975 target_table,
976 sizeof(target_table) / sizeof(*target_table));
978 display_set_layout(filer_window, last_layout);
979 drag_set_dest(filer_window);
981 /* Add decorations appropriate to the window's type */
982 if (panel_type)
984 int swidth, sheight, iwidth, iheight;
985 GtkWidget *frame, *win = filer_window->window;
987 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Panel",
988 PROJECT);
989 collection_set_panel(filer_window->collection, TRUE);
990 gtk_signal_connect(GTK_OBJECT(filer_window->window),
991 "delete_event",
992 GTK_SIGNAL_FUNC(filer_confirm_close),
993 filer_window);
995 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
996 iwidth = filer_window->collection->item_width;
997 iheight = filer_window->collection->item_height;
1000 int height = iheight + PANEL_BORDER;
1001 int y = panel_type == PANEL_TOP
1003 : sheight - height - PANEL_BORDER;
1005 gtk_widget_set_usize(collection, swidth, height);
1006 gtk_widget_set_uposition(win, 0, y);
1009 frame = gtk_frame_new(NULL);
1010 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1011 gtk_container_add(GTK_CONTAINER(frame), collection);
1012 gtk_container_add(GTK_CONTAINER(win), frame);
1014 gtk_widget_show_all(frame);
1015 gtk_widget_realize(win);
1016 if (override_redirect)
1017 gdk_window_set_override_redirect(win->window, TRUE);
1018 make_panel_window(win->window);
1020 else
1022 GtkWidget *vbox;
1023 int col_height = ROW_HEIGHT_LARGE * 3;
1025 gtk_signal_connect(GTK_OBJECT(collection),
1026 "key_press_event",
1027 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1028 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
1029 filer_window->display_style == LARGE_ICONS ? 400 : 512,
1030 o_toolbar == TOOLBAR_NONE ? col_height:
1031 o_toolbar == TOOLBAR_NORMAL ? col_height + 24 :
1032 col_height + 38);
1034 hbox = gtk_hbox_new(FALSE, 0);
1035 gtk_container_add(GTK_CONTAINER(filer_window->window),
1036 hbox);
1038 vbox = gtk_vbox_new(FALSE, 0);
1039 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1041 if (o_toolbar != TOOLBAR_NONE)
1043 GtkWidget *toolbar;
1045 toolbar = toolbar_new(filer_window);
1046 gtk_box_pack_start(GTK_BOX(vbox), toolbar,
1047 FALSE, TRUE, 0);
1048 gtk_widget_show_all(toolbar);
1051 gtk_box_pack_start(GTK_BOX(vbox), collection, TRUE, TRUE, 0);
1053 create_minibuffer(filer_window);
1054 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1055 FALSE, TRUE, 0);
1057 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1058 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
1059 gtk_accel_group_attach(filer_keys,
1060 GTK_OBJECT(filer_window->window));
1061 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
1062 collection);
1064 gtk_widget_show(hbox);
1065 gtk_widget_show(vbox);
1066 gtk_widget_show(scrollbar);
1067 gtk_widget_show(collection);
1070 gtk_widget_realize(filer_window->window);
1072 /* The collection is created empty and then attach() is called, which
1073 * links the filer window to the entry in the directory cache we
1074 * looked up / created above.
1076 * The attach() function will immediately callback to the filer window
1077 * to deliver a list of all known entries in the directory (so,
1078 * collection->number_of_items may be valid after the call to
1079 * attach() returns).
1081 * BUT, if the directory was not in the cache (because it hadn't been
1082 * opened it before) then the cached dir will be empty and nothing gets
1083 * added until a while later when some entries are actually available.
1086 attach(filer_window);
1088 /* Make the window visible */
1089 number_of_windows++;
1090 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1091 gtk_widget_show(filer_window->window);
1093 return filer_window;
1096 static gint clear_scanning_display(FilerWindow *filer_window)
1098 if (filer_exists(filer_window))
1099 filer_set_title(filer_window);
1100 return FALSE;
1103 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1105 if (scanning == filer_window->scanning)
1106 return;
1107 filer_window->scanning = scanning;
1109 if (scanning)
1110 filer_set_title(filer_window);
1111 else
1112 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1113 filer_window);
1116 /* Build up some option widgets to go in the options dialog, but don't
1117 * fill them in yet.
1119 static GtkWidget *create_options(void)
1121 GtkWidget *vbox;
1123 vbox = gtk_vbox_new(FALSE, 0);
1124 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1126 toggle_new_window_on_1 =
1127 gtk_check_button_new_with_label(
1128 _("New window on button 1 (RISC OS style)"));
1129 OPTION_TIP(toggle_new_window_on_1,
1130 "Clicking with mouse button 1 (usually the "
1131 "left button) opens a directory in a new window "
1132 "with this turned on. Clicking with the button-2 "
1133 "(middle) will reuse the current window.");
1134 gtk_box_pack_start(GTK_BOX(vbox), toggle_new_window_on_1,
1135 FALSE, TRUE, 0);
1137 toggle_menu_on_2 =
1138 gtk_check_button_new_with_label(
1139 _("Menu on button 2 (RISC OS style)"));
1140 OPTION_TIP(toggle_menu_on_2,
1141 "Use button 2, the middle button (click both buttons "
1142 "at once on two button mice), to pop up the menu. "
1143 "If off, use button 3 (right) instead.");
1144 gtk_box_pack_start(GTK_BOX(vbox), toggle_menu_on_2, FALSE, TRUE, 0);
1146 toggle_single_click =
1147 gtk_check_button_new_with_label(_("Single-click nagivation"));
1148 OPTION_TIP(toggle_single_click,
1149 "Clicking on an item opens it with this on. Hold down "
1150 "Control to select the item instead. If off, clicking "
1151 "once selects an item; double click to open things.");
1152 gtk_box_pack_start(GTK_BOX(vbox), toggle_single_click, FALSE, TRUE, 0);
1154 toggle_unique_filer_windows =
1155 gtk_check_button_new_with_label(_("Unique windows"));
1156 OPTION_TIP(toggle_unique_filer_windows,
1157 "If you open a directory and that directory is "
1158 "already displayed in another window, then this "
1159 "option causes the other window to be closed.");
1160 gtk_box_pack_start(GTK_BOX(vbox), toggle_unique_filer_windows,
1161 FALSE, TRUE, 0);
1163 return vbox;
1166 /* Reflect current state by changing the widgets in the options box */
1167 static void update_options()
1169 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_new_window_on_1),
1170 o_new_window_on_1);
1171 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_menu_on_2),
1172 collection_menu_button == 2 ? 1 : 0);
1173 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_single_click),
1174 o_single_click);
1175 gtk_toggle_button_set_active(
1176 GTK_TOGGLE_BUTTON(toggle_unique_filer_windows),
1177 o_unique_filer_windows);
1180 /* Set current values by reading the states of the widgets in the options box */
1181 static void set_options()
1183 o_new_window_on_1 = gtk_toggle_button_get_active(
1184 GTK_TOGGLE_BUTTON(toggle_new_window_on_1));
1186 collection_menu_button = gtk_toggle_button_get_active(
1187 GTK_TOGGLE_BUTTON(toggle_menu_on_2)) ? 2 : 3;
1189 o_single_click = gtk_toggle_button_get_active(
1190 GTK_TOGGLE_BUTTON(toggle_single_click));
1192 o_unique_filer_windows = gtk_toggle_button_get_active(
1193 GTK_TOGGLE_BUTTON(toggle_unique_filer_windows));
1195 collection_single_click = o_single_click ? TRUE : FALSE;
1198 static void save_options()
1200 option_write("filer_new_window_on_1", o_new_window_on_1 ? "1" : "0");
1201 option_write("filer_menu_on_2",
1202 collection_menu_button == 2 ? "1" : "0");
1203 option_write("filer_single_click", o_single_click ? "1" : "0");
1204 option_write("filer_unique_windows",
1205 o_unique_filer_windows ? "1" : "0");
1208 static char *filer_new_window_on_1(char *data)
1210 o_new_window_on_1 = atoi(data) != 0;
1211 return NULL;
1214 static char *filer_menu_on_2(char *data)
1216 collection_menu_button = atoi(data) != 0 ? 2 : 3;
1217 return NULL;
1220 static char *filer_single_click(char *data)
1222 o_single_click = atoi(data) != 0;
1223 collection_single_click = o_single_click ? TRUE : FALSE;
1224 return NULL;
1227 static char *filer_unique_windows(char *data)
1229 o_unique_filer_windows = atoi(data) != 0;
1230 return NULL;
1233 /* Note that filer_window may not exist after this call. */
1234 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1236 if (may_rescan(filer_window, warning))
1237 dir_update(filer_window->directory, filer_window->path);
1240 /* Refresh the various caches even if we don't think we need to */
1241 void full_refresh(void)
1243 mount_update(TRUE);
1246 /* See whether a filer window with a given path already exists
1247 * and is different from diff.
1249 static FilerWindow *find_filer_window(char *path, FilerWindow *diff)
1251 GList *next = all_filer_windows;
1253 while (next)
1255 FilerWindow *filer_window = (FilerWindow *) next->data;
1257 if (filer_window->panel_type == PANEL_NO &&
1258 filer_window != diff &&
1259 strcmp(path, filer_window->path) == 0)
1261 return filer_window;
1264 next = next->next;
1267 return NULL;
1270 /* This path has been mounted/umounted/deleted some files - update all dirs */
1271 void filer_check_mounted(char *path)
1273 GList *next = all_filer_windows;
1274 char *slash;
1275 int len;
1277 len = strlen(path);
1279 while (next)
1281 FilerWindow *filer_window = (FilerWindow *) next->data;
1283 next = next->next;
1285 if (strncmp(path, filer_window->path, len) == 0)
1287 char s = filer_window->path[len];
1289 if (s == '/' || s == '\0')
1290 filer_update_dir(filer_window, FALSE);
1294 slash = strrchr(path, '/');
1295 if (slash && slash != path)
1297 guchar *parent;
1299 parent = g_strndup(path, slash - path);
1301 refresh_dirs(parent);
1303 g_free(parent);
1306 pinboard_may_update(path);
1309 /* Like minibuffer_show(), except that:
1310 * - It returns FALSE (to be used from an idle callback)
1311 * - It checks that the filer window still exists.
1313 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1315 if (filer_exists(filer_window))
1316 minibuffer_show(filer_window, MINI_PATH);
1317 return FALSE;
1320 gboolean filer_exists(FilerWindow *filer_window)
1322 GList *next;
1324 for (next = all_filer_windows; next; next = next->next)
1326 FilerWindow *fw = (FilerWindow *) next->data;
1328 if (fw == filer_window)
1329 return TRUE;
1332 return FALSE;
1335 static void filer_set_title(FilerWindow *filer_window)
1337 if (filer_window->scanning)
1339 guchar *title;
1341 title = g_strdup_printf(_("%s (Scanning)"), filer_window->path);
1342 gtk_window_set_title(GTK_WINDOW(filer_window->window),
1343 title);
1344 g_free(title);
1346 else
1347 gtk_window_set_title(GTK_WINDOW(filer_window->window),
1348 filer_window->path);
1351 /* Reconnect to the same directory (used when the Show Hidden option is
1352 * toggled).
1354 void filer_detach_rescan(FilerWindow *filer_window)
1356 Directory *dir = filer_window->directory;
1358 g_fscache_data_ref(dir_cache, dir);
1359 detach(filer_window);
1360 filer_window->directory = dir;
1361 attach(filer_window);