clang-tidy: readability-non-const-parameter
[geeqie.git] / src / bar-keywords.cc
1 /*
2  * Copyright (C) 2004 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 "bar-keywords.h"
24
25 #include "bar.h"
26 #include "dnd.h"
27 #include "filedata.h"
28 #include "layout.h"
29 #include "metadata.h"
30 #include "misc.h"
31 #include "rcfile.h"
32 #include "secure-save.h"
33 #include "ui-fileops.h"
34 #include "ui-menu.h"
35 #include "ui-misc.h"
36 #include "ui-utildlg.h"
37
38 static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data);
39
40 static void autocomplete_keywords_list_load(const gchar *path);
41 static GtkListStore *keyword_store = nullptr;
42 static gboolean autocomplete_keywords_list_save(gchar *path);
43 static gboolean autocomplete_activate_cb(GtkWidget *widget, gpointer data);
44
45 /*
46  *-------------------------------------------------------------------
47  * keyword / comment utils
48  *-------------------------------------------------------------------
49  */
50
51
52 GList *keyword_list_pull(GtkWidget *text_widget)
53 {
54         GList *list;
55         gchar *text;
56
57         text = text_widget_text_pull(text_widget);
58         list = string_to_keywords_list(text);
59
60         g_free(text);
61
62         return list;
63 }
64
65 static GList *keyword_list_pull_selected(GtkWidget *text_widget)
66 {
67         GList *list;
68         gchar *text;
69
70         text = text_widget_text_pull_selected(text_widget);
71         list = string_to_keywords_list(text);
72
73         g_free(text);
74
75         return list;
76 }
77
78 /* the "changed" signal should be blocked before calling this */
79 static void keyword_list_push(GtkWidget *textview, GList *list)
80 {
81         GtkTextBuffer *buffer;
82         GtkTextIter start, end;
83
84         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
85         gtk_text_buffer_get_bounds(buffer, &start, &end);
86         gtk_text_buffer_delete(buffer, &start, &end);
87
88         while (list)
89                 {
90                 auto word = static_cast<const gchar *>(list->data);
91                 GtkTextIter iter;
92
93                 gtk_text_buffer_get_end_iter(buffer, &iter);
94                 if (word) gtk_text_buffer_insert(buffer, &iter, word, -1);
95                 gtk_text_buffer_get_end_iter(buffer, &iter);
96                 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
97
98                 list = list->next;
99                 }
100 }
101
102
103 /*
104  *-------------------------------------------------------------------
105  * info bar
106  *-------------------------------------------------------------------
107  */
108
109
110 enum {
111         FILTER_KEYWORD_COLUMN_TOGGLE = 0,
112         FILTER_KEYWORD_COLUMN_MARK,
113         FILTER_KEYWORD_COLUMN_NAME,
114         FILTER_KEYWORD_COLUMN_IS_KEYWORD,
115         FILTER_KEYWORD_COLUMN_COUNT
116 };
117
118 static GType filter_keyword_column_types[] = {G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN};
119
120 struct PaneKeywordsData
121 {
122         PaneData pane;
123         GtkWidget *widget;
124
125         GtkWidget *keyword_view;
126         GtkWidget *keyword_treeview;
127
128         GtkTreePath *click_tpath;
129
130         gboolean expand_checked;
131         gboolean collapse_unchecked;
132         gboolean hide_unchecked;
133
134         guint idle_id; /* event source id */
135         FileData *fd;
136         gchar *key;
137         gint height;
138
139         GList *expanded_rows;
140
141         GtkWidget *autocomplete;
142 };
143
144 struct ConfDialogData
145 {
146         PaneKeywordsData *pkd;
147         GtkTreePath *click_tpath;
148
149         /* dialog parts */
150         GenericDialog *gd;
151         GtkWidget *edit_widget;
152         gboolean is_keyword;
153
154         gboolean edit_existing;
155 };
156
157
158 static void bar_pane_keywords_write(PaneKeywordsData *pkd)
159 {
160         GList *list;
161
162         if (!pkd->fd) return;
163
164         list = keyword_list_pull(pkd->keyword_view);
165
166         metadata_write_list(pkd->fd, KEYWORD_KEY, list);
167
168         g_list_free_full(list, g_free);
169 }
170
171 static gboolean bar_keyword_tree_expand_if_set_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
172 {
173         auto pkd = static_cast<PaneKeywordsData *>(data);
174         gboolean set;
175
176         gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
177
178         if (set && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
179                 {
180                 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), path);
181                 }
182         return FALSE;
183 }
184
185 static gboolean bar_keyword_tree_collapse_if_unset_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
186 {
187         auto pkd = static_cast<PaneKeywordsData *>(data);
188         gboolean set;
189
190         gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
191
192         if (!set && gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
193                 {
194                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(pkd->keyword_treeview), path);
195                 }
196         return FALSE;
197 }
198
199 static void bar_keyword_tree_sync(PaneKeywordsData *pkd)
200 {
201         GtkTreeModel *model;
202
203         GtkTreeModel *keyword_tree;
204         GList *keywords;
205
206         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
207         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
208
209         keywords = keyword_list_pull(pkd->keyword_view);
210         keyword_show_set_in(GTK_TREE_STORE(keyword_tree), model, keywords);
211         if (pkd->hide_unchecked) keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
212         g_list_free_full(keywords, g_free);
213
214         gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(model));
215
216         if (pkd->expand_checked) gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
217         if (pkd->collapse_unchecked) gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
218 }
219
220 static void bar_pane_keywords_update(PaneKeywordsData *pkd)
221 {
222         GList *keywords = nullptr;
223         GList *orig_keywords = nullptr;
224         GList *work1, *work2;
225         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
226
227         keywords = metadata_read_list(pkd->fd, KEYWORD_KEY, METADATA_PLAIN);
228         orig_keywords = keyword_list_pull(pkd->keyword_view);
229
230         /* compare the lists */
231         work1 = keywords;
232         work2 = orig_keywords;
233
234         while (work1 && work2)
235                 {
236                 if (strcmp(static_cast<const gchar *>(work1->data), static_cast<const gchar *>(work2->data)) != 0) break;
237                 work1 = work1->next;
238                 work2 = work2->next;
239                 }
240
241         if (work1 || work2) /* lists differs */
242                 {
243                 g_signal_handlers_block_by_func(keyword_buffer, (gpointer)bar_pane_keywords_changed, pkd);
244                 keyword_list_push(pkd->keyword_view, keywords);
245                 bar_keyword_tree_sync(pkd);
246                 g_signal_handlers_unblock_by_func(keyword_buffer, (gpointer)bar_pane_keywords_changed, pkd);
247                 }
248         g_list_free_full(keywords, g_free);
249         g_list_free_full(orig_keywords, g_free);
250 }
251
252 static void bar_pane_keywords_set_fd(GtkWidget *pane, FileData *fd)
253 {
254         PaneKeywordsData *pkd;
255
256         pkd = static_cast<PaneKeywordsData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
257         if (!pkd) return;
258
259         file_data_unref(pkd->fd);
260         pkd->fd = file_data_ref(fd);
261
262         bar_pane_keywords_update(pkd);
263 }
264
265 static void bar_keyword_tree_get_expanded_cb(GtkTreeView *keyword_treeview, GtkTreePath *path,  gpointer data)
266 {
267         auto expanded = static_cast<GList **>(data);
268         GtkTreeModel *model;
269         GtkTreeIter iter;
270         gchar *path_string;
271
272         model = gtk_tree_view_get_model(GTK_TREE_VIEW(keyword_treeview));
273         gtk_tree_model_get_iter(model, &iter, path);
274
275         path_string = gtk_tree_model_get_string_from_iter(model, &iter);
276
277         *expanded = g_list_append(*expanded, g_strdup(path_string));
278         g_free(path_string);
279 }
280
281 static void bar_pane_keywords_entry_write_config(gchar *entry, GString *outstr, gint indent)
282 {
283         struct {
284                 gchar *path;
285         } expand;
286
287         expand.path = entry;
288
289         WRITE_NL(); WRITE_STRING("<expanded ");
290         WRITE_CHAR(expand, path);
291         WRITE_STRING("/>");
292 }
293
294 static void bar_pane_keywords_write_config(GtkWidget *pane, GString *outstr, gint indent)
295 {
296         PaneKeywordsData *pkd;
297         GList *path_expanded = nullptr;
298         gint w, h;
299
300         pkd = static_cast<PaneKeywordsData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
301         if (!pkd) return;
302
303         gtk_widget_get_size_request(GTK_WIDGET(pane), &w, &h);
304         pkd->height = h;
305
306         WRITE_NL(); WRITE_STRING("<pane_keywords ");
307         write_char_option(outstr, indent, "id", pkd->pane.id);
308         write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(pkd->pane.title)));
309         WRITE_BOOL(pkd->pane, expanded);
310         WRITE_CHAR(*pkd, key);
311         WRITE_INT(*pkd, height);
312         WRITE_STRING(">");
313         indent++;
314
315         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
316                                                                 (bar_keyword_tree_get_expanded_cb), &path_expanded);
317
318         GList *work = g_list_first(path_expanded);
319         while (work)
320                 {
321                 bar_pane_keywords_entry_write_config(static_cast<gchar *>(work->data), outstr, indent);
322                 work = work->next;
323                 }
324         g_list_free_full(path_expanded, g_free);
325
326         indent--;
327         WRITE_NL();
328         WRITE_STRING("</pane_keywords>");
329 }
330
331 static gint bar_pane_keywords_event(GtkWidget *bar, GdkEvent *event)
332 {
333         PaneKeywordsData *pkd;
334
335         pkd = static_cast<PaneKeywordsData *>(g_object_get_data(G_OBJECT(bar), "pane_data"));
336         if (!pkd) return FALSE;
337
338         if (gtk_widget_has_focus(pkd->keyword_view)) return gtk_widget_event(pkd->keyword_view, event);
339
340         if (gtk_widget_has_focus(pkd->autocomplete))
341                 {
342                 return gtk_widget_event(pkd->autocomplete, event);
343                 }
344         return FALSE;
345 }
346
347 static void bar_pane_keywords_keyword_toggle(GtkCellRendererToggle *, const gchar *path, gpointer data)
348 {
349         auto pkd = static_cast<PaneKeywordsData *>(data);
350         GtkTreeModel *model;
351         GtkTreeIter iter;
352         GtkTreePath *tpath;
353         gboolean active;
354         GList *list;
355         GtkTreeIter child_iter;
356         GtkTreeModel *keyword_tree;
357
358         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
359
360         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
361
362         tpath = gtk_tree_path_new_from_string(path);
363         gtk_tree_model_get_iter(model, &iter, tpath);
364         gtk_tree_path_free(tpath);
365
366         gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_TOGGLE, &active, -1);
367         active = (!active);
368
369
370         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
371         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
372
373         list = keyword_list_pull(pkd->keyword_view);
374         if (active)
375                 keyword_tree_set(keyword_tree, &child_iter, &list);
376         else
377                 keyword_tree_reset(keyword_tree, &child_iter, &list);
378
379         g_signal_handlers_block_by_func(keyword_buffer, (gpointer)bar_pane_keywords_changed, pkd);
380         keyword_list_push(pkd->keyword_view, list);
381         g_list_free_full(list, g_free);
382         g_signal_handlers_unblock_by_func(keyword_buffer, (gpointer)bar_pane_keywords_changed, pkd);
383
384         /* call this just once in the end */
385         bar_pane_keywords_changed(keyword_buffer, pkd);
386 }
387
388 static void bar_pane_keywords_filter_modify(GtkTreeModel *model, GtkTreeIter *iter, GValue *value, gint column, gpointer data)
389 {
390         auto pkd = static_cast<PaneKeywordsData *>(data);
391         GtkTreeModel *keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
392         GtkTreeIter child_iter;
393
394         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, iter);
395
396         memset(value, 0, sizeof (GValue));
397
398         switch (column)
399                 {
400                 case FILTER_KEYWORD_COLUMN_TOGGLE:
401                         {
402                         GList *keywords = keyword_list_pull(pkd->keyword_view);
403                         gboolean set = keyword_tree_is_set(keyword_tree, &child_iter, keywords);
404                         g_list_free_full(keywords, g_free);
405
406                         g_value_init(value, G_TYPE_BOOLEAN);
407                         g_value_set_boolean(value, set);
408                         break;
409                         }
410                 case FILTER_KEYWORD_COLUMN_MARK:
411                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_MARK, value);
412                         break;
413                 case FILTER_KEYWORD_COLUMN_NAME:
414                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_NAME, value);
415                         break;
416                 case FILTER_KEYWORD_COLUMN_IS_KEYWORD:
417                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_IS_KEYWORD, value);
418                         break;
419                 }
420 }
421
422 static gboolean bar_pane_keywords_filter_visible(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer data)
423 {
424         auto filter = static_cast<GtkTreeModel *>(data);
425
426         return !keyword_is_hidden_in(keyword_tree, iter, filter);
427 }
428
429 static void bar_pane_keywords_set_selection(PaneKeywordsData *pkd, gboolean append)
430 {
431         GList *keywords = nullptr;
432         GList *list = nullptr;
433         GList *work;
434
435         keywords = keyword_list_pull_selected(pkd->keyword_view);
436
437         list = layout_selection_list(pkd->pane.lw);
438         list = file_data_process_groups_in_selection(list, FALSE, nullptr);
439
440         work = list;
441         while (work)
442                 {
443                 auto fd = static_cast<FileData *>(work->data);
444                 work = work->next;
445
446                 if (append)
447                         {
448                         metadata_append_list(fd, KEYWORD_KEY, keywords);
449                         }
450                 else
451                         {
452                         metadata_write_list(fd, KEYWORD_KEY, keywords);
453                         }
454                 }
455
456         filelist_free(list);
457         g_list_free_full(keywords, g_free);
458 }
459
460 static void bar_pane_keywords_sel_add_cb(GtkWidget *, gpointer data)
461 {
462         auto pkd = static_cast<PaneKeywordsData *>(data);
463
464         bar_pane_keywords_set_selection(pkd, TRUE);
465 }
466
467 static void bar_pane_keywords_sel_replace_cb(GtkWidget *, gpointer data)
468 {
469         auto pkd = static_cast<PaneKeywordsData *>(data);
470
471         bar_pane_keywords_set_selection(pkd, FALSE);
472 }
473
474 static void bar_pane_keywords_populate_popup_cb(GtkTextView *, GtkMenu *menu, gpointer data)
475 {
476         auto pkd = static_cast<PaneKeywordsData *>(data);
477
478         menu_item_add_divider(GTK_WIDGET(menu));
479         menu_item_add_icon(GTK_WIDGET(menu), _("Add selected keywords to selected files"), GQ_ICON_ADD, G_CALLBACK(bar_pane_keywords_sel_add_cb), pkd);
480         menu_item_add_icon(GTK_WIDGET(menu), _("Replace existing keywords in selected files with selected keywords"), GQ_ICON_REPLACE, G_CALLBACK(bar_pane_keywords_sel_replace_cb), pkd);
481 }
482
483
484 static void bar_pane_keywords_notify_cb(FileData *fd, NotifyType type, gpointer data)
485 {
486         auto pkd = static_cast<PaneKeywordsData *>(data);
487         if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == pkd->fd)
488                 {
489                 DEBUG_1("Notify pane_keywords: %s %04x", fd->path, type);
490                 bar_pane_keywords_update(pkd);
491                 }
492 }
493
494 static gboolean bar_pane_keywords_changed_idle_cb(gpointer data)
495 {
496         auto pkd = static_cast<PaneKeywordsData *>(data);
497
498         bar_pane_keywords_write(pkd);
499         bar_keyword_tree_sync(pkd);
500         pkd->idle_id = 0;
501         return G_SOURCE_REMOVE;
502 }
503
504 static void bar_pane_keywords_changed(GtkTextBuffer *, gpointer data)
505 {
506         auto pkd = static_cast<PaneKeywordsData *>(data);
507
508         if (pkd->idle_id) return;
509         /* higher prio than redraw */
510         pkd->idle_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE, bar_pane_keywords_changed_idle_cb, pkd, nullptr);
511 }
512
513
514 /*
515  *-------------------------------------------------------------------
516  * dnd
517  *-------------------------------------------------------------------
518  */
519
520
521 static GtkTargetEntry bar_pane_keywords_drag_types[] = {
522         { const_cast<gchar *>(TARGET_APP_KEYWORD_PATH_STRING), GTK_TARGET_SAME_WIDGET, TARGET_APP_KEYWORD_PATH },
523         { const_cast<gchar *>("text/plain"), 0, TARGET_TEXT_PLAIN }
524 };
525 static gint n_keywords_drag_types = 2;
526
527
528 static GtkTargetEntry bar_pane_keywords_drop_types[] = {
529         { const_cast<gchar *>(TARGET_APP_KEYWORD_PATH_STRING), GTK_TARGET_SAME_WIDGET, TARGET_APP_KEYWORD_PATH },
530         { const_cast<gchar *>("text/plain"), 0, TARGET_TEXT_PLAIN }
531 };
532 static gint n_keywords_drop_types = 2;
533
534
535 static void bar_pane_keywords_dnd_get(GtkWidget *tree_view, GdkDragContext *,
536                                      GtkSelectionData *selection_data, guint info,
537                                      guint, gpointer)
538 {
539         GtkTreeIter iter;
540         GtkTreeModel *model;
541         GtkTreeIter child_iter;
542         GtkTreeModel *keyword_tree;
543
544         GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
545
546         if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
547
548         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
549         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
550
551         switch (info)
552                 {
553                 case TARGET_APP_KEYWORD_PATH:
554                         {
555                         GList *path = keyword_tree_get_path(keyword_tree, &child_iter);
556                         gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
557                                                8, reinterpret_cast<const guchar *>(&path), sizeof(path));
558                         break;
559                         }
560
561                 case TARGET_TEXT_PLAIN:
562                 default:
563                         {
564                         gchar *name = keyword_get_name(keyword_tree, &child_iter);
565                         gtk_selection_data_set_text(selection_data, name, -1);
566                         g_free(name);
567                         }
568                         break;
569                 }
570 }
571
572 static void bar_pane_keywords_dnd_begin(GtkWidget *tree_view, GdkDragContext *context, gpointer)
573 {
574         GtkTreeIter iter;
575         GtkTreeModel *model;
576         GtkTreeIter child_iter;
577         GtkTreeModel *keyword_tree;
578         gchar *name;
579
580         GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
581
582         if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
583
584         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
585         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
586
587         name = keyword_get_name(keyword_tree, &child_iter);
588
589         dnd_set_drag_label(tree_view, context, name);
590         g_free(name);
591
592 }
593
594 static void bar_pane_keywords_dnd_end(GtkWidget *, GdkDragContext *, gpointer)
595 {
596 }
597
598
599 static gboolean bar_pane_keywords_dnd_can_move(GtkTreeModel *keyword_tree, GtkTreeIter *src_kw_iter, GtkTreeIter *dest_kw_iter)
600 {
601         gchar *src_name;
602         GtkTreeIter parent;
603
604         if (dest_kw_iter && keyword_same_parent(keyword_tree, src_kw_iter, dest_kw_iter))
605                 {
606                 return TRUE; /* reordering of siblings is ok */
607                 }
608         if (!dest_kw_iter && !gtk_tree_model_iter_parent(keyword_tree, &parent, src_kw_iter))
609                 {
610                 return TRUE; /* reordering of top-level siblings is ok */
611                 }
612
613         src_name = keyword_get_name(keyword_tree, src_kw_iter);
614         if (keyword_exists(keyword_tree, nullptr, dest_kw_iter, src_name, FALSE, nullptr))
615                 {
616                 g_free(src_name);
617                 return FALSE;
618         }
619         g_free(src_name);
620         return TRUE;
621 }
622
623 static gboolean bar_pane_keywords_dnd_skip_existing(GtkTreeModel *keyword_tree, GtkTreeIter *dest_kw_iter, GList **keywords)
624 {
625         /* we have to find at least one keyword that does not already exist as a sibling of dest_kw_iter */
626         GList *work = *keywords;
627         while (work)
628                 {
629                 auto keyword = static_cast<gchar *>(work->data);
630                 if (keyword_exists(keyword_tree, nullptr, dest_kw_iter, keyword, FALSE, nullptr))
631                         {
632                         GList *next = work->next;
633                         g_free(keyword);
634                         *keywords = g_list_delete_link(*keywords, work);
635                         work = next;
636                         }
637                 else
638                         {
639                         work = work->next;
640                         }
641                 }
642         return !!*keywords;
643 }
644
645 static void bar_pane_keywords_dnd_receive(GtkWidget *tree_view, GdkDragContext *,
646                                           gint x, gint y,
647                                           GtkSelectionData *selection_data, guint info,
648                                           guint, gpointer data)
649 {
650         auto pkd = static_cast<PaneKeywordsData *>(data);
651         GtkTreePath *tpath = nullptr;
652         GtkTreeViewDropPosition pos;
653         GtkTreeModel *model;
654
655         GtkTreeModel *keyword_tree;
656         gboolean src_valid = FALSE;
657         GList *new_keywords = nullptr;
658         GList *work;
659
660         /* iterators for keyword_tree */
661         GtkTreeIter src_kw_iter;
662         GtkTreeIter dest_kw_iter;
663         GtkTreeIter new_kw_iter;
664
665         g_signal_stop_emission_by_name(tree_view, "drag_data_received");
666
667         model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
668         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
669
670         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &tpath, &pos);
671         gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), nullptr, pos);
672
673         switch (info)
674                 {
675                 case TARGET_APP_KEYWORD_PATH:
676                         {
677                         auto path = static_cast<GList *>(*reinterpret_cast<const gpointer *>(gtk_selection_data_get_data(selection_data)));
678                         src_valid = keyword_tree_get_iter(keyword_tree, &src_kw_iter, path);
679                         g_list_free_full(path, g_free);
680                         break;
681                         }
682                 default:
683                         new_keywords = string_to_keywords_list(reinterpret_cast<const gchar *>(gtk_selection_data_get_data(selection_data)));
684                         break;
685                 }
686
687         if (tpath)
688                 {
689                 GtkTreeIter dest_iter;
690                 gtk_tree_model_get_iter(model, &dest_iter, tpath);
691                 gtk_tree_path_free(tpath);
692                 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &dest_kw_iter, &dest_iter);
693
694                 if (src_valid && gtk_tree_store_is_ancestor(GTK_TREE_STORE(keyword_tree), &src_kw_iter, &dest_kw_iter))
695                         {
696                         /* can't move to it's own child */
697                         return;
698                         }
699
700                 if (src_valid && keyword_compare(keyword_tree, &src_kw_iter, &dest_kw_iter) == 0)
701                         {
702                         /* can't move to itself */
703                         return;
704                         }
705
706                 if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) &&
707                     !gtk_tree_model_iter_has_child(keyword_tree, &dest_kw_iter))
708                         {
709                         /* the node has no children, all keywords can be added */
710                         gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &new_kw_iter, &dest_kw_iter);
711                         }
712                 else
713                         {
714                         if (src_valid && !bar_pane_keywords_dnd_can_move(keyword_tree, &src_kw_iter, &dest_kw_iter))
715                                 {
716                                 /* the keyword can't be moved if the same name already exist */
717                                 return;
718                                 }
719                         if (new_keywords && !bar_pane_keywords_dnd_skip_existing(keyword_tree, &dest_kw_iter, &new_keywords))
720                                 {
721                                 /* the keywords can't be added if the same name already exist */
722                                 return;
723                                 }
724
725                         switch (pos)
726                                 {
727                                 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
728                                 case GTK_TREE_VIEW_DROP_BEFORE:
729                                         gtk_tree_store_insert_before(GTK_TREE_STORE(keyword_tree), &new_kw_iter, nullptr, &dest_kw_iter);
730                                         break;
731                                 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
732                                 case GTK_TREE_VIEW_DROP_AFTER:
733                                         gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &new_kw_iter, nullptr, &dest_kw_iter);
734                                         break;
735                                 }
736                         }
737
738                 }
739         else
740                 {
741                 if (src_valid && !bar_pane_keywords_dnd_can_move(keyword_tree, &src_kw_iter, nullptr))
742                         {
743                         /* the keyword can't be moved if the same name already exist */
744                         return;
745                         }
746                 if (new_keywords && !bar_pane_keywords_dnd_skip_existing(keyword_tree, nullptr, &new_keywords))
747                         {
748                         /* the keywords can't be added if the same name already exist */
749                         return;
750                         }
751                 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &new_kw_iter, nullptr);
752                 }
753
754
755         if (src_valid)
756                 {
757                 keyword_move_recursive(GTK_TREE_STORE(keyword_tree), &new_kw_iter, &src_kw_iter);
758                 }
759
760         work = new_keywords;
761         while (work)
762                 {
763                 auto keyword = static_cast<gchar *>(work->data);
764                 keyword_set(GTK_TREE_STORE(keyword_tree), &new_kw_iter, keyword, TRUE);
765                 work = work->next;
766
767                 if (work)
768                         {
769                         GtkTreeIter add;
770                         gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &add, nullptr, &new_kw_iter);
771                         new_kw_iter = add;
772                         }
773                 }
774         g_list_free_full(new_keywords, g_free);
775         bar_keyword_tree_sync(pkd);
776 }
777
778 static gint bar_pane_keywords_dnd_motion(GtkWidget *tree_view, GdkDragContext *context,
779                                         gint x, gint y, guint time, gpointer)
780 {
781         GtkTreePath *tpath = nullptr;
782         GtkTreeViewDropPosition pos;
783         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &tpath, &pos);
784         if (tpath)
785                 {
786                 GtkTreeModel *model;
787                 GtkTreeIter dest_iter;
788                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
789                 gtk_tree_model_get_iter(model, &dest_iter, tpath);
790                 if (pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE && gtk_tree_model_iter_has_child(model, &dest_iter))
791                         pos = GTK_TREE_VIEW_DROP_BEFORE;
792
793                 if (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER && gtk_tree_model_iter_has_child(model, &dest_iter))
794                         pos = GTK_TREE_VIEW_DROP_AFTER;
795                 }
796
797         gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), tpath, pos);
798         gtk_tree_path_free(tpath);
799
800         if (tree_view == gtk_drag_get_source_widget(context))
801                 gdk_drag_status(context, GDK_ACTION_MOVE, time);
802         else
803                 gdk_drag_status(context, GDK_ACTION_COPY, time);
804
805         return TRUE;
806 }
807
808 /*
809  *-------------------------------------------------------------------
810  * edit dialog
811  *-------------------------------------------------------------------
812  */
813
814 static void bar_pane_keywords_edit_destroy_cb(GtkWidget *, gpointer data)
815 {
816         auto cdd = static_cast<ConfDialogData *>(data);
817         gtk_tree_path_free(cdd->click_tpath);
818         g_free(cdd);
819 }
820
821
822 static void bar_pane_keywords_edit_cancel_cb(GenericDialog *, gpointer)
823 {
824 }
825
826
827 static void bar_pane_keywords_edit_ok_cb(GenericDialog *, gpointer data)
828 {
829         auto cdd = static_cast<ConfDialogData *>(data);
830         PaneKeywordsData *pkd = cdd->pkd;
831         GtkTreeModel *model;
832
833         GtkTreeModel *keyword_tree;
834         GtkTreeIter kw_iter;
835
836         gboolean have_dest = FALSE;
837
838         GList *keywords;
839
840         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
841         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
842
843         if (cdd->click_tpath)
844                 {
845                 GtkTreeIter iter;
846                 if (gtk_tree_model_get_iter(model, &iter, cdd->click_tpath))
847                         {
848                         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
849                         have_dest = TRUE;
850                         }
851                 }
852
853         if (cdd->edit_existing && !have_dest) return;
854
855         keywords = keyword_list_pull(cdd->edit_widget);
856
857         if (cdd->edit_existing)
858                 {
859                 if (keywords && keywords->data && /* there should be one keyword */
860                     !keyword_exists(keyword_tree, nullptr, &kw_iter, static_cast<const gchar *>(keywords->data), TRUE, nullptr))
861                         {
862                         keyword_set(GTK_TREE_STORE(keyword_tree), &kw_iter, static_cast<const gchar *>(keywords->data), cdd->is_keyword);
863                         }
864                 }
865         else
866                 {
867                 GList *work = keywords;
868                 gboolean append_to = FALSE;
869
870                 while (work)
871                         {
872                         GtkTreeIter add;
873                         if (keyword_exists(keyword_tree, nullptr, (have_dest || append_to) ? &kw_iter : nullptr, static_cast<const gchar *>(work->data), FALSE, nullptr))
874                                 {
875                                 work = work->next;
876                                 continue;
877                                 }
878                         if (have_dest)
879                                 {
880                                 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &add, &kw_iter);
881                                 }
882                         else if (append_to)
883                                 {
884                                 gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &add, nullptr, &kw_iter);
885                                 }
886                         else
887                                 {
888                                 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &add, nullptr);
889                                 append_to = TRUE;
890                                 kw_iter = add;
891                                 }
892                         keyword_set(GTK_TREE_STORE(keyword_tree), &add, static_cast<const gchar *>(work->data), cdd->is_keyword);
893                         work = work->next;
894                         }
895                 }
896         g_list_free_full(keywords, g_free);
897 }
898
899 static void bar_pane_keywords_conf_set_helper(GtkWidget *, gpointer data)
900 {
901         auto cdd = static_cast<ConfDialogData *>(data);
902         cdd->is_keyword = FALSE;
903 }
904
905 static void bar_pane_keywords_conf_set_kw(GtkWidget *, gpointer data)
906 {
907         auto cdd = static_cast<ConfDialogData *>(data);
908         cdd->is_keyword = TRUE;
909 }
910
911
912
913 static void bar_pane_keywords_edit_dialog(PaneKeywordsData *pkd, gboolean edit_existing)
914 {
915         ConfDialogData *cdd;
916         GenericDialog *gd;
917         GtkWidget *table;
918         GtkWidget *group;
919         GtkWidget *button;
920
921         gchar *name = nullptr;
922         gboolean is_keyword = TRUE;
923
924
925         if (edit_existing && pkd->click_tpath)
926                 {
927                 GtkTreeModel *model;
928                 GtkTreeIter iter;
929                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
930
931                 if (gtk_tree_model_get_iter(model, &iter, pkd->click_tpath))
932                         {
933                         gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
934                                                          FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
935                         }
936                 else
937                         {
938                         return;
939                         }
940                 }
941
942         if (edit_existing && !name) return;
943
944
945         cdd = g_new0(ConfDialogData, 1);
946         cdd->pkd =pkd;
947         cdd->click_tpath = pkd->click_tpath;
948         pkd->click_tpath = nullptr;
949         cdd->edit_existing = edit_existing;
950
951         cdd->gd = gd = generic_dialog_new(name ? _("Edit keyword") : _("New keyword"), "keyword_edit",
952                                 pkd->widget, TRUE,
953                                 bar_pane_keywords_edit_cancel_cb, cdd);
954         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
955                          G_CALLBACK(bar_pane_keywords_edit_destroy_cb), cdd);
956
957
958         generic_dialog_add_message(gd, nullptr, name ? _("Configure keyword") : _("New keyword"), nullptr, FALSE);
959
960         generic_dialog_add_button(gd, GQ_ICON_OK, "OK",
961                                   bar_pane_keywords_edit_ok_cb, TRUE);
962
963         table = pref_table_new(gd->vbox, 3, 1, FALSE, TRUE);
964         pref_table_label(table, 0, 0, _("Keyword:"), GTK_ALIGN_END);
965         cdd->edit_widget = gtk_entry_new();
966         gtk_widget_set_size_request(cdd->edit_widget, 300, -1);
967         if (name) gq_gtk_entry_set_text(GTK_ENTRY(cdd->edit_widget), name);
968         gq_gtk_grid_attach_default(GTK_GRID(table), cdd->edit_widget, 1, 2, 0, 1);
969         /* here could eventually be a text view instead of entry */
970         generic_dialog_attach_default(gd, cdd->edit_widget);
971         gtk_widget_show(cdd->edit_widget);
972
973         group = pref_group_new(gd->vbox, FALSE, _("Keyword type:"), GTK_ORIENTATION_VERTICAL);
974
975         button = pref_radiobutton_new(group, nullptr, _("Active keyword"),
976                                       (is_keyword),
977                                       G_CALLBACK(bar_pane_keywords_conf_set_kw), cdd);
978         button = pref_radiobutton_new(group, button, _("Helper"),
979                                       (!is_keyword),
980                                       G_CALLBACK(bar_pane_keywords_conf_set_helper), cdd);
981
982         cdd->is_keyword = is_keyword;
983
984         g_free(name);
985
986         gtk_widget_grab_focus(cdd->edit_widget);
987
988         gtk_widget_show(gd->dialog);
989 }
990
991
992
993
994 /*
995  *-------------------------------------------------------------------
996  * popup menu
997  *-------------------------------------------------------------------
998  */
999
1000 static void bar_pane_keywords_edit_dialog_cb(GtkWidget *, gpointer data)
1001 {
1002         auto pkd = static_cast<PaneKeywordsData *>(data);
1003         bar_pane_keywords_edit_dialog(pkd, TRUE);
1004 }
1005
1006 static void bar_pane_keywords_add_dialog_cb(GtkWidget *, gpointer data)
1007 {
1008         auto pkd = static_cast<PaneKeywordsData *>(data);
1009         bar_pane_keywords_edit_dialog(pkd, FALSE);
1010 }
1011
1012 static void bar_pane_keywords_connect_mark_cb(GtkWidget *menu_widget, gpointer data)
1013 {
1014         auto pkd = static_cast<PaneKeywordsData *>(data);
1015
1016         GtkTreeModel *model;
1017         GtkTreeIter iter;
1018
1019         GtkTreeModel *keyword_tree;
1020         GtkTreeIter kw_iter;
1021
1022         gint mark = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_widget), "mark")) - 1;
1023
1024         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1025         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1026
1027         if (!pkd->click_tpath) return;
1028         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1029
1030         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1031
1032         meta_data_connect_mark_with_keyword(keyword_tree, &kw_iter, mark);
1033 }
1034
1035 static void bar_pane_keywords_disconnect_marks_ok_cb(GenericDialog *, gpointer)
1036 {
1037         keyword_tree_disconnect_marks();
1038 }
1039
1040 static void dummy_cancel_cb(GenericDialog *, gpointer)
1041 {
1042         /* no op, only so cancel button appears */
1043 }
1044
1045 static void bar_pane_keywords_disconnect_marks_cb(GtkWidget *menu_widget, gpointer data)
1046 {
1047         auto pkd = static_cast<PaneKeywordsData *>(data);
1048
1049         GenericDialog *gd;
1050
1051         gd = generic_dialog_new(_("Marks Keywords"),
1052                                 "marks_keywords", menu_widget, TRUE, dummy_cancel_cb, pkd);
1053         generic_dialog_add_message(gd, GQ_ICON_DIALOG_WARNING,
1054                                 "Disconnect all Marks Keywords connections?", _("This will disconnect all Marks Keywords connections"), TRUE);
1055         generic_dialog_add_button(gd, GQ_ICON_OK, "OK", bar_pane_keywords_disconnect_marks_ok_cb, TRUE);
1056
1057         gtk_widget_show(gd->dialog);
1058 }
1059
1060 static void bar_pane_keywords_delete_cb(GtkWidget *, gpointer data)
1061 {
1062         auto pkd = static_cast<PaneKeywordsData *>(data);
1063         GtkTreeModel *model;
1064         GtkTreeIter iter;
1065
1066         GtkTreeModel *keyword_tree;
1067         GtkTreeIter kw_iter;
1068
1069         if (!pkd->click_tpath) return;
1070
1071         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1072         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1073
1074         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1075         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1076
1077         keyword_delete(GTK_TREE_STORE(keyword_tree), &kw_iter);
1078 }
1079
1080 static void bar_pane_keywords_hide_cb(GtkWidget *, gpointer data)
1081 {
1082         auto pkd = static_cast<PaneKeywordsData *>(data);
1083         GtkTreeModel *model;
1084         GtkTreeIter iter;
1085
1086         GtkTreeModel *keyword_tree;
1087         GtkTreeIter kw_iter;
1088
1089         if (!pkd->click_tpath) return;
1090
1091         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1092         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1093
1094         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1095         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1096
1097         keyword_hide_in(GTK_TREE_STORE(keyword_tree), &kw_iter, model);
1098 }
1099
1100 static void bar_pane_keywords_show_all_cb(GtkWidget *, gpointer data)
1101 {
1102         auto pkd = static_cast<PaneKeywordsData *>(data);
1103         GtkTreeModel *model;
1104
1105         GtkTreeModel *keyword_tree;
1106
1107         g_list_free_full(pkd->expanded_rows, g_free);
1108         pkd->expanded_rows = nullptr;
1109         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
1110                                                                 (bar_keyword_tree_get_expanded_cb), &pkd->expanded_rows);
1111
1112         pkd->hide_unchecked = FALSE;
1113
1114         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1115         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1116
1117         keyword_show_all_in(GTK_TREE_STORE(keyword_tree), model);
1118
1119         if (!pkd->collapse_unchecked) gtk_tree_view_expand_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1120         bar_keyword_tree_sync(pkd);
1121 }
1122
1123 static void bar_pane_keywords_revert_cb(GtkWidget *, gpointer data)
1124 {
1125         auto pkd = static_cast<PaneKeywordsData *>(data);
1126         GList *work;
1127         GtkTreePath *tree_path;
1128         gchar *path;
1129
1130         gtk_tree_view_collapse_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1131
1132         work = pkd->expanded_rows;
1133         while (work)
1134                 {
1135                 path = static_cast<gchar *>(work->data);
1136                 tree_path = gtk_tree_path_new_from_string(path);
1137                 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), tree_path);
1138                 work = work->next;
1139                 gtk_tree_path_free(tree_path);
1140                 }
1141
1142         bar_keyword_tree_sync(pkd);
1143 }
1144
1145 static void bar_pane_keywords_expand_checked_cb(GtkWidget *, gpointer data)
1146 {
1147         auto pkd = static_cast<PaneKeywordsData *>(data);
1148         GtkTreeModel *model;
1149
1150         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1151         gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
1152 }
1153
1154 static void bar_pane_keywords_collapse_all_cb(GtkWidget *, gpointer data)
1155 {
1156         auto pkd = static_cast<PaneKeywordsData *>(data);
1157
1158         g_list_free_full(pkd->expanded_rows, g_free);
1159         pkd->expanded_rows = nullptr;
1160         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
1161                                                                 (bar_keyword_tree_get_expanded_cb), &pkd->expanded_rows);
1162
1163         gtk_tree_view_collapse_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1164
1165         bar_keyword_tree_sync(pkd);
1166 }
1167
1168 static void bar_pane_keywords_revert_hidden_cb(GtkWidget *, gpointer data)
1169 {
1170         auto pkd = static_cast<PaneKeywordsData *>(data);
1171         GtkTreeModel *model;
1172         GtkTreeModel *keyword_tree;
1173
1174         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1175         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1176
1177         keyword_revert_hidden_in(GTK_TREE_STORE(keyword_tree), model);
1178
1179         bar_keyword_tree_sync(pkd);
1180 }
1181
1182 static void bar_pane_keywords_collapse_unchecked_cb(GtkWidget *, gpointer data)
1183 {
1184         auto pkd = static_cast<PaneKeywordsData *>(data);
1185         GtkTreeModel *model;
1186
1187         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1188         gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
1189 }
1190
1191 static void bar_pane_keywords_hide_unchecked_cb(GtkWidget *, gpointer data)
1192 {
1193         auto pkd = static_cast<PaneKeywordsData *>(data);
1194         GtkTreeModel *model;
1195
1196         GtkTreeModel *keyword_tree;
1197         GList *keywords;
1198
1199         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1200         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1201
1202         keywords = keyword_list_pull(pkd->keyword_view);
1203         keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
1204         g_list_free_full(keywords, g_free);
1205         bar_keyword_tree_sync(pkd);
1206 }
1207
1208 static void bar_pane_keywords_expand_checked_toggle_cb(GtkWidget *, gpointer data)
1209 {
1210         auto pkd = static_cast<PaneKeywordsData *>(data);
1211         pkd->expand_checked = !pkd->expand_checked;
1212         bar_keyword_tree_sync(pkd);
1213 }
1214
1215 static void bar_pane_keywords_collapse_unchecked_toggle_cb(GtkWidget *, gpointer data)
1216 {
1217         auto pkd = static_cast<PaneKeywordsData *>(data);
1218         pkd->collapse_unchecked = !pkd->collapse_unchecked;
1219         bar_keyword_tree_sync(pkd);
1220 }
1221
1222 static void bar_pane_keywords_hide_unchecked_toggle_cb(GtkWidget *, gpointer data)
1223 {
1224         auto pkd = static_cast<PaneKeywordsData *>(data);
1225         pkd->hide_unchecked = !pkd->hide_unchecked;
1226         bar_keyword_tree_sync(pkd);
1227 }
1228
1229 /**
1230  * @brief Callback for adding selected keyword to all selected images.
1231  */
1232 static void bar_pane_keywords_add_to_selected_cb(GtkWidget *, gpointer data)
1233 {
1234         auto pkd = static_cast<PaneKeywordsData *>(data);
1235         GtkTreeIter iter; /* This is the iter which initial holds the current keyword */
1236         GtkTreeIter child_iter;
1237         GtkTreeModel *model;
1238         GtkTreeModel *keyword_tree;
1239         GList *list, *work;
1240         GList *keywords = nullptr;
1241
1242         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1243
1244         /* Acquire selected keyword */
1245         if (pkd->click_tpath)
1246                 {
1247                 gboolean is_keyword = TRUE;
1248
1249                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1250                 if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1251                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1252                 if (!is_keyword) return;
1253                 }
1254         else
1255                 return;
1256
1257         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1258         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
1259
1260         list = keyword_list_pull(pkd->keyword_view); /* Get the left keyword view */
1261
1262         /* Now set the current image */
1263         keyword_tree_set(keyword_tree, &child_iter, &list);
1264
1265         keyword_list_push(pkd->keyword_view, list); /* Set the left keyword view */
1266         g_list_free_full(list, g_free);
1267
1268         bar_pane_keywords_changed(keyword_buffer, pkd); /* Get list of all keywords in the hierarchy */
1269
1270         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
1271         keywords = keyword_tree_get(keyword_tree, &child_iter);
1272
1273         list = layout_selection_list(pkd->pane.lw);
1274         work = list;
1275         while (work)
1276                 {
1277                 auto fd = static_cast<FileData *>(work->data);
1278                 work = work->next;
1279                 metadata_append_list(fd, KEYWORD_KEY, keywords);
1280                 }
1281         filelist_free(list);
1282         g_list_free_full(keywords, g_free);
1283 }
1284
1285 static void bar_pane_keywords_menu_popup(GtkWidget *, PaneKeywordsData *pkd, gint x, gint y)
1286 {
1287         GtkWidget *menu;
1288         GtkWidget *item;
1289         GtkWidget *submenu;
1290         GtkTreeViewDropPosition pos;
1291
1292         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1293         pkd->click_tpath = nullptr;
1294         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(pkd->keyword_treeview), x, y, &pkd->click_tpath, &pos);
1295
1296         menu = popup_menu_short_lived();
1297
1298         menu_item_add_icon(menu, _("New keyword"), GQ_ICON_NEW, G_CALLBACK(bar_pane_keywords_add_dialog_cb), pkd);
1299
1300         menu_item_add_divider(menu);
1301
1302         if (pkd->click_tpath)
1303                 {
1304                 /* for the entry */
1305                 gchar *text;
1306                 gchar *mark;
1307                 gint i;
1308                 gboolean keyword;
1309
1310                 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1311
1312                 GtkTreeIter iter;
1313                 gtk_tree_model_get_iter(model, &iter, pkd->click_tpath);
1314                 gchar *name;
1315
1316                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
1317                                                 FILTER_KEYWORD_COLUMN_MARK, &mark,
1318                                                 FILTER_KEYWORD_COLUMN_IS_KEYWORD, &keyword, -1);
1319
1320                 if (keyword)
1321                         {
1322                         text = g_strdup_printf(_("Add \"%s\" to all selected images"), name);
1323                         menu_item_add_icon(menu, text, GQ_ICON_ADD, G_CALLBACK(bar_pane_keywords_add_to_selected_cb), pkd);
1324                         g_free(text);
1325                         }
1326                 menu_item_add_divider(menu);
1327
1328                 text = g_strdup_printf(_("Hide \"%s\""), name);
1329                 menu_item_add(menu, text, G_CALLBACK(bar_pane_keywords_hide_cb), pkd);
1330                 g_free(text);
1331
1332                 submenu = gtk_menu_new();
1333                 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1334                         {
1335                         text = g_strdup_printf(_("Mark %d"), 1 + (i < 9 ? i : -1));
1336                         item = menu_item_add(submenu, text, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1337                         g_object_set_data(G_OBJECT(item), "mark", GINT_TO_POINTER(i + 1));
1338                         g_free(text);
1339                         }
1340
1341                 if (keyword)
1342                         {
1343                         text = g_strdup_printf(_("Connect \"%s\" to mark"), name);
1344                         item = menu_item_add(menu, text, nullptr, nullptr);
1345                         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1346                         g_free(text);
1347                         }
1348                 menu_item_add_divider(menu);
1349
1350                 text = g_strdup_printf(_("Edit \"%s\""), name);
1351                 menu_item_add_icon(menu, text, GQ_ICON_EDIT, G_CALLBACK(bar_pane_keywords_edit_dialog_cb), pkd);
1352                 g_free(text);
1353                 text = g_strdup_printf(_("Remove \"%s\""), name);
1354                 menu_item_add_icon(menu, text, GQ_ICON_DELETE, G_CALLBACK(bar_pane_keywords_delete_cb), pkd);
1355                 g_free(text);
1356
1357
1358                 if (mark && mark[0])
1359                         {
1360                         text = g_strdup_printf(_("Disconnect \"%s\" from mark %s"), name, mark);
1361                         menu_item_add_icon(menu, text, GQ_ICON_DELETE, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1362                         g_free(text);
1363                         }
1364
1365                 if (keyword)
1366                         {
1367                         text = g_strdup_printf(_("Disconnect all Mark Keyword connections"));
1368                         menu_item_add_icon(menu, text, GQ_ICON_DELETE, G_CALLBACK(bar_pane_keywords_disconnect_marks_cb), pkd);
1369                         g_free(text);
1370                         }
1371                 menu_item_add_divider(menu);
1372                 g_free(mark);
1373                 g_free(name);
1374                 }
1375         /* for the pane */
1376
1377
1378         menu_item_add(menu, _("Expand checked"), G_CALLBACK(bar_pane_keywords_expand_checked_cb), pkd);
1379         menu_item_add(menu, _("Collapse unchecked"), G_CALLBACK(bar_pane_keywords_collapse_unchecked_cb), pkd);
1380         menu_item_add(menu, _("Hide unchecked"), G_CALLBACK(bar_pane_keywords_hide_unchecked_cb), pkd);
1381         menu_item_add(menu, _("Revert all hidden"), G_CALLBACK(bar_pane_keywords_revert_hidden_cb), pkd);
1382         menu_item_add_divider(menu);
1383         menu_item_add(menu, _("Show all"), G_CALLBACK(bar_pane_keywords_show_all_cb), pkd);
1384         menu_item_add(menu, _("Collapse all"), G_CALLBACK(bar_pane_keywords_collapse_all_cb), pkd);
1385         menu_item_add(menu, _("Revert"), G_CALLBACK(bar_pane_keywords_revert_cb), pkd);
1386         menu_item_add_divider(menu);
1387
1388         submenu = gtk_menu_new();
1389         item = menu_item_add(menu, _("On any change"), nullptr, nullptr);
1390         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1391
1392         menu_item_add_check(submenu, _("Expand checked"), pkd->expand_checked, G_CALLBACK(bar_pane_keywords_expand_checked_toggle_cb), pkd);
1393         menu_item_add_check(submenu, _("Collapse unchecked"), pkd->collapse_unchecked, G_CALLBACK(bar_pane_keywords_collapse_unchecked_toggle_cb), pkd);
1394         menu_item_add_check(submenu, _("Hide unchecked"), pkd->hide_unchecked, G_CALLBACK(bar_pane_keywords_hide_unchecked_toggle_cb), pkd);
1395
1396         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
1397 }
1398
1399
1400 static gboolean bar_pane_keywords_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1401 {
1402         auto pkd = static_cast<PaneKeywordsData *>(data);
1403         if (bevent->button == MOUSE_BUTTON_RIGHT)
1404                 {
1405                 bar_pane_keywords_menu_popup(widget, pkd, bevent->x, bevent->y);
1406                 return TRUE;
1407                 }
1408         return FALSE;
1409 }
1410
1411 /*
1412  *-------------------------------------------------------------------
1413  * init
1414  *-------------------------------------------------------------------
1415  */
1416
1417 #pragma GCC diagnostic push
1418 #pragma GCC diagnostic ignored "-Wunused-function"
1419 void bar_pane_keywords_close_unused(GtkWidget *bar)
1420 {
1421         PaneKeywordsData *pkd;
1422
1423         pkd = static_cast<PaneKeywordsData *>(g_object_get_data(G_OBJECT(bar), "pane_data"));
1424         if (!pkd) return;
1425
1426         g_free(pkd->pane.id);
1427         g_object_unref(pkd->widget);
1428 }
1429 #pragma GCC diagnostic pop
1430
1431 static void bar_pane_keywords_destroy(GtkWidget *, gpointer data)
1432 {
1433         auto pkd = static_cast<PaneKeywordsData *>(data);
1434         gchar *path;
1435
1436         path = g_build_filename(get_rc_dir(), "keywords", NULL);
1437         autocomplete_keywords_list_save(path);
1438
1439         g_list_free_full(pkd->expanded_rows, g_free);
1440         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1441         if (pkd->idle_id) g_source_remove(pkd->idle_id);
1442         file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
1443
1444         file_data_unref(pkd->fd);
1445         g_free(pkd->key);
1446
1447         g_free(pkd);
1448 }
1449
1450
1451 static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded, gint height)
1452 {
1453         PaneKeywordsData *pkd;
1454         GtkWidget *hbox, *vbox;
1455         GtkWidget *scrolled;
1456         GtkTextBuffer *buffer;
1457         GtkTreeModel *store;
1458         GtkTreeViewColumn *column;
1459         GtkCellRenderer *renderer;
1460         GtkTreeIter iter;
1461         GtkEntryCompletion *completion;
1462         gchar *path;
1463
1464         pkd = g_new0(PaneKeywordsData, 1);
1465
1466         pkd->pane.pane_set_fd = bar_pane_keywords_set_fd;
1467         pkd->pane.pane_event = bar_pane_keywords_event;
1468         pkd->pane.pane_write_config = bar_pane_keywords_write_config;
1469         pkd->pane.title = bar_pane_expander_title(title);
1470         pkd->pane.id = g_strdup(id);
1471         pkd->pane.type = PANE_KEYWORDS;
1472
1473         pkd->pane.expanded = expanded;
1474
1475         pkd->height = height;
1476         pkd->key = g_strdup(key);
1477
1478         pkd->expand_checked = TRUE;
1479         pkd->expanded_rows = nullptr;
1480
1481         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1482         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
1483         gq_gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1484
1485         pkd->widget = vbox;
1486         g_object_set_data(G_OBJECT(pkd->widget), "pane_data", pkd);
1487         g_signal_connect(G_OBJECT(pkd->widget), "destroy",
1488                          G_CALLBACK(bar_pane_keywords_destroy), pkd);
1489         gtk_widget_set_size_request(pkd->widget, -1, height);
1490         gtk_widget_show(hbox);
1491
1492         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
1493         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1494         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1495                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1496         gq_gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1497         gtk_widget_show(scrolled);
1498
1499         pkd->keyword_view = gtk_text_view_new();
1500         gq_gtk_container_add(GTK_WIDGET(scrolled), pkd->keyword_view);
1501         g_signal_connect(G_OBJECT(pkd->keyword_view), "populate-popup",
1502                          G_CALLBACK(bar_pane_keywords_populate_popup_cb), pkd);
1503         gtk_widget_show(pkd->keyword_view);
1504
1505         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1506         g_signal_connect(G_OBJECT(buffer), "changed",
1507                          G_CALLBACK(bar_pane_keywords_changed), pkd);
1508
1509         if (options->show_predefined_keyword_tree)
1510                 {
1511                 scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
1512                 gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1513                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1514                                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1515                 gq_gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1516                 gtk_widget_show(scrolled);
1517                 }
1518
1519         pkd->autocomplete = gtk_entry_new();
1520         gq_gtk_box_pack_end(GTK_BOX(vbox), pkd->autocomplete, FALSE, FALSE, 0);
1521         gtk_widget_show(pkd->autocomplete);
1522         gtk_widget_show(vbox);
1523         gtk_widget_set_tooltip_text(pkd->autocomplete, "Keyword autocomplete");
1524
1525         path = g_build_filename(get_rc_dir(), "keywords", NULL);
1526         autocomplete_keywords_list_load(path);
1527
1528         completion = gtk_entry_completion_new();
1529         gtk_entry_set_completion(GTK_ENTRY(pkd->autocomplete), completion);
1530         gtk_entry_completion_set_inline_completion(completion, TRUE);
1531         gtk_entry_completion_set_inline_selection(completion, TRUE);
1532         g_object_unref(completion);
1533
1534         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(keyword_store));
1535         gtk_entry_completion_set_text_column(completion, 0);
1536
1537         g_signal_connect(G_OBJECT(pkd->autocomplete), "activate",
1538                          G_CALLBACK(autocomplete_activate_cb), pkd);
1539
1540         if (!keyword_tree || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1541                 {
1542                 /* keyword tree does not exist or is empty - fill with defaults */
1543                 keyword_tree_new_default();
1544                 }
1545
1546         store = gtk_tree_model_filter_new(GTK_TREE_MODEL(keyword_tree), nullptr);
1547
1548         gtk_tree_model_filter_set_modify_func(GTK_TREE_MODEL_FILTER(store),
1549                                               FILTER_KEYWORD_COLUMN_COUNT,
1550                                               filter_keyword_column_types,
1551                                               bar_pane_keywords_filter_modify,
1552                                               pkd,
1553                                               nullptr);
1554         gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(store),
1555                                                bar_pane_keywords_filter_visible,
1556                                                store,
1557                                                nullptr);
1558
1559         pkd->keyword_treeview = gtk_tree_view_new_with_model(store);
1560         g_object_unref(store);
1561
1562         gtk_widget_set_size_request(pkd->keyword_treeview, -1, 400);
1563
1564         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pkd->keyword_treeview), FALSE);
1565
1566         column = gtk_tree_view_column_new();
1567         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1568
1569         renderer = gtk_cell_renderer_text_new();
1570         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1571
1572         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_MARK);
1573
1574         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1575
1576         column = gtk_tree_view_column_new();
1577         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1578         renderer = gtk_cell_renderer_toggle_new();
1579         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1580         gtk_tree_view_column_add_attribute(column, renderer, "active", FILTER_KEYWORD_COLUMN_TOGGLE);
1581         gtk_tree_view_column_add_attribute(column, renderer, "visible", FILTER_KEYWORD_COLUMN_IS_KEYWORD);
1582         g_signal_connect(G_OBJECT(renderer), "toggled",
1583                          G_CALLBACK(bar_pane_keywords_keyword_toggle), pkd);
1584
1585         renderer = gtk_cell_renderer_text_new();
1586         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1587         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_NAME);
1588
1589         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1590         gtk_tree_view_set_expander_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1591
1592         gtk_drag_source_set(pkd->keyword_treeview,
1593                             static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
1594                             bar_pane_keywords_drag_types, n_keywords_drag_types,
1595                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
1596
1597         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_get",
1598                          G_CALLBACK(bar_pane_keywords_dnd_get), pkd);
1599
1600         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_begin",
1601                          G_CALLBACK(bar_pane_keywords_dnd_begin), pkd);
1602         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_end",
1603                          G_CALLBACK(bar_pane_keywords_dnd_end), pkd);
1604
1605         gtk_drag_dest_set(pkd->keyword_treeview,
1606                           static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP),
1607                           bar_pane_keywords_drop_types, n_keywords_drop_types,
1608                           static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE));
1609
1610         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_received",
1611                          G_CALLBACK(bar_pane_keywords_dnd_receive), pkd);
1612
1613         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_motion",
1614                          G_CALLBACK(bar_pane_keywords_dnd_motion), pkd);
1615
1616         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "button_release_event",
1617                          G_CALLBACK(bar_pane_keywords_menu_cb), pkd);
1618
1619         if (options->show_predefined_keyword_tree)
1620                 {
1621                 gq_gtk_container_add(GTK_WIDGET(scrolled), pkd->keyword_treeview);
1622                 gtk_widget_show(pkd->keyword_treeview);
1623                 }
1624
1625         file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
1626
1627         return pkd->widget;
1628 }
1629
1630 GtkWidget *bar_pane_keywords_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
1631 {
1632         gchar *id = g_strdup("keywords");
1633         gchar *title = nullptr;
1634         gchar *key = g_strdup(COMMENT_KEY);
1635         gboolean expanded = TRUE;
1636         gint height = 200;
1637         GtkWidget *ret;
1638
1639         while (*attribute_names)
1640                 {
1641                 const gchar *option = *attribute_names++;
1642                 const gchar *value = *attribute_values++;
1643
1644                 if (READ_CHAR_FULL("id", id)) continue;
1645                 if (READ_CHAR_FULL("title", title)) continue;
1646                 if (READ_CHAR_FULL("key", key)) continue;
1647                 if (READ_BOOL_FULL("expanded", expanded)) continue;
1648                 if (READ_INT_FULL("height", height)) continue;
1649
1650
1651                 log_printf("unknown attribute %s = %s\n", option, value);
1652                 }
1653
1654         options->info_keywords.height = height;
1655         bar_pane_translate_title(PANE_KEYWORDS, id, &title);
1656         ret = bar_pane_keywords_new(id, title, key, expanded, height);
1657         g_free(id);
1658         g_free(title);
1659         g_free(key);
1660         return ret;
1661 }
1662
1663 void bar_pane_keywords_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1664 {
1665         PaneKeywordsData *pkd;
1666
1667         pkd = static_cast<PaneKeywordsData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
1668         if (!pkd) return;
1669
1670         gchar *title = nullptr;
1671
1672         while (*attribute_names)
1673                 {
1674                 const gchar *option = *attribute_names++;
1675                 const gchar *value = *attribute_values++;
1676
1677                 if (READ_CHAR_FULL("title", title)) continue;
1678                 if (READ_CHAR_FULL("key", pkd->key)) continue;
1679                 if (READ_BOOL_FULL("expanded", pkd->pane.expanded)) continue;
1680                 if (READ_CHAR_FULL("id", pkd->pane.id)) continue;
1681
1682
1683                 log_printf("unknown attribute %s = %s\n", option, value);
1684                 }
1685
1686         if (title)
1687                 {
1688                 bar_pane_translate_title(PANE_KEYWORDS, pkd->pane.id, &title);
1689                 gtk_label_set_text(GTK_LABEL(pkd->pane.title), title);
1690                 g_free(title);
1691                 }
1692
1693         bar_update_expander(pane);
1694         bar_pane_keywords_update(pkd);
1695 }
1696
1697
1698 void bar_pane_keywords_entry_add_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1699 {
1700         PaneKeywordsData *pkd;
1701         gchar *path = nullptr;
1702         GtkTreePath *tree_path;
1703
1704         pkd = static_cast<PaneKeywordsData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
1705         if (!pkd) return;
1706
1707         while (*attribute_names)
1708                 {
1709                 const gchar *option = *attribute_names++;
1710                 const gchar *value = *attribute_values++;
1711
1712                 if (READ_CHAR_FULL("path", path))
1713                         {
1714                         tree_path = gtk_tree_path_new_from_string(path);
1715                         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), tree_path);
1716                         gtk_tree_path_free(tree_path);
1717                         pkd->expanded_rows = g_list_append(pkd->expanded_rows, g_strdup(path));
1718                         continue;
1719                         }
1720                 log_printf("unknown attribute %s = %s\n", option, value);
1721                 }
1722 }
1723
1724 /*
1725  *-----------------------------------------------------------------------------
1726  * Autocomplete keywords
1727  *-----------------------------------------------------------------------------
1728  */
1729
1730 static gboolean autocomplete_activate_cb(GtkWidget *, gpointer data)
1731 {
1732         auto pkd = static_cast<PaneKeywordsData *>(data);
1733         gchar *entry_text;
1734         GtkTextBuffer *buffer;
1735         GtkTextIter iter;
1736         GtkTreeIter iter_t;
1737         gchar *kw_cr;
1738         gchar *kw_split;
1739         gboolean valid;
1740         gboolean found = FALSE;
1741         gchar *string;
1742
1743         entry_text = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(pkd->autocomplete)));
1744         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1745
1746         kw_split = strtok(entry_text, ",");
1747         while (kw_split != nullptr)
1748                 {
1749                 kw_cr = g_strconcat(kw_split, "\n", NULL);
1750                 g_strchug(kw_cr);
1751                 gtk_text_buffer_get_end_iter(buffer, &iter);
1752                 gtk_text_buffer_insert(buffer, &iter, kw_cr, -1);
1753
1754                 kw_split = strtok(nullptr, ",");
1755                 g_free(kw_cr);
1756                 }
1757
1758         g_free(entry_text);
1759         entry_text = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(pkd->autocomplete)));
1760
1761         gq_gtk_entry_set_text(GTK_ENTRY(pkd->autocomplete), "");
1762
1763         valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter_t);
1764         while (valid)
1765                 {
1766                 gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter_t, 0, &string, -1);
1767                 if (g_strcmp0(entry_text, string) == 0)
1768                         {
1769                         found = TRUE;
1770                         break;
1771                         }
1772                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter_t);
1773                 }
1774
1775         if (!found)
1776                 {
1777                 gtk_list_store_append (keyword_store, &iter_t);
1778                 gtk_list_store_set(keyword_store, &iter_t, 0, entry_text, -1);
1779                 }
1780
1781         g_free(entry_text);
1782         return FALSE;
1783 }
1784
1785 static gint autocomplete_sort_iter_compare_func (GtkTreeModel *model,
1786                                                                         GtkTreeIter *a,
1787                                                                         GtkTreeIter *b,
1788                                                                         gpointer)
1789 {
1790         gint ret = 0;
1791         gchar *name1, *name2;
1792
1793         gtk_tree_model_get(model, a, 0, &name1, -1);
1794         gtk_tree_model_get(model, b, 0, &name2, -1);
1795
1796         if (name1 == nullptr || name2 == nullptr)
1797                 {
1798                 if (name1 == nullptr && name2 == nullptr)
1799                         {
1800                         ret = 0;
1801                         }
1802                 else
1803                         {
1804                         ret = (name1 == nullptr) ? -1 : 1;
1805                         }
1806                 }
1807         else
1808                 {
1809                 ret = g_utf8_collate(name1,name2);
1810                 }
1811
1812         g_free(name1);
1813         g_free(name2);
1814
1815         return ret;
1816 }
1817
1818 static void autocomplete_keywords_list_load(const gchar *path)
1819 {
1820         FILE *f;
1821         gchar s_buf[1024];
1822         gchar *pathl;
1823         gint len;
1824         GtkTreeIter iter;
1825         GtkTreeSortable *sortable;
1826
1827         if (keyword_store) return;
1828         keyword_store = gtk_list_store_new(1, G_TYPE_STRING);
1829
1830         sortable = GTK_TREE_SORTABLE(keyword_store);
1831         gtk_tree_sortable_set_sort_func(sortable, 0, autocomplete_sort_iter_compare_func,
1832                                                                                                 GINT_TO_POINTER(0), nullptr);
1833
1834         gtk_tree_sortable_set_sort_column_id(sortable, 0, GTK_SORT_ASCENDING);
1835
1836         pathl = path_from_utf8(path);
1837         f = fopen(pathl, "r");
1838
1839         if (!f)
1840                 {
1841                 log_printf("Warning: keywords file %s not loaded", pathl);
1842                 g_free(pathl);
1843                 return;
1844                 }
1845
1846         /* first line must start with Keywords comment */
1847         if (!fgets(s_buf, sizeof(s_buf), f) ||
1848                                         strncmp(s_buf, "#Keywords", 9) != 0)
1849                 {
1850                 fclose(f);
1851                 log_printf("Warning: keywords file %s not loaded", pathl);
1852                 g_free(pathl);
1853                 return;
1854                 }
1855
1856         g_free(pathl);
1857
1858         while (fgets(s_buf, sizeof(s_buf), f))
1859                 {
1860                 if (s_buf[0]=='#') continue;
1861
1862                 len = strlen(s_buf);
1863                 if( s_buf[len-1] == '\n' )
1864                         {
1865                         s_buf[len-1] = 0;
1866                         }
1867                 gtk_list_store_append (keyword_store, &iter);
1868                 gtk_list_store_set(keyword_store, &iter, 0, g_strdup(s_buf), -1);
1869                 }
1870
1871         fclose(f);
1872 }
1873
1874 static gboolean autocomplete_keywords_list_save(gchar *path)
1875 {
1876         SecureSaveInfo *ssi;
1877         gchar *pathl;
1878         gchar *string;
1879         gchar *string_nl;
1880         GtkTreeIter  iter;
1881         gboolean     valid;
1882
1883         pathl = path_from_utf8(path);
1884         ssi = secure_open(pathl);
1885         g_free(pathl);
1886
1887         if (!ssi)
1888                 {
1889                 log_printf(_("Error: Unable to write keywords list to: %s\n"), path);
1890                 return FALSE;
1891                 }
1892
1893         secure_fprintf(ssi, "#Keywords list\n");
1894
1895         valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter);
1896
1897         while (valid)
1898                 {
1899                 gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter, 0, &string, -1);
1900                 string_nl = g_strconcat(string, "\n", NULL);
1901                 secure_fprintf(ssi, "%s", string_nl);
1902
1903                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter);
1904
1905                 g_free(string_nl);
1906                 }
1907
1908         secure_fprintf(ssi, "#end\n");
1909         return (secure_close(ssi) == 0);
1910 }
1911
1912 GList *keyword_list_get()
1913 {
1914         GList *ret_list = nullptr;
1915         gchar *string;
1916         gchar *string_nl;
1917         GtkTreeIter iter;
1918         gboolean valid;
1919
1920         if (keyword_store)
1921                 {
1922                 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter);
1923
1924                 while (valid)
1925                         {
1926                         gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter, 0, &string, -1);
1927                         string_nl = g_strconcat(string, "\n", NULL);
1928                         ret_list = g_list_append(ret_list, string);
1929                         valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter);
1930
1931                         g_free(string_nl);
1932                         }
1933                 }
1934
1935         return ret_list;
1936 }
1937
1938 void keyword_list_set(GList *keyword_list)
1939 {
1940         GtkTreeIter  iter;
1941
1942         if (!keyword_list) return;
1943
1944         if (keyword_store)
1945                 {
1946                 gtk_list_store_clear(keyword_store);
1947                 }
1948         else
1949                 {
1950                 keyword_store = gtk_list_store_new(1, G_TYPE_STRING);
1951                 }
1952
1953         while (keyword_list)
1954                 {
1955                 gtk_list_store_append (keyword_store, &iter);
1956                 gtk_list_store_set(keyword_store, &iter, 0, keyword_list->data, -1);
1957
1958                 keyword_list = keyword_list->next;
1959                 }
1960 }
1961
1962 gboolean bar_keywords_autocomplete_focus(LayoutWindow *lw)
1963 {
1964         GtkWidget *pane;
1965         GtkWidget *current_focus;
1966         GList *children;
1967         gboolean ret;
1968
1969         current_focus = gtk_window_get_focus(GTK_WINDOW(lw->window));
1970         pane = bar_find_pane_by_id(lw->bar, PANE_KEYWORDS, "keywords");
1971
1972         children = gtk_container_get_children(GTK_CONTAINER(pane));
1973         children = g_list_last(children);
1974
1975         if (current_focus == children->data)
1976                 {
1977                 ret = TRUE;
1978                 }
1979         else
1980                 {
1981                 gtk_widget_grab_focus(GTK_WIDGET(children->data));
1982                 ret = FALSE;
1983                 }
1984
1985         g_list_free(children);
1986
1987         return ret;
1988 }
1989 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */