r127: The Show Hidden menu item now shows the true state for each window/panel.
[rox-filer.git] / ROX-Filer / src / filer.c
blob011d63b2c7e21b5c7434b3cb87799ba3c4ffdae7
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);
65 static char *filer_toolbar(char *data);
67 static OptionsSection options =
69 "Filer window options",
70 create_options,
71 update_options,
72 set_options,
73 save_options
75 static gboolean o_toolbar = TRUE;
76 static GtkWidget *toggle_toolbar;
77 static gboolean o_ro_bindings = FALSE;
78 static GtkWidget *toggle_ro_bindings;
80 /* Static prototypes */
81 static void filer_window_destroyed(GtkWidget *widget,
82 FilerWindow *filer_window);
83 static gboolean idle_scan_dir(gpointer data);
84 static void draw_item(GtkWidget *widget,
85 CollectionItem *item,
86 GdkRectangle *area);
87 void show_menu(Collection *collection, GdkEventButton *event,
88 int number_selected, gpointer user_data);
89 static int sort_by_name(const void *item1, const void *item2);
90 static void add_item(FilerWindow *filer_window, char *leafname);
91 static gboolean test_point(Collection *collection,
92 int point_x, int point_y,
93 CollectionItem *item,
94 int width, int height);
95 static void stop_scanning(FilerWindow *filer_window);
96 static gint focus_in(GtkWidget *widget,
97 GdkEventFocus *event,
98 FilerWindow *filer_window);
99 static gint focus_out(GtkWidget *widget,
100 GdkEventFocus *event,
101 FilerWindow *filer_window);
102 static void add_view(FilerWindow *filer_window);
103 static void remove_view(FilerWindow *filer_window);
104 static void free_item(FileItem *item);
105 static gboolean remove_deleted(gpointer item_data, gpointer data);
106 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window);
107 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window);
108 static void add_button(GtkContainer *box, int pixmap,
109 GtkSignalFunc cb, gpointer data);
110 static GtkWidget *create_toolbar(FilerWindow *filer_window);
112 static GdkAtom xa_string;
113 enum
115 TARGET_STRING,
116 TARGET_URI_LIST,
119 void filer_init()
121 xa_string = gdk_atom_intern("STRING", FALSE);
123 path_to_window_list = g_hash_table_new(g_str_hash, g_str_equal);
125 options_sections = g_slist_prepend(options_sections, &options);
126 option_register("filer_ro_bindings", filer_ro_bindings);
127 option_register("filer_toolbar", filer_toolbar);
131 /* When a filer window shows a directory, use this function to add
132 * it to the list of directories to be updated when the contents
133 * change.
135 static void add_view(FilerWindow *filer_window)
137 GList *list, *newlist;
139 g_return_if_fail(filer_window != NULL);
141 list = g_hash_table_lookup(path_to_window_list, filer_window->path);
142 newlist = g_list_prepend(list, filer_window);
143 if (newlist != list)
144 g_hash_table_insert(path_to_window_list, filer_window->path,
145 newlist);
148 /* When a filer window no longer shows a directory, call this to reverse
149 * the effect of add_view().
151 static void remove_view(FilerWindow *filer_window)
153 GList *list, *newlist;
155 g_return_if_fail(filer_window != NULL);
157 list = g_hash_table_lookup(path_to_window_list, filer_window->path);
158 newlist = g_list_remove(list, filer_window);
159 if (newlist != list)
160 g_hash_table_insert(path_to_window_list, filer_window->path,
161 newlist);
164 /* Go though all the FileItems in a collection, freeing all items.
165 * TODO: Maybe we should cache icons?
167 static void free_items(FilerWindow *filer_window)
169 guint i;
170 Collection *collection = filer_window->collection;
172 for (i = 0; i < collection->number_of_items; i++)
174 free_item((FileItem *) collection->items[i].data);
175 collection->items[i].data = NULL;
179 /* Set one item in a collection to NULL, freeing all data for it */
180 static void free_item(FileItem *item)
182 if (item->flags & ITEM_FLAG_TEMP_ICON)
184 pixmap_unref(item->image);
185 item->image = default_pixmap + TYPE_ERROR;
187 g_free(item->leafname);
188 g_free(item);
191 static void filer_window_destroyed(GtkWidget *widget,
192 FilerWindow *filer_window)
194 if (window_with_selection == filer_window)
195 window_with_selection = NULL;
196 if (window_with_focus == filer_window)
197 window_with_focus = NULL;
199 remove_view(filer_window);
201 free_items(filer_window);
202 if (filer_window->dir)
203 stop_scanning(filer_window);
204 g_free(filer_window->path);
205 g_free(filer_window);
207 if (--number_of_windows < 1)
208 gtk_main_quit();
211 static void stop_scanning(FilerWindow *filer_window)
213 g_return_if_fail(filer_window->dir != NULL);
215 closedir(filer_window->dir);
216 gtk_idle_remove(filer_window->idle_scan_id);
217 filer_window->dir = NULL;
218 if (filer_window->window->window)
219 gdk_window_set_cursor(filer_window->window->window, NULL);
222 /* This is called while we are scanning the directory */
223 static gboolean idle_scan_dir(gpointer data)
225 struct dirent *next;
226 FilerWindow *filer_window = (FilerWindow *) data;
230 next = readdir(filer_window->dir);
231 if (!next)
233 closedir(filer_window->dir);
234 filer_window->dir = NULL;
235 gdk_window_set_cursor(filer_window->window->window,
236 NULL);
238 collection_set_item_size(filer_window->collection,
239 filer_window->scan_min_width,
240 filer_window->collection->item_height);
241 collection_qsort(filer_window->collection,
242 filer_window->sort_fn);
243 if (filer_window->flags & FILER_UPDATING)
245 collection_delete_if(filer_window->collection,
246 remove_deleted, NULL);
248 if (filer_window->flags & FILER_NEEDS_RESCAN)
249 update_dir(filer_window);
251 return FALSE; /* Finished */
254 add_item(filer_window, next->d_name);
255 } while (!gtk_events_pending());
257 return TRUE;
260 /* Add a single object to a directory display */
261 static void add_item(FilerWindow *filer_window, char *leafname)
263 FileItem *item;
264 int old_item;
265 int item_width;
266 struct stat info;
267 int base_type;
268 GString *path;
270 if (leafname[0] == '.')
272 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
273 || (leafname[1] == '.' && leafname[2] == '\0'))
274 return;
277 item = g_malloc(sizeof(FileItem));
278 item->leafname = g_strdup(leafname);
279 item->flags = (ItemFlags) 0;
281 path = make_path(filer_window->path, leafname);
282 if (lstat(path->str, &info))
283 base_type = TYPE_ERROR;
284 else
286 if (S_ISREG(info.st_mode))
287 base_type = TYPE_FILE;
288 else if (S_ISDIR(info.st_mode))
290 base_type = TYPE_DIRECTORY;
292 if (g_hash_table_lookup(mtab_mounts, path->str))
293 item->flags |= ITEM_FLAG_MOUNT_POINT
294 | ITEM_FLAG_MOUNTED;
295 else if (g_hash_table_lookup(fstab_mounts, path->str))
296 item->flags |= ITEM_FLAG_MOUNT_POINT;
298 else if (S_ISBLK(info.st_mode))
299 base_type = TYPE_BLOCK_DEVICE;
300 else if (S_ISCHR(info.st_mode))
301 base_type = TYPE_CHAR_DEVICE;
302 else if (S_ISFIFO(info.st_mode))
303 base_type = TYPE_PIPE;
304 else if (S_ISSOCK(info.st_mode))
305 base_type = TYPE_SOCKET;
306 else if (S_ISLNK(info.st_mode))
308 if (stat(path->str, &info))
310 base_type = TYPE_ERROR;
312 else
314 if (S_ISREG(info.st_mode))
315 base_type = TYPE_FILE;
316 else if (S_ISDIR(info.st_mode))
317 base_type = TYPE_DIRECTORY;
318 else if (S_ISBLK(info.st_mode))
319 base_type = TYPE_BLOCK_DEVICE;
320 else if (S_ISCHR(info.st_mode))
321 base_type = TYPE_CHAR_DEVICE;
322 else if (S_ISFIFO(info.st_mode))
323 base_type = TYPE_PIPE;
324 else if (S_ISSOCK(info.st_mode))
325 base_type = TYPE_SOCKET;
326 else
327 base_type = TYPE_UNKNOWN;
330 item->flags |= ITEM_FLAG_SYMLINK;
332 else
333 base_type = TYPE_UNKNOWN;
336 item->base_type = base_type;
337 item->mime_type = NULL;
339 if (base_type == TYPE_DIRECTORY &&
340 !(item->flags & ITEM_FLAG_MOUNT_POINT))
342 uid_t uid = info.st_uid;
344 /* Might be an application directory - better check...
345 * AppRun must have the same owner as the directory
346 * (to stop people putting an AppRun in, eg, /tmp)
348 g_string_append(path, "/AppRun");
349 if (!stat(path->str, &info) && info.st_uid == uid)
351 item->flags |= ITEM_FLAG_APPDIR;
355 if (item->flags & ITEM_FLAG_APPDIR) /* path still ends /AppRun */
357 MaskedPixmap *app_icon;
359 g_string_truncate(path, path->len - 3);
360 g_string_append(path, "Icon.xpm");
361 app_icon = load_pixmap_from(filer_window->window, path->str);
362 if (app_icon)
364 item->image = app_icon;
365 item->flags |= ITEM_FLAG_TEMP_ICON;
367 else
368 item->image = default_pixmap + TYPE_APPDIR;
370 else
372 if (base_type == TYPE_FILE &&
373 (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
375 item->image = default_pixmap + TYPE_EXEC_FILE;
376 item->flags |= ITEM_FLAG_EXEC_FILE;
378 else if (base_type == TYPE_FILE)
380 item->mime_type = type_from_path(path->str);
381 item->image = type_to_icon(filer_window->window,
382 item->mime_type);
383 item->flags |= ITEM_FLAG_TEMP_ICON;
385 else
386 item->image = default_pixmap + base_type;
389 item->text_width = gdk_string_width(filer_window->window->style->font,
390 leafname);
392 if (!item->image)
393 item->image = default_pixmap + TYPE_UNKNOWN;
395 /* XXX: Must be a better way... */
396 item->pix_width = ((GdkPixmapPrivate *) item->image->pixmap)->width;
397 item->pix_height = ((GdkPixmapPrivate *) item->image->pixmap)->height;
399 item_width = MAX(item->pix_width, item->text_width) + 4;
401 if (item_width > filer_window->scan_min_width)
402 filer_window->scan_min_width = item_width;
404 if (item_width > filer_window->collection->item_width)
405 collection_set_item_size(filer_window->collection,
406 item_width,
407 filer_window->collection->item_height);
409 old_item = collection_find_item(filer_window->collection, item,
410 sort_by_name);
412 if (old_item >= 0)
414 CollectionItem *old =
415 &filer_window->collection->items[old_item];
417 free_item((FileItem *) old->data);
418 old->data = item;
419 collection_draw_item(filer_window->collection, old_item, TRUE);
421 else
422 collection_insert(filer_window->collection, item);
425 /* Is a point inside an item? */
426 static gboolean test_point(Collection *collection,
427 int point_x, int point_y,
428 CollectionItem *colitem,
429 int width, int height)
431 FileItem *item = (FileItem *) colitem->data;
432 GdkFont *font = GTK_WIDGET(collection)->style->font;
433 int text_height = font->ascent + font->descent;
434 int image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
435 int image_width = (item->pix_width >> 1) + 2;
436 int text_width = (item->text_width >> 1) + 2;
437 int x_limit;
439 if (point_y < image_y)
440 return FALSE; /* Too high up (don't worry about too low) */
442 if (point_y <= image_y + item->pix_height + 2)
443 x_limit = image_width;
444 else if (point_y > height - text_height - 2)
445 x_limit = text_width;
446 else
447 x_limit = MIN(image_width, text_width);
449 return ABS(point_x - (width >> 1)) < x_limit;
452 static void draw_item(GtkWidget *widget,
453 CollectionItem *colitem,
454 GdkRectangle *area)
456 FileItem *item = (FileItem *) colitem->data;
457 GdkGC *gc = colitem->selected ? widget->style->white_gc
458 : widget->style->black_gc;
459 int image_x = area->x + ((area->width - item->pix_width) >> 1);
460 GdkFont *font = widget->style->font;
461 int text_x = area->x + ((area->width - item->text_width) >> 1);
462 int text_y = area->y + area->height - font->descent - 2;
463 int text_height = font->ascent + font->descent;
465 if (item->image)
467 int image_y;
469 gdk_gc_set_clip_mask(gc, item->image->mask);
471 image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
472 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
473 gdk_draw_pixmap(widget->window, gc,
474 item->image->pixmap,
475 0, 0, /* Source x,y */
476 image_x, area->y + image_y, /* Dest x,y */
477 -1, MIN(item->pix_height, MAX_ICON_HEIGHT));
479 if (item->flags & ITEM_FLAG_SYMLINK)
481 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
482 gdk_gc_set_clip_mask(gc,
483 default_pixmap[TYPE_SYMLINK].mask);
484 gdk_draw_pixmap(widget->window, gc,
485 default_pixmap[TYPE_SYMLINK].pixmap,
486 0, 0, /* Source x,y */
487 image_x, area->y + 8, /* Dest x,y */
488 -1, -1);
490 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
492 int type = item->flags & ITEM_FLAG_MOUNTED
493 ? TYPE_MOUNTED
494 : TYPE_UNMOUNTED;
495 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
496 gdk_gc_set_clip_mask(gc,
497 default_pixmap[type].mask);
498 gdk_draw_pixmap(widget->window, gc,
499 default_pixmap[type].pixmap,
500 0, 0, /* Source x,y */
501 image_x, area->y + 8, /* Dest x,y */
502 -1, -1);
505 gdk_gc_set_clip_mask(gc, NULL);
506 gdk_gc_set_clip_origin(gc, 0, 0);
509 if (colitem->selected)
510 gtk_paint_flat_box(widget->style, widget->window,
511 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
512 NULL, widget, "text",
513 text_x, text_y - font->ascent,
514 item->text_width,
515 text_height);
517 gdk_draw_text(widget->window,
518 widget->style->font,
519 colitem->selected ? widget->style->white_gc
520 : widget->style->black_gc,
521 text_x, text_y,
522 item->leafname, strlen(item->leafname));
525 void show_menu(Collection *collection, GdkEventButton *event,
526 int item, gpointer user_data)
528 show_filer_menu((FilerWindow *) user_data, event, item);
531 static void may_rescan(FilerWindow *filer_window)
533 struct stat info;
535 g_return_if_fail(filer_window != NULL);
537 if (stat(filer_window->path, &info))
539 delayed_error("ROX-Filer", "Directory deleted");
540 gtk_widget_destroy(filer_window->window);
542 else if (info.st_mtime > filer_window->m_time)
544 if (filer_window->dir)
545 filer_window->flags |= FILER_NEEDS_RESCAN;
546 else
547 update_dir(filer_window);
551 /* Callback to collection_delete_if() */
552 static gboolean remove_deleted(gpointer item_data, gpointer data)
554 FileItem *item = (FileItem *) item_data;
556 if (item->flags & ITEM_FLAG_MAY_DELETE)
558 free_item(item);
559 return TRUE;
562 return FALSE;
565 /* Forget the old contents of a filer window and scan the directory
566 * from the start. If we are already scanning then rescan later.
568 void scan_dir(FilerWindow *filer_window)
570 if (filer_window->dir)
571 stop_scanning(filer_window);
573 free_items(filer_window);
574 collection_clear(filer_window->collection);
576 update_dir(filer_window);
577 filer_window->flags &= ~FILER_UPDATING;
579 gdk_window_set_cursor(filer_window->window->window,
580 gdk_cursor_new(GDK_WATCH));
583 /* Like scan_dir(), but assume new display will be similar to the old
584 * one (less flicker and doesn't lose the selection).
586 void update_dir(FilerWindow *filer_window)
588 Collection *collection = filer_window->collection;
589 struct stat info;
590 guint i;
592 if (filer_window->dir)
594 /* Already scanning - start again when we finish */
595 filer_window->flags |= FILER_NEEDS_RESCAN;
596 return;
598 filer_window->flags &= ~FILER_NEEDS_RESCAN;
599 filer_window->flags |= FILER_UPDATING;
601 for (i = 0; i < collection->number_of_items; i++)
603 FileItem *item = (FileItem *) collection->items[i].data;
604 item->flags |= ITEM_FLAG_MAY_DELETE;
607 mount_update();
609 gtk_window_set_title(GTK_WINDOW(filer_window->window),
610 filer_window->path);
612 if (stat(filer_window->path, &info))
614 report_error("Error statting directory", g_strerror(errno));
615 return;
617 filer_window->m_time = info.st_mtime;
619 filer_window->dir = opendir(filer_window->path);
620 if (!filer_window->dir)
622 report_error("Error scanning directory", g_strerror(errno));
623 return;
626 filer_window->scan_min_width = 64;
628 filer_window->idle_scan_id = gtk_idle_add(idle_scan_dir, filer_window);
631 /* Another app has grabbed the selection */
632 static gint collection_lose_selection(GtkWidget *widget,
633 GdkEventSelection *event)
635 if (window_with_selection &&
636 window_with_selection->collection == COLLECTION(widget))
638 FilerWindow *filer_window = window_with_selection;
639 window_with_selection = NULL;
640 collection_clear_selection(filer_window->collection);
643 return TRUE;
646 /* Someone wants us to send them the selection */
647 static void selection_get(GtkWidget *widget,
648 GtkSelectionData *selection_data,
649 guint info,
650 guint time,
651 gpointer data)
653 GString *reply, *header;
654 FilerWindow *filer_window;
655 int i;
656 Collection *collection;
658 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
660 reply = g_string_new(NULL);
661 header = g_string_new(NULL);
663 switch (info)
665 case TARGET_STRING:
666 g_string_sprintf(header, " %s",
667 make_path(filer_window->path, "")->str);
668 break;
669 case TARGET_URI_LIST:
670 g_string_sprintf(header, " file://%s%s",
671 our_host_name(),
672 make_path(filer_window->path, "")->str);
673 break;
676 collection = filer_window->collection;
677 for (i = 0; i < collection->number_of_items; i++)
679 if (collection->items[i].selected)
681 FileItem *item =
682 (FileItem *) collection->items[i].data;
684 g_string_append(reply, header->str);
685 g_string_append(reply, item->leafname);
688 /* This works, but I don't think I like it... */
689 /* g_string_append_c(reply, ' '); */
691 gtk_selection_data_set(selection_data, xa_string,
692 8, reply->str + 1, reply->len - 1);
693 g_string_free(reply, TRUE);
694 g_string_free(header, TRUE);
697 /* No items are now selected. This might be because another app claimed
698 * the selection or because the user unselected all the items.
700 static void lose_selection(Collection *collection,
701 guint time,
702 gpointer user_data)
704 FilerWindow *filer_window = (FilerWindow *) user_data;
706 if (window_with_selection == filer_window)
708 window_with_selection = NULL;
709 gtk_selection_owner_set(NULL,
710 GDK_SELECTION_PRIMARY,
711 time);
715 static void gain_selection(Collection *collection,
716 guint time,
717 gpointer user_data)
719 FilerWindow *filer_window = (FilerWindow *) user_data;
721 if (gtk_selection_owner_set(GTK_WIDGET(collection),
722 GDK_SELECTION_PRIMARY,
723 time))
725 window_with_selection = filer_window;
727 else
728 collection_clear_selection(filer_window->collection);
731 static int sort_by_name(const void *item1, const void *item2)
733 return strcmp((*((FileItem **)item1))->leafname,
734 (*((FileItem **)item2))->leafname);
737 static int sort_by_type(const void *item1, const void *item2)
739 const FileItem *i1 = (FileItem *) ((CollectionItem *) item1)->data;
740 const FileItem *i2 = (FileItem *) ((CollectionItem *) item2)->data;
741 MIME_type *m1, *m2;
743 int diff = i1->base_type - i2->base_type;
745 if (!diff)
746 diff = (i1->flags & ITEM_FLAG_APPDIR)
747 - (i2->flags & ITEM_FLAG_APPDIR);
748 if (diff)
749 return diff > 0 ? 1 : -1;
751 m1 = i1->mime_type;
752 m2 = i2->mime_type;
754 if (m1 && m2)
756 diff = strcmp(m1->media_type, m2->media_type);
757 if (!diff)
758 diff = strcmp(m1->subtype, m2->subtype);
760 else if (m1 || m2)
761 diff = m1 ? 1 : -1;
762 else
763 diff = 0;
765 if (diff)
766 return diff > 0 ? 1 : -1;
768 return sort_by_name(item1, item2);
771 void open_item(Collection *collection,
772 gpointer item_data, int item_number,
773 gpointer user_data)
775 FilerWindow *filer_window = (FilerWindow *) user_data;
776 FileItem *item = (FileItem *) item_data;
777 GdkEventButton *event;
778 char *full_path;
779 GtkWidget *widget;
780 gboolean shift;
781 gboolean adjust; /* do alternative action */
783 event = (GdkEventButton *) gtk_get_current_event();
784 full_path = make_path(filer_window->path, item->leafname)->str;
786 collection_wink_item(filer_window->collection, item_number);
788 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_BUTTON_PRESS)
790 shift = event->state & GDK_SHIFT_MASK;
791 adjust = (event->button != 1)
792 ^ ((event->state & GDK_CONTROL_MASK) != 0);
794 else
796 shift = FALSE;
797 adjust = FALSE;
800 widget = filer_window->window;
802 switch (item->base_type)
804 case TYPE_DIRECTORY:
805 if (item->flags & ITEM_FLAG_APPDIR && !shift)
807 run_app(make_path(filer_window->path,
808 item->leafname)->str);
809 if (adjust && !filer_window->panel)
810 gtk_widget_destroy(widget);
811 break;
813 if ((adjust ^ o_ro_bindings) || filer_window->panel)
814 filer_opendir(full_path, FALSE, BOTTOM);
815 else
817 remove_view(filer_window);
818 filer_window->path = pathdup(full_path);
819 add_view(filer_window);
820 scan_dir(filer_window);
822 break;
823 case TYPE_FILE:
824 if (item->flags & ITEM_FLAG_EXEC_FILE && !shift)
826 char *argv[] = {NULL, NULL};
828 argv[0] = full_path;
830 if (spawn_full(argv, getenv("HOME"), 0))
832 if (adjust && !filer_window->panel)
833 gtk_widget_destroy(widget);
835 else
836 report_error("ROX-Filer",
837 "Failed to fork() child");
839 else
841 GString *message;
842 MIME_type *type = shift ? &text_plain
843 : item->mime_type;
845 g_return_if_fail(type != NULL);
847 if (type_open(full_path, type))
849 if (adjust && !filer_window->panel)
850 gtk_widget_destroy(widget);
852 else
854 message = g_string_new(NULL);
855 g_string_sprintf(message, "No open "
856 "action specified for files of "
857 "this type (%s/%s)",
858 type->media_type,
859 type->subtype);
860 report_error("ROX-Filer", message->str);
861 g_string_free(message, TRUE);
864 break;
865 default:
866 report_error("open_item",
867 "I don't know how to open that");
868 break;
872 static gint pointer_in(GtkWidget *widget,
873 GdkEventCrossing *event,
874 FilerWindow *filer_window)
876 may_rescan(filer_window);
877 return FALSE;
880 static gint focus_in(GtkWidget *widget,
881 GdkEventFocus *event,
882 FilerWindow *filer_window)
884 window_with_focus = filer_window;
886 return FALSE;
889 static gint focus_out(GtkWidget *widget,
890 GdkEventFocus *event,
891 FilerWindow *filer_window)
893 /* TODO: Shade the cursor */
895 return FALSE;
898 /* Handle keys that can't be bound with the menu */
899 static gint key_press_event(GtkWidget *widget,
900 GdkEventKey *event,
901 FilerWindow *filer_window)
903 switch (event->keyval)
906 case GDK_Left:
907 move_cursor(-1, 0);
908 break;
909 case GDK_Right:
910 move_cursor(1, 0);
911 break;
912 case GDK_Up:
913 move_cursor(0, -1);
914 break;
915 case GDK_Down:
916 move_cursor(0, 1);
917 break;
918 case GDK_Return:
920 case GDK_BackSpace:
921 change_to_parent(filer_window);
922 return TRUE;
925 return FALSE;
928 static void toolbar_home_clicked(GtkWidget *widget, FilerWindow *filer_window)
930 remove_view(filer_window);
931 filer_window->path = pathdup(getenv("HOME"));
932 add_view(filer_window);
933 scan_dir(filer_window);
936 static void toolbar_up_clicked(GtkWidget *widget, FilerWindow *filer_window)
938 change_to_parent(filer_window);
941 void change_to_parent(FilerWindow *filer_window)
943 remove_view(filer_window);
944 filer_window->path = pathdup(make_path(
945 filer_window->path,
946 "..")->str);
947 add_view(filer_window);
948 scan_dir(filer_window);
951 FileItem *selected_item(Collection *collection)
953 int i;
955 g_return_val_if_fail(collection != NULL, NULL);
956 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
957 g_return_val_if_fail(collection->number_selected == 1, NULL);
959 for (i = 0; i < collection->number_of_items; i++)
960 if (collection->items[i].selected)
961 return (FileItem *) collection->items[i].data;
963 g_warning("selected_item: number_selected is wrong\n");
965 return NULL;
968 /* Refresh all windows onto this directory */
969 void refresh_dirs(char *path)
971 char *real;
972 GList *list, *next;
974 real = pathdup(path);
975 list = g_hash_table_lookup(path_to_window_list, real);
976 g_free(real);
978 while (list)
980 next = list->next;
981 update_dir((FilerWindow *) list->data);
982 list = next;
986 void filer_opendir(char *path, gboolean panel, Side panel_side)
988 GtkWidget *hbox, *scrollbar, *collection;
989 FilerWindow *filer_window;
990 GtkTargetEntry target_table[] =
992 {"text/uri-list", 0, TARGET_URI_LIST},
993 {"STRING", 0, TARGET_STRING},
996 filer_window = g_malloc(sizeof(FilerWindow));
997 filer_window->path = pathdup(path);
998 filer_window->dir = NULL; /* Not scanning */
999 filer_window->show_hidden = FALSE;
1000 filer_window->panel = panel;
1001 filer_window->panel_side = panel_side;
1002 filer_window->temp_item_selected = FALSE;
1003 filer_window->sort_fn = sort_by_type;
1004 filer_window->flags = (FilerFlags) 0;
1006 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1008 collection = collection_new(NULL);
1009 gtk_object_set_data(GTK_OBJECT(collection),
1010 "filer_window", filer_window);
1011 filer_window->collection = COLLECTION(collection);
1012 collection_set_item_size(filer_window->collection, 64, 64);
1013 collection_set_functions(filer_window->collection,
1014 draw_item, test_point);
1016 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1017 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1018 "enter-notify-event",
1019 GTK_SIGNAL_FUNC(pointer_in), filer_window);
1020 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
1021 GTK_SIGNAL_FUNC(focus_in), filer_window);
1022 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
1023 GTK_SIGNAL_FUNC(focus_out), filer_window);
1024 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1025 filer_window_destroyed, filer_window);
1027 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
1028 open_item, filer_window);
1029 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
1030 show_menu, filer_window);
1031 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
1032 gain_selection, filer_window);
1033 gtk_signal_connect(GTK_OBJECT(collection), "lose_selection",
1034 lose_selection, filer_window);
1035 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
1036 drag_selection, filer_window);
1037 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
1038 drag_data_get, filer_window);
1039 gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event",
1040 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1041 gtk_signal_connect (GTK_OBJECT(collection), "selection_get",
1042 GTK_SIGNAL_FUNC(selection_get), NULL);
1043 gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY,
1044 target_table,
1045 sizeof(target_table) / sizeof(*target_table));
1047 drag_set_dest(collection);
1049 if (panel)
1051 int swidth, sheight, iwidth, iheight;
1052 GtkWidget *frame, *win = filer_window->window;
1054 collection_set_panel(filer_window->collection, TRUE);
1056 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
1057 iwidth = filer_window->collection->item_width;
1058 iheight = filer_window->collection->item_height;
1060 if (panel_side == TOP || panel_side == BOTTOM)
1062 int height = iheight + PANEL_BORDER;
1063 int y = panel_side == TOP
1064 ? -PANEL_BORDER
1065 : sheight - height - PANEL_BORDER;
1067 gtk_widget_set_usize(collection, swidth, height);
1068 gtk_widget_set_uposition(win, 0, y);
1070 else
1072 int width = iwidth + PANEL_BORDER;
1073 int x = panel_side == LEFT
1074 ? -PANEL_BORDER
1075 : swidth - width - PANEL_BORDER;
1077 gtk_widget_set_usize(collection, width, sheight);
1078 gtk_widget_set_uposition(win, x, 0);
1081 frame = gtk_frame_new(NULL);
1082 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1083 gtk_container_add(GTK_CONTAINER(frame), collection);
1084 gtk_container_add(GTK_CONTAINER(win), frame);
1086 gtk_widget_realize(win);
1087 if (override_redirect)
1088 gdk_window_set_override_redirect(win->window, TRUE);
1089 make_panel_window(win->window);
1091 else
1093 hbox = gtk_hbox_new(FALSE, 0);
1095 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1096 "key_press_event",
1097 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1098 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
1099 400,
1100 o_toolbar ? 220 : 200);
1102 gtk_container_add(GTK_CONTAINER(filer_window->window),
1103 hbox);
1104 if (o_toolbar)
1106 GtkWidget *vbox, *toolbar;
1109 vbox = gtk_vbox_new(FALSE, 0);
1110 gtk_box_pack_start(GTK_BOX(hbox), vbox,
1111 TRUE, TRUE, 0);
1112 toolbar = create_toolbar(filer_window);
1113 gtk_box_pack_start(GTK_BOX(vbox), toolbar,
1114 FALSE, TRUE, 0);
1116 gtk_box_pack_start(GTK_BOX(vbox), collection,
1117 TRUE, TRUE, 0);
1119 else
1120 gtk_box_pack_start(GTK_BOX(hbox), collection,
1121 TRUE, TRUE, 0);
1123 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1124 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
1125 gtk_accel_group_attach(filer_keys,
1126 GTK_OBJECT(filer_window->window));
1129 gtk_widget_show_all(filer_window->window);
1130 number_of_windows++;
1132 add_view(filer_window);
1133 scan_dir(filer_window);
1136 static GtkWidget *create_toolbar(FilerWindow *filer_window)
1138 GtkWidget *frame, *box;
1140 frame = gtk_frame_new(NULL);
1141 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
1143 box = gtk_hbutton_box_new();
1144 gtk_button_box_set_child_size_default(16, 16);
1145 gtk_hbutton_box_set_spacing_default(2);
1146 gtk_button_box_set_layout(GTK_BUTTON_BOX(box), GTK_BUTTONBOX_START);
1147 gtk_container_add(GTK_CONTAINER(frame), box);
1148 add_button(GTK_CONTAINER(box), TOOLBAR_UP_ICON,
1149 GTK_SIGNAL_FUNC(toolbar_up_clicked),
1150 filer_window);
1151 add_button(GTK_CONTAINER(box), TOOLBAR_HOME_ICON,
1152 GTK_SIGNAL_FUNC(toolbar_home_clicked),
1153 filer_window);
1155 return frame;
1158 static void add_button(GtkContainer *box, int pixmap,
1159 GtkSignalFunc cb, gpointer data)
1161 GtkWidget *button, *icon;
1163 button = gtk_button_new();
1164 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
1165 gtk_container_add(box, button);
1167 icon = gtk_pixmap_new(default_pixmap[pixmap].pixmap,
1168 default_pixmap[pixmap].mask);
1169 gtk_container_add(GTK_CONTAINER(button), icon);
1170 gtk_signal_connect(GTK_OBJECT(button), "clicked", cb, data);
1173 /* Build up some option widgets to go in the options dialog, but don't
1174 * fill them in yet.
1176 static GtkWidget *create_options()
1178 GtkWidget *vbox;
1180 vbox = gtk_vbox_new(FALSE, 0);
1181 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1183 toggle_ro_bindings =
1184 gtk_check_button_new_with_label("Use RISC OS mouse bindings");
1185 gtk_box_pack_start(GTK_BOX(vbox), toggle_ro_bindings, FALSE, TRUE, 0);
1187 toggle_toolbar =
1188 gtk_check_button_new_with_label("Show toolbar on new windows");
1189 gtk_box_pack_start(GTK_BOX(vbox), toggle_toolbar, FALSE, TRUE, 0);
1191 return vbox;
1194 /* Reflect current state by changing the widgets in the options box */
1195 static void update_options()
1197 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_ro_bindings),
1198 o_ro_bindings);
1199 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_toolbar),
1200 o_toolbar);
1203 /* Set current values by reading the states of the widgets in the options box */
1204 static void set_options()
1206 o_ro_bindings = gtk_toggle_button_get_active(
1207 GTK_TOGGLE_BUTTON(toggle_ro_bindings));
1208 o_toolbar = gtk_toggle_button_get_active(
1209 GTK_TOGGLE_BUTTON(toggle_toolbar));
1212 static void save_options()
1214 option_write("filer_ro_bindings", o_ro_bindings ? "1" : "0");
1215 option_write("filer_toolbar", o_toolbar ? "1" : "0");
1218 static char *filer_ro_bindings(char *data)
1220 o_ro_bindings = atoi(data) != 0;
1221 return NULL;
1225 static char *filer_toolbar(char *data)
1227 o_toolbar = atoi(data) != 0;
1228 return NULL;