Merge few more vdlist/vdtree functions.
[geeqie.git] / src / view_dir_tree.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include "main.h"
13 #include "view_dir_tree.h"
14
15
16 #include "dnd.h"
17 #include "dupe.h"
18 #include "filelist.h"
19 #include "layout.h"
20 #include "layout_image.h"
21 #include "layout_util.h"
22 #include "utilops.h"
23 #include "ui_bookmark.h"
24 #include "ui_fileops.h"
25 #include "ui_menu.h"
26 #include "ui_tree_edit.h"
27 #include "view_dir.h"
28
29 #include <gdk/gdkkeysyms.h> /* for keyboard values */
30
31
32 #define VDTREE_INDENT 14
33 #define VDTREE_PAD 4
34
35 #define VDTREE_INFO(_vd_, _part_) (((ViewDirInfoTree *)(_vd_->info))->_part_)
36
37
38 typedef struct _PathData PathData;
39 struct _PathData
40 {
41         gchar *name;
42         FileData *node;
43 };
44
45
46
47 static gint vdtree_populate_path_by_iter(ViewDir *vd, GtkTreeIter *iter, gint force, const gchar *target_path);
48
49
50 /*
51  *----------------------------------------------------------------------------
52  * utils
53  *----------------------------------------------------------------------------
54  */
55
56 static void set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
57 {
58         GdkCursor *cursor = NULL;
59
60         if (!widget || !widget->window) return;
61
62         if (cursor_type > -1) cursor = gdk_cursor_new (cursor_type);
63         gdk_window_set_cursor (widget->window, cursor);
64         if (cursor) gdk_cursor_unref(cursor);
65         gdk_flush();
66 }
67
68 static void vdtree_busy_push(ViewDir *vd)
69 {
70         if (VDTREE_INFO(vd, busy_ref) == 0) set_cursor(vd->view, GDK_WATCH);
71         VDTREE_INFO(vd, busy_ref)++;
72 }
73
74 static void vdtree_busy_pop(ViewDir *vd)
75 {
76         if (VDTREE_INFO(vd, busy_ref) == 1) set_cursor(vd->view, -1);
77         if (VDTREE_INFO(vd, busy_ref) > 0) VDTREE_INFO(vd, busy_ref)--;
78 }
79
80 gint vdtree_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter, GtkTreeIter *parent)
81 {
82         GtkTreeModel *store;
83         gint valid;
84
85         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
86         if (parent)
87                 {
88                 valid = gtk_tree_model_iter_children(store, iter, parent);
89                 }
90         else
91                 {
92                 valid = gtk_tree_model_get_iter_first(store, iter);
93                 }
94         while (valid)
95                 {
96                 NodeData *nd;
97                 GtkTreeIter found;
98
99                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, DIR_COLUMN_POINTER, &nd, -1);
100                 if (nd->fd == fd) return TRUE;
101
102                 if (vdtree_find_row(vd, fd, &found, iter))
103                         {
104                         memcpy(iter, &found, sizeof(found));
105                         return TRUE;
106                         }
107
108                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), iter);
109                 }
110
111         return FALSE;
112 }
113
114 static void vdtree_icon_set_by_iter(ViewDir *vd, GtkTreeIter *iter, GdkPixbuf *pixbuf)
115 {
116         GtkTreeModel *store;
117         GdkPixbuf *old;
118
119         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
120         gtk_tree_model_get(store, iter, DIR_COLUMN_ICON, &old, -1);
121         if (old != vd->pf->deny)
122                 {
123                 gtk_tree_store_set(GTK_TREE_STORE(store), iter, DIR_COLUMN_ICON, pixbuf, -1);
124                 }
125 }
126
127 static void vdtree_expand_by_iter(ViewDir *vd, GtkTreeIter *iter, gint expand)
128 {
129         GtkTreeModel *store;
130         GtkTreePath *tpath;
131
132         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
133         tpath = gtk_tree_model_get_path(store, iter);
134         if (expand)
135                 {
136                 gtk_tree_view_expand_row(GTK_TREE_VIEW(vd->view), tpath, FALSE);
137                 vdtree_icon_set_by_iter(vd, iter, vd->pf->open);
138                 }
139         else
140                 {
141                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(vd->view), tpath);
142                 }
143         gtk_tree_path_free(tpath);
144 }
145
146 static void vdtree_expand_by_data(ViewDir *vd, FileData *fd, gint expand)
147 {
148         GtkTreeIter iter;
149
150         if (vd_find_row(vd, fd, &iter))
151                 {
152                 vdtree_expand_by_iter(vd, &iter, expand);
153                 }
154 }
155
156 static void vdtree_node_free(NodeData *nd)
157 {
158         if (!nd) return;
159
160         file_data_unref(nd->fd);
161         g_free(nd);
162 }
163
164 /*
165  *----------------------------------------------------------------------------
166  * dnd
167  *----------------------------------------------------------------------------
168  */
169
170 static GtkTargetEntry vdtree_dnd_drop_types[] = {
171         { "text/uri-list", 0, TARGET_URI_LIST }
172 };
173 static gint vdtree_dnd_drop_types_count = 1;
174
175
176 static void vdtree_dest_set(ViewDir *vd, gint enable)
177 {
178         if (enable)
179                 {
180                 gtk_drag_dest_set(vd->view,
181                                   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
182                                   vdtree_dnd_drop_types, vdtree_dnd_drop_types_count,
183                                   GDK_ACTION_MOVE | GDK_ACTION_COPY);
184                 }
185         else
186                 {
187                 gtk_drag_dest_unset(vd->view);
188                 }
189 }
190
191 static void vdtree_dnd_get(GtkWidget *widget, GdkDragContext *context,
192                            GtkSelectionData *selection_data, guint info,
193                            guint time, gpointer data)
194 {
195         ViewDir *vd = data;
196         GList *list;
197         gchar *uri_text = NULL;
198         gint length = 0;
199
200         if (!vd->click_fd) return;
201
202         switch (info)
203                 {
204                 case TARGET_URI_LIST:
205                 case TARGET_TEXT_PLAIN:
206                         list = g_list_prepend(NULL, vd->click_fd);
207                         uri_text = uri_text_from_filelist(list, &length, (info == TARGET_TEXT_PLAIN));
208                         g_list_free(list);
209                         break;
210                 }
211
212         if (uri_text)
213                 {
214                 gtk_selection_data_set(selection_data, selection_data->target,
215                                        8, (guchar *)uri_text, length);
216                 g_free(uri_text);
217                 }
218 }
219
220 static void vdtree_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
221 {
222         ViewDir *vd = data;
223
224         vd_color_set(vd, vd->click_fd, TRUE);
225         vdtree_dest_set(vd, FALSE);
226 }
227
228 static void vdtree_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
229 {
230         ViewDir *vd = data;
231
232         vd_color_set(vd, vd->click_fd, FALSE);
233         vdtree_dest_set(vd, TRUE);
234 }
235
236 static void vdtree_dnd_drop_receive(GtkWidget *widget,
237                                     GdkDragContext *context, gint x, gint y,
238                                     GtkSelectionData *selection_data, guint info,
239                                     guint time, gpointer data)
240 {
241         ViewDir *vd = data;
242         GtkTreePath *tpath;
243         GtkTreeIter iter;
244         FileData *fd = NULL;
245
246         vd->click_fd = NULL;
247
248         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y,
249                                           &tpath, NULL, NULL, NULL))
250                 {
251                 GtkTreeModel *store;
252                 NodeData *nd;
253
254                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
255                 gtk_tree_model_get_iter(store, &iter, tpath);
256                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
257                 gtk_tree_path_free(tpath);
258
259                 fd = (nd) ? nd->fd : NULL;
260                 }
261
262         if (!fd) return;
263
264         if (info == TARGET_URI_LIST)
265                 {
266                 GList *list;
267                 gint active;
268
269                 list = uri_filelist_from_text((gchar *)selection_data->data, TRUE);
270                 if (!list) return;
271
272                 active = access_file(fd->path, W_OK | X_OK);
273
274                 vd_color_set(vd, fd, TRUE);
275                 vd->popup = vd_drop_menu(vd, active);
276                 gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL, 0, time);
277
278                 vd->drop_fd = fd;
279                 vd->drop_list = list;
280                 }
281 }
282
283 static gint vdtree_dnd_drop_expand_cb(gpointer data)
284 {
285         ViewDir *vd = data;
286         GtkTreeIter iter;
287
288         if (vd->drop_fd &&
289             vd_find_row(vd, vd->drop_fd, &iter))
290                 {
291                 vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->path);
292                 vdtree_expand_by_data(vd, vd->drop_fd, TRUE);
293                 }
294
295         VDTREE_INFO(vd, drop_expand_id) = -1;
296         return FALSE;
297 }
298
299 static void vdtree_dnd_drop_expand_cancel(ViewDir *vd)
300 {
301         if (VDTREE_INFO(vd, drop_expand_id) != -1) g_source_remove(VDTREE_INFO(vd, drop_expand_id));
302         VDTREE_INFO(vd, drop_expand_id) = -1;
303 }
304
305 static void vdtree_dnd_drop_expand(ViewDir *vd)
306 {
307         vdtree_dnd_drop_expand_cancel(vd);
308         VDTREE_INFO(vd, drop_expand_id) = g_timeout_add(1000, vdtree_dnd_drop_expand_cb, vd);
309 }
310
311 static void vdtree_drop_update(ViewDir *vd, gint x, gint y)
312 {
313         GtkTreePath *tpath;
314         GtkTreeIter iter;
315         FileData *fd = NULL;
316
317         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vd->view), x, y,
318                                           &tpath, NULL, NULL, NULL))
319                 {
320                 GtkTreeModel *store;
321                 NodeData *nd;
322
323                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
324                 gtk_tree_model_get_iter(store, &iter, tpath);
325                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
326                 gtk_tree_path_free(tpath);
327
328                 fd = (nd) ? nd->fd : NULL;
329                 }
330
331         if (fd != vd->drop_fd)
332                 {
333                 vd_color_set(vd, vd->drop_fd, FALSE);
334                 vd_color_set(vd, fd, TRUE);
335                 if (fd) vdtree_dnd_drop_expand(vd);
336                 }
337
338         vd->drop_fd = fd;
339 }
340
341 static void vdtree_dnd_drop_scroll_cancel(ViewDir *vd)
342 {
343         if (vd->drop_scroll_id != -1) g_source_remove(vd->drop_scroll_id);
344         vd->drop_scroll_id = -1;
345 }
346
347 static gint vdtree_auto_scroll_idle_cb(gpointer data)
348 {
349         ViewDir *vd = data;
350
351         if (vd->drop_fd)
352                 {
353                 GdkWindow *window;
354                 gint x, y;
355                 gint w, h;
356
357                 window = vd->view->window;
358                 gdk_window_get_pointer(window, &x, &y, NULL);
359                 gdk_drawable_get_size(window, &w, &h);
360                 if (x >= 0 && x < w && y >= 0 && y < h)
361                         {
362                         vdtree_drop_update(vd, x, y);
363                         }
364                 }
365
366         vd->drop_scroll_id = -1;
367         return FALSE;
368 }
369
370 static gint vdtree_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data)
371 {
372         ViewDir *vd = data;
373
374         if (!vd->drop_fd || vd->drop_list) return FALSE;
375
376         if (vd->drop_scroll_id == -1) vd->drop_scroll_id = g_idle_add(vdtree_auto_scroll_idle_cb, vd);
377
378         return TRUE;
379 }
380
381 static gint vdtree_dnd_drop_motion(GtkWidget *widget, GdkDragContext *context,
382                                    gint x, gint y, guint time, gpointer data)
383 {
384         ViewDir *vd = data;
385
386         vd->click_fd = NULL;
387
388         if (gtk_drag_get_source_widget(context) == vd->view)
389                 {
390                 gdk_drag_status(context, 0, time);
391                 return TRUE;
392                 }
393         else
394                 {
395                 gdk_drag_status(context, context->suggested_action, time);
396                 }
397
398         vdtree_drop_update(vd, x, y);
399
400         if (vd->drop_fd)
401                 {
402                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vd->view));
403                 widget_auto_scroll_start(vd->view, adj, -1, -1, vdtree_auto_scroll_notify_cb, vd);
404                 }
405
406         return FALSE;
407 }
408
409 static void vdtree_dnd_drop_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data)
410 {
411         ViewDir *vd = data;
412
413         if (vd->drop_fd != vd->click_fd) vd_color_set(vd, vd->drop_fd, FALSE);
414
415         vd->drop_fd = NULL;
416
417         vdtree_dnd_drop_expand_cancel(vd);
418 }
419
420 static void vdtree_dnd_init(ViewDir *vd)
421 {
422         gtk_drag_source_set(vd->view, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
423                             dnd_file_drag_types, dnd_file_drag_types_count,
424                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
425         g_signal_connect(G_OBJECT(vd->view), "drag_data_get",
426                          G_CALLBACK(vdtree_dnd_get), vd);
427         g_signal_connect(G_OBJECT(vd->view), "drag_begin",
428                          G_CALLBACK(vdtree_dnd_begin), vd);
429         g_signal_connect(G_OBJECT(vd->view), "drag_end",
430                          G_CALLBACK(vdtree_dnd_end), vd);
431
432         vdtree_dest_set(vd, TRUE);
433         g_signal_connect(G_OBJECT(vd->view), "drag_data_received",
434                          G_CALLBACK(vdtree_dnd_drop_receive), vd);
435         g_signal_connect(G_OBJECT(vd->view), "drag_motion",
436                          G_CALLBACK(vdtree_dnd_drop_motion), vd);
437         g_signal_connect(G_OBJECT(vd->view), "drag_leave",
438                          G_CALLBACK(vdtree_dnd_drop_leave), vd);
439 }
440
441 /*
442  *----------------------------------------------------------------------------
443  * parts lists
444  *----------------------------------------------------------------------------
445  */
446
447 static GList *parts_list(const gchar *path)
448 {
449         GList *list = NULL;
450         const gchar *strb, *strp;
451         gint l;
452
453         strp = path;
454
455         if (*strp != '/') return NULL;
456
457         strp++;
458         strb = strp;
459         l = 0;
460
461         while (*strp != '\0')
462                 {
463                 if (*strp == '/')
464                         {
465                         if (l > 0) list = g_list_prepend(list, g_strndup(strb, l));
466                         strp++;
467                         strb = strp;
468                         l = 0;
469                         }
470                 else
471                         {
472                         strp++;
473                         l++;
474                         }
475                 }
476         if (l > 0) list = g_list_prepend(list, g_strndup(strb, l));
477
478         list = g_list_reverse(list);
479
480         list = g_list_prepend(list, g_strdup("/"));
481
482         return list;
483 }
484
485 static void parts_list_free(GList *list)
486 {
487         GList *work = list;
488         while (work)
489                 {
490                 PathData *pd = work->data;
491                 g_free(pd->name);
492                 g_free(pd);
493                 work = work->next;
494                 }
495
496         g_list_free(list);
497 }
498
499 static GList *parts_list_add_node_points(ViewDir *vd, GList *list)
500 {
501         GList *work;
502         GtkTreeModel *store;
503         GtkTreeIter iter;
504         gint valid;
505
506         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
507         valid = gtk_tree_model_get_iter_first(store, &iter);
508
509         work = list;
510         while (work)
511                 {
512                 PathData *pd;
513                 FileData *fd = NULL;
514
515                 pd = g_new0(PathData, 1);
516                 pd->name = work->data;
517
518                 while (valid && !fd)
519                         {
520                         NodeData *nd;
521
522                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
523                         if (strcmp(nd->fd->name, pd->name) == 0)
524                                 {
525                                 fd = nd->fd;
526                                 }
527                         else
528                                 {
529                                 valid = gtk_tree_model_iter_next(store, &iter);
530                                 }
531                         }
532
533                 pd->node = fd;
534                 work->data = pd;
535
536                 if (fd)
537                         {
538                         GtkTreeIter parent;
539                         memcpy(&parent, &iter, sizeof(parent));
540                         valid = gtk_tree_model_iter_children(store, &iter, &parent);
541                         }
542
543                 work = work->next;
544                 }
545
546         return list;
547 }
548
549 /*
550  *----------------------------------------------------------------------------
551  * misc
552  *----------------------------------------------------------------------------
553  */
554
555 #if 0
556 static void vdtree_row_deleted_cb(GtkTreeModel *tree_model, GtkTreePath *tpath, gpointer data)
557 {
558         GtkTreeIter iter;
559         NodeData *nd;
560
561         gtk_tree_model_get_iter(tree_model, &iter, tpath);
562         gtk_tree_model_get(tree_model, &iter, DIR_COLUMN_POINTER, &nd, -1);
563
564         if (!nd) return;
565
566         file_data_unref(nd->fd);
567         g_free(nd);
568 }
569 #endif
570
571 /*
572  *----------------------------------------------------------------------------
573  * node traversal, management
574  *----------------------------------------------------------------------------
575  */
576
577 static gint vdtree_find_iter_by_data(ViewDir *vd, GtkTreeIter *parent, NodeData *nd, GtkTreeIter *iter)
578 {
579         GtkTreeModel *store;
580
581         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
582         if (!nd || !gtk_tree_model_iter_children(store, iter, parent)) return -1;
583         do      {
584                 NodeData *cnd;
585
586                 gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &cnd, -1);
587                 if (cnd == nd) return TRUE;
588                 } while (gtk_tree_model_iter_next(store, iter));
589
590         return FALSE;
591 }
592
593 static NodeData *vdtree_find_iter_by_name(ViewDir *vd, GtkTreeIter *parent, const gchar *name, GtkTreeIter *iter)
594 {
595         GtkTreeModel *store;
596
597         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
598         if (!name || !gtk_tree_model_iter_children(store, iter, parent)) return NULL;
599         do      {
600                 NodeData *nd;
601
602                 gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
603                 if (nd && strcmp(nd->fd->name, name) == 0) return nd;
604                 } while (gtk_tree_model_iter_next(store, iter));
605
606         return NULL;
607 }
608
609 static void vdtree_add_by_data(ViewDir *vd, FileData *fd, GtkTreeIter *parent)
610 {
611         GtkTreeStore *store;
612         GtkTreeIter child;
613         NodeData *nd;
614         GdkPixbuf *pixbuf;
615         NodeData *end;
616         GtkTreeIter empty;
617
618         if (!fd) return;
619
620         if (access_file(fd->path, R_OK | X_OK))
621                 {
622                 pixbuf = vd->pf->close;
623                 }
624         else
625                 {
626                 pixbuf = vd->pf->deny;
627                 }
628
629         nd = g_new0(NodeData, 1);
630         nd->fd = fd;
631         nd->expanded = FALSE;
632         nd->last_update = time(NULL);
633
634         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view)));
635         gtk_tree_store_append(store, &child, parent);
636         gtk_tree_store_set(store, &child, DIR_COLUMN_POINTER, nd,
637                                          DIR_COLUMN_ICON, pixbuf,
638                                          DIR_COLUMN_NAME, nd->fd->name,
639                                          DIR_COLUMN_COLOR, FALSE, -1);
640
641         /* all nodes are created with an "empty" node, so that the expander is shown
642          * this is removed when the child is populated */
643         end = g_new0(NodeData, 1);
644         end->fd = file_data_new_simple("");
645         end->expanded = TRUE;
646
647         gtk_tree_store_append(store, &empty, &child);
648         gtk_tree_store_set(store, &empty, DIR_COLUMN_POINTER, end,
649                                           DIR_COLUMN_NAME, "empty", -1);
650
651         if (parent)
652                 {
653                 NodeData *pnd;
654                 GtkTreePath *tpath;
655
656                 gtk_tree_model_get(GTK_TREE_MODEL(store), parent, DIR_COLUMN_POINTER, &pnd, -1);
657                 tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), parent);
658                 if (options->tree_descend_subdirs &&
659                     gtk_tree_view_row_expanded(GTK_TREE_VIEW(vd->view), tpath) &&
660                     !nd->expanded)
661                         {
662                         vdtree_populate_path_by_iter(vd, &child, FALSE, vd->path);
663                         }
664                 gtk_tree_path_free(tpath);
665                 }
666 }
667
668 static gint vdtree_populate_path_by_iter(ViewDir *vd, GtkTreeIter *iter, gint force, const gchar *target_path)
669 {
670         GtkTreeModel *store;
671         GList *list;
672         GList *work;
673         GList *old;
674         time_t current_time;
675         GtkTreeIter child;
676         NodeData *nd;
677
678         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
679         gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
680
681         if (!nd) return FALSE;
682
683         current_time = time(NULL);
684         
685         if (nd->expanded)
686                 {
687                 if (!force && current_time - nd->last_update < 10) return TRUE;
688                 if (!isdir(nd->fd->path))
689                         {
690                         if (vd->click_fd == nd->fd) vd->click_fd = NULL;
691                         if (vd->drop_fd == nd->fd) vd->drop_fd = NULL;
692                         gtk_tree_store_remove(GTK_TREE_STORE(store), iter);
693                         vdtree_node_free(nd);
694                         return FALSE;
695                         }
696                 if (!force && filetime(nd->fd->path) == nd->fd->date) return TRUE;
697                 }
698
699         vdtree_busy_push(vd);
700
701         list = NULL;
702         filelist_read(nd->fd->path, NULL, &list);
703
704         /* when hidden files are not enabled, and the user enters a hidden path,
705          * allow the tree to display that path by specifically inserting the hidden entries
706          */
707         if (!options->file_filter.show_hidden_files &&
708             target_path &&
709             strncmp(nd->fd->path, target_path, strlen(nd->fd->path)) == 0)
710                 {
711                 gint n;
712
713                 n = strlen(nd->fd->path);
714                 if (target_path[n] == '/' && target_path[n+1] == '.')
715                         {
716                         gchar *name8;
717                         struct stat sbuf;
718
719                         n++;
720
721                         while (target_path[n] != '\0' && target_path[n] != '/') n++;
722                         name8 = g_strndup(target_path, n);
723
724                         if (stat_utf8(name8, &sbuf))
725                                 {
726                                 list = g_list_prepend(list, file_data_new_simple(name8));
727                                 }
728
729                         g_free(name8);
730                         }
731                 }
732
733         old = NULL;
734         if (gtk_tree_model_iter_children(store, &child, iter))
735                 {
736                 do      {
737                         NodeData *cnd;
738
739                         gtk_tree_model_get(store, &child, DIR_COLUMN_POINTER, &cnd, -1);
740                         old = g_list_prepend(old, cnd);
741                         } while (gtk_tree_model_iter_next(store, &child));
742                 }
743
744         work = list;
745         while (work)
746                 {
747                 FileData *fd;
748
749                 fd = work->data;
750                 work = work->next;
751
752                 if (strcmp(fd->name, ".") == 0 || strcmp(fd->name, "..") == 0)
753                         {
754                         file_data_unref(fd);
755                         }
756                 else
757                         {
758                         NodeData *cnd;
759
760                         cnd = vdtree_find_iter_by_name(vd, iter, fd->name, &child);
761                         if (cnd)
762                                 {
763                                 old = g_list_remove(old, cnd);
764                                 if (cnd->expanded && cnd->fd->date != fd->date &&
765                                     vdtree_populate_path_by_iter(vd, &child, FALSE, target_path))
766                                         {
767                                         cnd->fd->size = fd->size;
768                                         cnd->fd->date = fd->date;
769                                         }
770
771                                 file_data_unref(fd);
772                                 }
773                         else
774                                 {
775                                 vdtree_add_by_data(vd, fd, iter);
776                                 }
777                         }
778                 }
779
780         work = old;
781         while (work)
782                 {
783                 NodeData *cnd = work->data;
784                 work = work->next;
785
786                 if (vd->click_fd == cnd->fd) vd->click_fd = NULL;
787                 if (vd->drop_fd == cnd->fd) vd->drop_fd = NULL;
788
789                 if (vdtree_find_iter_by_data(vd, iter, cnd, &child))
790                         {
791                         gtk_tree_store_remove(GTK_TREE_STORE(store), &child);
792                         vdtree_node_free(cnd);
793                         }
794                 }
795
796         g_list_free(old);
797         g_list_free(list);
798
799         vdtree_busy_pop(vd);
800
801         nd->expanded = TRUE;
802         nd->last_update = current_time;
803
804         return TRUE;
805 }
806
807 FileData *vdtree_populate_path(ViewDir *vd, const gchar *path, gint expand, gint force)
808 {
809         GList *list;
810         GList *work;
811         FileData *fd = NULL;
812
813         if (!path) return NULL;
814
815         vdtree_busy_push(vd);
816
817         list = parts_list(path);
818         list = parts_list_add_node_points(vd, list);
819
820         work = list;
821         while (work)
822                 {
823                 PathData *pd = work->data;
824                 if (pd->node == NULL)
825                         {
826                         PathData *parent_pd;
827                         GtkTreeIter parent_iter;
828                         GtkTreeIter iter;
829                         NodeData *nd;
830
831                         if (work == list)
832                                 {
833                                 /* should not happen */
834                                 printf("vdtree warning, root node not found\n");
835                                 parts_list_free(list);
836                                 vdtree_busy_pop(vd);
837                                 return NULL;
838                                 }
839
840                         parent_pd = work->prev->data;
841
842                         if (!vd_find_row(vd, parent_pd->node, &parent_iter) ||
843                             !vdtree_populate_path_by_iter(vd, &parent_iter, force, path) ||
844                             (nd = vdtree_find_iter_by_name(vd, &parent_iter, pd->name, &iter)) == NULL)
845                                 {
846                                 printf("vdtree warning, aborted at %s\n", parent_pd->name);
847                                 parts_list_free(list);
848                                 vdtree_busy_pop(vd);
849                                 return NULL;
850                                 }
851
852                         pd->node = nd->fd;
853
854                         if (pd->node)
855                                 {
856                                 if (expand)
857                                         {
858                                         vdtree_expand_by_iter(vd, &parent_iter, TRUE);
859                                         vdtree_expand_by_iter(vd, &iter, TRUE);
860                                         }
861                                 vdtree_populate_path_by_iter(vd, &iter, force, path);
862                                 }
863                         }
864                 else
865                         {
866                         GtkTreeIter iter;
867
868                         if (vd_find_row(vd, pd->node, &iter))
869                                 {
870                                 if (expand) vdtree_expand_by_iter(vd, &iter, TRUE);
871                                 vdtree_populate_path_by_iter(vd, &iter, force, path);
872                                 }
873                         }
874
875                 work = work->next;
876                 }
877
878         work = g_list_last(list);
879         if (work)
880                 {
881                 PathData *pd = work->data;
882                 fd = pd->node;
883                 }
884         parts_list_free(list);
885
886         vdtree_busy_pop(vd);
887
888         return fd;
889 }
890
891 /*
892  *----------------------------------------------------------------------------
893  * access
894  *----------------------------------------------------------------------------
895  */
896
897 static gint selection_is_ok = FALSE;
898
899 static gboolean vdtree_select_cb(GtkTreeSelection *selection, GtkTreeModel *store, GtkTreePath *tpath,
900                                  gboolean path_currently_selected, gpointer data)
901 {
902         return selection_is_ok;
903 }
904
905 static void vdtree_select_row(ViewDir *vd, FileData *fd)
906 {
907         GtkTreeSelection *selection;
908         GtkTreeIter iter;
909                                                                                                                                
910         if (!vd_find_row(vd, fd, &iter)) return;
911         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vd->view));
912
913         /* hack, such that selection is only allowed to be changed from here */
914         selection_is_ok = TRUE;
915         gtk_tree_selection_select_iter(selection, &iter);
916         selection_is_ok = FALSE;
917
918         if (!vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->path)) return;
919
920         vdtree_expand_by_iter(vd, &iter, TRUE);
921
922         if (fd && vd->select_func)
923                 {
924                 vd->select_func(vd, fd->path, vd->select_data);
925                 }
926 }
927
928 gint vdtree_set_path(ViewDir *vd, const gchar *path)
929 {
930         FileData *fd;
931         GtkTreeIter iter;
932
933         if (!path) return FALSE;
934         if (vd->path && strcmp(path, vd->path) == 0) return TRUE;
935
936         g_free(vd->path);
937         vd->path = g_strdup(path);
938
939         fd = vdtree_populate_path(vd, vd->path, TRUE, FALSE);
940
941         if (!fd) return FALSE;
942
943         if (vd_find_row(vd, fd, &iter))
944                 {
945                 GtkTreeModel *store;
946                 GtkTreePath *tpath;
947
948                 tree_view_row_make_visible(GTK_TREE_VIEW(vd->view), &iter, TRUE);
949
950                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
951                 tpath = gtk_tree_model_get_path(store, &iter);
952                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vd->view), tpath, NULL, FALSE);
953                 gtk_tree_path_free(tpath);
954
955                 vdtree_select_row(vd, fd);
956                 }
957
958         return TRUE;
959 }
960
961 #if 0
962 const gchar *vdtree_get_path(ViewDir *vd)
963 {
964         return vd->path;
965 }
966 #endif
967
968 void vdtree_refresh(ViewDir *vd)
969 {
970         vdtree_populate_path(vd, vd->path, FALSE, TRUE);
971 }
972
973 const gchar *vdtree_row_get_path(ViewDir *vd, gint row)
974 {
975         printf("FIXME: no get row path\n");
976         return NULL;
977 }
978
979 /*
980  *----------------------------------------------------------------------------
981  * callbacks
982  *----------------------------------------------------------------------------
983  */
984
985 static void vdtree_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
986 {
987         ViewDir *vd = data;
988         GtkTreeModel *store;
989         GtkTreeIter iter;
990         GtkTreePath *tpath;
991         gint cw, ch;
992
993         if (vd_find_row(vd, vd->click_fd, &iter) < 0) return;
994         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
995         tpath = gtk_tree_model_get_path(store, &iter);
996         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
997         gtk_tree_path_free(tpath);
998         *y += ch;
999         popup_menu_position_clamp(menu, x, y, 0);
1000 }
1001
1002 static gint vdtree_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1003 {
1004         ViewDir *vd = data;
1005         GtkTreePath *tpath;
1006         GtkTreeIter iter;
1007         FileData *fd = NULL;
1008
1009         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vd->view), &tpath, NULL);
1010         if (tpath)
1011                 {
1012                 GtkTreeModel *store;
1013                 NodeData *nd;
1014
1015                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1016                 gtk_tree_model_get_iter(store, &iter, tpath);
1017                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
1018
1019                 gtk_tree_path_free(tpath);
1020
1021                 fd = (nd) ? nd->fd : NULL;
1022                 }
1023
1024         switch (event->keyval)
1025                 {
1026                 case GDK_Menu:
1027                         vd->click_fd = fd;
1028                         vd_color_set(vd, vd->click_fd, TRUE);
1029
1030                         vd->popup = vd_pop_menu(vd, vd->click_fd);
1031                         gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, vdtree_menu_position_cb, vd, 0, GDK_CURRENT_TIME);
1032
1033                         return TRUE;
1034                         break;
1035                 case GDK_plus:
1036                 case GDK_Right:
1037                 case GDK_KP_Add:
1038                         if (fd)
1039                                 {
1040                                 vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->path);
1041                                 vdtree_icon_set_by_iter(vd, &iter, vd->pf->open);
1042                                 }
1043                         break;
1044                 }
1045
1046         return FALSE;
1047 }
1048
1049 static gint vdtree_clicked_on_expander(GtkTreeView *treeview, GtkTreePath *tpath,
1050                                        GtkTreeViewColumn *column, gint x, gint y, gint *left_of_expander)
1051 {
1052         gint depth;
1053         gint size;
1054         gint sep;
1055         gint exp_width;
1056
1057         if (column != gtk_tree_view_get_expander_column(treeview)) return FALSE;
1058
1059         gtk_widget_style_get(GTK_WIDGET(treeview), "expander-size", &size, "horizontal-separator", &sep, NULL);
1060         depth = gtk_tree_path_get_depth(tpath);
1061
1062         exp_width = sep + size + sep;
1063
1064         if (x <= depth * exp_width)
1065                 {
1066                 if (left_of_expander) *left_of_expander = !(x >= (depth - 1) * exp_width);
1067                 return TRUE;
1068                 }
1069
1070         return FALSE;
1071 }
1072
1073 static gint vdtree_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1074 {
1075         ViewDir *vd = data;
1076         GtkTreePath *tpath;
1077         GtkTreeViewColumn *column;
1078         GtkTreeIter iter;
1079         NodeData *nd = NULL;
1080
1081         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1082                                           &tpath, &column, NULL, NULL))
1083                 {
1084                 GtkTreeModel *store;
1085                 gint left_of_expander;
1086
1087                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1088                 gtk_tree_model_get_iter(store, &iter, tpath);
1089                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
1090                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
1091
1092                 if (vdtree_clicked_on_expander(GTK_TREE_VIEW(widget), tpath, column, bevent->x, bevent->y, &left_of_expander))
1093                         {
1094                         vd->click_fd = NULL;
1095
1096                         /* clicking this region should automatically reveal an expander, if necessary
1097                          * treeview bug: the expander will not expand until a button_motion_event highlights it.
1098                          */
1099                         if (bevent->button == 1 &&
1100                             !left_of_expander &&
1101                             !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vd->view), tpath))
1102                                 {
1103                                 vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->path);
1104                                 vdtree_icon_set_by_iter(vd, &iter, vd->pf->open);
1105                                 }
1106
1107                         gtk_tree_path_free(tpath);
1108                         return FALSE;
1109                         }
1110
1111                 gtk_tree_path_free(tpath);
1112                 }
1113
1114         vd->click_fd = (nd) ? nd->fd : NULL;
1115         vd_color_set(vd, vd->click_fd, TRUE);
1116
1117         if (bevent->button == 3)
1118                 {
1119                 vd->popup = vd_pop_menu(vd, vd->click_fd);
1120                 gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL,
1121                                bevent->button, bevent->time);
1122                 }
1123
1124         return (bevent->button != 1);
1125 }
1126
1127 static gint vdtree_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1128 {
1129         ViewDir *vd = data;
1130         GtkTreePath *tpath;
1131         GtkTreeIter iter;
1132         NodeData *nd = NULL;
1133
1134         if (!vd->click_fd) return FALSE;
1135         vd_color_set(vd, vd->click_fd, FALSE);
1136
1137         if (bevent->button != 1) return TRUE;
1138
1139         if ((bevent->x != 0 || bevent->y != 0) &&
1140             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1141                                           &tpath, NULL, NULL, NULL))
1142                 {
1143                 GtkTreeModel *store;
1144
1145                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1146                 gtk_tree_model_get_iter(store, &iter, tpath);
1147                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
1148                 gtk_tree_path_free(tpath);
1149                 }
1150
1151         if (nd && vd->click_fd == nd->fd)
1152                 {
1153                 vdtree_select_row(vd, vd->click_fd);
1154                 }
1155
1156         return FALSE;
1157 }
1158
1159 static void vdtree_row_expanded(GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *tpath, gpointer data)
1160 {
1161         ViewDir *vd = data;
1162
1163         vdtree_populate_path_by_iter(vd, iter, FALSE, NULL);
1164         vdtree_icon_set_by_iter(vd, iter, vd->pf->open);
1165 }
1166
1167 static void vdtree_row_collapsed(GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *tpath, gpointer data)
1168 {
1169         ViewDir *vd = data;
1170
1171         vdtree_icon_set_by_iter(vd, iter, vd->pf->close);
1172 }
1173
1174 static gint vdtree_sort_cb(GtkTreeModel *store, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
1175 {
1176         NodeData *nda;
1177         NodeData *ndb;
1178
1179         gtk_tree_model_get(store, a, DIR_COLUMN_POINTER, &nda, -1);
1180         gtk_tree_model_get(store, b, DIR_COLUMN_POINTER, &ndb, -1);
1181
1182         return CASE_SORT(nda->fd->name, ndb->fd->name);
1183 }
1184
1185 /*
1186  *----------------------------------------------------------------------------
1187  * core
1188  *----------------------------------------------------------------------------
1189  */
1190
1191 static void vdtree_setup_root(ViewDir *vd)
1192 {
1193         const gchar *path = "/";
1194         FileData *fd;
1195
1196
1197         fd = file_data_new_simple(path);
1198         vdtree_add_by_data(vd, fd, NULL);
1199
1200         vdtree_expand_by_data(vd, fd, TRUE);
1201         vdtree_populate_path(vd, path, FALSE, FALSE);
1202 }
1203
1204 static void vdtree_activate_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *column, gpointer data)
1205 {
1206         ViewDir *vd = data;
1207         GtkTreeModel *store;
1208         GtkTreeIter iter;
1209         NodeData *nd;
1210
1211         store = gtk_tree_view_get_model(tview);
1212         gtk_tree_model_get_iter(store, &iter, tpath);
1213         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
1214
1215         vdtree_select_row(vd, nd->fd);
1216 }
1217
1218 static GdkColor *vdtree_color_shifted(GtkWidget *widget)
1219 {
1220         static GdkColor color;
1221         static GtkWidget *done = NULL;
1222
1223         if (done != widget)
1224                 {
1225                 GtkStyle *style;
1226
1227                 style = gtk_widget_get_style(widget);
1228                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1229                 shift_color(&color, -1, 0);
1230                 done = widget;
1231                 }
1232
1233         return &color;
1234 }
1235
1236 static void vdtree_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1237                             GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1238 {
1239         ViewDir *vd = data;
1240         gboolean set;
1241
1242         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
1243         g_object_set(G_OBJECT(cell),
1244                      "cell-background-gdk", vdtree_color_shifted(vd->view),
1245                      "cell-background-set", set, NULL);
1246 }
1247
1248 static gboolean vdtree_destroy_node_cb(GtkTreeModel *store, GtkTreePath *tpath, GtkTreeIter *iter, gpointer data)
1249 {
1250         NodeData *nd;
1251
1252         gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
1253         vdtree_node_free(nd);
1254
1255         return FALSE;
1256 }
1257
1258 static void vdtree_destroy_cb(GtkWidget *widget, gpointer data)
1259 {
1260         ViewDir *vd = data;
1261         GtkTreeModel *store;
1262
1263         vdtree_dnd_drop_expand_cancel(vd);
1264         vdtree_dnd_drop_scroll_cancel(vd);
1265         widget_auto_scroll_stop(vd->view);
1266
1267         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
1268         gtk_tree_model_foreach(store, vdtree_destroy_node_cb, vd);
1269 }
1270
1271 ViewDir *vdtree_new(ViewDir *vd, const gchar *path)
1272 {
1273         GtkTreeStore *store;
1274         GtkTreeSelection *selection;
1275         GtkTreeViewColumn *column;
1276         GtkCellRenderer *renderer;
1277
1278         vd->info = g_new0(ViewDirInfoTree, 1);
1279         vd->type = DIRVIEW_TREE;
1280         vd->widget_destroy_cb = vdtree_destroy_cb;
1281
1282         VDTREE_INFO(vd, drop_expand_id) = -1;
1283
1284         VDTREE_INFO(vd, busy_ref) = 0;
1285
1286         store = gtk_tree_store_new(4, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT);
1287         vd->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1288         g_object_unref(store);
1289
1290         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vd->view), FALSE);
1291         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vd->view), FALSE);
1292         gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(store), vdtree_sort_cb, vd, NULL);
1293         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
1294                                              GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
1295
1296         g_signal_connect(G_OBJECT(vd->view), "row_activated",
1297                          G_CALLBACK(vdtree_activate_cb), vd);
1298         g_signal_connect(G_OBJECT(vd->view), "row_expanded",
1299                          G_CALLBACK(vdtree_row_expanded), vd);
1300         g_signal_connect(G_OBJECT(vd->view), "row_collapsed",
1301                          G_CALLBACK(vdtree_row_collapsed), vd);
1302 #if 0
1303         g_signal_connect(G_OBJECT(store), "row_deleted",
1304                          G_CALLBACK(vdtree_row_deleted_cb), vd);
1305 #endif
1306
1307         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vd->view));
1308         gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1309         gtk_tree_selection_set_select_function(selection, vdtree_select_cb, vd, NULL);
1310
1311         column = gtk_tree_view_column_new();
1312         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1313
1314         renderer = gtk_cell_renderer_pixbuf_new();
1315         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1316         gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", DIR_COLUMN_ICON);
1317         gtk_tree_view_column_set_cell_data_func(column, renderer, vdtree_color_cb, vd, NULL);
1318
1319         renderer = gtk_cell_renderer_text_new();
1320         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1321         gtk_tree_view_column_add_attribute(column, renderer, "text", DIR_COLUMN_NAME);
1322         gtk_tree_view_column_set_cell_data_func(column, renderer, vdtree_color_cb, vd, NULL);
1323
1324         gtk_tree_view_append_column(GTK_TREE_VIEW(vd->view), column);
1325
1326         g_signal_connect(G_OBJECT(vd->view), "key_press_event",
1327                          G_CALLBACK(vdtree_press_key_cb), vd);
1328
1329         gtk_container_add(GTK_CONTAINER(vd->widget), vd->view);
1330         gtk_widget_show(vd->view);
1331
1332         vd->pf = folder_icons_new();
1333
1334         vdtree_setup_root(vd);
1335
1336         vdtree_dnd_init(vd);
1337
1338         g_signal_connect(G_OBJECT(vd->view), "button_press_event",
1339                          G_CALLBACK(vdtree_press_cb), vd);
1340         g_signal_connect(G_OBJECT(vd->view), "button_release_event",
1341                          G_CALLBACK(vdtree_release_cb), vd);
1342
1343         vdtree_set_path(vd, path);
1344
1345         return vd;
1346 }
1347
1348 #if 0
1349 void vdtree_set_click_func(ViewDir *vd,
1350                            void (*func)(ViewDir *vd, GdkEventButton *event, FileData *fd, gpointer), gpointer data)
1351 {
1352         if (!td) return;
1353         vd->click_func = func;
1354         vd->click_data = data;
1355 }
1356 #endif
1357
1358