From: Colin Clark Date: Sun, 26 Aug 2018 12:32:42 +0000 (+0100) Subject: Fix #305: Faster Tagging (Keywords) X-Git-Tag: v1.5~80 X-Git-Url: http://geeqie.org/cgi-bin/gitweb.cgi?p=geeqie.git;a=commitdiff_plain;h=bd30e33fcf9412f21a566b288e333252739031c5 Fix #305: Faster Tagging (Keywords) https://github.com/BestImageViewer/geeqie/issues/305 Keyword autocompletion - see the Info Sidebar section in the Help file for details --- diff --git a/doc/docbook/GuideOptionsKeywords.xml b/doc/docbook/GuideOptionsKeywords.xml new file mode 100644 index 00000000..6b919f3a --- /dev/null +++ b/doc/docbook/GuideOptionsKeywords.xml @@ -0,0 +1,20 @@ + +
+ Keywords + This section describes the keywords list used for autocompletion. + +
+ Keyword Search + + Pressing the Search button will open a dialog which permits a recursive search to be made for keywords already attached to images. The result of the search is automatically appended to the existing list. +
+
+ Keyword List + + + The list shows all keywords currently used for autocompletion. Text may be copy-pasted into the list or deleted from the list. + + When the list is saved, duplicates will autmatically be removed and the list sorted into alphabetical order. + +
+
diff --git a/doc/docbook/GuideOptionsMain.xml b/doc/docbook/GuideOptionsMain.xml index 6ed8850f..16ac3610 100644 --- a/doc/docbook/GuideOptionsMain.xml +++ b/doc/docbook/GuideOptionsMain.xml @@ -27,6 +27,7 @@ + diff --git a/doc/docbook/GuideSidebarsInfo.xml b/doc/docbook/GuideSidebarsInfo.xml index 79d59ba3..498d1550 100644 --- a/doc/docbook/GuideSidebarsInfo.xml +++ b/doc/docbook/GuideSidebarsInfo.xml @@ -165,6 +165,25 @@ +
+ Keyword Autocompletion + + The text box at the bottom of the keywords pane is used for autocompletion. Any keywords typed into the standard keyword box or the autocompletion box will be remembered as candidates for future autocompletion. + + Frequently used sets of keywords can be entered as comma-delimited lists. + + The list of keywords used for autocompletion can be edited on the + Keywords + tab of the Preferences dialog. + + The menu action "Keyword autocomplete", set to + Alt + K + by default, will shift the keyboard focus to the autocomplete box. Pressing + Alt + K + a second time will shift the keyboard focus back to the previous object. + + +
List panes diff --git a/src/bar_keywords.c b/src/bar_keywords.c index 3509bc5a..601481a5 100644 --- a/src/bar_keywords.c +++ b/src/bar_keywords.c @@ -37,11 +37,17 @@ #include "rcfile.h" #include "layout.h" #include "dnd.h" +#include "secure_save.h" //static void bar_pane_keywords_keyword_update_all(void); static void bar_pane_keywords_changed(GtkTextBuffer *buffer, gpointer data); +static void autocomplete_keywords_list_load(const gchar *path); +static GtkListStore *keyword_store = NULL; +static gboolean autocomplete_keywords_list_save(gchar *path); +static gboolean autocomplete_activate_cb(GtkWidget *widget, gpointer data); + /* *------------------------------------------------------------------- * keyword / comment utils @@ -138,6 +144,8 @@ struct _PaneKeywordsData gint height; GList *expanded_rows; + + GtkWidget *autocomplete; }; typedef struct _ConfDialogData ConfDialogData; @@ -336,6 +344,10 @@ gint bar_pane_keywords_event(GtkWidget *bar, GdkEvent *event) if (gtk_widget_has_focus(pkd->keyword_view)) return gtk_widget_event(pkd->keyword_view, event); + if (gtk_widget_has_focus(pkd->autocomplete)) + { + return gtk_widget_event(pkd->autocomplete, event); + } return FALSE; } @@ -1430,6 +1442,10 @@ void bar_pane_keywords_close(GtkWidget *bar) static void bar_pane_keywords_destroy(GtkWidget *widget, gpointer data) { PaneKeywordsData *pkd = data; + gchar *path; + + path = g_build_filename(get_rc_dir(), "keywords", NULL); + autocomplete_keywords_list_save(path); string_list_free(pkd->expanded_rows); if (pkd->click_tpath) gtk_tree_path_free(pkd->click_tpath); @@ -1446,13 +1462,15 @@ static void bar_pane_keywords_destroy(GtkWidget *widget, gpointer data) static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded, gint height) { PaneKeywordsData *pkd; - GtkWidget *hbox; + GtkWidget *hbox, *vbox; GtkWidget *scrolled; GtkTextBuffer *buffer; GtkTreeModel *store; GtkTreeViewColumn *column; GtkCellRenderer *renderer; GtkTreeIter iter; + GtkEntryCompletion *completion; + gchar *path; pkd = g_new0(PaneKeywordsData, 1); @@ -1471,9 +1489,11 @@ static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, con pkd->expand_checked = TRUE; pkd->expanded_rows = NULL; + vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP); hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); - pkd->widget = hbox; + pkd->widget = vbox; g_object_set_data(G_OBJECT(pkd->widget), "pane_data", pkd); g_signal_connect(G_OBJECT(pkd->widget), "destroy", G_CALLBACK(bar_pane_keywords_destroy), pkd); @@ -1504,6 +1524,26 @@ static GtkWidget *bar_pane_keywords_new(const gchar *id, const gchar *title, con gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0); gtk_widget_show(scrolled); + pkd->autocomplete = gtk_entry_new(); + gtk_box_pack_end(GTK_BOX(vbox), pkd->autocomplete, FALSE, FALSE, 0); + gtk_widget_show(pkd->autocomplete); + gtk_widget_show(vbox); + gtk_widget_set_tooltip_text(pkd->autocomplete, "Keyword autocomplete"); + + path = g_build_filename(get_rc_dir(), "keywords", NULL); + autocomplete_keywords_list_load(path); + + completion = gtk_entry_completion_new(); + gtk_entry_set_completion(GTK_ENTRY(pkd->autocomplete), completion); + gtk_entry_completion_set_inline_completion(completion, TRUE); + gtk_entry_completion_set_inline_selection(completion, TRUE); + g_object_unref(completion); + + gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(keyword_store)); + gtk_entry_completion_set_text_column(completion, 0); + + g_signal_connect(G_OBJECT(pkd->autocomplete), "activate", + G_CALLBACK(autocomplete_activate_cb), pkd); if (!keyword_tree || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) { @@ -1687,4 +1727,258 @@ void bar_pane_keywords_entry_add_from_config(GtkWidget *pane, const gchar **attr log_printf("unknown attribute %s = %s\n", option, value); } } + +/* + *----------------------------------------------------------------------------- + * Autocomplete keywords + *----------------------------------------------------------------------------- + */ + +static gboolean autocomplete_activate_cb(GtkWidget *widget, gpointer data) +{ + PaneKeywordsData *pkd = data; + gchar *entry_text; + GtkTextBuffer *buffer; + GtkTextIter iter; + GtkTreeIter iter_t; + gchar *kw_cr; + gchar *kw_split; + gboolean valid; + gboolean found = FALSE; + gchar *string; + + entry_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pkd->autocomplete))); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pkd->keyword_view)); + + kw_split = strtok(entry_text, ","); + while (kw_split != NULL) + { + kw_cr = g_strconcat(kw_split, "\n", NULL); + g_strchug(kw_cr); + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_insert(buffer, &iter, kw_cr, -1); + + kw_split = strtok(NULL, ","); + g_free(kw_cr); + } + + g_free(entry_text); + entry_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pkd->autocomplete))); + + gtk_entry_set_text(GTK_ENTRY(pkd->autocomplete), ""); + + valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter_t); + while (valid) + { + gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter_t, 0, &string, -1); + if (g_strcmp0(entry_text, string) == 0) + { + found = TRUE; + break; + } + valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter_t); + } + + if (!found) + { + gtk_list_store_append (keyword_store, &iter_t); + gtk_list_store_set(keyword_store, &iter_t, 0, entry_text, -1); + } + + g_free(entry_text); + return FALSE; +} + +gint autocomplete_sort_iter_compare_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer userdata) +{ + gint ret = 0; + gchar *name1, *name2; + + gtk_tree_model_get(model, a, 0, &name1, -1); + gtk_tree_model_get(model, b, 0, &name2, -1); + + if (name1 == NULL || name2 == NULL) + { + if (name1 == NULL && name2 == NULL) + { + ret = 0; + } + else + { + ret = (name1 == NULL) ? -1 : 1; + } + } + else + { + ret = g_utf8_collate(name1,name2); + } + + g_free(name1); + g_free(name2); + + return ret; +} + +static void autocomplete_keywords_list_load(const gchar *path) +{ + FILE *f; + gchar s_buf[1024]; + gchar *pathl; + gint len; + GtkTreeIter iter; + GtkTreeSortable *sortable; + + if (keyword_store) return; + keyword_store = gtk_list_store_new(1, G_TYPE_STRING); + + sortable = GTK_TREE_SORTABLE(keyword_store); + gtk_tree_sortable_set_sort_func(sortable, 0, autocomplete_sort_iter_compare_func, + GINT_TO_POINTER(0), NULL); + + gtk_tree_sortable_set_sort_column_id(sortable, 0, GTK_SORT_ASCENDING); + + pathl = path_from_utf8(path); + f = fopen(pathl, "r"); + g_free(pathl); + + if (!f) + { + log_printf("Warning: keywords file %s not loaded", pathl); + return; + } + + /* first line must start with Keywords comment */ + if (!fgets(s_buf, sizeof(s_buf), f) || + strncmp(s_buf, "#Keywords", 9) != 0) + { + fclose(f); + log_printf("Warning: keywords file %s not loaded", pathl); + return; + } + + while (fgets(s_buf, sizeof(s_buf), f)) + { + if (s_buf[0]=='#') continue; + + len = strlen(s_buf); + if( s_buf[len-1] == '\n' ) + { + s_buf[len-1] = 0; + } + gtk_list_store_append (keyword_store, &iter); + gtk_list_store_set(keyword_store, &iter, 0, g_strdup(s_buf), -1); + } + + fclose(f); +} + +static gboolean autocomplete_keywords_list_save(gchar *path) +{ + SecureSaveInfo *ssi; + gchar *pathl; + gchar *string; + gchar *string_nl; + GtkTreeIter iter; + gboolean valid; + + pathl = path_from_utf8(path); + ssi = secure_open(pathl); + g_free(pathl); + + if (!ssi) + { + log_printf(_("Error: Unable to write keywords list to: %s\n"), path); + return FALSE; + } + + secure_fprintf(ssi, "#Keywords list\n"); + + valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter); + + while (valid) + { + gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter, 0, &string, -1); + string_nl = g_strconcat(string, "\n", NULL); + secure_fprintf(ssi, "%s", string_nl); + + valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter); + + g_free(string_nl); + } + + secure_fprintf(ssi, "#end\n"); + return (secure_close(ssi) == 0); +} + +GList *keyword_list_get() +{ + GList *ret_list = NULL; + gchar *string; + gchar *string_nl; + GtkTreeIter iter; + gboolean valid; + + valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_store), &iter); + + while (valid) + { + gtk_tree_model_get (GTK_TREE_MODEL(keyword_store), &iter, 0, &string, -1); + string_nl = g_strconcat(string, "\n", NULL); + ret_list = g_list_append(ret_list, string); + valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_store), &iter); + + g_free(string_nl); + } + + return ret_list; +} + +void keyword_list_set(GList *keyword_list) +{ + GList *ret = NULL; + GtkTreeIter iter; + + if (!keyword_list) return; + + gtk_list_store_clear(keyword_store); + + while (keyword_list) + { + gtk_list_store_append (keyword_store, &iter); + gtk_list_store_set(keyword_store, &iter, 0, keyword_list->data, -1); + + keyword_list = keyword_list->next; + } +} + +gboolean bar_keywords_autocomplete_focus(LayoutWindow *lw) +{ + GtkWidget *pane; + GtkWidget *current_focus; + GList *children; + gboolean ret; + + current_focus = gtk_window_get_focus(GTK_WINDOW(lw->window)); + pane = bar_find_pane_by_id(lw->bar, PANE_KEYWORDS, "keywords"); + + children = gtk_container_get_children(GTK_CONTAINER(pane)); + children = g_list_last(children); + + if (current_focus == children->data) + { + ret = TRUE; + } + else + { + gtk_widget_grab_focus(children->data); + ret = FALSE; + } + + g_list_free(children); + + return ret; +} /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/bar_keywords.h b/src/bar_keywords.h index 1c2583ff..1fe87a0e 100644 --- a/src/bar_keywords.h +++ b/src/bar_keywords.h @@ -28,5 +28,8 @@ void bar_pane_keywords_entry_add_from_config(GtkWidget *pane, const gchar **attr /* used in search.c */ GList *keyword_list_pull(GtkWidget *text_widget); +GList *keyword_list_get(); +void keyword_list_set(GList *keyword_list); +gboolean bar_keywords_autocomplete_focus(LayoutWindow *lw); #endif /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/layout_util.c b/src/layout_util.c index a618f7aa..06edd5f2 100644 --- a/src/layout_util.c +++ b/src/layout_util.c @@ -25,6 +25,7 @@ #include "advanced_exif.h" #include "bar_sort.h" #include "bar.h" +#include "bar_keywords.h" #include "cache_maint.h" #include "collect.h" #include "collect-dlg.h" @@ -1656,6 +1657,25 @@ static void layout_menu_metadata_write_cb(GtkAction *action, gpointer data) metadata_write_queue_confirm(TRUE, NULL, NULL); } +static GtkWidget *last_focussed = NULL; +static void layout_menu_keyword_autocomplete_cb(GtkAction *action, gpointer data) +{ + LayoutWindow *lw = data; + GtkWidget *tmp; + gboolean auto_has_focus; + + tmp = gtk_window_get_focus(GTK_WINDOW(lw->window)); + auto_has_focus = bar_keywords_autocomplete_focus(lw); + + if (auto_has_focus) + { + gtk_widget_grab_focus(last_focussed); + } + else + { + last_focussed = tmp; + } +} /* *----------------------------------------------------------------------------- @@ -1888,6 +1908,7 @@ static GtkActionEntry menu_entries[] = { { "Maintenance", PIXBUF_INLINE_ICON_MAINTENANCE, N_("_Cache maintenance..."), NULL, N_("Cache maintenance..."), CB(layout_menu_remove_thumb_cb) }, { "Wallpaper", NULL, N_("Set as _wallpaper"), NULL, N_("Set as wallpaper"), CB(layout_menu_wallpaper_cb) }, { "SaveMetadata", GTK_STOCK_SAVE, N_("_Save metadata"), "S", N_("Save metadata"), CB(layout_menu_metadata_write_cb) }, + { "KeywordAutocomplete", NULL, N_("Keyword autocomplete"), "K", N_("Keyword Autocomplete"), CB(layout_menu_keyword_autocomplete_cb) }, { "ZoomIn", GTK_STOCK_ZOOM_IN, N_("Zoom _in"), "equal", N_("Zoom in"), CB(layout_menu_zoom_in_cb) }, { "ZoomInAlt1", GTK_STOCK_ZOOM_IN, N_("Zoom _in"), "KP_Add", N_("Zoom in"), CB(layout_menu_zoom_in_cb) }, { "ZoomOut", GTK_STOCK_ZOOM_OUT, N_("Zoom _out"), "minus", N_("Zoom out"), CB(layout_menu_zoom_out_cb) }, @@ -2110,6 +2131,7 @@ static const gchar *menu_ui_description = " " " " " " +" " " " " " " " diff --git a/src/preferences.c b/src/preferences.c index 88d315dd..3a784c8a 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -23,6 +23,7 @@ #include "preferences.h" #include "bar_exif.h" +#include "bar_keywords.h" #include "cache.h" #include "cache_maint.h" #include "editors.h" @@ -36,6 +37,7 @@ #include "img-view.h" #include "layout_config.h" #include "layout_util.h" +#include "metadata.h" #include "pixbuf_util.h" #include "slideshow.h" #include "toolbar.h" @@ -43,6 +45,7 @@ #include "utilops.h" #include "ui_fileops.h" #include "ui_misc.h" +#include "ui_spinner.h" #include "ui_tabcomp.h" #include "ui_utildlg.h" #include "window.h" @@ -63,6 +66,9 @@ static void image_overlay_set_text_colours(); +GtkWidget *keyword_text; +static void config_tab_keywords_save(); + typedef struct _ThumbSize ThumbSize; struct _ThumbSize { @@ -439,6 +445,8 @@ static void config_window_apply(void) } #endif + config_tab_keywords_save(); + image_options_sync(); if (refresh) @@ -2391,6 +2399,349 @@ static void config_tab_metadata(GtkWidget *notebook) gtk_widget_set_tooltip_text(ct_button,"On folder change, read DateTimeOriginal, DateTimeDigitized and Star Rating in the idle loop.\nIf this is not selected, initial loading of the folder will be faster but sorting on these items will be slower"); } +/* keywords tab */ + +typedef struct _KeywordFindData KeywordFindData; +struct _KeywordFindData +{ + GenericDialog *gd; + + GList *list; + GList *list_dir; + + GtkWidget *button_close; + GtkWidget *button_stop; + GtkWidget *button_start; + GtkWidget *progress; + GtkWidget *spinner; + + GtkWidget *group; + GtkWidget *entry; + + gboolean recurse; + + guint idle_id; /* event source id */ +}; + +#define KEYWORD_DIALOG_WIDTH 400 + +static void keywords_find_folder(KeywordFindData *kfd, FileData *dir_fd) +{ + GList *list_d = NULL; + GList *list_f = NULL; + + if (kfd->recurse) + { + filelist_read(dir_fd, &list_f, &list_d); + } + else + { + filelist_read(dir_fd, &list_f, NULL); + } + + list_f = filelist_filter(list_f, FALSE); + list_d = filelist_filter(list_d, TRUE); + + kfd->list = g_list_concat(list_f, kfd->list); + kfd->list_dir = g_list_concat(list_d, kfd->list_dir); +} + +static void keywords_find_reset(KeywordFindData *kfd) +{ + filelist_free(kfd->list); + kfd->list = NULL; + + filelist_free(kfd->list_dir); + kfd->list_dir = NULL; +} + +static void keywords_find_close_cb(GenericDialog *fd, gpointer data) +{ + KeywordFindData *kfd = data; + + if (!gtk_widget_get_sensitive(kfd->button_close)) return; + + keywords_find_reset(kfd); + generic_dialog_close(kfd->gd); + g_free(kfd); +} + +static void keywords_find_finish(KeywordFindData *kfd) +{ + keywords_find_reset(kfd); + + gtk_entry_set_text(GTK_ENTRY(kfd->progress), _("done")); + spinner_set_interval(kfd->spinner, -1); + + gtk_widget_set_sensitive(kfd->group, TRUE); + gtk_widget_set_sensitive(kfd->button_start, TRUE); + gtk_widget_set_sensitive(kfd->button_stop, FALSE); + gtk_widget_set_sensitive(kfd->button_close, TRUE); +} + +static void keywords_find_stop_cb(GenericDialog *fd, gpointer data) +{ + KeywordFindData *kfd = data; + + g_idle_remove_by_data(kfd); + + keywords_find_finish(kfd); +} + +static gboolean keywords_find_file(gpointer data) +{ + KeywordFindData *kfd = data; + GtkTextIter iter; + GtkTextBuffer *buffer; + gchar *tmp; + GList *keywords; + + if (kfd->list) + { + FileData *fd; + + fd = kfd->list->data; + kfd->list = g_list_remove(kfd->list, fd); + + keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(keyword_text)); + + while (keywords) + { + gtk_text_buffer_get_end_iter(buffer, &iter); + tmp = g_strconcat(keywords->data, "\n", NULL); + gtk_text_buffer_insert(buffer, &iter, tmp, -1); + g_free(tmp); + keywords = keywords->next; + } + + gtk_entry_set_text(GTK_ENTRY(kfd->progress), fd->path); + file_data_unref(fd); + string_list_free(keywords); + + return (TRUE); + } + else if (kfd->list_dir) + { + FileData *fd; + + fd = kfd->list_dir->data; + kfd->list_dir = g_list_remove(kfd->list_dir, fd); + + keywords_find_folder(kfd, fd); + + file_data_unref(fd); + + return TRUE; + } + + keywords_find_finish(kfd); + + return FALSE; +} + +static void keywords_find_start_cb(GenericDialog *fd, gpointer data) +{ + KeywordFindData *kfd = data; + gchar *path; + + if (kfd->list || !gtk_widget_get_sensitive(kfd->button_start)) return; + + path = remove_trailing_slash((gtk_entry_get_text(GTK_ENTRY(kfd->entry)))); + parse_out_relatives(path); + + if (!isdir(path)) + { + warning_dialog(_("Invalid folder"), + _("The specified folder can not be found."), + GTK_STOCK_DIALOG_WARNING, kfd->gd->dialog); + } + else + { + FileData *dir_fd; + + gtk_widget_set_sensitive(kfd->group, FALSE); + gtk_widget_set_sensitive(kfd->button_start, FALSE); + gtk_widget_set_sensitive(kfd->button_stop, TRUE); + gtk_widget_set_sensitive(kfd->button_close, FALSE); + spinner_set_interval(kfd->spinner, SPINNER_SPEED); + + dir_fd = file_data_new_dir(path); + keywords_find_folder(kfd, dir_fd); + file_data_unref(dir_fd); + kfd->idle_id = g_idle_add(keywords_find_file, kfd); + } + + g_free(path); +} + +static void keywords_find_dialog(GtkWidget *widget, const gchar *path) +{ + KeywordFindData *kfd; + GtkWidget *hbox; + GtkWidget *label; + + kfd = g_new0(KeywordFindData, 1); + + kfd->gd = generic_dialog_new(_("Search for keywords"), + "search_for_keywords", + widget, FALSE, + NULL, kfd); + gtk_window_set_default_size(GTK_WINDOW(kfd->gd->dialog), KEYWORD_DIALOG_WIDTH, -1); + kfd->gd->cancel_cb = keywords_find_close_cb; + kfd->button_close = generic_dialog_add_button(kfd->gd, GTK_STOCK_CLOSE, NULL, + keywords_find_close_cb, FALSE); + kfd->button_start = generic_dialog_add_button(kfd->gd, GTK_STOCK_OK, _("S_tart"), + keywords_find_start_cb, FALSE); + kfd->button_stop = generic_dialog_add_button(kfd->gd, GTK_STOCK_STOP, NULL, + keywords_find_stop_cb, FALSE); + gtk_widget_set_sensitive(kfd->button_stop, FALSE); + + generic_dialog_add_message(kfd->gd, NULL, _("Search for keywords"), NULL, FALSE); + + hbox = pref_box_new(kfd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0); + pref_spacer(hbox, PREF_PAD_INDENT); + kfd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP); + + hbox = pref_box_new(kfd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); + pref_label_new(hbox, _("Folder:")); + + label = tab_completion_new(&kfd->entry, path, NULL, NULL, NULL, NULL); + tab_completion_add_select_button(kfd->entry,_("Select folder") , TRUE); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + gtk_widget_show(label); + + pref_checkbox_new_int(kfd->group, _("Include subfolders"), FALSE, &kfd->recurse); + + pref_line(kfd->gd->vbox, PREF_PAD_SPACE); + hbox = pref_box_new(kfd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); + + kfd->progress = gtk_entry_new(); + gtk_widget_set_can_focus(kfd->progress, FALSE); + gtk_editable_set_editable(GTK_EDITABLE(kfd->progress), FALSE); + gtk_entry_set_text(GTK_ENTRY(kfd->progress), _("click start to begin")); + gtk_box_pack_start(GTK_BOX(hbox), kfd->progress, TRUE, TRUE, 0); + gtk_widget_show(kfd->progress); + + kfd->spinner = spinner_new(NULL, -1); + gtk_box_pack_start(GTK_BOX(hbox), kfd->spinner, FALSE, FALSE, 0); + gtk_widget_show(kfd->spinner); + + kfd->list = NULL; + + gtk_widget_show(kfd->gd->dialog); +} + +static void keywords_find_cb(GtkWidget *widget, gpointer data) +{ + const gchar *path = layout_get_path(NULL); + + if (!path || !*path) path = homedir(); + keywords_find_dialog(widget, path); +} + +static void config_tab_keywords_save() +{ + GtkTextIter start, end; + GtkTextBuffer *buffer; + GList *kw_list = NULL; + GList *work; + gchar *buffer_text; + gchar *kw_split; + gboolean found; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(keyword_text)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + + buffer_text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + + kw_split = strtok(buffer_text, "\n"); + while (kw_split != NULL) + { + work = kw_list; + found = FALSE; + while (work) + { + if (g_strcmp0(work->data, kw_split) == 0) + { + found = TRUE; + break; + } + work = work->next; + } + if (!found) + { + kw_list = g_list_append(kw_list, g_strdup(kw_split)); + } + kw_split = strtok(NULL, "\n"); + } + + keyword_list_set(kw_list); + + string_list_free(kw_list); + g_free(buffer_text); +} + +static void config_tab_keywords(GtkWidget *notebook) +{ + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *group; + GtkWidget *button; + GtkWidget *scrolled; + GtkTextIter iter; + GtkTextBuffer *buffer; + gchar *tmp; + + vbox = scrolled_notebook_page(notebook, _("Keywords")); + + group = pref_group_new(vbox, TRUE, _("Edit keywords autocompletion list"), GTK_ORIENTATION_VERTICAL); + + hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP); + + button = pref_button_new(hbox, GTK_STOCK_EXECUTE, _("Search"), FALSE, + G_CALLBACK(keywords_find_cb), keyword_text); + gtk_widget_set_tooltip_text(button, "Search for existing keywords"); + + + keyword_text = gtk_text_view_new(); + gtk_widget_set_size_request(keyword_text, 20, 20); + scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_box_pack_start(GTK_BOX(group), scrolled, TRUE, TRUE, 0); + gtk_widget_show(scrolled); + + gtk_container_add(GTK_CONTAINER(scrolled), keyword_text); + gtk_widget_show(keyword_text); + + gtk_text_view_set_editable(GTK_TEXT_VIEW(keyword_text), TRUE); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(keyword_text)); + gtk_text_buffer_create_tag(buffer, "monospace", + "family", "monospace", NULL); + + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(keyword_text), GTK_WRAP_WORD); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE); + gchar *path; + + path = g_build_filename(get_rc_dir(), "keywords", NULL); + + GList *kwl = keyword_list_get(); + kwl = g_list_first(kwl); + while (kwl) + { + gtk_text_buffer_get_end_iter (buffer, &iter); + tmp = g_strconcat(kwl->data, "\n", NULL); + gtk_text_buffer_insert(buffer, &iter, tmp, -1); + kwl = kwl->next; + g_free(tmp); + } + + gtk_text_buffer_set_modified(buffer, FALSE); + + g_free(path); +} + /* metadata tab */ #ifdef HAVE_LCMS static void intent_menu_cb(GtkWidget *combo, gpointer data) @@ -2878,6 +3229,7 @@ static void config_window_create(void) config_tab_accelerators(notebook); config_tab_files(notebook); config_tab_metadata(notebook); + config_tab_keywords(notebook); config_tab_color(notebook); config_tab_stereo(notebook); config_tab_behavior(notebook); diff --git a/web/help/GuideIndex.html b/web/help/GuideIndex.html index a6ba7241..2e8e0c35 100644 --- a/web/help/GuideIndex.html +++ b/web/help/GuideIndex.html @@ -592,16 +592,19 @@ dd.answer div.label { float: left; } 11.6. Metadata
  • -11.7. Color management options +11.7. Keywords
  • -11.8. Stereo image management +11.8. Color management options
  • -11.9. Behavior Options +11.9. Stereo image management
  • -11.10. Toolbar +11.10. Behavior Options +
  • +
  • +11.11. Toolbar
  • diff --git a/web/help/GuideOptionsBehavior.html b/web/help/GuideOptionsBehavior.html index 84ce4cea..4d52490d 100644 --- a/web/help/GuideOptionsBehavior.html +++ b/web/help/GuideOptionsBehavior.html @@ -435,6 +435,7 @@ dd.answer div.label { float: left; }
  • Keyboard Options
  • Files Options
  • Metadata
  • +
  • Keywords
  • Color management options
  • Stereo image management
  • Behavior Options
  • @@ -459,20 +460,20 @@ dd.answer div.label { float: left; }

    This section describes the options presented under the Behavior Tab of the preferences dialog.

    -

    11.9.1. Delete

    +

    11.10.1. Delete

    Confirm file delete @@ -550,7 +551,7 @@ dd.answer div.label { float: left; }
    -

    11.9.2. Behavior

    +

    11.10.2. Behavior

    Descend folders in tree view @@ -628,7 +629,7 @@ dd.answer div.label { float: left; }
    -

    11.9.3. Navigation

    +

    11.10.3. Navigation

    Progressive keyboard scrolling @@ -669,7 +670,7 @@ dd.answer div.label { float: left; }
    -

    11.9.4. Debugging

    +

    11.10.4. Debugging

    Debug level diff --git a/web/help/GuideOptionsColor.html b/web/help/GuideOptionsColor.html index 3c6cf8a1..c2fe1ab1 100644 --- a/web/help/GuideOptionsColor.html +++ b/web/help/GuideOptionsColor.html @@ -3,7 +3,7 @@ Color management options - +