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