r21: Files can now be copied by dragging them between filer windows.
[rox-filer.git] / ROX-Filer / src / filer.c
blob19baf9828dda790be22d972f370666316775891b
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>
16 #include <gtk/gtk.h>
17 #include <gdk/gdkprivate.h> /* XXX - find another way to do this */
18 #include <gdk/gdkkeysyms.h>
19 #include <collection.h>
21 #include "support.h"
22 #include "gui_support.h"
23 #include "filer.h"
24 #include "pixmaps.h"
25 #include "menu.h"
26 #include "dnd.h"
27 #include "apps.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,
44 CollectionItem *item,
45 GdkRectangle *area);
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,
52 CollectionItem *item,
53 int width, int height);
54 static void stop_scanning(FilerWindow *filer_window);
55 static gint focus_in(GtkWidget *widget,
56 GdkEventFocus *event,
57 FilerWindow *filer_window);
58 static gint focus_out(GtkWidget *widget,
59 GdkEventFocus *event,
60 FilerWindow *filer_window);
63 void filer_init()
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);
89 g_free(filer_window);
91 if (--number_of_windows < 1)
92 gtk_main_quit();
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)
107 struct dirent *next;
108 FilerWindow *filer_window = (FilerWindow *) data;
112 next = readdir(filer_window->dir);
113 if (!next)
115 closedir(filer_window->dir);
116 filer_window->dir = NULL;
118 collection_qsort(filer_window->collection,
119 sort_by_name);
120 return FALSE; /* Finished */
123 add_item(filer_window, next->d_name);
124 } while (!gtk_events_pending());
126 return TRUE;
129 /* Add a single object to a directory display */
130 static void add_item(FilerWindow *filer_window, char *leafname)
132 FileItem *item;
133 int item_width;
134 struct stat info;
135 int base_type;
136 GString *path;
138 /* Ignore dot files (should be an option) */
139 if (leafname[0] == '.')
140 return;
142 item = g_malloc(sizeof(FileItem));
143 item->leafname = g_strdup(leafname);
144 item->flags = 0;
146 path = make_path(filer_window->path, leafname);
147 if (lstat(path->str, &info))
148 base_type = TYPE_ERROR;
149 else
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;
169 else
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;
183 else
184 base_type = TYPE_UNKNOWN;
187 item->flags |= ITEM_FLAG_SYMLINK;
189 else
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;
205 else
206 item->image = default_pixmap + base_type;
208 item->text_width = gdk_string_width(filer_window->window->style->font,
209 leafname);
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,
218 item_width,
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 &&
236 point_y > 6)
237 return TRUE;
239 if (x_off <= (fileitem->text_width >> 1) + 2 &&
240 point_y > height - text_height - 2)
241 return TRUE;
243 return FALSE;
246 static void draw_item(GtkWidget *widget,
247 CollectionItem *colitem,
248 GdkRectangle *area)
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;
259 if (item->image)
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,
264 item->image->pixmap,
265 0, 0, /* Source x,y */
266 image_x, area->y + 8, /* Dest x,y */
267 -1, -1);
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 */
277 -1, -1);
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,
289 item->text_width,
290 text_height);
292 gdk_draw_text(widget->window,
293 widget->style->font,
294 colitem->selected ? widget->style->white_gc
295 : widget->style->black_gc,
296 text_x, text_y,
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),
314 filer_window->path);
316 filer_window->dir = opendir(filer_window->path);
317 if (!filer_window->dir)
319 report_error("Error scanning directory:", g_strerror(errno));
320 return;
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,
328 gpointer user_data)
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,
346 gpointer user_data)
348 FilerWindow *filer_window = (FilerWindow *) user_data;
349 FileItem *item = (FileItem *) item_data;
350 GdkEventButton *event;
351 char *full_path;
353 event = (GdkEventButton *) gtk_get_current_event();
354 full_path = make_path(filer_window->path, item->leafname)->str;
356 switch (item->base_type)
358 case TYPE_DIRECTORY:
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);
365 break;
367 if (event->type != GDK_2BUTTON_PRESS ||
368 event->button == 1)
370 filer_window->path = pathdup(full_path);
371 scan_dir(filer_window);
373 else
374 filer_opendir(full_path);
375 break;
376 default:
377 report_error("open_item",
378 "I don't know how to open that");
379 break;
383 static gint focus_in(GtkWidget *widget,
384 GdkEventFocus *event,
385 FilerWindow *filer_window)
387 window_with_focus = filer_window;
389 return FALSE;
392 static gint focus_out(GtkWidget *widget,
393 GdkEventFocus *event,
394 FilerWindow *filer_window)
396 /* TODO: Shade the cursor */
398 return FALSE;
401 /* Handle keys that can't be bound with the menu */
402 static gint key_press_event(GtkWidget *widget,
403 GdkEventKey *event,
404 FilerWindow *filer_window)
406 switch (event->keyval)
409 case GDK_Left:
410 move_cursor(-1, 0);
411 break;
412 case GDK_Right:
413 move_cursor(1, 0);
414 break;
415 case GDK_Up:
416 move_cursor(0, -1);
417 break;
418 case GDK_Down:
419 move_cursor(0, 1);
420 break;
421 case GDK_Return:
423 case GDK_BackSpace:
424 filer_window->path = pathdup(make_path(
425 filer_window->path,
426 "..")->str);
427 scan_dir(filer_window);
428 return TRUE;
431 return FALSE;
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),
453 400, 200);
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);
484 number_of_windows++;
486 load_default_pixmaps(collection->window);
488 gtk_accel_group_attach(filer_keys, GTK_OBJECT(filer_window->window));
490 scan_dir(filer_window);