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_FILE
)
244 item
->mime_type
= type_from_path(path
->str
);
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
);
267 item
->image
= app_icon
;
268 item
->flags
|= ITEM_FLAG_TEMP_ICON
;
271 item
->image
= default_pixmap
+ TYPE_APPDIR
;
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
;
283 item
->image
= type_to_icon(filer_window
->window
,
286 item
->image
= default_pixmap
+ base_type
;
290 item
->text_width
= gdk_string_width(filer_window
->window
->style
->font
,
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
,
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
)
325 if (x_off
<= (item
->text_width
>> 1) + 2 &&
326 point_y
> height
- text_height
- 2)
332 static void draw_item(GtkWidget
*widget
,
333 CollectionItem
*colitem
,
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
;
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
,
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 */
370 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
372 int type
= item
->flags
& ITEM_FLAG_MOUNTED
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 */
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
,
397 gdk_draw_text(widget
->window
,
399 colitem
->selected
? widget
->style
->white_gc
400 : widget
->style
->black_gc
,
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
)
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
);
425 free_temp_icons(filer_window
);
426 collection_clear(filer_window
->collection
);
427 gtk_window_set_title(GTK_WINDOW(filer_window
->window
),
430 if (stat(filer_window
->path
, &info
))
432 report_error("Error statting directory", g_strerror(errno
));
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
));
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
,
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
;
475 /* It is possible to highlight an item briefly on a panel by calling this
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
);
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
,
499 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
500 FileItem
*item
= (FileItem
*) item_data
;
501 GdkEventButton
*event
;
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
);
512 panel_set_timeout(filer_window
, 200);
515 switch (item
->base_type
)
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
);
526 if (filer_window
->panel
== FALSE
&&
527 (event
->type
!= GDK_2BUTTON_PRESS
||
530 filer_window
->path
= pathdup(full_path
);
531 scan_dir(filer_window
);
534 filer_opendir(full_path
, FALSE
, BOTTOM
);
537 if (item
->flags
& ITEM_FLAG_EXEC_FILE
)
539 char *argv
[] = {full_path
, NULL
};
542 report_error("ROX-Filer",
543 "Failed to fork() child");
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 "
556 type
? type
->name
: "unknown");
557 report_error("ROX-Filer", message
->str
);
558 g_string_free(message
, TRUE
);
563 report_error("open_item",
564 "I don't know how to open that");
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
;
578 if (now
> last_stat_time
+ 1 || filer_window
!= last_stat_filer
)
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
);
597 static gint
focus_in(GtkWidget
*widget
,
598 GdkEventFocus
*event
,
599 FilerWindow
*filer_window
)
601 window_with_focus
= filer_window
;
606 static gint
focus_out(GtkWidget
*widget
,
607 GdkEventFocus
*event
,
608 FilerWindow
*filer_window
)
610 /* TODO: Shade the cursor */
615 /* Handle keys that can't be bound with the menu */
616 static gint
key_press_event(GtkWidget
*widget
,
618 FilerWindow
*filer_window
)
620 switch (event
->keyval
)
638 filer_window
->path
= pathdup(make_path(
641 scan_dir(filer_window
);
648 FileItem
*selected_item(Collection
*collection
)
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");
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
);
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
728 : sheight
- height
- PANEL_BORDER
;
730 gtk_widget_set_usize(collection
, swidth
, height
);
731 gtk_widget_set_uposition(win
, 0, y
);
735 int width
= iwidth
+ PANEL_BORDER
;
736 int x
= panel_side
== LEFT
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
);
754 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
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
);
769 load_default_pixmaps(collection
->window
);
771 gtk_accel_group_attach(filer_keys
, GTK_OBJECT(filer_window
->window
));
773 scan_dir(filer_window
);