Additional keyword menu entries
[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);
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
1020 static void bar_pane_keywords_delete_cb(GtkWidget *menu_widget, gpointer data)
1021 {
1022         PaneKeywordsData *pkd = data;
1023         GtkTreeModel *model;
1024         GtkTreeIter iter;
1025
1026         GtkTreeModel *keyword_tree;
1027         GtkTreeIter kw_iter;
1028
1029         if (!pkd->click_tpath) return;
1030
1031         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1032         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1033
1034         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1035         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1036
1037         keyword_delete(GTK_TREE_STORE(keyword_tree), &kw_iter);
1038 }
1039
1040 static void bar_pane_keywords_hide_cb(GtkWidget *menu_widget, gpointer data)
1041 {
1042         PaneKeywordsData *pkd = data;
1043         GtkTreeModel *model;
1044         GtkTreeIter iter;
1045
1046         GtkTreeModel *keyword_tree;
1047         GtkTreeIter kw_iter;
1048
1049         if (!pkd->click_tpath) return;
1050
1051         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1052         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1053
1054         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1055         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1056
1057         keyword_hide_in(GTK_TREE_STORE(keyword_tree), &kw_iter, model);
1058 }
1059
1060 static void bar_pane_keywords_show_all_cb(GtkWidget *menu_widget, gpointer data)
1061 {
1062         PaneKeywordsData *pkd = data;
1063         GtkTreeModel *model;
1064
1065         GtkTreeModel *keyword_tree;
1066
1067         string_list_free(pkd->expanded_rows);
1068         pkd->expanded_rows = NULL;
1069         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
1070                                                                 (bar_keyword_tree_get_expanded_cb), &pkd->expanded_rows);
1071
1072         pkd->hide_unchecked = FALSE;
1073
1074         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1075         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1076
1077         keyword_show_all_in(GTK_TREE_STORE(keyword_tree), model);
1078
1079         if (!pkd->collapse_unchecked) gtk_tree_view_expand_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1080         bar_keyword_tree_sync(pkd);
1081 }
1082
1083 static void bar_pane_keywords_revert_cb(GtkWidget *menu_widget, gpointer data)
1084 {
1085         PaneKeywordsData *pkd = data;
1086         GList *work;
1087         GtkTreePath *tree_path;
1088         gchar *path;
1089
1090         gtk_tree_view_collapse_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1091
1092         work = pkd->expanded_rows;
1093         while (work)
1094                 {
1095                 path = work->data;
1096                 tree_path = gtk_tree_path_new_from_string(path);
1097                 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), tree_path);
1098                 work = work->next;
1099                 gtk_tree_path_free(tree_path);
1100                 }
1101
1102         bar_keyword_tree_sync(pkd);
1103 }
1104
1105 static void bar_pane_keywords_expand_checked_cb(GtkWidget *menu_widget, gpointer data)
1106 {
1107         PaneKeywordsData *pkd = data;
1108         GtkTreeModel *model;
1109
1110         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1111         gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
1112 }
1113
1114 static void bar_pane_keywords_collapse_all_cb(GtkWidget *menu_widget, gpointer data)
1115 {
1116         PaneKeywordsData *pkd = data;
1117
1118         string_list_free(pkd->expanded_rows);
1119         pkd->expanded_rows = NULL;
1120         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
1121                                                                 (bar_keyword_tree_get_expanded_cb), &pkd->expanded_rows);
1122
1123         gtk_tree_view_collapse_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1124
1125         bar_keyword_tree_sync(pkd);
1126 }
1127
1128 static void bar_pane_keywords_revert_hidden_cb(GtkWidget *menu_widget, gpointer data)
1129 {
1130         PaneKeywordsData *pkd = data;
1131         GtkTreeModel *model;
1132         GtkTreeModel *keyword_tree;
1133
1134         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1135         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1136
1137         keyword_revert_hidden_in(GTK_TREE_STORE(keyword_tree), model);
1138
1139         bar_keyword_tree_sync(pkd);
1140 }
1141
1142 static void bar_pane_keywords_collapse_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1143 {
1144         PaneKeywordsData *pkd = data;
1145         GtkTreeModel *model;
1146
1147         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1148         gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
1149 }
1150
1151 static void bar_pane_keywords_hide_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1152 {
1153         PaneKeywordsData *pkd = data;
1154         GtkTreeModel *model;
1155
1156         GtkTreeModel *keyword_tree;
1157         GList *keywords;
1158
1159         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1160         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1161
1162         keywords = keyword_list_pull(pkd->keyword_view);
1163         keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
1164         string_list_free(keywords);
1165         bar_keyword_tree_sync(pkd);
1166 }
1167
1168 static void bar_pane_keywords_expand_checked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1169 {
1170         PaneKeywordsData *pkd = data;
1171         pkd->expand_checked = !pkd->expand_checked;
1172         bar_keyword_tree_sync(pkd);
1173 }
1174
1175 static void bar_pane_keywords_collapse_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1176 {
1177         PaneKeywordsData *pkd = data;
1178         pkd->collapse_unchecked = !pkd->collapse_unchecked;
1179         bar_keyword_tree_sync(pkd);
1180 }
1181
1182 static void bar_pane_keywords_hide_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1183 {
1184         PaneKeywordsData *pkd = data;
1185         pkd->hide_unchecked = !pkd->hide_unchecked;
1186         bar_keyword_tree_sync(pkd);
1187 }
1188
1189 /**
1190  * \brief Callback for adding selected keyword to all selected images.
1191  */
1192 static void bar_pane_keywords_add_to_selected_cb(GtkWidget *menu_widget, gpointer data)
1193 {
1194         PaneKeywordsData *pkd = data;
1195         GtkTreeIter iter; /* This is the iter which initial holds the current keyword */
1196         GtkTreeIter child_iter;
1197         GtkTreeModel *model;
1198         GtkTreeModel *keyword_tree;
1199         GList *list, *work;
1200         GList *keywords = NULL;
1201
1202         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1203
1204         /* Aquire selected keyword */
1205         if (pkd->click_tpath)
1206                 {
1207                 gboolean is_keyword = TRUE;
1208
1209                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1210                 if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1211                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1212                 if (!is_keyword) return;
1213                 }
1214         else
1215                 return;
1216
1217         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1218         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
1219
1220         list = keyword_list_pull(pkd->keyword_view); /* Get the left keyword view */
1221
1222         /* Now set the current image */
1223         keyword_tree_set(keyword_tree, &child_iter, &list);
1224
1225         keyword_list_push(pkd->keyword_view, list); /* Set the left keyword view */
1226         string_list_free(list);
1227
1228         bar_pane_keywords_changed(keyword_buffer, pkd); /* Get list of all keywords in the hierarchy */
1229
1230         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
1231         keywords = keyword_tree_get(keyword_tree, &child_iter);
1232
1233         list = layout_selection_list(pkd->pane.lw);
1234         work = list;
1235         while (work)
1236                 {
1237                 FileData *fd = work->data;
1238                 work = work->next;
1239                 metadata_append_list(fd, KEYWORD_KEY, keywords);
1240                 }
1241         filelist_free(list);
1242         string_list_free(keywords);
1243 }
1244
1245 static void bar_pane_keywords_menu_popup(GtkWidget *widget, PaneKeywordsData *pkd, gint x, gint y)
1246 {
1247         GtkWidget *menu;
1248         GtkWidget *item;
1249         GtkWidget *submenu;
1250         GtkTreeViewDropPosition pos;
1251
1252         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1253         pkd->click_tpath = NULL;
1254         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(pkd->keyword_treeview), x, y, &pkd->click_tpath, &pos);
1255
1256         menu = popup_menu_short_lived();
1257
1258         menu_item_add_stock(menu, _("New keyword"), GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_add_dialog_cb), pkd);
1259
1260         menu_item_add_divider(menu);
1261
1262         menu_item_add(menu, _("Add keyword to all selected images"), G_CALLBACK(bar_pane_keywords_add_to_selected_cb), pkd);
1263
1264         menu_item_add_divider(menu);
1265
1266         if (pkd->click_tpath)
1267                 {
1268                 /* for the entry */
1269                 gchar *text;
1270                 gchar *mark;
1271                 gint i;
1272
1273                 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1274
1275                 GtkTreeIter iter;
1276                 gtk_tree_model_get_iter(model, &iter, pkd->click_tpath);
1277                 gchar *name;
1278
1279                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
1280                                                  FILTER_KEYWORD_COLUMN_MARK, &mark, -1);
1281
1282                 text = g_strdup_printf(_("Hide \"%s\""), name);
1283                 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_hide_cb), pkd);
1284                 g_free(text);
1285
1286                 submenu = gtk_menu_new();
1287                 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1288                         {
1289                         text = g_strdup_printf(_("Mark %d"), i + 1);
1290                         item = menu_item_add(submenu, text, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1291                         g_object_set_data(G_OBJECT(item), "mark", GINT_TO_POINTER(i + 1));
1292                         g_free(text);
1293                         }
1294                 text = g_strdup_printf(_("Connect \"%s\" to mark"), name);
1295                 item = menu_item_add(menu, text, NULL, NULL);
1296                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1297                 g_free(text);
1298
1299                 menu_item_add_divider(menu);
1300
1301                 text = g_strdup_printf(_("Edit \"%s\""), name);
1302                 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_edit_dialog_cb), pkd);
1303                 g_free(text);
1304                 text = g_strdup_printf(_("Remove \"%s\""), name);
1305                 menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_delete_cb), pkd);
1306                 g_free(text);
1307
1308
1309                 if (mark && mark[0])
1310                         {
1311                         text = g_strdup_printf(_("Disconnect \"%s\" from mark %s"), name, mark);
1312                         menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1313                         g_free(text);
1314                         }
1315
1316                 menu_item_add_divider(menu);
1317                 g_free(mark);
1318                 g_free(name);
1319                 }
1320         /* for the pane */
1321
1322
1323         menu_item_add(menu, _("Expand checked"), G_CALLBACK(bar_pane_keywords_expand_checked_cb), pkd);
1324         menu_item_add(menu, _("Collapse unchecked"), G_CALLBACK(bar_pane_keywords_collapse_unchecked_cb), pkd);
1325         menu_item_add(menu, _("Hide unchecked"), G_CALLBACK(bar_pane_keywords_hide_unchecked_cb), pkd);
1326         menu_item_add(menu, _("Revert all hidden"), G_CALLBACK(bar_pane_keywords_revert_hidden_cb), pkd);
1327         menu_item_add_divider(menu);
1328         menu_item_add(menu, _("Show all"), G_CALLBACK(bar_pane_keywords_show_all_cb), pkd);
1329         menu_item_add(menu, _("Collapse all"), G_CALLBACK(bar_pane_keywords_collapse_all_cb), pkd);
1330         menu_item_add(menu, _("Revert"), G_CALLBACK(bar_pane_keywords_revert_cb), pkd);
1331         menu_item_add_divider(menu);
1332
1333         submenu = gtk_menu_new();
1334         item = menu_item_add(menu, _("On any change"), NULL, NULL);
1335         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1336
1337         menu_item_add_check(submenu, _("Expand checked"), pkd->expand_checked, G_CALLBACK(bar_pane_keywords_expand_checked_toggle_cb), pkd);
1338         menu_item_add_check(submenu, _("Collapse unchecked"), pkd->collapse_unchecked, G_CALLBACK(bar_pane_keywords_collapse_unchecked_toggle_cb), pkd);
1339         menu_item_add_check(submenu, _("Hide unchecked"), pkd->hide_unchecked, G_CALLBACK(bar_pane_keywords_hide_unchecked_toggle_cb), pkd);
1340
1341         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
1342 }
1343
1344
1345 static gboolean bar_pane_keywords_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1346 {
1347         PaneKeywordsData *pkd = data;
1348         if (bevent->button == MOUSE_BUTTON_RIGHT)
1349                 {
1350                 bar_pane_keywords_menu_popup(widget, pkd, bevent->x, bevent->y);
1351                 return TRUE;
1352                 }
1353         return FALSE;
1354 }
1355
1356 /*
1357  *-------------------------------------------------------------------
1358  * init
1359  *-------------------------------------------------------------------
1360  */
1361
1362 void bar_pane_keywords_close(GtkWidget *bar)
1363 {
1364         PaneKeywordsData *pkd;
1365
1366         pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
1367         if (!pkd) return;
1368
1369         g_free(pkd->pane.id);
1370         gtk_widget_destroy(pkd->widget);
1371 }
1372
1373 static void bar_pane_keywords_destroy(GtkWidget *widget, gpointer data)
1374 {
1375         PaneKeywordsData *pkd = data;
1376
1377         string_list_free(pkd->expanded_rows);
1378         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1379         if (pkd->idle_id) g_source_remove(pkd->idle_id);
1380         file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
1381
1382         file_data_unref(pkd->fd);
1383         g_free(pkd->key);
1384
1385         g_free(pkd);
1386 }
1387
1388
1389 static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded, gint height)
1390 {
1391         PaneKeywordsData *pkd;
1392         GtkWidget *hbox;
1393         GtkWidget *scrolled;
1394         GtkTextBuffer *buffer;
1395         GtkTreeModel *store;
1396         GtkTreeViewColumn *column;
1397         GtkCellRenderer *renderer;
1398         GtkTreeIter iter;
1399
1400         pkd = g_new0(PaneKeywordsData, 1);
1401
1402         pkd->pane.pane_set_fd = bar_pane_keywords_set_fd;
1403         pkd->pane.pane_event = bar_pane_keywords_event;
1404         pkd->pane.pane_write_config = bar_pane_keywords_write_config;
1405         pkd->pane.title = bar_pane_expander_title(title);
1406         pkd->pane.id = g_strdup(id);
1407         pkd->pane.type = PANE_KEYWORDS;
1408
1409         pkd->pane.expanded = expanded;
1410
1411         pkd->height = height;
1412         pkd->key = g_strdup(key);
1413
1414         pkd->expand_checked = TRUE;
1415         pkd->expanded_rows = NULL;
1416
1417         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
1418
1419         pkd->widget = hbox;
1420         g_object_set_data(G_OBJECT(pkd->widget), "pane_data", pkd);
1421         g_signal_connect(G_OBJECT(pkd->widget), "destroy",
1422                          G_CALLBACK(bar_pane_keywords_destroy), pkd);
1423         gtk_widget_set_size_request(pkd->widget, -1, height);
1424         gtk_widget_show(hbox);
1425
1426         scrolled = gtk_scrolled_window_new(NULL, NULL);
1427         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1428         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1429                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1430         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1431         gtk_widget_show(scrolled);
1432
1433         pkd->keyword_view = gtk_text_view_new();
1434         gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_view);
1435         g_signal_connect(G_OBJECT(pkd->keyword_view), "populate-popup",
1436                          G_CALLBACK(bar_pane_keywords_populate_popup_cb), pkd);
1437         gtk_widget_show(pkd->keyword_view);
1438
1439         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1440         g_signal_connect(G_OBJECT(buffer), "changed",
1441                          G_CALLBACK(bar_pane_keywords_changed), pkd);
1442
1443         scrolled = gtk_scrolled_window_new(NULL, NULL);
1444         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1445         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1446                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1447         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1448         gtk_widget_show(scrolled);
1449
1450
1451         if (!keyword_tree || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1452                 {
1453                 /* keyword tree does not exist or is empty - fill with defaults */
1454                 keyword_tree_new_default();
1455                 }
1456
1457         store = gtk_tree_model_filter_new(GTK_TREE_MODEL(keyword_tree), NULL);
1458
1459         gtk_tree_model_filter_set_modify_func(GTK_TREE_MODEL_FILTER(store),
1460                                               FILTER_KEYWORD_COLUMN_COUNT,
1461                                               filter_keyword_column_types,
1462                                               bar_pane_keywords_filter_modify,
1463                                               pkd,
1464                                               NULL);
1465         gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(store),
1466                                                bar_pane_keywords_filter_visible,
1467                                                store,
1468                                                NULL);
1469
1470         pkd->keyword_treeview = gtk_tree_view_new_with_model(store);
1471         g_object_unref(store);
1472
1473         gtk_widget_set_size_request(pkd->keyword_treeview, -1, 400);
1474
1475         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pkd->keyword_treeview), FALSE);
1476
1477 //      gtk_tree_view_set_search_column(GTK_TREE_VIEW(pkd->keyword_treeview), FILTER_KEYWORD_COLUMN_);
1478
1479         column = gtk_tree_view_column_new();
1480         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1481
1482         renderer = gtk_cell_renderer_text_new();
1483         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1484
1485         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_MARK);
1486
1487         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1488
1489         column = gtk_tree_view_column_new();
1490         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1491         renderer = gtk_cell_renderer_toggle_new();
1492         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1493         gtk_tree_view_column_add_attribute(column, renderer, "active", FILTER_KEYWORD_COLUMN_TOGGLE);
1494         gtk_tree_view_column_add_attribute(column, renderer, "visible", FILTER_KEYWORD_COLUMN_IS_KEYWORD);
1495         g_signal_connect(G_OBJECT(renderer), "toggled",
1496                          G_CALLBACK(bar_pane_keywords_keyword_toggle), pkd);
1497
1498         renderer = gtk_cell_renderer_text_new();
1499         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1500         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_NAME);
1501
1502         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1503         gtk_tree_view_set_expander_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1504
1505         gtk_drag_source_set(pkd->keyword_treeview,
1506                             GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
1507                             bar_pane_keywords_drag_types, n_keywords_drag_types,
1508                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1509
1510         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_get",
1511                          G_CALLBACK(bar_pane_keywords_dnd_get), pkd);
1512
1513         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_begin",
1514                          G_CALLBACK(bar_pane_keywords_dnd_begin), pkd);
1515         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_end",
1516                          G_CALLBACK(bar_pane_keywords_dnd_end), pkd);
1517
1518         gtk_drag_dest_set(pkd->keyword_treeview,
1519                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
1520                           bar_pane_keywords_drop_types, n_keywords_drop_types,
1521                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
1522
1523         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_received",
1524                          G_CALLBACK(bar_pane_keywords_dnd_receive), pkd);
1525
1526         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_motion",
1527                          G_CALLBACK(bar_pane_keywords_dnd_motion), pkd);
1528
1529         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "button_release_event",
1530                          G_CALLBACK(bar_pane_keywords_menu_cb), pkd);
1531
1532         gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_treeview);
1533         gtk_widget_show(pkd->keyword_treeview);
1534
1535         file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
1536
1537         return pkd->widget;
1538 }
1539
1540 GtkWidget *bar_pane_keywords_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
1541 {
1542         gchar *id = g_strdup("keywords");
1543         gchar *title = NULL;
1544         gchar *key = g_strdup(COMMENT_KEY);
1545         gboolean expanded = TRUE;
1546         gint height = 200;
1547         GtkWidget *ret;
1548
1549         while (*attribute_names)
1550                 {
1551                 const gchar *option = *attribute_names++;
1552                 const gchar *value = *attribute_values++;
1553
1554                 if (READ_CHAR_FULL("id", id)) continue;
1555                 if (READ_CHAR_FULL("title", title)) continue;
1556                 if (READ_CHAR_FULL("key", key)) continue;
1557                 if (READ_BOOL_FULL("expanded", expanded)) continue;
1558                 if (READ_INT_FULL("height", height)) continue;
1559
1560
1561                 log_printf("unknown attribute %s = %s\n", option, value);
1562                 }
1563
1564         options->info_keywords.height = height;
1565         bar_pane_translate_title(PANE_KEYWORDS, id, &title);
1566         ret = bar_pane_keywords_new(id, title, key, expanded, height);
1567         g_free(id);
1568         g_free(title);
1569         g_free(key);
1570         return ret;
1571 }
1572
1573 void bar_pane_keywords_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1574 {
1575         PaneKeywordsData *pkd;
1576
1577         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
1578         if (!pkd) return;
1579
1580         gchar *title = NULL;
1581
1582         while (*attribute_names)
1583                 {
1584                 const gchar *option = *attribute_names++;
1585                 const gchar *value = *attribute_values++;
1586
1587                 if (READ_CHAR_FULL("title", title)) continue;
1588                 if (READ_CHAR_FULL("key", pkd->key)) continue;
1589                 if (READ_BOOL_FULL("expanded", pkd->pane.expanded)) continue;
1590                 if (READ_CHAR_FULL("id", pkd->pane.id)) continue;
1591
1592
1593                 log_printf("unknown attribute %s = %s\n", option, value);
1594                 }
1595
1596         if (title)
1597                 {
1598                 bar_pane_translate_title(PANE_KEYWORDS, pkd->pane.id, &title);
1599                 gtk_label_set_text(GTK_LABEL(pkd->pane.title), title);
1600                 g_free(title);
1601                 }
1602
1603         bar_update_expander(pane);
1604         bar_pane_keywords_update(pkd);
1605 }
1606
1607
1608 void bar_pane_keywords_entry_add_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1609 {
1610         PaneKeywordsData *pkd;
1611         gchar *path = NULL;
1612         GtkTreePath *tree_path;
1613
1614         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
1615         if (!pkd) return;
1616
1617         while (*attribute_names)
1618                 {
1619                 const gchar *option = *attribute_names++;
1620                 const gchar *value = *attribute_values++;
1621
1622                 if (READ_CHAR_FULL("path", path))
1623                         {
1624                         tree_path = gtk_tree_path_new_from_string(path);
1625                         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), tree_path);
1626                         gtk_tree_path_free(tree_path);
1627                         pkd->expanded_rows = g_list_append(pkd->expanded_rows, g_strdup(path));
1628                         continue;
1629                         }
1630                 log_printf("unknown attribute %s = %s\n", option, value);
1631                 }
1632 }
1633 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */