r38: Rescanning a directory only shrinks the item width at the end rather than the
[rox-filer.git] / ROX-Filer / src / filer.c
blob6fed581e2230ab7b67cfb75bc46aacb267de05e1
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_DIRECTORY)
245 /* Might be an application directory - better check... */
246 g_string_append(path, "/AppRun");
247 if (!stat(path->str, &info))
249 item->flags |= ITEM_FLAG_APPDIR;
253 if (item->flags & ITEM_FLAG_APPDIR) /* path still ends /AppRun */
255 MaskedPixmap *app_icon;
257 g_string_truncate(path, path->len - 3);
258 g_string_append(path, "Icon.xpm");
259 app_icon = load_pixmap_from(filer_window->window, path->str);
260 if (app_icon)
262 item->image = app_icon;
263 item->flags |= ITEM_FLAG_TEMP_ICON;
265 else
266 item->image = default_pixmap + TYPE_APPDIR;
268 else
270 if (base_type == TYPE_FILE &&
271 (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
273 item->image = default_pixmap + TYPE_EXEC_FILE;
274 item->flags |= ITEM_FLAG_EXEC_FILE;
276 else
277 item->image = default_pixmap + base_type;
280 item->text_width = gdk_string_width(filer_window->window->style->font,
281 leafname);
283 /* XXX: Must be a better way... */
284 item->pix_width = ((GdkPixmapPrivate *) item->image->pixmap)->width;
285 item->pix_height = ((GdkPixmapPrivate *) item->image->pixmap)->height;
287 item_width = MAX(item->pix_width, item->text_width) + 4;
289 if (item_width > filer_window->scan_min_width)
290 filer_window->scan_min_width = item_width;
292 if (item_width > filer_window->collection->item_width)
293 collection_set_item_size(filer_window->collection,
294 item_width,
295 filer_window->collection->item_height);
297 collection_insert(filer_window->collection, item);
300 static gboolean test_point(Collection *collection,
301 int point_x, int point_y,
302 CollectionItem *colitem,
303 int width, int height)
305 FileItem *item = (FileItem *) colitem->data;
306 GdkFont *font = GTK_WIDGET(collection)->style->font;
307 int text_height = font->ascent + font->descent;
308 int x_off = ABS(point_x - (width >> 1));
309 int image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
311 if (x_off <= (item->pix_width >> 1) + 2 &&
312 point_y >= image_y && point_y <= image_y + item->pix_height)
313 return TRUE;
315 if (x_off <= (item->text_width >> 1) + 2 &&
316 point_y > height - text_height - 2)
317 return TRUE;
319 return FALSE;
322 static void draw_item(GtkWidget *widget,
323 CollectionItem *colitem,
324 GdkRectangle *area)
326 FileItem *item = (FileItem *) colitem->data;
327 GdkGC *gc = colitem->selected ? widget->style->white_gc
328 : widget->style->black_gc;
329 int image_x = area->x + ((area->width - item->pix_width) >> 1);
330 GdkFont *font = widget->style->font;
331 int text_x = area->x + ((area->width - item->text_width) >> 1);
332 int text_y = area->y + area->height - font->descent - 2;
333 int text_height = font->ascent + font->descent;
335 if (item->image)
337 int image_y;
339 gdk_gc_set_clip_mask(gc, item->image->mask);
341 image_y = MAX(0, MAX_ICON_HEIGHT - item->pix_height);
342 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
343 gdk_draw_pixmap(widget->window, gc,
344 item->image->pixmap,
345 0, 0, /* Source x,y */
346 image_x, area->y + image_y, /* Dest x,y */
347 -1, MIN(item->pix_height, MAX_ICON_HEIGHT));
349 if (item->flags & ITEM_FLAG_SYMLINK)
351 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
352 gdk_gc_set_clip_mask(gc,
353 default_pixmap[TYPE_SYMLINK].mask);
354 gdk_draw_pixmap(widget->window, gc,
355 default_pixmap[TYPE_SYMLINK].pixmap,
356 0, 0, /* Source x,y */
357 image_x, area->y + 8, /* Dest x,y */
358 -1, -1);
360 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
362 int type = item->flags & ITEM_FLAG_MOUNTED
363 ? TYPE_MOUNTED
364 : TYPE_UNMOUNTED;
365 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
366 gdk_gc_set_clip_mask(gc,
367 default_pixmap[type].mask);
368 gdk_draw_pixmap(widget->window, gc,
369 default_pixmap[type].pixmap,
370 0, 0, /* Source x,y */
371 image_x, area->y + 8, /* Dest x,y */
372 -1, -1);
375 gdk_gc_set_clip_mask(gc, NULL);
376 gdk_gc_set_clip_origin(gc, 0, 0);
379 if (colitem->selected)
380 gtk_paint_flat_box(widget->style, widget->window,
381 GTK_STATE_SELECTED, GTK_SHADOW_NONE,
382 NULL, widget, "text",
383 text_x, text_y - font->ascent,
384 item->text_width,
385 text_height);
387 gdk_draw_text(widget->window,
388 widget->style->font,
389 colitem->selected ? widget->style->white_gc
390 : widget->style->black_gc,
391 text_x, text_y,
392 item->leafname, strlen(item->leafname));
395 void show_menu(Collection *collection, GdkEventButton *event,
396 int item, gpointer user_data)
398 show_filer_menu((FilerWindow *) user_data, event, item);
401 void scan_dir(FilerWindow *filer_window)
403 struct stat info;
405 if (filer_window->dir)
406 stop_scanning(filer_window);
407 if (panel_with_timeout == filer_window)
409 panel_with_timeout = NULL;
410 gtk_timeout_remove(panel_timeout);
413 mount_update();
415 free_temp_icons(filer_window);
416 collection_clear(filer_window->collection);
417 gtk_window_set_title(GTK_WINDOW(filer_window->window),
418 filer_window->path);
420 if (stat(filer_window->path, &info))
422 report_error("Error statting directory", g_strerror(errno));
423 return;
425 filer_window->m_time = info.st_mtime;
427 filer_window->dir = opendir(filer_window->path);
428 if (!filer_window->dir)
430 report_error("Error scanning directory", g_strerror(errno));
431 return;
434 filer_window->scan_min_width = 64;
436 filer_window->idle_scan_id = gtk_idle_add(idle_scan_dir, filer_window);
439 static void gain_selection(Collection *collection,
440 gint number_selected,
441 gpointer user_data)
443 FilerWindow *filer_window = (FilerWindow *) user_data;
445 if (window_with_selection && window_with_selection != filer_window)
446 collection_clear_selection(window_with_selection->collection);
448 window_with_selection = filer_window;
451 static int sort_by_name(const void *item1, const void *item2)
453 return strcmp((*((FileItem **)item1))->leafname,
454 (*((FileItem **)item2))->leafname);
457 static gint clear_panel_hilight(gpointer data)
459 collection_set_cursor_item(panel_with_timeout->collection, -1);
460 panel_with_timeout = NULL;
462 return FALSE;
465 /* It is possible to highlight an item briefly on a panel by calling this
466 * function.
468 void panel_set_timeout(FilerWindow *filer_window, gulong msec)
470 if (panel_with_timeout)
472 /* Can't have two timeouts at once */
473 gtk_timeout_remove(panel_timeout);
474 clear_panel_hilight(NULL);
477 if (filer_window)
479 panel_with_timeout = filer_window;
480 panel_timeout = gtk_timeout_add(msec,
481 clear_panel_hilight, NULL);
485 void open_item(Collection *collection,
486 gpointer item_data, int item_number,
487 gpointer user_data)
489 FilerWindow *filer_window = (FilerWindow *) user_data;
490 FileItem *item = (FileItem *) item_data;
491 GdkEventButton *event;
492 char *full_path;
494 event = (GdkEventButton *) gtk_get_current_event();
495 full_path = make_path(filer_window->path, item->leafname)->str;
497 if (filer_window->panel)
499 panel_set_timeout(NULL, 0);
500 collection_set_cursor_item(collection, item_number);
501 gdk_flush();
502 panel_set_timeout(filer_window, 200);
505 switch (item->base_type)
507 case TYPE_DIRECTORY:
508 if (item->flags & ITEM_FLAG_APPDIR &&
509 (event->type != GDK_2BUTTON_PRESS ||
510 (event->state & GDK_SHIFT_MASK) == 0))
512 run_app(make_path(filer_window->path,
513 item->leafname)->str);
514 break;
516 if (filer_window->panel == FALSE &&
517 (event->type != GDK_2BUTTON_PRESS ||
518 event->button == 1))
520 filer_window->path = pathdup(full_path);
521 scan_dir(filer_window);
523 else
524 filer_opendir(full_path, FALSE, BOTTOM);
525 break;
526 case TYPE_FILE:
527 if (item->flags & ITEM_FLAG_EXEC_FILE)
529 char *argv[] = {full_path, NULL};
531 if (!spawn(argv))
532 report_error("ROX-Filer",
533 "Failed to fork() child");
535 else
537 GString *message;
538 MIME_type *type;
540 type = type_from_path(full_path);
542 if ((!type) || !type_open(full_path, type))
544 message = g_string_new(NULL);
545 g_string_sprintf(message, "No open "
546 "action specified for files of "
547 "this type (%s)",
548 type ? type->name : "unknown");
549 report_error("ROX-Filer", message->str);
550 g_string_free(message, TRUE);
553 break;
554 default:
555 report_error("open_item",
556 "I don't know how to open that");
557 break;
561 static gint pointer_in(GtkWidget *widget,
562 GdkEventCrossing *event,
563 FilerWindow *filer_window)
565 static time_t last_stat_time = 0;
566 static FilerWindow *last_stat_filer = NULL;
567 time_t now;
569 now = time(NULL);
570 if (now > last_stat_time + 1 || filer_window != last_stat_filer)
572 struct stat info;
574 last_stat_time = now;
575 last_stat_filer = filer_window;
577 if (stat(filer_window->path, &info))
579 delayed_error("ROX-Filer", "Directory deleted");
580 gtk_widget_destroy(filer_window->window);
582 else if (info.st_mtime > filer_window->m_time)
583 scan_dir(filer_window);
586 return FALSE;
589 static gint focus_in(GtkWidget *widget,
590 GdkEventFocus *event,
591 FilerWindow *filer_window)
593 window_with_focus = filer_window;
595 return FALSE;
598 static gint focus_out(GtkWidget *widget,
599 GdkEventFocus *event,
600 FilerWindow *filer_window)
602 /* TODO: Shade the cursor */
604 return FALSE;
607 /* Handle keys that can't be bound with the menu */
608 static gint key_press_event(GtkWidget *widget,
609 GdkEventKey *event,
610 FilerWindow *filer_window)
612 switch (event->keyval)
615 case GDK_Left:
616 move_cursor(-1, 0);
617 break;
618 case GDK_Right:
619 move_cursor(1, 0);
620 break;
621 case GDK_Up:
622 move_cursor(0, -1);
623 break;
624 case GDK_Down:
625 move_cursor(0, 1);
626 break;
627 case GDK_Return:
629 case GDK_BackSpace:
630 filer_window->path = pathdup(make_path(
631 filer_window->path,
632 "..")->str);
633 scan_dir(filer_window);
634 return TRUE;
637 return FALSE;
640 FileItem *selected_item(Collection *collection)
642 int i;
644 g_return_val_if_fail(collection != NULL, NULL);
645 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
646 g_return_val_if_fail(collection->number_selected == 1, NULL);
648 for (i = 0; i < collection->number_of_items; i++)
649 if (collection->items[i].selected)
650 return (FileItem *) collection->items[i].data;
652 g_warning("selected_item: number_selected is wrong\n");
654 return NULL;
657 void filer_opendir(char *path, gboolean panel, Side panel_side)
659 GtkWidget *hbox, *scrollbar, *collection;
660 FilerWindow *filer_window;
662 filer_window = g_malloc(sizeof(FilerWindow));
663 filer_window->path = pathdup(path);
664 filer_window->dir = NULL; /* Not scanning */
665 filer_window->panel = panel;
666 filer_window->panel_side = panel_side;
667 filer_window->temp_item_selected = FALSE;
669 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
671 collection = collection_new(NULL);
672 gtk_object_set_data(GTK_OBJECT(collection),
673 "filer_window", filer_window);
674 filer_window->collection = COLLECTION(collection);
675 collection_set_item_size(filer_window->collection, 64, 64);
676 collection_set_functions(filer_window->collection,
677 draw_item, test_point);
679 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
680 gtk_signal_connect(GTK_OBJECT(filer_window->window),
681 "enter-notify-event",
682 GTK_SIGNAL_FUNC(pointer_in), filer_window);
683 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
684 GTK_SIGNAL_FUNC(focus_in), filer_window);
685 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_out_event",
686 GTK_SIGNAL_FUNC(focus_out), filer_window);
687 gtk_signal_connect(GTK_OBJECT(filer_window->window), "key_press_event",
688 GTK_SIGNAL_FUNC(key_press_event), filer_window);
689 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
690 filer_window_destroyed, filer_window);
692 gtk_signal_connect(GTK_OBJECT(filer_window->collection), "open_item",
693 open_item, filer_window);
694 gtk_signal_connect(GTK_OBJECT(collection), "show_menu",
695 show_menu, filer_window);
696 gtk_signal_connect(GTK_OBJECT(collection), "gain_selection",
697 gain_selection, filer_window);
698 gtk_signal_connect(GTK_OBJECT(collection), "drag_selection",
699 drag_selection, filer_window);
700 gtk_signal_connect(GTK_OBJECT(collection), "drag_data_get",
701 drag_data_get, filer_window);
702 drag_set_dest(collection);
704 if (panel)
706 int swidth, sheight, iwidth, iheight;
707 GtkWidget *frame, *win = filer_window->window;
709 collection_set_panel(filer_window->collection, TRUE);
711 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
712 iwidth = filer_window->collection->item_width;
713 iheight = filer_window->collection->item_height;
715 if (panel_side == TOP || panel_side == BOTTOM)
717 int height = iheight + PANEL_BORDER;
718 int y = panel_side == TOP
719 ? -PANEL_BORDER
720 : sheight - height - PANEL_BORDER;
722 gtk_widget_set_usize(collection, swidth, height);
723 gtk_widget_set_uposition(win, 0, y);
725 else
727 int width = iwidth + PANEL_BORDER;
728 int x = panel_side == LEFT
729 ? -PANEL_BORDER
730 : swidth - width - PANEL_BORDER;
732 gtk_widget_set_usize(collection, width, sheight);
733 gtk_widget_set_uposition(win, x, 0);
736 frame = gtk_frame_new(NULL);
737 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
738 gtk_container_add(GTK_CONTAINER(frame), collection);
739 gtk_container_add(GTK_CONTAINER(win), frame);
741 gtk_widget_realize(win);
742 make_panel_window(win->window);
744 else
746 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
747 400, 200);
749 hbox = gtk_hbox_new(FALSE, 0);
750 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
752 gtk_box_pack_start(GTK_BOX(hbox), collection, TRUE, TRUE, 0);
754 scrollbar = gtk_vscrollbar_new(COLLECTION(collection)->vadj);
755 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
758 gtk_widget_show_all(filer_window->window);
759 number_of_windows++;
761 load_default_pixmaps(collection->window);
763 gtk_accel_group_attach(filer_keys, GTK_OBJECT(filer_window->window));
765 scan_dir(filer_window);