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