r280: All objects now have MIME types. This allows you to specify the action for
[rox-filer/ma.git] / ROX-Filer / src / filer.c
blob47a02cf3b62cfa53855481bde4f66ec00d32d765
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
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 <sys/param.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <ctype.h>
35 #include <gtk/gtk.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdkkeysyms.h>
38 #include "collection.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 "run.h"
48 #include "mount.h"
49 #include "type.h"
50 #include "options.h"
51 #include "action.h"
52 #include "minibuffer.h"
54 #define ROW_HEIGHT_LARGE 64
55 #define ROW_HEIGHT_SMALL 20
56 #define ROW_HEIGHT_FULL_INFO 44
57 #define SMALL_ICON_HEIGHT 20
58 #ifdef HAVE_IMLIB
59 # define SMALL_ICON_WIDTH 24
60 #else
61 # define SMALL_ICON_WIDTH 48
62 #endif
63 #define MAX_ICON_HEIGHT 42
64 #define MAX_ICON_WIDTH 48
65 #define PANEL_BORDER 2
66 #define MIN_ITEM_WIDTH 64
68 extern int collection_menu_button;
69 extern gboolean collection_single_click;
71 FilerWindow *window_with_focus = NULL;
72 GList *all_filer_windows = NULL;
74 static DisplayStyle last_display_style = LARGE_ICONS;
75 static gboolean last_show_hidden = FALSE;
76 static int (*last_sort_fn)(const void *a, const void *b) = sort_by_type;
78 static FilerWindow *window_with_selection = NULL;
80 /* Options bits */
81 static guchar *style_to_name(void);
82 static guchar *sort_fn_to_name(void);
83 static void update_options_label(void);
85 static GtkWidget *create_options();
86 static void update_options();
87 static void set_options();
88 static void save_options();
89 static char *filer_sort_nocase(char *data);
90 static char *filer_single_click(char *data);
91 static char *filer_unique_windows(char *data);
92 static char *filer_menu_on_2(char *data);
93 static char *filer_new_window_on_1(char *data);
94 static char *filer_toolbar(char *data);
95 static char *filer_display_style(char *data);
96 static char *filer_sort_by(char *data);
98 static OptionsSection options =
100 N_("Filer window options"),
101 create_options,
102 update_options,
103 set_options,
104 save_options
107 /* The values correspond to the menu indexes in the option widget */
108 typedef enum {
109 TOOLBAR_NONE = 0,
110 TOOLBAR_NORMAL = 1,
111 TOOLBAR_GNOME = 2,
112 } ToolbarType;
113 static ToolbarType o_toolbar = TOOLBAR_NORMAL;
114 static GtkWidget *menu_toolbar;
116 static GtkWidget *display_label;
118 static gboolean o_sort_nocase = FALSE;
119 static gboolean o_single_click = FALSE;
120 static gboolean o_new_window_on_1 = FALSE; /* Button 1 => New window */
121 gboolean o_unique_filer_windows = FALSE;
122 static GtkWidget *toggle_sort_nocase;
123 static GtkWidget *toggle_single_click;
124 static GtkWidget *toggle_new_window_on_1;
125 static GtkWidget *toggle_menu_on_2;
126 static GtkWidget *toggle_unique_filer_windows;
128 /* Static prototypes */
129 static void attach(FilerWindow *filer_window);
130 static void detach(FilerWindow *filer_window);
131 static void filer_window_destroyed(GtkWidget *widget,
132 FilerWindow *filer_window);
133 static void show_menu(Collection *collection, GdkEventButton *event,
134 int number_selected, gpointer user_data);
135 static gint focus_in(GtkWidget *widget,
136 GdkEventFocus *event,
137 FilerWindow *filer_window);
138 static gint focus_out(GtkWidget *widget,
139 GdkEventFocus *event,
140 FilerWindow *filer_window);
141 static void add_item(FilerWindow *filer_window, DirItem *item);
142 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window);
143 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window);
144 static void add_button(GtkWidget *box, int pixmap,
145 GtkSignalFunc cb, FilerWindow *filer_window,
146 char *label, char *tip);
147 static GtkWidget *create_toolbar(FilerWindow *filer_window);
148 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
149 FilerWindow *window);
150 static int calc_width(FilerWindow *filer_window, DirItem *item);
151 static void draw_large_icon(GtkWidget *widget,
152 GdkRectangle *area,
153 DirItem *item,
154 gboolean selected);
155 static void draw_string(GtkWidget *widget,
156 GdkFont *font,
157 char *string,
158 int x,
159 int y,
160 int width,
161 gboolean selected);
162 static void draw_item_large(GtkWidget *widget,
163 CollectionItem *item,
164 GdkRectangle *area);
165 static void draw_item_small(GtkWidget *widget,
166 CollectionItem *item,
167 GdkRectangle *area);
168 static void draw_item_full_info(GtkWidget *widget,
169 CollectionItem *colitem,
170 GdkRectangle *area);
171 static gboolean test_point_large(Collection *collection,
172 int point_x, int point_y,
173 CollectionItem *item,
174 int width, int height);
175 static gboolean test_point_small(Collection *collection,
176 int point_x, int point_y,
177 CollectionItem *item,
178 int width, int height);
179 static gboolean test_point_full_info(Collection *collection,
180 int point_x, int point_y,
181 CollectionItem *item,
182 int width, int height);
183 static void update_display(Directory *dir,
184 DirAction action,
185 GPtrArray *items,
186 FilerWindow *filer_window);
187 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
188 static void shrink_width(FilerWindow *filer_window);
189 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
190 static void open_item(Collection *collection,
191 gpointer item_data, int item_number,
192 gpointer user_data);
193 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
194 static FilerWindow *find_filer_window(char *path, FilerWindow *diff);
195 static void filer_set_title(FilerWindow *filer_window);
197 static GdkAtom xa_string;
198 enum
200 TARGET_STRING,
201 TARGET_URI_LIST,
204 static GdkCursor *busy_cursor = NULL;
205 static GtkTooltips *tooltips = NULL;
207 void filer_init()
209 xa_string = gdk_atom_intern("STRING", FALSE);
211 options_sections = g_slist_prepend(options_sections, &options);
212 option_register("filer_sort_nocase", filer_sort_nocase);
213 option_register("filer_new_window_on_1", filer_new_window_on_1);
214 option_register("filer_menu_on_2", filer_menu_on_2);
215 option_register("filer_single_click", filer_single_click);
216 option_register("filer_unique_windows", filer_unique_windows);
217 option_register("filer_toolbar", filer_toolbar);
218 option_register("filer_display_style", filer_display_style);
219 option_register("filer_sort_by", filer_sort_by);
221 busy_cursor = gdk_cursor_new(GDK_WATCH);
223 tooltips = gtk_tooltips_new();
226 static gboolean if_deleted(gpointer item, gpointer removed)
228 int i = ((GPtrArray *) removed)->len;
229 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
230 char *leafname = ((DirItem *) item)->leafname;
232 while (i--)
234 if (strcmp(leafname, r[i]->leafname) == 0)
235 return TRUE;
238 return FALSE;
241 static void update_item(FilerWindow *filer_window, DirItem *item)
243 int i;
244 char *leafname = item->leafname;
246 if (leafname[0] == '.')
248 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
249 || (leafname[1] == '.' && leafname[2] == '\0'))
250 return;
253 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
255 if (i >= 0)
256 collection_draw_item(filer_window->collection, i, TRUE);
257 else
258 g_warning("Failed to find '%s'\n", item->leafname);
261 static void update_display(Directory *dir,
262 DirAction action,
263 GPtrArray *items,
264 FilerWindow *filer_window)
266 int old_num;
267 int i;
268 int cursor = filer_window->collection->cursor_item;
269 char *as;
270 Collection *collection = filer_window->collection;
272 switch (action)
274 case DIR_ADD:
275 as = filer_window->auto_select;
277 old_num = collection->number_of_items;
278 for (i = 0; i < items->len; i++)
280 DirItem *item = (DirItem *) items->pdata[i];
282 add_item(filer_window, item);
284 if (cursor != -1 || !as)
285 continue;
287 if (strcmp(as, item->leafname) != 0)
288 continue;
290 cursor = collection->number_of_items - 1;
291 if (filer_window->had_cursor)
293 collection_set_cursor_item(collection,
294 cursor);
295 filer_window->mini_cursor_base = cursor;
297 else
298 collection_wink_item(collection,
299 cursor);
302 if (old_num != collection->number_of_items)
303 collection_qsort(filer_window->collection,
304 filer_window->sort_fn);
305 break;
306 case DIR_REMOVE:
307 collection_delete_if(filer_window->collection,
308 if_deleted,
309 items);
310 break;
311 case DIR_START_SCAN:
312 set_scanning_display(filer_window, TRUE);
313 break;
314 case DIR_END_SCAN:
315 if (filer_window->window->window)
316 gdk_window_set_cursor(
317 filer_window->window->window,
318 NULL);
319 shrink_width(filer_window);
320 if (filer_window->had_cursor &&
321 collection->cursor_item == -1)
323 collection_set_cursor_item(collection, 0);
324 filer_window->had_cursor = FALSE;
326 set_scanning_display(filer_window, FALSE);
327 break;
328 case DIR_UPDATE:
329 for (i = 0; i < items->len; i++)
331 DirItem *item = (DirItem *) items->pdata[i];
333 update_item(filer_window, item);
335 collection_qsort(filer_window->collection,
336 filer_window->sort_fn);
337 break;
341 static void attach(FilerWindow *filer_window)
343 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
344 collection_clear(filer_window->collection);
345 filer_window->scanning = TRUE;
346 dir_attach(filer_window->directory, (DirCallback) update_display,
347 filer_window);
348 filer_set_title(filer_window);
351 static void detach(FilerWindow *filer_window)
353 g_return_if_fail(filer_window->directory != NULL);
355 dir_detach(filer_window->directory,
356 (DirCallback) update_display, filer_window);
357 g_fscache_data_unref(dir_cache, filer_window->directory);
358 filer_window->directory = NULL;
361 static void filer_window_destroyed(GtkWidget *widget,
362 FilerWindow *filer_window)
364 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
366 if (window_with_selection == filer_window)
367 window_with_selection = NULL;
368 if (window_with_focus == filer_window)
369 window_with_focus = NULL;
371 if (filer_window->directory)
372 detach(filer_window);
374 g_free(filer_window->auto_select);
375 g_free(filer_window->path);
376 g_free(filer_window);
378 if (--number_of_windows < 1)
379 gtk_main_quit();
382 static int calc_width(FilerWindow *filer_window, DirItem *item)
384 int pix_width = item->image->width;
386 switch (filer_window->display_style)
388 case FULL_INFO:
389 return MAX_ICON_WIDTH + 12 +
390 MAX(item->details_width, item->name_width);
391 case SMALL_ICONS:
392 return SMALL_ICON_WIDTH + 12 + item->name_width;
393 default:
394 return MAX(pix_width, item->name_width) + 4;
398 /* Add a single object to a directory display */
399 static void add_item(FilerWindow *filer_window, DirItem *item)
401 char *leafname = item->leafname;
402 int item_width;
404 if (leafname[0] == '.')
406 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
407 || (leafname[1] == '.' && leafname[2] == '\0'))
408 return;
411 item_width = calc_width(filer_window, item);
412 if (item_width > filer_window->collection->item_width)
413 collection_set_item_size(filer_window->collection,
414 item_width,
415 filer_window->collection->item_height);
416 collection_insert(filer_window->collection, item);
419 /* Is a point inside an item? */
420 static gboolean test_point_large(Collection *collection,
421 int point_x, int point_y,
422 CollectionItem *colitem,
423 int width, int height)
425 DirItem *item = (DirItem *) colitem->data;
426 int text_height = item_font->ascent + item_font->descent;
427 MaskedPixmap *image = item->image;
428 int image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
429 int image_width = (image->width >> 1) + 2;
430 int text_width = (item->name_width >> 1) + 2;
431 int x_limit;
433 if (point_y < image_y)
434 return FALSE; /* Too high up (don't worry about too low) */
436 if (point_y <= image_y + image->height + 2)
437 x_limit = image_width;
438 else if (point_y > height - text_height - 2)
439 x_limit = text_width;
440 else
441 x_limit = MIN(image_width, text_width);
443 return ABS(point_x - (width >> 1)) < x_limit;
446 static gboolean test_point_full_info(Collection *collection,
447 int point_x, int point_y,
448 CollectionItem *colitem,
449 int width, int height)
451 DirItem *item = (DirItem *) colitem->data;
452 MaskedPixmap *image = item->image;
453 int image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
454 int low_top = height
455 - fixed_font->descent - 2 - fixed_font->ascent;
457 if (point_x < image->width + 2)
458 return point_x > 2 && point_y > image_y;
460 point_x -= MAX_ICON_WIDTH + 8;
462 if (point_y >= low_top)
463 return point_x < item->details_width;
464 if (point_y >= low_top - item_font->ascent - item_font->descent)
465 return point_x < item->name_width;
466 return FALSE;
469 static gboolean test_point_small(Collection *collection,
470 int point_x, int point_y,
471 CollectionItem *colitem,
472 int width, int height)
474 DirItem *item = (DirItem *) colitem->data;
475 MaskedPixmap *image = item->image;
476 int image_y = MAX(0, SMALL_ICON_HEIGHT - image->height);
477 int low_top = height
478 - fixed_font->descent - 2 - item_font->ascent;
479 int iwidth = MIN(SMALL_ICON_WIDTH, image->width);
481 if (point_x < iwidth + 2)
482 return point_x > 2 && point_y > image_y;
484 point_x -= SMALL_ICON_WIDTH + 4;
486 if (point_y >= low_top)
487 return point_x < item->name_width;
488 return FALSE;
491 static void draw_small_icon(GtkWidget *widget,
492 GdkRectangle *area,
493 DirItem *item,
494 gboolean selected)
496 GdkGC *gc = selected ? widget->style->white_gc
497 : widget->style->black_gc;
498 MaskedPixmap *image = item->image;
499 int width, height, image_x, image_y;
501 if (!image)
502 return;
504 if (!image->sm_pixmap)
505 pixmap_make_small(image);
507 width = MIN(image->sm_width, SMALL_ICON_WIDTH);
508 height = MIN(image->sm_height, SMALL_ICON_HEIGHT);
509 image_x = area->x + ((area->width - width) >> 1);
511 gdk_gc_set_clip_mask(gc, item->image->sm_mask);
513 image_y = MAX(0, SMALL_ICON_HEIGHT - image->sm_height);
514 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
515 gdk_draw_pixmap(widget->window, gc,
516 item->image->sm_pixmap,
517 0, 0, /* Source x,y */
518 image_x, area->y + image_y, /* Dest x,y */
519 width, height);
521 if (selected)
523 gdk_gc_set_function(gc, GDK_INVERT);
524 gdk_draw_rectangle(widget->window,
526 TRUE, image_x, area->y + image_y,
527 width, height);
528 gdk_gc_set_function(gc, GDK_COPY);
531 if (item->flags & ITEM_FLAG_SYMLINK)
533 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
534 gdk_gc_set_clip_mask(gc,
535 default_pixmap[TYPE_SYMLINK]->mask);
536 gdk_draw_pixmap(widget->window, gc,
537 default_pixmap[TYPE_SYMLINK]->pixmap,
538 0, 0, /* Source x,y */
539 image_x, area->y + 8, /* Dest x,y */
540 -1, -1);
542 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
544 int type = item->flags & ITEM_FLAG_MOUNTED
545 ? TYPE_MOUNTED
546 : TYPE_UNMOUNTED;
547 MaskedPixmap *mp = default_pixmap[type];
549 if (!mp->sm_pixmap)
550 pixmap_make_small(mp);
551 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
552 gdk_gc_set_clip_mask(gc, mp->sm_mask);
553 gdk_draw_pixmap(widget->window, gc,
554 mp->sm_pixmap,
555 0, 0, /* Source x,y */
556 image_x, area->y + 8, /* Dest x,y */
557 -1, -1);
560 gdk_gc_set_clip_mask(gc, NULL);
561 gdk_gc_set_clip_origin(gc, 0, 0);
564 static void draw_large_icon(GtkWidget *widget,
565 GdkRectangle *area,
566 DirItem *item,
567 gboolean selected)
569 MaskedPixmap *image = item->image;
570 int width = MIN(image->width, MAX_ICON_WIDTH);
571 int height = MIN(image->height, MAX_ICON_WIDTH);
572 int image_x = area->x + ((area->width - width) >> 1);
573 int image_y;
574 GdkGC *gc = selected ? widget->style->white_gc
575 : widget->style->black_gc;
577 gdk_gc_set_clip_mask(gc, item->image->mask);
579 image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
580 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
581 gdk_draw_pixmap(widget->window, gc,
582 item->image->pixmap,
583 0, 0, /* Source x,y */
584 image_x, area->y + image_y, /* Dest x,y */
585 width, height);
587 if (selected)
589 gdk_gc_set_function(gc, GDK_INVERT);
590 gdk_draw_rectangle(widget->window,
592 TRUE, image_x, area->y + image_y,
593 width, height);
594 gdk_gc_set_function(gc, GDK_COPY);
597 if (item->flags & ITEM_FLAG_SYMLINK)
599 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
600 gdk_gc_set_clip_mask(gc,
601 default_pixmap[TYPE_SYMLINK]->mask);
602 gdk_draw_pixmap(widget->window, gc,
603 default_pixmap[TYPE_SYMLINK]->pixmap,
604 0, 0, /* Source x,y */
605 image_x, area->y + 8, /* Dest x,y */
606 -1, -1);
608 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
610 int type = item->flags & ITEM_FLAG_MOUNTED
611 ? TYPE_MOUNTED
612 : TYPE_UNMOUNTED;
613 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
614 gdk_gc_set_clip_mask(gc,
615 default_pixmap[type]->mask);
616 gdk_draw_pixmap(widget->window, gc,
617 default_pixmap[type]->pixmap,
618 0, 0, /* Source x,y */
619 image_x, area->y + 8, /* Dest x,y */
620 -1, -1);
623 gdk_gc_set_clip_mask(gc, NULL);
624 gdk_gc_set_clip_origin(gc, 0, 0);
627 static void draw_string(GtkWidget *widget,
628 GdkFont *font,
629 char *string,
630 int x,
631 int y,
632 int width,
633 gboolean selected)
635 int text_height = font->ascent + font->descent;
637 if (selected)
638 gtk_paint_flat_box(widget->style, widget->window,
639 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
640 NULL, widget, "text",
641 x, y - font->ascent,
642 width,
643 text_height);
645 gdk_draw_text(widget->window,
646 font,
647 selected ? widget->style->white_gc
648 : widget->style->black_gc,
649 x, y,
650 string, strlen(string));
653 /* Return a string (valid until next call) giving details
654 * of this item.
656 char *details(DirItem *item)
658 mode_t m = item->mode;
659 static guchar *buf = NULL;
661 if (buf)
662 g_free(buf);
664 if (item->lstat_errno)
665 buf = g_strdup_printf(_("lstat(2) failed: %s"),
666 g_strerror(item->lstat_errno));
667 else
668 buf = g_strdup_printf("%s %s %-8.8s %-8.8s %s %s",
669 item->flags & ITEM_FLAG_APPDIR? "App " :
670 S_ISDIR(m) ? "Dir " :
671 S_ISCHR(m) ? "Char" :
672 S_ISBLK(m) ? "Blck" :
673 S_ISLNK(m) ? "Link" :
674 S_ISSOCK(m) ? "Sock" :
675 S_ISFIFO(m) ? "Pipe" : "File",
676 pretty_permissions(m),
677 user_name(item->uid),
678 group_name(item->gid),
679 format_size_aligned(item->size),
680 pretty_time(&item->mtime));
681 return buf;
684 static void draw_item_full_info(GtkWidget *widget,
685 CollectionItem *colitem,
686 GdkRectangle *area)
688 DirItem *item = (DirItem *) colitem->data;
689 MaskedPixmap *image = item->image;
690 int text_x = area->x + MAX_ICON_WIDTH + 8;
691 int low_text_y = area->y + area->height - fixed_font->descent - 2;
692 gboolean selected = colitem->selected;
693 GdkRectangle pic_area;
695 pic_area.x = area->x;
696 pic_area.y = area->y;
697 pic_area.width = image->width + 8;
698 pic_area.height = area->height;
700 draw_large_icon(widget, &pic_area, item, selected);
702 draw_string(widget,
703 item_font,
704 item->leafname,
705 text_x,
706 low_text_y - item_font->descent - fixed_font->ascent,
707 item->name_width,
708 selected);
709 draw_string(widget,
710 fixed_font,
711 details(item),
712 text_x, low_text_y,
713 item->details_width,
714 selected);
716 if (item->lstat_errno)
717 return;
719 /* Underline the effective permissions */
720 gdk_draw_rectangle(widget->window,
721 selected ? widget->style->white_gc
722 : widget->style->black_gc,
723 TRUE,
724 text_x - 1 + fixed_width *
725 (5 + 4 * applicable(item->uid, item->gid)),
726 low_text_y + fixed_font->descent - 1,
727 fixed_width * 3 + 1, 1);
730 static void draw_item_small(GtkWidget *widget,
731 CollectionItem *colitem,
732 GdkRectangle *area)
734 DirItem *item = (DirItem *) colitem->data;
735 int text_x = area->x + SMALL_ICON_WIDTH + 4;
736 int low_text_y = area->y + area->height - item_font->descent - 2;
737 gboolean selected = colitem->selected;
738 GdkRectangle pic_area;
740 pic_area.x = area->x;
741 pic_area.y = area->y;
742 pic_area.width = SMALL_ICON_WIDTH;
743 pic_area.height = SMALL_ICON_HEIGHT;
745 draw_small_icon(widget, &pic_area, item, selected);
747 draw_string(widget,
748 item_font,
749 item->leafname,
750 text_x,
751 low_text_y,
752 item->name_width,
753 selected);
756 static void draw_item_large(GtkWidget *widget,
757 CollectionItem *colitem,
758 GdkRectangle *area)
760 DirItem *item = (DirItem *) colitem->data;
761 int text_x = area->x + ((area->width - item->name_width) >> 1);
762 int text_y = area->y + area->height - item_font->descent - 2;
763 gboolean selected = colitem->selected;
765 draw_large_icon(widget, area, item, selected);
767 draw_string(widget,
768 item_font,
769 item->leafname,
770 text_x, text_y, item->name_width,
771 selected);
774 static void show_menu(Collection *collection, GdkEventButton *event,
775 int item, gpointer user_data)
777 show_filer_menu((FilerWindow *) user_data, event, item);
780 /* Returns TRUE iff the directory still exists. */
781 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
783 Directory *dir;
785 g_return_val_if_fail(filer_window != NULL, FALSE);
787 /* We do a fresh lookup (rather than update) because the inode may
788 * have changed.
790 dir = g_fscache_lookup(dir_cache, filer_window->path);
791 if (!dir)
793 if (warning)
794 delayed_error(PROJECT, _("Directory missing/deleted"));
795 gtk_widget_destroy(filer_window->window);
796 return FALSE;
798 if (dir == filer_window->directory)
799 g_fscache_data_unref(dir_cache, dir);
800 else
802 detach(filer_window);
803 filer_window->directory = dir;
804 attach(filer_window);
807 return TRUE;
810 /* Another app has grabbed the selection */
811 static gint collection_lose_selection(GtkWidget *widget,
812 GdkEventSelection *event)
814 if (window_with_selection &&
815 window_with_selection->collection == COLLECTION(widget))
817 FilerWindow *filer_window = window_with_selection;
818 window_with_selection = NULL;
819 collection_clear_selection(filer_window->collection);
822 return TRUE;
825 /* Someone wants us to send them the selection */
826 static void selection_get(GtkWidget *widget,
827 GtkSelectionData *selection_data,
828 guint info,
829 guint time,
830 gpointer data)
832 GString *reply, *header;
833 FilerWindow *filer_window;
834 int i;
835 Collection *collection;
837 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
839 reply = g_string_new(NULL);
840 header = g_string_new(NULL);
842 switch (info)
844 case TARGET_STRING:
845 g_string_sprintf(header, " %s",
846 make_path(filer_window->path, "")->str);
847 break;
848 case TARGET_URI_LIST:
849 g_string_sprintf(header, " file://%s%s",
850 our_host_name(),
851 make_path(filer_window->path, "")->str);
852 break;
855 collection = filer_window->collection;
856 for (i = 0; i < collection->number_of_items; i++)
858 if (collection->items[i].selected)
860 DirItem *item =
861 (DirItem *) collection->items[i].data;
863 g_string_append(reply, header->str);
864 g_string_append(reply, item->leafname);
867 /* This works, but I don't think I like it... */
868 /* g_string_append_c(reply, ' '); */
870 gtk_selection_data_set(selection_data, xa_string,
871 8, reply->str + 1, reply->len - 1);
872 g_string_free(reply, TRUE);
873 g_string_free(header, TRUE);
876 /* No items are now selected. This might be because another app claimed
877 * the selection or because the user unselected all the items.
879 static void lose_selection(Collection *collection,
880 guint time,
881 gpointer user_data)
883 FilerWindow *filer_window = (FilerWindow *) user_data;
885 if (window_with_selection == filer_window)
887 window_with_selection = NULL;
888 gtk_selection_owner_set(NULL,
889 GDK_SELECTION_PRIMARY,
890 time);
894 static void gain_selection(Collection *collection,
895 guint time,
896 gpointer user_data)
898 FilerWindow *filer_window = (FilerWindow *) user_data;
900 if (gtk_selection_owner_set(GTK_WIDGET(collection),
901 GDK_SELECTION_PRIMARY,
902 time))
904 window_with_selection = filer_window;
906 else
907 collection_clear_selection(filer_window->collection);
910 int sort_by_name(const void *item1, const void *item2)
912 if (o_sort_nocase)
913 return g_strcasecmp((*((DirItem **)item1))->leafname,
914 (*((DirItem **)item2))->leafname);
915 return strcmp((*((DirItem **)item1))->leafname,
916 (*((DirItem **)item2))->leafname);
919 int sort_by_type(const void *item1, const void *item2)
921 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
922 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
923 MIME_type *m1, *m2;
925 int diff = i1->base_type - i2->base_type;
927 if (!diff)
928 diff = (i1->flags & ITEM_FLAG_APPDIR)
929 - (i2->flags & ITEM_FLAG_APPDIR);
930 if (diff)
931 return diff > 0 ? 1 : -1;
933 m1 = i1->mime_type;
934 m2 = i2->mime_type;
936 if (m1 && m2)
938 diff = strcmp(m1->media_type, m2->media_type);
939 if (!diff)
940 diff = strcmp(m1->subtype, m2->subtype);
942 else if (m1 || m2)
943 diff = m1 ? 1 : -1;
944 else
945 diff = 0;
947 if (diff)
948 return diff > 0 ? 1 : -1;
950 return sort_by_name(item1, item2);
953 int sort_by_date(const void *item1, const void *item2)
955 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
956 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
958 return i1->mtime > i2->mtime ? -1 :
959 i1->mtime < i2->mtime ? 1 :
960 sort_by_name(item1, item2);
963 int sort_by_size(const void *item1, const void *item2)
965 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
966 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
968 return i1->size > i2->size ? -1 :
969 i1->size < i2->size ? 1 :
970 sort_by_name(item1, item2);
973 static void open_item(Collection *collection,
974 gpointer item_data, int item_number,
975 gpointer user_data)
977 FilerWindow *filer_window = (FilerWindow *) user_data;
978 GdkEvent *event;
979 GdkEventButton *bevent;
980 GdkEventKey *kevent;
981 OpenFlags flags = 0;
983 event = (GdkEvent *) gtk_get_current_event();
985 bevent = (GdkEventButton *) event;
986 kevent = (GdkEventKey *) event;
988 switch (event->type)
990 case GDK_2BUTTON_PRESS:
991 case GDK_BUTTON_PRESS:
992 case GDK_BUTTON_RELEASE:
993 if (bevent->state & GDK_SHIFT_MASK)
994 flags |= OPEN_SHIFT;
996 if (o_new_window_on_1 ^ (bevent->button == 1))
997 flags |= OPEN_SAME_WINDOW;
999 if (bevent->button != 1)
1000 flags |= OPEN_CLOSE_WINDOW;
1002 if (o_single_click == FALSE &&
1003 (bevent->state & GDK_CONTROL_MASK) != 0)
1004 flags ^= OPEN_SAME_WINDOW | OPEN_CLOSE_WINDOW;
1005 break;
1006 case GDK_KEY_PRESS:
1007 flags |= OPEN_SAME_WINDOW;
1008 if (kevent->state & GDK_SHIFT_MASK)
1009 flags |= OPEN_SHIFT;
1010 break;
1011 default:
1012 break;
1015 filer_openitem(filer_window, item_number, flags);
1018 /* Return the full path to the directory containing object 'path'.
1019 * Relative paths are resolved from the filerwindow's path.
1021 static void follow_symlink(FilerWindow *filer_window, char *path,
1022 gboolean same_window)
1024 char *real, *slash;
1025 char *new_dir;
1027 if (path[0] != '/')
1028 path = make_path(filer_window->path, path)->str;
1030 real = pathdup(path);
1031 slash = strrchr(real, '/');
1032 if (!slash)
1034 g_free(real);
1035 delayed_error(PROJECT,
1036 _("Broken symlink (or you don't have permission "
1037 "to follow it)."));
1038 return;
1041 *slash = '\0';
1043 if (*real)
1044 new_dir = real;
1045 else
1046 new_dir = "/";
1048 if (filer_window->panel_type || !same_window)
1050 FilerWindow *new;
1052 new = filer_opendir(new_dir, PANEL_NO);
1053 filer_set_autoselect(new, slash + 1);
1055 else
1056 filer_change_to(filer_window, new_dir, slash + 1);
1058 g_free(real);
1061 /* Open the item (or add it to the shell command minibuffer) */
1062 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
1064 gboolean shift = (flags & OPEN_SHIFT) != 0;
1065 gboolean close_mini = flags & OPEN_FROM_MINI;
1066 gboolean same_window = (flags & OPEN_SAME_WINDOW) != 0
1067 && !filer_window->panel_type;
1068 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0
1069 && !filer_window->panel_type;
1070 GtkWidget *widget;
1071 char *full_path;
1072 DirItem *item = (DirItem *)
1073 filer_window->collection->items[item_number].data;
1074 gboolean wink = TRUE, destroy = FALSE;
1076 widget = filer_window->window;
1077 if (filer_window->mini_type == MINI_SHELL)
1079 minibuffer_add(filer_window, item->leafname);
1080 return;
1083 full_path = make_path(filer_window->path,
1084 item->leafname)->str;
1086 if (item->flags & ITEM_FLAG_SYMLINK && shift)
1088 char path[MAXPATHLEN + 1];
1089 int got;
1091 got = readlink(make_path(filer_window->path,
1092 item->leafname)->str,
1093 path, MAXPATHLEN);
1094 if (got < 0)
1095 delayed_error(PROJECT, g_strerror(errno));
1096 else
1098 g_return_if_fail(got <= MAXPATHLEN);
1099 path[got] = '\0';
1101 follow_symlink(filer_window, path,
1102 flags & OPEN_SAME_WINDOW);
1104 return;
1107 switch (item->base_type)
1109 case TYPE_DIRECTORY:
1110 if (item->flags & ITEM_FLAG_APPDIR && !shift)
1112 run_app(make_path(filer_window->path,
1113 item->leafname)->str);
1114 if (close_window)
1115 destroy = TRUE;
1116 break;
1119 if (item->flags & ITEM_FLAG_MOUNT_POINT && shift)
1121 action_mount(filer_window, item);
1122 if (item->flags & ITEM_FLAG_MOUNTED)
1123 break;
1126 if (same_window)
1128 wink = FALSE;
1129 filer_change_to(filer_window, full_path, NULL);
1130 close_mini = FALSE;
1132 else
1133 filer_opendir(full_path, PANEL_NO);
1134 break;
1135 case TYPE_FILE:
1136 if ((item->flags & ITEM_FLAG_EXEC_FILE) && !shift)
1138 char *argv[] = {NULL, NULL};
1140 argv[0] = full_path;
1142 if (spawn_full(argv, filer_window->path))
1144 if (close_window)
1145 destroy = TRUE;
1147 else
1148 report_error(PROJECT,
1149 _("Failed to fork() child"));
1151 else
1153 GString *message;
1154 MIME_type *type = shift ? &text_plain
1155 : item->mime_type;
1157 g_return_if_fail(type != NULL);
1159 if (type_open(full_path, type))
1161 if (close_window)
1162 destroy = TRUE;
1164 else
1166 message = g_string_new(NULL);
1167 g_string_sprintf(message,
1168 _("No run action specified for files of this type (%s/%s) - "
1169 "you can set a run action using by choosing `Set Run Action' "
1170 "from the Window menu"),
1171 type->media_type,
1172 type->subtype);
1173 report_error(PROJECT, message->str);
1174 g_string_free(message, TRUE);
1177 break;
1178 default:
1179 report_error("open_item",
1180 "I don't know how to open that");
1181 break;
1184 if (destroy)
1185 gtk_widget_destroy(filer_window->window);
1186 else
1188 if (wink)
1189 collection_wink_item(filer_window->collection,
1190 item_number);
1191 if (close_mini)
1192 minibuffer_hide(filer_window);
1196 static gint pointer_in(GtkWidget *widget,
1197 GdkEventCrossing *event,
1198 FilerWindow *filer_window)
1200 may_rescan(filer_window, TRUE);
1201 return FALSE;
1204 static gint focus_in(GtkWidget *widget,
1205 GdkEventFocus *event,
1206 FilerWindow *filer_window)
1208 window_with_focus = filer_window;
1210 return FALSE;
1213 static gint focus_out(GtkWidget *widget,
1214 GdkEventFocus *event,
1215 FilerWindow *filer_window)
1217 /* TODO: Shade the cursor */
1219 return FALSE;
1222 /* Handle keys that can't be bound with the menu */
1223 static gint key_press_event(GtkWidget *widget,
1224 GdkEventKey *event,
1225 FilerWindow *filer_window)
1227 switch (event->keyval)
1229 case GDK_BackSpace:
1230 change_to_parent(filer_window);
1231 break;
1232 default:
1233 return FALSE;
1236 return TRUE;
1239 static void toolbar_refresh_clicked(GtkWidget *widget,
1240 FilerWindow *filer_window)
1242 GdkEvent *event;
1244 event = gtk_get_current_event();
1245 if (event->type == GDK_BUTTON_RELEASE &&
1246 ((GdkEventButton *) event)->button == 3)
1248 filer_opendir(filer_window->path, PANEL_NO);
1250 else
1252 full_refresh();
1253 filer_update_dir(filer_window, TRUE);
1257 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window)
1259 GdkEvent *event;
1261 event = gtk_get_current_event();
1262 if (event->type == GDK_BUTTON_RELEASE &&
1263 ((GdkEventButton *) event)->button == 3)
1265 filer_opendir(home_dir, PANEL_NO);
1267 else
1268 filer_change_to(filer_window, home_dir, NULL);
1271 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window)
1273 GdkEvent *event;
1275 event = gtk_get_current_event();
1276 if (event->type == GDK_BUTTON_RELEASE &&
1277 ((GdkEventButton *) event)->button == 3)
1279 filer_open_parent(filer_window);
1281 else
1282 change_to_parent(filer_window);
1285 void change_to_parent(FilerWindow *filer_window)
1287 char *copy;
1288 char *slash;
1290 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
1291 return; /* Already in the root */
1293 copy = g_strdup(filer_window->path);
1294 slash = strrchr(copy, '/');
1296 if (slash)
1298 *slash = '\0';
1299 filer_change_to(filer_window,
1300 *copy ? copy : "/",
1301 slash + 1);
1303 else
1304 g_warning("No / in directory path!\n");
1306 g_free(copy);
1310 /* Make filer_window display path. When finished, highlight item 'from', or
1311 * the first item if from is NULL. If there is currently no cursor then
1312 * simply wink 'from' (if not NULL).
1314 void filer_change_to(FilerWindow *filer_window, char *path, char *from)
1316 char *from_dup;
1317 char *real_path = pathdup(path);
1319 if (o_unique_filer_windows)
1321 FilerWindow *fw;
1323 fw = find_filer_window(real_path, filer_window);
1324 if (fw)
1325 gtk_widget_destroy(fw->window);
1328 from_dup = from && *from ? g_strdup(from) : NULL;
1330 detach(filer_window);
1331 g_free(filer_window->path);
1332 filer_window->path = real_path;
1334 filer_window->directory = g_fscache_lookup(dir_cache,
1335 filer_window->path);
1336 if (filer_window->directory)
1338 g_free(filer_window->auto_select);
1339 filer_window->had_cursor =
1340 filer_window->collection->cursor_item != -1
1341 || filer_window->had_cursor;
1342 filer_window->auto_select = from_dup;
1344 filer_set_title(filer_window);
1345 collection_set_cursor_item(filer_window->collection, -1);
1346 attach(filer_window);
1348 if (filer_window->mini_type == MINI_PATH)
1349 gtk_idle_add((GtkFunction) minibuffer_show_cb,
1350 filer_window);
1352 else
1354 char *error;
1356 g_free(from_dup);
1357 error = g_strdup_printf(_("Directory '%s' is not accessible"),
1358 path);
1359 delayed_error(PROJECT, error);
1360 g_free(error);
1361 gtk_widget_destroy(filer_window->window);
1365 void filer_open_parent(FilerWindow *filer_window)
1367 char *copy;
1368 char *slash;
1370 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
1371 return; /* Already in the root */
1373 copy = g_strdup(filer_window->path);
1374 slash = strrchr(copy, '/');
1376 if (slash)
1378 *slash = '\0';
1379 filer_opendir(*copy ? copy : "/", PANEL_NO);
1381 else
1382 g_warning("No / in directory path!\n");
1384 g_free(copy);
1387 int selected_item_number(Collection *collection)
1389 int i;
1391 g_return_val_if_fail(collection != NULL, -1);
1392 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1393 g_return_val_if_fail(collection->number_selected == 1, -1);
1395 for (i = 0; i < collection->number_of_items; i++)
1396 if (collection->items[i].selected)
1397 return i;
1399 g_warning("selected_item: number_selected is wrong\n");
1401 return -1;
1404 DirItem *selected_item(Collection *collection)
1406 int item;
1408 item = selected_item_number(collection);
1410 if (item > -1)
1411 return (DirItem *) collection->items[item].data;
1412 return NULL;
1415 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
1416 FilerWindow *window)
1418 /* TODO: We can open lots of these - very irritating! */
1419 return get_choice(_("Close panel?"),
1420 _("You have tried to close a panel via the window "
1421 "manager - I usually find that this is accidental... "
1422 "really close?"),
1423 2, _("Remove"), _("Cancel")) != 0;
1426 /* Make the items as narrow as possible */
1427 static void shrink_width(FilerWindow *filer_window)
1429 int i;
1430 Collection *col = filer_window->collection;
1431 int width = MIN_ITEM_WIDTH;
1432 int this_width;
1433 DisplayStyle style = filer_window->display_style;
1434 int text_height;
1436 text_height = item_font->ascent + item_font->descent;
1438 for (i = 0; i < col->number_of_items; i++)
1440 this_width = calc_width(filer_window,
1441 (DirItem *) col->items[i].data);
1442 if (this_width > width)
1443 width = this_width;
1446 collection_set_item_size(filer_window->collection,
1447 width,
1448 style == FULL_INFO ? MAX_ICON_HEIGHT + 4 :
1449 style == SMALL_ICONS ? MAX(text_height, SMALL_ICON_HEIGHT) + 4
1450 : text_height + MAX_ICON_HEIGHT + 8);
1453 void filer_set_sort_fn(FilerWindow *filer_window,
1454 int (*fn)(const void *a, const void *b))
1456 if (filer_window->sort_fn == fn)
1457 return;
1459 filer_window->sort_fn = fn;
1460 last_sort_fn = fn;
1462 collection_qsort(filer_window->collection,
1463 filer_window->sort_fn);
1465 update_options_label();
1468 void filer_style_set(FilerWindow *filer_window, DisplayStyle style)
1470 if (filer_window->display_style == style)
1471 return;
1473 if (filer_window->panel_type)
1474 style = LARGE_ICONS;
1475 else
1476 last_display_style = style;
1478 filer_window->display_style = style;
1480 switch (style)
1482 case SMALL_ICONS:
1483 collection_set_functions(filer_window->collection,
1484 draw_item_small, test_point_small);
1485 break;
1486 case FULL_INFO:
1487 collection_set_functions(filer_window->collection,
1488 draw_item_full_info, test_point_full_info);
1489 break;
1490 default:
1491 collection_set_functions(filer_window->collection,
1492 draw_item_large, test_point_large);
1493 break;
1496 shrink_width(filer_window);
1498 update_options_label();
1501 FilerWindow *filer_opendir(char *path, PanelType panel_type)
1503 GtkWidget *hbox, *scrollbar, *collection;
1504 FilerWindow *filer_window;
1505 GtkTargetEntry target_table[] =
1507 {"text/uri-list", 0, TARGET_URI_LIST},
1508 {"STRING", 0, TARGET_STRING},
1510 char *real_path;
1512 real_path = pathdup(path);
1514 if (o_unique_filer_windows && panel_type == PANEL_NO)
1516 FilerWindow *fw;
1518 fw = find_filer_window(real_path, NULL);
1520 if (fw)
1522 /* TODO: this should bring the window to the front
1523 * at the same coordinates.
1525 gtk_widget_hide(fw->window);
1526 gtk_widget_show(fw->window);
1527 g_free(real_path);
1528 return fw;
1532 filer_window = g_new(FilerWindow, 1);
1533 filer_window->minibuffer = NULL;
1534 filer_window->minibuffer_label = NULL;
1535 filer_window->minibuffer_area = NULL;
1536 filer_window->path = real_path;
1537 filer_window->scanning = FALSE;
1538 filer_window->had_cursor = FALSE;
1539 filer_window->auto_select = NULL;
1540 filer_window->mini_type = MINI_NONE;
1542 filer_window->directory = g_fscache_lookup(dir_cache,
1543 filer_window->path);
1544 if (!filer_window->directory)
1546 char *error;
1548 error = g_strdup_printf(_("Directory '%s' not found."), path);
1549 delayed_error(PROJECT, error);
1550 g_free(error);
1551 g_free(filer_window->path);
1552 g_free(filer_window);
1553 return NULL;
1556 filer_window->show_hidden = last_show_hidden;
1557 filer_window->panel_type = panel_type;
1558 filer_window->temp_item_selected = FALSE;
1559 filer_window->sort_fn = last_sort_fn;
1560 filer_window->flags = (FilerFlags) 0;
1561 filer_window->display_style = UNKNOWN_STYLE;
1563 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1564 filer_set_title(filer_window);
1566 collection = collection_new(NULL);
1567 gtk_object_set_data(GTK_OBJECT(collection),
1568 "filer_window", filer_window);
1569 filer_window->collection = COLLECTION(collection);
1571 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1572 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1573 "enter-notify-event",
1574 GTK_SIGNAL_FUNC(pointer_in), filer_window);
1575 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
1576 GTK_SIGNAL_FUNC(focus_in), filer_window);
1577 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
1578 GTK_SIGNAL_FUNC(focus_out), filer_window);
1579 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1580 filer_window_destroyed, filer_window);
1582 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
1583 open_item, filer_window);
1584 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
1585 show_menu, filer_window);
1586 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
1587 gain_selection, filer_window);
1588 gtk_signal_connect(GTK_OBJECT(collection), "lose_selection",
1589 lose_selection, filer_window);
1590 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
1591 drag_selection, filer_window);
1592 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
1593 drag_data_get, filer_window);
1594 gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event",
1595 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1596 gtk_signal_connect (GTK_OBJECT(collection), "selection_get",
1597 GTK_SIGNAL_FUNC(selection_get), NULL);
1598 gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY,
1599 target_table,
1600 sizeof(target_table) / sizeof(*target_table));
1602 filer_style_set(filer_window, last_display_style);
1603 drag_set_dest(filer_window);
1605 if (panel_type)
1607 int swidth, sheight, iwidth, iheight;
1608 GtkWidget *frame, *win = filer_window->window;
1610 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Panel",
1611 PROJECT);
1612 collection_set_panel(filer_window->collection, TRUE);
1613 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1614 "delete_event",
1615 GTK_SIGNAL_FUNC(filer_confirm_close),
1616 filer_window);
1618 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
1619 iwidth = filer_window->collection->item_width;
1620 iheight = filer_window->collection->item_height;
1623 int height = iheight + PANEL_BORDER;
1624 int y = panel_type == PANEL_TOP
1625 ? -PANEL_BORDER
1626 : sheight - height - PANEL_BORDER;
1628 gtk_widget_set_usize(collection, swidth, height);
1629 gtk_widget_set_uposition(win, 0, y);
1632 frame = gtk_frame_new(NULL);
1633 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1634 gtk_container_add(GTK_CONTAINER(frame), collection);
1635 gtk_container_add(GTK_CONTAINER(win), frame);
1637 gtk_widget_show_all(frame);
1638 gtk_widget_realize(win);
1639 if (override_redirect)
1640 gdk_window_set_override_redirect(win->window, TRUE);
1641 make_panel_window(win->window);
1643 else
1645 GtkWidget *vbox;
1646 int col_height = ROW_HEIGHT_LARGE * 3;
1648 gtk_signal_connect(GTK_OBJECT(collection),
1649 "key_press_event",
1650 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1651 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
1652 filer_window->display_style == LARGE_ICONS ? 400 : 512,
1653 o_toolbar == TOOLBAR_NONE ? col_height:
1654 o_toolbar == TOOLBAR_NORMAL ? col_height + 24 :
1655 col_height + 38);
1657 hbox = gtk_hbox_new(FALSE, 0);
1658 gtk_container_add(GTK_CONTAINER(filer_window->window),
1659 hbox);
1661 vbox = gtk_vbox_new(FALSE, 0);
1662 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1664 if (o_toolbar != TOOLBAR_NONE)
1666 GtkWidget *toolbar;
1668 toolbar = create_toolbar(filer_window);
1669 gtk_box_pack_start(GTK_BOX(vbox), toolbar,
1670 FALSE, TRUE, 0);
1671 gtk_widget_show_all(toolbar);
1674 gtk_box_pack_start(GTK_BOX(vbox), collection, TRUE, TRUE, 0);
1676 create_minibuffer(filer_window);
1677 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1678 FALSE, TRUE, 0);
1680 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1681 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
1682 gtk_accel_group_attach(filer_keys,
1683 GTK_OBJECT(filer_window->window));
1684 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
1685 collection);
1687 gtk_widget_show(hbox);
1688 gtk_widget_show(vbox);
1689 gtk_widget_show(scrollbar);
1690 gtk_widget_show(collection);
1693 number_of_windows++;
1694 gtk_widget_show(filer_window->window);
1695 attach(filer_window);
1697 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1699 return filer_window;
1702 static gint clear_scanning_display(FilerWindow *filer_window)
1704 if (filer_exists(filer_window))
1705 filer_set_title(filer_window);
1706 return FALSE;
1709 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1711 if (scanning == filer_window->scanning)
1712 return;
1713 filer_window->scanning = scanning;
1715 if (scanning)
1716 filer_set_title(filer_window);
1717 else
1718 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1719 filer_window);
1722 static GtkWidget *create_toolbar(FilerWindow *filer_window)
1724 GtkWidget *frame, *box;
1726 if (o_toolbar == TOOLBAR_GNOME)
1728 frame = gtk_handle_box_new();
1729 box = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL,
1730 GTK_TOOLBAR_BOTH);
1731 gtk_container_set_border_width(GTK_CONTAINER(box), 2);
1732 gtk_toolbar_set_space_style(GTK_TOOLBAR(box),
1733 GTK_TOOLBAR_SPACE_LINE);
1734 gtk_toolbar_set_button_relief(GTK_TOOLBAR(box),
1735 GTK_RELIEF_NONE);
1737 else
1739 frame = gtk_frame_new(NULL);
1740 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1742 box = gtk_hbutton_box_new();
1743 gtk_button_box_set_child_size_default(16, 16);
1744 gtk_hbutton_box_set_spacing_default(2);
1745 gtk_button_box_set_layout(GTK_BUTTON_BOX(box),
1746 GTK_BUTTONBOX_START);
1749 gtk_container_add(GTK_CONTAINER(frame), box);
1751 add_button(box, TOOLBAR_UP_ICON,
1752 GTK_SIGNAL_FUNC(toolbar_up_clicked),
1753 filer_window,
1754 _("Up"), _("Change to parent directory"));
1755 add_button(box, TOOLBAR_HOME_ICON,
1756 GTK_SIGNAL_FUNC(toolbar_home_clicked),
1757 filer_window,
1758 _("Home"), _("Change to home directory"));
1759 add_button(box, TOOLBAR_REFRESH_ICON,
1760 GTK_SIGNAL_FUNC(toolbar_refresh_clicked),
1761 filer_window,
1762 _("Rescan"), _("Rescan directory contents"));
1764 return frame;
1767 /* This is used to simulate a click when button 3 is used (GtkButton
1768 * normally ignores this). Currently, this button does not pop in -
1769 * this may be fixed in future versions of GTK+.
1771 static gint toolbar_adjust_pressed(GtkButton *button,
1772 GdkEventButton *event,
1773 FilerWindow *filer_window)
1775 if (event->button == 3)
1777 gtk_grab_add(GTK_WIDGET(button));
1778 gtk_button_pressed(button);
1781 return TRUE;
1784 static gint toolbar_adjust_released(GtkButton *button,
1785 GdkEventButton *event,
1786 FilerWindow *filer_window)
1788 if (event->button == 3)
1790 gtk_grab_remove(GTK_WIDGET(button));
1791 gtk_button_released(button);
1794 return TRUE;
1797 static void add_button(GtkWidget *box, int pixmap,
1798 GtkSignalFunc cb, FilerWindow *filer_window,
1799 char *label, char *tip)
1801 GtkWidget *button, *icon;
1803 icon = gtk_pixmap_new(default_pixmap[pixmap]->pixmap,
1804 default_pixmap[pixmap]->mask);
1806 if (o_toolbar == TOOLBAR_GNOME)
1808 gtk_toolbar_append_element(GTK_TOOLBAR(box),
1809 GTK_TOOLBAR_CHILD_BUTTON,
1810 NULL,
1811 label,
1812 tip, NULL,
1813 icon,
1814 cb, filer_window);
1816 else
1818 button = gtk_button_new();
1819 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
1821 gtk_container_add(GTK_CONTAINER(button), icon);
1822 gtk_signal_connect(GTK_OBJECT(button), "button_press_event",
1823 GTK_SIGNAL_FUNC(toolbar_adjust_pressed), filer_window);
1824 gtk_signal_connect(GTK_OBJECT(button), "button_release_event",
1825 GTK_SIGNAL_FUNC(toolbar_adjust_released), filer_window);
1826 gtk_signal_connect(GTK_OBJECT(button), "clicked",
1827 cb, filer_window);
1829 gtk_tooltips_set_tip(tooltips, button, tip, NULL);
1831 gtk_container_add(GTK_CONTAINER(box), button);
1835 /* Build up some option widgets to go in the options dialog, but don't
1836 * fill them in yet.
1838 static GtkWidget *create_options()
1840 GtkWidget *vbox, *menu, *hbox;
1842 vbox = gtk_vbox_new(FALSE, 0);
1843 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1845 display_label = gtk_label_new("<>");
1846 gtk_label_set_line_wrap(GTK_LABEL(display_label), TRUE);
1847 gtk_box_pack_start(GTK_BOX(vbox), display_label, FALSE, TRUE, 0);
1849 toggle_sort_nocase =
1850 gtk_check_button_new_with_label(_("Ignore case when sorting"));
1851 gtk_box_pack_start(GTK_BOX(vbox), toggle_sort_nocase, FALSE, TRUE, 0);
1853 toggle_new_window_on_1 =
1854 gtk_check_button_new_with_label(
1855 _("New window on button 1 (RISC OS style)"));
1856 gtk_box_pack_start(GTK_BOX(vbox), toggle_new_window_on_1,
1857 FALSE, TRUE, 0);
1859 toggle_menu_on_2 =
1860 gtk_check_button_new_with_label(
1861 _("Menu on button 2 (RISC OS style)"));
1862 gtk_box_pack_start(GTK_BOX(vbox), toggle_menu_on_2, FALSE, TRUE, 0);
1864 toggle_single_click =
1865 gtk_check_button_new_with_label(_("Single-click nagivation"));
1866 gtk_box_pack_start(GTK_BOX(vbox), toggle_single_click, FALSE, TRUE, 0);
1868 toggle_unique_filer_windows =
1869 gtk_check_button_new_with_label(_("Unique windows"));
1870 gtk_box_pack_start(GTK_BOX(vbox), toggle_unique_filer_windows,
1871 FALSE, TRUE, 0);
1873 hbox = gtk_hbox_new(FALSE, 4);
1874 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
1876 gtk_box_pack_start(GTK_BOX(hbox),
1877 gtk_label_new(_("Toolbar type for new windows")),
1878 FALSE, TRUE, 0);
1879 menu_toolbar = gtk_option_menu_new();
1880 menu = gtk_menu_new();
1881 gtk_menu_append(GTK_MENU(menu),
1882 gtk_menu_item_new_with_label(_("None")));
1883 gtk_menu_append(GTK_MENU(menu),
1884 gtk_menu_item_new_with_label(_("Normal")));
1885 gtk_menu_append(GTK_MENU(menu),
1886 gtk_menu_item_new_with_label(_("GNOME")));
1887 gtk_option_menu_set_menu(GTK_OPTION_MENU(menu_toolbar), menu);
1888 gtk_box_pack_start(GTK_BOX(hbox), menu_toolbar, TRUE, TRUE, 0);
1890 return vbox;
1893 static void update_options_label(void)
1895 guchar *str;
1897 str = g_strdup_printf(_("The last used display style (%s) and sort "
1898 "function (Sort By %s) will be saved if you click on "
1899 "Save."), style_to_name(), sort_fn_to_name());
1900 gtk_label_set_text(GTK_LABEL(display_label), str);
1901 g_free(str);
1904 /* Reflect current state by changing the widgets in the options box */
1905 static void update_options()
1907 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_sort_nocase),
1908 o_sort_nocase);
1909 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_new_window_on_1),
1910 o_new_window_on_1);
1911 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_menu_on_2),
1912 collection_menu_button == 2 ? 1 : 0);
1913 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_single_click),
1914 o_single_click);
1915 gtk_toggle_button_set_active(
1916 GTK_TOGGLE_BUTTON(toggle_unique_filer_windows),
1917 o_unique_filer_windows);
1918 gtk_option_menu_set_history(GTK_OPTION_MENU(menu_toolbar), o_toolbar);
1920 update_options_label();
1923 /* Set current values by reading the states of the widgets in the options box */
1924 static void set_options()
1926 GtkWidget *item, *menu;
1927 GList *list;
1928 gboolean old_case = o_sort_nocase;
1930 o_sort_nocase = gtk_toggle_button_get_active(
1931 GTK_TOGGLE_BUTTON(toggle_sort_nocase));
1933 o_new_window_on_1 = gtk_toggle_button_get_active(
1934 GTK_TOGGLE_BUTTON(toggle_new_window_on_1));
1936 collection_menu_button = gtk_toggle_button_get_active(
1937 GTK_TOGGLE_BUTTON(toggle_menu_on_2)) ? 2 : 3;
1939 o_single_click = gtk_toggle_button_get_active(
1940 GTK_TOGGLE_BUTTON(toggle_single_click));
1942 o_unique_filer_windows = gtk_toggle_button_get_active(
1943 GTK_TOGGLE_BUTTON(toggle_unique_filer_windows));
1945 collection_single_click = o_single_click ? TRUE : FALSE;
1947 menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(menu_toolbar));
1948 item = gtk_menu_get_active(GTK_MENU(menu));
1949 list = gtk_container_children(GTK_CONTAINER(menu));
1950 o_toolbar = (ToolbarType) g_list_index(list, item);
1951 g_list_free(list);
1953 if (o_sort_nocase != old_case)
1955 GList *next = all_filer_windows;
1957 while (next)
1959 FilerWindow *filer_window = (FilerWindow *) next->data;
1961 collection_qsort(filer_window->collection,
1962 filer_window->sort_fn);
1964 next = next->next;
1969 static guchar *style_to_name(void)
1971 return last_display_style == LARGE_ICONS ? _("Large Icons") :
1972 last_display_style == SMALL_ICONS ? _("Small Icons") :
1973 _("Full Info");
1976 static guchar *sort_fn_to_name(void)
1978 return last_sort_fn == sort_by_name ? _("Name") :
1979 last_sort_fn == sort_by_type ? _("Type") :
1980 last_sort_fn == sort_by_date ? _("Date") :
1981 _("Size");
1984 static void save_options()
1986 option_write("filer_sort_nocase", o_sort_nocase ? "1" : "0");
1987 option_write("filer_new_window_on_1", o_new_window_on_1 ? "1" : "0");
1988 option_write("filer_menu_on_2",
1989 collection_menu_button == 2 ? "1" : "0");
1990 option_write("filer_single_click", o_single_click ? "1" : "0");
1991 option_write("filer_unique_windows",
1992 o_unique_filer_windows ? "1" : "0");
1993 option_write("filer_display_style",
1994 last_display_style == LARGE_ICONS ? "Large Icons" :
1995 last_display_style == SMALL_ICONS ? "Small Icons" :
1996 "Full Info");
1997 option_write("filer_sort_by",
1998 last_sort_fn == sort_by_name ? "Name" :
1999 last_sort_fn == sort_by_type ? "Type" :
2000 last_sort_fn == sort_by_date ? "Date" :
2001 "Size");
2002 option_write("filer_toolbar", o_toolbar == TOOLBAR_NONE ? "None" :
2003 o_toolbar == TOOLBAR_NORMAL ? "Normal" :
2004 o_toolbar == TOOLBAR_GNOME ? "GNOME" :
2005 "Unknown");
2008 static char *filer_sort_nocase(char *data)
2010 o_sort_nocase = atoi(data) != 0;
2011 return NULL;
2014 static char *filer_new_window_on_1(char *data)
2016 o_new_window_on_1 = atoi(data) != 0;
2017 return NULL;
2020 static char *filer_menu_on_2(char *data)
2022 collection_menu_button = atoi(data) != 0 ? 2 : 3;
2023 return NULL;
2026 static char *filer_single_click(char *data)
2028 o_single_click = atoi(data) != 0;
2029 collection_single_click = o_single_click ? TRUE : FALSE;
2030 return NULL;
2033 static char *filer_unique_windows(char *data)
2035 o_unique_filer_windows = atoi(data) != 0;
2036 return NULL;
2039 static char *filer_display_style(char *data)
2041 if (g_strcasecmp(data, "Large Icons") == 0)
2042 last_display_style = LARGE_ICONS;
2043 else if (g_strcasecmp(data, "Small Icons") == 0)
2044 last_display_style = SMALL_ICONS;
2045 else if (g_strcasecmp(data, "Full Info") == 0)
2046 last_display_style = FULL_INFO;
2047 else
2048 return _("Unknown display style");
2050 return NULL;
2053 static char *filer_sort_by(char *data)
2055 if (g_strcasecmp(data, "Name") == 0)
2056 last_sort_fn = sort_by_name;
2057 else if (g_strcasecmp(data, "Type") == 0)
2058 last_sort_fn = sort_by_type;
2059 else if (g_strcasecmp(data, "Date") == 0)
2060 last_sort_fn = sort_by_date;
2061 else if (g_strcasecmp(data, "Size") == 0)
2062 last_sort_fn = sort_by_size;
2063 else
2064 return _("Unknown sort type");
2066 return NULL;
2069 static char *filer_toolbar(char *data)
2071 if (g_strcasecmp(data, "None") == 0)
2072 o_toolbar = TOOLBAR_NONE;
2073 else if (g_strcasecmp(data, "Normal") == 0)
2074 o_toolbar = TOOLBAR_NORMAL;
2075 else if (g_strcasecmp(data, "GNOME") == 0)
2076 o_toolbar = TOOLBAR_GNOME;
2077 else
2078 return _("Unknown toolbar type");
2080 return NULL;
2083 /* Note that filer_window may not exist after this call. */
2084 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
2086 if (may_rescan(filer_window, warning))
2087 dir_update(filer_window->directory, filer_window->path);
2090 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
2092 Directory *dir = filer_window->directory;
2094 if (filer_window->show_hidden == hidden)
2095 return;
2097 filer_window->show_hidden = hidden;
2098 last_show_hidden = hidden;
2100 g_fscache_data_ref(dir_cache, dir);
2101 detach(filer_window);
2102 filer_window->directory = dir;
2103 attach(filer_window);
2106 /* Refresh the various caches even if we don't think we need to */
2107 void full_refresh(void)
2109 mount_update(TRUE);
2112 /* See whether a filer window with a given path already exists
2113 * and is different from diff.
2115 static FilerWindow *find_filer_window(char *path, FilerWindow *diff)
2117 GList *next = all_filer_windows;
2119 while (next)
2121 FilerWindow *filer_window = (FilerWindow *) next->data;
2123 if (filer_window->panel_type == PANEL_NO &&
2124 filer_window != diff &&
2125 strcmp(path, filer_window->path) == 0)
2127 return filer_window;
2130 next = next->next;
2133 return NULL;
2136 /* This path has been mounted/umounted/deleted some files - update all dirs */
2137 void filer_check_mounted(char *path)
2139 GList *next = all_filer_windows;
2140 int len;
2142 len = strlen(path);
2144 while (next)
2146 FilerWindow *filer_window = (FilerWindow *) next->data;
2148 next = next->next;
2150 if (strncmp(path, filer_window->path, len) == 0)
2152 char s = filer_window->path[len];
2154 if (s == '/' || s == '\0')
2155 filer_update_dir(filer_window, FALSE);
2160 /* Like minibuffer_show(), except that:
2161 * - It returns FALSE (to be used from an idle callback)
2162 * - It checks that the filer window still exists.
2164 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
2166 if (filer_exists(filer_window))
2167 minibuffer_show(filer_window, MINI_PATH);
2168 return FALSE;
2171 gboolean filer_exists(FilerWindow *filer_window)
2173 GList *next;
2175 for (next = all_filer_windows; next; next = next->next)
2177 FilerWindow *fw = (FilerWindow *) next->data;
2179 if (fw == filer_window)
2180 return TRUE;
2183 return FALSE;
2186 /* Highlight (wink or cursor) this item in the filer window. If the item
2187 * isn't already there but we're scanning then highlight it if it
2188 * appears later.
2190 void filer_set_autoselect(FilerWindow *filer_window, guchar *leaf)
2192 Collection *col = filer_window->collection;
2193 int i;
2195 g_free(filer_window->auto_select);
2196 filer_window->auto_select = NULL;
2198 for (i = 0; i < col->number_of_items; i++)
2200 DirItem *item = (DirItem *) col->items[i].data;
2202 if (strcmp(item->leafname, leaf) == 0)
2204 if (col->cursor_item != -1)
2205 collection_set_cursor_item(col, i);
2206 else
2207 collection_wink_item(col, i);
2208 return;
2212 filer_window->auto_select = g_strdup(leaf);
2215 static void filer_set_title(FilerWindow *filer_window)
2217 if (filer_window->scanning)
2219 guchar *title;
2221 title = g_strdup_printf(_("%s (Scanning)"), filer_window->path);
2222 gtk_window_set_title(GTK_WINDOW(filer_window->window),
2223 title);
2224 g_free(title);
2226 else
2227 gtk_window_set_title(GTK_WINDOW(filer_window->window),
2228 filer_window->path);