r110: Added a option for using RISC OS mouse bindings to the options box, although
[rox-filer.git] / ROX-Filer / src / filer.c
blob995e520559fb1f48f42131fd7d6cd8fb0e66df40
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>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkx.h>
33 #include <gdk/gdkprivate.h> /* XXX - find another way to do this */
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 MAX_ICON_HEIGHT 42
50 #define PANEL_BORDER 2
52 FilerWindow *window_with_focus = NULL;
54 /* Link paths to GLists of filer windows */
55 GHashTable *path_to_window_list = NULL;
57 static FilerWindow *window_with_selection = NULL;
59 /* Options bits */
60 static GtkWidget *create_options();
61 static void update_options();
62 static void set_options();
63 static void save_options();
64 static char *filer_ro_bindings(char *data);
66 static OptionsSection options =
68 "Filer window options",
69 create_options,
70 update_options,
71 set_options,
72 save_options
74 static gboolean o_ro_bindings = FALSE;
75 static GtkWidget *toggle_ro_bindings;
77 /* Static prototypes */
78 static void filer_window_destroyed(GtkWidget *widget,
79 FilerWindow *filer_window);
80 static gboolean idle_scan_dir(gpointer data);
81 static void draw_item(GtkWidget *widget,
82 CollectionItem *item,
83 GdkRectangle *area);
84 void show_menu(Collection *collection, GdkEventButton *event,
85 int number_selected, gpointer user_data);
86 static int sort_by_name(const void *item1, const void *item2);
87 static void add_item(FilerWindow *filer_window, char *leafname);
88 static gboolean test_point(Collection *collection,
89 int point_x, int point_y,
90 CollectionItem *item,
91 int width, int height);
92 static void stop_scanning(FilerWindow *filer_window);
93 static gint focus_in(GtkWidget *widget,
94 GdkEventFocus *event,
95 FilerWindow *filer_window);
96 static gint focus_out(GtkWidget *widget,
97 GdkEventFocus *event,
98 FilerWindow *filer_window);
99 static void add_view(FilerWindow *filer_window);
100 static void remove_view(FilerWindow *filer_window);
101 static void free_item(FileItem *item);
102 static gboolean remove_deleted(gpointer item_data, gpointer data);
104 static GdkAtom xa_string;
105 enum
107 TARGET_STRING,
108 TARGET_URI_LIST,
111 void filer_init()
113 xa_string = gdk_atom_intern("STRING", FALSE);
115 path_to_window_list = g_hash_table_new(g_str_hash, g_str_equal);
117 options_sections = g_slist_prepend(options_sections, &options);
118 option_register("filer_ro_bindings", filer_ro_bindings);
122 /* When a filer window shows a directory, use this function to add
123 * it to the list of directories to be updated when the contents
124 * change.
126 static void add_view(FilerWindow *filer_window)
128 GList *list, *newlist;
130 g_return_if_fail(filer_window != NULL);
132 list = g_hash_table_lookup(path_to_window_list, filer_window->path);
133 newlist = g_list_prepend(list, filer_window);
134 if (newlist != list)
135 g_hash_table_insert(path_to_window_list, filer_window->path,
136 newlist);
139 /* When a filer window no longer shows a directory, call this to reverse
140 * the effect of add_view().
142 static void remove_view(FilerWindow *filer_window)
144 GList *list, *newlist;
146 g_return_if_fail(filer_window != NULL);
148 list = g_hash_table_lookup(path_to_window_list, filer_window->path);
149 newlist = g_list_remove(list, filer_window);
150 if (newlist != list)
151 g_hash_table_insert(path_to_window_list, filer_window->path,
152 newlist);
155 /* Go though all the FileItems in a collection, freeing all items.
156 * TODO: Maybe we should cache icons?
158 static void free_items(FilerWindow *filer_window)
160 guint i;
161 Collection *collection = filer_window->collection;
163 for (i = 0; i < collection->number_of_items; i++)
165 free_item((FileItem *) collection->items[i].data);
166 collection->items[i].data = NULL;
170 /* Set one item in a collection to NULL, freeing all data for it */
171 static void free_item(FileItem *item)
173 if (item->flags & ITEM_FLAG_TEMP_ICON)
175 gdk_pixmap_unref(item->image->pixmap);
176 gdk_pixmap_unref(item->image->mask);
177 g_free(item->image);
178 item->image = default_pixmap + TYPE_ERROR;
180 g_free(item->leafname);
181 g_free(item);
184 static void filer_window_destroyed(GtkWidget *widget,
185 FilerWindow *filer_window)
187 if (window_with_selection == filer_window)
188 window_with_selection = NULL;
189 if (window_with_focus == filer_window)
190 window_with_focus = NULL;
192 remove_view(filer_window);
194 free_items(filer_window);
195 if (filer_window->dir)
196 stop_scanning(filer_window);
197 g_free(filer_window->path);
198 g_free(filer_window);
200 if (--number_of_windows < 1)
201 gtk_main_quit();
204 static void stop_scanning(FilerWindow *filer_window)
206 g_return_if_fail(filer_window->dir != NULL);
208 closedir(filer_window->dir);
209 gtk_idle_remove(filer_window->idle_scan_id);
210 filer_window->dir = NULL;
211 if (filer_window->window->window)
212 gdk_window_set_cursor(filer_window->window->window, NULL);
215 /* This is called while we are scanning the directory */
216 static gboolean idle_scan_dir(gpointer data)
218 struct dirent *next;
219 FilerWindow *filer_window = (FilerWindow *) data;
223 next = readdir(filer_window->dir);
224 if (!next)
226 closedir(filer_window->dir);
227 filer_window->dir = NULL;
228 gdk_window_set_cursor(filer_window->window->window,
229 NULL);
231 collection_set_item_size(filer_window->collection,
232 filer_window->scan_min_width,
233 filer_window->collection->item_height);
234 collection_qsort(filer_window->collection,
235 filer_window->sort_fn);
236 if (filer_window->flags & FILER_UPDATING)
238 collection_delete_if(filer_window->collection,
239 remove_deleted, NULL);
241 if (filer_window->flags & FILER_NEEDS_RESCAN)
242 update_dir(filer_window);
244 return FALSE; /* Finished */
247 add_item(filer_window, next->d_name);
248 } while (!gtk_events_pending());
250 return TRUE;
253 /* Add a single object to a directory display */
254 static void add_item(FilerWindow *filer_window, char *leafname)
256 FileItem *item;
257 int old_item;
258 int item_width;
259 struct stat info;
260 int base_type;
261 GString *path;
263 if (leafname[0] == '.')
265 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
266 || (leafname[1] == '.' && leafname[2] == '\0'))
267 return;
270 item = g_malloc(sizeof(FileItem));
271 item->leafname = g_strdup(leafname);
272 item->flags = (ItemFlags) 0;
274 path = make_path(filer_window->path, leafname);
275 if (lstat(path->str, &info))
276 base_type = TYPE_ERROR;
277 else
279 if (S_ISREG(info.st_mode))
280 base_type = TYPE_FILE;
281 else if (S_ISDIR(info.st_mode))
283 base_type = TYPE_DIRECTORY;
285 if (g_hash_table_lookup(mtab_mounts, path->str))
286 item->flags |= ITEM_FLAG_MOUNT_POINT
287 | ITEM_FLAG_MOUNTED;
288 else if (g_hash_table_lookup(fstab_mounts, path->str))
289 item->flags |= ITEM_FLAG_MOUNT_POINT;
291 else if (S_ISBLK(info.st_mode))
292 base_type = TYPE_BLOCK_DEVICE;
293 else if (S_ISCHR(info.st_mode))
294 base_type = TYPE_CHAR_DEVICE;
295 else if (S_ISFIFO(info.st_mode))
296 base_type = TYPE_PIPE;
297 else if (S_ISSOCK(info.st_mode))
298 base_type = TYPE_SOCKET;
299 else if (S_ISLNK(info.st_mode))
301 if (stat(path->str, &info))
303 base_type = TYPE_ERROR;
305 else
307 if (S_ISREG(info.st_mode))
308 base_type = TYPE_FILE;
309 else if (S_ISDIR(info.st_mode))
310 base_type = TYPE_DIRECTORY;
311 else if (S_ISBLK(info.st_mode))
312 base_type = TYPE_BLOCK_DEVICE;
313 else if (S_ISCHR(info.st_mode))
314 base_type = TYPE_CHAR_DEVICE;
315 else if (S_ISFIFO(info.st_mode))
316 base_type = TYPE_PIPE;
317 else if (S_ISSOCK(info.st_mode))
318 base_type = TYPE_SOCKET;
319 else
320 base_type = TYPE_UNKNOWN;
323 item->flags |= ITEM_FLAG_SYMLINK;
325 else
326 base_type = TYPE_UNKNOWN;
329 item->base_type = base_type;
330 item->mime_type = NULL;
332 if (base_type == TYPE_DIRECTORY &&
333 !(item->flags & ITEM_FLAG_MOUNT_POINT))
335 uid_t uid = info.st_uid;
337 /* Might be an application directory - better check...
338 * AppRun must have the same owner as the directory
339 * (to stop people putting an AppRun in, eg, /tmp)
341 g_string_append(path, "/AppRun");
342 if (!stat(path->str, &info) && info.st_uid == uid)
344 item->flags |= ITEM_FLAG_APPDIR;
348 if (item->flags & ITEM_FLAG_APPDIR) /* path still ends /AppRun */
350 MaskedPixmap *app_icon;
352 g_string_truncate(path, path->len - 3);
353 g_string_append(path, "Icon.xpm");
354 app_icon = load_pixmap_from(filer_window->window, path->str);
355 if (app_icon)
357 item->image = app_icon;
358 item->flags |= ITEM_FLAG_TEMP_ICON;
360 else
361 item->image = default_pixmap + TYPE_APPDIR;
363 else
365 if (base_type == TYPE_FILE &&
366 (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
368 item->image = default_pixmap + TYPE_EXEC_FILE;
369 item->flags |= ITEM_FLAG_EXEC_FILE;
371 else if (base_type == TYPE_FILE)
373 item->mime_type = type_from_path(path->str);
374 item->image = type_to_icon(filer_window->window,
375 item->mime_type);
377 else
378 item->image = default_pixmap + base_type;
381 item->text_width = gdk_string_width(filer_window->window->style->font,
382 leafname);
384 if (!item->image)
385 item->image = default_pixmap + TYPE_UNKNOWN;
387 /* XXX: Must be a better way... */
388 item->pix_width = ((GdkPixmapPrivate *) item->image->pixmap)->width;
389 item->pix_height = ((GdkPixmapPrivate *) item->image->pixmap)->height;
391 item_width = MAX(item->pix_width, item->text_width) + 4;
393 if (item_width > filer_window->scan_min_width)
394 filer_window->scan_min_width = item_width;
396 if (item_width > filer_window->collection->item_width)
397 collection_set_item_size(filer_window->collection,
398 item_width,
399 filer_window->collection->item_height);
401 old_item = collection_find_item(filer_window->collection, item,
402 sort_by_name);
404 if (old_item >= 0)
406 CollectionItem *old =
407 &filer_window->collection->items[old_item];
409 free_item((FileItem *) old->data);
410 old->data = item;
411 collection_draw_item(filer_window->collection, old_item, TRUE);
413 else
414 collection_insert(filer_window->collection, item);
417 /* Is a point inside an item? */
418 static gboolean test_point(Collection *collection,
419 int point_x, int point_y,
420 CollectionItem *colitem,
421 int width, int height)
423 FileItem *item = (FileItem *) colitem->data;
424 GdkFont *font = GTK_WIDGET(collection)->style->font;
425 int text_height = font->ascent + font->descent;
426 int image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
427 int image_width = (item->pix_width >> 1) + 2;
428 int text_width = (item->text_width >> 1) + 2;
429 int x_limit;
431 if (point_y < image_y)
432 return FALSE; /* Too high up (don't worry about too low) */
434 if (point_y <= image_y + item->pix_height + 2)
435 x_limit = image_width;
436 else if (point_y > height - text_height - 2)
437 x_limit = text_width;
438 else
439 x_limit = MIN(image_width, text_width);
441 return ABS(point_x - (width >> 1)) < x_limit;
444 static void draw_item(GtkWidget *widget,
445 CollectionItem *colitem,
446 GdkRectangle *area)
448 FileItem *item = (FileItem *) colitem->data;
449 GdkGC *gc = colitem->selected ? widget->style->white_gc
450 : widget->style->black_gc;
451 int image_x = area->x + ((area->width - item->pix_width) >> 1);
452 GdkFont *font = widget->style->font;
453 int text_x = area->x + ((area->width - item->text_width) >> 1);
454 int text_y = area->y + area->height - font->descent - 2;
455 int text_height = font->ascent + font->descent;
457 if (item->image)
459 int image_y;
461 gdk_gc_set_clip_mask(gc, item->image->mask);
463 image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
464 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
465 gdk_draw_pixmap(widget->window, gc,
466 item->image->pixmap,
467 0, 0, /* Source x,y */
468 image_x, area->y + image_y, /* Dest x,y */
469 -1, MIN(item->pix_height, MAX_ICON_HEIGHT));
471 if (item->flags & ITEM_FLAG_SYMLINK)
473 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
474 gdk_gc_set_clip_mask(gc,
475 default_pixmap[TYPE_SYMLINK].mask);
476 gdk_draw_pixmap(widget->window, gc,
477 default_pixmap[TYPE_SYMLINK].pixmap,
478 0, 0, /* Source x,y */
479 image_x, area->y + 8, /* Dest x,y */
480 -1, -1);
482 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
484 int type = item->flags & ITEM_FLAG_MOUNTED
485 ? TYPE_MOUNTED
486 : TYPE_UNMOUNTED;
487 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
488 gdk_gc_set_clip_mask(gc,
489 default_pixmap[type].mask);
490 gdk_draw_pixmap(widget->window, gc,
491 default_pixmap[type].pixmap,
492 0, 0, /* Source x,y */
493 image_x, area->y + 8, /* Dest x,y */
494 -1, -1);
497 gdk_gc_set_clip_mask(gc, NULL);
498 gdk_gc_set_clip_origin(gc, 0, 0);
501 if (colitem->selected)
502 gtk_paint_flat_box(widget->style, widget->window,
503 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
504 NULL, widget, "text",
505 text_x, text_y - font->ascent,
506 item->text_width,
507 text_height);
509 gdk_draw_text(widget->window,
510 widget->style->font,
511 colitem->selected ? widget->style->white_gc
512 : widget->style->black_gc,
513 text_x, text_y,
514 item->leafname, strlen(item->leafname));
517 void show_menu(Collection *collection, GdkEventButton *event,
518 int item, gpointer user_data)
520 show_filer_menu((FilerWindow *) user_data, event, item);
523 static void may_rescan(FilerWindow *filer_window)
525 struct stat info;
527 g_return_if_fail(filer_window != NULL);
529 if (stat(filer_window->path, &info))
531 delayed_error("ROX-Filer", "Directory deleted");
532 gtk_widget_destroy(filer_window->window);
534 else if (info.st_mtime > filer_window->m_time)
536 if (filer_window->dir)
537 filer_window->flags |= FILER_NEEDS_RESCAN;
538 else
539 update_dir(filer_window);
543 /* Callback to collection_delete_if() */
544 static gboolean remove_deleted(gpointer item_data, gpointer data)
546 FileItem *item = (FileItem *) item_data;
548 if (item->flags & ITEM_FLAG_MAY_DELETE)
550 free_item(item);
551 return TRUE;
554 return FALSE;
557 /* Forget the old contents of a filer window and scan the directory
558 * from the start. If we are already scanning then rescan later.
560 void scan_dir(FilerWindow *filer_window)
562 if (filer_window->dir)
563 stop_scanning(filer_window);
565 free_items(filer_window);
566 collection_clear(filer_window->collection);
568 update_dir(filer_window);
569 filer_window->flags &= ~FILER_UPDATING;
571 gdk_window_set_cursor(filer_window->window->window,
572 gdk_cursor_new(GDK_WATCH));
575 /* Like scan_dir(), but assume new display will be similar to the old
576 * one (less flicker and doesn't lose the selection).
578 void update_dir(FilerWindow *filer_window)
580 Collection *collection = filer_window->collection;
581 struct stat info;
582 guint i;
584 if (filer_window->dir)
586 /* Already scanning - start again when we finish */
587 filer_window->flags |= FILER_NEEDS_RESCAN;
588 return;
590 filer_window->flags &= ~FILER_NEEDS_RESCAN;
591 filer_window->flags |= FILER_UPDATING;
593 for (i = 0; i < collection->number_of_items; i++)
595 FileItem *item = (FileItem *) collection->items[i].data;
596 item->flags |= ITEM_FLAG_MAY_DELETE;
599 mount_update();
601 gtk_window_set_title(GTK_WINDOW(filer_window->window),
602 filer_window->path);
604 if (stat(filer_window->path, &info))
606 report_error("Error statting directory", g_strerror(errno));
607 return;
609 filer_window->m_time = info.st_mtime;
611 filer_window->dir = opendir(filer_window->path);
612 if (!filer_window->dir)
614 report_error("Error scanning directory", g_strerror(errno));
615 return;
618 filer_window->scan_min_width = 64;
620 filer_window->idle_scan_id = gtk_idle_add(idle_scan_dir, filer_window);
623 /* Another app has grabbed the selection */
624 static gint collection_lose_selection(GtkWidget *widget,
625 GdkEventSelection *event)
627 if (window_with_selection &&
628 window_with_selection->collection == COLLECTION(widget))
630 FilerWindow *filer_window = window_with_selection;
631 window_with_selection = NULL;
632 collection_clear_selection(filer_window->collection);
635 return TRUE;
638 /* Someone wants us to send them the selection */
639 static void selection_get(GtkWidget *widget,
640 GtkSelectionData *selection_data,
641 guint info,
642 guint time,
643 gpointer data)
645 GString *reply, *header;
646 FilerWindow *filer_window;
647 int i;
648 Collection *collection;
650 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
652 reply = g_string_new(NULL);
653 header = g_string_new(NULL);
655 switch (info)
657 case TARGET_STRING:
658 g_string_sprintf(header, " %s",
659 make_path(filer_window->path, "")->str);
660 break;
661 case TARGET_URI_LIST:
662 g_string_sprintf(header, " file://%s%s",
663 our_host_name(),
664 make_path(filer_window->path, "")->str);
665 break;
668 collection = filer_window->collection;
669 for (i = 0; i < collection->number_of_items; i++)
671 if (collection->items[i].selected)
673 FileItem *item =
674 (FileItem *) collection->items[i].data;
676 g_string_append(reply, header->str);
677 g_string_append(reply, item->leafname);
680 /* This works, but I don't think I like it... */
681 /* g_string_append_c(reply, ' '); */
683 gtk_selection_data_set(selection_data, xa_string,
684 8, reply->str + 1, reply->len - 1);
685 g_string_free(reply, TRUE);
686 g_string_free(header, TRUE);
689 /* No items are now selected. This might be because another app claimed
690 * the selection or because the user unselected all the items.
692 static void lose_selection(Collection *collection,
693 guint time,
694 gpointer user_data)
696 FilerWindow *filer_window = (FilerWindow *) user_data;
698 if (window_with_selection == filer_window)
700 window_with_selection = NULL;
701 gtk_selection_owner_set(NULL,
702 GDK_SELECTION_PRIMARY,
703 time);
707 static void gain_selection(Collection *collection,
708 guint time,
709 gpointer user_data)
711 FilerWindow *filer_window = (FilerWindow *) user_data;
713 if (gtk_selection_owner_set(GTK_WIDGET(collection),
714 GDK_SELECTION_PRIMARY,
715 time))
717 window_with_selection = filer_window;
719 else
720 collection_clear_selection(filer_window->collection);
723 static int sort_by_name(const void *item1, const void *item2)
725 return strcmp((*((FileItem **)item1))->leafname,
726 (*((FileItem **)item2))->leafname);
729 static int sort_by_type(const void *item1, const void *item2)
731 const FileItem *i1 = (FileItem *) ((CollectionItem *) item1)->data;
732 const FileItem *i2 = (FileItem *) ((CollectionItem *) item2)->data;
733 MIME_type *m1, *m2;
735 int diff = i1->base_type - i2->base_type;
737 if (!diff)
738 diff = (i1->flags & ITEM_FLAG_APPDIR)
739 - (i2->flags & ITEM_FLAG_APPDIR);
740 if (diff)
741 return diff > 0 ? 1 : -1;
743 m1 = i1->mime_type;
744 m2 = i2->mime_type;
746 if (m1 && m2)
748 diff = strcmp(m1->media_type, m2->media_type);
749 if (!diff)
750 diff = strcmp(m1->subtype, m2->subtype);
752 else if (m1 || m2)
753 diff = m1 ? 1 : -1;
754 else
755 diff = 0;
757 if (diff)
758 return diff > 0 ? 1 : -1;
760 return sort_by_name(item1, item2);
763 void open_item(Collection *collection,
764 gpointer item_data, int item_number,
765 gpointer user_data)
767 FilerWindow *filer_window = (FilerWindow *) user_data;
768 FileItem *item = (FileItem *) item_data;
769 GdkEventButton *event;
770 char *full_path;
771 GtkWidget *widget;
772 gboolean shift, adjust;
774 event = (GdkEventButton *) gtk_get_current_event();
775 full_path = make_path(filer_window->path, item->leafname)->str;
777 collection_wink_item(filer_window->collection, item_number);
779 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_BUTTON_PRESS)
781 shift = event->state & GDK_SHIFT_MASK;
782 adjust = (event->button != (o_ro_bindings ? 2 : 1))
783 ^ ((event->state & GDK_CONTROL_MASK) != 0);
785 else
787 shift = FALSE;
788 adjust = FALSE;
791 widget = filer_window->window;
793 switch (item->base_type)
795 case TYPE_DIRECTORY:
796 if (item->flags & ITEM_FLAG_APPDIR && !shift)
798 run_app(make_path(filer_window->path,
799 item->leafname)->str);
800 if (adjust && !filer_window->panel)
801 gtk_widget_destroy(widget);
802 break;
804 if (adjust || filer_window->panel)
805 filer_opendir(full_path, FALSE, BOTTOM);
806 else
808 remove_view(filer_window);
809 filer_window->path = pathdup(full_path);
810 add_view(filer_window);
811 scan_dir(filer_window);
813 break;
814 case TYPE_FILE:
815 if (item->flags & ITEM_FLAG_EXEC_FILE && !shift)
817 char *argv[] = {NULL, NULL};
819 argv[0] = full_path;
821 if (spawn_full(argv, getenv("HOME"), 0))
823 if (adjust && !filer_window->panel)
824 gtk_widget_destroy(widget);
826 else
827 report_error("ROX-Filer",
828 "Failed to fork() child");
830 else
832 GString *message;
833 MIME_type *type = shift ? &text_plain
834 : item->mime_type;
836 g_return_if_fail(type != NULL);
838 if (type_open(full_path, type))
840 if (adjust && !filer_window->panel)
841 gtk_widget_destroy(widget);
843 else
845 message = g_string_new(NULL);
846 g_string_sprintf(message, "No open "
847 "action specified for files of "
848 "this type (%s/%s)",
849 type->media_type,
850 type->subtype);
851 report_error("ROX-Filer", message->str);
852 g_string_free(message, TRUE);
855 break;
856 default:
857 report_error("open_item",
858 "I don't know how to open that");
859 break;
863 static gint pointer_in(GtkWidget *widget,
864 GdkEventCrossing *event,
865 FilerWindow *filer_window)
867 may_rescan(filer_window);
868 return FALSE;
871 static gint focus_in(GtkWidget *widget,
872 GdkEventFocus *event,
873 FilerWindow *filer_window)
875 window_with_focus = filer_window;
877 return FALSE;
880 static gint focus_out(GtkWidget *widget,
881 GdkEventFocus *event,
882 FilerWindow *filer_window)
884 /* TODO: Shade the cursor */
886 return FALSE;
889 /* Handle keys that can't be bound with the menu */
890 static gint key_press_event(GtkWidget *widget,
891 GdkEventKey *event,
892 FilerWindow *filer_window)
894 switch (event->keyval)
897 case GDK_Left:
898 move_cursor(-1, 0);
899 break;
900 case GDK_Right:
901 move_cursor(1, 0);
902 break;
903 case GDK_Up:
904 move_cursor(0, -1);
905 break;
906 case GDK_Down:
907 move_cursor(0, 1);
908 break;
909 case GDK_Return:
911 case GDK_BackSpace:
912 change_to_parent(filer_window);
913 return TRUE;
916 return FALSE;
919 void change_to_parent(FilerWindow *filer_window)
921 remove_view(filer_window);
922 filer_window->path = pathdup(make_path(
923 filer_window->path,
924 "..")->str);
925 add_view(filer_window);
926 scan_dir(filer_window);
929 FileItem *selected_item(Collection *collection)
931 int i;
933 g_return_val_if_fail(collection != NULL, NULL);
934 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
935 g_return_val_if_fail(collection->number_selected == 1, NULL);
937 for (i = 0; i < collection->number_of_items; i++)
938 if (collection->items[i].selected)
939 return (FileItem *) collection->items[i].data;
941 g_warning("selected_item: number_selected is wrong\n");
943 return NULL;
946 /* Refresh all windows onto this directory */
947 void refresh_dirs(char *path)
949 char *real;
950 GList *list, *next;
952 real = pathdup(path);
953 list = g_hash_table_lookup(path_to_window_list, real);
954 g_free(real);
956 while (list)
958 next = list->next;
959 update_dir((FilerWindow *) list->data);
960 list = next;
964 void filer_opendir(char *path, gboolean panel, Side panel_side)
966 GtkWidget *hbox, *scrollbar, *collection;
967 FilerWindow *filer_window;
968 GtkTargetEntry target_table[] =
970 {"text/uri-list", 0, TARGET_URI_LIST},
971 {"STRING", 0, TARGET_STRING},
974 filer_window = g_malloc(sizeof(FilerWindow));
975 filer_window->path = pathdup(path);
976 filer_window->dir = NULL; /* Not scanning */
977 filer_window->show_hidden = FALSE;
978 filer_window->panel = panel;
979 filer_window->panel_side = panel_side;
980 filer_window->temp_item_selected = FALSE;
981 filer_window->sort_fn = sort_by_type;
982 filer_window->flags = (FilerFlags) 0;
984 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
986 collection = collection_new(NULL);
987 gtk_object_set_data(GTK_OBJECT(collection),
988 "filer_window", filer_window);
989 filer_window->collection = COLLECTION(collection);
990 collection_set_item_size(filer_window->collection, 64, 64);
991 collection_set_functions(filer_window->collection,
992 draw_item, test_point);
994 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
995 gtk_signal_connect(GTK_OBJECT(filer_window->window),
996 "enter-notify-event",
997 GTK_SIGNAL_FUNC(pointer_in), filer_window);
998 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
999 GTK_SIGNAL_FUNC(focus_in), filer_window);
1000 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
1001 GTK_SIGNAL_FUNC(focus_out), filer_window);
1002 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1003 filer_window_destroyed, filer_window);
1005 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
1006 open_item, filer_window);
1007 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
1008 show_menu, filer_window);
1009 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
1010 gain_selection, filer_window);
1011 gtk_signal_connect(GTK_OBJECT(collection), "lose_selection",
1012 lose_selection, filer_window);
1013 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
1014 drag_selection, filer_window);
1015 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
1016 drag_data_get, filer_window);
1017 gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event",
1018 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1019 gtk_signal_connect (GTK_OBJECT(collection), "selection_get",
1020 GTK_SIGNAL_FUNC(selection_get), NULL);
1021 gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY,
1022 target_table,
1023 sizeof(target_table) / sizeof(*target_table));
1025 drag_set_dest(collection);
1027 if (panel)
1029 int swidth, sheight, iwidth, iheight;
1030 GtkWidget *frame, *win = filer_window->window;
1032 collection_set_panel(filer_window->collection, TRUE);
1034 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
1035 iwidth = filer_window->collection->item_width;
1036 iheight = filer_window->collection->item_height;
1038 if (panel_side == TOP || panel_side == BOTTOM)
1040 int height = iheight + PANEL_BORDER;
1041 int y = panel_side == TOP
1042 ? -PANEL_BORDER
1043 : sheight - height - PANEL_BORDER;
1045 gtk_widget_set_usize(collection, swidth, height);
1046 gtk_widget_set_uposition(win, 0, y);
1048 else
1050 int width = iwidth + PANEL_BORDER;
1051 int x = panel_side == LEFT
1052 ? -PANEL_BORDER
1053 : swidth - width - PANEL_BORDER;
1055 gtk_widget_set_usize(collection, width, sheight);
1056 gtk_widget_set_uposition(win, x, 0);
1059 frame = gtk_frame_new(NULL);
1060 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1061 gtk_container_add(GTK_CONTAINER(frame), collection);
1062 gtk_container_add(GTK_CONTAINER(win), frame);
1064 gtk_widget_realize(win);
1065 if (override_redirect)
1066 gdk_window_set_override_redirect(win->window, TRUE);
1067 make_panel_window(win->window);
1069 else
1071 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1072 "key_press_event",
1073 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1074 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
1075 400, 200);
1077 hbox = gtk_hbox_new(FALSE, 0);
1078 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1080 gtk_box_pack_start(GTK_BOX(hbox), collection, TRUE, TRUE, 0);
1082 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1083 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
1084 gtk_accel_group_attach(filer_keys,
1085 GTK_OBJECT(filer_window->window));
1088 gtk_widget_show_all(filer_window->window);
1089 number_of_windows++;
1091 load_default_pixmaps(collection->window);
1093 add_view(filer_window);
1094 scan_dir(filer_window);
1097 /* Build up some option widgets to go in the options dialog, but don't
1098 * fill them in yet.
1100 static GtkWidget *create_options()
1102 GtkWidget *vbox;
1104 vbox = gtk_vbox_new(FALSE, 0);
1105 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1107 toggle_ro_bindings =
1108 gtk_check_button_new_with_label("Use RISC OS mouse bindings");
1109 gtk_box_pack_start(GTK_BOX(vbox), toggle_ro_bindings, FALSE, TRUE, 0);
1111 return vbox;
1114 /* Reflect current state by changing the widgets in the options box */
1115 static void update_options()
1117 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_ro_bindings),
1118 o_ro_bindings);
1121 /* Set current values by reading the states of the widgets in the options box */
1122 static void set_options()
1124 o_ro_bindings = gtk_toggle_button_get_active(
1125 GTK_TOGGLE_BUTTON(toggle_ro_bindings));
1128 static void save_options()
1130 option_write("filer_ro_bindings", o_ro_bindings ? "1" : "0");
1133 static char *filer_ro_bindings(char *data)
1135 o_ro_bindings = atoi(data) != 0;
1136 return NULL;