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