Simplify vflist_get_formatted()
[geeqie.git] / src / bar_keywords.c
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <glib/gprintf.h>
23
24 #include "main.h"
25 #include "bar_keywords.h"
26
27 #include "filedata.h"
28 #include "history_list.h"
29 #include "metadata.h"
30 #include "misc.h"
31 #include "ui_fileops.h"
32 #include "ui_misc.h"
33 #include "ui_utildlg.h"
34 #include "utilops.h"
35 #include "bar.h"
36 #include "ui_menu.h"
37 #include "rcfile.h"
38 #include "layout.h"
39 #include "dnd.h"
40 #include "secure_save.h"
41
42
43 //static void bar_pane_keywords_keyword_update_all(void);
44 static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data);
45
46 static void autocomplete_keywords_list_load(const gchar *path);
47 static GtkListStore *keyword_store = NULL;
48 static gboolean autocomplete_keywords_list_save(gchar *path);
49 static gboolean autocomplete_activate_cb(GtkWidget *widget, gpointer data);
50
51 /*
52  *-------------------------------------------------------------------
53  * keyword / comment utils
54  *-------------------------------------------------------------------
55  */
56
57
58 GList *keyword_list_pull(GtkWidget *text_widget)
59 {
60         GList *list;
61         gchar *text;
62
63         text = text_widget_text_pull(text_widget);
64         list = string_to_keywords_list(text);
65
66         g_free(text);
67
68         return list;
69 }
70
71 static GList *keyword_list_pull_selected(GtkWidget *text_widget)
72 {
73         GList *list;
74         gchar *text;
75
76         text = text_widget_text_pull_selected(text_widget);
77         list = string_to_keywords_list(text);
78
79         g_free(text);
80
81         return list;
82 }
83
84 /* the "changed" signal should be blocked before calling this */
85 static void keyword_list_push(GtkWidget *textview, GList *list)
86 {
87         GtkTextBuffer *buffer;
88         GtkTextIter start, end;
89
90         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
91         gtk_text_buffer_get_bounds(buffer, &start, &end);
92         gtk_text_buffer_delete(buffer, &start, &end);
93
94         while (list)
95                 {
96                 const gchar *word = list->data;
97                 GtkTextIter iter;
98
99                 gtk_text_buffer_get_end_iter(buffer, &iter);
100                 if (word) gtk_text_buffer_insert(buffer, &iter, word, -1);
101                 gtk_text_buffer_get_end_iter(buffer, &iter);
102                 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
103
104                 list = list->next;
105                 }
106 }
107
108
109 /*
110  *-------------------------------------------------------------------
111  * info bar
112  *-------------------------------------------------------------------
113  */
114
115
116 enum {
117         FILTER_KEYWORD_COLUMN_TOGGLE = 0,
118         FILTER_KEYWORD_COLUMN_MARK,
119         FILTER_KEYWORD_COLUMN_NAME,
120         FILTER_KEYWORD_COLUMN_IS_KEYWORD,
121         FILTER_KEYWORD_COLUMN_COUNT
122 };
123
124 static GType filter_keyword_column_types[] = {G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN};
125
126 typedef struct _PaneKeywordsData PaneKeywordsData;
127 struct _PaneKeywordsData
128 {
129         PaneData pane;
130         GtkWidget *widget;
131
132         GtkWidget *keyword_view;
133         GtkWidget *keyword_treeview;
134
135         GtkTreePath *click_tpath;
136
137         gboolean expand_checked;
138         gboolean collapse_unchecked;
139         gboolean hide_unchecked;
140
141         guint idle_id; /* event source id */
142         FileData *fd;
143         gchar *key;
144         gint height;
145
146         GList *expanded_rows;
147
148         GtkWidget *autocomplete;
149 };
150
151 typedef struct _ConfDialogData ConfDialogData;
152 struct _ConfDialogData
153 {
154         PaneKeywordsData *pkd;
155         GtkTreePath *click_tpath;
156
157         /* dialog parts */
158         GenericDialog *gd;
159         GtkWidget *edit_widget;
160         gboolean is_keyword;
161
162         gboolean edit_existing;
163 };
164
165
166 static void bar_pane_keywords_write(PaneKeywordsData *pkd)
167 {
168         GList *list;
169
170         if (!pkd->fd) return;
171
172         list = keyword_list_pull(pkd->keyword_view);
173
174         metadata_write_list(pkd->fd, KEYWORD_KEY, list);
175
176         string_list_free(list);
177 }
178
179 gboolean bar_keyword_tree_expand_if_set_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
180 {
181         PaneKeywordsData *pkd = data;
182         gboolean set;
183
184         gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
185
186         if (set && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
187                 {
188                 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), path);
189                 }
190         return FALSE;
191 }
192
193 gboolean bar_keyword_tree_collapse_if_unset_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
194 {
195         PaneKeywordsData *pkd = data;
196         gboolean set;
197
198         gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
199
200         if (!set && gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
201                 {
202                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(pkd->keyword_treeview), path);
203                 }
204         return FALSE;
205 }
206
207 static void bar_keyword_tree_sync(PaneKeywordsData *pkd)
208 {
209         GtkTreeModel *model;
210
211         GtkTreeModel *keyword_tree;
212         GList *keywords;
213
214         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
215         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
216
217         keywords = keyword_list_pull(pkd->keyword_view);
218         keyword_show_set_in(GTK_TREE_STORE(keyword_tree), model, keywords);
219         if (pkd->hide_unchecked) keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
220         string_list_free(keywords);
221
222         gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(model));
223
224         if (pkd->expand_checked) gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
225         if (pkd->collapse_unchecked) gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
226 }
227
228 static void bar_pane_keywords_update(PaneKeywordsData *pkd)
229 {
230         GList *keywords = NULL;
231         GList *orig_keywords = NULL;
232         GList *work1, *work2;
233         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
234
235         keywords = metadata_read_list(pkd->fd, KEYWORD_KEY, METADATA_PLAIN);
236         orig_keywords = keyword_list_pull(pkd->keyword_view);
237
238         /* compare the lists */
239         work1 = keywords;
240         work2 = orig_keywords;
241
242         while (work1 && work2)
243                 {
244                 if (strcmp(work1->data, work2->data) != 0) break;
245                 work1 = work1->next;
246                 work2 = work2->next;
247                 }
248
249         if (work1 || work2) /* lists differs */
250                 {
251                 g_signal_handlers_block_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
252                 keyword_list_push(pkd->keyword_view, keywords);
253                 bar_keyword_tree_sync(pkd);
254                 g_signal_handlers_unblock_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
255                 }
256         string_list_free(keywords);
257         string_list_free(orig_keywords);
258 }
259
260 void bar_pane_keywords_set_fd(GtkWidget *pane, FileData *fd)
261 {
262         PaneKeywordsData *pkd;
263
264         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
265         if (!pkd) return;
266
267         file_data_unref(pkd->fd);
268         pkd->fd = file_data_ref(fd);
269
270         bar_pane_keywords_update(pkd);
271 }
272
273 void bar_keyword_tree_get_expanded_cb(GtkTreeView *keyword_treeview, GtkTreePath *path,  gpointer data)
274 {
275         GList **expanded = data;
276         GtkTreeModel *model;
277         GtkTreeIter iter;
278         gchar *path_string;
279
280         model = gtk_tree_view_get_model(GTK_TREE_VIEW(keyword_treeview));
281         gtk_tree_model_get_iter(model, &iter, path);
282
283         path_string = gtk_tree_model_get_string_from_iter(model, &iter);
284
285         *expanded = g_list_append(*expanded, g_strdup(path_string));
286         g_free(path_string);
287 }
288
289 static void bar_pane_keywords_entry_write_config(gchar *entry, GString *outstr, gint indent)
290 {
291         struct {
292                 gchar *path;
293         } expand;
294
295         expand.path = entry;
296
297         WRITE_NL(); WRITE_STRING("<expanded ");
298         WRITE_CHAR(expand, path);
299         WRITE_STRING("/>");
300 }
301
302 static void bar_pane_keywords_write_config(GtkWidget *pane, GString *outstr, gint indent)
303 {
304         PaneKeywordsData *pkd;
305         GList *path_expanded = NULL;
306         gint w, h;
307
308         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
309         if (!pkd) return;
310
311         gtk_widget_get_size_request(GTK_WIDGET(pane), &w, &h);
312         pkd->height = h;
313
314         WRITE_NL(); WRITE_STRING("<pane_keywords ");
315         write_char_option(outstr, indent, "id", pkd->pane.id);
316         write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(pkd->pane.title)));
317         WRITE_BOOL(pkd->pane, expanded);
318         WRITE_CHAR(*pkd, key);
319         WRITE_INT(*pkd, height);
320         WRITE_STRING(">");
321         indent++;
322
323         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
324                                                                 (bar_keyword_tree_get_expanded_cb), &path_expanded);
325
326         g_list_first(path_expanded);
327         while (path_expanded)
328                 {
329                 bar_pane_keywords_entry_write_config(path_expanded->data, outstr, indent);
330                 g_free(path_expanded->data);
331                 path_expanded = path_expanded->next;
332                 }
333         g_list_free(path_expanded);
334
335         indent--;
336         WRITE_NL();
337         WRITE_STRING("</pane_keywords>");
338 }
339
340 gint bar_pane_keywords_event(GtkWidget *bar, GdkEvent *event)
341 {
342         PaneKeywordsData *pkd;
343
344         pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
345         if (!pkd) return FALSE;
346
347         if (gtk_widget_has_focus(pkd->keyword_view)) return gtk_widget_event(pkd->keyword_view, event);
348
349         if (gtk_widget_has_focus(pkd->autocomplete))
350                 {
351                 return gtk_widget_event(pkd->autocomplete, event);
352                 }
353         return FALSE;
354 }
355
356 static void bar_pane_keywords_keyword_toggle(GtkCellRendererToggle *toggle, const gchar *path, gpointer data)
357 {
358         PaneKeywordsData *pkd = data;
359         GtkTreeModel *model;
360         GtkTreeIter iter;
361         GtkTreePath *tpath;
362         gboolean active;
363         GList *list;
364         GtkTreeIter child_iter;
365         GtkTreeModel *keyword_tree;
366
367         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
368
369         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
370
371         tpath = gtk_tree_path_new_from_string(path);
372         gtk_tree_model_get_iter(model, &iter, tpath);
373         gtk_tree_path_free(tpath);
374
375         gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_TOGGLE, &active, -1);
376         active = (!active);
377
378
379         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
380         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
381
382         list = keyword_list_pull(pkd->keyword_view);
383         if (active)
384                 keyword_tree_set(keyword_tree, &child_iter, &list);
385         else
386                 keyword_tree_reset(keyword_tree, &child_iter, &list);
387
388         g_signal_handlers_block_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
389         keyword_list_push(pkd->keyword_view, list);
390         string_list_free(list);
391         g_signal_handlers_unblock_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
392
393         /* call this just once in the end */
394         bar_pane_keywords_changed(keyword_buffer, pkd);
395 }
396
397 void bar_pane_keywords_filter_modify(GtkTreeModel *model, GtkTreeIter *iter, GValue *value, gint column, gpointer data)
398 {
399         PaneKeywordsData *pkd = data;
400         GtkTreeModel *keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
401         GtkTreeIter child_iter;
402
403         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, iter);
404
405         memset(value, 0, sizeof (GValue));
406
407         switch (column)
408                 {
409                 case FILTER_KEYWORD_COLUMN_TOGGLE:
410                         {
411                         GList *keywords = keyword_list_pull(pkd->keyword_view);
412                         gboolean set = keyword_tree_is_set(keyword_tree, &child_iter, keywords);
413                         string_list_free(keywords);
414
415                         g_value_init(value, G_TYPE_BOOLEAN);
416                         g_value_set_boolean(value, set);
417                         break;
418                         }
419                 case FILTER_KEYWORD_COLUMN_MARK:
420                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_MARK, value);
421                         break;
422                 case FILTER_KEYWORD_COLUMN_NAME:
423                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_NAME, value);
424                         break;
425                 case FILTER_KEYWORD_COLUMN_IS_KEYWORD:
426                         gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_IS_KEYWORD, value);
427                         break;
428                 }
429         return;
430
431 }
432
433 gboolean bar_pane_keywords_filter_visible(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer data)
434 {
435         GtkTreeModel *filter = data;
436
437         return !keyword_is_hidden_in(keyword_tree, iter, filter);
438 }
439
440 static void bar_pane_keywords_set_selection(PaneKeywordsData *pkd, gboolean append)
441 {
442         GList *keywords = NULL;
443         GList *list = NULL;
444         GList *work;
445
446         keywords = keyword_list_pull_selected(pkd->keyword_view);
447
448         list = layout_selection_list(pkd->pane.lw);
449         list = file_data_process_groups_in_selection(list, FALSE, NULL);
450
451         work = list;
452         while (work)
453                 {
454                 FileData *fd = work->data;
455                 work = work->next;
456
457                 if (append)
458                         {
459                         metadata_append_list(fd, KEYWORD_KEY, keywords);
460                         }
461                 else
462                         {
463                         metadata_write_list(fd, KEYWORD_KEY, keywords);
464                         }
465                 }
466
467         filelist_free(list);
468         string_list_free(keywords);
469 }
470
471 static void bar_pane_keywords_sel_add_cb(GtkWidget *button, gpointer data)
472 {
473         PaneKeywordsData *pkd = data;
474
475         bar_pane_keywords_set_selection(pkd, TRUE);
476 }
477
478 static void bar_pane_keywords_sel_replace_cb(GtkWidget *button, gpointer data)
479 {
480         PaneKeywordsData *pkd = data;
481
482         bar_pane_keywords_set_selection(pkd, FALSE);
483 }
484
485 static void bar_pane_keywords_populate_popup_cb(GtkTextView *textview, GtkMenu *menu, gpointer data)
486 {
487         PaneKeywordsData *pkd = data;
488
489         menu_item_add_divider(GTK_WIDGET(menu));
490         menu_item_add_stock(GTK_WIDGET(menu), _("Add selected keywords to selected files"), GTK_STOCK_ADD, G_CALLBACK(bar_pane_keywords_sel_add_cb), pkd);
491         menu_item_add_stock(GTK_WIDGET(menu), _("Replace existing keywords in selected files with selected keywords"), GTK_STOCK_CONVERT, G_CALLBACK(bar_pane_keywords_sel_replace_cb), pkd);
492 }
493
494
495 static void bar_pane_keywords_notify_cb(FileData *fd, NotifyType type, gpointer data)
496 {
497         PaneKeywordsData *pkd = data;
498         if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == pkd->fd)
499                 {
500                 DEBUG_1("Notify pane_keywords: %s %04x", fd->path, type);
501                 bar_pane_keywords_update(pkd);
502                 }
503 }
504
505 static gboolean bar_pane_keywords_changed_idle_cb(gpointer data)
506 {
507         PaneKeywordsData *pkd = data;
508
509         bar_pane_keywords_write(pkd);
510         bar_keyword_tree_sync(pkd);
511         pkd->idle_id = 0;
512         return FALSE;
513 }
514
515 static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data)
516 {
517         PaneKeywordsData *pkd = data;
518
519         if (pkd->idle_id) return;
520         /* higher prio than redraw */
521         pkd->idle_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE, bar_pane_keywords_changed_idle_cb, pkd, NULL);
522 }
523
524
525 /*
526  *-------------------------------------------------------------------
527  * dnd
528  *-------------------------------------------------------------------
529  */
530
531
532 static GtkTargetEntry bar_pane_keywords_drag_types[] = {
533         { TARGET_APP_KEYWORD_PATH_STRING, GTK_TARGET_SAME_WIDGET, TARGET_APP_KEYWORD_PATH },
534         { "text/plain", 0, TARGET_TEXT_PLAIN }
535 };
536 static gint n_keywords_drag_types = 2;
537
538
539 static GtkTargetEntry bar_pane_keywords_drop_types[] = {
540         { TARGET_APP_KEYWORD_PATH_STRING, GTK_TARGET_SAME_WIDGET, TARGET_APP_KEYWORD_PATH },
541         { "text/plain", 0, TARGET_TEXT_PLAIN }
542 };
543 static gint n_keywords_drop_types = 2;
544
545
546 static void bar_pane_keywords_dnd_get(GtkWidget *tree_view, GdkDragContext *context,
547                                      GtkSelectionData *selection_data, guint info,
548                                      guint time, gpointer data)
549 {
550         GtkTreeIter iter;
551         GtkTreeModel *model;
552         GtkTreeIter child_iter;
553         GtkTreeModel *keyword_tree;
554
555         GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
556
557         if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
558
559         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
560         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
561
562         switch (info)
563                 {
564                 case TARGET_APP_KEYWORD_PATH:
565                         {
566                         GList *path = keyword_tree_get_path(keyword_tree, &child_iter);
567                         gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
568                                                8, (gpointer) &path, sizeof(path));
569                         break;
570                         }
571
572                 case TARGET_TEXT_PLAIN:
573                 default:
574                         {
575                         gchar *name = keyword_get_name(keyword_tree, &child_iter);
576                         gtk_selection_data_set_text(selection_data, name, -1);
577                         g_free(name);
578                         }
579                         break;
580                 }
581 }
582
583 static void bar_pane_keywords_dnd_begin(GtkWidget *tree_view, GdkDragContext *context, gpointer data)
584 {
585         GtkTreeIter iter;
586         GtkTreeModel *model;
587         GtkTreeIter child_iter;
588         GtkTreeModel *keyword_tree;
589         gchar *name;
590
591         GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
592
593         if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
594
595         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
596         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
597
598         name = keyword_get_name(keyword_tree, &child_iter);
599
600         dnd_set_drag_label(tree_view, context, name);
601         g_free(name);
602
603 }
604
605 static void bar_pane_keywords_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
606 {
607 }
608
609
610 static gboolean bar_pane_keywords_dnd_can_move(GtkTreeModel *keyword_tree, GtkTreeIter *src_kw_iter, GtkTreeIter *dest_kw_iter)
611 {
612         gchar *src_name;
613         GtkTreeIter parent;
614
615         if (dest_kw_iter && keyword_same_parent(keyword_tree, src_kw_iter, dest_kw_iter))
616                 {
617                 return TRUE; /* reordering of siblings is ok */
618                 }
619         if (!dest_kw_iter && !gtk_tree_model_iter_parent(keyword_tree, &parent, src_kw_iter))
620                 {
621                 return TRUE; /* reordering of top-level siblings is ok */
622                 }
623
624         src_name = keyword_get_name(keyword_tree, src_kw_iter);
625         if (keyword_exists(keyword_tree, NULL, dest_kw_iter, src_name, FALSE, NULL))
626                 {
627                 g_free(src_name);
628                 return FALSE;
629         }
630         g_free(src_name);
631         return TRUE;
632 }
633
634 static gboolean bar_pane_keywords_dnd_skip_existing(GtkTreeModel *keyword_tree, GtkTreeIter *dest_kw_iter, GList **keywords)
635 {
636         /* we have to find at least one keyword that does not already exist as a sibling of dest_kw_iter */
637         GList *work = *keywords;
638         while (work)
639                 {
640                 gchar *keyword = work->data;
641                 if (keyword_exists(keyword_tree, NULL, dest_kw_iter, keyword, FALSE, NULL))
642                         {
643                         GList *next = work->next;
644                         g_free(keyword);
645                         *keywords = g_list_delete_link(*keywords, work);
646                         work = next;
647                         }
648                 else
649                         {
650                         work = work->next;
651                         }
652                 }
653         return !!*keywords;
654 }
655
656 static void bar_pane_keywords_dnd_receive(GtkWidget *tree_view, GdkDragContext *context,
657                                           gint x, gint y,
658                                           GtkSelectionData *selection_data, guint info,
659                                           guint time, gpointer data)
660 {
661         PaneKeywordsData *pkd = data;
662         GtkTreePath *tpath = NULL;
663         GtkTreeViewDropPosition pos;
664         GtkTreeModel *model;
665
666         GtkTreeModel *keyword_tree;
667         gboolean src_valid = FALSE;
668         GList *new_keywords = NULL;
669         GList *work;
670
671         /* iterators for keyword_tree */
672         GtkTreeIter src_kw_iter;
673         GtkTreeIter dest_kw_iter;
674         GtkTreeIter new_kw_iter;
675
676         g_signal_stop_emission_by_name(tree_view, "drag_data_received");
677
678         model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
679         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
680
681         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &tpath, &pos);
682         gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), NULL, pos);
683
684         switch (info)
685                 {
686                 case TARGET_APP_KEYWORD_PATH:
687                         {
688                         GList *path = *(gpointer *)gtk_selection_data_get_data(selection_data);
689                         src_valid = keyword_tree_get_iter(keyword_tree, &src_kw_iter, path);
690                         string_list_free(path);
691                         break;
692                         }
693                 default:
694                         new_keywords = string_to_keywords_list((gchar *)gtk_selection_data_get_data(selection_data));
695                         break;
696                 }
697
698         if (tpath)
699                 {
700                 GtkTreeIter dest_iter;
701                 gtk_tree_model_get_iter(model, &dest_iter, tpath);
702                 gtk_tree_path_free(tpath);
703                 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &dest_kw_iter, &dest_iter);
704
705                 if (src_valid && gtk_tree_store_is_ancestor(GTK_TREE_STORE(keyword_tree), &src_kw_iter, &dest_kw_iter))
706                         {
707                         /* can't move to it's own child */
708                         return;
709                         }
710
711                 if (src_valid && keyword_compare(keyword_tree, &src_kw_iter, &dest_kw_iter) == 0)
712                         {
713                         /* can't move to itself */
714                         return;
715                         }
716
717                 if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) &&
718                     !gtk_tree_model_iter_has_child(keyword_tree, &dest_kw_iter))
719                         {
720                         /* the node has no children, all keywords can be added */
721                         gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &new_kw_iter, &dest_kw_iter);
722                         }
723                 else
724                         {
725                         if (src_valid && !bar_pane_keywords_dnd_can_move(keyword_tree, &src_kw_iter, &dest_kw_iter))
726                                 {
727                                 /* the keyword can't be moved if the same name already exist */
728                                 return;
729                                 }
730                         if (new_keywords && !bar_pane_keywords_dnd_skip_existing(keyword_tree, &dest_kw_iter, &new_keywords))
731                                 {
732                                 /* the keywords can't be added if the same name already exist */
733                                 return;
734                                 }
735
736                         switch (pos)
737                                 {
738                                 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
739                                 case GTK_TREE_VIEW_DROP_BEFORE:
740                                         gtk_tree_store_insert_before(GTK_TREE_STORE(keyword_tree), &new_kw_iter, NULL, &dest_kw_iter);
741                                         break;
742                                 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
743                                 case GTK_TREE_VIEW_DROP_AFTER:
744                                         gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &new_kw_iter, NULL, &dest_kw_iter);
745                                         break;
746                                 }
747                         }
748
749                 }
750         else
751                 {
752                 if (src_valid && !bar_pane_keywords_dnd_can_move(keyword_tree, &src_kw_iter, NULL))
753                         {
754                         /* the keyword can't be moved if the same name already exist */
755                         return;
756                         }
757                 if (new_keywords && !bar_pane_keywords_dnd_skip_existing(keyword_tree, NULL, &new_keywords))
758                         {
759                         /* the keywords can't be added if the same name already exist */
760                         return;
761                         }
762                 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &new_kw_iter, NULL);
763                 }
764
765
766         if (src_valid)
767                 {
768                 keyword_move_recursive(GTK_TREE_STORE(keyword_tree), &new_kw_iter, &src_kw_iter);
769                 }
770
771         work = new_keywords;
772         while (work)
773                 {
774                 gchar *keyword = work->data;
775                 keyword_set(GTK_TREE_STORE(keyword_tree), &new_kw_iter, keyword, TRUE);
776                 work = work->next;
777
778                 if (work)
779                         {
780                         GtkTreeIter add;
781                         gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &add, NULL, &new_kw_iter);
782                         new_kw_iter = add;
783                         }
784                 }
785         string_list_free(new_keywords);
786         bar_keyword_tree_sync(pkd);
787 }
788
789 static gint bar_pane_keywords_dnd_motion(GtkWidget *tree_view, GdkDragContext *context,
790                                         gint x, gint y, guint time, gpointer data)
791 {
792         GtkTreePath *tpath = NULL;
793         GtkTreeViewDropPosition pos;
794         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &tpath, &pos);
795         if (tpath)
796                 {
797                 GtkTreeModel *model;
798                 GtkTreeIter dest_iter;
799                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
800                 gtk_tree_model_get_iter(model, &dest_iter, tpath);
801                 if (pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE && gtk_tree_model_iter_has_child(model, &dest_iter))
802                         pos = GTK_TREE_VIEW_DROP_BEFORE;
803
804                 if (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER && gtk_tree_model_iter_has_child(model, &dest_iter))
805                         pos = GTK_TREE_VIEW_DROP_AFTER;
806                 }
807
808         gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), tpath, pos);
809         gtk_tree_path_free(tpath);
810
811         if (tree_view == gtk_drag_get_source_widget(context))
812                 gdk_drag_status(context, GDK_ACTION_MOVE, time);
813         else
814                 gdk_drag_status(context, GDK_ACTION_COPY, time);
815
816         return TRUE;
817 }
818
819 /*
820  *-------------------------------------------------------------------
821  * edit dialog
822  *-------------------------------------------------------------------
823  */
824
825 static void bar_pane_keywords_edit_destroy_cb(GtkWidget *widget, gpointer data)
826 {
827         ConfDialogData *cdd = data;
828         gtk_tree_path_free(cdd->click_tpath);
829         g_free(cdd);
830 }
831
832
833 static void bar_pane_keywords_edit_cancel_cb(GenericDialog *gd, gpointer data)
834 {
835 }
836
837
838 static void bar_pane_keywords_edit_ok_cb(GenericDialog *gd, gpointer data)
839 {
840         ConfDialogData *cdd = data;
841         PaneKeywordsData *pkd = cdd->pkd;
842         GtkTreeModel *model;
843
844         GtkTreeModel *keyword_tree;
845         GtkTreeIter kw_iter;
846
847         gboolean have_dest = FALSE;
848
849         GList *keywords;
850
851         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
852         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
853
854         if (cdd->click_tpath)
855                 {
856                 GtkTreeIter iter;
857                 if (gtk_tree_model_get_iter(model, &iter, cdd->click_tpath))
858                         {
859                         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
860                         have_dest = TRUE;
861                         }
862                 }
863
864         if (cdd->edit_existing && !have_dest) return;
865
866         keywords = keyword_list_pull(cdd->edit_widget);
867
868         if (cdd->edit_existing)
869                 {
870                 if (keywords && keywords->data && /* there should be one keyword */
871                     !keyword_exists(keyword_tree, NULL, &kw_iter, keywords->data, TRUE, NULL))
872                         {
873                         keyword_set(GTK_TREE_STORE(keyword_tree), &kw_iter, keywords->data, cdd->is_keyword);
874                         }
875                 }
876         else
877                 {
878                 GList *work = keywords;
879                 gboolean append_to = FALSE;
880
881                 while (work)
882                         {
883                         GtkTreeIter add;
884                         if (keyword_exists(keyword_tree, NULL, (have_dest || append_to) ? &kw_iter : NULL, work->data, FALSE, NULL))
885                                 {
886                                 work = work->next;
887                                 continue;
888                                 }
889                         if (have_dest)
890                                 {
891                                 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &add, &kw_iter);
892                                 }
893                         else if (append_to)
894                                 {
895                                 gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &add, NULL, &kw_iter);
896                                 }
897                         else
898                                 {
899                                 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &add, NULL);
900                                 append_to = TRUE;
901                                 kw_iter = add;
902                                 }
903                         keyword_set(GTK_TREE_STORE(keyword_tree), &add, work->data, cdd->is_keyword);
904                         work = work->next;
905                         }
906                 }
907         string_list_free(keywords);
908 }
909
910 static void bar_pane_keywords_conf_set_helper(GtkWidget *widget, gpointer data)
911 {
912         ConfDialogData *cdd = data;
913         cdd->is_keyword = FALSE;
914 }
915
916 static void bar_pane_keywords_conf_set_kw(GtkWidget *widget, gpointer data)
917 {
918         ConfDialogData *cdd = data;
919         cdd->is_keyword = TRUE;
920 }
921
922
923
924 static void bar_pane_keywords_edit_dialog(PaneKeywordsData *pkd, gboolean edit_existing)
925 {
926         ConfDialogData *cdd;
927         GenericDialog *gd;
928         GtkWidget *table;
929         GtkWidget *group;
930         GtkWidget *button;
931
932         gchar *name = NULL;
933         gboolean is_keyword = TRUE;
934
935
936         if (edit_existing && pkd->click_tpath)
937                 {
938                 GtkTreeModel *model;
939                 GtkTreeIter iter;
940                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
941
942                 if (gtk_tree_model_get_iter(model, &iter, pkd->click_tpath))
943                         {
944                         gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
945                                                          FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
946                         }
947                 else
948                         {
949                         return;
950                         }
951                 }
952
953         if (edit_existing && !name) return;
954
955
956         cdd = g_new0(ConfDialogData, 1);
957         cdd->pkd =pkd;
958         cdd->click_tpath = pkd->click_tpath;
959         pkd->click_tpath = NULL;
960         cdd->edit_existing = edit_existing;
961
962         cdd->gd = gd = generic_dialog_new(name ? _("Edit keyword") : _("New keyword"), "keyword_edit",
963                                 pkd->widget, TRUE,
964                                 bar_pane_keywords_edit_cancel_cb, cdd);
965         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
966                          G_CALLBACK(bar_pane_keywords_edit_destroy_cb), cdd);
967
968
969         generic_dialog_add_message(gd, NULL, name ? _("Configure keyword") : _("New keyword"), NULL, FALSE);
970
971         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
972                                   bar_pane_keywords_edit_ok_cb, TRUE);
973
974         table = pref_table_new(gd->vbox, 3, 1, FALSE, TRUE);
975         pref_table_label(table, 0, 0, _("Keyword:"), 1.0);
976         cdd->edit_widget = gtk_entry_new();
977         gtk_widget_set_size_request(cdd->edit_widget, 300, -1);
978         if (name) gtk_entry_set_text(GTK_ENTRY(cdd->edit_widget), name);
979         gtk_table_attach_defaults(GTK_TABLE(table), cdd->edit_widget, 1, 2, 0, 1);
980         /* here could eventually be a text view instead of entry */
981         generic_dialog_attach_default(gd, cdd->edit_widget);
982         gtk_widget_show(cdd->edit_widget);
983
984         group = pref_group_new(gd->vbox, FALSE, _("Keyword type:"), GTK_ORIENTATION_VERTICAL);
985
986         button = pref_radiobutton_new(group, NULL, _("Active keyword"),
987                                       (is_keyword),
988                                       G_CALLBACK(bar_pane_keywords_conf_set_kw), cdd);
989         button = pref_radiobutton_new(group, button, _("Helper"),
990                                       (!is_keyword),
991                                       G_CALLBACK(bar_pane_keywords_conf_set_helper), cdd);
992
993         cdd->is_keyword = is_keyword;
994
995         g_free(name);
996
997         gtk_widget_grab_focus(cdd->edit_widget);
998
999         gtk_widget_show(gd->dialog);
1000 }
1001
1002
1003
1004
1005 /*
1006  *-------------------------------------------------------------------
1007  * popup menu
1008  *-------------------------------------------------------------------
1009  */
1010
1011 static void bar_pane_keywords_edit_dialog_cb(GtkWidget *menu_widget, gpointer data)
1012 {
1013         PaneKeywordsData *pkd = data;
1014         bar_pane_keywords_edit_dialog(pkd, TRUE);
1015 }
1016
1017 static void bar_pane_keywords_add_dialog_cb(GtkWidget *menu_widget, gpointer data)
1018 {
1019         PaneKeywordsData *pkd = data;
1020         bar_pane_keywords_edit_dialog(pkd, FALSE);
1021 }
1022
1023 static void bar_pane_keywords_connect_mark_cb(GtkWidget *menu_widget, gpointer data)
1024 {
1025         PaneKeywordsData *pkd = data;
1026
1027         GtkTreeModel *model;
1028         GtkTreeIter iter;
1029
1030         GtkTreeModel *keyword_tree;
1031         GtkTreeIter kw_iter;
1032
1033         gint mark = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_widget), "mark")) - 1;
1034
1035         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1036         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1037
1038         if (!pkd->click_tpath) return;
1039         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1040
1041         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1042
1043         meta_data_connect_mark_with_keyword(keyword_tree, &kw_iter, mark);
1044 }
1045
1046 static void bar_pane_keywords_disconnect_marks_ok_cb(GenericDialog *gd, gpointer data)
1047 {
1048         keyword_tree_disconnect_marks();
1049 }
1050
1051 static void dummy_cancel_cb(GenericDialog *gd, gpointer data)
1052 {
1053         /* no op, only so cancel button appears */
1054 }
1055
1056 static void bar_pane_keywords_disconnect_marks_cb(GtkWidget *menu_widget, gpointer data)
1057 {
1058         PaneKeywordsData *pkd = data;
1059
1060         GenericDialog *gd;
1061         GString *message = g_string_new("");
1062
1063         message = g_string_append(message, _("This will disconnect all Marks Keywords connections"));
1064
1065         gd = generic_dialog_new(_("Marks Keywords"),
1066                                 "marks_keywords", menu_widget, TRUE, dummy_cancel_cb, pkd);
1067         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
1068                                 "Disconnect all Marks Keywords connections?", message->str, TRUE);
1069         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, bar_pane_keywords_disconnect_marks_ok_cb, TRUE);
1070
1071         gtk_widget_show(gd->dialog);
1072
1073         g_string_free(message, TRUE);
1074 }
1075
1076 static void bar_pane_keywords_delete_cb(GtkWidget *menu_widget, gpointer data)
1077 {
1078         PaneKeywordsData *pkd = data;
1079         GtkTreeModel *model;
1080         GtkTreeIter iter;
1081
1082         GtkTreeModel *keyword_tree;
1083         GtkTreeIter kw_iter;
1084
1085         if (!pkd->click_tpath) return;
1086
1087         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1088         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1089
1090         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1091         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1092
1093         keyword_delete(GTK_TREE_STORE(keyword_tree), &kw_iter);
1094 }
1095
1096 static void bar_pane_keywords_hide_cb(GtkWidget *menu_widget, gpointer data)
1097 {
1098         PaneKeywordsData *pkd = data;
1099         GtkTreeModel *model;
1100         GtkTreeIter iter;
1101
1102         GtkTreeModel *keyword_tree;
1103         GtkTreeIter kw_iter;
1104
1105         if (!pkd->click_tpath) return;
1106
1107         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1108         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1109
1110         if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1111         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
1112
1113         keyword_hide_in(GTK_TREE_STORE(keyword_tree), &kw_iter, model);
1114 }
1115
1116 static void bar_pane_keywords_show_all_cb(GtkWidget *menu_widget, gpointer data)
1117 {
1118         PaneKeywordsData *pkd = data;
1119         GtkTreeModel *model;
1120
1121         GtkTreeModel *keyword_tree;
1122
1123         string_list_free(pkd->expanded_rows);
1124         pkd->expanded_rows = NULL;
1125         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
1126                                                                 (bar_keyword_tree_get_expanded_cb), &pkd->expanded_rows);
1127
1128         pkd->hide_unchecked = FALSE;
1129
1130         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1131         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1132
1133         keyword_show_all_in(GTK_TREE_STORE(keyword_tree), model);
1134
1135         if (!pkd->collapse_unchecked) gtk_tree_view_expand_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1136         bar_keyword_tree_sync(pkd);
1137 }
1138
1139 static void bar_pane_keywords_revert_cb(GtkWidget *menu_widget, gpointer data)
1140 {
1141         PaneKeywordsData *pkd = data;
1142         GList *work;
1143         GtkTreePath *tree_path;
1144         gchar *path;
1145
1146         gtk_tree_view_collapse_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1147
1148         work = pkd->expanded_rows;
1149         while (work)
1150                 {
1151                 path = work->data;
1152                 tree_path = gtk_tree_path_new_from_string(path);
1153                 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), tree_path);
1154                 work = work->next;
1155                 gtk_tree_path_free(tree_path);
1156                 }
1157
1158         bar_keyword_tree_sync(pkd);
1159 }
1160
1161 static void bar_pane_keywords_expand_checked_cb(GtkWidget *menu_widget, gpointer data)
1162 {
1163         PaneKeywordsData *pkd = data;
1164         GtkTreeModel *model;
1165
1166         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1167         gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
1168 }
1169
1170 static void bar_pane_keywords_collapse_all_cb(GtkWidget *menu_widget, gpointer data)
1171 {
1172         PaneKeywordsData *pkd = data;
1173
1174         string_list_free(pkd->expanded_rows);
1175         pkd->expanded_rows = NULL;
1176         gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(pkd->keyword_treeview),
1177                                                                 (bar_keyword_tree_get_expanded_cb), &pkd->expanded_rows);
1178
1179         gtk_tree_view_collapse_all(GTK_TREE_VIEW(pkd->keyword_treeview));
1180
1181         bar_keyword_tree_sync(pkd);
1182 }
1183
1184 static void bar_pane_keywords_revert_hidden_cb(GtkWidget *menu_widget, gpointer data)
1185 {
1186         PaneKeywordsData *pkd = data;
1187         GtkTreeModel *model;
1188         GtkTreeModel *keyword_tree;
1189
1190         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1191         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1192
1193         keyword_revert_hidden_in(GTK_TREE_STORE(keyword_tree), model);
1194
1195         bar_keyword_tree_sync(pkd);
1196 }
1197
1198 static void bar_pane_keywords_collapse_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1199 {
1200         PaneKeywordsData *pkd = data;
1201         GtkTreeModel *model;
1202
1203         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1204         gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
1205 }
1206
1207 static void bar_pane_keywords_hide_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1208 {
1209         PaneKeywordsData *pkd = data;
1210         GtkTreeModel *model;
1211
1212         GtkTreeModel *keyword_tree;
1213         GList *keywords;
1214
1215         model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1216         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1217
1218         keywords = keyword_list_pull(pkd->keyword_view);
1219         keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
1220         string_list_free(keywords);
1221         bar_keyword_tree_sync(pkd);
1222 }
1223
1224 static void bar_pane_keywords_expand_checked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1225 {
1226         PaneKeywordsData *pkd = data;
1227         pkd->expand_checked = !pkd->expand_checked;
1228         bar_keyword_tree_sync(pkd);
1229 }
1230
1231 static void bar_pane_keywords_collapse_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1232 {
1233         PaneKeywordsData *pkd = data;
1234         pkd->collapse_unchecked = !pkd->collapse_unchecked;
1235         bar_keyword_tree_sync(pkd);
1236 }
1237
1238 static void bar_pane_keywords_hide_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1239 {
1240         PaneKeywordsData *pkd = data;
1241         pkd->hide_unchecked = !pkd->hide_unchecked;
1242         bar_keyword_tree_sync(pkd);
1243 }
1244
1245 /**
1246  * @brief Callback for adding selected keyword to all selected images.
1247  */
1248 static void bar_pane_keywords_add_to_selected_cb(GtkWidget *menu_widget, gpointer data)
1249 {
1250         PaneKeywordsData *pkd = data;
1251         GtkTreeIter iter; /* This is the iter which initial holds the current keyword */
1252         GtkTreeIter child_iter;
1253         GtkTreeModel *model;
1254         GtkTreeModel *keyword_tree;
1255         GList *list, *work;
1256         GList *keywords = NULL;
1257
1258         GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1259
1260         /* Aquire selected keyword */
1261         if (pkd->click_tpath)
1262                 {
1263                 gboolean is_keyword = TRUE;
1264
1265                 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1266                 if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
1267                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1268                 if (!is_keyword) return;
1269                 }
1270         else
1271                 return;
1272
1273         keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1274         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
1275
1276         list = keyword_list_pull(pkd->keyword_view); /* Get the left keyword view */
1277
1278         /* Now set the current image */
1279         keyword_tree_set(keyword_tree, &child_iter, &list);
1280
1281         keyword_list_push(pkd->keyword_view, list); /* Set the left keyword view */
1282         string_list_free(list);
1283
1284         bar_pane_keywords_changed(keyword_buffer, pkd); /* Get list of all keywords in the hierarchy */
1285
1286         gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
1287         keywords = keyword_tree_get(keyword_tree, &child_iter);
1288
1289         list = layout_selection_list(pkd->pane.lw);
1290         work = list;
1291         while (work)
1292                 {
1293                 FileData *fd = work->data;
1294                 work = work->next;
1295                 metadata_append_list(fd, KEYWORD_KEY, keywords);
1296                 }
1297         filelist_free(list);
1298         string_list_free(keywords);
1299 }
1300
1301 static void bar_pane_keywords_menu_popup(GtkWidget *widget, PaneKeywordsData *pkd, gint x, gint y)
1302 {
1303         GtkWidget *menu;
1304         GtkWidget *item;
1305         GtkWidget *submenu;
1306         GtkTreeViewDropPosition pos;
1307
1308         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1309         pkd->click_tpath = NULL;
1310         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(pkd->keyword_treeview), x, y, &pkd->click_tpath, &pos);
1311
1312         menu = popup_menu_short_lived();
1313
1314         menu_item_add_stock(menu, _("New keyword"), GTK_STOCK_NEW, G_CALLBACK(bar_pane_keywords_add_dialog_cb), pkd);
1315
1316         menu_item_add_divider(menu);
1317
1318         if (pkd->click_tpath)
1319                 {
1320                 /* for the entry */
1321                 gchar *text;
1322                 gchar *mark;
1323                 gint i;
1324                 gboolean keyword;
1325
1326                 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1327
1328                 GtkTreeIter iter;
1329                 gtk_tree_model_get_iter(model, &iter, pkd->click_tpath);
1330                 gchar *name;
1331
1332                 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
1333                                                 FILTER_KEYWORD_COLUMN_MARK, &mark,
1334                                                 FILTER_KEYWORD_COLUMN_IS_KEYWORD, &keyword, -1);
1335
1336                 if (keyword)
1337                         {
1338                         text = g_strdup_printf(_("Add \"%s\" to all selected images"), name);
1339                         menu_item_add_stock(menu, text, GTK_STOCK_ADD, G_CALLBACK(bar_pane_keywords_add_to_selected_cb), pkd);
1340                         g_free(text);
1341                         }
1342                 menu_item_add_divider(menu);
1343
1344                 text = g_strdup_printf(_("Hide \"%s\""), name);
1345                 menu_item_add(menu, text, G_CALLBACK(bar_pane_keywords_hide_cb), pkd);
1346                 g_free(text);
1347
1348                 submenu = gtk_menu_new();
1349                 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1350                         {
1351                         text = g_strdup_printf(_("Mark %d"), 1 + (i < 9 ? i : -1));
1352                         item = menu_item_add(submenu, text, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1353                         g_object_set_data(G_OBJECT(item), "mark", GINT_TO_POINTER(i + 1));
1354                         g_free(text);
1355                         }
1356
1357                 if (keyword)
1358                         {
1359                         text = g_strdup_printf(_("Connect \"%s\" to mark"), name);
1360                         item = menu_item_add(menu, text, NULL, NULL);
1361                         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1362                         g_free(text);
1363                         }
1364                 menu_item_add_divider(menu);
1365
1366                 text = g_strdup_printf(_("Edit \"%s\""), name);
1367                 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_edit_dialog_cb), pkd);
1368                 g_free(text);
1369                 text = g_strdup_printf(_("Remove \"%s\""), name);
1370                 menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_delete_cb), pkd);
1371                 g_free(text);
1372
1373
1374                 if (mark && mark[0])
1375                         {
1376                         text = g_strdup_printf(_("Disconnect \"%s\" from mark %s"), name, mark);
1377                         menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1378                         g_free(text);
1379                         }
1380
1381                 if (keyword)
1382                         {
1383                         text = g_strdup_printf(_("Disconnect all Mark Keyword connections"));
1384                         menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_disconnect_marks_cb), pkd);
1385                         g_free(text);
1386                         }
1387                 menu_item_add_divider(menu);
1388                 g_free(mark);
1389                 g_free(name);
1390                 }
1391         /* for the pane */
1392
1393
1394         menu_item_add(menu, _("Expand checked"), G_CALLBACK(bar_pane_keywords_expand_checked_cb), pkd);
1395         menu_item_add(menu, _("Collapse unchecked"), G_CALLBACK(bar_pane_keywords_collapse_unchecked_cb), pkd);
1396         menu_item_add(menu, _("Hide unchecked"), G_CALLBACK(bar_pane_keywords_hide_unchecked_cb), pkd);
1397         menu_item_add(menu, _("Revert all hidden"), G_CALLBACK(bar_pane_keywords_revert_hidden_cb), pkd);
1398         menu_item_add_divider(menu);
1399         menu_item_add(menu, _("Show all"), G_CALLBACK(bar_pane_keywords_show_all_cb), pkd);
1400         menu_item_add(menu, _("Collapse all"), G_CALLBACK(bar_pane_keywords_collapse_all_cb), pkd);
1401         menu_item_add(menu, _("Revert"), G_CALLBACK(bar_pane_keywords_revert_cb), pkd);
1402         menu_item_add_divider(menu);
1403
1404         submenu = gtk_menu_new();
1405         item = menu_item_add(menu, _("On any change"), NULL, NULL);
1406         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1407
1408         menu_item_add_check(submenu, _("Expand checked"), pkd->expand_checked, G_CALLBACK(bar_pane_keywords_expand_checked_toggle_cb), pkd);
1409         menu_item_add_check(submenu, _("Collapse unchecked"), pkd->collapse_unchecked, G_CALLBACK(bar_pane_keywords_collapse_unchecked_toggle_cb), pkd);
1410         menu_item_add_check(submenu, _("Hide unchecked"), pkd->hide_unchecked, G_CALLBACK(bar_pane_keywords_hide_unchecked_toggle_cb), pkd);
1411
1412         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
1413 }
1414
1415
1416 static gboolean bar_pane_keywords_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1417 {
1418         PaneKeywordsData *pkd = data;
1419         if (bevent->button == MOUSE_BUTTON_RIGHT)
1420                 {
1421                 bar_pane_keywords_menu_popup(widget, pkd, bevent->x, bevent->y);
1422                 return TRUE;
1423                 }
1424         return FALSE;
1425 }
1426
1427 /*
1428  *-------------------------------------------------------------------
1429  * init
1430  *-------------------------------------------------------------------
1431  */
1432
1433 void bar_pane_keywords_close(GtkWidget *bar)
1434 {
1435         PaneKeywordsData *pkd;
1436
1437         pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
1438         if (!pkd) return;
1439
1440         g_free(pkd->pane.id);
1441         gtk_widget_destroy(pkd->widget);
1442 }
1443
1444 static void bar_pane_keywords_destroy(GtkWidget *widget, gpointer data)
1445 {
1446         PaneKeywordsData *pkd = data;
1447         gchar *path;
1448
1449         path = g_build_filename(get_rc_dir(), "keywords", NULL);
1450         autocomplete_keywords_list_save(path);
1451
1452         string_list_free(pkd->expanded_rows);
1453         if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1454         if (pkd->idle_id) g_source_remove(pkd->idle_id);
1455         file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
1456
1457         file_data_unref(pkd->fd);
1458         g_free(pkd->key);
1459
1460         g_free(pkd);
1461 }
1462
1463
1464 static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded, gint height)
1465 {
1466         PaneKeywordsData *pkd;
1467         GtkWidget *hbox, *vbox;
1468         GtkWidget *scrolled;
1469         GtkTextBuffer *buffer;
1470         GtkTreeModel *store;
1471         GtkTreeViewColumn *column;
1472         GtkCellRenderer *renderer;
1473         GtkTreeIter iter;
1474         GtkEntryCompletion *completion;
1475         gchar *path;
1476
1477         pkd = g_new0(PaneKeywordsData, 1);
1478
1479         pkd->pane.pane_set_fd = bar_pane_keywords_set_fd;
1480         pkd->pane.pane_event = bar_pane_keywords_event;
1481         pkd->pane.pane_write_config = bar_pane_keywords_write_config;
1482         pkd->pane.title = bar_pane_expander_title(title);
1483         pkd->pane.id = g_strdup(id);
1484         pkd->pane.type = PANE_KEYWORDS;
1485
1486         pkd->pane.expanded = expanded;
1487
1488         pkd->height = height;
1489         pkd->key = g_strdup(key);
1490
1491         pkd->expand_checked = TRUE;
1492         pkd->expanded_rows = NULL;
1493
1494         vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
1495         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
1496         gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1497
1498         pkd->widget = vbox;
1499         g_object_set_data(G_OBJECT(pkd->widget), "pane_data", pkd);
1500         g_signal_connect(G_OBJECT(pkd->widget), "destroy",
1501                          G_CALLBACK(bar_pane_keywords_destroy), pkd);
1502         gtk_widget_set_size_request(pkd->widget, -1, height);
1503         gtk_widget_show(hbox);
1504
1505         scrolled = gtk_scrolled_window_new(NULL, NULL);
1506         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1507         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1508                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1509         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1510         gtk_widget_show(scrolled);
1511
1512         pkd->keyword_view = gtk_text_view_new();
1513         gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_view);
1514         g_signal_connect(G_OBJECT(pkd->keyword_view), "populate-popup",
1515                          G_CALLBACK(bar_pane_keywords_populate_popup_cb), pkd);
1516         gtk_widget_show(pkd->keyword_view);
1517
1518         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1519         g_signal_connect(G_OBJECT(buffer), "changed",
1520                          G_CALLBACK(bar_pane_keywords_changed), pkd);
1521
1522         if (options->show_predefined_keyword_tree)
1523                 {
1524                 scrolled = gtk_scrolled_window_new(NULL, NULL);
1525                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1526                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1527                                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1528                 gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1529                 gtk_widget_show(scrolled);
1530                 }
1531
1532         pkd->autocomplete = gtk_entry_new();
1533         gtk_box_pack_end(GTK_BOX(vbox), pkd->autocomplete, FALSE, FALSE, 0);
1534         gtk_widget_show(pkd->autocomplete);
1535         gtk_widget_show(vbox);
1536         gtk_widget_set_tooltip_text(pkd->autocomplete, "Keyword autocomplete");
1537
1538         path = g_build_filename(get_rc_dir(), "keywords", NULL);
1539         autocomplete_keywords_list_load(path);
1540
1541         completion = gtk_entry_completion_new();
1542         gtk_entry_set_completion(GTK_ENTRY(pkd->autocomplete), completion);
1543         gtk_entry_completion_set_inline_completion(completion, TRUE);
1544         gtk_entry_completion_set_inline_selection(completion, TRUE);
1545         g_object_unref(completion);
1546
1547         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(keyword_store));
1548         gtk_entry_completion_set_text_column(completion, 0);
1549
1550         g_signal_connect(G_OBJECT(pkd->autocomplete), "activate",
1551                          G_CALLBACK(autocomplete_activate_cb), pkd);
1552
1553         if (!keyword_tree || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1554                 {
1555                 /* keyword tree does not exist or is empty - fill with defaults */
1556                 keyword_tree_new_default();
1557                 }
1558
1559         store = gtk_tree_model_filter_new(GTK_TREE_MODEL(keyword_tree), NULL);
1560
1561         gtk_tree_model_filter_set_modify_func(GTK_TREE_MODEL_FILTER(store),
1562                                               FILTER_KEYWORD_COLUMN_COUNT,
1563                                               filter_keyword_column_types,
1564                                               bar_pane_keywords_filter_modify,
1565                                               pkd,
1566                                               NULL);
1567         gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(store),
1568                                                bar_pane_keywords_filter_visible,
1569                                                store,
1570                                                NULL);
1571
1572         pkd->keyword_treeview = gtk_tree_view_new_with_model(store);
1573         g_object_unref(store);
1574
1575         gtk_widget_set_size_request(pkd->keyword_treeview, -1, 400);
1576
1577         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pkd->keyword_treeview), FALSE);
1578
1579 //      gtk_tree_view_set_search_column(GTK_TREE_VIEW(pkd->keyword_treeview), FILTER_KEYWORD_COLUMN_);
1580
1581         column = gtk_tree_view_column_new();
1582         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1583
1584         renderer = gtk_cell_renderer_text_new();
1585         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1586
1587         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_MARK);
1588
1589         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1590
1591         column = gtk_tree_view_column_new();
1592         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1593         renderer = gtk_cell_renderer_toggle_new();
1594         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1595         gtk_tree_view_column_add_attribute(column, renderer, "active", FILTER_KEYWORD_COLUMN_TOGGLE);
1596         gtk_tree_view_column_add_attribute(column, renderer, "visible", FILTER_KEYWORD_COLUMN_IS_KEYWORD);
1597         g_signal_connect(G_OBJECT(renderer), "toggled",
1598                          G_CALLBACK(bar_pane_keywords_keyword_toggle), pkd);
1599
1600         renderer = gtk_cell_renderer_text_new();
1601         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1602         gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_NAME);
1603
1604         gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1605         gtk_tree_view_set_expander_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1606
1607         gtk_drag_source_set(pkd->keyword_treeview,
1608                             GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
1609                             bar_pane_keywords_drag_types, n_keywords_drag_types,
1610                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1611
1612         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_get",
1613                          G_CALLBACK(bar_pane_keywords_dnd_get), pkd);
1614
1615         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_begin",
1616                          G_CALLBACK(bar_pane_keywords_dnd_begin), pkd);
1617         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_end",
1618                          G_CALLBACK(bar_pane_keywords_dnd_end), pkd);
1619
1620         gtk_drag_dest_set(pkd->keyword_treeview,
1621                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
1622                           bar_pane_keywords_drop_types, n_keywords_drop_types,
1623                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
1624
1625         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_received",
1626                          G_CALLBACK(bar_pane_keywords_dnd_receive), pkd);
1627
1628         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_motion",
1629                          G_CALLBACK(bar_pane_keywords_dnd_motion), pkd);
1630
1631         g_signal_connect(G_OBJECT(pkd->keyword_treeview), "button_release_event",
1632                          G_CALLBACK(bar_pane_keywords_menu_cb), pkd);
1633
1634         if (options->show_predefined_keyword_tree)
1635                 {
1636                 gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_treeview);
1637                 gtk_widget_show(pkd->keyword_treeview);
1638                 }
1639
1640         file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
1641
1642         return pkd->widget;
1643 }
1644
1645 GtkWidget *bar_pane_keywords_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
1646 {
1647         gchar *id = g_strdup("keywords");
1648         gchar *title = NULL;
1649         gchar *key = g_strdup(COMMENT_KEY);
1650         gboolean expanded = TRUE;
1651         gint height = 200;
1652         GtkWidget *ret;
1653
1654         while (*attribute_names)
1655                 {
1656                 const gchar *option = *attribute_names++;
1657                 const gchar *value = *attribute_values++;
1658
1659                 if (READ_CHAR_FULL("id", id)) continue;
1660                 if (READ_CHAR_FULL("title", title)) continue;
1661                 if (READ_CHAR_FULL("key", key)) continue;
1662                 if (READ_BOOL_FULL("expanded", expanded)) continue;
1663                 if (READ_INT_FULL("height", height)) continue;
1664
1665
1666                 log_printf("unknown attribute %s = %s\n", option, value);
1667                 }
1668
1669         options->info_keywords.height = height;
1670         bar_pane_translate_title(PANE_KEYWORDS, id, &title);
1671         ret = bar_pane_keywords_new(id, title, key, expanded, height);
1672         g_free(id);
1673         g_free(title);
1674         g_free(key);
1675         return ret;
1676 }
1677
1678 void bar_pane_keywords_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1679 {
1680         PaneKeywordsData *pkd;
1681
1682         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
1683         if (!pkd) return;
1684
1685         gchar *title = NULL;
1686
1687         while (*attribute_names)
1688                 {
1689                 const gchar *option = *attribute_names++;
1690                 const gchar *value = *attribute_values++;
1691
1692                 if (READ_CHAR_FULL("title", title)) continue;
1693                 if (READ_CHAR_FULL("key", pkd->key)) continue;
1694                 if (READ_BOOL_FULL("expanded", pkd->pane.expanded)) continue;
1695                 if (READ_CHAR_FULL("id", pkd->pane.id)) continue;
1696
1697
1698                 log_printf("unknown attribute %s = %s\n", option, value);
1699                 }
1700
1701         if (title)
1702                 {
1703                 bar_pane_translate_title(PANE_KEYWORDS, pkd->pane.id, &title);
1704                 gtk_label_set_text(GTK_LABEL(pkd->pane.title), title);
1705                 g_free(title);
1706                 }
1707
1708         bar_update_expander(pane);
1709         bar_pane_keywords_update(pkd);
1710 }
1711
1712
1713 void bar_pane_keywords_entry_add_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
1714 {
1715         PaneKeywordsData *pkd;
1716         gchar *path = NULL;
1717         GtkTreePath *tree_path;
1718
1719         pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
1720         if (!pkd) return;
1721
1722         while (*attribute_names)
1723                 {
1724                 const gchar *option = *attribute_names++;
1725                 const gchar *value = *attribute_values++;
1726
1727                 if (READ_CHAR_FULL("path", path))
1728                         {
1729                         tree_path = gtk_tree_path_new_from_string(path);
1730                         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), tree_path);
1731                         gtk_tree_path_free(tree_path);
1732                         pkd->expanded_rows = g_list_append(pkd->expanded_rows, g_strdup(path));
1733                         continue;
1734                         }
1735                 log_printf("unknown attribute %s = %s\n", option, value);
1736                 }
1737 }
1738
1739 /*
1740  *-----------------------------------------------------------------------------
1741  * Autocomplete keywords
1742  *-----------------------------------------------------------------------------
1743  */
1744
1745 static gboolean autocomplete_activate_cb(GtkWidget *widget, gpointer data)
1746 {
1747         PaneKeywordsData *pkd = data;
1748         gchar *entry_text;
1749         GtkTextBuffer *buffer;
1750         GtkTextIter iter;
1751         GtkTreeIter iter_t;
1752         gchar *kw_cr;
1753         gchar *kw_split;
1754         gboolean valid;
1755         gboolean found = FALSE;
1756         gchar *string;
1757
1758         entry_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pkd->autocomplete)));
1759         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1760
1761         kw_split = strtok(entry_text, ",");
1762         while (kw_split != NULL)
1763                 {
1764                 kw_cr = g_strconcat(kw_split, "\n", NULL);
1765                 g_strchug(kw_cr);
1766                 gtk_text_buffer_get_end_iter(buffer, &iter);
1767                 gtk_text_buffer_insert(buffer, &iter, kw_cr, -1);
1768
1769                 kw_split = strtok(NULL, ",");
1770                 g_free(kw_cr);
1771                 }
1772
1773         g_free(entry_text);
1774         entry_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pkd->autocomplete)));
1775
1776         gtk_entry_set_text(GTK_ENTRY(pkd->autocomplete), "");
1777
1778         valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter_t);
1779         while (valid)
1780                 {
1781                 gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter_t, 0, &string, -1);
1782                 if (g_strcmp0(entry_text, string) == 0)
1783                         {
1784                         found = TRUE;
1785                         break;
1786                         }
1787                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter_t);
1788                 }
1789
1790         if (!found)
1791                 {
1792                 gtk_list_store_append (keyword_store, &iter_t);
1793                 gtk_list_store_set(keyword_store, &iter_t, 0, entry_text, -1);
1794                 }
1795
1796         g_free(entry_text);
1797         return FALSE;
1798 }
1799
1800 gint autocomplete_sort_iter_compare_func (GtkTreeModel *model,
1801                                                                         GtkTreeIter *a,
1802                                                                         GtkTreeIter *b,
1803                                                                         gpointer userdata)
1804 {
1805         gint ret = 0;
1806         gchar *name1, *name2;
1807
1808         gtk_tree_model_get(model, a, 0, &name1, -1);
1809         gtk_tree_model_get(model, b, 0, &name2, -1);
1810
1811         if (name1 == NULL || name2 == NULL)
1812                 {
1813                 if (name1 == NULL && name2 == NULL)
1814                         {
1815                         ret = 0;
1816                         }
1817                 else
1818                         {
1819                         ret = (name1 == NULL) ? -1 : 1;
1820                         }
1821                 }
1822         else
1823                 {
1824                 ret = g_utf8_collate(name1,name2);
1825                 }
1826
1827         g_free(name1);
1828         g_free(name2);
1829
1830         return ret;
1831 }
1832
1833 static void autocomplete_keywords_list_load(const gchar *path)
1834 {
1835         FILE *f;
1836         gchar s_buf[1024];
1837         gchar *pathl;
1838         gint len;
1839         GtkTreeIter iter;
1840         GtkTreeSortable *sortable;
1841
1842         if (keyword_store) return;
1843         keyword_store = gtk_list_store_new(1, G_TYPE_STRING);
1844
1845         sortable = GTK_TREE_SORTABLE(keyword_store);
1846         gtk_tree_sortable_set_sort_func(sortable, 0, autocomplete_sort_iter_compare_func,
1847                                                                                                 GINT_TO_POINTER(0), NULL);
1848
1849         gtk_tree_sortable_set_sort_column_id(sortable, 0, GTK_SORT_ASCENDING);
1850
1851         pathl = path_from_utf8(path);
1852         f = fopen(pathl, "r");
1853         g_free(pathl);
1854
1855         if (!f)
1856                 {
1857                 log_printf("Warning: keywords file %s not loaded", pathl);
1858                 return;
1859                 }
1860
1861         /* first line must start with Keywords comment */
1862         if (!fgets(s_buf, sizeof(s_buf), f) ||
1863                                         strncmp(s_buf, "#Keywords", 9) != 0)
1864                 {
1865                 fclose(f);
1866                 log_printf("Warning: keywords file %s not loaded", pathl);
1867                 return;
1868                 }
1869
1870         while (fgets(s_buf, sizeof(s_buf), f))
1871                 {
1872                 if (s_buf[0]=='#') continue;
1873
1874                 len = strlen(s_buf);
1875                 if( s_buf[len-1] == '\n' )
1876                         {
1877                         s_buf[len-1] = 0;
1878                         }
1879                 gtk_list_store_append (keyword_store, &iter);
1880                 gtk_list_store_set(keyword_store, &iter, 0, g_strdup(s_buf), -1);
1881                 }
1882
1883         fclose(f);
1884 }
1885
1886 static gboolean autocomplete_keywords_list_save(gchar *path)
1887 {
1888         SecureSaveInfo *ssi;
1889         gchar *pathl;
1890         gchar *string;
1891         gchar *string_nl;
1892         GtkTreeIter  iter;
1893         gboolean     valid;
1894
1895         pathl = path_from_utf8(path);
1896         ssi = secure_open(pathl);
1897         g_free(pathl);
1898
1899         if (!ssi)
1900                 {
1901                 log_printf(_("Error: Unable to write keywords list to: %s\n"), path);
1902                 return FALSE;
1903                 }
1904
1905         secure_fprintf(ssi, "#Keywords list\n");
1906
1907         valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter);
1908
1909         while (valid)
1910                 {
1911                 gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter, 0, &string, -1);
1912                 string_nl = g_strconcat(string, "\n", NULL);
1913                 secure_fprintf(ssi, "%s", string_nl);
1914
1915                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter);
1916
1917                 g_free(string_nl);
1918                 }
1919
1920         secure_fprintf(ssi, "#end\n");
1921         return (secure_close(ssi) == 0);
1922 }
1923
1924 GList *keyword_list_get()
1925 {
1926         GList *ret_list = NULL;
1927         gchar *string;
1928         gchar *string_nl;
1929         GtkTreeIter iter;
1930         gboolean valid;
1931
1932         valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter);
1933
1934         while (valid)
1935                 {
1936                 gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter, 0, &string, -1);
1937                 string_nl = g_strconcat(string, "\n", NULL);
1938                 ret_list = g_list_append(ret_list, string);
1939                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter);
1940
1941                 g_free(string_nl);
1942                 }
1943
1944         return ret_list;
1945 }
1946
1947 void keyword_list_set(GList *keyword_list)
1948 {
1949         GtkTreeIter  iter;
1950
1951         if (!keyword_list) return;
1952
1953         gtk_list_store_clear(keyword_store);
1954
1955         while (keyword_list)
1956                 {
1957                 gtk_list_store_append (keyword_store, &iter);
1958                 gtk_list_store_set(keyword_store, &iter, 0, keyword_list->data, -1);
1959
1960                 keyword_list = keyword_list->next;
1961                 }
1962 }
1963
1964 gboolean bar_keywords_autocomplete_focus(LayoutWindow *lw)
1965 {
1966         GtkWidget *pane;
1967         GtkWidget *current_focus;
1968         GList *children;
1969         gboolean ret;
1970
1971         current_focus = gtk_window_get_focus(GTK_WINDOW(lw->window));
1972         pane = bar_find_pane_by_id(lw->bar, PANE_KEYWORDS, "keywords");
1973
1974         children = gtk_container_get_children(GTK_CONTAINER(pane));
1975         children = g_list_last(children);
1976
1977         if (current_focus == children->data)
1978                 {
1979                 ret = TRUE;
1980                 }
1981         else
1982                 {
1983                 gtk_widget_grab_focus(children->data);
1984                 ret = FALSE;
1985                 }
1986
1987         g_list_free(children);
1988
1989         return ret;
1990 }
1991 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */