Revert "FIXME: this can be rather slow and blocks until the size is known"
[geeqie.git] / src / view-dir-tree.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "view-dir-tree.h"
24
25 #include "filedata.h"
26 #include "ui-fileops.h"
27 #include "ui-tree-edit.h"
28 #include "view-dir.h"
29
30 #define VDTREE(_vd_) ((ViewDirInfoTree *)(_vd_->info))
31
32
33 typedef struct _PathData PathData;
34 struct _PathData
35 {
36         gchar *name;
37         FileData *node;
38 };
39
40
41 static void vdtree_row_expanded(GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *tpath, gpointer data);
42
43
44 /*
45  *----------------------------------------------------------------------------
46  * utils
47  *----------------------------------------------------------------------------
48  */
49
50 static void set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
51 {
52         GdkCursor *cursor = NULL;
53
54         if (!widget || !gtk_widget_get_window(widget)) return;
55
56         if (cursor_type > -1) cursor = gdk_cursor_new(cursor_type);
57         gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
58         if (cursor) g_object_unref(G_OBJECT(cursor));
59         gdk_flush();
60 }
61
62 static void vdtree_busy_push(ViewDir *vd)
63 {
64         if (VDTREE(vd)->busy_ref == 0) set_cursor(vd->view, GDK_WATCH);
65         VDTREE(vd)->busy_ref++;
66 }
67
68 static void vdtree_busy_pop(ViewDir *vd)
69 {
70         if (VDTREE(vd)->busy_ref == 1) set_cursor(vd->view, GDK_CURSOR_IS_PIXMAP);
71         if (VDTREE(vd)->busy_ref > 0) VDTREE(vd)->busy_ref--;
72 }
73
74 gboolean vdtree_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter, GtkTreeIter *parent)
75 {
76         GtkTreeModel *store;
77         gboolean valid;
78
79         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
80         if (parent)
81                 {
82                 valid = gtk_tree_model_iter_children(store, iter, parent);
83                 }
84         else
85                 {
86                 valid = gtk_tree_model_get_iter_first(store, iter);
87                 }
88         while (valid)
89                 {
90                 NodeData *nd;
91                 GtkTreeIter found;
92
93                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, DIR_COLUMN_POINTER, &nd, -1);
94                 if (nd->fd == fd) return TRUE;
95
96                 if (vdtree_find_row(vd, fd, &found, iter))
97                         {
98                         memcpy(iter, &found, sizeof(found));
99                         return TRUE;
100                         }
101
102                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), iter);
103                 }
104
105         return FALSE;
106 }
107
108 static void vdtree_icon_set_by_iter(ViewDir *vd, GtkTreeIter *iter, GdkPixbuf *pixbuf)
109 {
110         GtkTreeModel *store;
111         GdkPixbuf *old;
112
113         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
114         gtk_tree_model_get(store, iter, DIR_COLUMN_ICON, &old, -1);
115         if (old != vd->pf->deny)
116                 {
117                 gtk_tree_store_set(GTK_TREE_STORE(store), iter, DIR_COLUMN_ICON, pixbuf, -1);
118                 }
119 }
120
121 static void vdtree_expand_by_iter(ViewDir *vd, GtkTreeIter *iter, gboolean expand)
122 {
123         GtkTreeModel *store;
124         GtkTreePath *tpath;
125         NodeData *nd;
126         FileData *fd = NULL;
127
128         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
129         tpath = gtk_tree_model_get_path(store, iter);
130
131         if (expand)
132                 {
133                 /* block signal handler, icon is set here, the caller of vdtree_expand_by_iter must make sure
134                    that the iter is populated */
135                 g_signal_handlers_block_by_func(G_OBJECT(vd->view), (gpointer)vdtree_row_expanded, vd);
136                 gtk_tree_view_expand_row(GTK_TREE_VIEW(vd->view), tpath, FALSE);
137                 gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
138                 fd = (nd) ? nd->fd : NULL;
139
140                 if (fd && islink(fd->path))
141                         {
142                         vdtree_icon_set_by_iter(vd, iter, vd->pf->link);
143                         }
144                 else
145                         {
146                         vdtree_icon_set_by_iter(vd, iter, vd->pf->open);
147                         }
148
149                 g_signal_handlers_unblock_by_func(G_OBJECT(vd->view), (gpointer)vdtree_row_expanded, vd);
150                 }
151         else
152                 {
153                 /* signal handler vdtree_row_collapsed is called, it updates the icon */
154                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(vd->view), tpath);
155                 }
156         gtk_tree_path_free(tpath);
157 }
158
159 static void vdtree_expand_by_data(ViewDir *vd, FileData *fd, gboolean expand)
160 {
161         GtkTreeIter iter;
162
163         if (vd_find_row(vd, fd, &iter))
164                 {
165                 vdtree_expand_by_iter(vd, &iter, expand);
166                 }
167 }
168
169 static void vdtree_node_free(NodeData *nd)
170 {
171         if (!nd) return;
172
173         if (nd->fd) file_data_unref(nd->fd);
174         g_free(nd);
175 }
176
177 /*
178  *----------------------------------------------------------------------------
179  * dnd
180  *----------------------------------------------------------------------------
181  */
182
183 static gboolean vdtree_dnd_drop_expand_cb(gpointer data)
184 {
185         ViewDir *vd = static_cast<ViewDir *>(data);
186         GtkTreeIter iter;
187
188         if (vd->drop_fd && vd_find_row(vd, vd->drop_fd, &iter))
189                 {
190                 vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->dir_fd);
191                 vdtree_expand_by_data(vd, vd->drop_fd, TRUE);
192                 }
193
194         VDTREE(vd)->drop_expand_id = 0;
195         return FALSE;
196 }
197
198 static void vdtree_dnd_drop_expand_cancel(ViewDir *vd)
199 {
200         if (VDTREE(vd)->drop_expand_id)
201                 {
202                 g_source_remove(VDTREE(vd)->drop_expand_id);
203                 VDTREE(vd)->drop_expand_id = 0;
204                 }
205 }
206
207 static void vdtree_dnd_drop_expand(ViewDir *vd)
208 {
209         vdtree_dnd_drop_expand_cancel(vd);
210         VDTREE(vd)->drop_expand_id = g_timeout_add(1000, vdtree_dnd_drop_expand_cb, vd);
211 }
212
213 /*
214  *----------------------------------------------------------------------------
215  * parts lists
216  *----------------------------------------------------------------------------
217  */
218
219 static GList *parts_list(const gchar *path)
220 {
221         GList *list = NULL;
222         const gchar *strb, *strp;
223         gint l;
224
225         strp = path;
226
227         if (*strp != G_DIR_SEPARATOR) return NULL;
228
229         strp++;
230         strb = strp;
231         l = 0;
232
233         while (*strp != '\0')
234                 {
235                 if (*strp == G_DIR_SEPARATOR)
236                         {
237                         if (l > 0) list = g_list_prepend(list, g_strndup(strb, l));
238                         strp++;
239                         strb = strp;
240                         l = 0;
241                         }
242                 else
243                         {
244                         strp++;
245                         l++;
246                         }
247                 }
248         if (l > 0) list = g_list_prepend(list, g_strndup(strb, l));
249
250         list = g_list_reverse(list);
251
252         list = g_list_prepend(list, g_strdup(G_DIR_SEPARATOR_S));
253
254         return list;
255 }
256
257 static void parts_list_free(GList *list)
258 {
259         GList *work = list;
260         while (work)
261                 {
262                 PathData *pd = static_cast<PathData *>(work->data);
263                 g_free(pd->name);
264                 g_free(pd);
265                 work = work->next;
266                 }
267
268         g_list_free(list);
269 }
270
271 static GList *parts_list_add_node_points(ViewDir *vd, GList *list)
272 {
273         GList *work;
274         GtkTreeModel *store;
275         GtkTreeIter iter;
276         gboolean valid;
277
278         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
279         valid = gtk_tree_model_get_iter_first(store, &iter);
280
281         work = list;
282         while (work)
283                 {
284                 PathData *pd;
285                 FileData *fd = NULL;
286
287                 pd = g_new0(PathData, 1);
288                 pd->name = static_cast<gchar *>(work->data);
289
290                 while (valid && !fd)
291                         {
292                         NodeData *nd;
293
294                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
295                         if (nd->fd && strcmp(nd->fd->name, pd->name) == 0)
296                                 {
297                                 fd = nd->fd;
298                                 }
299                         else
300                                 {
301                                 valid = gtk_tree_model_iter_next(store, &iter);
302                                 }
303                         }
304
305                 pd->node = fd;
306                 work->data = pd;
307
308                 if (fd)
309                         {
310                         GtkTreeIter parent;
311                         memcpy(&parent, &iter, sizeof(parent));
312                         valid = gtk_tree_model_iter_children(store, &iter, &parent);
313                         }
314
315                 work = work->next;
316                 }
317
318         return list;
319 }
320
321
322 /*
323  *----------------------------------------------------------------------------
324  * node traversal, management
325  *----------------------------------------------------------------------------
326  */
327
328 static gboolean vdtree_find_iter_by_data(ViewDir *vd, GtkTreeIter *parent, NodeData *nd, GtkTreeIter *iter)
329 {
330         GtkTreeModel *store;
331
332         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
333         if (!nd || !gtk_tree_model_iter_children(store, iter, parent)) return -1;
334         do      {
335                 NodeData *cnd;
336
337                 gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &cnd, -1);
338                 if (cnd == nd) return TRUE;
339                 } while (gtk_tree_model_iter_next(store, iter));
340
341         return FALSE;
342 }
343
344 static NodeData *vdtree_find_iter_by_name(ViewDir *vd, GtkTreeIter *parent, const gchar *name, GtkTreeIter *iter)
345 {
346         GtkTreeModel *store;
347
348         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
349         if (!name || !gtk_tree_model_iter_children(store, iter, parent)) return NULL;
350         do      {
351                 NodeData *nd;
352
353                 gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
354                 if (nd && strcmp(nd->fd->name, name) == 0) return nd;
355                 } while (gtk_tree_model_iter_next(store, iter));
356
357         return NULL;
358 }
359
360 static NodeData *vdtree_find_iter_by_fd(ViewDir *vd, GtkTreeIter *parent, FileData *fd, GtkTreeIter *iter)
361 {
362         GtkTreeModel *store;
363
364         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
365         if (!fd || !gtk_tree_model_iter_children(store, iter, parent)) return NULL;
366         do      {
367                 NodeData *nd;
368
369                 gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
370                 if (nd && nd->fd == fd) return nd;
371                 } while (gtk_tree_model_iter_next(store, iter));
372
373         return NULL;
374 }
375
376 static void vdtree_add_by_data(ViewDir *vd, FileData *fd, GtkTreeIter *parent)
377 {
378         GtkTreeStore *store;
379         GtkTreeIter child;
380         NodeData *nd;
381         GdkPixbuf *pixbuf;
382         NodeData *end;
383         GtkTreeIter empty;
384         gchar *link = NULL;
385
386         if (!fd) return;
387
388         if (access_file(fd->path, R_OK | X_OK))
389                 {
390                 if (islink(fd->path))
391                         {
392                         pixbuf = vd->pf->link;
393                         }
394                 else if (!access_file(fd->path, W_OK) )
395                         {
396                         pixbuf = vd->pf->read_only;
397                         }
398                 else
399                         {
400                         pixbuf = vd->pf->close;
401                         }
402                 }
403         else
404                 {
405                 pixbuf = vd->pf->deny;
406                 }
407
408         nd = g_new0(NodeData, 1);
409         nd->fd = fd;
410         nd->version = fd->version;
411         nd->expanded = FALSE;
412         nd->last_update = time(NULL);
413
414         if (islink(fd->path))
415                 {
416                 link = realpath(fd->path, NULL);
417                 }
418         else
419                 {
420                 link = NULL;
421                 }
422
423         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view)));
424         gtk_tree_store_append(store, &child, parent);
425         gtk_tree_store_set(store, &child, DIR_COLUMN_POINTER, nd,
426                                          DIR_COLUMN_ICON, pixbuf,
427                                          DIR_COLUMN_NAME, nd->fd->name,
428                                          DIR_COLUMN_LINK, link,
429                                          DIR_COLUMN_COLOR, FALSE, -1);
430
431         /* all nodes are created with an "empty" node, so that the expander is shown
432          * this is removed when the child is populated */
433         end = g_new0(NodeData, 1);
434         end->fd = NULL;
435         end->expanded = TRUE;
436
437         gtk_tree_store_append(store, &empty, &child);
438         gtk_tree_store_set(store, &empty, DIR_COLUMN_POINTER, end,
439                                           DIR_COLUMN_NAME, "empty", -1);
440
441         if (parent)
442                 {
443                 NodeData *pnd;
444                 GtkTreePath *tpath;
445
446                 gtk_tree_model_get(GTK_TREE_MODEL(store), parent, DIR_COLUMN_POINTER, &pnd, -1);
447                 tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), parent);
448                 if (options->tree_descend_subdirs &&
449                     gtk_tree_view_row_expanded(GTK_TREE_VIEW(vd->view), tpath) &&
450                     !nd->expanded)
451                         {
452                         vdtree_populate_path_by_iter(vd, &child, FALSE, vd->dir_fd);
453                         }
454                 gtk_tree_path_free(tpath);
455                 }
456
457         g_free(link);
458 }
459
460 gboolean vdtree_populate_path_by_iter(ViewDir *vd, GtkTreeIter *iter, gboolean force, FileData *target_fd)
461 {
462         GtkTreeModel *store;
463         GList *list;
464         GList *work;
465         GList *old;
466         time_t current_time;
467         GtkTreeIter child;
468         NodeData *nd;
469         gboolean add_hidden = FALSE;
470         gchar *link = NULL;
471
472         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
473         gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
474
475         if (!nd) return FALSE;
476
477         current_time = time(NULL);
478
479         if (nd->expanded)
480                 {
481                 if (!nd->fd || !isdir(nd->fd->path))
482                         {
483                         if (vd->click_fd == nd->fd) vd->click_fd = NULL;
484                         if (vd->drop_fd == nd->fd) vd->drop_fd = NULL;
485                         gtk_tree_store_remove(GTK_TREE_STORE(store), iter);
486                         vdtree_node_free(nd);
487                         return FALSE;
488                         }
489                 if (!force && current_time - nd->last_update < 2)
490                         {
491                         DEBUG_1("Too frequent update of %s", nd->fd->path);
492                         return TRUE;
493                         }
494                 file_data_check_changed_files(nd->fd); /* make sure we have recent info */
495                 }
496
497         /* when hidden files are not enabled, and the user enters a hidden path,
498          * allow the tree to display that path by specifically inserting the hidden entries
499          */
500         if (!options->file_filter.show_hidden_files &&
501             target_fd &&
502             strncmp(nd->fd->path, target_fd->path, strlen(nd->fd->path)) == 0)
503                 {
504                 gint n;
505
506                 n = strlen(nd->fd->path);
507                 if (target_fd->path[n] == G_DIR_SEPARATOR && target_fd->path[n+1] == '.')
508                         add_hidden = TRUE;
509                 }
510
511         if (nd->expanded && (!force && !add_hidden) && nd->fd->version == nd->version)
512                 return TRUE;
513
514         vdtree_busy_push(vd);
515
516         filelist_read(nd->fd, NULL, &list);
517
518         if (add_hidden)
519                 {
520                 gint n;
521                 gchar *name8;
522
523                 n = strlen(nd->fd->path) + 1;
524
525                 while (target_fd->path[n] != '\0' && target_fd->path[n] != G_DIR_SEPARATOR) n++;
526                 name8 = g_strndup(target_fd->path, n);
527
528                 if (isdir(name8))
529                         {
530                         list = g_list_prepend(list, file_data_new_dir(name8));
531                         }
532
533                 g_free(name8);
534                 }
535
536         old = NULL;
537         if (gtk_tree_model_iter_children(store, &child, iter))
538                 {
539                 do      {
540                         NodeData *cnd;
541
542                         gtk_tree_model_get(store, &child, DIR_COLUMN_POINTER, &cnd, -1);
543                         old = g_list_prepend(old, cnd);
544                         } while (gtk_tree_model_iter_next(store, &child));
545                 }
546
547         work = list;
548         while (work)
549                 {
550                 FileData *fd;
551
552                 fd = static_cast<FileData *>(work->data);
553                 work = work->next;
554
555                 if (strcmp(fd->name, ".") == 0 || strcmp(fd->name, "..") == 0)
556                         {
557                         file_data_unref(fd);
558                         }
559                 else
560                         {
561                         NodeData *cnd;
562
563                         cnd = vdtree_find_iter_by_fd(vd, iter, fd, &child);
564                         if (cnd)
565                                 {
566                                 if (cnd->expanded && cnd->version != fd->version)
567                                         {
568                                         vdtree_populate_path_by_iter(vd, &child, FALSE, target_fd);
569                                         }
570
571                                 gtk_tree_store_set(GTK_TREE_STORE(store), &child, DIR_COLUMN_NAME, fd->name, -1);
572
573                                 if (islink(fd->path))
574                                         {
575                                         link = realpath(fd->path, NULL);
576                                         }
577                                 else
578                                         {
579                                         link = NULL;
580                                         }
581
582                                 gtk_tree_store_set(GTK_TREE_STORE(store), &child, DIR_COLUMN_LINK, link, -1);
583
584                                 cnd->version = fd->version;
585                                 old = g_list_remove(old, cnd);
586                                 file_data_unref(fd);
587                                 }
588                         else
589                                 {
590                                 vdtree_add_by_data(vd, fd, iter);
591                                 }
592                         }
593                 }
594
595         work = old;
596         while (work)
597                 {
598                 NodeData *cnd = static_cast<NodeData *>(work->data);
599                 work = work->next;
600
601                 if (vd->click_fd == cnd->fd) vd->click_fd = NULL;
602                 if (vd->drop_fd == cnd->fd) vd->drop_fd = NULL;
603
604                 if (vdtree_find_iter_by_data(vd, iter, cnd, &child))
605                         {
606                         gtk_tree_store_remove(GTK_TREE_STORE(store), &child);
607                         vdtree_node_free(cnd);
608                         }
609                 }
610
611         g_list_free(old);
612         g_list_free(list);
613
614         vdtree_busy_pop(vd);
615
616         nd->expanded = TRUE;
617         nd->last_update = current_time;
618
619         g_free(link);
620
621         return TRUE;
622 }
623
624 FileData *vdtree_populate_path(ViewDir *vd, FileData *target_fd, gboolean expand, gboolean force)
625 {
626         GList *list;
627         GList *work;
628         FileData *fd = NULL;
629
630         if (!target_fd) return NULL;
631
632         vdtree_busy_push(vd);
633
634         list = parts_list(target_fd->path);
635         list = parts_list_add_node_points(vd, list);
636
637         work = list;
638         while (work)
639                 {
640                 PathData *pd = static_cast<PathData *>(work->data);
641                 if (pd->node == NULL)
642                         {
643                         PathData *parent_pd;
644                         GtkTreeIter parent_iter;
645                         GtkTreeIter iter;
646                         NodeData *nd;
647
648                         if (work == list)
649                                 {
650                                 /* should not happen */
651                                 log_printf("vdtree warning, root node not found\n");
652                                 parts_list_free(list);
653                                 vdtree_busy_pop(vd);
654                                 return NULL;
655                                 }
656
657                         parent_pd = static_cast<PathData *>(work->prev->data);
658
659                         if (!vd_find_row(vd, parent_pd->node, &parent_iter) ||
660                             !vdtree_populate_path_by_iter(vd, &parent_iter, force, target_fd) ||
661                             (nd = vdtree_find_iter_by_name(vd, &parent_iter, pd->name, &iter)) == NULL)
662                                 {
663                                 log_printf("vdtree warning, aborted at %s\n", parent_pd->name);
664                                 parts_list_free(list);
665                                 vdtree_busy_pop(vd);
666                                 return NULL;
667                                 }
668
669                         pd->node = nd->fd;
670
671                         if (pd->node)
672                                 {
673                                 if (expand)
674                                         {
675                                         vdtree_expand_by_iter(vd, &parent_iter, TRUE);
676                                         vdtree_expand_by_iter(vd, &iter, TRUE);
677                                         }
678                                 vdtree_populate_path_by_iter(vd, &iter, force, target_fd);
679                                 }
680                         }
681                 else
682                         {
683                         GtkTreeIter iter;
684
685                         if (vd_find_row(vd, pd->node, &iter))
686                                 {
687                                 if (expand) vdtree_expand_by_iter(vd, &iter, TRUE);
688                                 vdtree_populate_path_by_iter(vd, &iter, force, target_fd);
689                                 }
690                         }
691
692                 work = work->next;
693                 }
694
695         work = g_list_last(list);
696         if (work)
697                 {
698                 PathData *pd = static_cast<PathData *>(work->data);
699                 fd = pd->node;
700                 }
701         parts_list_free(list);
702
703         vdtree_busy_pop(vd);
704
705         return fd;
706 }
707
708 /*
709  *----------------------------------------------------------------------------
710  * access
711  *----------------------------------------------------------------------------
712  */
713
714 static gboolean selection_is_ok = FALSE;
715
716 static gboolean vdtree_select_cb(GtkTreeSelection *UNUSED(selection), GtkTreeModel *UNUSED(store), GtkTreePath *UNUSED(tpath), gboolean UNUSED(path_currently_selected), gpointer UNUSED(data))
717 {
718         return selection_is_ok;
719 }
720
721 gboolean vdtree_set_fd(ViewDir *vd, FileData *dir_fd)
722 {
723         FileData *fd;
724         GtkTreeIter iter;
725
726         if (!dir_fd) return FALSE;
727         if (vd->dir_fd == dir_fd) return TRUE;
728
729         file_data_unref(vd->dir_fd);
730         vd->dir_fd = file_data_ref(dir_fd);;
731
732         fd = vdtree_populate_path(vd, vd->dir_fd, TRUE, FALSE);
733
734         if (!fd) return FALSE;
735
736         if (vd_find_row(vd, fd, &iter))
737                 {
738                 GtkTreeModel *store;
739                 GtkTreePath *tpath, *old_tpath;
740                 GtkTreeSelection *selection;
741
742                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
743
744                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vd->view));
745
746                 /* hack, such that selection is only allowed to be changed from here */
747                 selection_is_ok = TRUE;
748                 gtk_tree_selection_select_iter(selection, &iter);
749                 selection_is_ok = FALSE;
750
751                 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vd->view), &old_tpath, NULL);
752                 tpath = gtk_tree_model_get_path(store, &iter);
753
754                 if (!old_tpath || gtk_tree_path_compare(tpath, old_tpath) != 0)
755                         {
756                         /* setting the cursor scrolls the view; do not do that unless it is necessary */
757                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(vd->view), tpath, NULL, FALSE);
758
759                         /* gtk_tree_view_set_cursor scrolls the window itself, but it sometimes
760                            does not work (switch from dir_list to dir_tree) */
761                         tree_view_row_make_visible(GTK_TREE_VIEW(vd->view), &iter, TRUE);
762                         }
763                 gtk_tree_path_free(tpath);
764                 gtk_tree_path_free(old_tpath);
765                 }
766
767         return TRUE;
768 }
769
770 void vdtree_refresh(ViewDir *vd)
771 {
772         vdtree_populate_path(vd, vd->dir_fd, FALSE, TRUE);
773 }
774
775 const gchar *vdtree_row_get_path(ViewDir *UNUSED(vd), gint UNUSED(row))
776 {
777 /** @FIXME no get row path */
778         log_printf("FIXME: no get row path\n");
779         return NULL;
780 }
781
782 /*
783  *----------------------------------------------------------------------------
784  * callbacks
785  *----------------------------------------------------------------------------
786  */
787
788 gboolean vdtree_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
789 {
790         ViewDir *vd = static_cast<ViewDir *>(data);
791         GtkTreePath *tpath;
792         GtkTreeIter iter;
793         FileData *fd = NULL;
794
795         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vd->view), &tpath, NULL);
796         if (tpath)
797                 {
798                 GtkTreeModel *store;
799                 NodeData *nd;
800
801                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
802                 gtk_tree_model_get_iter(store, &iter, tpath);
803                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
804
805                 gtk_tree_path_free(tpath);
806
807                 fd = (nd) ? nd->fd : NULL;
808                 }
809
810         switch (event->keyval)
811                 {
812                 case GDK_KEY_Menu:
813                         vd->click_fd = fd;
814                         vd_color_set(vd, vd->click_fd, TRUE);
815
816                         vd->popup = vd_pop_menu(vd, vd->click_fd);
817                         gtk_menu_popup_at_pointer(GTK_MENU(vd->popup), NULL);
818
819                         return TRUE;
820                         break;
821                 case GDK_KEY_plus:
822                 case GDK_KEY_Right:
823                 case GDK_KEY_KP_Add:
824                         if (fd)
825                                 {
826                                 vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->dir_fd);
827
828                                 if (islink(fd->path))
829                                         {
830                                         vdtree_icon_set_by_iter(vd, &iter, vd->pf->link);
831                                         }
832                                 else
833                                         {
834                                         vdtree_icon_set_by_iter(vd, &iter, vd->pf->open);
835                                         }
836                                 }
837                         break;
838                 }
839
840         return FALSE;
841 }
842
843 static gboolean vdtree_clicked_on_expander(GtkTreeView *treeview, GtkTreePath *tpath,
844                                            GtkTreeViewColumn *column, gint x, gint UNUSED(y), gint *left_of_expander)
845 {
846         gint depth;
847         gint size;
848         gint sep;
849         gint exp_width;
850
851         if (column != gtk_tree_view_get_expander_column(treeview)) return FALSE;
852
853         gtk_widget_style_get(GTK_WIDGET(treeview), "expander-size", &size, "horizontal-separator", &sep, NULL);
854         depth = gtk_tree_path_get_depth(tpath);
855
856         exp_width = sep + size + sep;
857
858         if (x <= depth * exp_width)
859                 {
860                 if (left_of_expander) *left_of_expander = !(x >= (depth - 1) * exp_width);
861                 return TRUE;
862                 }
863
864         return FALSE;
865 }
866
867 gboolean vdtree_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
868 {
869         ViewDir *vd = static_cast<ViewDir *>(data);
870         GtkTreePath *tpath;
871         GtkTreeViewColumn *column;
872         GtkTreeIter iter;
873         NodeData *nd = NULL;
874         FileData *fd;
875
876         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
877                                           &tpath, &column, NULL, NULL))
878                 {
879                 GtkTreeModel *store;
880                 gint left_of_expander;
881
882                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
883                 gtk_tree_model_get_iter(store, &iter, tpath);
884                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
885                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
886
887                 if (vdtree_clicked_on_expander(GTK_TREE_VIEW(widget), tpath, column, bevent->x, bevent->y, &left_of_expander))
888                         {
889                         vd->click_fd = NULL;
890
891                         /* clicking this region should automatically reveal an expander, if necessary
892                          * treeview bug: the expander will not expand until a button_motion_event highlights it.
893                          */
894                         if (bevent->button == MOUSE_BUTTON_LEFT &&
895                             !left_of_expander &&
896                             !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vd->view), tpath))
897                                 {
898                                 vdtree_populate_path_by_iter(vd, &iter, FALSE, vd->dir_fd);
899
900                                 fd = (nd) ? nd->fd : NULL;
901                                 if (fd && islink(fd->path))
902                                         {
903                                         vdtree_icon_set_by_iter(vd, &iter, vd->pf->link);
904                                         }
905                                 else
906                                         {
907                                         vdtree_icon_set_by_iter(vd, &iter, vd->pf->open);
908                                         }
909                                 }
910
911                         gtk_tree_path_free(tpath);
912                         return FALSE;
913                         }
914
915                 gtk_tree_path_free(tpath);
916                 }
917
918         vd->click_fd = (nd) ? nd->fd : NULL;
919         vd_color_set(vd, vd->click_fd, TRUE);
920
921         if (bevent->button == MOUSE_BUTTON_RIGHT)
922                 {
923                 vd->popup = vd_pop_menu(vd, vd->click_fd);
924                 gtk_menu_popup_at_pointer(GTK_MENU(vd->popup), NULL);
925                 }
926
927         return (bevent->button != MOUSE_BUTTON_LEFT);
928 }
929
930 static void vdtree_row_expanded(GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *tpath, gpointer data)
931 {
932         ViewDir *vd = static_cast<ViewDir *>(data);
933         GtkTreeModel *store;
934         NodeData *nd = NULL;
935         FileData *fd;
936
937         gtk_tree_view_set_tooltip_column(treeview, DIR_COLUMN_LINK);
938
939         vdtree_populate_path_by_iter(vd, iter, FALSE, NULL);
940         store = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
941
942         gtk_tree_model_get_iter(store, iter, tpath);
943         gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
944
945         fd = (nd) ? nd->fd : NULL;
946         if (fd && islink(fd->path))
947                 {
948                 vdtree_icon_set_by_iter(vd, iter, vd->pf->link);
949                 }
950         else
951                 {
952                 vdtree_icon_set_by_iter(vd, iter, vd->pf->open);
953                 }
954 }
955
956 static void vdtree_row_collapsed(GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *tpath, gpointer data)
957 {
958         ViewDir *vd = static_cast<ViewDir *>(data);
959         GtkTreeModel *store;
960         NodeData *nd = NULL;
961         FileData *fd;
962
963         vdtree_populate_path_by_iter(vd, iter, FALSE, NULL);
964         store = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
965
966         gtk_tree_model_get_iter(store, iter, tpath);
967         gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
968
969         fd = (nd) ? nd->fd : NULL;
970         if (fd && islink(fd->path))
971                 {
972                 vdtree_icon_set_by_iter(vd, iter, vd->pf->link);
973                 }
974         else
975                 {
976                 vdtree_icon_set_by_iter(vd, iter, vd->pf->close);
977                 }
978 }
979
980 static gint vdtree_sort_cb(GtkTreeModel *store, GtkTreeIter *a, GtkTreeIter *b, gpointer UNUSED(data))
981 {
982         NodeData *nda;
983         NodeData *ndb;
984
985         gtk_tree_model_get(store, a, DIR_COLUMN_POINTER, &nda, -1);
986         gtk_tree_model_get(store, b, DIR_COLUMN_POINTER, &ndb, -1);
987
988         if (!nda->fd && !ndb->fd) return 0;
989         if (!nda->fd) return 1;
990         if (!ndb->fd) return -1;
991
992         if (options->file_sort.case_sensitive)
993                 return strcmp(nda->fd->collate_key_name, ndb->fd->collate_key_name);
994         else
995                 return strcmp(nda->fd->collate_key_name_nocase, ndb->fd->collate_key_name_nocase);
996 }
997
998 /*
999  *----------------------------------------------------------------------------
1000  * core
1001  *----------------------------------------------------------------------------
1002  */
1003
1004 static void vdtree_setup_root(ViewDir *vd)
1005 {
1006         const gchar *path = G_DIR_SEPARATOR_S;
1007         FileData *fd;
1008
1009
1010         fd = file_data_new_dir(path);
1011         vdtree_add_by_data(vd, fd, NULL);
1012
1013         vdtree_expand_by_data(vd, fd, TRUE);
1014         vdtree_populate_path(vd, fd, FALSE, FALSE);
1015 }
1016
1017 static gboolean vdtree_destroy_node_cb(GtkTreeModel *store, GtkTreePath *UNUSED(tpath), GtkTreeIter *iter, gpointer UNUSED(data))
1018 {
1019         NodeData *nd;
1020
1021         gtk_tree_model_get(store, iter, DIR_COLUMN_POINTER, &nd, -1);
1022         vdtree_node_free(nd);
1023
1024         return FALSE;
1025 }
1026
1027 void vdtree_destroy_cb(GtkWidget *UNUSED(widget), gpointer data)
1028 {
1029         ViewDir *vd = static_cast<ViewDir *>(data);
1030         GtkTreeModel *store;
1031
1032         vdtree_dnd_drop_expand_cancel(vd);
1033         vd_dnd_drop_scroll_cancel(vd);
1034         widget_auto_scroll_stop(vd->view);
1035
1036         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
1037         gtk_tree_model_foreach(store, vdtree_destroy_node_cb, vd);
1038 }
1039
1040 ViewDir *vdtree_new(ViewDir *vd, FileData *UNUSED(dir_fd))
1041 {
1042         GtkTreeStore *store;
1043         GtkTreeSelection *selection;
1044         GtkTreeViewColumn *column;
1045         GtkCellRenderer *renderer;
1046
1047         vd->info = g_new0(ViewDirInfoTree, 1);
1048
1049         vd->type = DIRVIEW_TREE;
1050
1051         vd->dnd_drop_leave_func = vdtree_dnd_drop_expand_cancel;
1052         vd->dnd_drop_update_func = vdtree_dnd_drop_expand;
1053
1054         store = gtk_tree_store_new(6, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);
1055         vd->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1056         g_object_unref(store);
1057
1058         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vd->view), FALSE);
1059         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vd->view), FALSE);
1060         gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(store), vdtree_sort_cb, vd, NULL);
1061         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
1062                                              GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
1063
1064         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vd->view));
1065         gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1066         gtk_tree_selection_set_select_function(selection, vdtree_select_cb, vd, NULL);
1067
1068         column = gtk_tree_view_column_new();
1069         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1070
1071         renderer = gtk_cell_renderer_pixbuf_new();
1072         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1073         gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", DIR_COLUMN_ICON);
1074         gtk_tree_view_column_set_cell_data_func(column, renderer, vd_color_cb, vd, NULL);
1075
1076         renderer = gtk_cell_renderer_text_new();
1077         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1078         gtk_tree_view_column_add_attribute(column, renderer, "text", DIR_COLUMN_NAME);
1079         gtk_tree_view_column_set_cell_data_func(column, renderer, vd_color_cb, vd, NULL);
1080
1081         gtk_tree_view_append_column(GTK_TREE_VIEW(vd->view), column);
1082
1083         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vd->view), DIR_COLUMN_LINK);
1084
1085         vdtree_setup_root(vd);
1086
1087         g_signal_connect(G_OBJECT(vd->view), "row_expanded",
1088                          G_CALLBACK(vdtree_row_expanded), vd);
1089         g_signal_connect(G_OBJECT(vd->view), "row_collapsed",
1090                          G_CALLBACK(vdtree_row_collapsed), vd);
1091
1092         return vd;
1093 }
1094 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */