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