r33: Changed to using cursor to highlight panel items instead of selecting them.
[rox-filer.git] / ROX-Filer / src / filer.c
blob8462f8da36b614bdf3f5ed7bd26aafbe2d67b523
1 /* vi: set cindent:
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
6 */
8 /* filer.c - code for handling filer windows */
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <errno.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <time.h>
17 #include <gtk/gtk.h>
18 #include <gdk/gdkx.h>
19 #include <gdk/gdkprivate.h> /* XXX - find another way to do this */
20 #include <gdk/gdkkeysyms.h>
21 #include <collection.h>
23 #include "support.h"
24 #include "gui_support.h"
25 #include "filer.h"
26 #include "pixmaps.h"
27 #include "menu.h"
28 #include "dnd.h"
29 #include "apps.h"
30 #include "mount.h"
32 #define MAX_ICON_HEIGHT 42
33 #define PANEL_BORDER 2
35 FilerWindow *window_with_focus = NULL;
37 /* When a child process that changes a directory dies we need to know
38 * which filer window to update. Use this table.
40 GHashTable *child_to_filer = NULL;
42 static int number_of_windows = 0;
43 static FilerWindow *window_with_selection = NULL;
44 static FilerWindow *panel_with_timeout = NULL;
45 static gint panel_timeout;
47 /* Static prototypes */
48 static void filer_window_destroyed(GtkWidget *widget,
49 FilerWindow *filer_window);
50 static gboolean idle_scan_dir(gpointer data);
51 static void draw_item(GtkWidget *widget,
52 CollectionItem *item,
53 GdkRectangle *area);
54 void show_menu(Collection *collection, GdkEventButton *event,
55 int number_selected, gpointer user_data);
56 static int sort_by_name(const void *item1, const void *item2);
57 static void add_item(FilerWindow *filer_window, char *leafname);
58 static gboolean test_point(Collection *collection,
59 int point_x, int point_y,
60 CollectionItem *item,
61 int width, int height);
62 static void stop_scanning(FilerWindow *filer_window);
63 static gint focus_in(GtkWidget *widget,
64 GdkEventFocus *event,
65 FilerWindow *filer_window);
66 static gint focus_out(GtkWidget *widget,
67 GdkEventFocus *event,
68 FilerWindow *filer_window);
70 void filer_init()
72 child_to_filer = g_hash_table_new(NULL, NULL);
75 /* When a filer window is destroyed we call this for each entry in the
76 * child_to_filer hash table to remove old entries.
78 static gboolean child_eq(gpointer key, gpointer data, gpointer filer_window)
80 return data == filer_window;
83 /* Go though all the FileItems in a collection, freeing all the temp
84 * icons.
85 * TODO: Maybe we should cache icons?
87 static void free_temp_icons(FilerWindow *filer_window)
89 int i;
90 Collection *collection = filer_window->collection;
92 for (i = 0; i < collection->number_of_items; i++)
94 FileItem *item = (FileItem *) collection->items[i].data;
95 if (item->flags & ITEM_FLAG_TEMP_ICON)
97 gdk_pixmap_unref(item->image->pixmap);
98 gdk_pixmap_unref(item->image->mask);
99 g_free(item->image);
100 item->image = default_pixmap + TYPE_ERROR;
105 static void filer_window_destroyed(GtkWidget *widget,
106 FilerWindow *filer_window)
108 if (window_with_selection == filer_window)
109 window_with_selection = NULL;
110 if (window_with_focus == filer_window)
111 window_with_focus = NULL;
112 if (panel_with_timeout == filer_window)
114 /* Can this happen? */
115 panel_with_timeout = NULL;
116 gtk_timeout_remove(panel_timeout);
119 g_hash_table_foreach_remove(child_to_filer, child_eq, filer_window);
121 free_temp_icons(filer_window);
122 if (filer_window->dir)
123 stop_scanning(filer_window);
124 g_free(filer_window->path);
125 g_free(filer_window);
127 if (--number_of_windows < 1)
128 gtk_main_quit();
131 static void stop_scanning(FilerWindow *filer_window)
133 g_return_if_fail(filer_window->dir != NULL);
135 closedir(filer_window->dir);
136 gtk_idle_remove(filer_window->idle_scan_id);
137 filer_window->dir = NULL;
140 /* This is called while we are scanning the directory */
141 static gboolean idle_scan_dir(gpointer data)
143 struct dirent *next;
144 FilerWindow *filer_window = (FilerWindow *) data;
148 next = readdir(filer_window->dir);
149 if (!next)
151 closedir(filer_window->dir);
152 filer_window->dir = NULL;
154 collection_qsort(filer_window->collection,
155 sort_by_name);
156 return FALSE; /* Finished */
159 add_item(filer_window, next->d_name);
160 } while (!gtk_events_pending());
162 return TRUE;
165 /* Add a single object to a directory display */
166 static void add_item(FilerWindow *filer_window, char *leafname)
168 FileItem *item;
169 int item_width;
170 struct stat info;
171 int base_type;
172 GString *path;
174 /* Ignore dot files (should be an option) */
175 if (leafname[0] == '.')
176 return;
178 item = g_malloc(sizeof(FileItem));
179 item->leafname = g_strdup(leafname);
180 item->flags = 0;
182 path = make_path(filer_window->path, leafname);
183 if (lstat(path->str, &info))
184 base_type = TYPE_ERROR;
185 else
187 if (S_ISREG(info.st_mode))
188 base_type = TYPE_FILE;
189 else if (S_ISDIR(info.st_mode))
191 base_type = TYPE_DIRECTORY;
193 if (g_hash_table_lookup(mtab_mounts, path->str))
194 item->flags |= ITEM_FLAG_MOUNT_POINT
195 | ITEM_FLAG_MOUNTED;
196 else if (g_hash_table_lookup(fstab_mounts, path->str))
197 item->flags |= ITEM_FLAG_MOUNT_POINT;
199 else if (S_ISBLK(info.st_mode))
200 base_type = TYPE_BLOCK_DEVICE;
201 else if (S_ISCHR(info.st_mode))
202 base_type = TYPE_CHAR_DEVICE;
203 else if (S_ISFIFO(info.st_mode))
204 base_type = TYPE_PIPE;
205 else if (S_ISSOCK(info.st_mode))
206 base_type = TYPE_SOCKET;
207 else if (S_ISLNK(info.st_mode))
209 if (stat(path->str, &info))
211 base_type = TYPE_ERROR;
213 else
215 if (S_ISREG(info.st_mode))
216 base_type = TYPE_FILE;
217 else if (S_ISDIR(info.st_mode))
218 base_type = TYPE_DIRECTORY;
219 else if (S_ISBLK(info.st_mode))
220 base_type = TYPE_BLOCK_DEVICE;
221 else if (S_ISCHR(info.st_mode))
222 base_type = TYPE_CHAR_DEVICE;
223 else if (S_ISFIFO(info.st_mode))
224 base_type = TYPE_PIPE;
225 else if (S_ISSOCK(info.st_mode))
226 base_type = TYPE_SOCKET;
227 else
228 base_type = TYPE_UNKNOWN;
231 item->flags |= ITEM_FLAG_SYMLINK;
233 else
234 base_type = TYPE_UNKNOWN;
237 item->base_type = base_type;
239 if (base_type == TYPE_DIRECTORY)
241 /* Might be an application directory - better check... */
242 g_string_append(path, "/AppRun");
243 if (!stat(path->str, &info))
245 item->flags |= ITEM_FLAG_APPDIR;
249 if (item->flags & ITEM_FLAG_APPDIR) /* path still ends /AppRun */
251 MaskedPixmap *app_icon;
253 g_string_truncate(path, path->len - 3);
254 g_string_append(path, "Icon.xpm");
255 app_icon = load_pixmap_from(filer_window->window, path->str);
256 if (app_icon)
258 item->image = app_icon;
259 item->flags |= ITEM_FLAG_TEMP_ICON;
261 else
262 item->image = default_pixmap + TYPE_APPDIR;
264 else
265 item->image = default_pixmap + base_type;
267 item->text_width = gdk_string_width(filer_window->window->style->font,
268 leafname);
270 /* XXX: Must be a better way... */
271 item->pix_width = ((GdkPixmapPrivate *) item->image->pixmap)->width;
272 item->pix_height = ((GdkPixmapPrivate *) item->image->pixmap)->height;
274 item_width = MAX(item->pix_width, item->text_width) + 4;
276 if (item_width > filer_window->collection->item_width)
277 collection_set_item_size(filer_window->collection,
278 item_width,
279 filer_window->collection->item_height);
281 collection_insert(filer_window->collection, item);
284 static gboolean test_point(Collection *collection,
285 int point_x, int point_y,
286 CollectionItem *colitem,
287 int width, int height)
289 FileItem *item = (FileItem *) colitem->data;
290 GdkFont *font = GTK_WIDGET(collection)->style->font;
291 int text_height = font->ascent + font->descent;
292 int x_off = ABS(point_x - (width >> 1));
293 int image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
295 if (x_off <= (item->pix_width >> 1) + 2 &&
296 point_y >= image_y && point_y <= image_y + item->pix_height)
297 return TRUE;
299 if (x_off <= (item->text_width >> 1) + 2 &&
300 point_y > height - text_height - 2)
301 return TRUE;
303 return FALSE;
306 static void draw_item(GtkWidget *widget,
307 CollectionItem *colitem,
308 GdkRectangle *area)
310 FileItem *item = (FileItem *) colitem->data;
311 GdkGC *gc = colitem->selected ? widget->style->white_gc
312 : widget->style->black_gc;
313 int image_x = area->x + ((area->width - item->pix_width) >> 1);
314 GdkFont *font = widget->style->font;
315 int text_x = area->x + ((area->width - item->text_width) >> 1);
316 int text_y = area->y + area->height - font->descent - 2;
317 int text_height = font->ascent + font->descent;
319 if (item->image)
321 int image_y;
323 gdk_gc_set_clip_mask(gc, item->image->mask);
325 image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
326 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
327 gdk_draw_pixmap(widget->window, gc,
328 item->image->pixmap,
329 0, 0, /* Source x,y */
330 image_x, area->y + image_y, /* Dest x,y */
331 -1, MIN(item->pix_height, MAX_ICON_HEIGHT));
333 if (item->flags & ITEM_FLAG_SYMLINK)
335 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
336 gdk_gc_set_clip_mask(gc,
337 default_pixmap[TYPE_SYMLINK].mask);
338 gdk_draw_pixmap(widget->window, gc,
339 default_pixmap[TYPE_SYMLINK].pixmap,
340 0, 0, /* Source x,y */
341 image_x, area->y + 8, /* Dest x,y */
342 -1, -1);
344 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
346 int type = item->flags & ITEM_FLAG_MOUNTED
347 ? TYPE_MOUNTED
348 : TYPE_UNMOUNTED;
349 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
350 gdk_gc_set_clip_mask(gc,
351 default_pixmap[type].mask);
352 gdk_draw_pixmap(widget->window, gc,
353 default_pixmap[type].pixmap,
354 0, 0, /* Source x,y */
355 image_x, area->y + 8, /* Dest x,y */
356 -1, -1);
359 gdk_gc_set_clip_mask(gc, NULL);
360 gdk_gc_set_clip_origin(gc, 0, 0);
363 if (colitem->selected)
364 gtk_paint_flat_box(widget->style, widget->window,
365 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
366 NULL, widget, "text",
367 text_x, text_y - font->ascent,
368 item->text_width,
369 text_height);
371 gdk_draw_text(widget->window,
372 widget->style->font,
373 colitem->selected ? widget->style->white_gc
374 : widget->style->black_gc,
375 text_x, text_y,
376 item->leafname, strlen(item->leafname));
379 void show_menu(Collection *collection, GdkEventButton *event,
380 int item, gpointer user_data)
382 show_filer_menu((FilerWindow *) user_data, event, item);
385 void scan_dir(FilerWindow *filer_window)
387 struct stat info;
389 if (filer_window->dir)
390 stop_scanning(filer_window);
391 if (panel_with_timeout == filer_window)
393 panel_with_timeout = NULL;
394 gtk_timeout_remove(panel_timeout);
397 mount_update();
399 collection_set_item_size(filer_window->collection, 64, 64);
400 free_temp_icons(filer_window);
401 collection_clear(filer_window->collection);
402 gtk_window_set_title(GTK_WINDOW(filer_window->window),
403 filer_window->path);
405 if (stat(filer_window->path, &info))
407 report_error("Error statting directory", g_strerror(errno));
408 return;
410 filer_window->m_time = info.st_mtime;
412 filer_window->dir = opendir(filer_window->path);
413 if (!filer_window->dir)
415 report_error("Error scanning directory", g_strerror(errno));
416 return;
419 filer_window->idle_scan_id = gtk_idle_add(idle_scan_dir, filer_window);
422 static void gain_selection(Collection *collection,
423 gint number_selected,
424 gpointer user_data)
426 FilerWindow *filer_window = (FilerWindow *) user_data;
428 if (window_with_selection && window_with_selection != filer_window)
429 collection_clear_selection(window_with_selection->collection);
431 window_with_selection = filer_window;
434 static int sort_by_name(const void *item1, const void *item2)
436 return strcmp((*((FileItem **)item1))->leafname,
437 (*((FileItem **)item2))->leafname);
440 static gint clear_panel_hilight(gpointer data)
442 collection_set_cursor_item(panel_with_timeout->collection, -1);
443 panel_with_timeout = NULL;
445 return FALSE;
448 /* It is possible to highlight an item briefly on a panel by calling this
449 * function.
451 void panel_set_timeout(FilerWindow *filer_window, gulong msec)
453 if (panel_with_timeout)
455 /* Can't have two timeouts at once */
456 gtk_timeout_remove(panel_timeout);
457 clear_panel_hilight(NULL);
460 if (filer_window)
462 panel_with_timeout = filer_window;
463 panel_timeout = gtk_timeout_add(msec,
464 clear_panel_hilight, NULL);
468 void open_item(Collection *collection,
469 gpointer item_data, int item_number,
470 gpointer user_data)
472 FilerWindow *filer_window = (FilerWindow *) user_data;
473 FileItem *item = (FileItem *) item_data;
474 GdkEventButton *event;
475 char *full_path;
477 event = (GdkEventButton *) gtk_get_current_event();
478 full_path = make_path(filer_window->path, item->leafname)->str;
480 if (filer_window->panel)
482 panel_set_timeout(NULL, 0);
483 collection_set_cursor_item(collection, item_number);
484 gdk_flush();
485 panel_set_timeout(filer_window, 200);
488 switch (item->base_type)
490 case TYPE_DIRECTORY:
491 if (item->flags & ITEM_FLAG_APPDIR &&
492 (event->type != GDK_2BUTTON_PRESS ||
493 (event->state & GDK_SHIFT_MASK) == 0))
495 run_app(make_path(filer_window->path,
496 item->leafname)->str);
497 break;
499 if (filer_window->panel == FALSE &&
500 (event->type != GDK_2BUTTON_PRESS ||
501 event->button == 1))
503 filer_window->path = pathdup(full_path);
504 scan_dir(filer_window);
506 else
507 filer_opendir(full_path, FALSE, BOTTOM);
508 break;
509 default:
510 report_error("open_item",
511 "I don't know how to open that");
512 break;
516 static gint pointer_in(GtkWidget *widget,
517 GdkEventCrossing *event,
518 FilerWindow *filer_window)
520 static time_t last_stat_time = 0;
521 static FilerWindow *last_stat_filer = NULL;
522 time_t now;
524 now = time(NULL);
525 if (now > last_stat_time + 1 || filer_window != last_stat_filer)
527 struct stat info;
529 last_stat_time = now;
530 last_stat_filer = filer_window;
532 if (stat(filer_window->path, &info))
533 gtk_widget_destroy(filer_window->window);
534 else if (info.st_mtime > filer_window->m_time)
535 scan_dir(filer_window);
538 return FALSE;
541 static gint focus_in(GtkWidget *widget,
542 GdkEventFocus *event,
543 FilerWindow *filer_window)
545 window_with_focus = filer_window;
547 return FALSE;
550 static gint focus_out(GtkWidget *widget,
551 GdkEventFocus *event,
552 FilerWindow *filer_window)
554 /* TODO: Shade the cursor */
556 return FALSE;
559 /* Handle keys that can't be bound with the menu */
560 static gint key_press_event(GtkWidget *widget,
561 GdkEventKey *event,
562 FilerWindow *filer_window)
564 switch (event->keyval)
567 case GDK_Left:
568 move_cursor(-1, 0);
569 break;
570 case GDK_Right:
571 move_cursor(1, 0);
572 break;
573 case GDK_Up:
574 move_cursor(0, -1);
575 break;
576 case GDK_Down:
577 move_cursor(0, 1);
578 break;
579 case GDK_Return:
581 case GDK_BackSpace:
582 filer_window->path = pathdup(make_path(
583 filer_window->path,
584 "..")->str);
585 scan_dir(filer_window);
586 return TRUE;
589 return FALSE;
593 void filer_opendir(char *path, gboolean panel, Side panel_side)
595 GtkWidget *hbox, *scrollbar, *collection;
596 FilerWindow *filer_window;
598 filer_window = g_malloc(sizeof(FilerWindow));
599 filer_window->path = pathdup(path);
600 filer_window->dir = NULL; /* Not scanning */
601 filer_window->panel = panel;
602 filer_window->panel_side = panel_side;
603 filer_window->temp_item_selected = FALSE;
605 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
607 collection = collection_new(NULL);
608 gtk_object_set_data(GTK_OBJECT(collection),
609 "filer_window", filer_window);
610 filer_window->collection = COLLECTION(collection);
611 collection_set_functions(filer_window->collection,
612 draw_item, test_point);
614 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
615 gtk_signal_connect(GTK_OBJECT(filer_window->window),
616 "enter-notify-event",
617 GTK_SIGNAL_FUNC(pointer_in), filer_window);
618 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
619 GTK_SIGNAL_FUNC(focus_in), filer_window);
620 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
621 GTK_SIGNAL_FUNC(focus_out), filer_window);
622 gtk_signal_connect(GTK_OBJECT(filer_window->window), "key_press_event",
623 GTK_SIGNAL_FUNC(key_press_event), filer_window);
624 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
625 filer_window_destroyed, filer_window);
627 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
628 open_item, filer_window);
629 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
630 show_menu, filer_window);
631 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
632 gain_selection, filer_window);
633 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
634 drag_selection, filer_window);
635 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
636 drag_data_get, filer_window);
637 drag_set_dest(collection);
639 if (panel)
641 int swidth, sheight, iwidth, iheight;
642 GtkWidget *frame, *win = filer_window->window;
644 collection_set_panel(filer_window->collection, TRUE);
646 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
647 iwidth = filer_window->collection->item_width;
648 iheight = filer_window->collection->item_height;
650 if (panel_side == TOP || panel_side == BOTTOM)
652 int height = iheight + PANEL_BORDER;
653 int y = panel_side == TOP
654 ? -PANEL_BORDER
655 : sheight - height - PANEL_BORDER;
657 gtk_widget_set_usize(collection, swidth, height);
658 gtk_widget_set_uposition(win, 0, y);
660 else
662 int width = iwidth + PANEL_BORDER;
663 int x = panel_side == LEFT
664 ? -PANEL_BORDER
665 : swidth - width - PANEL_BORDER;
667 gtk_widget_set_usize(collection, width, sheight);
668 gtk_widget_set_uposition(win, x, 0);
671 frame = gtk_frame_new(NULL);
672 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
673 gtk_container_add(GTK_CONTAINER(frame), collection);
674 gtk_container_add(GTK_CONTAINER(win), frame);
676 gtk_widget_realize(win);
677 make_panel_window(win->window);
679 else
681 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
682 400, 200);
684 hbox = gtk_hbox_new(FALSE, 0);
685 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
687 gtk_box_pack_start(GTK_BOX(hbox), collection, TRUE, TRUE, 0);
689 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
690 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
693 gtk_widget_show_all(filer_window->window);
694 number_of_windows++;
696 load_default_pixmaps(collection->window);
698 gtk_accel_group_attach(filer_keys, GTK_OBJECT(filer_window->window));
700 scan_dir(filer_window);