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 */
17 #include <gdk/gdkprivate.h> /* XXX - find another way to do this */
18 #include <gdk/gdkkeysyms.h>
19 #include <collection.h>
22 #include "gui_support.h"
29 FilerWindow
*window_with_focus
= NULL
;
31 /* When a child process that changes a directory dies we need to know
32 * which filer window to update. Use this table.
34 GHashTable
*child_to_filer
= NULL
;
36 static int number_of_windows
= 0;
37 static FilerWindow
*window_with_selection
= NULL
;
39 /* Static prototypes */
40 static void filer_window_destroyed(GtkWidget
*widget
,
41 FilerWindow
*filer_window
);
42 static gboolean
idle_scan_dir(gpointer data
);
43 static void draw_item(GtkWidget
*widget
,
46 void show_menu(Collection
*collection
, GdkEventButton
*event
,
47 int number_selected
, gpointer user_data
);
48 static int sort_by_name(const void *item1
, const void *item2
);
49 static void add_item(FilerWindow
*filer_window
, char *leafname
);
50 static gboolean
test_point(Collection
*collection
,
51 int point_x
, int point_y
,
53 int width
, int height
);
54 static void stop_scanning(FilerWindow
*filer_window
);
55 static gint
focus_in(GtkWidget
*widget
,
57 FilerWindow
*filer_window
);
58 static gint
focus_out(GtkWidget
*widget
,
60 FilerWindow
*filer_window
);
65 child_to_filer
= g_hash_table_new(NULL
, NULL
);
68 /* When a filer window is destroyed we call this for each entry in the
69 * child_to_filer hash table to remove old entries.
71 static gboolean
child_eq(gpointer key
, gpointer data
, gpointer filer_window
)
73 return data
== filer_window
;
76 static void filer_window_destroyed(GtkWidget
*widget
,
77 FilerWindow
*filer_window
)
79 if (window_with_selection
== filer_window
)
80 window_with_selection
= NULL
;
81 if (window_with_focus
== filer_window
)
82 window_with_focus
= NULL
;
84 g_hash_table_foreach_remove(child_to_filer
, child_eq
, filer_window
);
86 if (filer_window
->dir
)
87 stop_scanning(filer_window
);
88 g_free(filer_window
->path
);
91 if (--number_of_windows
< 1)
95 static void stop_scanning(FilerWindow
*filer_window
)
97 g_return_if_fail(filer_window
->dir
!= NULL
);
99 closedir(filer_window
->dir
);
100 gtk_idle_remove(filer_window
->idle_scan_id
);
101 filer_window
->dir
= NULL
;
104 /* This is called while we are scanning the directory */
105 static gboolean
idle_scan_dir(gpointer data
)
108 FilerWindow
*filer_window
= (FilerWindow
*) data
;
112 next
= readdir(filer_window
->dir
);
115 closedir(filer_window
->dir
);
116 filer_window
->dir
= NULL
;
118 collection_qsort(filer_window
->collection
,
120 return FALSE
; /* Finished */
123 add_item(filer_window
, next
->d_name
);
124 } while (!gtk_events_pending());
129 /* Add a single object to a directory display */
130 static void add_item(FilerWindow
*filer_window
, char *leafname
)
138 /* Ignore dot files (should be an option) */
139 if (leafname
[0] == '.')
142 item
= g_malloc(sizeof(FileItem
));
143 item
->leafname
= g_strdup(leafname
);
146 path
= make_path(filer_window
->path
, leafname
);
147 if (lstat(path
->str
, &info
))
148 base_type
= TYPE_ERROR
;
151 if (S_ISREG(info
.st_mode
))
152 base_type
= TYPE_FILE
;
153 else if (S_ISDIR(info
.st_mode
))
154 base_type
= TYPE_DIRECTORY
;
155 else if (S_ISBLK(info
.st_mode
))
156 base_type
= TYPE_BLOCK_DEVICE
;
157 else if (S_ISCHR(info
.st_mode
))
158 base_type
= TYPE_CHAR_DEVICE
;
159 else if (S_ISFIFO(info
.st_mode
))
160 base_type
= TYPE_PIPE
;
161 else if (S_ISSOCK(info
.st_mode
))
162 base_type
= TYPE_SOCKET
;
163 else if (S_ISLNK(info
.st_mode
))
165 if (stat(path
->str
, &info
))
167 base_type
= TYPE_ERROR
;
171 if (S_ISREG(info
.st_mode
))
172 base_type
= TYPE_FILE
;
173 else if (S_ISDIR(info
.st_mode
))
174 base_type
= TYPE_DIRECTORY
;
175 else if (S_ISBLK(info
.st_mode
))
176 base_type
= TYPE_BLOCK_DEVICE
;
177 else if (S_ISCHR(info
.st_mode
))
178 base_type
= TYPE_CHAR_DEVICE
;
179 else if (S_ISFIFO(info
.st_mode
))
180 base_type
= TYPE_PIPE
;
181 else if (S_ISSOCK(info
.st_mode
))
182 base_type
= TYPE_SOCKET
;
184 base_type
= TYPE_UNKNOWN
;
187 item
->flags
|= ITEM_FLAG_SYMLINK
;
190 base_type
= TYPE_UNKNOWN
;
193 item
->base_type
= base_type
;
195 if (base_type
== TYPE_DIRECTORY
)
197 /* Might be an application directory - better check... */
198 path
= g_string_append(path
, "/AppInfo");
199 if (!stat(path
->str
, &info
))
200 item
->flags
|= ITEM_FLAG_APPDIR
;
203 if (item
->flags
& ITEM_FLAG_APPDIR
)
204 item
->image
= default_pixmap
+ TYPE_APPDIR
;
206 item
->image
= default_pixmap
+ base_type
;
208 item
->text_width
= gdk_string_width(filer_window
->window
->style
->font
,
211 /* XXX: Must be a better way... */
212 item
->pix_width
= ((GdkPixmapPrivate
*) item
->image
->pixmap
)->width
;
214 item_width
= MAX(item
->pix_width
, item
->text_width
) + 4;
216 if (item_width
> filer_window
->collection
->item_width
)
217 collection_set_item_size(filer_window
->collection
,
219 filer_window
->collection
->item_height
);
221 collection_insert(filer_window
->collection
, item
);
224 static gboolean
test_point(Collection
*collection
,
225 int point_x
, int point_y
,
226 CollectionItem
*item
,
227 int width
, int height
)
229 FileItem
*fileitem
= (FileItem
*) item
->data
;
230 GdkFont
*font
= GTK_WIDGET(collection
)->style
->font
;
231 int text_height
= font
->ascent
+ font
->descent
;
232 int x_off
= ABS(point_x
- (width
>> 1));
234 if (x_off
<= (fileitem
->pix_width
>> 1) + 2 &&
235 point_y
< height
- text_height
- 2 &&
239 if (x_off
<= (fileitem
->text_width
>> 1) + 2 &&
240 point_y
> height
- text_height
- 2)
246 static void draw_item(GtkWidget
*widget
,
247 CollectionItem
*colitem
,
250 FileItem
*item
= (FileItem
*) colitem
->data
;
251 GdkGC
*gc
= colitem
->selected
? widget
->style
->white_gc
252 : widget
->style
->black_gc
;
253 int image_x
= area
->x
+ ((area
->width
- item
->pix_width
) >> 1);
254 GdkFont
*font
= widget
->style
->font
;
255 int text_x
= area
->x
+ ((area
->width
- item
->text_width
) >> 1);
256 int text_y
= area
->y
+ area
->height
- font
->descent
- 2;
257 int text_height
= font
->ascent
+ font
->descent
;
261 gdk_gc_set_clip_mask(gc
, item
->image
->mask
);
262 gdk_gc_set_clip_origin(gc
, image_x
, area
->y
+ 8);
263 gdk_draw_pixmap(widget
->window
, gc
,
265 0, 0, /* Source x,y */
266 image_x
, area
->y
+ 8, /* Dest x,y */
269 if (item
->flags
& ITEM_FLAG_SYMLINK
)
271 gdk_gc_set_clip_mask(gc
,
272 default_pixmap
[TYPE_SYMLINK
].mask
);
273 gdk_draw_pixmap(widget
->window
, gc
,
274 default_pixmap
[TYPE_SYMLINK
].pixmap
,
275 0, 0, /* Source x,y */
276 image_x
, area
->y
+ 8, /* Dest x,y */
280 gdk_gc_set_clip_mask(gc
, NULL
);
281 gdk_gc_set_clip_origin(gc
, 0, 0);
284 if (colitem
->selected
)
285 gtk_paint_flat_box(widget
->style
, widget
->window
,
286 GTK_STATE_SELECTED
, GTK_SHADOW_NONE
,
287 NULL
, widget
, "text",
288 text_x
, text_y
- font
->ascent
,
292 gdk_draw_text(widget
->window
,
294 colitem
->selected
? widget
->style
->white_gc
295 : widget
->style
->black_gc
,
297 item
->leafname
, strlen(item
->leafname
));
300 void show_menu(Collection
*collection
, GdkEventButton
*event
,
301 int number_selected
, gpointer user_data
)
303 show_filer_menu((FilerWindow
*) user_data
, event
);
306 void scan_dir(FilerWindow
*filer_window
)
308 if (filer_window
->dir
)
309 stop_scanning(filer_window
);
311 collection_set_item_size(filer_window
->collection
, 64, 64);
312 collection_clear(filer_window
->collection
);
313 gtk_window_set_title(GTK_WINDOW(filer_window
->window
),
316 filer_window
->dir
= opendir(filer_window
->path
);
317 if (!filer_window
->dir
)
319 report_error("Error scanning directory:", g_strerror(errno
));
323 filer_window
->idle_scan_id
= gtk_idle_add(idle_scan_dir
, filer_window
);
326 static void gain_selection(Collection
*collection
,
327 gint number_selected
,
330 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
332 if (window_with_selection
&& window_with_selection
!= filer_window
)
333 collection_clear_selection(window_with_selection
->collection
);
335 window_with_selection
= filer_window
;
338 static int sort_by_name(const void *item1
, const void *item2
)
340 return strcmp((*((FileItem
**)item1
))->leafname
,
341 (*((FileItem
**)item2
))->leafname
);
344 void open_item(Collection
*collection
,
345 gpointer item_data
, int item_number
,
348 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
349 FileItem
*item
= (FileItem
*) item_data
;
350 GdkEventButton
*event
;
353 event
= (GdkEventButton
*) gtk_get_current_event();
354 full_path
= make_path(filer_window
->path
, item
->leafname
)->str
;
356 switch (item
->base_type
)
359 if (item
->flags
& ITEM_FLAG_APPDIR
&&
360 (event
->type
!= GDK_2BUTTON_PRESS
||
361 (event
->state
& GDK_SHIFT_MASK
) == 0))
363 run_app(make_path(filer_window
->path
,
364 item
->leafname
)->str
);
367 if (event
->type
!= GDK_2BUTTON_PRESS
||
370 filer_window
->path
= pathdup(full_path
);
371 scan_dir(filer_window
);
374 filer_opendir(full_path
);
377 report_error("open_item",
378 "I don't know how to open that");
383 static gint
focus_in(GtkWidget
*widget
,
384 GdkEventFocus
*event
,
385 FilerWindow
*filer_window
)
387 window_with_focus
= filer_window
;
392 static gint
focus_out(GtkWidget
*widget
,
393 GdkEventFocus
*event
,
394 FilerWindow
*filer_window
)
396 /* TODO: Shade the cursor */
401 /* Handle keys that can't be bound with the menu */
402 static gint
key_press_event(GtkWidget
*widget
,
404 FilerWindow
*filer_window
)
406 switch (event
->keyval
)
424 filer_window
->path
= pathdup(make_path(
427 scan_dir(filer_window
);
435 void filer_opendir(char *path
)
437 GtkWidget
*hbox
, *scrollbar
, *collection
;
438 FilerWindow
*filer_window
;
440 filer_window
= g_malloc(sizeof(FilerWindow
));
441 filer_window
->path
= pathdup(path
);
442 filer_window
->dir
= NULL
; /* Not scanning */
444 collection
= collection_new(NULL
);
445 gtk_object_set_data(GTK_OBJECT(collection
),
446 "filer_window", filer_window
);
447 filer_window
->collection
= COLLECTION(collection
);
448 collection_set_functions(filer_window
->collection
,
449 draw_item
, test_point
);
451 filer_window
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
452 gtk_window_set_default_size(GTK_WINDOW(filer_window
->window
),
455 hbox
= gtk_hbox_new(FALSE
, 0);
456 gtk_container_add(GTK_CONTAINER(filer_window
->window
), hbox
);
458 gtk_box_pack_start(GTK_BOX(hbox
), collection
, TRUE
, TRUE
, 0);
460 scrollbar
= gtk_vscrollbar_new(COLLECTION(collection
)->vadj
);
461 gtk_box_pack_start(GTK_BOX(hbox
), scrollbar
, FALSE
, TRUE
, 0);
463 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "focus_in_event",
464 GTK_SIGNAL_FUNC(focus_in
), filer_window
);
465 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "focus_out_event",
466 GTK_SIGNAL_FUNC(focus_out
), filer_window
);
467 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "key_press_event",
468 GTK_SIGNAL_FUNC(key_press_event
), filer_window
);
469 gtk_signal_connect(GTK_OBJECT(filer_window
->collection
), "open_item",
470 open_item
, filer_window
);
471 gtk_signal_connect(GTK_OBJECT(filer_window
->window
), "destroy",
472 filer_window_destroyed
, filer_window
);
473 gtk_signal_connect(GTK_OBJECT(collection
), "show_menu",
474 show_menu
, filer_window
);
475 gtk_signal_connect(GTK_OBJECT(collection
), "gain_selection",
476 gain_selection
, filer_window
);
477 gtk_signal_connect(GTK_OBJECT(collection
), "drag_selection",
478 drag_selection
, filer_window
);
479 gtk_signal_connect(GTK_OBJECT(collection
), "drag_data_get",
480 drag_data_get
, filer_window
);
481 drag_set_dest(collection
);
483 gtk_widget_show_all(filer_window
->window
);
486 load_default_pixmaps(collection
->window
);
488 gtk_accel_group_attach(filer_keys
, GTK_OBJECT(filer_window
->window
));
490 scan_dir(filer_window
);