r126: Added better ref counting for file icons.
[rox-filer.git] / ROX-Filer / src / filer.c
blobbe4e483b149a10183c16be6086cae0f2d8049402
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 pixmap_unref(item->image);
176 item->image = default_pixmap + TYPE_ERROR;
178 g_free(item->leafname);
179 g_free(item);
182 static void filer_window_destroyed(GtkWidget *widget,
183 FilerWindow *filer_window)
185 if (window_with_selection == filer_window)
186 window_with_selection = NULL;
187 if (window_with_focus == filer_window)
188 window_with_focus = NULL;
190 remove_view(filer_window);
192 free_items(filer_window);
193 if (filer_window->dir)
194 stop_scanning(filer_window);
195 g_free(filer_window->path);
196 g_free(filer_window);
198 if (--number_of_windows < 1)
199 gtk_main_quit();
202 static void stop_scanning(FilerWindow *filer_window)
204 g_return_if_fail(filer_window->dir != NULL);
206 closedir(filer_window->dir);
207 gtk_idle_remove(filer_window->idle_scan_id);
208 filer_window->dir = NULL;
209 if (filer_window->window->window)
210 gdk_window_set_cursor(filer_window->window->window, NULL);
213 /* This is called while we are scanning the directory */
214 static gboolean idle_scan_dir(gpointer data)
216 struct dirent *next;
217 FilerWindow *filer_window = (FilerWindow *) data;
221 next = readdir(filer_window->dir);
222 if (!next)
224 closedir(filer_window->dir);
225 filer_window->dir = NULL;
226 gdk_window_set_cursor(filer_window->window->window,
227 NULL);
229 collection_set_item_size(filer_window->collection,
230 filer_window->scan_min_width,
231 filer_window->collection->item_height);
232 collection_qsort(filer_window->collection,
233 filer_window->sort_fn);
234 if (filer_window->flags & FILER_UPDATING)
236 collection_delete_if(filer_window->collection,
237 remove_deleted, NULL);
239 if (filer_window->flags & FILER_NEEDS_RESCAN)
240 update_dir(filer_window);
242 return FALSE; /* Finished */
245 add_item(filer_window, next->d_name);
246 } while (!gtk_events_pending());
248 return TRUE;
251 /* Add a single object to a directory display */
252 static void add_item(FilerWindow *filer_window, char *leafname)
254 FileItem *item;
255 int old_item;
256 int item_width;
257 struct stat info;
258 int base_type;
259 GString *path;
261 if (leafname[0] == '.')
263 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
264 || (leafname[1] == '.' && leafname[2] == '\0'))
265 return;
268 item = g_malloc(sizeof(FileItem));
269 item->leafname = g_strdup(leafname);
270 item->flags = (ItemFlags) 0;
272 path = make_path(filer_window->path, leafname);
273 if (lstat(path->str, &info))
274 base_type = TYPE_ERROR;
275 else
277 if (S_ISREG(info.st_mode))
278 base_type = TYPE_FILE;
279 else if (S_ISDIR(info.st_mode))
281 base_type = TYPE_DIRECTORY;
283 if (g_hash_table_lookup(mtab_mounts, path->str))
284 item->flags |= ITEM_FLAG_MOUNT_POINT
285 | ITEM_FLAG_MOUNTED;
286 else if (g_hash_table_lookup(fstab_mounts, path->str))
287 item->flags |= ITEM_FLAG_MOUNT_POINT;
289 else if (S_ISBLK(info.st_mode))
290 base_type = TYPE_BLOCK_DEVICE;
291 else if (S_ISCHR(info.st_mode))
292 base_type = TYPE_CHAR_DEVICE;
293 else if (S_ISFIFO(info.st_mode))
294 base_type = TYPE_PIPE;
295 else if (S_ISSOCK(info.st_mode))
296 base_type = TYPE_SOCKET;
297 else if (S_ISLNK(info.st_mode))
299 if (stat(path->str, &info))
301 base_type = TYPE_ERROR;
303 else
305 if (S_ISREG(info.st_mode))
306 base_type = TYPE_FILE;
307 else if (S_ISDIR(info.st_mode))
308 base_type = TYPE_DIRECTORY;
309 else if (S_ISBLK(info.st_mode))
310 base_type = TYPE_BLOCK_DEVICE;
311 else if (S_ISCHR(info.st_mode))
312 base_type = TYPE_CHAR_DEVICE;
313 else if (S_ISFIFO(info.st_mode))
314 base_type = TYPE_PIPE;
315 else if (S_ISSOCK(info.st_mode))
316 base_type = TYPE_SOCKET;
317 else
318 base_type = TYPE_UNKNOWN;
321 item->flags |= ITEM_FLAG_SYMLINK;
323 else
324 base_type = TYPE_UNKNOWN;
327 item->base_type = base_type;
328 item->mime_type = NULL;
330 if (base_type == TYPE_DIRECTORY &&
331 !(item->flags & ITEM_FLAG_MOUNT_POINT))
333 uid_t uid = info.st_uid;
335 /* Might be an application directory - better check...
336 * AppRun must have the same owner as the directory
337 * (to stop people putting an AppRun in, eg, /tmp)
339 g_string_append(path, "/AppRun");
340 if (!stat(path->str, &info) && info.st_uid == uid)
342 item->flags |= ITEM_FLAG_APPDIR;
346 if (item->flags & ITEM_FLAG_APPDIR) /* path still ends /AppRun */
348 MaskedPixmap *app_icon;
350 g_string_truncate(path, path->len - 3);
351 g_string_append(path, "Icon.xpm");
352 app_icon = load_pixmap_from(filer_window->window, path->str);
353 if (app_icon)
355 item->image = app_icon;
356 item->flags |= ITEM_FLAG_TEMP_ICON;
358 else
359 item->image = default_pixmap + TYPE_APPDIR;
361 else
363 if (base_type == TYPE_FILE &&
364 (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
366 item->image = default_pixmap + TYPE_EXEC_FILE;
367 item->flags |= ITEM_FLAG_EXEC_FILE;
369 else if (base_type == TYPE_FILE)
371 item->mime_type = type_from_path(path->str);
372 item->image = type_to_icon(filer_window->window,
373 item->mime_type);
374 item->flags |= ITEM_FLAG_TEMP_ICON;
376 else
377 item->image = default_pixmap + base_type;
380 item->text_width = gdk_string_width(filer_window->window->style->font,
381 leafname);
383 if (!item->image)
384 item->image = default_pixmap + TYPE_UNKNOWN;
386 /* XXX: Must be a better way... */
387 item->pix_width = ((GdkPixmapPrivate *) item->image->pixmap)->width;
388 item->pix_height = ((GdkPixmapPrivate *) item->image->pixmap)->height;
390 item_width = MAX(item->pix_width, item->text_width) + 4;
392 if (item_width > filer_window->scan_min_width)
393 filer_window->scan_min_width = item_width;
395 if (item_width > filer_window->collection->item_width)
396 collection_set_item_size(filer_window->collection,
397 item_width,
398 filer_window->collection->item_height);
400 old_item = collection_find_item(filer_window->collection, item,
401 sort_by_name);
403 if (old_item >= 0)
405 CollectionItem *old =
406 &filer_window->collection->items[old_item];
408 free_item((FileItem *) old->data);
409 old->data = item;
410 collection_draw_item(filer_window->collection, old_item, TRUE);
412 else
413 collection_insert(filer_window->collection, item);
416 /* Is a point inside an item? */
417 static gboolean test_point(Collection *collection,
418 int point_x, int point_y,
419 CollectionItem *colitem,
420 int width, int height)
422 FileItem *item = (FileItem *) colitem->data;
423 GdkFont *font = GTK_WIDGET(collection)->style->font;
424 int text_height = font->ascent + font->descent;
425 int image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
426 int image_width = (item->pix_width >> 1) + 2;
427 int text_width = (item->text_width >> 1) + 2;
428 int x_limit;
430 if (point_y < image_y)
431 return FALSE; /* Too high up (don't worry about too low) */
433 if (point_y <= image_y + item->pix_height + 2)
434 x_limit = image_width;
435 else if (point_y > height - text_height - 2)
436 x_limit = text_width;
437 else
438 x_limit = MIN(image_width, text_width);
440 return ABS(point_x - (width >> 1)) < x_limit;
443 static void draw_item(GtkWidget *widget,
444 CollectionItem *colitem,
445 GdkRectangle *area)
447 FileItem *item = (FileItem *) colitem->data;
448 GdkGC *gc = colitem->selected ? widget->style->white_gc
449 : widget->style->black_gc;
450 int image_x = area->x + ((area->width - item->pix_width) >> 1);
451 GdkFont *font = widget->style->font;
452 int text_x = area->x + ((area->width - item->text_width) >> 1);
453 int text_y = area->y + area->height - font->descent - 2;
454 int text_height = font->ascent + font->descent;
456 if (item->image)
458 int image_y;
460 gdk_gc_set_clip_mask(gc, item->image->mask);
462 image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
463 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
464 gdk_draw_pixmap(widget->window, gc,
465 item->image->pixmap,
466 0, 0, /* Source x,y */
467 image_x, area->y + image_y, /* Dest x,y */
468 -1, MIN(item->pix_height, MAX_ICON_HEIGHT));
470 if (item->flags & ITEM_FLAG_SYMLINK)
472 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
473 gdk_gc_set_clip_mask(gc,
474 default_pixmap[TYPE_SYMLINK].mask);
475 gdk_draw_pixmap(widget->window, gc,
476 default_pixmap[TYPE_SYMLINK].pixmap,
477 0, 0, /* Source x,y */
478 image_x, area->y + 8, /* Dest x,y */
479 -1, -1);
481 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
483 int type = item->flags & ITEM_FLAG_MOUNTED
484 ? TYPE_MOUNTED
485 : TYPE_UNMOUNTED;
486 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
487 gdk_gc_set_clip_mask(gc,
488 default_pixmap[type].mask);
489 gdk_draw_pixmap(widget->window, gc,
490 default_pixmap[type].pixmap,
491 0, 0, /* Source x,y */
492 image_x, area->y + 8, /* Dest x,y */
493 -1, -1);
496 gdk_gc_set_clip_mask(gc, NULL);
497 gdk_gc_set_clip_origin(gc, 0, 0);
500 if (colitem->selected)
501 gtk_paint_flat_box(widget->style, widget->window,
502 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
503 NULL, widget, "text",
504 text_x, text_y - font->ascent,
505 item->text_width,
506 text_height);
508 gdk_draw_text(widget->window,
509 widget->style->font,
510 colitem->selected ? widget->style->white_gc
511 : widget->style->black_gc,
512 text_x, text_y,
513 item->leafname, strlen(item->leafname));
516 void show_menu(Collection *collection, GdkEventButton *event,
517 int item, gpointer user_data)
519 show_filer_menu((FilerWindow *) user_data, event, item);
522 static void may_rescan(FilerWindow *filer_window)
524 struct stat info;
526 g_return_if_fail(filer_window != NULL);
528 if (stat(filer_window->path, &info))
530 delayed_error("ROX-Filer", "Directory deleted");
531 gtk_widget_destroy(filer_window->window);
533 else if (info.st_mtime > filer_window->m_time)
535 if (filer_window->dir)
536 filer_window->flags |= FILER_NEEDS_RESCAN;
537 else
538 update_dir(filer_window);
542 /* Callback to collection_delete_if() */
543 static gboolean remove_deleted(gpointer item_data, gpointer data)
545 FileItem *item = (FileItem *) item_data;
547 if (item->flags & ITEM_FLAG_MAY_DELETE)
549 free_item(item);
550 return TRUE;
553 return FALSE;
556 /* Forget the old contents of a filer window and scan the directory
557 * from the start. If we are already scanning then rescan later.
559 void scan_dir(FilerWindow *filer_window)
561 if (filer_window->dir)
562 stop_scanning(filer_window);
564 free_items(filer_window);
565 collection_clear(filer_window->collection);
567 update_dir(filer_window);
568 filer_window->flags &= ~FILER_UPDATING;
570 gdk_window_set_cursor(filer_window->window->window,
571 gdk_cursor_new(GDK_WATCH));
574 /* Like scan_dir(), but assume new display will be similar to the old
575 * one (less flicker and doesn't lose the selection).
577 void update_dir(FilerWindow *filer_window)
579 Collection *collection = filer_window->collection;
580 struct stat info;
581 guint i;
583 if (filer_window->dir)
585 /* Already scanning - start again when we finish */
586 filer_window->flags |= FILER_NEEDS_RESCAN;
587 return;
589 filer_window->flags &= ~FILER_NEEDS_RESCAN;
590 filer_window->flags |= FILER_UPDATING;
592 for (i = 0; i < collection->number_of_items; i++)
594 FileItem *item = (FileItem *) collection->items[i].data;
595 item->flags |= ITEM_FLAG_MAY_DELETE;
598 mount_update();
600 gtk_window_set_title(GTK_WINDOW(filer_window->window),
601 filer_window->path);
603 if (stat(filer_window->path, &info))
605 report_error("Error statting directory", g_strerror(errno));
606 return;
608 filer_window->m_time = info.st_mtime;
610 filer_window->dir = opendir(filer_window->path);
611 if (!filer_window->dir)
613 report_error("Error scanning directory", g_strerror(errno));
614 return;
617 filer_window->scan_min_width = 64;
619 filer_window->idle_scan_id = gtk_idle_add(idle_scan_dir, filer_window);
622 /* Another app has grabbed the selection */
623 static gint collection_lose_selection(GtkWidget *widget,
624 GdkEventSelection *event)
626 if (window_with_selection &&
627 window_with_selection->collection == COLLECTION(widget))
629 FilerWindow *filer_window = window_with_selection;
630 window_with_selection = NULL;
631 collection_clear_selection(filer_window->collection);
634 return TRUE;
637 /* Someone wants us to send them the selection */
638 static void selection_get(GtkWidget *widget,
639 GtkSelectionData *selection_data,
640 guint info,
641 guint time,
642 gpointer data)
644 GString *reply, *header;
645 FilerWindow *filer_window;
646 int i;
647 Collection *collection;
649 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
651 reply = g_string_new(NULL);
652 header = g_string_new(NULL);
654 switch (info)
656 case TARGET_STRING:
657 g_string_sprintf(header, " %s",
658 make_path(filer_window->path, "")->str);
659 break;
660 case TARGET_URI_LIST:
661 g_string_sprintf(header, " file://%s%s",
662 our_host_name(),
663 make_path(filer_window->path, "")->str);
664 break;
667 collection = filer_window->collection;
668 for (i = 0; i < collection->number_of_items; i++)
670 if (collection->items[i].selected)
672 FileItem *item =
673 (FileItem *) collection->items[i].data;
675 g_string_append(reply, header->str);
676 g_string_append(reply, item->leafname);
679 /* This works, but I don't think I like it... */
680 /* g_string_append_c(reply, ' '); */
682 gtk_selection_data_set(selection_data, xa_string,
683 8, reply->str + 1, reply->len - 1);
684 g_string_free(reply, TRUE);
685 g_string_free(header, TRUE);
688 /* No items are now selected. This might be because another app claimed
689 * the selection or because the user unselected all the items.
691 static void lose_selection(Collection *collection,
692 guint time,
693 gpointer user_data)
695 FilerWindow *filer_window = (FilerWindow *) user_data;
697 if (window_with_selection == filer_window)
699 window_with_selection = NULL;
700 gtk_selection_owner_set(NULL,
701 GDK_SELECTION_PRIMARY,
702 time);
706 static void gain_selection(Collection *collection,
707 guint time,
708 gpointer user_data)
710 FilerWindow *filer_window = (FilerWindow *) user_data;
712 if (gtk_selection_owner_set(GTK_WIDGET(collection),
713 GDK_SELECTION_PRIMARY,
714 time))
716 window_with_selection = filer_window;
718 else
719 collection_clear_selection(filer_window->collection);
722 static int sort_by_name(const void *item1, const void *item2)
724 return strcmp((*((FileItem **)item1))->leafname,
725 (*((FileItem **)item2))->leafname);
728 static int sort_by_type(const void *item1, const void *item2)
730 const FileItem *i1 = (FileItem *) ((CollectionItem *) item1)->data;
731 const FileItem *i2 = (FileItem *) ((CollectionItem *) item2)->data;
732 MIME_type *m1, *m2;
734 int diff = i1->base_type - i2->base_type;
736 if (!diff)
737 diff = (i1->flags & ITEM_FLAG_APPDIR)
738 - (i2->flags & ITEM_FLAG_APPDIR);
739 if (diff)
740 return diff > 0 ? 1 : -1;
742 m1 = i1->mime_type;
743 m2 = i2->mime_type;
745 if (m1 && m2)
747 diff = strcmp(m1->media_type, m2->media_type);
748 if (!diff)
749 diff = strcmp(m1->subtype, m2->subtype);
751 else if (m1 || m2)
752 diff = m1 ? 1 : -1;
753 else
754 diff = 0;
756 if (diff)
757 return diff > 0 ? 1 : -1;
759 return sort_by_name(item1, item2);
762 void open_item(Collection *collection,
763 gpointer item_data, int item_number,
764 gpointer user_data)
766 FilerWindow *filer_window = (FilerWindow *) user_data;
767 FileItem *item = (FileItem *) item_data;
768 GdkEventButton *event;
769 char *full_path;
770 GtkWidget *widget;
771 gboolean shift;
772 gboolean adjust; /* do alternative action */
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 != 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 ^ o_ro_bindings) || 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;