r156: Fixed some compiler warnings for systems with signed char types.
[rox-filer/ma.git] / ROX-Filer / src / filer.c
blobbe672c3c62919b049c5c807dcdb827d0be5cd5e5
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 <errno.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <ctype.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkx.h>
34 #include <gdk/gdkkeysyms.h>
35 #include "collection.h"
37 #include "main.h"
38 #include "support.h"
39 #include "gui_support.h"
40 #include "filer.h"
41 #include "pixmaps.h"
42 #include "menu.h"
43 #include "dnd.h"
44 #include "apps.h"
45 #include "mount.h"
46 #include "type.h"
47 #include "options.h"
49 #define ROW_HEIGHT_LARGE 64
50 #define ROW_HEIGHT_SMALL 32
51 #define ROW_HEIGHT_FULL_INFO 44
52 #define SMALL_ICON_HEIGHT 20
53 #define SMALL_ICON_WIDTH 48
54 #define MAX_ICON_HEIGHT 42
55 #define MAX_ICON_WIDTH 48
56 #define PANEL_BORDER 2
57 #define MIN_ITEM_WIDTH 64
59 extern int collection_menu_button;
61 FilerWindow *window_with_focus = NULL;
62 GdkFont *fixed_font = NULL;
64 static FilerWindow *window_with_selection = NULL;
66 /* Options bits */
67 static GtkWidget *create_options();
68 static void update_options();
69 static void set_options();
70 static void save_options();
71 static char *filer_ro_bindings(char *data);
72 static char *filer_toolbar(char *data);
74 static OptionsSection options =
76 "Filer window options",
77 create_options,
78 update_options,
79 set_options,
80 save_options
82 static gboolean o_toolbar = TRUE;
83 static GtkWidget *toggle_toolbar;
84 static gboolean o_ro_bindings = FALSE;
85 static GtkWidget *toggle_ro_bindings;
87 /* Static prototypes */
88 static void attach(FilerWindow *filer_window);
89 static void detach(FilerWindow *filer_window);
90 static void filer_window_destroyed(GtkWidget *widget,
91 FilerWindow *filer_window);
92 void show_menu(Collection *collection, GdkEventButton *event,
93 int number_selected, gpointer user_data);
94 static gint focus_in(GtkWidget *widget,
95 GdkEventFocus *event,
96 FilerWindow *filer_window);
97 static gint focus_out(GtkWidget *widget,
98 GdkEventFocus *event,
99 FilerWindow *filer_window);
100 static void add_item(FilerWindow *filer_window, DirItem *item);
101 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window);
102 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window);
103 static void add_button(GtkContainer *box, int pixmap,
104 GtkSignalFunc cb, gpointer data);
105 static GtkWidget *create_toolbar(FilerWindow *filer_window);
106 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
107 FilerWindow *window);
108 static int calc_width(FilerWindow *filer_window, DirItem *item);
109 static void draw_large_icon(GtkWidget *widget,
110 GdkRectangle *area,
111 DirItem *item,
112 gboolean selected);
113 static void draw_string(GtkWidget *widget,
114 GdkFont *font,
115 char *string,
116 int x,
117 int y,
118 int width,
119 gboolean selected);
120 static void draw_item_large(GtkWidget *widget,
121 CollectionItem *item,
122 GdkRectangle *area);
123 static void draw_item_small(GtkWidget *widget,
124 CollectionItem *item,
125 GdkRectangle *area);
126 static void draw_item_full_info(GtkWidget *widget,
127 CollectionItem *colitem,
128 GdkRectangle *area);
129 static gboolean test_point_large(Collection *collection,
130 int point_x, int point_y,
131 CollectionItem *item,
132 int width, int height);
133 static gboolean test_point_small(Collection *collection,
134 int point_x, int point_y,
135 CollectionItem *item,
136 int width, int height);
137 static gboolean test_point_full_info(Collection *collection,
138 int point_x, int point_y,
139 CollectionItem *item,
140 int width, int height);
141 static void update_display(Directory *dir,
142 DirAction action,
143 GPtrArray *items,
144 FilerWindow *filer_window);
145 void filer_change_to(FilerWindow *filer_window, char *path);
146 static void shrink_width(FilerWindow *filer_window);
148 static GdkAtom xa_string;
149 enum
151 TARGET_STRING,
152 TARGET_URI_LIST,
155 void filer_init()
157 xa_string = gdk_atom_intern("STRING", FALSE);
159 options_sections = g_slist_prepend(options_sections, &options);
160 option_register("filer_ro_bindings", filer_ro_bindings);
161 option_register("filer_toolbar", filer_toolbar);
163 fixed_font = gdk_font_load("fixed");
166 static gboolean if_deleted(gpointer item, gpointer removed)
168 int i = ((GPtrArray *) removed)->len;
169 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
170 char *leafname = ((DirItem *) item)->leafname;
172 while (i--)
174 if (strcmp(leafname, r[i]->leafname) == 0)
175 return TRUE;
178 return FALSE;
181 static void update_item(FilerWindow *filer_window, DirItem *item)
183 int i;
184 char *leafname = item->leafname;
186 if (leafname[0] == '.')
188 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
189 || (leafname[1] == '.' && leafname[2] == '\0'))
190 return;
193 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
195 if (i >= 0)
196 collection_draw_item(filer_window->collection, i, TRUE);
197 else
198 g_warning("Failed to find '%s'\n", item->leafname);
201 static void update_display(Directory *dir,
202 DirAction action,
203 GPtrArray *items,
204 FilerWindow *filer_window)
206 int i;
208 switch (action)
210 case DIR_ADD:
211 for (i = 0; i < items->len; i++)
213 DirItem *item = (DirItem *) items->pdata[i];
215 add_item(filer_window, item);
218 collection_qsort(filer_window->collection,
219 filer_window->sort_fn);
220 break;
221 case DIR_REMOVE:
222 collection_delete_if(filer_window->collection,
223 if_deleted,
224 items);
225 break;
226 case DIR_END_SCAN:
227 if (filer_window->window->window)
228 gdk_window_set_cursor(
229 filer_window->window->window,
230 NULL);
231 shrink_width(filer_window);
232 break;
233 case DIR_UPDATE:
234 for (i = 0; i < items->len; i++)
236 DirItem *item = (DirItem *) items->pdata[i];
238 update_item(filer_window, item);
240 break;
244 static void attach(FilerWindow *filer_window)
246 gdk_window_set_cursor(filer_window->window->window,
247 gdk_cursor_new(GDK_WATCH));
248 collection_clear(filer_window->collection);
249 dir_attach(filer_window->directory, (DirCallback) update_display,
250 filer_window);
253 static void detach(FilerWindow *filer_window)
255 g_return_if_fail(filer_window->directory != NULL);
257 dir_detach(filer_window->directory,
258 (DirCallback) update_display, filer_window);
259 g_fscache_data_unref(dir_cache, filer_window->directory);
260 filer_window->directory = NULL;
263 static void filer_window_destroyed(GtkWidget *widget,
264 FilerWindow *filer_window)
266 if (window_with_selection == filer_window)
267 window_with_selection = NULL;
268 if (window_with_focus == filer_window)
269 window_with_focus = NULL;
271 if (filer_window->directory)
272 detach(filer_window);
274 g_free(filer_window->path);
275 g_free(filer_window);
277 if (--number_of_windows < 1)
278 gtk_main_quit();
281 static int calc_width(FilerWindow *filer_window, DirItem *item)
283 int pix_width = item->image->width;
285 switch (filer_window->display_style)
287 case FULL_INFO:
288 return MAX_ICON_WIDTH + 12 +
289 MAX(item->details_width, item->name_width);
290 case SMALL_ICONS:
291 return SMALL_ICON_WIDTH + 12 + item->name_width;
292 default:
293 return MAX(pix_width, item->name_width) + 4;
297 /* Add a single object to a directory display */
298 static void add_item(FilerWindow *filer_window, DirItem *item)
300 char *leafname = item->leafname;
301 int item_width;
303 if (leafname[0] == '.')
305 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
306 || (leafname[1] == '.' && leafname[2] == '\0'))
307 return;
310 item_width = calc_width(filer_window, item);
311 if (item_width > filer_window->collection->item_width)
312 collection_set_item_size(filer_window->collection,
313 item_width,
314 filer_window->collection->item_height);
315 collection_insert(filer_window->collection, item);
318 /* Is a point inside an item? */
319 static gboolean test_point_large(Collection *collection,
320 int point_x, int point_y,
321 CollectionItem *colitem,
322 int width, int height)
324 DirItem *item = (DirItem *) colitem->data;
325 GdkFont *font = GTK_WIDGET(collection)->style->font;
326 int text_height = font->ascent + font->descent;
327 MaskedPixmap *image = item->image;
328 int image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
329 int image_width = (image->width >> 1) + 2;
330 int text_width = (item->name_width >> 1) + 2;
331 int x_limit;
333 if (point_y < image_y)
334 return FALSE; /* Too high up (don't worry about too low) */
336 if (point_y <= image_y + image->height + 2)
337 x_limit = image_width;
338 else if (point_y > height - text_height - 2)
339 x_limit = text_width;
340 else
341 x_limit = MIN(image_width, text_width);
343 return ABS(point_x - (width >> 1)) < x_limit;
346 static gboolean test_point_full_info(Collection *collection,
347 int point_x, int point_y,
348 CollectionItem *colitem,
349 int width, int height)
351 DirItem *item = (DirItem *) colitem->data;
352 GdkFont *font = GTK_WIDGET(collection)->style->font;
353 MaskedPixmap *image = item->image;
354 int image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
355 int low_top = height
356 - fixed_font->descent - 2 - fixed_font->ascent;
358 if (point_x < image->width + 2)
359 return point_x > 2 && point_y > image_y;
361 point_x -= MAX_ICON_WIDTH + 8;
363 if (point_y >= low_top)
364 return point_x < item->details_width;
365 if (point_y >= low_top - font->ascent - font->descent)
366 return point_x < item->name_width;
367 return FALSE;
370 static gboolean test_point_small(Collection *collection,
371 int point_x, int point_y,
372 CollectionItem *colitem,
373 int width, int height)
375 DirItem *item = (DirItem *) colitem->data;
376 MaskedPixmap *image = item->image;
377 int image_y = MAX(0, SMALL_ICON_HEIGHT - image->height);
378 GdkFont *font = GTK_WIDGET(collection)->style->font;
379 int low_top = height
380 - fixed_font->descent - 2 - font->ascent;
381 int iwidth = MIN(SMALL_ICON_WIDTH, image->width);
383 if (point_x < iwidth + 2)
384 return point_x > 2 && point_y > image_y;
386 point_x -= SMALL_ICON_WIDTH + 4;
388 if (point_y >= low_top)
389 return point_x < item->name_width;
390 return FALSE;
393 static void draw_small_icon(GtkWidget *widget,
394 GdkRectangle *area,
395 DirItem *item,
396 gboolean selected)
398 MaskedPixmap *image = item->image;
399 int width = MIN(image->width, SMALL_ICON_WIDTH);
400 int height = MIN(image->height, SMALL_ICON_HEIGHT);
401 int image_x = area->x + ((area->width - width) >> 1);
402 int image_y;
403 GdkGC *gc = selected ? widget->style->white_gc
404 : widget->style->black_gc;
405 if (!item->image)
406 return;
408 gdk_gc_set_clip_mask(gc, item->image->mask);
410 image_y = MAX(0, SMALL_ICON_HEIGHT - image->height);
411 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
412 gdk_draw_pixmap(widget->window, gc,
413 item->image->pixmap,
414 0, 0, /* Source x,y */
415 image_x, area->y + image_y, /* Dest x,y */
416 width, height);
418 if (selected)
420 gdk_gc_set_function(gc, GDK_INVERT);
421 gdk_draw_rectangle(widget->window,
423 TRUE, image_x, area->y + image_y,
424 width, height);
425 gdk_gc_set_function(gc, GDK_COPY);
428 if (item->flags & ITEM_FLAG_SYMLINK)
430 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
431 gdk_gc_set_clip_mask(gc,
432 default_pixmap[TYPE_SYMLINK].mask);
433 gdk_draw_pixmap(widget->window, gc,
434 default_pixmap[TYPE_SYMLINK].pixmap,
435 0, 0, /* Source x,y */
436 image_x, area->y + 8, /* Dest x,y */
437 -1, -1);
439 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
441 int type = item->flags & ITEM_FLAG_MOUNTED
442 ? TYPE_MOUNTED
443 : TYPE_UNMOUNTED;
444 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
445 gdk_gc_set_clip_mask(gc,
446 default_pixmap[type].mask);
447 gdk_draw_pixmap(widget->window, gc,
448 default_pixmap[type].pixmap,
449 0, 0, /* Source x,y */
450 image_x, area->y + 8, /* Dest x,y */
451 -1, -1);
454 gdk_gc_set_clip_mask(gc, NULL);
455 gdk_gc_set_clip_origin(gc, 0, 0);
458 static void draw_large_icon(GtkWidget *widget,
459 GdkRectangle *area,
460 DirItem *item,
461 gboolean selected)
463 MaskedPixmap *image = item->image;
464 int width = MIN(image->width, MAX_ICON_WIDTH);
465 int height = MIN(image->height, MAX_ICON_WIDTH);
466 int image_x = area->x + ((area->width - width) >> 1);
467 int image_y;
468 GdkGC *gc = selected ? widget->style->white_gc
469 : widget->style->black_gc;
471 gdk_gc_set_clip_mask(gc, item->image->mask);
473 image_y = MAX(0, MAX_ICON_HEIGHT - image->height);
474 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
475 gdk_draw_pixmap(widget->window, gc,
476 item->image->pixmap,
477 0, 0, /* Source x,y */
478 image_x, area->y + image_y, /* Dest x,y */
479 width, height);
481 if (selected)
483 gdk_gc_set_function(gc, GDK_INVERT);
484 gdk_draw_rectangle(widget->window,
486 TRUE, image_x, area->y + image_y,
487 width, height);
488 gdk_gc_set_function(gc, GDK_COPY);
491 if (item->flags & ITEM_FLAG_SYMLINK)
493 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
494 gdk_gc_set_clip_mask(gc,
495 default_pixmap[TYPE_SYMLINK].mask);
496 gdk_draw_pixmap(widget->window, gc,
497 default_pixmap[TYPE_SYMLINK].pixmap,
498 0, 0, /* Source x,y */
499 image_x, area->y + 8, /* Dest x,y */
500 -1, -1);
502 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
504 int type = item->flags & ITEM_FLAG_MOUNTED
505 ? TYPE_MOUNTED
506 : TYPE_UNMOUNTED;
507 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
508 gdk_gc_set_clip_mask(gc,
509 default_pixmap[type].mask);
510 gdk_draw_pixmap(widget->window, gc,
511 default_pixmap[type].pixmap,
512 0, 0, /* Source x,y */
513 image_x, area->y + 8, /* Dest x,y */
514 -1, -1);
517 gdk_gc_set_clip_mask(gc, NULL);
518 gdk_gc_set_clip_origin(gc, 0, 0);
521 static void draw_string(GtkWidget *widget,
522 GdkFont *font,
523 char *string,
524 int x,
525 int y,
526 int width,
527 gboolean selected)
529 int text_height = font->ascent + font->descent;
531 if (selected)
532 gtk_paint_flat_box(widget->style, widget->window,
533 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
534 NULL, widget, "text",
535 x, y - font->ascent,
536 width,
537 text_height);
539 gdk_draw_text(widget->window,
540 font,
541 selected ? widget->style->white_gc
542 : widget->style->black_gc,
543 x, y,
544 string, strlen(string));
547 /* Return a string (valid until next call) giving details
548 * of this item.
550 char *details(DirItem *item)
552 mode_t m = item->mode;
553 static GString *buf = NULL;
555 if (!buf)
556 buf = g_string_new(NULL);
558 g_string_sprintf(buf, "%s, %c%c%c:%c%c%c:%c%c%c:%c%c"
559 #ifdef S_ISVTX
560 "%c"
561 #endif
562 ", %s",
563 S_ISDIR(m) ? "Dir" :
564 S_ISCHR(m) ? "Char" :
565 S_ISBLK(m) ? "Blck" :
566 S_ISLNK(m) ? "Link" :
567 S_ISSOCK(m) ? "Sock" :
568 S_ISFIFO(m) ? "Pipe" : "File",
570 m & S_IRUSR ? 'r' : '-',
571 m & S_IWUSR ? 'w' : '-',
572 m & S_IXUSR ? 'x' : '-',
574 m & S_IRGRP ? 'r' : '-',
575 m & S_IWGRP ? 'w' : '-',
576 m & S_IXGRP ? 'x' : '-',
578 m & S_IROTH ? 'r' : '-',
579 m & S_IWOTH ? 'w' : '-',
580 m & S_IXOTH ? 'x' : '-',
582 m & S_ISUID ? 'U' : '-',
583 m & S_ISGID ? 'G' : '-',
584 #ifdef S_ISVTX
585 m & S_ISVTX ? 'T' : '-',
586 #endif
587 format_size(item->size));
589 return buf->str;
592 static void draw_item_full_info(GtkWidget *widget,
593 CollectionItem *colitem,
594 GdkRectangle *area)
596 DirItem *item = (DirItem *) colitem->data;
597 MaskedPixmap *image = item->image;
598 GdkFont *font = widget->style->font;
599 int text_x = area->x + MAX_ICON_WIDTH + 8;
600 int low_text_y = area->y + area->height - fixed_font->descent - 2;
601 gboolean selected = colitem->selected;
602 GdkRectangle pic_area;
604 pic_area.x = area->x;
605 pic_area.y = area->y;
606 pic_area.width = image->width + 8;
607 pic_area.height = area->height;
609 draw_large_icon(widget, &pic_area, item, selected);
611 draw_string(widget,
612 widget->style->font,
613 item->leafname,
614 text_x,
615 low_text_y - font->descent - fixed_font->ascent,
616 item->name_width,
617 selected);
618 draw_string(widget,
619 fixed_font,
620 details(item),
621 text_x, low_text_y,
622 item->details_width,
623 selected);
626 static void draw_item_small(GtkWidget *widget,
627 CollectionItem *colitem,
628 GdkRectangle *area)
630 DirItem *item = (DirItem *) colitem->data;
631 GdkFont *font = widget->style->font;
632 int text_x = area->x + SMALL_ICON_WIDTH + 4;
633 int low_text_y = area->y + area->height - font->descent - 2;
634 gboolean selected = colitem->selected;
635 GdkRectangle pic_area;
637 pic_area.x = area->x;
638 pic_area.y = area->y;
639 pic_area.width = SMALL_ICON_WIDTH;
640 pic_area.height = SMALL_ICON_HEIGHT;
642 draw_small_icon(widget, &pic_area, item, selected);
644 draw_string(widget,
645 widget->style->font,
646 item->leafname,
647 text_x,
648 low_text_y,
649 item->name_width,
650 selected);
653 static void draw_item_large(GtkWidget *widget,
654 CollectionItem *colitem,
655 GdkRectangle *area)
657 DirItem *item = (DirItem *) colitem->data;
658 GdkFont *font = widget->style->font;
659 int text_x = area->x + ((area->width - item->name_width) >> 1);
660 int text_y = area->y + area->height - font->descent - 2;
661 gboolean selected = colitem->selected;
663 draw_large_icon(widget, area, item, selected);
665 draw_string(widget,
666 widget->style->font,
667 item->leafname,
668 text_x, text_y, item->name_width,
669 selected);
672 void show_menu(Collection *collection, GdkEventButton *event,
673 int item, gpointer user_data)
675 show_filer_menu((FilerWindow *) user_data, event, item);
678 /* Returns TRUE iff the directory still exits. */
679 static gboolean may_rescan(FilerWindow *filer_window)
681 Directory *dir;
683 g_return_val_if_fail(filer_window != NULL, FALSE);
685 /* We do a fresh lookup (rather than update) because the inode may
686 * have changed.
688 dir = g_fscache_lookup(dir_cache, filer_window->path);
689 if (!dir)
691 delayed_error("ROX-Filer", "Directory missing/deleted");
692 gtk_widget_destroy(filer_window->window);
693 return FALSE;
695 if (dir == filer_window->directory)
696 g_fscache_data_unref(dir_cache, dir);
697 else
699 detach(filer_window);
700 filer_window->directory = dir;
701 attach(filer_window);
704 return TRUE;
707 /* Callback to collection_delete_if() */
708 #if 0
709 static gboolean remove_deleted(gpointer item_data, gpointer data)
711 DirItem *item = (DirItem *) item_data;
713 if (item->flags & ITEM_FLAG_MAY_DELETE)
715 free_item(item);
716 return TRUE;
719 return FALSE;
721 #endif
723 /* Another app has grabbed the selection */
724 static gint collection_lose_selection(GtkWidget *widget,
725 GdkEventSelection *event)
727 if (window_with_selection &&
728 window_with_selection->collection == COLLECTION(widget))
730 FilerWindow *filer_window = window_with_selection;
731 window_with_selection = NULL;
732 collection_clear_selection(filer_window->collection);
735 return TRUE;
738 /* Someone wants us to send them the selection */
739 static void selection_get(GtkWidget *widget,
740 GtkSelectionData *selection_data,
741 guint info,
742 guint time,
743 gpointer data)
745 GString *reply, *header;
746 FilerWindow *filer_window;
747 int i;
748 Collection *collection;
750 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
752 reply = g_string_new(NULL);
753 header = g_string_new(NULL);
755 switch (info)
757 case TARGET_STRING:
758 g_string_sprintf(header, " %s",
759 make_path(filer_window->path, "")->str);
760 break;
761 case TARGET_URI_LIST:
762 g_string_sprintf(header, " file://%s%s",
763 our_host_name(),
764 make_path(filer_window->path, "")->str);
765 break;
768 collection = filer_window->collection;
769 for (i = 0; i < collection->number_of_items; i++)
771 if (collection->items[i].selected)
773 DirItem *item =
774 (DirItem *) collection->items[i].data;
776 g_string_append(reply, header->str);
777 g_string_append(reply, item->leafname);
780 /* This works, but I don't think I like it... */
781 /* g_string_append_c(reply, ' '); */
783 gtk_selection_data_set(selection_data, xa_string,
784 8, reply->str + 1, reply->len - 1);
785 g_string_free(reply, TRUE);
786 g_string_free(header, TRUE);
789 /* No items are now selected. This might be because another app claimed
790 * the selection or because the user unselected all the items.
792 static void lose_selection(Collection *collection,
793 guint time,
794 gpointer user_data)
796 FilerWindow *filer_window = (FilerWindow *) user_data;
798 if (window_with_selection == filer_window)
800 window_with_selection = NULL;
801 gtk_selection_owner_set(NULL,
802 GDK_SELECTION_PRIMARY,
803 time);
807 static void gain_selection(Collection *collection,
808 guint time,
809 gpointer user_data)
811 FilerWindow *filer_window = (FilerWindow *) user_data;
813 if (gtk_selection_owner_set(GTK_WIDGET(collection),
814 GDK_SELECTION_PRIMARY,
815 time))
817 window_with_selection = filer_window;
819 else
820 collection_clear_selection(filer_window->collection);
823 int sort_by_name(const void *item1, const void *item2)
825 return strcmp((*((DirItem **)item1))->leafname,
826 (*((DirItem **)item2))->leafname);
829 int sort_by_type(const void *item1, const void *item2)
831 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
832 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
833 MIME_type *m1, *m2;
835 int diff = i1->base_type - i2->base_type;
837 if (!diff)
838 diff = (i1->flags & ITEM_FLAG_APPDIR)
839 - (i2->flags & ITEM_FLAG_APPDIR);
840 if (diff)
841 return diff > 0 ? 1 : -1;
843 m1 = i1->mime_type;
844 m2 = i2->mime_type;
846 if (m1 && m2)
848 diff = strcmp(m1->media_type, m2->media_type);
849 if (!diff)
850 diff = strcmp(m1->subtype, m2->subtype);
852 else if (m1 || m2)
853 diff = m1 ? 1 : -1;
854 else
855 diff = 0;
857 if (diff)
858 return diff > 0 ? 1 : -1;
860 return sort_by_name(item1, item2);
863 int sort_by_date(const void *item1, const void *item2)
865 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
866 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
868 return i1->mtime > i2->mtime ? -1 :
869 i1->mtime < i2->mtime ? 1 :
870 sort_by_name(item1, item2);
873 int sort_by_size(const void *item1, const void *item2)
875 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
876 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
878 return i1->size > i2->size ? -1 :
879 i1->size < i2->size ? 1 :
880 sort_by_name(item1, item2);
883 void open_item(Collection *collection,
884 gpointer item_data, int item_number,
885 gpointer user_data)
887 FilerWindow *filer_window = (FilerWindow *) user_data;
888 DirItem *item = (DirItem *) item_data;
889 GdkEventButton *event;
890 char *full_path;
891 GtkWidget *widget;
892 gboolean shift;
893 gboolean adjust; /* do alternative action */
895 event = (GdkEventButton *) gtk_get_current_event();
896 full_path = make_path(filer_window->path,
897 item->leafname)->str;
899 collection_wink_item(filer_window->collection, item_number);
901 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_BUTTON_PRESS)
903 shift = event->state & GDK_SHIFT_MASK;
904 adjust = (event->button != 1)
905 ^ ((event->state & GDK_CONTROL_MASK) != 0);
907 else
909 shift = FALSE;
910 adjust = FALSE;
913 widget = filer_window->window;
915 switch (item->base_type)
917 case TYPE_DIRECTORY:
918 if (item->flags & ITEM_FLAG_APPDIR && !shift)
920 run_app(make_path(filer_window->path,
921 item->leafname)->str);
922 if (adjust && !filer_window->panel)
923 gtk_widget_destroy(widget);
924 break;
926 if ((adjust ^ o_ro_bindings) || filer_window->panel)
927 filer_opendir(full_path, FALSE, BOTTOM);
928 else
929 filer_change_to(filer_window, full_path);
930 break;
931 case TYPE_FILE:
932 if (item->flags & ITEM_FLAG_EXEC_FILE
933 && !shift)
935 char *argv[] = {NULL, NULL};
937 argv[0] = full_path;
939 if (spawn_full(argv, getenv("HOME"), 0))
941 if (adjust && !filer_window->panel)
942 gtk_widget_destroy(widget);
944 else
945 report_error("ROX-Filer",
946 "Failed to fork() child");
948 else
950 GString *message;
951 MIME_type *type = shift ? &text_plain
952 : item->mime_type;
954 g_return_if_fail(type != NULL);
956 if (type_open(full_path, type))
958 if (adjust && !filer_window->panel)
959 gtk_widget_destroy(widget);
961 else
963 message = g_string_new(NULL);
964 g_string_sprintf(message, "No open "
965 "action specified for files of "
966 "this type (%s/%s)",
967 type->media_type,
968 type->subtype);
969 report_error("ROX-Filer", message->str);
970 g_string_free(message, TRUE);
973 break;
974 default:
975 report_error("open_item",
976 "I don't know how to open that");
977 break;
981 static gint pointer_in(GtkWidget *widget,
982 GdkEventCrossing *event,
983 FilerWindow *filer_window)
985 may_rescan(filer_window);
986 return FALSE;
989 static gint focus_in(GtkWidget *widget,
990 GdkEventFocus *event,
991 FilerWindow *filer_window)
993 window_with_focus = filer_window;
995 return FALSE;
998 static gint focus_out(GtkWidget *widget,
999 GdkEventFocus *event,
1000 FilerWindow *filer_window)
1002 /* TODO: Shade the cursor */
1004 return FALSE;
1007 /* Handle keys that can't be bound with the menu */
1008 static gint key_press_event(GtkWidget *widget,
1009 GdkEventKey *event,
1010 FilerWindow *filer_window)
1012 switch (event->keyval)
1015 case GDK_Left:
1016 move_cursor(-1, 0);
1017 break;
1018 case GDK_Right:
1019 move_cursor(1, 0);
1020 break;
1021 case GDK_Up:
1022 move_cursor(0, -1);
1023 break;
1024 case GDK_Down:
1025 move_cursor(0, 1);
1026 break;
1027 case GDK_Return:
1029 case GDK_BackSpace:
1030 change_to_parent(filer_window);
1031 return TRUE;
1034 return FALSE;
1037 static void toolbar_refresh_clicked(GtkWidget *widget,
1038 FilerWindow *filer_window)
1040 full_refresh();
1041 update_dir(filer_window);
1044 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window)
1046 filer_change_to(filer_window, getenv("HOME"));
1049 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window)
1051 change_to_parent(filer_window);
1054 void change_to_parent(FilerWindow *filer_window)
1056 filer_change_to(filer_window, make_path(filer_window->path, "..")->str);
1059 void filer_change_to(FilerWindow *filer_window, char *path)
1061 detach(filer_window);
1062 g_free(filer_window->path);
1063 filer_window->path = pathdup(path);
1065 filer_window->directory = g_fscache_lookup(dir_cache,
1066 filer_window->path);
1067 if (filer_window->directory)
1069 gtk_window_set_title(GTK_WINDOW(filer_window->window),
1070 filer_window->path);
1071 attach(filer_window);
1073 else
1075 char *error;
1077 error = g_strdup_printf("Directory '%s' is not accessible.",
1078 path);
1079 delayed_error("ROX-Filer", error);
1080 g_free(error);
1081 gtk_widget_destroy(filer_window->window);
1085 DirItem *selected_item(Collection *collection)
1087 int i;
1089 g_return_val_if_fail(collection != NULL, NULL);
1090 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
1091 g_return_val_if_fail(collection->number_selected == 1, NULL);
1093 for (i = 0; i < collection->number_of_items; i++)
1094 if (collection->items[i].selected)
1095 return (DirItem *) collection->items[i].data;
1097 g_warning("selected_item: number_selected is wrong\n");
1099 return NULL;
1102 static int filer_confirm_close(GtkWidget *widget, GdkEvent *event,
1103 FilerWindow *window)
1105 /* TODO: We can open lots of these - very irritating! */
1106 return get_choice("Close panel?",
1107 "You have tried to close a panel via the window "
1108 "manager - I usually find that this is accidental... "
1109 "really close?",
1110 2, "Remove", "Cancel") != 0;
1113 /* Make the items as narrow as possible */
1114 static void shrink_width(FilerWindow *filer_window)
1116 int i;
1117 Collection *col = filer_window->collection;
1118 int width = MIN_ITEM_WIDTH;
1119 int this_width;
1120 DisplayStyle style = filer_window->display_style;
1121 GdkFont *font;
1122 int text_height;
1124 font = gtk_widget_get_default_style()->font;
1125 text_height = font->ascent + font->descent;
1127 for (i = 0; i < col->number_of_items; i++)
1129 this_width = calc_width(filer_window,
1130 (DirItem *) col->items[i].data);
1131 if (this_width > width)
1132 width = this_width;
1135 collection_set_item_size(filer_window->collection,
1136 width,
1137 style == FULL_INFO ? MAX_ICON_HEIGHT + 4 :
1138 style == SMALL_ICONS ? MAX(text_height, SMALL_ICON_HEIGHT) + 4
1139 : text_height + MAX_ICON_HEIGHT + 8);
1142 void filer_set_sort_fn(FilerWindow *filer_window,
1143 int (*fn)(const void *a, const void *b))
1145 if (filer_window->sort_fn == fn)
1146 return;
1148 filer_window->sort_fn = fn;
1149 collection_qsort(filer_window->collection,
1150 filer_window->sort_fn);
1153 void filer_style_set(FilerWindow *filer_window, DisplayStyle style)
1155 if (filer_window->display_style == style)
1156 return;
1158 filer_window->display_style = style;
1159 switch (style)
1161 case SMALL_ICONS:
1162 collection_set_functions(filer_window->collection,
1163 draw_item_small, test_point_small);
1164 break;
1165 case FULL_INFO:
1166 collection_set_functions(filer_window->collection,
1167 draw_item_full_info, test_point_full_info);
1168 break;
1169 default:
1170 collection_set_functions(filer_window->collection,
1171 draw_item_large, test_point_large);
1172 break;
1175 shrink_width(filer_window);
1178 void filer_opendir(char *path, gboolean panel, Side panel_side)
1180 GtkWidget *hbox, *scrollbar, *collection;
1181 FilerWindow *filer_window;
1182 GtkTargetEntry target_table[] =
1184 {"text/uri-list", 0, TARGET_URI_LIST},
1185 {"STRING", 0, TARGET_STRING},
1188 filer_window = g_new(FilerWindow, 1);
1189 filer_window->path = pathdup(path);
1191 filer_window->directory = g_fscache_lookup(dir_cache,
1192 filer_window->path);
1193 if (!filer_window->directory)
1195 char *error;
1197 error = g_strdup_printf("Directory '%s' not found.", path);
1198 delayed_error("ROX-Filer", error);
1199 g_free(error);
1200 g_free(filer_window->path);
1201 g_free(filer_window);
1202 return;
1205 filer_window->show_hidden = FALSE;
1206 filer_window->panel = panel;
1207 filer_window->panel_side = panel_side;
1208 filer_window->temp_item_selected = FALSE;
1209 filer_window->sort_fn = sort_by_type;
1210 filer_window->flags = (FilerFlags) 0;
1211 filer_window->display_style = UNKNOWN_STYLE;
1213 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1214 gtk_window_set_title(GTK_WINDOW(filer_window->window),
1215 filer_window->path);
1217 collection = collection_new(NULL);
1218 gtk_object_set_data(GTK_OBJECT(collection),
1219 "filer_window", filer_window);
1220 filer_window->collection = COLLECTION(collection);
1222 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1223 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1224 "enter-notify-event",
1225 GTK_SIGNAL_FUNC(pointer_in), filer_window);
1226 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
1227 GTK_SIGNAL_FUNC(focus_in), filer_window);
1228 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
1229 GTK_SIGNAL_FUNC(focus_out), filer_window);
1230 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1231 filer_window_destroyed, filer_window);
1233 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
1234 open_item, filer_window);
1235 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
1236 show_menu, filer_window);
1237 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
1238 gain_selection, filer_window);
1239 gtk_signal_connect(GTK_OBJECT(collection), "lose_selection",
1240 lose_selection, filer_window);
1241 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
1242 drag_selection, filer_window);
1243 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
1244 drag_data_get, filer_window);
1245 gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event",
1246 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1247 gtk_signal_connect (GTK_OBJECT(collection), "selection_get",
1248 GTK_SIGNAL_FUNC(selection_get), NULL);
1249 gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY,
1250 target_table,
1251 sizeof(target_table) / sizeof(*target_table));
1253 filer_style_set(filer_window, LARGE_ICONS);
1254 drag_set_dest(collection);
1256 if (panel)
1258 int swidth, sheight, iwidth, iheight;
1259 GtkWidget *frame, *win = filer_window->window;
1261 collection_set_panel(filer_window->collection, TRUE);
1262 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1263 "delete_event",
1264 GTK_SIGNAL_FUNC(filer_confirm_close),
1265 filer_window);
1267 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
1268 iwidth = filer_window->collection->item_width;
1269 iheight = filer_window->collection->item_height;
1271 if (panel_side == TOP || panel_side == BOTTOM)
1273 int height = iheight + PANEL_BORDER;
1274 int y = panel_side == TOP
1275 ? -PANEL_BORDER
1276 : sheight - height - PANEL_BORDER;
1278 gtk_widget_set_usize(collection, swidth, height);
1279 gtk_widget_set_uposition(win, 0, y);
1281 else
1283 int width = iwidth + PANEL_BORDER;
1284 int x = panel_side == LEFT
1285 ? -PANEL_BORDER
1286 : swidth - width - PANEL_BORDER;
1288 gtk_widget_set_usize(collection, width, sheight);
1289 gtk_widget_set_uposition(win, x, 0);
1292 frame = gtk_frame_new(NULL);
1293 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1294 gtk_container_add(GTK_CONTAINER(frame), collection);
1295 gtk_container_add(GTK_CONTAINER(win), frame);
1297 gtk_widget_realize(win);
1298 if (override_redirect)
1299 gdk_window_set_override_redirect(win->window, TRUE);
1300 make_panel_window(win->window);
1302 else
1304 hbox = gtk_hbox_new(FALSE, 0);
1306 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1307 "key_press_event",
1308 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1309 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
1310 filer_window->display_style == LARGE_ICONS ? 400 : 512,
1311 o_toolbar ? 220 : 200);
1313 gtk_container_add(GTK_CONTAINER(filer_window->window),
1314 hbox);
1315 if (o_toolbar)
1317 GtkWidget *vbox, *toolbar;
1320 vbox = gtk_vbox_new(FALSE, 0);
1321 gtk_box_pack_start(GTK_BOX(hbox), vbox,
1322 TRUE, TRUE, 0);
1323 toolbar = create_toolbar(filer_window);
1324 gtk_box_pack_start(GTK_BOX(vbox), toolbar,
1325 FALSE, TRUE, 0);
1327 gtk_box_pack_start(GTK_BOX(vbox), collection,
1328 TRUE, TRUE, 0);
1330 else
1331 gtk_box_pack_start(GTK_BOX(hbox), collection,
1332 TRUE, TRUE, 0);
1334 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1335 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
1336 gtk_accel_group_attach(filer_keys,
1337 GTK_OBJECT(filer_window->window));
1340 number_of_windows++;
1341 gtk_widget_show_all(filer_window->window);
1342 attach(filer_window);
1345 static GtkWidget *create_toolbar(FilerWindow *filer_window)
1347 GtkWidget *frame, *box;
1349 frame = gtk_frame_new(NULL);
1350 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1352 box = gtk_hbutton_box_new();
1353 gtk_button_box_set_child_size_default(16, 16);
1354 gtk_hbutton_box_set_spacing_default(2);
1355 gtk_button_box_set_layout(GTK_BUTTON_BOX(box), GTK_BUTTONBOX_START);
1356 gtk_container_add(GTK_CONTAINER(frame), box);
1357 add_button(GTK_CONTAINER(box), TOOLBAR_UP_ICON,
1358 GTK_SIGNAL_FUNC(toolbar_up_clicked),
1359 filer_window);
1360 add_button(GTK_CONTAINER(box), TOOLBAR_HOME_ICON,
1361 GTK_SIGNAL_FUNC(toolbar_home_clicked),
1362 filer_window);
1363 add_button(GTK_CONTAINER(box), TOOLBAR_REFRESH_ICON,
1364 GTK_SIGNAL_FUNC(toolbar_refresh_clicked),
1365 filer_window);
1367 return frame;
1370 static void add_button(GtkContainer *box, int pixmap,
1371 GtkSignalFunc cb, gpointer data)
1373 GtkWidget *button, *icon;
1375 button = gtk_button_new();
1376 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
1377 gtk_container_add(box, button);
1379 icon = gtk_pixmap_new(default_pixmap[pixmap].pixmap,
1380 default_pixmap[pixmap].mask);
1381 gtk_container_add(GTK_CONTAINER(button), icon);
1382 gtk_signal_connect(GTK_OBJECT(button), "clicked", cb, data);
1385 /* Build up some option widgets to go in the options dialog, but don't
1386 * fill them in yet.
1388 static GtkWidget *create_options()
1390 GtkWidget *vbox;
1392 vbox = gtk_vbox_new(FALSE, 0);
1393 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1395 toggle_ro_bindings =
1396 gtk_check_button_new_with_label("Use RISC OS mouse bindings");
1397 gtk_box_pack_start(GTK_BOX(vbox), toggle_ro_bindings, FALSE, TRUE, 0);
1399 toggle_toolbar =
1400 gtk_check_button_new_with_label("Show toolbar on new windows");
1401 gtk_box_pack_start(GTK_BOX(vbox), toggle_toolbar, FALSE, TRUE, 0);
1403 return vbox;
1406 /* Reflect current state by changing the widgets in the options box */
1407 static void update_options()
1409 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_ro_bindings),
1410 o_ro_bindings);
1411 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_toolbar),
1412 o_toolbar);
1415 /* Set current values by reading the states of the widgets in the options box */
1416 static void set_options()
1418 o_ro_bindings = gtk_toggle_button_get_active(
1419 GTK_TOGGLE_BUTTON(toggle_ro_bindings));
1420 o_toolbar = gtk_toggle_button_get_active(
1421 GTK_TOGGLE_BUTTON(toggle_toolbar));
1422 collection_menu_button = o_ro_bindings ? 2 : 3;
1425 static void save_options()
1427 option_write("filer_ro_bindings", o_ro_bindings ? "1" : "0");
1428 option_write("filer_toolbar", o_toolbar ? "1" : "0");
1431 static char *filer_ro_bindings(char *data)
1433 o_ro_bindings = atoi(data) != 0;
1434 collection_menu_button = o_ro_bindings ? 2 : 3;
1435 return NULL;
1438 static char *filer_toolbar(char *data)
1440 o_toolbar = atoi(data) != 0;
1441 return NULL;
1444 /* Note that filer_window may not exist after this call. */
1445 void update_dir(FilerWindow *filer_window)
1447 if (may_rescan(filer_window))
1448 dir_update(filer_window->directory, filer_window->path);
1451 void filer_set_hidden(FilerWindow *filer_window, gboolean hidden)
1453 Directory *dir = filer_window->directory;
1455 if (filer_window->show_hidden == hidden)
1456 return;
1458 filer_window->show_hidden = hidden;
1460 g_fscache_data_ref(dir_cache, dir);
1461 detach(filer_window);
1462 filer_window->directory = dir;
1463 attach(filer_window);
1466 /* Refresh the various caches even if we don't think we need to */
1467 void full_refresh(void)
1469 mount_update(TRUE);