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