r39: Dropping on the panel now works correctly.
[rox-filer.git] / ROX-Filer / src / filer.c
blob355d090d416d8e2e686d60ca795d37f1fe9d7a6e
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 "main.h"
24 #include "support.h"
25 #include "gui_support.h"
26 #include "filer.h"
27 #include "pixmaps.h"
28 #include "menu.h"
29 #include "dnd.h"
30 #include "apps.h"
31 #include "mount.h"
32 #include "type.h"
34 #define MAX_ICON_HEIGHT 42
35 #define PANEL_BORDER 2
37 FilerWindow *window_with_focus = NULL;
39 /* When a child process that changes a directory dies we need to know
40 * which filer window to update. Use this table.
42 GHashTable *child_to_filer = NULL;
44 static FilerWindow *window_with_selection = NULL;
45 static FilerWindow *panel_with_timeout = NULL;
46 static gint panel_timeout;
48 /* Static prototypes */
49 static void filer_window_destroyed(GtkWidget *widget,
50 FilerWindow *filer_window);
51 static gboolean idle_scan_dir(gpointer data);
52 static void draw_item(GtkWidget *widget,
53 CollectionItem *item,
54 GdkRectangle *area);
55 void show_menu(Collection *collection, GdkEventButton *event,
56 int number_selected, gpointer user_data);
57 static int sort_by_name(const void *item1, const void *item2);
58 static void add_item(FilerWindow *filer_window, char *leafname);
59 static gboolean test_point(Collection *collection,
60 int point_x, int point_y,
61 CollectionItem *item,
62 int width, int height);
63 static void stop_scanning(FilerWindow *filer_window);
64 static gint focus_in(GtkWidget *widget,
65 GdkEventFocus *event,
66 FilerWindow *filer_window);
67 static gint focus_out(GtkWidget *widget,
68 GdkEventFocus *event,
69 FilerWindow *filer_window);
71 void filer_init()
73 child_to_filer = g_hash_table_new(NULL, NULL);
76 /* When a filer window is destroyed we call this for each entry in the
77 * child_to_filer hash table to remove old entries.
79 static gboolean child_eq(gpointer key, gpointer data, gpointer filer_window)
81 return data == filer_window;
84 /* Go though all the FileItems in a collection, freeing all the temp
85 * icons.
86 * TODO: Maybe we should cache icons?
88 static void free_temp_icons(FilerWindow *filer_window)
90 int i;
91 Collection *collection = filer_window->collection;
93 for (i = 0; i < collection->number_of_items; i++)
95 FileItem *item = (FileItem *) collection->items[i].data;
96 if (item->flags & ITEM_FLAG_TEMP_ICON)
98 gdk_pixmap_unref(item->image->pixmap);
99 gdk_pixmap_unref(item->image->mask);
100 g_free(item->image);
101 item->image = default_pixmap + TYPE_ERROR;
106 static void filer_window_destroyed(GtkWidget *widget,
107 FilerWindow *filer_window)
109 if (window_with_selection == filer_window)
110 window_with_selection = NULL;
111 if (window_with_focus == filer_window)
112 window_with_focus = NULL;
113 if (panel_with_timeout == filer_window)
115 /* Can this happen? */
116 panel_with_timeout = NULL;
117 gtk_timeout_remove(panel_timeout);
120 g_hash_table_foreach_remove(child_to_filer, child_eq, filer_window);
122 free_temp_icons(filer_window);
123 if (filer_window->dir)
124 stop_scanning(filer_window);
125 g_free(filer_window->path);
126 g_free(filer_window);
128 if (--number_of_windows < 1)
129 gtk_main_quit();
132 static void stop_scanning(FilerWindow *filer_window)
134 g_return_if_fail(filer_window->dir != NULL);
136 closedir(filer_window->dir);
137 gtk_idle_remove(filer_window->idle_scan_id);
138 filer_window->dir = NULL;
141 /* This is called while we are scanning the directory */
142 static gboolean idle_scan_dir(gpointer data)
144 struct dirent *next;
145 FilerWindow *filer_window = (FilerWindow *) data;
149 next = readdir(filer_window->dir);
150 if (!next)
152 closedir(filer_window->dir);
153 filer_window->dir = NULL;
155 collection_set_item_size(filer_window->collection,
156 filer_window->scan_min_width,
157 filer_window->collection->item_height);
158 collection_qsort(filer_window->collection,
159 sort_by_name);
160 return FALSE; /* Finished */
163 add_item(filer_window, next->d_name);
164 } while (!gtk_events_pending());
166 return TRUE;
169 /* Add a single object to a directory display */
170 static void add_item(FilerWindow *filer_window, char *leafname)
172 FileItem *item;
173 int item_width;
174 struct stat info;
175 int base_type;
176 GString *path;
178 /* Ignore dot files (should be an option) */
179 if (leafname[0] == '.')
180 return;
182 item = g_malloc(sizeof(FileItem));
183 item->leafname = g_strdup(leafname);
184 item->flags = 0;
186 path = make_path(filer_window->path, leafname);
187 if (lstat(path->str, &info))
188 base_type = TYPE_ERROR;
189 else
191 if (S_ISREG(info.st_mode))
192 base_type = TYPE_FILE;
193 else if (S_ISDIR(info.st_mode))
195 base_type = TYPE_DIRECTORY;
197 if (g_hash_table_lookup(mtab_mounts, path->str))
198 item->flags |= ITEM_FLAG_MOUNT_POINT
199 | ITEM_FLAG_MOUNTED;
200 else if (g_hash_table_lookup(fstab_mounts, path->str))
201 item->flags |= ITEM_FLAG_MOUNT_POINT;
203 else if (S_ISBLK(info.st_mode))
204 base_type = TYPE_BLOCK_DEVICE;
205 else if (S_ISCHR(info.st_mode))
206 base_type = TYPE_CHAR_DEVICE;
207 else if (S_ISFIFO(info.st_mode))
208 base_type = TYPE_PIPE;
209 else if (S_ISSOCK(info.st_mode))
210 base_type = TYPE_SOCKET;
211 else if (S_ISLNK(info.st_mode))
213 if (stat(path->str, &info))
215 base_type = TYPE_ERROR;
217 else
219 if (S_ISREG(info.st_mode))
220 base_type = TYPE_FILE;
221 else if (S_ISDIR(info.st_mode))
222 base_type = TYPE_DIRECTORY;
223 else if (S_ISBLK(info.st_mode))
224 base_type = TYPE_BLOCK_DEVICE;
225 else if (S_ISCHR(info.st_mode))
226 base_type = TYPE_CHAR_DEVICE;
227 else if (S_ISFIFO(info.st_mode))
228 base_type = TYPE_PIPE;
229 else if (S_ISSOCK(info.st_mode))
230 base_type = TYPE_SOCKET;
231 else
232 base_type = TYPE_UNKNOWN;
235 item->flags |= ITEM_FLAG_SYMLINK;
237 else
238 base_type = TYPE_UNKNOWN;
241 item->base_type = base_type;
243 if (base_type == TYPE_FILE)
244 item->mime_type = type_from_path(path->str);
245 else
246 item->mime_type = NULL;
248 if (base_type == TYPE_DIRECTORY)
250 /* Might be an application directory - better check... */
251 g_string_append(path, "/AppRun");
252 if (!stat(path->str, &info))
254 item->flags |= ITEM_FLAG_APPDIR;
258 if (item->flags & ITEM_FLAG_APPDIR) /* path still ends /AppRun */
260 MaskedPixmap *app_icon;
262 g_string_truncate(path, path->len - 3);
263 g_string_append(path, "Icon.xpm");
264 app_icon = load_pixmap_from(filer_window->window, path->str);
265 if (app_icon)
267 item->image = app_icon;
268 item->flags |= ITEM_FLAG_TEMP_ICON;
270 else
271 item->image = default_pixmap + TYPE_APPDIR;
273 else
275 if (base_type == TYPE_FILE &&
276 (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
278 item->image = default_pixmap + TYPE_EXEC_FILE;
279 item->flags |= ITEM_FLAG_EXEC_FILE;
281 else
283 item->image = type_to_icon(filer_window->window,
284 item->mime_type);
285 if (!item->image)
286 item->image = default_pixmap + base_type;
290 item->text_width = gdk_string_width(filer_window->window->style->font,
291 leafname);
293 /* XXX: Must be a better way... */
294 item->pix_width = ((GdkPixmapPrivate *) item->image->pixmap)->width;
295 item->pix_height = ((GdkPixmapPrivate *) item->image->pixmap)->height;
297 item_width = MAX(item->pix_width, item->text_width) + 4;
299 if (item_width > filer_window->scan_min_width)
300 filer_window->scan_min_width = item_width;
302 if (item_width > filer_window->collection->item_width)
303 collection_set_item_size(filer_window->collection,
304 item_width,
305 filer_window->collection->item_height);
307 collection_insert(filer_window->collection, item);
310 static gboolean test_point(Collection *collection,
311 int point_x, int point_y,
312 CollectionItem *colitem,
313 int width, int height)
315 FileItem *item = (FileItem *) colitem->data;
316 GdkFont *font = GTK_WIDGET(collection)->style->font;
317 int text_height = font->ascent + font->descent;
318 int x_off = ABS(point_x - (width >> 1));
319 int image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
321 if (x_off <= (item->pix_width >> 1) + 2 &&
322 point_y >= image_y && point_y <= image_y + item->pix_height)
323 return TRUE;
325 if (x_off <= (item->text_width >> 1) + 2 &&
326 point_y > height - text_height - 2)
327 return TRUE;
329 return FALSE;
332 static void draw_item(GtkWidget *widget,
333 CollectionItem *colitem,
334 GdkRectangle *area)
336 FileItem *item = (FileItem *) colitem->data;
337 GdkGC *gc = colitem->selected ? widget->style->white_gc
338 : widget->style->black_gc;
339 int image_x = area->x + ((area->width - item->pix_width) >> 1);
340 GdkFont *font = widget->style->font;
341 int text_x = area->x + ((area->width - item->text_width) >> 1);
342 int text_y = area->y + area->height - font->descent - 2;
343 int text_height = font->ascent + font->descent;
345 if (item->image)
347 int image_y;
349 gdk_gc_set_clip_mask(gc, item->image->mask);
351 image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
352 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
353 gdk_draw_pixmap(widget->window, gc,
354 item->image->pixmap,
355 0, 0, /* Source x,y */
356 image_x, area->y + image_y, /* Dest x,y */
357 -1, MIN(item->pix_height, MAX_ICON_HEIGHT));
359 if (item->flags & ITEM_FLAG_SYMLINK)
361 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
362 gdk_gc_set_clip_mask(gc,
363 default_pixmap[TYPE_SYMLINK].mask);
364 gdk_draw_pixmap(widget->window, gc,
365 default_pixmap[TYPE_SYMLINK].pixmap,
366 0, 0, /* Source x,y */
367 image_x, area->y + 8, /* Dest x,y */
368 -1, -1);
370 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
372 int type = item->flags & ITEM_FLAG_MOUNTED
373 ? TYPE_MOUNTED
374 : TYPE_UNMOUNTED;
375 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
376 gdk_gc_set_clip_mask(gc,
377 default_pixmap[type].mask);
378 gdk_draw_pixmap(widget->window, gc,
379 default_pixmap[type].pixmap,
380 0, 0, /* Source x,y */
381 image_x, area->y + 8, /* Dest x,y */
382 -1, -1);
385 gdk_gc_set_clip_mask(gc, NULL);
386 gdk_gc_set_clip_origin(gc, 0, 0);
389 if (colitem->selected)
390 gtk_paint_flat_box(widget->style, widget->window,
391 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
392 NULL, widget, "text",
393 text_x, text_y - font->ascent,
394 item->text_width,
395 text_height);
397 gdk_draw_text(widget->window,
398 widget->style->font,
399 colitem->selected ? widget->style->white_gc
400 : widget->style->black_gc,
401 text_x, text_y,
402 item->leafname, strlen(item->leafname));
405 void show_menu(Collection *collection, GdkEventButton *event,
406 int item, gpointer user_data)
408 show_filer_menu((FilerWindow *) user_data, event, item);
411 void scan_dir(FilerWindow *filer_window)
413 struct stat info;
415 if (filer_window->dir)
416 stop_scanning(filer_window);
417 if (panel_with_timeout == filer_window)
419 panel_with_timeout = NULL;
420 gtk_timeout_remove(panel_timeout);
423 mount_update();
425 free_temp_icons(filer_window);
426 collection_clear(filer_window->collection);
427 gtk_window_set_title(GTK_WINDOW(filer_window->window),
428 filer_window->path);
430 if (stat(filer_window->path, &info))
432 report_error("Error statting directory", g_strerror(errno));
433 return;
435 filer_window->m_time = info.st_mtime;
437 filer_window->dir = opendir(filer_window->path);
438 if (!filer_window->dir)
440 report_error("Error scanning directory", g_strerror(errno));
441 return;
444 filer_window->scan_min_width = 64;
446 filer_window->idle_scan_id = gtk_idle_add(idle_scan_dir, filer_window);
449 static void gain_selection(Collection *collection,
450 gint number_selected,
451 gpointer user_data)
453 FilerWindow *filer_window = (FilerWindow *) user_data;
455 if (window_with_selection && window_with_selection != filer_window)
456 collection_clear_selection(window_with_selection->collection);
458 window_with_selection = filer_window;
461 static int sort_by_name(const void *item1, const void *item2)
463 return strcmp((*((FileItem **)item1))->leafname,
464 (*((FileItem **)item2))->leafname);
467 static gint clear_panel_hilight(gpointer data)
469 collection_set_cursor_item(panel_with_timeout->collection, -1);
470 panel_with_timeout = NULL;
472 return FALSE;
475 /* It is possible to highlight an item briefly on a panel by calling this
476 * function.
478 void panel_set_timeout(FilerWindow *filer_window, gulong msec)
480 if (panel_with_timeout)
482 /* Can't have two timeouts at once */
483 gtk_timeout_remove(panel_timeout);
484 clear_panel_hilight(NULL);
487 if (filer_window)
489 panel_with_timeout = filer_window;
490 panel_timeout = gtk_timeout_add(msec,
491 clear_panel_hilight, NULL);
495 void open_item(Collection *collection,
496 gpointer item_data, int item_number,
497 gpointer user_data)
499 FilerWindow *filer_window = (FilerWindow *) user_data;
500 FileItem *item = (FileItem *) item_data;
501 GdkEventButton *event;
502 char *full_path;
504 event = (GdkEventButton *) gtk_get_current_event();
505 full_path = make_path(filer_window->path, item->leafname)->str;
507 if (filer_window->panel)
509 panel_set_timeout(NULL, 0);
510 collection_set_cursor_item(collection, item_number);
511 gdk_flush();
512 panel_set_timeout(filer_window, 200);
515 switch (item->base_type)
517 case TYPE_DIRECTORY:
518 if (item->flags & ITEM_FLAG_APPDIR &&
519 (event->type != GDK_2BUTTON_PRESS ||
520 (event->state & GDK_SHIFT_MASK) == 0))
522 run_app(make_path(filer_window->path,
523 item->leafname)->str);
524 break;
526 if (filer_window->panel == FALSE &&
527 (event->type != GDK_2BUTTON_PRESS ||
528 event->button == 1))
530 filer_window->path = pathdup(full_path);
531 scan_dir(filer_window);
533 else
534 filer_opendir(full_path, FALSE, BOTTOM);
535 break;
536 case TYPE_FILE:
537 if (item->flags & ITEM_FLAG_EXEC_FILE)
539 char *argv[] = {full_path, NULL};
541 if (!spawn(argv))
542 report_error("ROX-Filer",
543 "Failed to fork() child");
545 else
547 GString *message;
548 MIME_type *type = item->mime_type;
550 if ((!type) || !type_open(full_path, type))
552 message = g_string_new(NULL);
553 g_string_sprintf(message, "No open "
554 "action specified for files of "
555 "this type (%s)",
556 type ? type->name : "unknown");
557 report_error("ROX-Filer", message->str);
558 g_string_free(message, TRUE);
561 break;
562 default:
563 report_error("open_item",
564 "I don't know how to open that");
565 break;
569 static gint pointer_in(GtkWidget *widget,
570 GdkEventCrossing *event,
571 FilerWindow *filer_window)
573 static time_t last_stat_time = 0;
574 static FilerWindow *last_stat_filer = NULL;
575 time_t now;
577 now = time(NULL);
578 if (now > last_stat_time + 1 || filer_window != last_stat_filer)
580 struct stat info;
582 last_stat_time = now;
583 last_stat_filer = filer_window;
585 if (stat(filer_window->path, &info))
587 delayed_error("ROX-Filer", "Directory deleted");
588 gtk_widget_destroy(filer_window->window);
590 else if (info.st_mtime > filer_window->m_time)
591 scan_dir(filer_window);
594 return FALSE;
597 static gint focus_in(GtkWidget *widget,
598 GdkEventFocus *event,
599 FilerWindow *filer_window)
601 window_with_focus = filer_window;
603 return FALSE;
606 static gint focus_out(GtkWidget *widget,
607 GdkEventFocus *event,
608 FilerWindow *filer_window)
610 /* TODO: Shade the cursor */
612 return FALSE;
615 /* Handle keys that can't be bound with the menu */
616 static gint key_press_event(GtkWidget *widget,
617 GdkEventKey *event,
618 FilerWindow *filer_window)
620 switch (event->keyval)
623 case GDK_Left:
624 move_cursor(-1, 0);
625 break;
626 case GDK_Right:
627 move_cursor(1, 0);
628 break;
629 case GDK_Up:
630 move_cursor(0, -1);
631 break;
632 case GDK_Down:
633 move_cursor(0, 1);
634 break;
635 case GDK_Return:
637 case GDK_BackSpace:
638 filer_window->path = pathdup(make_path(
639 filer_window->path,
640 "..")->str);
641 scan_dir(filer_window);
642 return TRUE;
645 return FALSE;
648 FileItem *selected_item(Collection *collection)
650 int i;
652 g_return_val_if_fail(collection != NULL, NULL);
653 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
654 g_return_val_if_fail(collection->number_selected == 1, NULL);
656 for (i = 0; i < collection->number_of_items; i++)
657 if (collection->items[i].selected)
658 return (FileItem *) collection->items[i].data;
660 g_warning("selected_item: number_selected is wrong\n");
662 return NULL;
665 void filer_opendir(char *path, gboolean panel, Side panel_side)
667 GtkWidget *hbox, *scrollbar, *collection;
668 FilerWindow *filer_window;
670 filer_window = g_malloc(sizeof(FilerWindow));
671 filer_window->path = pathdup(path);
672 filer_window->dir = NULL; /* Not scanning */
673 filer_window->panel = panel;
674 filer_window->panel_side = panel_side;
675 filer_window->temp_item_selected = FALSE;
677 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
679 collection = collection_new(NULL);
680 gtk_object_set_data(GTK_OBJECT(collection),
681 "filer_window", filer_window);
682 filer_window->collection = COLLECTION(collection);
683 collection_set_item_size(filer_window->collection, 64, 64);
684 collection_set_functions(filer_window->collection,
685 draw_item, test_point);
687 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
688 gtk_signal_connect(GTK_OBJECT(filer_window->window),
689 "enter-notify-event",
690 GTK_SIGNAL_FUNC(pointer_in), filer_window);
691 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
692 GTK_SIGNAL_FUNC(focus_in), filer_window);
693 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
694 GTK_SIGNAL_FUNC(focus_out), filer_window);
695 gtk_signal_connect(GTK_OBJECT(filer_window->window), "key_press_event",
696 GTK_SIGNAL_FUNC(key_press_event), filer_window);
697 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
698 filer_window_destroyed, filer_window);
700 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
701 open_item, filer_window);
702 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
703 show_menu, filer_window);
704 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
705 gain_selection, filer_window);
706 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
707 drag_selection, filer_window);
708 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
709 drag_data_get, filer_window);
710 drag_set_dest(collection);
712 if (panel)
714 int swidth, sheight, iwidth, iheight;
715 GtkWidget *frame, *win = filer_window->window;
717 collection_set_panel(filer_window->collection, TRUE);
719 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
720 iwidth = filer_window->collection->item_width;
721 iheight = filer_window->collection->item_height;
723 if (panel_side == TOP || panel_side == BOTTOM)
725 int height = iheight + PANEL_BORDER;
726 int y = panel_side == TOP
727 ? -PANEL_BORDER
728 : sheight - height - PANEL_BORDER;
730 gtk_widget_set_usize(collection, swidth, height);
731 gtk_widget_set_uposition(win, 0, y);
733 else
735 int width = iwidth + PANEL_BORDER;
736 int x = panel_side == LEFT
737 ? -PANEL_BORDER
738 : swidth - width - PANEL_BORDER;
740 gtk_widget_set_usize(collection, width, sheight);
741 gtk_widget_set_uposition(win, x, 0);
744 frame = gtk_frame_new(NULL);
745 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
746 gtk_container_add(GTK_CONTAINER(frame), collection);
747 gtk_container_add(GTK_CONTAINER(win), frame);
749 gtk_widget_realize(win);
750 make_panel_window(win->window);
752 else
754 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
755 400, 200);
757 hbox = gtk_hbox_new(FALSE, 0);
758 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
760 gtk_box_pack_start(GTK_BOX(hbox), collection, TRUE, TRUE, 0);
762 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
763 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
766 gtk_widget_show_all(filer_window->window);
767 number_of_windows++;
769 load_default_pixmaps(collection->window);
771 gtk_accel_group_attach(filer_keys, GTK_OBJECT(filer_window->window));
773 scan_dir(filer_window);