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