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