r163: Choosing something from the selection menu when nothing is selected
[rox-filer/ma.git] / ROX-Filer / src / filer.c
blobf8f29e11060cb34dbdf5daea729c6e3beca9af8e
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 1999, 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 <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <time.h>
31 #include <ctype.h>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkx.h>
35 #include <gdk/gdkkeysyms.h>
36 #include "collection.h"
38 #include "main.h"
39 #include "support.h"
40 #include "gui_support.h"
41 #include "filer.h"
42 #include "pixmaps.h"
43 #include "menu.h"
44 #include "dnd.h"
45 #include "run.h"
46 #include "mount.h"
47 #include "type.h"
48 #include "options.h"
49 #include "action.h"
51 #define ROW_HEIGHT_LARGE 64
52 #define ROW_HEIGHT_SMALL 32
53 #define ROW_HEIGHT_FULL_INFO 44
54 #define SMALL_ICON_HEIGHT 20
55 #define SMALL_ICON_WIDTH 48
56 #define MAX_ICON_HEIGHT 42
57 #define MAX_ICON_WIDTH 48
58 #define PANEL_BORDER 2
59 #define MIN_ITEM_WIDTH 64
61 extern int collection_menu_button;
62 extern gboolean collection_single_click;
64 FilerWindow *window_with_focus = NULL;
65 GdkFont *fixed_font = NULL;
66 GList *all_filer_windows = NULL;
68 static FilerWindow *window_with_selection = NULL;
70 /* Options bits */
71 static GtkWidget *create_options();
72 static void update_options();
73 static void set_options();
74 static void save_options();
75 static char *filer_single_click(char *data);
76 static char *filer_ro_bindings(char *data);
77 static char *filer_toolbar(char *data);
79 static OptionsSection options =
81 "Filer window options",
82 create_options,
83 update_options,
84 set_options,
85 save_options
87 static gboolean o_toolbar = TRUE;
88 static GtkWidget *toggle_toolbar;
89 static gboolean o_single_click = FALSE;
90 static GtkWidget *toggle_single_click;
91 static gboolean o_ro_bindings = FALSE;
92 static GtkWidget *toggle_ro_bindings;
94 /* Static prototypes */
95 static void attach(FilerWindow *filer_window);
96 static void detach(FilerWindow *filer_window);
97 static void filer_window_destroyed(GtkWidget *widget,
98 FilerWindow *filer_window);
99 void show_menu(Collection *collection, GdkEventButton *event,
100 int number_selected, gpointer user_data);
101 static gint focus_in(GtkWidget *widget,
102 GdkEventFocus *event,
103 FilerWindow *filer_window);
104 static gint focus_out(GtkWidget *widget,
105 GdkEventFocus *event,
106 FilerWindow *filer_window);
107 static void add_item(FilerWindow *filer_window, DirItem *item);
108 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window);
109 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window);
110 static void add_button(GtkContainer *box, int pixmap,
111 GtkSignalFunc cb, gpointer data);
112 static GtkWidget *create_toolbar(FilerWindow *filer_window);
113 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
114 FilerWindow *window);
115 static int calc_width(FilerWindow *filer_window, DirItem *item);
116 static void draw_large_icon(GtkWidget *widget,
117 GdkRectangle *area,
118 DirItem *item,
119 gboolean selected);
120 static void draw_string(GtkWidget *widget,
121 GdkFont *font,
122 char *string,
123 int x,
124 int y,
125 int width,
126 gboolean selected);
127 static void draw_item_large(GtkWidget *widget,
128 CollectionItem *item,
129 GdkRectangle *area);
130 static void draw_item_small(GtkWidget *widget,
131 CollectionItem *item,
132 GdkRectangle *area);
133 static void draw_item_full_info(GtkWidget *widget,
134 CollectionItem *colitem,
135 GdkRectangle *area);
136 static gboolean test_point_large(Collection *collection,
137 int point_x, int point_y,
138 CollectionItem *item,
139 int width, int height);
140 static gboolean test_point_small(Collection *collection,
141 int point_x, int point_y,
142 CollectionItem *item,
143 int width, int height);
144 static gboolean test_point_full_info(Collection *collection,
145 int point_x, int point_y,
146 CollectionItem *item,
147 int width, int height);
148 static void update_display(Directory *dir,
149 DirAction action,
150 GPtrArray *items,
151 FilerWindow *filer_window);
152 void filer_change_to(FilerWindow *filer_window, char *path);
153 static void shrink_width(FilerWindow *filer_window);
154 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
156 static GdkAtom xa_string;
157 enum
159 TARGET_STRING,
160 TARGET_URI_LIST,
163 static GdkCursor *busy_cursor = NULL;
165 void filer_init()
167 xa_string = gdk_atom_intern("STRING", FALSE);
169 options_sections = g_slist_prepend(options_sections, &options);
170 option_register("filer_ro_bindings", filer_ro_bindings);
171 option_register("filer_single_click", filer_single_click);
172 option_register("filer_toolbar", filer_toolbar);
174 fixed_font = gdk_font_load("fixed");
176 busy_cursor = gdk_cursor_new(GDK_WATCH);
179 static gboolean if_deleted(gpointer item, gpointer removed)
181 int i = ((GPtrArray *) removed)->len;
182 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
183 char *leafname = ((DirItem *) item)->leafname;
185 while (i--)
187 if (strcmp(leafname, r[i]->leafname) == 0)
188 return TRUE;
191 return FALSE;
194 static void update_item(FilerWindow *filer_window, DirItem *item)
196 int i;
197 char *leafname = item->leafname;
199 if (leafname[0] == '.')
201 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
202 || (leafname[1] == '.' && leafname[2] == '\0'))
203 return;
206 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
208 if (i >= 0)
209 collection_draw_item(filer_window->collection, i, TRUE);
210 else
211 g_warning("Failed to find '%s'\n", item->leafname);
214 static void update_display(Directory *dir,
215 DirAction action,
216 GPtrArray *items,
217 FilerWindow *filer_window)
219 int i;
221 switch (action)
223 case DIR_ADD:
224 for (i = 0; i < items->len; i++)
226 DirItem *item = (DirItem *) items->pdata[i];
228 add_item(filer_window, item);
231 collection_qsort(filer_window->collection,
232 filer_window->sort_fn);
233 break;
234 case DIR_REMOVE:
235 collection_delete_if(filer_window->collection,
236 if_deleted,
237 items);
238 break;
239 case DIR_END_SCAN:
240 if (filer_window->window->window)
241 gdk_window_set_cursor(
242 filer_window->window->window,
243 NULL);
244 shrink_width(filer_window);
245 break;
246 case DIR_UPDATE:
247 for (i = 0; i < items->len; i++)
249 DirItem *item = (DirItem *) items->pdata[i];
251 update_item(filer_window, item);
253 break;
257 static void attach(FilerWindow *filer_window)
259 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
260 collection_clear(filer_window->collection);
261 dir_attach(filer_window->directory, (DirCallback) update_display,
262 filer_window);
265 static void detach(FilerWindow *filer_window)
267 g_return_if_fail(filer_window->directory != NULL);
269 dir_detach(filer_window->directory,
270 (DirCallback) update_display, filer_window);
271 g_fscache_data_unref(dir_cache, filer_window->directory);
272 filer_window->directory = NULL;
275 static void filer_window_destroyed(GtkWidget *widget,
276 FilerWindow *filer_window)
278 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
280 if (window_with_selection == filer_window)
281 window_with_selection = NULL;
282 if (window_with_focus == filer_window)
283 window_with_focus = NULL;
285 if (filer_window->directory)
286 detach(filer_window);
288 g_free(filer_window->path);
289 g_free(filer_window);
291 if (--number_of_windows < 1)
292 gtk_main_quit();
295 static int calc_width(FilerWindow *filer_window, DirItem *item)
297 int pix_width = item->image->width;
299 switch (filer_window->display_style)
301 case FULL_INFO:
302 return MAX_ICON_WIDTH + 12 +
303 MAX(item->details_width, item->name_width);
304 case SMALL_ICONS:
305 return SMALL_ICON_WIDTH + 12 + item->name_width;
306 default:
307 return MAX(pix_width, item->name_width) + 4;
311 /* Add a single object to a directory display */
312 static void add_item(FilerWindow *filer_window, DirItem *item)
314 char *leafname = item->leafname;
315 int item_width;
317 if (leafname[0] == '.')
319 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
320 || (leafname[1] == '.' && leafname[2] == '\0'))
321 return;
324 item_width = calc_width(filer_window, item);
325 if (item_width > filer_window->collection->item_width)
326 collection_set_item_size(filer_window->collection,
327 item_width,
328 filer_window->collection->item_height);
329 collection_insert(filer_window->collection, item);
332 /* Is a point inside an item? */
333 static gboolean test_point_large(Collection *collection,
334 int point_x, int point_y,
335 CollectionItem *colitem,
336 int width, int height)
338 DirItem *item = (DirItem *) colitem->data;
339 GdkFont *font = GTK_WIDGET(collection)->style->font;
340 int text_height = font->ascent + font->descent;
341 MaskedPixmap *image = item->image;
342 int image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
343 int image_width = (image->width >> 1) + 2;
344 int text_width = (item->name_width >> 1) + 2;
345 int x_limit;
347 if (point_y < image_y)
348 return FALSE; /* Too high up (don't worry about too low) */
350 if (point_y <= image_y + image->height + 2)
351 x_limit = image_width;
352 else if (point_y > height - text_height - 2)
353 x_limit = text_width;
354 else
355 x_limit = MIN(image_width, text_width);
357 return ABS(point_x - (width >> 1)) < x_limit;
360 static gboolean test_point_full_info(Collection *collection,
361 int point_x, int point_y,
362 CollectionItem *colitem,
363 int width, int height)
365 DirItem *item = (DirItem *) colitem->data;
366 GdkFont *font = GTK_WIDGET(collection)->style->font;
367 MaskedPixmap *image = item->image;
368 int image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
369 int low_top = height
370 - fixed_font->descent - 2 - fixed_font->ascent;
372 if (point_x < image->width + 2)
373 return point_x > 2 && point_y > image_y;
375 point_x -= MAX_ICON_WIDTH + 8;
377 if (point_y >= low_top)
378 return point_x < item->details_width;
379 if (point_y >= low_top - font->ascent - font->descent)
380 return point_x < item->name_width;
381 return FALSE;
384 static gboolean test_point_small(Collection *collection,
385 int point_x, int point_y,
386 CollectionItem *colitem,
387 int width, int height)
389 DirItem *item = (DirItem *) colitem->data;
390 MaskedPixmap *image = item->image;
391 int image_y = MAX(0, SMALL_ICON_HEIGHT - image->height);
392 GdkFont *font = GTK_WIDGET(collection)->style->font;
393 int low_top = height
394 - fixed_font->descent - 2 - font->ascent;
395 int iwidth = MIN(SMALL_ICON_WIDTH, image->width);
397 if (point_x < iwidth + 2)
398 return point_x > 2 && point_y > image_y;
400 point_x -= SMALL_ICON_WIDTH + 4;
402 if (point_y >= low_top)
403 return point_x < item->name_width;
404 return FALSE;
407 static void draw_small_icon(GtkWidget *widget,
408 GdkRectangle *area,
409 DirItem *item,
410 gboolean selected)
412 MaskedPixmap *image = item->image;
413 int width = MIN(image->width, SMALL_ICON_WIDTH);
414 int height = MIN(image->height, SMALL_ICON_HEIGHT);
415 int image_x = area->x + ((area->width - width) >> 1);
416 int image_y;
417 GdkGC *gc = selected ? widget->style->white_gc
418 : widget->style->black_gc;
419 if (!item->image)
420 return;
422 gdk_gc_set_clip_mask(gc, item->image->mask);
424 image_y = MAX(0, SMALL_ICON_HEIGHT - image->height);
425 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
426 gdk_draw_pixmap(widget->window, gc,
427 item->image->pixmap,
428 0, 0, /* Source x,y */
429 image_x, area->y + image_y, /* Dest x,y */
430 width, height);
432 if (selected)
434 gdk_gc_set_function(gc, GDK_INVERT);
435 gdk_draw_rectangle(widget->window,
437 TRUE, image_x, area->y + image_y,
438 width, height);
439 gdk_gc_set_function(gc, GDK_COPY);
442 if (item->flags & ITEM_FLAG_SYMLINK)
444 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
445 gdk_gc_set_clip_mask(gc,
446 default_pixmap[TYPE_SYMLINK].mask);
447 gdk_draw_pixmap(widget->window, gc,
448 default_pixmap[TYPE_SYMLINK].pixmap,
449 0, 0, /* Source x,y */
450 image_x, area->y + 8, /* Dest x,y */
451 -1, -1);
453 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
455 int type = item->flags & ITEM_FLAG_MOUNTED
456 ? TYPE_MOUNTED
457 : TYPE_UNMOUNTED;
458 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
459 gdk_gc_set_clip_mask(gc,
460 default_pixmap[type].mask);
461 gdk_draw_pixmap(widget->window, gc,
462 default_pixmap[type].pixmap,
463 0, 0, /* Source x,y */
464 image_x, area->y + 8, /* Dest x,y */
465 -1, -1);
468 gdk_gc_set_clip_mask(gc, NULL);
469 gdk_gc_set_clip_origin(gc, 0, 0);
472 static void draw_large_icon(GtkWidget *widget,
473 GdkRectangle *area,
474 DirItem *item,
475 gboolean selected)
477 MaskedPixmap *image = item->image;
478 int width = MIN(image->width, MAX_ICON_WIDTH);
479 int height = MIN(image->height, MAX_ICON_WIDTH);
480 int image_x = area->x + ((area->width - width) >> 1);
481 int image_y;
482 GdkGC *gc = selected ? widget->style->white_gc
483 : widget->style->black_gc;
485 gdk_gc_set_clip_mask(gc, item->image->mask);
487 image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
488 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
489 gdk_draw_pixmap(widget->window, gc,
490 item->image->pixmap,
491 0, 0, /* Source x,y */
492 image_x, area->y + image_y, /* Dest x,y */
493 width, height);
495 if (selected)
497 gdk_gc_set_function(gc, GDK_INVERT);
498 gdk_draw_rectangle(widget->window,
500 TRUE, image_x, area->y + image_y,
501 width, height);
502 gdk_gc_set_function(gc, GDK_COPY);
505 if (item->flags & ITEM_FLAG_SYMLINK)
507 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
508 gdk_gc_set_clip_mask(gc,
509 default_pixmap[TYPE_SYMLINK].mask);
510 gdk_draw_pixmap(widget->window, gc,
511 default_pixmap[TYPE_SYMLINK].pixmap,
512 0, 0, /* Source x,y */
513 image_x, area->y + 8, /* Dest x,y */
514 -1, -1);
516 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
518 int type = item->flags & ITEM_FLAG_MOUNTED
519 ? TYPE_MOUNTED
520 : TYPE_UNMOUNTED;
521 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
522 gdk_gc_set_clip_mask(gc,
523 default_pixmap[type].mask);
524 gdk_draw_pixmap(widget->window, gc,
525 default_pixmap[type].pixmap,
526 0, 0, /* Source x,y */
527 image_x, area->y + 8, /* Dest x,y */
528 -1, -1);
531 gdk_gc_set_clip_mask(gc, NULL);
532 gdk_gc_set_clip_origin(gc, 0, 0);
535 static void draw_string(GtkWidget *widget,
536 GdkFont *font,
537 char *string,
538 int x,
539 int y,
540 int width,
541 gboolean selected)
543 int text_height = font->ascent + font->descent;
545 if (selected)
546 gtk_paint_flat_box(widget->style, widget->window,
547 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
548 NULL, widget, "text",
549 x, y - font->ascent,
550 width,
551 text_height);
553 gdk_draw_text(widget->window,
554 font,
555 selected ? widget->style->white_gc
556 : widget->style->black_gc,
557 x, y,
558 string, strlen(string));
561 /* Return a string (valid until next call) giving details
562 * of this item.
564 char *details(DirItem *item)
566 mode_t m = item->mode;
567 static GString *buf = NULL;
569 if (!buf)
570 buf = g_string_new(NULL);
572 g_string_sprintf(buf, "%s, %c%c%c:%c%c%c:%c%c%c:%c%c"
573 #ifdef S_ISVTX
574 "%c"
575 #endif
576 ", %s",
577 S_ISDIR(m) ? "Dir" :
578 S_ISCHR(m) ? "Char" :
579 S_ISBLK(m) ? "Blck" :
580 S_ISLNK(m) ? "Link" :
581 S_ISSOCK(m) ? "Sock" :
582 S_ISFIFO(m) ? "Pipe" : "File",
584 m & S_IRUSR ? 'r' : '-',
585 m & S_IWUSR ? 'w' : '-',
586 m & S_IXUSR ? 'x' : '-',
588 m & S_IRGRP ? 'r' : '-',
589 m & S_IWGRP ? 'w' : '-',
590 m & S_IXGRP ? 'x' : '-',
592 m & S_IROTH ? 'r' : '-',
593 m & S_IWOTH ? 'w' : '-',
594 m & S_IXOTH ? 'x' : '-',
596 m & S_ISUID ? 'U' : '-',
597 m & S_ISGID ? 'G' : '-',
598 #ifdef S_ISVTX
599 m & S_ISVTX ? 'T' : '-',
600 #endif
601 format_size(item->size));
603 return buf->str;
606 static void draw_item_full_info(GtkWidget *widget,
607 CollectionItem *colitem,
608 GdkRectangle *area)
610 DirItem *item = (DirItem *) colitem->data;
611 MaskedPixmap *image = item->image;
612 GdkFont *font = widget->style->font;
613 int text_x = area->x + MAX_ICON_WIDTH + 8;
614 int low_text_y = area->y + area->height - fixed_font->descent - 2;
615 gboolean selected = colitem->selected;
616 GdkRectangle pic_area;
618 pic_area.x = area->x;
619 pic_area.y = area->y;
620 pic_area.width = image->width + 8;
621 pic_area.height = area->height;
623 draw_large_icon(widget, &pic_area, item, selected);
625 draw_string(widget,
626 widget->style->font,
627 item->leafname,
628 text_x,
629 low_text_y - font->descent - fixed_font->ascent,
630 item->name_width,
631 selected);
632 draw_string(widget,
633 fixed_font,
634 details(item),
635 text_x, low_text_y,
636 item->details_width,
637 selected);
640 static void draw_item_small(GtkWidget *widget,
641 CollectionItem *colitem,
642 GdkRectangle *area)
644 DirItem *item = (DirItem *) colitem->data;
645 GdkFont *font = widget->style->font;
646 int text_x = area->x + SMALL_ICON_WIDTH + 4;
647 int low_text_y = area->y + area->height - font->descent - 2;
648 gboolean selected = colitem->selected;
649 GdkRectangle pic_area;
651 pic_area.x = area->x;
652 pic_area.y = area->y;
653 pic_area.width = SMALL_ICON_WIDTH;
654 pic_area.height = SMALL_ICON_HEIGHT;
656 draw_small_icon(widget, &pic_area, item, selected);
658 draw_string(widget,
659 widget->style->font,
660 item->leafname,
661 text_x,
662 low_text_y,
663 item->name_width,
664 selected);
667 static void draw_item_large(GtkWidget *widget,
668 CollectionItem *colitem,
669 GdkRectangle *area)
671 DirItem *item = (DirItem *) colitem->data;
672 GdkFont *font = widget->style->font;
673 int text_x = area->x + ((area->width - item->name_width) >> 1);
674 int text_y = area->y + area->height - font->descent - 2;
675 gboolean selected = colitem->selected;
677 draw_large_icon(widget, area, item, selected);
679 draw_string(widget,
680 widget->style->font,
681 item->leafname,
682 text_x, text_y, item->name_width,
683 selected);
686 void show_menu(Collection *collection, GdkEventButton *event,
687 int item, gpointer user_data)
689 show_filer_menu((FilerWindow *) user_data, event, item);
692 /* Returns TRUE iff the directory still exits. */
693 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
695 Directory *dir;
697 g_return_val_if_fail(filer_window != NULL, FALSE);
699 /* We do a fresh lookup (rather than update) because the inode may
700 * have changed.
702 dir = g_fscache_lookup(dir_cache, filer_window->path);
703 if (!dir)
705 if (warning)
706 delayed_error("ROX-Filer", "Directory missing/deleted");
707 gtk_widget_destroy(filer_window->window);
708 return FALSE;
710 if (dir == filer_window->directory)
711 g_fscache_data_unref(dir_cache, dir);
712 else
714 detach(filer_window);
715 filer_window->directory = dir;
716 attach(filer_window);
719 return TRUE;
722 /* Another app has grabbed the selection */
723 static gint collection_lose_selection(GtkWidget *widget,
724 GdkEventSelection *event)
726 if (window_with_selection &&
727 window_with_selection->collection == COLLECTION(widget))
729 FilerWindow *filer_window = window_with_selection;
730 window_with_selection = NULL;
731 collection_clear_selection(filer_window->collection);
734 return TRUE;
737 /* Someone wants us to send them the selection */
738 static void selection_get(GtkWidget *widget,
739 GtkSelectionData *selection_data,
740 guint info,
741 guint time,
742 gpointer data)
744 GString *reply, *header;
745 FilerWindow *filer_window;
746 int i;
747 Collection *collection;
749 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
751 reply = g_string_new(NULL);
752 header = g_string_new(NULL);
754 switch (info)
756 case TARGET_STRING:
757 g_string_sprintf(header, " %s",
758 make_path(filer_window->path, "")->str);
759 break;
760 case TARGET_URI_LIST:
761 g_string_sprintf(header, " file://%s%s",
762 our_host_name(),
763 make_path(filer_window->path, "")->str);
764 break;
767 collection = filer_window->collection;
768 for (i = 0; i < collection->number_of_items; i++)
770 if (collection->items[i].selected)
772 DirItem *item =
773 (DirItem *) collection->items[i].data;
775 g_string_append(reply, header->str);
776 g_string_append(reply, item->leafname);
779 /* This works, but I don't think I like it... */
780 /* g_string_append_c(reply, ' '); */
782 gtk_selection_data_set(selection_data, xa_string,
783 8, reply->str + 1, reply->len - 1);
784 g_string_free(reply, TRUE);
785 g_string_free(header, TRUE);
788 /* No items are now selected. This might be because another app claimed
789 * the selection or because the user unselected all the items.
791 static void lose_selection(Collection *collection,
792 guint time,
793 gpointer user_data)
795 FilerWindow *filer_window = (FilerWindow *) user_data;
797 if (window_with_selection == filer_window)
799 window_with_selection = NULL;
800 gtk_selection_owner_set(NULL,
801 GDK_SELECTION_PRIMARY,
802 time);
806 static void gain_selection(Collection *collection,
807 guint time,
808 gpointer user_data)
810 FilerWindow *filer_window = (FilerWindow *) user_data;
812 if (gtk_selection_owner_set(GTK_WIDGET(collection),
813 GDK_SELECTION_PRIMARY,
814 time))
816 window_with_selection = filer_window;
818 else
819 collection_clear_selection(filer_window->collection);
822 int sort_by_name(const void *item1, const void *item2)
824 return strcmp((*((DirItem **)item1))->leafname,
825 (*((DirItem **)item2))->leafname);
828 int sort_by_type(const void *item1, const void *item2)
830 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
831 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
832 MIME_type *m1, *m2;
834 int diff = i1->base_type - i2->base_type;
836 if (!diff)
837 diff = (i1->flags & ITEM_FLAG_APPDIR)
838 - (i2->flags & ITEM_FLAG_APPDIR);
839 if (diff)
840 return diff > 0 ? 1 : -1;
842 m1 = i1->mime_type;
843 m2 = i2->mime_type;
845 if (m1 && m2)
847 diff = strcmp(m1->media_type, m2->media_type);
848 if (!diff)
849 diff = strcmp(m1->subtype, m2->subtype);
851 else if (m1 || m2)
852 diff = m1 ? 1 : -1;
853 else
854 diff = 0;
856 if (diff)
857 return diff > 0 ? 1 : -1;
859 return sort_by_name(item1, item2);
862 int sort_by_date(const void *item1, const void *item2)
864 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
865 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
867 return i1->mtime > i2->mtime ? -1 :
868 i1->mtime < i2->mtime ? 1 :
869 sort_by_name(item1, item2);
872 int sort_by_size(const void *item1, const void *item2)
874 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
875 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
877 return i1->size > i2->size ? -1 :
878 i1->size < i2->size ? 1 :
879 sort_by_name(item1, item2);
882 void open_item(Collection *collection,
883 gpointer item_data, int item_number,
884 gpointer user_data)
886 FilerWindow *filer_window = (FilerWindow *) user_data;
887 DirItem *item = (DirItem *) item_data;
888 GdkEventButton *event;
889 gboolean shift;
890 gboolean adjust; /* do alternative action */
892 collection_wink_item(filer_window->collection, item_number);
894 event = (GdkEventButton *) gtk_get_current_event();
895 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_BUTTON_PRESS
896 || event->type == GDK_BUTTON_RELEASE)
898 shift = event->state & GDK_SHIFT_MASK;
899 adjust = (event->button != 1)
900 ^ ((event->state & GDK_CONTROL_MASK) != 0
901 && o_single_click == 0);
903 else
905 shift = FALSE;
906 adjust = FALSE;
909 filer_openitem(filer_window, item, shift, adjust);
912 void filer_openitem(FilerWindow *filer_window, DirItem *item,
913 gboolean shift, gboolean adjust)
915 GtkWidget *widget;
916 char *full_path;
918 widget = filer_window->window;
919 full_path = make_path(filer_window->path,
920 item->leafname)->str;
922 switch (item->base_type)
924 case TYPE_DIRECTORY:
925 if (item->flags & ITEM_FLAG_APPDIR && !shift)
927 run_app(make_path(filer_window->path,
928 item->leafname)->str);
929 if (adjust && !filer_window->panel)
930 gtk_widget_destroy(widget);
931 break;
934 if (item->flags & ITEM_FLAG_MOUNT_POINT && shift)
936 action_mount(filer_window, item);
937 if (item->flags & ITEM_FLAG_MOUNTED)
938 break;
941 if ((adjust ^ o_ro_bindings) || filer_window->panel)
942 filer_opendir(full_path, FALSE, BOTTOM);
943 else
944 filer_change_to(filer_window, full_path);
945 break;
946 case TYPE_FILE:
947 if (item->flags & ITEM_FLAG_EXEC_FILE
948 && !shift)
950 char *argv[] = {NULL, NULL};
952 argv[0] = full_path;
954 if (spawn_full(argv, getenv("HOME"), 0))
956 if (adjust && !filer_window->panel)
957 gtk_widget_destroy(widget);
959 else
960 report_error("ROX-Filer",
961 "Failed to fork() child");
963 else
965 GString *message;
966 MIME_type *type = shift ? &text_plain
967 : item->mime_type;
969 g_return_if_fail(type != NULL);
971 if (type_open(full_path, type))
973 if (adjust && !filer_window->panel)
974 gtk_widget_destroy(widget);
976 else
978 message = g_string_new(NULL);
979 g_string_sprintf(message, "No open "
980 "action specified for files of "
981 "this type (%s/%s)",
982 type->media_type,
983 type->subtype);
984 report_error("ROX-Filer", message->str);
985 g_string_free(message, TRUE);
988 break;
989 default:
990 report_error("open_item",
991 "I don't know how to open that");
992 break;
996 static gint pointer_in(GtkWidget *widget,
997 GdkEventCrossing *event,
998 FilerWindow *filer_window)
1000 may_rescan(filer_window, TRUE);
1001 return FALSE;
1004 static gint focus_in(GtkWidget *widget,
1005 GdkEventFocus *event,
1006 FilerWindow *filer_window)
1008 window_with_focus = filer_window;
1010 return FALSE;
1013 static gint focus_out(GtkWidget *widget,
1014 GdkEventFocus *event,
1015 FilerWindow *filer_window)
1017 /* TODO: Shade the cursor */
1019 return FALSE;
1022 /* Handle keys that can't be bound with the menu */
1023 static gint key_press_event(GtkWidget *widget,
1024 GdkEventKey *event,
1025 FilerWindow *filer_window)
1027 switch (event->keyval)
1030 case GDK_Left:
1031 move_cursor(-1, 0);
1032 break;
1033 case GDK_Right:
1034 move_cursor(1, 0);
1035 break;
1036 case GDK_Up:
1037 move_cursor(0, -1);
1038 break;
1039 case GDK_Down:
1040 move_cursor(0, 1);
1041 break;
1042 case GDK_Return:
1044 case GDK_BackSpace:
1045 change_to_parent(filer_window);
1046 return TRUE;
1049 return FALSE;
1052 static void toolbar_refresh_clicked(GtkWidget *widget,
1053 FilerWindow *filer_window)
1055 full_refresh();
1056 update_dir(filer_window, TRUE);
1059 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window)
1061 filer_change_to(filer_window, getenv("HOME"));
1064 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window)
1066 change_to_parent(filer_window);
1069 void change_to_parent(FilerWindow *filer_window)
1071 filer_change_to(filer_window, make_path(filer_window->path, "..")->str);
1074 void filer_change_to(FilerWindow *filer_window, char *path)
1076 detach(filer_window);
1077 g_free(filer_window->path);
1078 filer_window->path = pathdup(path);
1080 filer_window->directory = g_fscache_lookup(dir_cache,
1081 filer_window->path);
1082 if (filer_window->directory)
1084 gtk_window_set_title(GTK_WINDOW(filer_window->window),
1085 filer_window->path);
1086 attach(filer_window);
1088 else
1090 char *error;
1092 error = g_strdup_printf("Directory '%s' is not accessible.",
1093 path);
1094 delayed_error("ROX-Filer", error);
1095 g_free(error);
1096 gtk_widget_destroy(filer_window->window);
1100 DirItem *selected_item(Collection *collection)
1102 int i;
1104 g_return_val_if_fail(collection != NULL, NULL);
1105 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
1106 g_return_val_if_fail(collection->number_selected == 1, NULL);
1108 for (i = 0; i < collection->number_of_items; i++)
1109 if (collection->items[i].selected)
1110 return (DirItem *) collection->items[i].data;
1112 g_warning("selected_item: number_selected is wrong\n");
1114 return NULL;
1117 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
1118 FilerWindow *window)
1120 /* TODO: We can open lots of these - very irritating! */
1121 return get_choice("Close panel?",
1122 "You have tried to close a panel via the window "
1123 "manager - I usually find that this is accidental... "
1124 "really close?",
1125 2, "Remove", "Cancel") != 0;
1128 /* Make the items as narrow as possible */
1129 static void shrink_width(FilerWindow *filer_window)
1131 int i;
1132 Collection *col = filer_window->collection;
1133 int width = MIN_ITEM_WIDTH;
1134 int this_width;
1135 DisplayStyle style = filer_window->display_style;
1136 GdkFont *font;
1137 int text_height;
1139 font = gtk_widget_get_default_style()->font;
1140 text_height = font->ascent + font->descent;
1142 for (i = 0; i < col->number_of_items; i++)
1144 this_width = calc_width(filer_window,
1145 (DirItem *) col->items[i].data);
1146 if (this_width > width)
1147 width = this_width;
1150 collection_set_item_size(filer_window->collection,
1151 width,
1152 style == FULL_INFO ? MAX_ICON_HEIGHT + 4 :
1153 style == SMALL_ICONS ? MAX(text_height, SMALL_ICON_HEIGHT) + 4
1154 : text_height + MAX_ICON_HEIGHT + 8);
1157 void filer_set_sort_fn(FilerWindow *filer_window,
1158 int (*fn)(const void *a, const void *b))
1160 if (filer_window->sort_fn == fn)
1161 return;
1163 filer_window->sort_fn = fn;
1164 collection_qsort(filer_window->collection,
1165 filer_window->sort_fn);
1168 void filer_style_set(FilerWindow *filer_window, DisplayStyle style)
1170 if (filer_window->display_style == style)
1171 return;
1173 filer_window->display_style = style;
1174 switch (style)
1176 case SMALL_ICONS:
1177 collection_set_functions(filer_window->collection,
1178 draw_item_small, test_point_small);
1179 break;
1180 case FULL_INFO:
1181 collection_set_functions(filer_window->collection,
1182 draw_item_full_info, test_point_full_info);
1183 break;
1184 default:
1185 collection_set_functions(filer_window->collection,
1186 draw_item_large, test_point_large);
1187 break;
1190 shrink_width(filer_window);
1193 void filer_opendir(char *path, gboolean panel, Side panel_side)
1195 GtkWidget *hbox, *scrollbar, *collection;
1196 FilerWindow *filer_window;
1197 GtkTargetEntry target_table[] =
1199 {"text/uri-list", 0, TARGET_URI_LIST},
1200 {"STRING", 0, TARGET_STRING},
1203 filer_window = g_new(FilerWindow, 1);
1204 filer_window->path = pathdup(path);
1206 filer_window->directory = g_fscache_lookup(dir_cache,
1207 filer_window->path);
1208 if (!filer_window->directory)
1210 char *error;
1212 error = g_strdup_printf("Directory '%s' not found.", path);
1213 delayed_error("ROX-Filer", error);
1214 g_free(error);
1215 g_free(filer_window->path);
1216 g_free(filer_window);
1217 return;
1220 filer_window->show_hidden = FALSE;
1221 filer_window->panel = panel;
1222 filer_window->panel_side = panel_side;
1223 filer_window->temp_item_selected = FALSE;
1224 filer_window->sort_fn = sort_by_type;
1225 filer_window->flags = (FilerFlags) 0;
1226 filer_window->display_style = UNKNOWN_STYLE;
1228 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1229 gtk_window_set_title(GTK_WINDOW(filer_window->window),
1230 filer_window->path);
1232 collection = collection_new(NULL);
1233 gtk_object_set_data(GTK_OBJECT(collection),
1234 "filer_window", filer_window);
1235 filer_window->collection = COLLECTION(collection);
1237 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1238 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1239 "enter-notify-event",
1240 GTK_SIGNAL_FUNC(pointer_in), filer_window);
1241 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
1242 GTK_SIGNAL_FUNC(focus_in), filer_window);
1243 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
1244 GTK_SIGNAL_FUNC(focus_out), filer_window);
1245 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1246 filer_window_destroyed, filer_window);
1248 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
1249 open_item, filer_window);
1250 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
1251 show_menu, filer_window);
1252 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
1253 gain_selection, filer_window);
1254 gtk_signal_connect(GTK_OBJECT(collection), "lose_selection",
1255 lose_selection, filer_window);
1256 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
1257 drag_selection, filer_window);
1258 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
1259 drag_data_get, filer_window);
1260 gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event",
1261 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1262 gtk_signal_connect (GTK_OBJECT(collection), "selection_get",
1263 GTK_SIGNAL_FUNC(selection_get), NULL);
1264 gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY,
1265 target_table,
1266 sizeof(target_table) / sizeof(*target_table));
1268 filer_style_set(filer_window, LARGE_ICONS);
1269 drag_set_dest(collection);
1271 if (panel)
1273 int swidth, sheight, iwidth, iheight;
1274 GtkWidget *frame, *win = filer_window->window;
1276 collection_set_panel(filer_window->collection, TRUE);
1277 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1278 "delete_event",
1279 GTK_SIGNAL_FUNC(filer_confirm_close),
1280 filer_window);
1282 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
1283 iwidth = filer_window->collection->item_width;
1284 iheight = filer_window->collection->item_height;
1286 if (panel_side == TOP || panel_side == BOTTOM)
1288 int height = iheight + PANEL_BORDER;
1289 int y = panel_side == TOP
1290 ? -PANEL_BORDER
1291 : sheight - height - PANEL_BORDER;
1293 gtk_widget_set_usize(collection, swidth, height);
1294 gtk_widget_set_uposition(win, 0, y);
1296 else
1298 int width = iwidth + PANEL_BORDER;
1299 int x = panel_side == LEFT
1300 ? -PANEL_BORDER
1301 : swidth - width - PANEL_BORDER;
1303 gtk_widget_set_usize(collection, width, sheight);
1304 gtk_widget_set_uposition(win, x, 0);
1307 frame = gtk_frame_new(NULL);
1308 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1309 gtk_container_add(GTK_CONTAINER(frame), collection);
1310 gtk_container_add(GTK_CONTAINER(win), frame);
1312 gtk_widget_realize(win);
1313 if (override_redirect)
1314 gdk_window_set_override_redirect(win->window, TRUE);
1315 make_panel_window(win->window);
1317 else
1319 hbox = gtk_hbox_new(FALSE, 0);
1321 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1322 "key_press_event",
1323 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1324 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
1325 filer_window->display_style == LARGE_ICONS ? 400 : 512,
1326 o_toolbar ? 220 : 200);
1328 gtk_container_add(GTK_CONTAINER(filer_window->window),
1329 hbox);
1330 if (o_toolbar)
1332 GtkWidget *vbox, *toolbar;
1335 vbox = gtk_vbox_new(FALSE, 0);
1336 gtk_box_pack_start(GTK_BOX(hbox), vbox,
1337 TRUE, TRUE, 0);
1338 toolbar = create_toolbar(filer_window);
1339 gtk_box_pack_start(GTK_BOX(vbox), toolbar,
1340 FALSE, TRUE, 0);
1342 gtk_box_pack_start(GTK_BOX(vbox), collection,
1343 TRUE, TRUE, 0);
1345 else
1346 gtk_box_pack_start(GTK_BOX(hbox), collection,
1347 TRUE, TRUE, 0);
1349 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1350 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
1351 gtk_accel_group_attach(filer_keys,
1352 GTK_OBJECT(filer_window->window));
1355 number_of_windows++;
1356 gtk_widget_show_all(filer_window->window);
1357 attach(filer_window);
1359 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1362 static GtkWidget *create_toolbar(FilerWindow *filer_window)
1364 GtkWidget *frame, *box;
1366 frame = gtk_frame_new(NULL);
1367 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1369 box = gtk_hbutton_box_new();
1370 gtk_button_box_set_child_size_default(16, 16);
1371 gtk_hbutton_box_set_spacing_default(2);
1372 gtk_button_box_set_layout(GTK_BUTTON_BOX(box), GTK_BUTTONBOX_START);
1373 gtk_container_add(GTK_CONTAINER(frame), box);
1374 add_button(GTK_CONTAINER(box), TOOLBAR_UP_ICON,
1375 GTK_SIGNAL_FUNC(toolbar_up_clicked),
1376 filer_window);
1377 add_button(GTK_CONTAINER(box), TOOLBAR_HOME_ICON,
1378 GTK_SIGNAL_FUNC(toolbar_home_clicked),
1379 filer_window);
1380 add_button(GTK_CONTAINER(box), TOOLBAR_REFRESH_ICON,
1381 GTK_SIGNAL_FUNC(toolbar_refresh_clicked),
1382 filer_window);
1384 return frame;
1387 static void add_button(GtkContainer *box, int pixmap,
1388 GtkSignalFunc cb, gpointer data)
1390 GtkWidget *button, *icon;
1392 button = gtk_button_new();
1393 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
1394 gtk_container_add(box, button);
1396 icon = gtk_pixmap_new(default_pixmap[pixmap].pixmap,
1397 default_pixmap[pixmap].mask);
1398 gtk_container_add(GTK_CONTAINER(button), icon);
1399 gtk_signal_connect(GTK_OBJECT(button), "clicked", cb, data);
1402 /* Build up some option widgets to go in the options dialog, but don't
1403 * fill them in yet.
1405 static GtkWidget *create_options()
1407 GtkWidget *vbox;
1409 vbox = gtk_vbox_new(FALSE, 0);
1410 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1412 toggle_ro_bindings =
1413 gtk_check_button_new_with_label("Use RISC OS mouse bindings");
1414 gtk_box_pack_start(GTK_BOX(vbox), toggle_ro_bindings, FALSE, TRUE, 0);
1416 toggle_single_click =
1417 gtk_check_button_new_with_label("Single-click nagivation");
1418 gtk_box_pack_start(GTK_BOX(vbox), toggle_single_click, FALSE, TRUE, 0);
1420 toggle_toolbar =
1421 gtk_check_button_new_with_label("Show toolbar on new windows");
1422 gtk_box_pack_start(GTK_BOX(vbox), toggle_toolbar, FALSE, TRUE, 0);
1424 return vbox;
1427 /* Reflect current state by changing the widgets in the options box */
1428 static void update_options()
1430 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_ro_bindings),
1431 o_ro_bindings);
1432 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_single_click),
1433 o_single_click);
1434 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_toolbar),
1435 o_toolbar);
1438 /* Set current values by reading the states of the widgets in the options box */
1439 static void set_options()
1441 o_ro_bindings = gtk_toggle_button_get_active(
1442 GTK_TOGGLE_BUTTON(toggle_ro_bindings));
1444 o_single_click = gtk_toggle_button_get_active(
1445 GTK_TOGGLE_BUTTON(toggle_single_click));
1446 collection_single_click = o_single_click ? TRUE : FALSE;
1448 o_toolbar = gtk_toggle_button_get_active(
1449 GTK_TOGGLE_BUTTON(toggle_toolbar));
1450 collection_menu_button = o_ro_bindings ? 2 : 3;
1453 static void save_options()
1455 option_write("filer_ro_bindings", o_ro_bindings ? "1" : "0");
1456 option_write("filer_single_click", o_single_click ? "1" : "0");
1457 option_write("filer_toolbar", o_toolbar ? "1" : "0");
1460 static char *filer_ro_bindings(char *data)
1462 o_ro_bindings = atoi(data) != 0;
1463 collection_menu_button = o_ro_bindings ? 2 : 3;
1464 return NULL;
1467 static char *filer_single_click(char *data)
1469 o_single_click = atoi(data) != 0;
1470 collection_single_click = o_single_click ? TRUE : FALSE;
1471 return NULL;
1474 static char *filer_toolbar(char *data)
1476 o_toolbar = atoi(data) != 0;
1477 return NULL;
1480 /* Note that filer_window may not exist after this call. */
1481 void update_dir(FilerWindow *filer_window, gboolean warning)
1483 if (may_rescan(filer_window, warning))
1484 dir_update(filer_window->directory, filer_window->path);
1487 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
1489 Directory *dir = filer_window->directory;
1491 if (filer_window->show_hidden == hidden)
1492 return;
1494 filer_window->show_hidden = hidden;
1496 g_fscache_data_ref(dir_cache, dir);
1497 detach(filer_window);
1498 filer_window->directory = dir;
1499 attach(filer_window);
1502 /* Refresh the various caches even if we don't think we need to */
1503 void full_refresh(void)
1505 mount_update(TRUE);
1508 /* This path has been mounted/umounted - update all dirs */
1509 void filer_check_mounted(char *path)
1511 GList *next = all_filer_windows;
1512 int len;
1514 len = strlen(path);
1516 while (next)
1518 FilerWindow *filer_window = (FilerWindow *) next->data;
1520 next = next->next;
1522 if (strncmp(path, filer_window->path, len) == 0)
1524 char s = filer_window->path[len];
1526 if (s == '/' || s == '\0')
1527 update_dir(filer_window, FALSE);