changeset 2824:c9eded8ad4e0

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
author Colin Clark <colin.clark@cclark.uk>
date Sun, 26 Aug 2018 13:32:42 +0100
parents 938790283e32
children b29ce668abca
files doc/docbook/GuideOptionsKeywords.xml doc/docbook/GuideOptionsMain.xml doc/docbook/GuideSidebarsInfo.xml src/bar_keywords.c src/bar_keywords.h src/layout_util.c src/preferences.c web/help/GuideIndex.html web/help/GuideOptionsBehavior.html web/help/GuideOptionsColor.html web/help/GuideOptionsFiltering.html web/help/GuideOptionsGeneral.html web/help/GuideOptionsImage.html web/help/GuideOptionsKeyboard.html web/help/GuideOptionsMain.html web/help/GuideOptionsMetadata.html web/help/GuideOptionsStereo.html web/help/GuideOptionsToolbar.html web/help/GuideOptionsWindow.html web/help/GuideSidebarsInfo.html
diffstat 20 files changed, 778 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/docbook/GuideOptionsKeywords.xml	Sun Aug 26 13:32:42 2018 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<section id="GuideOptionsKeywords">
+  <title id="titleGuideOptionsKeywords">Keywords</title>
+  <para>This section describes the keywords list used for autocompletion.</para>
+  <para />
+  <section id="KeywordSearch">
+    <title>Keyword Search</title>
+    <para />
+    <para>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.</para>
+  </section>
+  <section id="KeywordList">
+    <title>Keyword List</title>
+    <para />
+    <para>
+      The list shows all keywords currently used for autocompletion. Text may be copy-pasted into the list or deleted from the list.
+      <para />
+      When the list is saved, duplicates will autmatically be removed and the list sorted into alphabetical order.
+    </para>
+  </section>
+</section>
--- a/doc/docbook/GuideOptionsMain.xml	Tue Aug 21 17:02:20 2018 +0100
+++ b/doc/docbook/GuideOptionsMain.xml	Sun Aug 26 13:32:42 2018 +0100
@@ -27,6 +27,7 @@
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsKeyboard.xml" />
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsFiltering.xml" />
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsMetadata.xml" />
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsKeywords.xml" />
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsColor.xml" />
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsStereo.xml" />
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsBehavior.xml" />
--- a/doc/docbook/GuideSidebarsInfo.xml	Tue Aug 21 17:02:20 2018 +0100
+++ b/doc/docbook/GuideSidebarsInfo.xml	Sun Aug 26 13:32:42 2018 +0100
@@ -165,6 +165,25 @@
     </note>
     <para />
   </section>
+  <section id="KeywordAutocompletion">
+    <title id="titleKeywordAutocompletion">Keyword Autocompletion</title>
+    <para>
+      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.
+      <para />
+      Frequently used sets of keywords can be entered as comma-delimited lists.
+      <para />
+      The list of keywords used for autocompletion can be edited on the
+      <link linkend="GuideOptionsKeywords">Keywords</link>
+      tab of the Preferences dialog.
+      <para />
+      The menu action "Keyword autocomplete", set to
+      <code>Alt + K</code>
+      by default, will shift the keyboard focus to the autocomplete box. Pressing
+      <code>Alt + K</code>
+      a second time will shift the keyboard focus back to the previous object.
+    </para>
+    <para />
+  </section>
   <section id="Listpanes-ExifFileinfoCopyrightLocationandGPS">
     <title id="titleExif">List panes</title>
     <para>
--- a/src/bar_keywords.c	Tue Aug 21 17:02:20 2018 +0100
+++ b/src/bar_keywords.c	Sun Aug 26 13:32:42 2018 +0100
@@ -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 @@
 	gint height;
 
 	GList *expanded_rows;
+
+	GtkWidget *autocomplete;
 };
 
 typedef struct _ConfDialogData ConfDialogData;
@@ -336,6 +344,10 @@
 
 	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 @@
 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 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 @@
 	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 @@
 	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 @@
 		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: */
--- a/src/bar_keywords.h	Tue Aug 21 17:02:20 2018 +0100
+++ b/src/bar_keywords.h	Sun Aug 26 13:32:42 2018 +0100
@@ -28,5 +28,8 @@
 /* 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: */
--- a/src/layout_util.c	Tue Aug 21 17:02:20 2018 +0100
+++ b/src/layout_util.c	Sun Aug 26 13:32:42 2018 +0100
@@ -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 @@
 	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 @@
   { "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"),			"<control>S",		N_("Save metadata"),			CB(layout_menu_metadata_write_cb) },
+  { "KeywordAutocomplete",	NULL,	N_("Keyword autocomplete"),		"<alt>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 @@
 "        <separator/>"
 "      </menu>"
 "      <menuitem action='SaveMetadata'/>"
+"      <menuitem action='KeywordAutocomplete'/>"
 "      <placeholder name='PropertiesSection'/>"
 "      <separator/>"
 "      <menuitem action='DrawRectangle'/>"
--- a/src/preferences.c	Tue Aug 21 17:02:20 2018 +0100
+++ b/src/preferences.c	Sun Aug 26 13:32:42 2018 +0100
@@ -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 @@
 		}
 #endif
 
+	config_tab_keywords_save();
+
 	image_options_sync();
 
 	if (refresh)
@@ -2391,6 +2399,349 @@
 	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 @@
 	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);
--- a/web/help/GuideIndex.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideIndex.html	Sun Aug 26 13:32:42 2018 +0100
@@ -592,16 +592,19 @@
 <span class="label">11.6. </span><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a>
 </li>
 <li>
-<span class="label">11.7. </span><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a>
+<span class="label">11.7. </span><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a>
+</li>
+<li>
+<span class="label">11.8. </span><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a>
 </li>
 <li>
-<span class="label">11.8. </span><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a>
+<span class="label">11.9. </span><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a>
 </li>
 <li>
-<span class="label">11.9. </span><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a>
+<span class="label">11.10. </span><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a>
 </li>
 <li>
-<span class="label">11.10. </span><a class="xref" href="GuideOptionsToolbar.html" title="Toolbar">Toolbar</a>
+<span class="label">11.11. </span><a class="xref" href="GuideOptionsToolbar.html" title="Toolbar">Toolbar</a>
 </li>
 </ul></div>
 </li>
--- a/web/help/GuideOptionsBehavior.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsBehavior.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li>Behavior Options</li>
@@ -459,20 +460,20 @@
 <p class="para block block-first">This section describes the options presented under the Behavior Tab of the preferences dialog.</p>
 <div class="autotoc"><ul>
 <li>
-<span class="label">11.9.1. </span><a class="xref" href="GuideOptionsBehavior.html#Delete" title="Delete">Delete</a>
+<span class="label">11.10.1. </span><a class="xref" href="GuideOptionsBehavior.html#Delete" title="Delete">Delete</a>
 </li>
 <li>
-<span class="label">11.9.2. </span><a class="xref" href="GuideOptionsBehavior.html#Behaviour" title="Behavior">Behavior</a>
+<span class="label">11.10.2. </span><a class="xref" href="GuideOptionsBehavior.html#Behaviour" title="Behavior">Behavior</a>
 </li>
 <li>
-<span class="label">11.9.3. </span><a class="xref" href="GuideOptionsBehavior.html#Navigation" title="Navigation">Navigation</a>
+<span class="label">11.10.3. </span><a class="xref" href="GuideOptionsBehavior.html#Navigation" title="Navigation">Navigation</a>
 </li>
 <li>
-<span class="label">11.9.4. </span><a class="xref" href="GuideOptionsBehavior.html#Debugging" title="Debugging">Debugging</a>
+<span class="label">11.10.4. </span><a class="xref" href="GuideOptionsBehavior.html#Debugging" title="Debugging">Debugging</a>
 </li>
 </ul></div>
 <div class="division section">
-<a name="Delete"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.9.1. </span>Delete</span></h2></div>
+<a name="Delete"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.10.1. </span>Delete</span></h2></div>
 <div class="block list variablelist"><dl class="variablelist">
 <dt class="term dt-first">
           <span class="guilabel">Confirm file delete</span>
@@ -550,7 +551,7 @@
 </dl></div>
 </div>
 <div class="division section">
-<a name="Behaviour"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.9.2. </span>Behavior</span></h2></div>
+<a name="Behaviour"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.10.2. </span>Behavior</span></h2></div>
 <div class="block list variablelist"><dl class="variablelist">
 <dt class="term dt-first">
           <span class="guilabel">Descend folders in tree view</span>
@@ -628,7 +629,7 @@
 </dl></div>
 </div>
 <div class="division section">
-<a name="Navigation"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.9.3. </span>Navigation</span></h2></div>
+<a name="Navigation"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.10.3. </span>Navigation</span></h2></div>
 <div class="block list variablelist"><dl class="variablelist">
 <dt class="term dt-first">
           <span class="guilabel">Progressive keyboard scrolling</span>
@@ -669,7 +670,7 @@
 </dl></div>
 </div>
 <div class="division section">
-<a name="Debugging"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.9.4. </span>Debugging</span></h2></div>
+<a name="Debugging"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.10.4. </span>Debugging</span></h2></div>
 <div class="block list variablelist"><dl class="variablelist">
 <dt class="term dt-first">
           <span class="guilabel">Debug level</span>
--- a/web/help/GuideOptionsColor.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsColor.html	Sun Aug 26 13:32:42 2018 +0100
@@ -3,7 +3,7 @@
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Color management options</title>
-<link rel="previous" href="GuideOptionsMetadata.html" title="Metadata">
+<link rel="previous" href="GuideOptionsKeywords.html" title="Keywords">
 <link rel="next" href="GuideOptionsStereo.html" title="Stereo image management">
 <link rel="top" href="GuideIndex.html" title="The Geeqie User Manual">
 <style type="text/css">
@@ -410,7 +410,7 @@
 </head>
 <body>
 <div class="navbar navbar-top"><table class="navbar"><tr>
-<td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></td>
+<td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></td>
 <td class="navbar-next"><a class="navbar-next" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></td>
 </tr></table></div>
 <div class="sidebar"><div class="sidenav"><div class="autotoc"><ul>
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li>Color management options</li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
@@ -493,7 +494,7 @@
 </dl></div>
 </div></div>
 <div class="navbar navbar-bottom"><table class="navbar"><tr>
-<td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></td>
+<td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></td>
 <td class="navbar-next"><a class="navbar-next" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></td>
 </tr></table></div>
 </body>
--- a/web/help/GuideOptionsFiltering.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsFiltering.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li>Files Options</li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideOptionsGeneral.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsGeneral.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideOptionsImage.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsImage.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideOptionsKeyboard.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsKeyboard.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li>Keyboard Options</li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideOptionsMain.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsMain.html	Sun Aug 26 13:32:42 2018 +0100
@@ -434,6 +434,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
@@ -485,16 +486,19 @@
 <span class="label">11.6. </span><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a>
 </li>
 <li>
-<span class="label">11.7. </span><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a>
+<span class="label">11.7. </span><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a>
+</li>
+<li>
+<span class="label">11.8. </span><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a>
 </li>
 <li>
-<span class="label">11.8. </span><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a>
+<span class="label">11.9. </span><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a>
 </li>
 <li>
-<span class="label">11.9. </span><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a>
+<span class="label">11.10. </span><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a>
 </li>
 <li>
-<span class="label">11.10. </span><a class="xref" href="GuideOptionsToolbar.html" title="Toolbar">Toolbar</a>
+<span class="label">11.11. </span><a class="xref" href="GuideOptionsToolbar.html" title="Toolbar">Toolbar</a>
 </li>
 </ul></div>
 </div></div>
--- a/web/help/GuideOptionsMetadata.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsMetadata.html	Sun Aug 26 13:32:42 2018 +0100
@@ -4,7 +4,7 @@
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Metadata</title>
 <link rel="previous" href="GuideOptionsFiltering.html" title="Files Options">
-<link rel="next" href="GuideOptionsColor.html" title="Color management options">
+<link rel="next" href="GuideOptionsKeywords.html" title="Keywords">
 <link rel="top" href="GuideIndex.html" title="The Geeqie User Manual">
 <style type="text/css">
 
@@ -411,7 +411,7 @@
 <body>
 <div class="navbar navbar-top"><table class="navbar"><tr>
 <td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></td>
-<td class="navbar-next"><a class="navbar-next" href="GuideOptionsColor.html" title="Color management options">Color management options</a></td>
+<td class="navbar-next"><a class="navbar-next" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></td>
 </tr></table></div>
 <div class="sidebar"><div class="sidenav"><div class="autotoc"><ul>
 <li><a href="GuideIndex-info.html" title="About This Document">About This Document</a></li>
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li>Metadata</li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
@@ -657,7 +658,7 @@
 </div></div>
 <div class="navbar navbar-bottom"><table class="navbar"><tr>
 <td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></td>
-<td class="navbar-next"><a class="navbar-next" href="GuideOptionsColor.html" title="Color management options">Color management options</a></td>
+<td class="navbar-next"><a class="navbar-next" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></td>
 </tr></table></div>
 </body>
 </html>
--- a/web/help/GuideOptionsStereo.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsStereo.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li>Stereo image management</li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideOptionsToolbar.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsToolbar.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideOptionsWindow.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideOptionsWindow.html	Sun Aug 26 13:32:42 2018 +0100
@@ -435,6 +435,7 @@
 <li><a class="xref" href="GuideOptionsKeyboard.html" title="Keyboard Options">Keyboard Options</a></li>
 <li><a class="xref" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></li>
 <li><a class="xref" href="GuideOptionsMetadata.html" title="Metadata">Metadata</a></li>
+<li><a class="xref" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a></li>
 <li><a class="xref" href="GuideOptionsColor.html" title="Color management options">Color management options</a></li>
 <li><a class="xref" href="GuideOptionsStereo.html" title="Stereo image management">Stereo image management</a></li>
 <li><a class="xref" href="GuideOptionsBehavior.html" title="Behavior Options">Behavior Options</a></li>
--- a/web/help/GuideSidebarsInfo.html	Tue Aug 21 17:02:20 2018 +0100
+++ b/web/help/GuideSidebarsInfo.html	Sun Aug 26 13:32:42 2018 +0100
@@ -472,10 +472,13 @@
 <span class="label">3.1.4. </span><a class="xref" href="GuideSidebarsInfo.html#Keywords" title="Keywords">Keywords</a>
 </li>
 <li>
-<span class="label">3.1.5. </span><a class="xref" href="GuideSidebarsInfo.html#Listpanes-ExifFileinfoCopyrightLocationandGPS" title="List panes">List panes</a>
+<span class="label">3.1.5. </span><a class="xref" href="GuideSidebarsInfo.html#KeywordAutocompletion" title="Keyword Autocompletion">Keyword Autocompletion</a>
 </li>
 <li>
-<span class="label">3.1.6. </span><a class="xref" href="GuideSidebarsInfo.html#GPSMap" title="Map Display">Map Display</a>
+<span class="label">3.1.6. </span><a class="xref" href="GuideSidebarsInfo.html#Listpanes-ExifFileinfoCopyrightLocationandGPS" title="List panes">List panes</a>
+</li>
+<li>
+<span class="label">3.1.7. </span><a class="xref" href="GuideSidebarsInfo.html#GPSMap" title="Map Display">Map Display</a>
 </li>
 </ul></div>
 <div class="division section">
@@ -624,7 +627,26 @@
 <p class="para block"></p>
 </div>
 <div class="division section">
-<a name="Listpanes-ExifFileinfoCopyrightLocationandGPS"></a><div class="header"><h2 class="section title"><span class="title"><a name="titleExif"></a><span class="label">3.1.5. </span>List panes</span></h2></div>
+<a name="KeywordAutocompletion"></a><div class="header"><h2 class="section title"><span class="title"><a name="titleKeywordAutocompletion"></a><span class="label">3.1.5. </span>Keyword Autocompletion</span></h2></div>
+<p class="para block block-first">
+      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.
+      <p class="para block block-first"></p>
+      Frequently used sets of keywords can be entered as comma-delimited lists.
+      <p class="para block"></p>
+      The list of keywords used for autocompletion can be edited on the
+      <a class="link" href="GuideOptionsKeywords.html" title="Keywords">Keywords</a>
+      tab of the Preferences dialog.
+      <p class="para block"></p>
+      The menu action "Keyword autocomplete", set to
+      <span class="code" dir="ltr">Alt + K</span>
+      by default, will shift the keyboard focus to the autocomplete box. Pressing
+      <span class="code" dir="ltr">Alt + K</span>
+      a second time will shift the keyboard focus back to the previous object.
+    </p>
+<p class="para block"></p>
+</div>
+<div class="division section">
+<a name="Listpanes-ExifFileinfoCopyrightLocationandGPS"></a><div class="header"><h2 class="section title"><span class="title"><a name="titleExif"></a><span class="label">3.1.6. </span>List panes</span></h2></div>
 <p class="para block block-first">
       There are five list panes, whose contents can be configured to display Exif, XMP or IPTC metadata tags. The five panes are:
       <div class="block list itemizedlist"><ul class="itemizedlist">
@@ -821,7 +843,7 @@
 <p class="para block"></p>
 </div>
 <div class="division section">
-<a name="GPSMap"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">3.1.6. </span>Map Display</span></h2></div>
+<a name="GPSMap"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">3.1.7. </span>Map Display</span></h2></div>
 <p class="para block block-first">
       Geeqie can display maps created by the
       <a class="ulink" href="https://www.openstreetmap.org" title="https://www.openstreetmap.org">openstreetmap.org</a>