added 2010 to copyright text
[geeqie.git] / src / bar_keywords.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 - 2010 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #include <glib/gprintf.h>
14
15 #include "main.h"
16 #include "bar_keywords.h"
17
18 #include "filedata.h"
19 #include "history_list.h"
20 #include "metadata.h"
21 #include "misc.h"
22 #include "ui_fileops.h"
23 #include "ui_misc.h"
24 #include "ui_utildlg.h"
25 #include "utilops.h"
26 #include "bar.h"
27 #include "ui_menu.h"
28 #include "rcfile.h"
29 #include "layout.h"
30 #include "dnd.h"
31
32
33 //static void bar_pane_keywords_keyword_update_all(void);
34 static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data);
35
36 /*
37  *-------------------------------------------------------------------
38  * keyword / comment utils
39  *-------------------------------------------------------------------
40  */
41
42
43 GList *keyword_list_pull(GtkWidget *text_widget)
44 {
45         GList *list;
46         gchar *text;
47
48         text = text_widget_text_pull(text_widget);
49         list = string_to_keywords_list(text);
50
51         g_free(text);
52
53         return list;
54 }
55
56 /* the "changed" signal should be blocked before calling this */
57 static void keyword_list_push(GtkWidget *textview, GList *list)
58 {
59         GtkTextBuffer *buffer;
60         GtkTextIter start, end;
61
62         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
63         gtk_text_buffer_get_bounds(buffer, &start, &end);
64         gtk_text_buffer_delete(buffer, &start, &end);
65
66         while (list)
67                 {
68                 const gchar *word = list->data;
69                 GtkTextIter iter;
70
71                 gtk_text_buffer_get_end_iter(buffer, &iter);
72                 if (word) gtk_text_buffer_insert(buffer, &iter, word, -1);
73                 gtk_text_buffer_get_end_iter(buffer, &iter);
74                 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
75
76                 list = list->next;
77                 }
78 }
79
80
81 /*
82  *-------------------------------------------------------------------
83  * info bar
84  *-------------------------------------------------------------------
85  */
86
87
88 enum {
89         FILTER_KEYWORD_COLUMN_TOGGLE = 0,
90         FILTER_KEYWORD_COLUMN_MARK,
91         FILTER_KEYWORD_COLUMN_NAME,
92         FILTER_KEYWORD_COLUMN_IS_KEYWORD,
93         FILTER_KEYWORD_COLUMN_COUNT
94 };
95
96 static GType filter_keyword_column_types[] = {G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN};
97
98 typedef struct _PaneKeywordsData PaneKeywordsData;
99 struct _PaneKeywordsData
100 {
101         PaneData pane;
102         GtkWidget *widget;
103
104         GtkWidget *keyword_view;
105         GtkWidget *keyword_treeview;
106
107         GtkTreePath *click_tpath;
108
109         gboolean expand_checked;
110         gboolean collapse_unchecked;
111         gboolean hide_unchecked;
112
113         guint idle_id; /* event source id */    
114         FileData *fd;
115         gchar *key;
116 };
117
118 typedef struct _ConfDialogData ConfDialogData;
119 struct _ConfDialogData
120 {
121         PaneKeywordsData *pkd;
122         GtkTreePath *click_tpath;
123         
124         /* dialog parts */
125         GenericDialog *gd;
126         GtkWidget *edit_widget;
127         gboolean is_keyword;
128         
129         gboolean edit_existing;
130 };
131
132 //static GList *bar_list = NULL;
133
134
135 static void bar_pane_keywords_write(PaneKeywordsData *pkd)
136 {
137         GList *list;
138
139         if (!pkd->fd) return;
140
141         list = keyword_list_pull(pkd->keyword_view);
142
143         metadata_write_list(pkd->fd, KEYWORD_KEY, list);
144
145         string_list_free(list);
146 }
147
148 gboolean bar_keyword_tree_expand_if_set_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
149 {
150         PaneKeywordsData *pkd = data;
151         gboolean set;
152
153         gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
154         
155         if (set && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
156                 {
157                 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), path);
158                 }
159         return FALSE;
160 }
161
162 gboolean bar_keyword_tree_collapse_if_unset_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
163 {
164         PaneKeywordsData *pkd = data;
165         gboolean set;
166
167         gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
168         
169         if (!set && gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
170                 {
171                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(pkd->keyword_treeview), path);
172                 }
173         return FALSE;
174 }
175
176 static void bar_keyword_tree_sync(PaneKeywordsData *pkd)
177 {
178         GtkTreeModel *model;
179
180         GtkTreeModel *keyword_tree;
181         GList *keywords;
182
183         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
184         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
185
186         keywords = keyword_list_pull(pkd->keyword_view);
187         keyword_show_set_in(GTK_TREE_STORE(keyword_tree), model, keywords);
188         if (pkd->hide_unchecked) keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
189         string_list_free(keywords);
190
191         gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(model));
192
193         if (pkd->expand_checked) gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
194         if (pkd->collapse_unchecked) gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
195 }
196
197 #if 0
198 static void bar_pane_keywords_keyword_update_all(void)
199 {
200         GList *work;
201
202         work = bar_list;
203         while (work)
204                 {
205                 PaneKeywordsData *pkd;
206 //              GList *keywords;
207
208                 pkd = work->data;
209                 work = work->next;
210
211                 bar_keyword_tree_sync(pkd);
212                 }
213 }
214 #endif
215
216 static void bar_pane_keywords_update(PaneKeywordsData *pkd)
217 {
218         GList *keywords = NULL;
219         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
220
221         g_signal_handlers_block_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
222
223         keywords = metadata_read_list(pkd->fd, KEYWORD_KEY, METADATA_PLAIN);
224         keyword_list_push(pkd->keyword_view, keywords);
225         bar_keyword_tree_sync(pkd);
226         string_list_free(keywords);
227         
228         g_signal_handlers_unblock_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
229
230 }
231
232 void bar_pane_keywords_set_fd(GtkWidget *pane, FileData *fd)
233 {
234         PaneKeywordsData *pkd;
235
236         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
237         if (!pkd) return;
238
239         file_data_unref(pkd->fd);
240         pkd->fd = file_data_ref(fd);
241
242         bar_pane_keywords_update(pkd);
243 }
244
245 static void bar_pane_keywords_write_config(GtkWidget *pane, GString *outstr, gint indent)
246 {
247         PaneKeywordsData *pkd;
248
249         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
250         if (!pkd) return;
251
252         WRITE_NL(); WRITE_STRING("<pane_keywords ");
253         write_char_option(outstr, indent, "id", pkd->pane.id);
254         write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(pkd->pane.title)));
255         WRITE_BOOL(pkd->pane, expanded);
256         WRITE_CHAR(*pkd, key);
257         WRITE_STRING("/>");
258 }
259
260 gint bar_pane_keywords_event(GtkWidget *bar, GdkEvent *event)
261 {
262         PaneKeywordsData *pkd;
263
264         pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
265         if (!pkd) return FALSE;
266
267         if (GTK_WIDGET_HAS_FOCUS(pkd->keyword_view)) return gtk_widget_event(pkd->keyword_view, event);
268
269         return FALSE;
270 }
271
272 static void bar_pane_keywords_keyword_toggle(GtkCellRendererToggle *toggle, const gchar *path, gpointer data)
273 {
274         PaneKeywordsData *pkd = data;
275         GtkTreeModel *model;
276         GtkTreeIter iter;
277         GtkTreePath *tpath;
278         gboolean active;
279         GList *list;
280         GtkTreeIter child_iter;
281         GtkTreeModel *keyword_tree;
282         
283         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
284
285         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
286
287         tpath = gtk_tree_path_new_from_string(path);
288         gtk_tree_model_get_iter(model, &iter, tpath);
289         gtk_tree_path_free(tpath);
290
291         gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_TOGGLE, &active, -1);
292         active = (!active);
293
294
295         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
296         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
297
298         list = keyword_list_pull(pkd->keyword_view);
299         if (active) 
300                 keyword_tree_set(keyword_tree, &child_iter, &list);
301         else
302                 keyword_tree_reset(keyword_tree, &child_iter, &list);
303         
304         g_signal_handlers_block_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
305         keyword_list_push(pkd->keyword_view, list);
306         string_list_free(list);
307         g_signal_handlers_unblock_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
308
309         /* call this just once in the end */
310         bar_pane_keywords_changed(keyword_buffer, pkd);
311         /*
312           bar_pane_keywords_change calls bar_keyword_tree_sync, no need to do it again
313         bar_keyword_tree_sync(pkd);
314         */
315 }
316
317 void bar_pane_keywords_filter_modify(GtkTreeModel *model, GtkTreeIter *iter, GValue *value, gint column, gpointer data)
318 {
319         PaneKeywordsData *pkd = data;
320         GtkTreeModel *keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
321         GtkTreeIter child_iter;
322
323         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, iter);
324         
325         memset(value, 0, sizeof (GValue));
326
327         switch (column)
328                 {
329                 case FILTER_KEYWORD_COLUMN_TOGGLE:
330                         {
331                         GList *keywords = keyword_list_pull(pkd->keyword_view);
332                         gboolean set = keyword_tree_is_set(keyword_tree, &child_iter, keywords);
333                         string_list_free(keywords);
334                         
335                         g_value_init(value, G_TYPE_BOOLEAN);
336                         g_value_set_boolean(value, set);
337                         break;
338                         }
339                 case FILTER_KEYWORD_COLUMN_MARK:
340                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_MARK, value);
341                         break;
342                 case FILTER_KEYWORD_COLUMN_NAME:
343                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_NAME, value);
344                         break;
345                 case FILTER_KEYWORD_COLUMN_IS_KEYWORD:
346                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_IS_KEYWORD, value);
347                         break;
348                 }
349         return;
350
351 }
352
353 gboolean bar_pane_keywords_filter_visible(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer data)
354 {
355         GtkTreeModel *filter = data;
356         
357         return !keyword_is_hidden_in(keyword_tree, iter, filter);
358 }
359
360 static void bar_pane_keywords_set_selection(PaneKeywordsData *pkd, gboolean append)
361 {
362         GList *keywords = NULL;
363         GList *list = NULL;
364         GList *work;
365
366         keywords = keyword_list_pull(pkd->keyword_view);
367
368         list = layout_selection_list(pkd->pane.lw);
369         list = file_data_process_groups_in_selection(list, FALSE, NULL);
370         
371         work = list;
372         while (work)
373                 {
374                 FileData *fd = work->data;
375                 work = work->next;
376
377                 if (append)
378                         {
379                         metadata_append_list(fd, KEYWORD_KEY, keywords);
380                         }
381                 else
382                         {
383                         metadata_write_list(fd, KEYWORD_KEY, keywords);
384                         }
385                 }
386
387         filelist_free(list);
388         string_list_free(keywords);
389 }
390
391 static void bar_pane_keywords_sel_add_cb(GtkWidget *button, gpointer data)
392 {
393         PaneKeywordsData *pkd = data;
394
395         bar_pane_keywords_set_selection(pkd, TRUE);
396 }
397
398 static void bar_pane_keywords_sel_replace_cb(GtkWidget *button, gpointer data)
399 {
400         PaneKeywordsData *pkd = data;
401
402         bar_pane_keywords_set_selection(pkd, FALSE);
403 }
404
405 static void bar_pane_keywords_populate_popup_cb(GtkTextView *textview, GtkMenu *menu, gpointer data)
406 {
407         PaneKeywordsData *pkd = data;
408
409         menu_item_add_divider(GTK_WIDGET(menu));
410         menu_item_add_stock(GTK_WIDGET(menu), _("Add keywords to selected files"), GTK_STOCK_ADD, G_CALLBACK(bar_pane_keywords_sel_add_cb), pkd);
411         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);
412 }
413
414
415 static void bar_pane_keywords_notify_cb(FileData *fd, NotifyType type, gpointer data)
416 {
417         PaneKeywordsData *pkd = data;
418         if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == pkd->fd) 
419                 {
420                 DEBUG_1("Notify pane_keywords: %s %04x", fd->path, type);
421                 bar_pane_keywords_update(pkd);
422                 }
423 }
424
425 static gboolean bar_pane_keywords_changed_idle_cb(gpointer data)
426 {
427         PaneKeywordsData *pkd = data;
428
429         file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
430         bar_pane_keywords_write(pkd);
431         bar_keyword_tree_sync(pkd);
432         file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
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, selection_data->target,
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 *)selection_data->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 *)selection_data->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") : _("Add keywords"), "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") : _("Add 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         file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
966
967         meta_data_connect_mark_with_keyword(keyword_tree, &kw_iter, mark);
968
969         file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
970 //      bar_pane_keywords_update(pkd);
971 }
972
973
974 static void bar_pane_keywords_delete_cb(GtkWidget *menu_widget, gpointer data)
975 {
976         PaneKeywordsData *pkd = data;
977         GtkTreeModel *model;
978         GtkTreeIter iter;
979
980         GtkTreeModel *keyword_tree;
981         GtkTreeIter kw_iter;
982
983         if (!pkd->click_tpath) return;
984
985         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
986         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
987
988         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
989         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
990         
991         keyword_delete(GTK_TREE_STORE(keyword_tree), &kw_iter);
992 }
993
994 static void bar_pane_keywords_hide_cb(GtkWidget *menu_widget, gpointer data)
995 {
996         PaneKeywordsData *pkd = data;
997         GtkTreeModel *model;
998         GtkTreeIter iter;
999
1000         GtkTreeModel *keyword_tree;
1001         GtkTreeIter kw_iter;
1002
1003         if (!pkd->click_tpath) return;
1004
1005         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1006         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1007
1008         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1009         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1010         
1011         keyword_hide_in(GTK_TREE_STORE(keyword_tree), &kw_iter, model);
1012 }
1013
1014 static void bar_pane_keywords_show_all_cb(GtkWidget *menu_widget, gpointer data)
1015 {
1016         PaneKeywordsData *pkd = data;
1017         GtkTreeModel *model;
1018
1019         GtkTreeModel *keyword_tree;
1020
1021         pkd->hide_unchecked = FALSE;
1022
1023         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1024         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1025
1026         keyword_show_all_in(GTK_TREE_STORE(keyword_tree), model);
1027         
1028         if (!pkd->collapse_unchecked) gtk_tree_view_expand_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1029         bar_keyword_tree_sync(pkd);
1030 }
1031
1032 static void bar_pane_keywords_expand_checked_cb(GtkWidget *menu_widget, gpointer data)
1033 {
1034         PaneKeywordsData *pkd = data;
1035         GtkTreeModel *model;
1036
1037         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1038         gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
1039 }
1040
1041 static void bar_pane_keywords_collapse_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1042 {
1043         PaneKeywordsData *pkd = data;
1044         GtkTreeModel *model;
1045
1046         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1047         gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
1048 }
1049
1050 static void bar_pane_keywords_hide_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1051 {
1052         PaneKeywordsData *pkd = data;
1053         GtkTreeModel *model;
1054
1055         GtkTreeModel *keyword_tree;
1056         GList *keywords;
1057         
1058         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1059         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1060
1061         keywords = keyword_list_pull(pkd->keyword_view);
1062         keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
1063         string_list_free(keywords);
1064         bar_keyword_tree_sync(pkd);
1065 }
1066
1067 static void bar_pane_keywords_expand_checked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1068 {
1069         PaneKeywordsData *pkd = data;
1070         pkd->expand_checked = !pkd->expand_checked;
1071         bar_keyword_tree_sync(pkd);
1072 }
1073
1074 static void bar_pane_keywords_collapse_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1075 {
1076         PaneKeywordsData *pkd = data;
1077         pkd->collapse_unchecked = !pkd->collapse_unchecked;
1078         bar_keyword_tree_sync(pkd);
1079 }
1080
1081 static void bar_pane_keywords_hide_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1082 {
1083         PaneKeywordsData *pkd = data;
1084         pkd->hide_unchecked = !pkd->hide_unchecked;
1085         bar_keyword_tree_sync(pkd);
1086 }
1087
1088 static void bar_pane_keywords_menu_popup(GtkWidget *widget, PaneKeywordsData *pkd, gint x, gint y)
1089 {
1090         GtkWidget *menu;
1091         GtkWidget *item;
1092         GtkWidget *submenu;
1093         GtkTreeViewDropPosition pos;
1094         
1095         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1096         pkd->click_tpath = NULL;
1097         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(pkd->keyword_treeview), x, y, &pkd->click_tpath, &pos);
1098
1099         menu = popup_menu_short_lived();
1100
1101         menu_item_add_stock(menu, _("Add keyword"), GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_add_dialog_cb), pkd);
1102         
1103         menu_item_add_divider(menu);
1104
1105         if (pkd->click_tpath)
1106                 {
1107                 /* for the entry */
1108                 gchar *text;
1109                 gchar *mark;
1110                 gint i;
1111                 
1112                 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1113                 
1114                 GtkTreeIter iter;
1115                 gtk_tree_model_get_iter(model, &iter, pkd->click_tpath);
1116                 gchar *name;
1117                 
1118                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
1119                                                  FILTER_KEYWORD_COLUMN_MARK, &mark, -1);
1120                 
1121                 text = g_strdup_printf(_("Hide \"%s\""), name);
1122                 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_hide_cb), pkd);
1123                 g_free(text);
1124                 
1125                 submenu = gtk_menu_new();
1126                 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1127                         {
1128                         text = g_strdup_printf(_("Mark %d"), i + 1);
1129                         item = menu_item_add(submenu, text, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1130                         g_object_set_data(G_OBJECT(item), "mark", GINT_TO_POINTER(i + 1));
1131                         g_free(text);
1132                         }
1133                 text = g_strdup_printf(_("Connect \"%s\" to mark"), name);
1134                 item = menu_item_add(menu, text, NULL, NULL);
1135                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1136                 g_free(text);
1137
1138                 menu_item_add_divider(menu);
1139
1140                 text = g_strdup_printf(_("Edit \"%s\""), name);
1141                 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_edit_dialog_cb), pkd);
1142                 g_free(text);
1143                 text = g_strdup_printf(_("Remove \"%s\""), name);
1144                 menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_delete_cb), pkd);
1145                 g_free(text);
1146
1147                 
1148                 if (mark && mark[0])
1149                         {
1150                         text = g_strdup_printf(_("Disconnect \"%s\" from mark %s"), name, mark);
1151                         menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1152                         g_free(text);
1153                         }
1154
1155                 menu_item_add_divider(menu);
1156                 g_free(mark);
1157                 g_free(name);
1158                 }
1159         /* for the pane */
1160
1161
1162         menu_item_add(menu, _("Expand checked"), G_CALLBACK(bar_pane_keywords_expand_checked_cb), pkd);
1163         menu_item_add(menu, _("Collapse unchecked"), G_CALLBACK(bar_pane_keywords_collapse_unchecked_cb), pkd);
1164         menu_item_add(menu, _("Hide unchecked"), G_CALLBACK(bar_pane_keywords_hide_unchecked_cb), pkd);
1165         menu_item_add(menu, _("Show all"), G_CALLBACK(bar_pane_keywords_show_all_cb), pkd);
1166
1167         submenu = gtk_menu_new();
1168         item = menu_item_add(menu, _("On any change"), NULL, NULL);
1169         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1170
1171         menu_item_add_check(submenu, _("Expand checked"), pkd->expand_checked, G_CALLBACK(bar_pane_keywords_expand_checked_toggle_cb), pkd);
1172         menu_item_add_check(submenu, _("Collapse unchecked"), pkd->collapse_unchecked, G_CALLBACK(bar_pane_keywords_collapse_unchecked_toggle_cb), pkd);
1173         menu_item_add_check(submenu, _("Hide unchecked"), pkd->hide_unchecked, G_CALLBACK(bar_pane_keywords_hide_unchecked_toggle_cb), pkd);
1174
1175         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
1176 }
1177
1178
1179 static gboolean bar_pane_keywords_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) 
1180
1181         PaneKeywordsData *pkd = data;
1182         if (bevent->button == MOUSE_BUTTON_RIGHT)
1183                 {
1184                 bar_pane_keywords_menu_popup(widget, pkd, bevent->x, bevent->y);
1185                 return TRUE;
1186                 }
1187         return FALSE;
1188
1189
1190 /*
1191  *-------------------------------------------------------------------
1192  * init
1193  *-------------------------------------------------------------------
1194  */
1195
1196 void bar_pane_keywords_close(GtkWidget *bar)
1197 {
1198         PaneKeywordsData *pkd;
1199
1200         pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
1201         if (!pkd) return;
1202         
1203         g_free(pkd->pane.id);
1204         gtk_widget_destroy(pkd->widget);
1205 }
1206
1207 static void bar_pane_keywords_destroy(GtkWidget *widget, gpointer data)
1208 {
1209         PaneKeywordsData *pkd = data;
1210
1211         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1212         if (pkd->idle_id) g_source_remove(pkd->idle_id);
1213         file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
1214
1215         file_data_unref(pkd->fd);
1216         g_free(pkd->key);
1217
1218         g_free(pkd);
1219 }
1220
1221
1222 static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded)
1223 {
1224         PaneKeywordsData *pkd;
1225         GtkWidget *hbox;
1226         GtkWidget *scrolled;
1227         GtkTextBuffer *buffer;
1228         GtkTreeModel *store;
1229         GtkTreeViewColumn *column;
1230         GtkCellRenderer *renderer;
1231         GtkTreeIter iter;
1232
1233         pkd = g_new0(PaneKeywordsData, 1);
1234
1235         pkd->pane.pane_set_fd = bar_pane_keywords_set_fd;
1236         pkd->pane.pane_event = bar_pane_keywords_event;
1237         pkd->pane.pane_write_config = bar_pane_keywords_write_config;
1238         pkd->pane.title = bar_pane_expander_title(title);
1239         pkd->pane.id = g_strdup(id);
1240         pkd->pane.type = PANE_KEYWORDS;
1241
1242         pkd->pane.expanded = expanded;
1243
1244         pkd->key = g_strdup(key);
1245         
1246         pkd->expand_checked = TRUE;
1247         
1248         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
1249
1250         pkd->widget = hbox;
1251         g_object_set_data(G_OBJECT(pkd->widget), "pane_data", pkd);
1252         g_signal_connect(G_OBJECT(pkd->widget), "destroy",
1253                          G_CALLBACK(bar_pane_keywords_destroy), pkd);
1254         gtk_widget_show(hbox);
1255
1256         scrolled = gtk_scrolled_window_new(NULL, NULL);
1257         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1258         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1259                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1260         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1261         gtk_widget_show(scrolled);
1262
1263         pkd->keyword_view = gtk_text_view_new();
1264         gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_view);
1265         g_signal_connect(G_OBJECT(pkd->keyword_view), "populate-popup",
1266                          G_CALLBACK(bar_pane_keywords_populate_popup_cb), pkd);
1267         gtk_widget_show(pkd->keyword_view);
1268
1269         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1270         g_signal_connect(G_OBJECT(buffer), "changed",
1271                          G_CALLBACK(bar_pane_keywords_changed), pkd);
1272
1273         scrolled = gtk_scrolled_window_new(NULL, NULL);
1274         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1275         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1276                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1277         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1278         gtk_widget_show(scrolled);
1279
1280
1281         if (!keyword_tree || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1282                 {
1283                 /* keyword tree does not exist or is empty - fill with defaults */
1284                 keyword_tree_new_default();
1285                 }
1286
1287         store = gtk_tree_model_filter_new(GTK_TREE_MODEL(keyword_tree), NULL);
1288
1289         gtk_tree_model_filter_set_modify_func(GTK_TREE_MODEL_FILTER(store),
1290                                               FILTER_KEYWORD_COLUMN_COUNT,
1291                                               filter_keyword_column_types,
1292                                               bar_pane_keywords_filter_modify,
1293                                               pkd,
1294                                               NULL);
1295         gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(store),
1296                                                bar_pane_keywords_filter_visible,
1297                                                store,
1298                                                NULL);
1299
1300         pkd->keyword_treeview = gtk_tree_view_new_with_model(store);
1301         g_object_unref(store);
1302         
1303         gtk_widget_set_size_request(pkd->keyword_treeview, -1, 400);
1304
1305         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pkd->keyword_treeview), FALSE);
1306
1307 //      gtk_tree_view_set_search_column(GTK_TREE_VIEW(pkd->keyword_treeview), FILTER_KEYWORD_COLUMN_);
1308
1309         column = gtk_tree_view_column_new();
1310         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1311
1312         renderer = gtk_cell_renderer_text_new();
1313         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1314
1315         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_MARK);
1316
1317         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1318
1319         column = gtk_tree_view_column_new();
1320         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1321         renderer = gtk_cell_renderer_toggle_new();
1322         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1323         gtk_tree_view_column_add_attribute(column, renderer, "active", FILTER_KEYWORD_COLUMN_TOGGLE);
1324         gtk_tree_view_column_add_attribute(column, renderer, "visible", FILTER_KEYWORD_COLUMN_IS_KEYWORD);
1325         g_signal_connect(G_OBJECT(renderer), "toggled",
1326                          G_CALLBACK(bar_pane_keywords_keyword_toggle), pkd);
1327
1328         renderer = gtk_cell_renderer_text_new();
1329         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1330         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_NAME);
1331
1332         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1333         gtk_tree_view_set_expander_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1334
1335         gtk_drag_source_set(pkd->keyword_treeview,
1336                             GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
1337                             bar_pane_keywords_drag_types, n_keywords_drag_types,
1338                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1339
1340         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_get",
1341                          G_CALLBACK(bar_pane_keywords_dnd_get), pkd);
1342
1343         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_begin",
1344                          G_CALLBACK(bar_pane_keywords_dnd_begin), pkd);
1345         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_end",
1346                          G_CALLBACK(bar_pane_keywords_dnd_end), pkd);
1347
1348         gtk_drag_dest_set(pkd->keyword_treeview,
1349                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
1350                           bar_pane_keywords_drop_types, n_keywords_drop_types,
1351                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
1352                           
1353         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_received",
1354                          G_CALLBACK(bar_pane_keywords_dnd_receive), pkd);
1355
1356         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_motion",
1357                          G_CALLBACK(bar_pane_keywords_dnd_motion), pkd);
1358
1359         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "button_release_event", 
1360                          G_CALLBACK(bar_pane_keywords_menu_cb), pkd);
1361         
1362         gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_treeview);
1363         gtk_widget_show(pkd->keyword_treeview);
1364
1365         file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
1366
1367         return pkd->widget;
1368 }
1369
1370 GtkWidget *bar_pane_keywords_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
1371 {
1372         gchar *id = g_strdup("keywords");
1373         gchar *title = NULL;
1374         gchar *key = g_strdup(COMMENT_KEY);
1375         gboolean expanded = TRUE;
1376         GtkWidget *ret;
1377
1378         while (*attribute_names)
1379                 {
1380                 const gchar *option = *attribute_names++;
1381                 const gchar *value = *attribute_values++;
1382
1383                 if (READ_CHAR_FULL("id", id)) continue;
1384                 if (READ_CHAR_FULL("title", title)) continue;
1385                 if (READ_CHAR_FULL("key", key)) continue;
1386                 if (READ_BOOL_FULL("expanded", expanded)) continue;
1387                 
1388
1389                 log_printf("unknown attribute %s = %s\n", option, value);
1390                 }
1391         
1392         bar_pane_translate_title(PANE_KEYWORDS, id, &title);
1393         ret = bar_pane_keywords_new(id, title, key, expanded);
1394         g_free(id);
1395         g_free(title);
1396         g_free(key);
1397         return ret;
1398 }
1399
1400 void bar_pane_keywords_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1401 {
1402         PaneKeywordsData *pkd;
1403
1404         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
1405         if (!pkd) return;
1406
1407         gchar *title = NULL;
1408
1409         while (*attribute_names)
1410                 {
1411                 const gchar *option = *attribute_names++;
1412                 const gchar *value = *attribute_values++;
1413
1414                 if (READ_CHAR_FULL("title", title)) continue;
1415                 if (READ_CHAR_FULL("key", pkd->key)) continue;
1416                 if (READ_BOOL_FULL("expanded", pkd->pane.expanded)) continue;
1417                 if (READ_CHAR_FULL("id", pkd->pane.id)) continue;
1418                 
1419
1420                 log_printf("unknown attribute %s = %s\n", option, value);
1421                 }
1422
1423         if (title)
1424                 {
1425                 bar_pane_translate_title(PANE_KEYWORDS, pkd->pane.id, &title);
1426                 gtk_label_set_text(GTK_LABEL(pkd->pane.title), title);
1427                 g_free(title);
1428                 }
1429
1430         bar_update_expander(pane);
1431         bar_pane_keywords_update(pkd);
1432 }
1433
1434
1435 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */