4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
8 /* filer.c - code for handling filer windows */
19 #include <gdk/gdkprivate.h> /* XXX - find another way to do this */
20 #include <gdk/gdkkeysyms.h>
21 #include <collection.h>
25 #include "gui_support.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
,
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
,
62 int width
, int height
);
63 static void stop_scanning(FilerWindow
*filer_window
);
64 static gint
focus_in(GtkWidget
*widget
,
66 FilerWindow
*filer_window
);
67 static gint
focus_out(GtkWidget
*widget
,
69 FilerWindow
*filer_window
);
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
86 * TODO: Maybe we should cache icons?
88 static void free_temp_icons(FilerWindow
*filer_window
)
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
);
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)
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
)
145 FilerWindow
*filer_window
= (FilerWindow
*) data
;
149 next
= readdir(filer_window
->dir
);
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
,
160 return FALSE
; /* Finished */
163 add_item(filer_window
, next
->d_name
);
164 } while (!gtk_events_pending());
169 /* Add a single object to a directory display */
170 static void add_item(FilerWindow
*filer_window
, char *leafname
)
178 /* Ignore dot files (should be an option) */
179 if (leafname
[0] == '.')
182 item
= g_malloc(sizeof(FileItem
));
183 item
->leafname
= g_strdup(leafname
);
186 path
= make_path(filer_window
->path
, leafname
);
187 if (lstat(path
->str
, &info
))
188 base_type
= TYPE_ERROR
;
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
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
;
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
;
232 base_type
= TYPE_UNKNOWN
;
235 item
->flags
|= ITEM_FLAG_SYMLINK
;
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
);
262 item
->image
= app_icon
;
263 item
->flags
|= ITEM_FLAG_TEMP_ICON
;
266 item
->image
= default_pixmap
+ TYPE_APPDIR
;
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
;
277 item
->image
= default_pixmap
+ base_type
;
280 item
->text_width
= gdk_string_width(filer_window
->window
->style
->font
,
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
,
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
)
315 if (x_off
<= (item
->text_width
>> 1) + 2 &&
316 point_y
> height
- text_height
- 2)
322 static void draw_item(GtkWidget
*widget
,
323 CollectionItem
*colitem
,
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
;
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
,
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 */
360 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
362 int type
= item
->flags
& ITEM_FLAG_MOUNTED
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 */
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
,
387 gdk_draw_text(widget
->window
,
389 colitem
->selected
? widget
->style
->white_gc
390 : widget
->style
->black_gc
,
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
)
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
);
415 free_temp_icons(filer_window
);
416 collection_clear(filer_window
->collection
);
417 gtk_window_set_title(GTK_WINDOW(filer_window
->window
),
420 if (stat(filer_window
->path
, &info
))
422 report_error("Error statting directory", g_strerror(errno
));
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
));
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
,
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
;
465 /* It is possible to highlight an item briefly on a panel by calling this
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
);
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
,
489 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
490 FileItem
*item
= (FileItem
*) item_data
;
491 GdkEventButton
*event
;
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
);
502 panel_set_timeout(filer_window
, 200);
505 switch (item
->base_type
)
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
);
516 if (filer_window
->panel
== FALSE
&&
517 (event
->type
!= GDK_2BUTTON_PRESS
||
520 filer_window
->path
= pathdup(full_path
);
521 scan_dir(filer_window
);
524 filer_opendir(full_path
, FALSE
, BOTTOM
);
527 if (item
->flags
& ITEM_FLAG_EXEC_FILE
)
529 char *argv
[] = {full_path
, NULL
};
532 report_error("ROX-Filer",
533 "Failed to fork() child");
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 "
548 type
? type
->name
: "unknown");
549 report_error("ROX-Filer", message
->str
);
550 g_string_free(message
, TRUE
);
555 report_error("open_item",
556 "I don't know how to open that");
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
;
570 if (now
> last_stat_time
+ 1 || filer_window
!= last_stat_filer
)
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
);
589 static gint
focus_in(GtkWidget
*widget
,
590 GdkEventFocus
*event
,
591 FilerWindow
*filer_window
)
593 window_with_focus
= filer_window
;
598 static gint
focus_out(GtkWidget
*widget
,
599 GdkEventFocus
*event
,
600 FilerWindow
*filer_window
)
602 /* TODO: Shade the cursor */
607 /* Handle keys that can't be bound with the menu */
608 static gint
key_press_event(GtkWidget
*widget
,
610 FilerWindow
*filer_window
)
612 switch (event
->keyval
)
630 filer_window
->path
= pathdup(make_path(
633 scan_dir(filer_window
);
640 FileItem
*selected_item(Collection
*collection
)
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");
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
);
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
720 : sheight
- height
- PANEL_BORDER
;
722 gtk_widget_set_usize(collection
, swidth
, height
);
723 gtk_widget_set_uposition(win
, 0, y
);
727 int width
= iwidth
+ PANEL_BORDER
;
728 int x
= panel_side
== LEFT
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
);
746 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
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
);
761 load_default_pixmaps(collection
->window
);
763 gtk_accel_group_attach(filer_keys
, GTK_OBJECT(filer_window
->window
));
765 scan_dir(filer_window
);