4 * Copyright (C) 2008 - 2009 The Geeqie Team
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!
13 #include <glib/gprintf.h>
16 #include "bar_keywords.h"
19 #include "history_list.h"
22 #include "ui_fileops.h"
24 #include "ui_utildlg.h"
33 static void bar_pane_keywords_keyword_update_all(void);
34 static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data);
37 *-------------------------------------------------------------------
38 * keyword / comment utils
39 *-------------------------------------------------------------------
43 GList *keyword_list_pull(GtkWidget *text_widget)
48 text = text_widget_text_pull(text_widget);
49 list = string_to_keywords_list(text);
56 static void keyword_list_push(GtkWidget *textview, GList *list)
58 GtkTextBuffer *buffer;
59 GtkTextIter start, end;
61 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
62 gtk_text_buffer_get_bounds(buffer, &start, &end);
63 gtk_text_buffer_delete(buffer, &start, &end);
67 const gchar *word = list->data;
70 gtk_text_buffer_get_end_iter(buffer, &iter);
71 if (word) gtk_text_buffer_insert(buffer, &iter, word, -1);
72 gtk_text_buffer_get_end_iter(buffer, &iter);
73 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
81 *-------------------------------------------------------------------
83 *-------------------------------------------------------------------
88 FILTER_KEYWORD_COLUMN_TOGGLE = 0,
89 FILTER_KEYWORD_COLUMN_MARK,
90 FILTER_KEYWORD_COLUMN_NAME,
91 FILTER_KEYWORD_COLUMN_IS_KEYWORD,
92 FILTER_KEYWORD_COLUMN_COUNT
95 static GType filter_keyword_column_types[] = {G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN};
97 typedef struct _PaneKeywordsData PaneKeywordsData;
98 struct _PaneKeywordsData
103 GtkWidget *keyword_view;
104 GtkWidget *keyword_treeview;
106 GtkTreePath *click_tpath;
108 gboolean expand_checked;
109 gboolean collapse_unchecked;
110 gboolean hide_unchecked;
116 typedef struct _ConfDialogData ConfDialogData;
117 struct _ConfDialogData
119 PaneKeywordsData *pkd;
120 GtkTreePath *click_tpath;
124 GtkWidget *edit_widget;
127 gboolean edit_existing;
130 static GList *bar_list = NULL;
133 static void bar_pane_keywords_write(PaneKeywordsData *pkd)
137 if (!pkd->fd) return;
139 list = keyword_list_pull(pkd->keyword_view);
141 metadata_write_list(pkd->fd, KEYWORD_KEY, list);
143 string_list_free(list);
146 gboolean bar_keyword_tree_expand_if_set_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
148 PaneKeywordsData *pkd = data;
151 gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set, -1);
153 if (set && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
155 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pkd->keyword_treeview), path);
160 gboolean bar_keyword_tree_collapse_if_unset_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
162 PaneKeywordsData *pkd = data;
166 gtk_tree_model_get(model, iter, FILTER_KEYWORD_COLUMN_TOGGLE, &set,
167 FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
169 if (is_keyword && !set && gtk_tree_view_row_expanded(GTK_TREE_VIEW(pkd->keyword_treeview), path))
171 gtk_tree_view_collapse_row(GTK_TREE_VIEW(pkd->keyword_treeview), path);
176 static void bar_keyword_tree_sync(PaneKeywordsData *pkd)
180 GtkTreeModel *keyword_tree;
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));
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);
191 gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(model));
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);
197 static void bar_pane_keywords_keyword_update_all(void)
204 PaneKeywordsData *pkd;
210 bar_keyword_tree_sync(pkd);
214 static void bar_pane_keywords_update(PaneKeywordsData *pkd)
216 GList *keywords = NULL;
217 GtkTextBuffer *keyword_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
219 g_signal_handlers_block_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
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);
226 g_signal_handlers_unblock_by_func(keyword_buffer, bar_pane_keywords_changed, pkd);
230 void bar_pane_keywords_set_fd(GtkWidget *pane, FileData *fd)
232 PaneKeywordsData *pkd;
234 pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
237 file_data_unref(pkd->fd);
238 pkd->fd = file_data_ref(fd);
240 bar_pane_keywords_update(pkd);
243 static void bar_pane_keywords_write_config(GtkWidget *pane, GString *outstr, gint indent)
245 PaneKeywordsData *pkd;
247 pkd = g_object_get_data(G_OBJECT(pane), "pane_data");
250 WRITE_STRING("<pane_keywords\n");
252 write_char_option(outstr, indent, "pane.title", gtk_label_get_text(GTK_LABEL(pkd->pane.title)));
253 WRITE_BOOL(*pkd, pane.expanded);
254 WRITE_CHAR(*pkd, key);
256 WRITE_STRING("/>\n");
259 gint bar_pane_keywords_event(GtkWidget *bar, GdkEvent *event)
261 PaneKeywordsData *pkd;
263 pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
264 if (!pkd) return FALSE;
266 if (GTK_WIDGET_HAS_FOCUS(pkd->keyword_view)) return gtk_widget_event(pkd->keyword_view, event);
271 static void bar_pane_keywords_keyword_toggle(GtkCellRendererToggle *toggle, const gchar *path, gpointer data)
273 PaneKeywordsData *pkd = data;
279 GtkTreeIter child_iter;
280 GtkTreeModel *keyword_tree;
282 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
284 tpath = gtk_tree_path_new_from_string(path);
285 gtk_tree_model_get_iter(model, &iter, tpath);
286 gtk_tree_path_free(tpath);
288 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_TOGGLE, &active, -1);
292 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
293 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
295 list = keyword_list_pull(pkd->keyword_view);
297 keyword_tree_set(keyword_tree, &child_iter, &list);
299 keyword_tree_reset(keyword_tree, &child_iter, &list);
301 keyword_list_push(pkd->keyword_view, list);
302 string_list_free(list);
304 keyword_list_push triggers bar_pane_keywords_change which calls bar_keyword_tree_sync, no need to do it again
305 bar_keyword_tree_sync(pkd);
309 void bar_pane_keywords_filter_modify(GtkTreeModel *model, GtkTreeIter *iter, GValue *value, gint column, gpointer data)
311 PaneKeywordsData *pkd = data;
312 GtkTreeModel *keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
313 GtkTreeIter child_iter;
315 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, iter);
317 memset(value, 0, sizeof (GValue));
321 case FILTER_KEYWORD_COLUMN_TOGGLE:
323 GList *keywords = keyword_list_pull(pkd->keyword_view);
324 gboolean set = keyword_tree_is_set(keyword_tree, &child_iter, keywords);
325 string_list_free(keywords);
327 g_value_init(value, G_TYPE_BOOLEAN);
328 g_value_set_boolean(value, set);
331 case FILTER_KEYWORD_COLUMN_MARK:
332 gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_MARK, value);
334 case FILTER_KEYWORD_COLUMN_NAME:
335 gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_NAME, value);
337 case FILTER_KEYWORD_COLUMN_IS_KEYWORD:
338 gtk_tree_model_get_value(keyword_tree, &child_iter, KEYWORD_COLUMN_IS_KEYWORD, value);
345 gboolean bar_pane_keywords_filter_visible(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer data)
347 GtkTreeModel *filter = data;
349 return !keyword_is_hidden_in(keyword_tree, iter, filter);
352 static void bar_pane_keywords_set_selection(PaneKeywordsData *pkd, gboolean append)
354 GList *keywords = NULL;
358 keywords = keyword_list_pull(pkd->keyword_view);
360 list = layout_selection_list(pkd->pane.lw);
364 FileData *fd = work->data;
369 metadata_append_list(fd, KEYWORD_KEY, keywords);
373 metadata_write_list(fd, KEYWORD_KEY, keywords);
378 string_list_free(keywords);
381 static void bar_pane_keywords_sel_add_cb(GtkWidget *button, gpointer data)
383 PaneKeywordsData *pkd = data;
385 bar_pane_keywords_set_selection(pkd, TRUE);
388 static void bar_pane_keywords_sel_replace_cb(GtkWidget *button, gpointer data)
390 PaneKeywordsData *pkd = data;
392 bar_pane_keywords_set_selection(pkd, FALSE);
395 static void bar_pane_keywords_populate_popup_cb(GtkTextView *textview, GtkMenu *menu, gpointer data)
397 PaneKeywordsData *pkd = data;
399 menu_item_add_divider(GTK_WIDGET(menu));
400 menu_item_add_stock(GTK_WIDGET(menu), _("Add keywords to selected files"), GTK_STOCK_ADD, G_CALLBACK(bar_pane_keywords_sel_add_cb), pkd);
401 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);
405 static void bar_pane_keywords_notify_cb(FileData *fd, NotifyType type, gpointer data)
407 PaneKeywordsData *pkd = data;
408 if (fd == pkd->fd) bar_pane_keywords_update(pkd);
411 static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data)
413 PaneKeywordsData *pkd = data;
415 file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
416 bar_pane_keywords_write(pkd);
417 bar_keyword_tree_sync(pkd);
418 file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
423 *-------------------------------------------------------------------
425 *-------------------------------------------------------------------
429 static GtkTargetEntry bar_pane_keywords_drag_types[] = {
430 { TARGET_APP_KEYWORD_PATH_STRING, GTK_TARGET_SAME_WIDGET, TARGET_APP_KEYWORD_PATH },
431 { "text/plain", 0, TARGET_TEXT_PLAIN }
433 static gint n_keywords_drag_types = 2;
436 static GtkTargetEntry bar_pane_keywords_drop_types[] = {
437 { TARGET_APP_KEYWORD_PATH_STRING, GTK_TARGET_SAME_WIDGET, TARGET_APP_KEYWORD_PATH },
438 { "text/plain", 0, TARGET_TEXT_PLAIN }
440 static gint n_keywords_drop_types = 2;
443 static void bar_pane_keywords_dnd_get(GtkWidget *tree_view, GdkDragContext *context,
444 GtkSelectionData *selection_data, guint info,
445 guint time, gpointer data)
449 GtkTreeIter child_iter;
450 GtkTreeModel *keyword_tree;
452 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
454 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
456 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
457 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
461 case TARGET_APP_KEYWORD_PATH:
463 GList *path = keyword_tree_get_path(keyword_tree, &child_iter);
464 gtk_selection_data_set(selection_data, selection_data->target,
465 8, (gpointer) &path, sizeof(path));
469 case TARGET_TEXT_PLAIN:
472 gchar *name = keyword_get_name(keyword_tree, &child_iter);
473 gtk_selection_data_set_text(selection_data, name, -1);
480 static void bar_pane_keywords_dnd_begin(GtkWidget *tree_view, GdkDragContext *context, gpointer data)
484 GtkTreeIter child_iter;
485 GtkTreeModel *keyword_tree;
488 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
490 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
492 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
493 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
495 name = keyword_get_name(keyword_tree, &child_iter);
497 dnd_set_drag_label(tree_view, context, name);
502 static void bar_pane_keywords_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
507 static gboolean bar_pane_keywords_dnd_can_move(GtkTreeModel *keyword_tree, GtkTreeIter *src_kw_iter, GtkTreeIter *dest_kw_iter)
512 if (dest_kw_iter && keyword_same_parent(keyword_tree, src_kw_iter, dest_kw_iter))
514 return TRUE; /* reordering of siblings is ok */
516 if (!dest_kw_iter && !gtk_tree_model_iter_parent(keyword_tree, &parent, src_kw_iter))
518 return TRUE; /* reordering of top-level siblings is ok */
521 src_name = keyword_get_name(keyword_tree, src_kw_iter);
522 if (keyword_exists(keyword_tree, NULL, dest_kw_iter, src_name, FALSE))
531 static gboolean bar_pane_keywords_dnd_skip_existing(GtkTreeModel *keyword_tree, GtkTreeIter *dest_kw_iter, GList **keywords)
533 /* we have to find at least one keyword that does not already exist as a sibling of dest_kw_iter */
534 GList *work = *keywords;
537 gchar *keyword = work->data;
538 if (keyword_exists(keyword_tree, NULL, dest_kw_iter, keyword, FALSE))
540 GList *next = work->next;
542 *keywords = g_list_delete_link(*keywords, work);
553 static void bar_pane_keywords_dnd_receive(GtkWidget *tree_view, GdkDragContext *context,
555 GtkSelectionData *selection_data, guint info,
556 guint time, gpointer data)
558 PaneKeywordsData *pkd = data;
559 GtkTreePath *tpath = NULL;
560 GtkTreeViewDropPosition pos;
563 GtkTreeModel *keyword_tree;
564 gboolean src_valid = FALSE;
565 GList *new_keywords = NULL;
568 /* iterators for keyword_tree */
569 GtkTreeIter src_kw_iter;
570 GtkTreeIter dest_kw_iter;
571 GtkTreeIter new_kw_iter;
573 g_signal_stop_emission_by_name(tree_view, "drag_data_received");
575 model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
576 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
578 gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &tpath, &pos);
579 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), NULL, pos);
583 case TARGET_APP_KEYWORD_PATH:
585 GList *path = *(gpointer *)selection_data->data;
586 src_valid = keyword_tree_get_iter(keyword_tree, &src_kw_iter, path);
587 string_list_free(path);
591 new_keywords = string_to_keywords_list((gchar *)selection_data->data);
597 GtkTreeIter dest_iter;
598 gtk_tree_model_get_iter(model, &dest_iter, tpath);
599 gtk_tree_path_free(tpath);
600 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &dest_kw_iter, &dest_iter);
602 if (src_valid && gtk_tree_store_is_ancestor(GTK_TREE_STORE(keyword_tree), &src_kw_iter, &dest_kw_iter))
604 /* can't move to it's own child */
608 if (src_valid && keyword_compare(keyword_tree, &src_kw_iter, &dest_kw_iter) == 0)
610 /* can't move to itself */
614 if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) &&
615 !gtk_tree_model_iter_has_child(keyword_tree, &dest_kw_iter))
617 /* the node has no children, all keywords can be added */
618 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &new_kw_iter, &dest_kw_iter);
622 if (src_valid && !bar_pane_keywords_dnd_can_move(keyword_tree, &src_kw_iter, &dest_kw_iter))
624 /* the keyword can't be moved if the same name already exist */
627 if (new_keywords && !bar_pane_keywords_dnd_skip_existing(keyword_tree, &dest_kw_iter, &new_keywords))
629 /* the keywords can't be added if the same name already exist */
635 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
636 case GTK_TREE_VIEW_DROP_BEFORE:
637 gtk_tree_store_insert_before(GTK_TREE_STORE(keyword_tree), &new_kw_iter, NULL, &dest_kw_iter);
639 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
640 case GTK_TREE_VIEW_DROP_AFTER:
641 gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &new_kw_iter, NULL, &dest_kw_iter);
649 if (src_valid && !bar_pane_keywords_dnd_can_move(keyword_tree, &src_kw_iter, NULL))
651 /* the keyword can't be moved if the same name already exist */
654 if (new_keywords && !bar_pane_keywords_dnd_skip_existing(keyword_tree, NULL, &new_keywords))
656 /* the keywords can't be added if the same name already exist */
659 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &new_kw_iter, NULL);
665 keyword_move_recursive(GTK_TREE_STORE(keyword_tree), &new_kw_iter, &src_kw_iter);
671 gchar *keyword = work->data;
672 keyword_set(GTK_TREE_STORE(keyword_tree), &new_kw_iter, keyword, TRUE);
678 gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &add, NULL, &new_kw_iter);
682 string_list_free(new_keywords);
683 bar_keyword_tree_sync(pkd);
686 static gint bar_pane_keywords_dnd_motion(GtkWidget *tree_view, GdkDragContext *context,
687 gint x, gint y, guint time, gpointer data)
689 GtkTreePath *tpath = NULL;
690 GtkTreeViewDropPosition pos;
691 gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y, &tpath, &pos);
695 GtkTreeIter dest_iter;
696 model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
697 gtk_tree_model_get_iter(model, &dest_iter, tpath);
698 if (pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE && gtk_tree_model_iter_has_child(model, &dest_iter))
699 pos = GTK_TREE_VIEW_DROP_BEFORE;
701 if (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER && gtk_tree_model_iter_has_child(model, &dest_iter))
702 pos = GTK_TREE_VIEW_DROP_AFTER;
705 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), tpath, pos);
706 gtk_tree_path_free(tpath);
708 if (tree_view == gtk_drag_get_source_widget(context))
709 gdk_drag_status(context, GDK_ACTION_MOVE, time);
711 gdk_drag_status(context, GDK_ACTION_COPY, time);
717 *-------------------------------------------------------------------
719 *-------------------------------------------------------------------
722 static void bar_pane_keywords_edit_destroy_cb(GtkWidget *widget, gpointer data)
724 ConfDialogData *cdd = data;
725 gtk_tree_path_free(cdd->click_tpath);
730 static void bar_pane_keywords_edit_cancel_cb(GenericDialog *gd, gpointer data)
735 static void bar_pane_keywords_edit_ok_cb(GenericDialog *gd, gpointer data)
737 ConfDialogData *cdd = data;
738 PaneKeywordsData *pkd = cdd->pkd;
741 GtkTreeModel *keyword_tree;
744 gboolean have_dest = FALSE;
748 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
749 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
751 if (cdd->click_tpath)
754 if (gtk_tree_model_get_iter(model, &iter, cdd->click_tpath))
756 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
761 if (cdd->edit_existing && !have_dest) return;
763 keywords = keyword_list_pull(cdd->edit_widget);
765 if (cdd->edit_existing)
767 if (keywords && keywords->data && /* there should be one keyword */
768 !keyword_exists(keyword_tree, NULL, &kw_iter, keywords->data, TRUE))
770 keyword_set(GTK_TREE_STORE(keyword_tree), &kw_iter, keywords->data, cdd->is_keyword);
775 GList *work = keywords;
780 if (keyword_exists(keyword_tree, NULL, have_dest ? &kw_iter : NULL, work->data, FALSE))
787 gtk_tree_store_insert_after(GTK_TREE_STORE(keyword_tree), &add, NULL, &kw_iter);
791 gtk_tree_store_append(GTK_TREE_STORE(keyword_tree), &add, NULL);
795 keyword_set(GTK_TREE_STORE(keyword_tree), &kw_iter, work->data, cdd->is_keyword);
799 string_list_free(keywords);
802 static void bar_pane_keywords_conf_set_helper(GtkWidget *widget, gpointer data)
804 ConfDialogData *cdd = data;
805 cdd->is_keyword = FALSE;
808 static void bar_pane_keywords_conf_set_kw(GtkWidget *widget, gpointer data)
810 ConfDialogData *cdd = data;
811 cdd->is_keyword = TRUE;
816 static void bar_pane_keywords_edit_dialog(PaneKeywordsData *pkd, gboolean edit_existing)
825 gboolean is_keyword = TRUE;
828 if (edit_existing && pkd->click_tpath)
832 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
834 if (gtk_tree_model_get_iter(model, &iter, pkd->click_tpath))
836 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
837 FILTER_KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
845 if (edit_existing && !name) return;
848 cdd = g_new0(ConfDialogData, 1);
850 cdd->click_tpath = pkd->click_tpath;
851 pkd->click_tpath = NULL;
852 cdd->is_keyword = is_keyword;
853 cdd->edit_existing = edit_existing;
855 cdd->gd = gd = generic_dialog_new(name ? _("Edit keyword") : _("Add keywords"), "keyword_edit",
857 bar_pane_keywords_edit_cancel_cb, cdd);
858 g_signal_connect(G_OBJECT(gd->dialog), "destroy",
859 G_CALLBACK(bar_pane_keywords_edit_destroy_cb), cdd);
862 generic_dialog_add_message(gd, NULL, name ? _("Configure keyword") : _("Add keyword"), NULL);
864 generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
865 bar_pane_keywords_edit_ok_cb, TRUE);
867 table = pref_table_new(gd->vbox, 3, 1, FALSE, TRUE);
868 pref_table_label(table, 0, 0, _("Keyword:"), 1.0);
869 cdd->edit_widget = gtk_entry_new();
870 gtk_widget_set_size_request(cdd->edit_widget, 300, -1);
871 if (name) gtk_entry_set_text(GTK_ENTRY(cdd->edit_widget), name);
872 gtk_table_attach_defaults(GTK_TABLE(table), cdd->edit_widget, 1, 2, 0, 1);
873 /* here could eventually be a text view instead of entry */
874 generic_dialog_attach_default(gd, cdd->edit_widget);
875 gtk_widget_show(cdd->edit_widget);
877 group = pref_group_new(gd->vbox, FALSE, _("Keyword type:"), GTK_ORIENTATION_VERTICAL);
879 button = pref_radiobutton_new(group, NULL, _("Active keyword"),
881 G_CALLBACK(bar_pane_keywords_conf_set_kw), cdd);
882 button = pref_radiobutton_new(group, button, _("Helper"),
884 G_CALLBACK(bar_pane_keywords_conf_set_helper), cdd);
887 gtk_widget_show(gd->dialog);
894 *-------------------------------------------------------------------
896 *-------------------------------------------------------------------
899 static void bar_pane_keywords_edit_dialog_cb(GtkWidget *menu_widget, gpointer data)
901 PaneKeywordsData *pkd = data;
902 bar_pane_keywords_edit_dialog(pkd, TRUE);
905 static void bar_pane_keywords_add_dialog_cb(GtkWidget *menu_widget, gpointer data)
907 PaneKeywordsData *pkd = data;
908 bar_pane_keywords_edit_dialog(pkd, FALSE);
911 static void bar_pane_keywords_connect_mark_cb(GtkWidget *menu_widget, gpointer data)
913 PaneKeywordsData *pkd = data;
918 GtkTreeModel *keyword_tree;
921 gint mark = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_widget), "mark")) - 1;
923 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
924 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
926 if (!pkd->click_tpath) return;
927 if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
929 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
931 file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
933 meta_data_connect_mark_with_keyword(keyword_tree, &kw_iter, mark);
935 file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
936 // bar_pane_keywords_update(pkd);
940 static void bar_pane_keywords_delete_cb(GtkWidget *menu_widget, gpointer data)
942 PaneKeywordsData *pkd = data;
946 GtkTreeModel *keyword_tree;
949 if (!pkd->click_tpath) return;
951 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
952 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
954 if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
955 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
957 keyword_delete(GTK_TREE_STORE(keyword_tree), &kw_iter);
960 static void bar_pane_keywords_hide_cb(GtkWidget *menu_widget, gpointer data)
962 PaneKeywordsData *pkd = data;
966 GtkTreeModel *keyword_tree;
969 if (!pkd->click_tpath) return;
971 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
972 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
974 if (!gtk_tree_model_get_iter(model, &iter, pkd->click_tpath)) return;
975 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model), &kw_iter, &iter);
977 keyword_hide_in(GTK_TREE_STORE(keyword_tree), &kw_iter, model);
980 static void bar_pane_keywords_show_all_cb(GtkWidget *menu_widget, gpointer data)
982 PaneKeywordsData *pkd = data;
985 GtkTreeModel *keyword_tree;
987 pkd->hide_unchecked = FALSE;
989 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
990 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
992 keyword_show_all_in(GTK_TREE_STORE(keyword_tree), model);
994 if (!pkd->collapse_unchecked) gtk_tree_view_expand_all(GTK_TREE_VIEW(pkd->keyword_treeview));
995 bar_keyword_tree_sync(pkd);
998 static void bar_pane_keywords_expand_checked_cb(GtkWidget *menu_widget, gpointer data)
1000 PaneKeywordsData *pkd = data;
1001 GtkTreeModel *model;
1003 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1004 gtk_tree_model_foreach(model, bar_keyword_tree_expand_if_set_cb, pkd);
1007 static void bar_pane_keywords_collapse_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1009 PaneKeywordsData *pkd = data;
1010 GtkTreeModel *model;
1012 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1013 gtk_tree_model_foreach(model, bar_keyword_tree_collapse_if_unset_cb, pkd);
1016 static void bar_pane_keywords_hide_unchecked_cb(GtkWidget *menu_widget, gpointer data)
1018 PaneKeywordsData *pkd = data;
1019 GtkTreeModel *model;
1021 GtkTreeModel *keyword_tree;
1024 model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1025 keyword_tree = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1027 keywords = keyword_list_pull(pkd->keyword_view);
1028 keyword_hide_unset_in(GTK_TREE_STORE(keyword_tree), model, keywords);
1029 string_list_free(keywords);
1030 bar_keyword_tree_sync(pkd);
1033 static void bar_pane_keywords_expand_checked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1035 PaneKeywordsData *pkd = data;
1036 pkd->expand_checked = !pkd->expand_checked;
1037 bar_keyword_tree_sync(pkd);
1040 static void bar_pane_keywords_collapse_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1042 PaneKeywordsData *pkd = data;
1043 pkd->collapse_unchecked = !pkd->collapse_unchecked;
1044 bar_keyword_tree_sync(pkd);
1047 static void bar_pane_keywords_hide_unchecked_toggle_cb(GtkWidget *menu_widget, gpointer data)
1049 PaneKeywordsData *pkd = data;
1050 pkd->hide_unchecked = !pkd->hide_unchecked;
1051 bar_keyword_tree_sync(pkd);
1054 static void bar_pane_keywords_menu_popup(GtkWidget *widget, PaneKeywordsData *pkd, gint x, gint y)
1059 GtkTreeViewDropPosition pos;
1061 if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1062 pkd->click_tpath = NULL;
1063 gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(pkd->keyword_treeview), x, y, &pkd->click_tpath, &pos);
1065 menu = popup_menu_short_lived();
1067 if (pkd->click_tpath)
1074 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pkd->keyword_treeview));
1077 gtk_tree_model_get_iter(model, &iter, pkd->click_tpath);
1080 gtk_tree_model_get(model, &iter, FILTER_KEYWORD_COLUMN_NAME, &name,
1081 FILTER_KEYWORD_COLUMN_MARK, &mark, -1);
1083 text = g_strdup_printf(_("Hide \"%s\""), name);
1084 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_hide_cb), pkd);
1087 submenu = gtk_menu_new();
1088 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1090 text = g_strdup_printf(_("Mark %d"), i + 1);
1091 item = menu_item_add(submenu, text, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1092 g_object_set_data(G_OBJECT(item), "mark", GINT_TO_POINTER(i + 1));
1095 text = g_strdup_printf(_("Connect \"%s\" to mark"), name);
1096 item = menu_item_add(menu, text, NULL, NULL);
1097 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1100 menu_item_add_divider(menu);
1102 text = g_strdup_printf(_("Edit \"%s\""), name);
1103 menu_item_add_stock(menu, text, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_edit_dialog_cb), pkd);
1105 text = g_strdup_printf(_("Delete \"%s\""), name);
1106 menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_delete_cb), pkd);
1110 if (mark && mark[0])
1112 text = g_strdup_printf(_("Disconnect \"%s\" from mark %s"), name, mark);
1113 menu_item_add_stock(menu, text, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_keywords_connect_mark_cb), pkd);
1117 menu_item_add_divider(menu);
1124 menu_item_add(menu, _("Expand checked"), G_CALLBACK(bar_pane_keywords_expand_checked_cb), pkd);
1125 menu_item_add(menu, _("Collapse unchecked"), G_CALLBACK(bar_pane_keywords_collapse_unchecked_cb), pkd);
1126 menu_item_add(menu, _("Hide unchecked"), G_CALLBACK(bar_pane_keywords_hide_unchecked_cb), pkd);
1127 menu_item_add(menu, _("Show all"), G_CALLBACK(bar_pane_keywords_show_all_cb), pkd);
1129 submenu = gtk_menu_new();
1130 item = menu_item_add(menu, _("On any change"), NULL, NULL);
1131 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1133 menu_item_add_check(submenu, _("Expand checked"), pkd->expand_checked, G_CALLBACK(bar_pane_keywords_expand_checked_toggle_cb), pkd);
1134 menu_item_add_check(submenu, _("Collapse unchecked"), pkd->collapse_unchecked, G_CALLBACK(bar_pane_keywords_collapse_unchecked_toggle_cb), pkd);
1135 menu_item_add_check(submenu, _("Hide unchecked"), pkd->hide_unchecked, G_CALLBACK(bar_pane_keywords_hide_unchecked_toggle_cb), pkd);
1137 menu_item_add_divider(menu);
1139 menu_item_add_stock(menu, _("Add keyword"), GTK_STOCK_EDIT, G_CALLBACK(bar_pane_keywords_add_dialog_cb), pkd);
1141 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
1145 static gboolean bar_pane_keywords_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1147 PaneKeywordsData *pkd = data;
1148 if (bevent->button == MOUSE_BUTTON_RIGHT)
1150 bar_pane_keywords_menu_popup(widget, pkd, bevent->x, bevent->y);
1157 *-------------------------------------------------------------------
1159 *-------------------------------------------------------------------
1162 void bar_pane_keywords_close(GtkWidget *bar)
1164 PaneKeywordsData *pkd;
1166 pkd = g_object_get_data(G_OBJECT(bar), "pane_data");
1169 gtk_widget_destroy(pkd->widget);
1172 static void bar_pane_keywords_destroy(GtkWidget *widget, gpointer data)
1174 PaneKeywordsData *pkd = data;
1176 if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath);
1178 file_data_unregister_notify_func(bar_pane_keywords_notify_cb, pkd);
1180 file_data_unref(pkd->fd);
1187 GtkWidget *bar_pane_keywords_new(const gchar *title, const gchar *key, gboolean expanded)
1189 PaneKeywordsData *pkd;
1191 GtkWidget *scrolled;
1192 GtkTextBuffer *buffer;
1193 GtkTreeModel *store;
1194 GtkTreeViewColumn *column;
1195 GtkCellRenderer *renderer;
1197 pkd = g_new0(PaneKeywordsData, 1);
1199 pkd->pane.pane_set_fd = bar_pane_keywords_set_fd;
1200 pkd->pane.pane_event = bar_pane_keywords_event;
1201 pkd->pane.pane_write_config = bar_pane_keywords_write_config;
1202 pkd->pane.title = bar_pane_expander_title(title);
1204 pkd->pane.expanded = expanded;
1206 pkd->key = g_strdup(key);
1208 pkd->expand_checked = TRUE;
1210 hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
1213 g_object_set_data(G_OBJECT(pkd->widget), "pane_data", pkd);
1214 g_signal_connect(G_OBJECT(pkd->widget), "destroy",
1215 G_CALLBACK(bar_pane_keywords_destroy), pkd);
1216 gtk_widget_show(hbox);
1218 scrolled = gtk_scrolled_window_new(NULL, NULL);
1219 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1220 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1221 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1222 gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1223 gtk_widget_show(scrolled);
1225 pkd->keyword_view = gtk_text_view_new();
1226 gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_view);
1227 g_signal_connect(G_OBJECT(pkd->keyword_view), "populate-popup",
1228 G_CALLBACK(bar_pane_keywords_populate_popup_cb), pkd);
1229 gtk_widget_show(pkd->keyword_view);
1231 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view));
1232 g_signal_connect(G_OBJECT(buffer), "changed",
1233 G_CALLBACK(bar_pane_keywords_changed), pkd);
1235 scrolled = gtk_scrolled_window_new(NULL, NULL);
1236 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1237 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1238 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1239 gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1240 gtk_widget_show(scrolled);
1243 if (!keyword_tree) keyword_tree_new_default();
1245 store = gtk_tree_model_filter_new(GTK_TREE_MODEL(keyword_tree), NULL);
1247 gtk_tree_model_filter_set_modify_func(GTK_TREE_MODEL_FILTER(store),
1248 FILTER_KEYWORD_COLUMN_COUNT,
1249 filter_keyword_column_types,
1250 bar_pane_keywords_filter_modify,
1253 gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(store),
1254 bar_pane_keywords_filter_visible,
1258 pkd->keyword_treeview = gtk_tree_view_new_with_model(store);
1259 g_object_unref(store);
1261 gtk_widget_set_size_request(pkd->keyword_treeview, -1, 400);
1263 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pkd->keyword_treeview), FALSE);
1265 // gtk_tree_view_set_search_column(GTK_TREE_VIEW(pkd->keyword_treeview), FILTER_KEYWORD_COLUMN_);
1267 column = gtk_tree_view_column_new();
1268 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1270 renderer = gtk_cell_renderer_text_new();
1271 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1273 gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_MARK);
1275 gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1277 column = gtk_tree_view_column_new();
1278 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1279 renderer = gtk_cell_renderer_toggle_new();
1280 gtk_tree_view_column_pack_start(column, renderer, FALSE);
1281 gtk_tree_view_column_add_attribute(column, renderer, "active", FILTER_KEYWORD_COLUMN_TOGGLE);
1282 gtk_tree_view_column_add_attribute(column, renderer, "visible", FILTER_KEYWORD_COLUMN_IS_KEYWORD);
1283 g_signal_connect(G_OBJECT(renderer), "toggled",
1284 G_CALLBACK(bar_pane_keywords_keyword_toggle), pkd);
1286 renderer = gtk_cell_renderer_text_new();
1287 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1288 gtk_tree_view_column_add_attribute(column, renderer, "text", FILTER_KEYWORD_COLUMN_NAME);
1290 gtk_tree_view_append_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1291 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(pkd->keyword_treeview), column);
1293 gtk_drag_source_set(pkd->keyword_treeview,
1294 GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
1295 bar_pane_keywords_drag_types, n_keywords_drag_types,
1296 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1298 g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_get",
1299 G_CALLBACK(bar_pane_keywords_dnd_get), pkd);
1301 g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_begin",
1302 G_CALLBACK(bar_pane_keywords_dnd_begin), pkd);
1303 g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_end",
1304 G_CALLBACK(bar_pane_keywords_dnd_end), pkd);
1306 gtk_drag_dest_set(pkd->keyword_treeview,
1307 GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
1308 bar_pane_keywords_drop_types, n_keywords_drop_types,
1309 GDK_ACTION_COPY | GDK_ACTION_MOVE);
1311 g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_data_received",
1312 G_CALLBACK(bar_pane_keywords_dnd_receive), pkd);
1314 g_signal_connect(G_OBJECT(pkd->keyword_treeview), "drag_motion",
1315 G_CALLBACK(bar_pane_keywords_dnd_motion), pkd);
1317 g_signal_connect(G_OBJECT(pkd->keyword_treeview), "button_press_event",
1318 G_CALLBACK(bar_pane_keywords_menu_cb), pkd);
1320 gtk_container_add(GTK_CONTAINER(scrolled), pkd->keyword_treeview);
1321 gtk_widget_show(pkd->keyword_treeview);
1323 file_data_register_notify_func(bar_pane_keywords_notify_cb, pkd, NOTIFY_PRIORITY_LOW);
1328 GtkWidget *bar_pane_keywords_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
1330 gchar *title = g_strdup(_("NoName"));
1331 gchar *key = g_strdup(COMMENT_KEY);
1332 gboolean expanded = TRUE;
1334 while (*attribute_names)
1336 const gchar *option = *attribute_names++;
1337 const gchar *value = *attribute_values++;
1339 if (READ_CHAR_FULL("pane.title", title)) continue;
1340 if (READ_CHAR_FULL("key", key)) continue;
1341 if (READ_BOOL_FULL("pane.expanded", expanded)) continue;
1344 DEBUG_1("unknown attribute %s = %s", option, value);
1347 return bar_pane_keywords_new(title, key, expanded);
1350 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */