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>
24 #include "gui_support.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
,
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
,
61 int width
, int height
);
62 static void stop_scanning(FilerWindow
*filer_window
);
63 static gint
focus_in(GtkWidget
*widget
,
65 FilerWindow
*filer_window
);
66 static gint
focus_out(GtkWidget
*widget
,
68 FilerWindow
*filer_window
);
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
85 * TODO: Maybe we should cache icons?
87 static void free_temp_icons(FilerWindow
*filer_window
)
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
);
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)
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
)
144 FilerWindow
*filer_window
= (FilerWindow
*) data
;
148 next
= readdir(filer_window
->dir
);
151 closedir(filer_window
->dir
);
152 filer_window
->dir
= NULL
;
154 collection_qsort(filer_window
->collection
,
156 return FALSE
; /* Finished */
159 add_item(filer_window
, next
->d_name
);
160 } while (!gtk_events_pending());
165 /* Add a single object to a directory display */
166 static void add_item(FilerWindow
*filer_window
, char *leafname
)
174 /* Ignore dot files (should be an option) */
175 if (leafname
[0] == '.')
178 item
= g_malloc(sizeof(FileItem
));
179 item
->leafname
= g_strdup(leafname
);
182 path
= make_path(filer_window
->path
, leafname
);
183 if (lstat(path
->str
, &info
))
184 base_type
= TYPE_ERROR
;
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
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
;
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
;
228 base_type
= TYPE_UNKNOWN
;
231 item
->flags
|= ITEM_FLAG_SYMLINK
;
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
);
258 item
->image
= app_icon
;
259 item
->flags
|= ITEM_FLAG_TEMP_ICON
;
262 item
->image
= default_pixmap
+ TYPE_APPDIR
;
265 item
->image
= default_pixmap
+ base_type
;
267 item
->text_width
= gdk_string_width(filer_window
->window
->style
->font
,
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
,
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
)
299 if (x_off
<= (item
->text_width
>> 1) + 2 &&
300 point_y
> height
- text_height
- 2)
306 static void draw_item(GtkWidget
*widget
,
307 CollectionItem
*colitem
,
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
;
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
,
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 */
344 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
346 int type
= item
->flags
& ITEM_FLAG_MOUNTED
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 */
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
,
371 gdk_draw_text(widget
->window
,
373 colitem
->selected
? widget
->style
->white_gc
374 : widget
->style
->black_gc
,
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
)
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
);
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
),
405 if (stat(filer_window
->path
, &info
))
407 report_error("Error statting directory", g_strerror(errno
));
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
));
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
,
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
;
448 /* It is possible to highlight an item briefly on a panel by calling this
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
);
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
,
472 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
473 FileItem
*item
= (FileItem
*) item_data
;
474 GdkEventButton
*event
;
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
);
485 panel_set_timeout(filer_window
, 200);
488 switch (item
->base_type
)
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
);
499 if (filer_window
->panel
== FALSE
&&
500 (event
->type
!= GDK_2BUTTON_PRESS
||
503 filer_window
->path
= pathdup(full_path
);
504 scan_dir(filer_window
);
507 filer_opendir(full_path
, FALSE
, BOTTOM
);
510 report_error("open_item",
511 "I don't know how to open that");
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
;
525 if (now
> last_stat_time
+ 1 || filer_window
!= last_stat_filer
)
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
);
541 static gint
focus_in(GtkWidget
*widget
,
542 GdkEventFocus
*event
,
543 FilerWindow
*filer_window
)
545 window_with_focus
= filer_window
;
550 static gint
focus_out(GtkWidget
*widget
,
551 GdkEventFocus
*event
,
552 FilerWindow
*filer_window
)
554 /* TODO: Shade the cursor */
559 /* Handle keys that can't be bound with the menu */
560 static gint
key_press_event(GtkWidget
*widget
,
562 FilerWindow
*filer_window
)
564 switch (event
->keyval
)
582 filer_window
->path
= pathdup(make_path(
585 scan_dir(filer_window
);
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
);
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
655 : sheight
- height
- PANEL_BORDER
;
657 gtk_widget_set_usize(collection
, swidth
, height
);
658 gtk_widget_set_uposition(win
, 0, y
);
662 int width
= iwidth
+ PANEL_BORDER
;
663 int x
= panel_side
== LEFT
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
);
681 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
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
);
696 load_default_pixmaps(collection
->window
);
698 gtk_accel_group_attach(filer_keys
, GTK_OBJECT(filer_window
->window
));
700 scan_dir(filer_window
);