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