r496: Many internal changes to the Options system.
[rox-filer.git] / ROX-Filer / src / filer.c
blob078cb54b8740d24a64cf80308dacd901e04a081b
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>
32 #include <math.h>
34 #include <gtk/gtk.h>
35 #include <gdk/gdkx.h>
36 #include <gdk/gdkkeysyms.h>
37 #include "collection.h"
39 #include "global.h"
41 #include "main.h"
42 #include "support.h"
43 #include "gui_support.h"
44 #include "filer.h"
45 #include "pixmaps.h"
46 #include "menu.h"
47 #include "dnd.h"
48 #include "dir.h"
49 #include "run.h"
50 #include "mount.h"
51 #include "type.h"
52 #include "options.h"
53 #include "minibuffer.h"
54 #include "icon.h"
55 #include "toolbar.h"
56 #include "bind.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,
71 GdkEventFocus *event,
72 FilerWindow *filer_window);
73 static void add_item(FilerWindow *filer_window, DirItem *item);
74 static void update_display(Directory *dir,
75 DirAction action,
76 GPtrArray *items,
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,
110 set_unique);
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;
127 while (i--)
129 if (strcmp(leafname, r[i]->leafname) == 0)
130 return TRUE;
133 return FALSE;
136 static void update_item(FilerWindow *filer_window, DirItem *item)
138 int i;
139 char *leafname = item->leafname;
141 if (leafname[0] == '.' && filer_window->show_hidden == FALSE)
142 return;
144 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
146 if (i >= 0)
147 collection_draw_item(filer_window->collection, i, TRUE);
148 else
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,
171 MAX(req->width, w),
172 MAX(req->height, h));
174 else
175 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
176 w, h);
179 void filer_window_autosize(FilerWindow *filer_window)
181 Collection *collection = filer_window->collection;
182 int n = collection->number_of_items;
183 int x;
184 int rows, cols;
185 int w = collection->item_width;
186 int h = collection->item_height;
187 int max_x, max_rows;
188 const float r = 2.5;
189 int t = 0;
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;
198 n = MAX(n, 2);
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.
207 * t = toolbar height
208 * r = desired (width / height) ratio
210 * Want to display all items:
211 * (x/w)(y/h) = n
212 * => xy = nwh
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))
216 * Now,
217 * 4hr(nw - 1) > 0
218 * so
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;
229 /* Limit x */
230 if (x > max_x)
231 x = max_x;
233 cols = x / w;
234 cols = MAX(cols, 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;
240 if (rows > max_rows)
241 rows = max_rows;
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);
263 return FALSE;
266 static void update_display(Directory *dir,
267 DirAction action,
268 GPtrArray *items,
269 FilerWindow *filer_window)
271 int old_num;
272 int i;
273 int cursor = filer_window->collection->cursor_item;
274 char *as;
275 Collection *collection = filer_window->collection;
277 switch (action)
279 case DIR_ADD:
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)
290 continue;
292 if (strcmp(as, item->leafname) != 0)
293 continue;
295 cursor = collection->number_of_items - 1;
296 if (filer_window->had_cursor)
298 collection_set_cursor_item(collection,
299 cursor);
300 filer_window->mini_cursor_base = cursor;
302 else
303 collection_wink_item(collection,
304 cursor);
307 if (old_num != collection->number_of_items)
308 collection_qsort(filer_window->collection,
309 filer_window->sort_fn);
310 break;
311 case DIR_REMOVE:
312 collection_delete_if(filer_window->collection,
313 if_deleted,
314 items);
315 break;
316 case DIR_START_SCAN:
317 set_scanning_display(filer_window, TRUE);
318 break;
319 case DIR_END_SCAN:
320 if (filer_window->window->window)
321 gdk_window_set_cursor(
322 filer_window->window->window,
323 NULL);
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);
333 break;
334 case DIR_UPDATE:
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);
343 break;
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,
353 filer_window);
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)
377 if (popup_menu)
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)
396 gtk_main_quit();
399 /* Add a single object to a directory display */
400 static void add_item(FilerWindow *filer_window, DirItem *item)
402 char *leafname = item->leafname;
403 int item_width;
405 if (leafname[0] == '.')
407 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
408 || (leafname[1] == '.' && leafname[2] == '\0'))
409 return;
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,
415 item_width,
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)
423 Directory *dir;
425 g_return_val_if_fail(filer_window != NULL, FALSE);
427 /* We do a fresh lookup (rather than update) because the inode may
428 * have changed.
430 dir = g_fscache_lookup(dir_cache, filer_window->path);
431 if (!dir)
433 if (warning)
434 delayed_error(PROJECT, _("Directory missing/deleted"));
435 gtk_widget_destroy(filer_window->window);
436 return FALSE;
438 if (dir == filer_window->directory)
439 g_fscache_data_unref(dir_cache, dir);
440 else
442 detach(filer_window);
443 filer_window->directory = dir;
444 attach(filer_window);
447 return TRUE;
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);
462 return TRUE;
465 /* Someone wants us to send them the selection */
466 static void selection_get(GtkWidget *widget,
467 GtkSelectionData *selection_data,
468 guint info,
469 guint time,
470 gpointer data)
472 GString *reply, *header;
473 FilerWindow *filer_window;
474 int i;
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);
482 switch (info)
484 case TARGET_STRING:
485 g_string_sprintf(header, " %s",
486 make_path(filer_window->path, "")->str);
487 break;
488 case TARGET_URI_LIST:
489 g_string_sprintf(header, " file://%s%s",
490 our_host_name(),
491 make_path(filer_window->path, "")->str);
492 break;
495 collection = filer_window->collection;
496 for (i = 0; i < collection->number_of_items; i++)
498 if (collection->items[i].selected)
500 DirItem *item =
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,
520 guint time,
521 gpointer user_data)
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,
530 time);
534 static void gain_selection(Collection *collection,
535 guint time,
536 gpointer user_data)
538 FilerWindow *filer_window = (FilerWindow *) user_data;
540 if (gtk_selection_owner_set(GTK_WIDGET(collection),
541 GDK_SELECTION_PRIMARY,
542 time))
544 window_with_selection = filer_window;
546 else
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;
556 GtkWidget *widget;
557 DirItem *item = (DirItem *)
558 filer_window->collection->items[item_number].data;
559 guchar *full_path;
560 gboolean wink = TRUE;
561 Directory *old_dir;
563 widget = filer_window->window;
564 if (filer_window->mini_type == MINI_SHELL)
566 minibuffer_add(filer_window, item->leafname);
567 return;
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))
581 wink = FALSE;
583 old_dir = filer_window->directory;
584 if (run_diritem(full_path, item,
585 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
586 shift))
588 if (old_dir != filer_window->directory)
589 return;
591 if (close_window)
592 gtk_widget_destroy(filer_window->window);
593 else
595 if (wink)
596 collection_wink_item(filer_window->collection,
597 item_number);
598 if (close_mini)
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);
609 return FALSE;
612 static gint focus_in(GtkWidget *widget,
613 GdkEventFocus *event,
614 FilerWindow *filer_window)
616 window_with_focus = filer_window;
618 return FALSE;
621 /* Move the cursor to the next selected item in direction 'dir'
622 * (+1 or -1).
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 */
635 if (dir == 1)
636 item = 0;
637 else
638 item = collection->number_of_items - 1;
640 if (collection->items[item].selected)
641 goto found;
644 while (--to_check > 0)
646 item += dir;
648 if (item >= collection->number_of_items)
649 item = 0;
650 else if (item < 0)
651 item = collection->number_of_items - 1;
653 if (collection->items[item].selected)
654 goto found;
657 gdk_beep();
658 return;
659 found:
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)
673 return;
675 if (cb)
677 cb(filer_window, item, data);
678 return;
681 if (event->state & GDK_SHIFT_MASK)
682 flags |= OPEN_SHIFT;
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,
689 GdkEventKey *event,
690 FilerWindow *filer_window)
692 switch (event->keyval)
694 case GDK_Escape:
695 filer_target_mode(filer_window, NULL, NULL, NULL);
696 return FALSE;
697 case GDK_Return:
698 return_pressed(filer_window, event);
699 break;
700 case GDK_ISO_Left_Tab:
701 next_selected(filer_window, -1);
702 break;
703 case GDK_Tab:
704 next_selected(filer_window, 1);
705 break;
706 case GDK_BackSpace:
707 change_to_parent(filer_window);
708 break;
709 default:
710 return FALSE;
713 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
714 return TRUE;
717 void change_to_parent(FilerWindow *filer_window)
719 char *copy;
720 char *slash;
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, '/');
728 if (slash)
730 *slash = '\0';
731 filer_change_to(filer_window,
732 *copy ? copy : "/",
733 slash + 1);
735 else
736 g_warning("No / in directory path!\n");
738 g_free(copy);
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)
748 char *from_dup;
749 char *real_path;
750 Directory *new_dir;
752 g_return_if_fail(filer_window != NULL);
754 real_path = pathdup(path);
755 new_dir = g_fscache_lookup(dir_cache, real_path);
757 if (!new_dir)
759 char *error;
761 error = g_strdup_printf(_("Directory '%s' is not accessible"),
762 real_path);
763 g_free(real_path);
764 delayed_error(PROJECT, error);
765 g_free(error);
766 return;
769 if (o_unique_filer_windows)
771 FilerWindow *fw;
773 fw = find_filer_window(real_path, filer_window);
774 if (fw)
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,
797 filer_window);
800 void filer_open_parent(FilerWindow *filer_window)
802 char *copy;
803 char *slash;
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, '/');
811 if (slash)
813 *slash = '\0';
814 filer_opendir(*copy ? copy : "/");
816 else
817 g_warning("No / in directory path!\n");
819 g_free(copy);
822 int selected_item_number(Collection *collection)
824 int i;
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)
832 return i;
834 g_warning("selected_item: number_selected is wrong\n");
836 return -1;
839 DirItem *selected_item(Collection *collection)
841 int item;
843 item = selected_item_number(collection);
845 if (item > -1)
846 return (DirItem *) collection->items[item].data;
847 return NULL;
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;
854 GString *leader;
855 int i, num_selected;
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");
874 num_selected--;
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;
887 char *real_path;
888 int i;
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,
911 filer_window->path);
912 if (!filer_window->directory)
914 char *error;
916 error = g_strdup_printf(_("Directory '%s' not found."), path);
917 delayed_error(PROJECT, error);
918 g_free(error);
919 g_free(filer_window->path);
920 g_free(filer_window);
921 return NULL;
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 :
931 sort_by_size;
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,
953 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
962 * attach() returns).
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
972 * the old window!
974 number_of_windows++;
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)
982 FilerWindow *fw;
984 fw = find_filer_window(filer_window->path, NULL);
986 if (fw)
987 gtk_widget_destroy(fw->window);
990 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
992 return 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)
1025 GtkWidget *label;
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
1033 * have a toolbar.
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),
1038 GTK_SHADOW_OUT);
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)
1045 GtkWidget *toolbar;
1047 toolbar = toolbar_new(filer_window);
1048 gtk_container_add(GTK_CONTAINER(filer_window->toolbar_frame),
1049 toolbar);
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,
1059 FALSE, TRUE, 0);
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,
1116 target_table,
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);
1137 return FALSE;
1140 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1142 if (scanning == filer_window->scanning)
1143 return;
1144 filer_window->scanning = scanning;
1146 if (scanning)
1147 filer_set_title(filer_window);
1148 else
1149 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1150 filer_window);
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)
1163 mount_update(TRUE);
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;
1173 while (next)
1175 FilerWindow *filer_window = (FilerWindow *) next->data;
1177 if (filer_window != diff &&
1178 strcmp(path, filer_window->path) == 0)
1180 return filer_window;
1183 next = next->next;
1186 return NULL;
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;
1193 char *slash;
1194 int len;
1196 len = strlen(path);
1198 while (next)
1200 FilerWindow *filer_window = (FilerWindow *) next->data;
1202 next = next->next;
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)
1216 guchar *parent;
1218 parent = g_strndup(path, slash - path);
1220 refresh_dirs(parent);
1222 g_free(parent);
1224 else
1225 refresh_dirs("/");
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);
1238 return FALSE;
1241 gboolean filer_exists(FilerWindow *filer_window)
1243 GList *next;
1245 for (next = all_filer_windows; next; next = next->next)
1247 FilerWindow *fw = (FilerWindow *) next->data;
1249 if (fw == filer_window)
1250 return TRUE;
1253 return FALSE;
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,
1269 scanning,
1270 NULL);
1273 if (!title)
1274 title = g_strconcat(filer_window->path, scanning, NULL);
1276 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1277 g_free(title);
1280 /* Reconnect to the same directory (used when the Show Hidden option is
1281 * toggled).
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);
1302 return TRUE;
1305 perform_action(filer_window, event);
1307 return TRUE;
1310 static void perform_action(FilerWindow *filer_window, GdkEventButton *event)
1312 Collection *collection = filer_window->collection;
1313 DirItem *dir_item;
1314 int item;
1315 BindAction action;
1316 gboolean press = event->type == GDK_BUTTON_PRESS;
1317 gboolean selected = FALSE;
1318 OpenFlags flags = 0;
1320 if (event->button > 3)
1321 return;
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);
1333 return;
1336 action = bind_lookup_bev(
1337 item == -1 ? BIND_DIRECTORY : BIND_DIRECTORY_ICON,
1338 event);
1340 if (item != -1)
1342 dir_item = (DirItem *) collection->items[item].data;
1343 selected = collection->items[item].selected;
1345 else
1346 dir_item = NULL;
1348 switch (action)
1350 case ACT_CLEAR_SELECTION:
1351 collection_clear_selection(collection);
1352 break;
1353 case ACT_TOGGLE_SELECTED:
1354 collection_toggle_item(collection, item);
1355 break;
1356 case ACT_SELECT_EXCL:
1357 collection_clear_except(collection, item);
1358 break;
1359 case ACT_EDIT_ITEM:
1360 flags |= OPEN_SHIFT;
1361 /* (no break) */
1362 case ACT_OPEN_ITEM:
1363 if (event->button != 1)
1364 flags |= OPEN_CLOSE_WINDOW;
1365 else
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);
1373 break;
1374 case ACT_POPUP_MENU:
1375 dnd_motion_ungrab();
1376 show_filer_menu(filer_window, event, item);
1377 break;
1378 case ACT_PRIME_AND_SELECT:
1379 if (!selected)
1380 collection_clear_except(collection, item);
1381 dnd_motion_start(MOTION_READY_FOR_DND);
1382 break;
1383 case ACT_PRIME_AND_TOGGLE:
1384 collection_toggle_item(collection, item);
1385 dnd_motion_start(MOTION_READY_FOR_DND);
1386 break;
1387 case ACT_PRIME_FOR_DND:
1388 collection_wink_item(collection, item);
1389 dnd_motion_start(MOTION_READY_FOR_DND);
1390 break;
1391 case ACT_IGNORE:
1392 if (press && event->button < 4)
1394 if (item)
1395 collection_wink_item(collection, item);
1396 dnd_motion_start(MOTION_NONE);
1398 break;
1399 case ACT_LASSO_CLEAR:
1400 collection_clear_selection(collection);
1401 /* (no break) */
1402 case ACT_LASSO_MODIFY:
1403 collection_lasso_box(collection, event->x, event->y);
1404 break;
1405 default:
1406 g_warning("Unsupported action : %d\n", action);
1407 break;
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);
1420 return TRUE;
1423 static gint coll_motion_notify(GtkWidget *widget,
1424 GdkEventMotion *event,
1425 FilerWindow *filer_window)
1427 Collection *collection = filer_window->collection;
1428 int i;
1430 if (motion_state != MOTION_READY_FOR_DND)
1431 return TRUE;
1433 if (!dnd_motion_moved(event))
1434 return TRUE;
1436 i = collection_get_item(collection,
1437 event->x - (event->x_root - drag_start_x),
1438 event->y - (event->y_root - drag_start_y));
1439 if (i == -1)
1440 return TRUE;
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;
1452 else
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,
1468 item);
1470 else
1472 GString *uris;
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);
1480 return 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,
1490 TargetFunc fn,
1491 gpointer data,
1492 char *reason)
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),
1504 fn ? reason : "");