changeset 2866:87242753ed2c

Ref #160: Replace print dialog by standard GTK dialog https://github.com/BestImageViewer/geeqie/issues/160 Permit exif tags to be included in the image text annotations (as for the Overlay screen Display)
author Colin Clark <colin.clark@cclark.uk>
date Thu, 22 Nov 2018 15:08:54 +0000
parents cb5326e72117
children 52edc8a3990c
files po/POTFILES.in src/Makefile.am src/image-overlay.c src/options.c src/options.h src/osd.c src/osd.h src/preferences.c src/print.c src/rcfile.c
diffstat 10 files changed, 670 insertions(+), 542 deletions(-) [+]
line wrap: on
line diff
--- a/po/POTFILES.in	Sun Nov 18 17:38:10 2018 +0000
+++ b/po/POTFILES.in	Thu Nov 22 15:08:54 2018 +0000
@@ -70,6 +70,7 @@
 src/metadata.c
 src/misc.c
 src/options.c
+src/osd.c
 src/pan-view/pan-calendar.c
 src/pan-view/pan-folder.c
 src/pan-view/pan-grid.c
--- a/src/Makefile.am	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/Makefile.am	Thu Nov 22 15:08:54 2018 +0000
@@ -220,6 +220,8 @@
 	misc.h		\
 	options.c	\
 	options.h	\
+	osd.c 	\
+	osd.h	\
 	pan-view.h	\
 	pixbuf-renderer.c	\
 	pixbuf-renderer.h	\
--- a/src/image-overlay.c	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/image-overlay.c	Thu Nov 22 15:08:54 2018 +0000
@@ -22,19 +22,16 @@
 #include "main.h"
 #include "image-overlay.h"
 
-#include "collect.h"
-#include "exif.h"
 #include "filedata.h"
 #include "histogram.h"
 #include "image.h"
 #include "img-view.h"
 #include "layout.h"
-#include "metadata.h"
+#include "osd.h"
 #include "pixbuf-renderer.h"
 #include "pixbuf_util.h"
 #include "ui_fileops.h"
 #include "image-load.h"
-#include "glua.h"
 
 /*
  *----------------------------------------------------------------------------
@@ -232,287 +229,6 @@
 		}
 }
 
-static gchar *keywords_to_string(FileData *fd)
-{
-	GList *keywords;
-	GString *kwstr = NULL;
-	gchar *ret = NULL;
-
-	g_assert(fd);
-
-	keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
-
-	if (keywords)
-		{
-		GList *work = keywords;
-
-		while (work)
-			{
-			gchar *kw = work->data;
-			work = work->next;
-
-			if (!kw) continue;
-			if (!kwstr)
-				kwstr = g_string_new("");
-			else
-				g_string_append(kwstr, ", ");
-
-			g_string_append(kwstr, kw);
-			}
-		string_list_free(keywords);
-		}
-
-	if (kwstr)
-		{
-		ret = kwstr->str;
-		g_string_free(kwstr, FALSE);
-		}
-
-	return ret;
-}
-
-static gchar *image_osd_mkinfo(const gchar *str, ImageWindow *imd, GHashTable *vars)
-{
-	gchar delim = '%', imp = '|', sep[] = " - ";
-	gchar *start, *end;
-	guint pos, prev;
-	gboolean want_separator = FALSE;
-	gchar *name, *data;
-	GString *new;
-	gchar *ret;
-
-	if (!str || !*str) return g_strdup("");
-
-	new = g_string_new(str);
-
-	prev = -1;
-
-	while (TRUE)
-		{
-		guint limit = 0;
-		gchar *trunc = NULL;
-		gchar *limpos = NULL;
-		gchar *extra = NULL;
-		gchar *extrapos = NULL;
-		gchar *p;
-
-		start = strchr(new->str + (prev + 1), delim);
-		if (!start)
-			break;
-		end = strchr(start+1, delim);
-		if (!end)
-			break;
-
-		/* Search for optionnal modifiers
-		 * %name:99:extra% -> name = "name", limit=99, extra = "extra"
-		 */
-		for (p = start + 1; p < end; p++)
-			{
-			if (p[0] == ':')
-				{
-				if (g_ascii_isdigit(p[1]) && !limpos)
-					{
-					limpos = p + 1;
-					if (!trunc) trunc = p;
-					}
-				else
-					{
-					extrapos = p + 1;
-					if (!trunc) trunc = p;
-					break;
-					}
-				}
-			}
-
-		if (limpos)
-			limit = (guint) atoi(limpos);
-
-		if (extrapos)
-			extra = g_strndup(extrapos, end - extrapos);
-
-		name = g_strndup(start+1, (trunc ? trunc : end)-start-1);
-		pos = start - new->str;
-		data = NULL;
-
-		if (strcmp(name, "keywords") == 0)
-			{
-			data = keywords_to_string(imd->image_fd);
-			}
-		else if (strcmp(name, "comment") == 0)
-			{
-			data = metadata_read_string(imd->image_fd, COMMENT_KEY, METADATA_PLAIN);
-			}
-		else if (strcmp(name, "imagecomment") == 0)
-			{
-			data = exif_get_image_comment(imd->image_fd);
-			}
-		else if (strcmp(name, "rating") == 0)
-			{
-			data = metadata_read_string(imd->image_fd, RATING_KEY, METADATA_PLAIN);
-			}
-#ifdef HAVE_LUA
-		else if (strncmp(name, "lua/", 4) == 0)
-			{
-			gchar *tmp;
-			tmp = strchr(name+4, '/');
-			if (!tmp)
-				break;
-			*tmp = '\0';
-			data = lua_callvalue(imd->image_fd, name+4, tmp+1);
-			}
-#endif
-		else
-			{
-			data = g_strdup(g_hash_table_lookup(vars, name));
-			if (!data)
-				data = metadata_read_string(imd->image_fd, name, METADATA_FORMATTED);
-			}
-
-		if (data && *data && limit > 0 && strlen(data) > limit + 3)
-			{
-			gchar *new_data = g_strdup_printf("%-*.*s...", limit, limit, data);
-			g_free(data);
-			data = new_data;
-			}
-
-		if (data)
-			{
-			/* Since we use pango markup to display, we need to escape here */
-			gchar *escaped = g_markup_escape_text(data, -1);
-			g_free(data);
-			data = escaped;
-			}
-
-		if (extra)
-			{
-			if (data && *data)
-				{
-				/* Display data between left and right parts of extra string
-				 * the data is expressed by a '*' character. A '*' may be escaped
-				 * by a \. You should escape all '*' characters, do not rely on the
-				 * current implementation which only replaces the first unescaped '*'.
-				 * If no "*" is present, the extra string is just appended to data string.
-				 * Pango mark up is accepted in left and right parts.
-				 * Any \n is replaced by a newline
-				 * Examples:
-				 * "<i>*</i>\n" -> data is displayed in italics ended with a newline
-				 * "\n" 	-> ended with newline
-				 * "ISO *"	-> prefix data with "ISO " (ie. "ISO 100")
-				 * "\**\*"	-> prefix data with a star, and append a star (ie. "*100*")
-				 * "\\*"	-> prefix data with an anti slash (ie "\100")
-				 * "Collection <b>*</b>\n" -> display data in bold prefixed by "Collection " and a newline is appended
-				 *
-				 * FIXME: using background / foreground colors lead to weird results.
-				 */
-				gchar *new_data;
-				gchar *left = NULL;
-				gchar *right = extra;
-				gchar *p;
-				guint len = strlen(extra);
-
-				/* Search for left and right parts and unescape characters */
-				for (p = extra; *p; p++, len--)
-					if (p[0] == '\\')
-						{
-						if (p[1] == 'n')
-							{
-							memmove(p+1, p+2, --len);
-							p[0] = '\n';
-							}
-						else if (p[1] != '\0')
-							memmove(p, p+1, len--); // includes \0
-						}
-					else if (p[0] == '*' && !left)
-						{
-						right = p + 1;
-						left = extra;
-						}
-
-				if (left) right[-1] = '\0';
-
-				new_data = g_strdup_printf("%s%s%s", left ? left : "", data, right);
-				g_free(data);
-				data = new_data;
-				}
-			g_free(extra);
-			}
-
-		g_string_erase(new, pos, end-start+1);
-		if (data && *data)
-			{
-			if (want_separator)
-				{
-				/* insert separator */
-				g_string_insert(new, pos, sep);
-				pos += strlen(sep);
-				want_separator = FALSE;
-				}
-
-			g_string_insert(new, pos, data);
-			pos += strlen(data);
-		}
-
-		if (pos-prev >= 1 && new->str[pos] == imp)
-			{
-			/* pipe character is replaced by a separator, delete it
-			 * and raise a flag if needed */
-			g_string_erase(new, pos--, 1);
-			want_separator |= (data && *data);
-			}
-
-		if (new->str[pos] == '\n') want_separator = FALSE;
-
-		prev = pos - 1;
-
-		g_free(name);
-		g_free(data);
-		}
-
-	/* search and destroy empty lines */
-	end = new->str;
-	while ((start = strchr(end, '\n')))
-		{
-		end = start;
-		while (*++(end) == '\n')
-			;
-		g_string_erase(new, start-new->str, end-start-1);
-		}
-
-	g_strchomp(new->str);
-
-	ret = new->str;
-	g_string_free(new, FALSE);
-
-	return ret;
-}
-
-typedef enum {
-	OSDT_NONE 	= 0,
-	OSDT_FREE 	= 1 << 0,
-	OSDT_NO_DUP 	= 1 << 1
-} OsdTemplateFlags;
-
-static void osd_template_insert(GHashTable *vars, gchar *keyword, gchar *value, OsdTemplateFlags flags)
-{
-	if (!value)
-		{
-		g_hash_table_insert(vars, keyword, g_strdup(""));
-		return;
-		}
-
-	if (flags & OSDT_NO_DUP)
-		{
-		g_hash_table_insert(vars, keyword, value);
-		return;
-		}
-	else
-		{
-		g_hash_table_insert(vars, keyword, g_strdup(value));
-		}
-
-	if (flags & OSDT_FREE) g_free((gpointer) value);
-}
-
 static GdkPixbuf *image_osd_info_render(OverlayStateData *osd)
 {
 	GdkPixbuf *pixbuf = NULL;
@@ -624,7 +340,7 @@
 	 		osd_template_insert(vars, "res", NULL, OSDT_NONE);
 			}
 
-	 	text = image_osd_mkinfo(options->image_overlay.template_string, imd, vars);
+		text = image_osd_mkinfo(options->image_overlay.template_string, imd->image_fd, vars);
 		g_hash_table_destroy(vars);
 
 	} else {
--- a/src/options.c	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/options.c	Thu Nov 22 15:08:54 2018 +0000
@@ -197,10 +197,10 @@
 	options->star_rating.star = STAR_RATING_STAR;
 	options->star_rating.rejected = STAR_RATING_REJECTED;
 
+	options->printer.template_string = NULL;
 	options->printer.image_font = g_strdup("Serif 10");
 	options->printer.page_font = g_strdup("Serif 10");
 	options->printer.page_text = NULL;
-	options->printer.text_fields = 1;
 	options->printer.image_text_position = 1;
 	options->printer.page_text_position = 3;
 
--- a/src/options.h	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/options.h	Thu Nov 22 15:08:54 2018 +0000
@@ -307,12 +307,12 @@
 	struct {
 		gchar *image_font;
 		gchar *page_font;
-		gint text_fields;
 		gboolean show_image_text;
 		gboolean show_page_text;
 		gchar *page_text;
 		gint image_text_position;
 		gint page_text_position;
+		gchar *template_string;
 	} printer;
 
 	gboolean read_metadata_in_idle;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/osd.c	Thu Nov 22 15:08:54 2018 +0000
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2018 The Geeqie Team
+ *
+ * Author: Colin Clark
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Routines for creating the Overlay Screen Display text. Also
+ * used for the same purposes by the Print routines
+ */
+
+#include "main.h"
+#include "osd.h"
+
+#include "dnd.h"
+#include "exif.h"
+#include "glua.h"
+#include "metadata.h"
+#include "ui_fileops.h"
+#include "ui_misc.h"
+
+#include <math.h>
+
+static const gchar *predefined_tags[][2] = {
+	{"%name%",							N_("Name")},
+	{"%path:60%",						N_("Path")},
+	{"%date%",							N_("Date")},
+	{"%size%",							N_("Size")},
+	{"%zoom%",							N_("Zoom")},
+	{"%dimensions%",					N_("Dimensions")},
+	{"%collection%",					N_("Collection")},
+	{"%number%",						N_("Image index")},
+	{"%total%",							N_("Images total")},
+	{"%comment%",						N_("Comment")},
+	{"%keywords%",						N_("Keywords")},
+	{"%file.ctime%",					N_("File ctime")},
+	{"%file.mode%",						N_("File mode")},
+	{"%file.owner%",					N_("File owner")},
+	{"%file.group%",					N_("File group")},
+	{"%file.link%",						N_("File link")},
+	{"%file.class%",					N_("File class")},
+	{"%formatted.DateTime%",			N_("Image date")},
+	{"%formatted.DateTimeDigitized%",	N_("Date digitized")},
+	{"%formatted.ShutterSpeed%",		N_("ShutterSpeed")},
+	{"%formatted.Aperture%",			N_("Aperture")},
+	{"%formatted.ExposureBias%",		N_("Exposure bias")},
+	{"%formatted.Resolution%",			N_("Resolution")},
+	{"%formatted.Camera%",				N_("Camera")},
+	{"%formatted.ISOSpeedRating%",		N_("ISO")},
+	{"%formatted.FocalLength%",			N_("Focal length")},
+	{"%formatted.FocalLength35mmFilm%",	N_("Focal len. 35mm")},
+	{"%formatted.SubjectDistance%",		N_("Subject distance")},
+	{"%formatted.Flash%",				N_("Flash")},
+	{"%formatted.ColorProfile%",		N_("Color profile")},
+	{"%formatted.GPSPosition%",			N_("Lat, Long")},
+	{"%formatted.GPSAltitude%",			N_("Altitude")},
+	{"%formatted.localtime%",			N_("Local time")},
+	{"%formatted.timezone%",			N_("Timezone")},
+	{"%formatted.countryname%",			N_("Country name")},
+	{"%formatted.countrycode%",			N_("Country code")},
+	{"%rating%",						N_("Rating")},
+	{"%formatted.star_rating%",			N_("Star rating")},
+	{"%Xmp.dc.creator%",				N_("© Creator")},
+	{"%Xmp.dc.contributor%",			N_("© Contributor")},
+	{"%Xmp.dc.rights%",					N_("© Rights")},
+	{NULL, NULL}};
+
+static GtkTargetEntry osd_drag_types[] = {
+	{ "text/plain", GTK_TARGET_SAME_APP, TARGET_TEXT_PLAIN }
+};
+
+typedef struct _TagData TagData;
+struct _TagData
+{
+	gchar *key;
+	gchar *title;
+};
+
+static void tag_button_cb(GtkWidget *widget, gpointer data)
+{
+	GtkTextView *image_overlay_template_view = data;
+	GtkTextBuffer *buffer;
+	TagData *td;
+
+	buffer = gtk_text_view_get_buffer(image_overlay_template_view);
+	td = g_object_get_data(G_OBJECT(widget), "tag_data");
+	gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(buffer), td->key, -1);
+
+	gtk_widget_grab_focus(GTK_WIDGET(image_overlay_template_view));
+}
+
+static void osd_dnd_get_cb(GtkWidget *btn, GdkDragContext *context,
+								GtkSelectionData *selection_data, guint info,
+								guint time, gpointer data)
+{
+	TagData *td;
+	GtkTextView *image_overlay_template_view = data;
+
+	td = g_object_get_data(G_OBJECT(btn), "tag_data");
+	gtk_selection_data_set_text(selection_data, td->key, -1);
+
+	gtk_widget_grab_focus(GTK_WIDGET(image_overlay_template_view));
+}
+
+static void osd_btn_destroy_cb(GtkWidget *btn, GdkDragContext *context,
+								GtkSelectionData *selection_data, guint info,
+								guint time, gpointer data)
+{
+	TagData *td;
+
+	td = g_object_get_data(G_OBJECT(btn), "tag_data");
+	g_free(td->key);
+	g_free(td->title);
+}
+
+static void set_osd_button(GtkTable *table, const gint rows, const gint cols, const gchar *key, const gchar *title, GtkWidget *template_view)
+{
+	GtkWidget *new_button;
+	TagData *td;
+
+	new_button = gtk_button_new_with_label(title);
+	g_signal_connect(G_OBJECT(new_button), "clicked", G_CALLBACK(tag_button_cb), template_view);
+	gtk_widget_show(new_button);
+
+	td = g_new0(TagData, 1);
+	td->key = g_strdup(key);
+	td->title = g_strdup(title);
+
+	g_object_set_data(G_OBJECT(new_button), "tag_data", td);
+
+	gtk_drag_source_set(new_button, GDK_BUTTON1_MASK, osd_drag_types, 1, GDK_ACTION_COPY);
+	g_signal_connect(G_OBJECT(new_button), "drag_data_get",
+							G_CALLBACK(osd_dnd_get_cb), template_view);
+	g_signal_connect(G_OBJECT(new_button), "destroy",
+							G_CALLBACK(osd_btn_destroy_cb), new_button);
+
+	gtk_table_attach_defaults(table, new_button, cols, cols+1, rows, rows+1);
+
+}
+
+GtkWidget *osd_new(gint max_cols, GtkWidget *template_view)
+{
+	GtkWidget *hbox;
+	GtkWidget *vbox;
+	GtkWidget *vbox_buttons;
+	GtkWidget *group;
+	GtkWidget *button;
+	GtkWidget *scrolled;
+	GtkTextBuffer *buffer;
+	GtkWidget *label;
+	GtkWidget *	subgroup;
+	gint i = 0;
+	gint rows = 0;
+	gint max_rows = 0;
+	gint col = 0;
+	gint cols = 0;
+	gdouble entries;
+	GtkWidget *viewport;
+
+	vbox = gtk_vbox_new(FALSE, 0);
+
+	pref_label_new(vbox, _("To include predefined tags in the template, click a button or drag-and-drop"));
+
+	scrolled = gtk_scrolled_window_new(NULL, NULL);
+	gtk_box_pack_start(GTK_BOX(vbox), scrolled, FALSE, FALSE, 0);
+	gtk_container_set_border_width(GTK_CONTAINER(scrolled), PREF_PAD_BORDER);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+				       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	label = gtk_label_new("title");
+	gtk_widget_show(scrolled);
+	gtk_widget_set_size_request(scrolled, -1, 140);
+
+	viewport = gtk_viewport_new(NULL, NULL);
+	gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+	gtk_container_add(GTK_CONTAINER(scrolled), viewport);
+	gtk_widget_show(viewport);
+
+	entries = (sizeof(predefined_tags) / sizeof(predefined_tags[0])) - 1;
+	max_rows = ceil(entries / max_cols);
+
+	GtkTable *table;
+	table = GTK_TABLE(gtk_table_new(max_rows, max_cols, FALSE));
+	gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(table));
+	gtk_widget_show(GTK_WIDGET(table));
+
+	for (rows = 0; rows < max_rows; rows++)
+		{
+		cols = 0;
+
+		while (cols < max_cols && predefined_tags[i][0])
+			{
+			set_osd_button(table, rows, cols, predefined_tags[i][0], predefined_tags[i][1], template_view);
+			i = i + 1;
+			cols++;
+			}
+		}
+	return vbox;
+}
+static gchar *keywords_to_string(FileData *fd)
+{
+	GList *keywords;
+	GString *kwstr = NULL;
+	gchar *ret = NULL;
+
+	g_assert(fd);
+
+	keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
+
+	if (keywords)
+		{
+		GList *work = keywords;
+
+		while (work)
+			{
+			gchar *kw = work->data;
+			work = work->next;
+
+			if (!kw) continue;
+			if (!kwstr)
+				kwstr = g_string_new("");
+			else
+				g_string_append(kwstr, ", ");
+
+			g_string_append(kwstr, kw);
+			}
+		string_list_free(keywords);
+		}
+
+	if (kwstr)
+		{
+		ret = kwstr->str;
+		g_string_free(kwstr, FALSE);
+		}
+
+	return ret;
+}
+
+gchar *image_osd_mkinfo(const gchar *str, FileData *fd, GHashTable *vars)
+{
+	gchar delim = '%', imp = '|', sep[] = " - ";
+	gchar *start, *end;
+	guint pos, prev;
+	gboolean want_separator = FALSE;
+	gchar *name, *data;
+	GString *new;
+	gchar *ret;
+
+	if (!str || !*str) return g_strdup("");
+
+	new = g_string_new(str);
+
+	prev = -1;
+
+	while (TRUE)
+		{
+		guint limit = 0;
+		gchar *trunc = NULL;
+		gchar *limpos = NULL;
+		gchar *extra = NULL;
+		gchar *extrapos = NULL;
+		gchar *p;
+
+		start = strchr(new->str + (prev + 1), delim);
+		if (!start)
+			break;
+		end = strchr(start+1, delim);
+		if (!end)
+			break;
+
+		/* Search for optionnal modifiers
+		 * %name:99:extra% -> name = "name", limit=99, extra = "extra"
+		 */
+		for (p = start + 1; p < end; p++)
+			{
+			if (p[0] == ':')
+				{
+				if (g_ascii_isdigit(p[1]) && !limpos)
+					{
+					limpos = p + 1;
+					if (!trunc) trunc = p;
+					}
+				else
+					{
+					extrapos = p + 1;
+					if (!trunc) trunc = p;
+					break;
+					}
+				}
+			}
+
+		if (limpos)
+			limit = (guint) atoi(limpos);
+
+		if (extrapos)
+			extra = g_strndup(extrapos, end - extrapos);
+
+		name = g_strndup(start+1, (trunc ? trunc : end)-start-1);
+		pos = start - new->str;
+		data = NULL;
+
+		if (strcmp(name, "keywords") == 0)
+			{
+			data = keywords_to_string(fd);
+			}
+		else if (strcmp(name, "comment") == 0)
+			{
+			data = metadata_read_string(fd, COMMENT_KEY, METADATA_PLAIN);
+			}
+		else if (strcmp(name, "imagecomment") == 0)
+			{
+			data = exif_get_image_comment(fd);
+			}
+		else if (strcmp(name, "rating") == 0)
+			{
+			data = metadata_read_string(fd, RATING_KEY, METADATA_PLAIN);
+			}
+#ifdef HAVE_LUA
+		else if (strncmp(name, "lua/", 4) == 0)
+			{
+			gchar *tmp;
+			tmp = strchr(name+4, '/');
+			if (!tmp)
+				break;
+			*tmp = '\0';
+			data = lua_callvalue(fd, name+4, tmp+1);
+			}
+#endif
+		else
+			{
+			data = g_strdup(g_hash_table_lookup(vars, name));
+			if (!data)
+				data = metadata_read_string(fd, name, METADATA_FORMATTED);
+			}
+
+		if (data && *data && limit > 0 && strlen(data) > limit + 3)
+			{
+			gchar *new_data = g_strdup_printf("%-*.*s...", limit, limit, data);
+			g_free(data);
+			data = new_data;
+			}
+
+		if (data)
+			{
+			/* Since we use pango markup to display, we need to escape here */
+			gchar *escaped = g_markup_escape_text(data, -1);
+			g_free(data);
+			data = escaped;
+			}
+
+		if (extra)
+			{
+			if (data && *data)
+				{
+				/* Display data between left and right parts of extra string
+				 * the data is expressed by a '*' character. A '*' may be escaped
+				 * by a \. You should escape all '*' characters, do not rely on the
+				 * current implementation which only replaces the first unescaped '*'.
+				 * If no "*" is present, the extra string is just appended to data string.
+				 * Pango mark up is accepted in left and right parts.
+				 * Any \n is replaced by a newline
+				 * Examples:
+				 * "<i>*</i>\n" -> data is displayed in italics ended with a newline
+				 * "\n" 	-> ended with newline
+				 * "ISO *"	-> prefix data with "ISO " (ie. "ISO 100")
+				 * "\**\*"	-> prefix data with a star, and append a star (ie. "*100*")
+				 * "\\*"	-> prefix data with an anti slash (ie "\100")
+				 * "Collection <b>*</b>\n" -> display data in bold prefixed by "Collection " and a newline is appended
+				 *
+				 * FIXME: using background / foreground colors lead to weird results.
+				 */
+				gchar *new_data;
+				gchar *left = NULL;
+				gchar *right = extra;
+				gchar *p;
+				guint len = strlen(extra);
+
+				/* Search for left and right parts and unescape characters */
+				for (p = extra; *p; p++, len--)
+					if (p[0] == '\\')
+						{
+						if (p[1] == 'n')
+							{
+							memmove(p+1, p+2, --len);
+							p[0] = '\n';
+							}
+						else if (p[1] != '\0')
+							memmove(p, p+1, len--); // includes \0
+						}
+					else if (p[0] == '*' && !left)
+						{
+						right = p + 1;
+						left = extra;
+						}
+
+				if (left) right[-1] = '\0';
+
+				new_data = g_strdup_printf("%s%s%s", left ? left : "", data, right);
+				g_free(data);
+				data = new_data;
+				}
+			g_free(extra);
+			}
+
+		g_string_erase(new, pos, end-start+1);
+		if (data && *data)
+			{
+			if (want_separator)
+				{
+				/* insert separator */
+				g_string_insert(new, pos, sep);
+				pos += strlen(sep);
+				want_separator = FALSE;
+				}
+
+			g_string_insert(new, pos, data);
+			pos += strlen(data);
+		}
+
+		if (pos-prev >= 1 && new->str[pos] == imp)
+			{
+			/* pipe character is replaced by a separator, delete it
+			 * and raise a flag if needed */
+			g_string_erase(new, pos--, 1);
+			want_separator |= (data && *data);
+			}
+
+		if (new->str[pos] == '\n') want_separator = FALSE;
+
+		prev = pos - 1;
+
+		g_free(name);
+		g_free(data);
+		}
+
+	/* search and destroy empty lines */
+	end = new->str;
+	while ((start = strchr(end, '\n')))
+		{
+		end = start;
+		while (*++(end) == '\n')
+			;
+		g_string_erase(new, start-new->str, end-start-1);
+		}
+
+	g_strchomp(new->str);
+
+	ret = new->str;
+	g_string_free(new, FALSE);
+
+	return ret;
+}
+
+void osd_template_insert(GHashTable *vars, gchar *keyword, gchar *value, OsdTemplateFlags flags)
+{
+	if (!value)
+		{
+		g_hash_table_insert(vars, keyword, g_strdup(""));
+		return;
+		}
+
+	if (flags & OSDT_NO_DUP)
+		{
+		g_hash_table_insert(vars, keyword, value);
+		return;
+		}
+	else
+		{
+		g_hash_table_insert(vars, keyword, g_strdup(value));
+		}
+
+	if (flags & OSDT_FREE) g_free((gpointer) value);
+}
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/osd.h	Thu Nov 22 15:08:54 2018 +0000
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Geeqie Team
+ *
+ * Author: Colin Clark
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef OSD_H
+#define OSD_H
+
+typedef enum {
+	OSDT_NONE 	= 0,
+	OSDT_FREE 	= 1 << 0,
+	OSDT_NO_DUP 	= 1 << 1
+} OsdTemplateFlags;
+
+GtkWidget *osd_new(gint max_cols, GtkWidget *template_view);
+gchar *image_osd_mkinfo(const gchar *str, FileData *fd, GHashTable *vars);
+void osd_template_insert(GHashTable *vars, gchar *keyword, gchar *value, OsdTemplateFlags flags);
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/preferences.c	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/preferences.c	Thu Nov 22 15:08:54 2018 +0000
@@ -39,6 +39,7 @@
 #include "layout_config.h"
 #include "layout_util.h"
 #include "metadata.h"
+#include "osd.h"
 #include "pixbuf_util.h"
 #include "slideshow.h"
 #include "toolbar.h"
@@ -52,8 +53,6 @@
 #include "window.h"
 #include "zonedetect.h"
 
-#include <math.h>
-
 #ifdef HAVE_LCMS
 #ifdef HAVE_LCMS2
 #include <lcms2.h>
@@ -2056,116 +2055,7 @@
 			      options->fullscreen.disable_saver, &c_options->fullscreen.disable_saver);
 }
 
-/* overlay screen display tab */
-static const gchar *predefined_tags[][2] = {
-	{"%name%",							N_("Name")},
-	{"%path:60%*",						N_("Path")},
-	{"%date%",							N_("Date")},
-	{"%size%",							N_("Size")},
-	{"%zoom%",							N_("Zoom")},
-	{"%dimensions%",					N_("Dimensions")},
-	{"%collection%",					N_("Collection")},
-	{"%number%",						N_("Collection number")},
-	{"%total%",							N_("Collection total")},
-	{"%file.ctime%",					N_("File ctime")},
-	{"%file.mode%",						N_("File mode")},
-	{"%file.owner%",					N_("File owner")},
-	{"%file.group%",					N_("File group")},
-	{"%file.link%",						N_("File link")},
-	{"%file.class%",					N_("File class")},
-	{"%formatted.DateTime%",			N_("Image date")},
-	{"%formatted.DateTimeDigitized%",	N_("Date digitized")},
-	{"%formatted.ShutterSpeed%",		N_("ShutterSpeed")},
-	{"%formatted.Aperture%",			N_("Aperture")},
-	{"%formatted.ExposureBias%",		N_("Exposure bias")},
-	{"%formatted.Resolution%",			N_("Resolution")},
-	{"%formatted.Camera%",				N_("Camera")},
-	{"%formatted.ShutterSpeed%",		N_("Shutter speed")},
-	{"%formatted.ISOSpeedRating%",		N_("ISO")},
-	{"%formatted.FocalLength%",			N_("Focal length")},
-	{"%formatted.FocalLength35mmFilm%",	N_("Focal len. 35mm")},
-	{"%formatted.SubjectDistance%",		N_("Subject distance")},
-	{"%formatted.Flash%",				N_("Flash")},
-	{"%formatted.ColorProfile%",		N_("Color profile")},
-	{"%formatted.GPSPosition%",			N_("Lat, Long")},
-	{"%formatted.GPSAltitude%",			N_("Altitude")},
-	{"%formatted.localtime%",			N_("Local time")},
-	{"%formatted.timezone%",			N_("Timezone")},
-	{"%formatted.countryname%",			N_("Country name")},
-	{"%formatted.countrycode%",			N_("Country code")},
-	{"%formatted.star_rating%",			N_("Star rating")},
-	{NULL, NULL}};
-
-static GtkTargetEntry osd_drag_types[] = {
-	{ "text/plain", GTK_TARGET_SAME_APP, TARGET_TEXT_PLAIN }
-};
-
-typedef struct _TagData TagData;
-struct _TagData
-{
-	gchar *key;
-	gchar *title;
-};
-
-static void tag_button_cb(GtkWidget *widget, gpointer data)
-{
-	GtkTextView *image_overlay_template_view = data;
-	GtkTextBuffer *buffer;
-	TagData *td;
-
-	buffer = gtk_text_view_get_buffer(image_overlay_template_view);
-	td = g_object_get_data(G_OBJECT(widget), "tag_data");
-	gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(buffer), td->key, -1);
-
-	gtk_widget_grab_focus(GTK_WIDGET(image_overlay_template_view));
-}
-
-static void osd_dnd_get_cb(GtkWidget *btn, GdkDragContext *context,
-								GtkSelectionData *selection_data, guint info,
-								guint time, gpointer data)
-{
-	TagData *td;
-	GtkTextView *image_overlay_template_view = data;
-
-	td = g_object_get_data(G_OBJECT(btn), "tag_data");
-	gtk_selection_data_set_text(selection_data, td->key, -1);
-
-	gtk_widget_grab_focus(GTK_WIDGET(image_overlay_template_view));
-}
-
-static void osd_btn_destroy_cb(GtkWidget *btn, GdkDragContext *context,
-								GtkSelectionData *selection_data, guint info,
-								guint time, gpointer data)
-{
-	TagData *td;
-
-	td = g_object_get_data(G_OBJECT(btn), "tag_data");
-	g_free(td->key);
-	g_free(td->title);
-}
-
-static void set_osd_button(GtkWidget *widget, const gchar *key, const gchar *title,
-										GtkWidget *image_overlay_template_view)
-{
-	GtkWidget *new_button;
-	TagData *td;
-
-	new_button = pref_button_new(widget, NULL, _(title), TRUE,
-							G_CALLBACK(tag_button_cb), image_overlay_template_view);
-
-	td = g_new0(TagData, 1);
-	td->key = g_strdup(key);
-	td->title = g_strdup(title);
-
-	g_object_set_data(G_OBJECT(new_button), "tag_data", td);
-
-	gtk_drag_source_set(new_button, GDK_BUTTON1_MASK, osd_drag_types, 1, GDK_ACTION_COPY);
-	g_signal_connect(G_OBJECT(new_button), "drag_data_get",
-							G_CALLBACK(osd_dnd_get_cb), image_overlay_template_view);
-	g_signal_connect(G_OBJECT(new_button), "destroy",
-							G_CALLBACK(osd_btn_destroy_cb), new_button);
-}
-
+#define PRE_FORMATTED_COLUMNS 5
 static void config_tab_osd(GtkWidget *notebook)
 {
 	GtkWidget *hbox;
@@ -2175,6 +2065,7 @@
 	GtkWidget *button;
 	GtkWidget *image_overlay_template_view;
 	GtkWidget *scrolled;
+	GtkWidget *scrolled_pre_formatted;
 	GtkTextBuffer *buffer;
 	GtkWidget *label;
 	GtkWidget *	subgroup;
@@ -2188,33 +2079,13 @@
 
 	group = pref_group_new(vbox, FALSE, _("Overlay Screen Display"), GTK_ORIENTATION_VERTICAL);
 
-	hbox = gtk_hbox_new(FALSE, 0);
-
-	gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
-	gtk_widget_show(hbox);
-
-	pref_label_new(hbox, _("To include predefined tags in the template, click a button or drag-and-drop"));
-
 	subgroup = pref_box_new(group, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
-	hbox = gtk_hbox_new(FALSE, 0);
-	gtk_box_pack_start(GTK_BOX(subgroup), hbox, FALSE, FALSE, 0);
-	gtk_widget_show(hbox);
-
-	for (cols = 0; cols < 6; cols++)
-		{
-		vbox_buttons = gtk_vbox_new(FALSE, 0);
-		rows = 0;
-
-		gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0);
-
-		while (rows < 6 && predefined_tags[i][0])
-			{
-			set_osd_button(vbox_buttons, predefined_tags[i][0], predefined_tags[i][1], image_overlay_template_view);
-			i = i + 1;
-			rows++;
-			}
-		gtk_widget_show(vbox_buttons);
-		}
+
+	scrolled_pre_formatted = osd_new(PRE_FORMATTED_COLUMNS, image_overlay_template_view);
+	gtk_widget_set_size_request(scrolled_pre_formatted, 200, 150);
+	gtk_box_pack_start(GTK_BOX(subgroup), scrolled_pre_formatted, FALSE, FALSE, 0);
+	gtk_widget_show(scrolled_pre_formatted);
+	gtk_widget_show(subgroup);
 
 	pref_line(group, PREF_PAD_GAP);
 
--- a/src/print.c	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/print.c	Thu Nov 22 15:08:54 2018 +0000
@@ -24,6 +24,7 @@
 #include "exif.h"
 #include "filedata.h"
 #include "image-load.h"
+#include "osd.h"
 #include "pixbuf_util.h"
 #include "ui_misc.h"
 #include "ui_fileops.h"
@@ -37,14 +38,6 @@
 /* method to use when scaling down image data */
 #define PRINT_MAX_INTERP GDK_INTERP_HYPER
 
-typedef enum {
-	TEXT_INFO_FILENAME = 1 << 0,
-	TEXT_INFO_FILEDATE = 1 << 1,
-	TEXT_INFO_FILESIZE = 1 << 2,
-	TEXT_INFO_DIMENSIONS = 1 << 3,
-	TEXT_INFO_FILEPATH = 1 << 4
-} TextInfo;
-
 /* reverse order is important */
 typedef enum {
 	FOOTER_2,
@@ -59,9 +52,10 @@
 	GtkWidget *vbox;
 	GList *source_selection;
 
-	TextInfo	text_fields;
-	gint		 job_page;
+	gint job_page;
 	GtkTextBuffer *page_text;
+	gchar *template_string;
+	GtkWidget *parent;
 	ImageLoader	*job_loader;
 
 	GList *print_pixbuf_queue;
@@ -127,63 +121,6 @@
 	return TRUE;
 }
 
-static void print_text_field_set(PrintWindow *pw, TextInfo field, gboolean active)
-{
-	if (active)
-		{
-		pw->text_fields |= field;
-		}
-	else
-		{
-		pw->text_fields &= ~field;
-		}
-}
-
-static void print_text_cb_name(GtkWidget *widget, gpointer data)
-{
-	PrintWindow *pw = data;
-	gboolean active;
-
-	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
-	print_text_field_set(pw, TEXT_INFO_FILENAME, active);
-}
-
-static void print_text_cb_path(GtkWidget *widget, gpointer data)
-{
-	PrintWindow *pw = data;
-	gboolean active;
-
-	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
-	print_text_field_set(pw, TEXT_INFO_FILEPATH, active);
-}
-
-static void print_text_cb_date(GtkWidget *widget, gpointer data)
-{
-	PrintWindow *pw = data;
-	gboolean active;
-
-	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
-	print_text_field_set(pw, TEXT_INFO_FILEDATE, active);
-}
-
-static void print_text_cb_size(GtkWidget *widget, gpointer data)
-{
-	PrintWindow *pw = data;
-	gboolean active;
-
-	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
-	print_text_field_set(pw, TEXT_INFO_FILESIZE, active);
-}
-
-static void print_text_cb_dims(GtkWidget *widget, gpointer data)
-{
-	PrintWindow *pw = data;
-	gboolean active;
-
-	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
-	print_text_field_set(pw, TEXT_INFO_DIMENSIONS, active);
-}
-
 static void print_set_font_cb(GtkWidget *widget, gpointer data)
 {
 	gpointer option;
@@ -371,6 +308,32 @@
 		}
 }
 
+static void set_print_image_text_string(gchar **template_string, const gchar *value)
+{
+	g_assert(template_string);
+
+	g_free(*template_string);
+	*template_string = g_strdup(value);
+}
+
+static void image_text_template_view_changed_cb(GtkWidget *widget, gpointer data)
+{
+	GtkWidget *pTextView;
+	GtkTextBuffer *pTextBuffer;
+	GtkTextIter iStart;
+	GtkTextIter iEnd;
+
+	pTextView = GTK_WIDGET(data);
+
+	pTextBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pTextView));
+	gtk_text_buffer_get_start_iter(pTextBuffer, &iStart);
+	gtk_text_buffer_get_end_iter(pTextBuffer, &iEnd);
+
+	set_print_image_text_string(&options->printer.template_string,
+					  gtk_text_buffer_get_text(pTextBuffer, &iStart, &iEnd, TRUE));
+}
+
+#define PRE_FORMATTED_COLUMNS 4
 static void print_text_menu(GtkWidget *box, PrintWindow *pw)
 {
 	GtkWidget *group;
@@ -382,6 +345,10 @@
 	GtkWidget *page_text_button;
 	GtkWidget *subgroup;
 	GtkWidget *page_text_view;
+	GtkWidget *image_text_template_view;
+	GtkWidget *scrolled;
+	GtkWidget *scrolled_pre_formatted;
+	GtkTextBuffer *buffer;
 
 	group = pref_group_new(box, FALSE, _("Image text"), GTK_ORIENTATION_VERTICAL);
 
@@ -411,16 +378,31 @@
 	gtk_widget_show(hbox);
 	pw->image_group = (gtk_radio_button_get_group(GTK_RADIO_BUTTON(button1)));
 
-	pref_checkbox_new(subgroup, _("Name"), (pw->text_fields & TEXT_INFO_FILENAME),
-			  G_CALLBACK(print_text_cb_name), pw);
-	pref_checkbox_new(subgroup, _("Path"), (pw->text_fields & TEXT_INFO_FILEPATH),
-			  G_CALLBACK(print_text_cb_path), pw);
-	pref_checkbox_new(subgroup, _("Date"), (pw->text_fields & TEXT_INFO_FILEDATE),
-			  G_CALLBACK(print_text_cb_date), pw);
-	pref_checkbox_new(subgroup, _("Size"), (pw->text_fields & TEXT_INFO_FILESIZE),
-			  G_CALLBACK(print_text_cb_size), pw);
-	pref_checkbox_new(subgroup, _("Dimensions"), (pw->text_fields & TEXT_INFO_DIMENSIONS),
-			  G_CALLBACK(print_text_cb_dims), pw);
+	image_text_template_view = gtk_text_view_new();
+
+	scrolled_pre_formatted = osd_new(PRE_FORMATTED_COLUMNS, image_text_template_view);
+	gtk_box_pack_start(GTK_BOX(subgroup), scrolled_pre_formatted, FALSE, FALSE, 0);
+	gtk_widget_show(scrolled_pre_formatted);
+	gtk_widget_show(subgroup);
+
+	gtk_widget_set_tooltip_markup(image_text_template_view,
+					_("Extensive formatting options are shown in the Help file"));
+
+	scrolled = gtk_scrolled_window_new(NULL, NULL);
+	gtk_widget_set_size_request(scrolled, 200, 50);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+									GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start(GTK_BOX(subgroup), scrolled, TRUE, TRUE, 5);
+	gtk_widget_show(scrolled);
+
+	gtk_container_add(GTK_CONTAINER(scrolled), image_text_template_view);
+	gtk_widget_show(image_text_template_view);
+
+	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(image_text_template_view));
+	if (options->printer.template_string) gtk_text_buffer_set_text(buffer, options->printer.template_string, -1);
+	g_signal_connect(G_OBJECT(buffer), "changed",
+			 G_CALLBACK(image_text_template_view_changed_cb), image_text_template_view);
 
 	hbox = pref_box_new(subgroup, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
 
@@ -466,8 +448,6 @@
 	gtk_widget_show(hbox);
 	pw->page_group = (gtk_radio_button_get_group(GTK_RADIO_BUTTON(button2)));
 
-	GtkWidget *scrolled;
-
 	scrolled = gtk_scrolled_window_new(NULL, NULL);
 	gtk_widget_set_size_request(scrolled, 50, 50);
 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
@@ -517,6 +497,69 @@
 		}
 }
 
+gchar *form_image_text(const gchar *template_string, FileData *fd, PrintWindow *pw, gint page_nr, gint total)
+{
+	const gchar *name;
+	gchar *text = NULL;
+	GHashTable *vars;
+	gchar *window_title;
+	gchar *delimiter;
+	gchar *collection_name;
+
+	if (!fd) return NULL;
+
+	name = fd->name;
+
+	vars = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+	window_title = g_strdup(gtk_window_get_title(GTK_WINDOW(pw->parent)));
+	delimiter = g_strstr_len(window_title, -1, " - Collection - ");
+	if (delimiter)
+		{
+		collection_name = g_strndup(window_title, delimiter - window_title);
+		}
+	else
+		{
+		collection_name = NULL;
+		}
+	g_free(window_title);
+
+	if (collection_name)
+		{
+		osd_template_insert(vars, "collection", collection_name, OSDT_NONE);
+		}
+
+	osd_template_insert(vars, "number", g_strdup_printf("%d", page_nr + 1), OSDT_NO_DUP);
+	osd_template_insert(vars, "total", g_strdup_printf("%d", total), OSDT_NO_DUP);
+	osd_template_insert(vars, "name", (gchar *) name, OSDT_NONE);
+	osd_template_insert(vars, "date", fd ? ((gchar *) text_from_time(fd->date)) : "", OSDT_NONE);
+	osd_template_insert(vars, "size", fd ? (text_from_size_abrev(fd->size)) : g_strdup(""), OSDT_FREE);
+
+	if (fd->pixbuf)
+		{
+		gint w, h;
+		w = gdk_pixbuf_get_width(fd->pixbuf);
+		h = gdk_pixbuf_get_height(fd->pixbuf);
+
+		osd_template_insert(vars, "width", g_strdup_printf("%d", w), OSDT_NO_DUP);
+ 		osd_template_insert(vars, "height", g_strdup_printf("%d", h), OSDT_NO_DUP);
+ 		osd_template_insert(vars, "res", g_strdup_printf("%d × %d", w, h), OSDT_FREE);
+ 		}
+	else
+		{
+		osd_template_insert(vars, "width", NULL, OSDT_NONE);
+ 		osd_template_insert(vars, "height", NULL, OSDT_NONE);
+ 		osd_template_insert(vars, "res", NULL, OSDT_NONE);
+		}
+
+	text = image_osd_mkinfo(template_string, fd, vars);
+	g_hash_table_destroy(vars);
+
+	g_free(collection_name);
+
+	return text;
+}
+
 static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context,
 									gint page_nr, gpointer data)
 {
@@ -545,8 +588,10 @@
 	gdouble pango_page_height;
 	GtkTextIter start, end;
 	gchar *tmp;
+	gint total;
 
 	fd = g_list_nth_data(pw->source_selection, page_nr);
+	total = g_list_length(pw->source_selection);
 
 	pixbuf = g_list_nth_data(pw->print_pixbuf_queue, page_nr);
 	if (fd->exif_orientation != EXIF_ORIENTATION_TOP_LEFT)
@@ -560,36 +605,7 @@
 
 	if (options->printer.show_image_text)
 		{
-		if (pw->text_fields & TEXT_INFO_FILENAME)
-			{
-			image_text = g_string_append(image_text, g_strdup(fd->name));
-			image_text = g_string_append(image_text, "\n");
-			}
-		if (pw->text_fields & TEXT_INFO_FILEDATE)
-			{
-			image_text = g_string_append(image_text, g_strdup(text_from_time(fd->date)));
-			image_text = g_string_append(image_text, "\n");
-			}
-		if (pw->text_fields & TEXT_INFO_FILESIZE)
-			{
-			image_text = g_string_append(image_text, g_strdup(text_from_size(fd->size)));
-			image_text = g_string_append(image_text, "\n");
-			}
-		if (pw->text_fields & TEXT_INFO_DIMENSIONS)
-			{
-			g_string_append_printf(image_text, "%d x %d", (gint)pixbuf_image_width,
-												(gint)pixbuf_image_height);
-			image_text = g_string_append(image_text, "\n");
-			}
-		if (pw->text_fields & TEXT_INFO_FILEPATH)
-			{
-			image_text = g_string_append(image_text, g_strdup(fd->path));
-			image_text = g_string_append(image_text, "\n");
-			}
-		if (image_text->len > 0)
-			{
-			image_text = g_string_truncate(image_text, image_text->len - 1);
-			}
+		image_text = g_string_append(image_text, form_image_text(options->printer.template_string, fd, pw, page_nr, total));
 		}
 
 	if (options->printer.show_page_text)
@@ -789,8 +805,6 @@
 	gchar *tmp;
 	GtkTextIter start, end;
 
-	options->printer.text_fields = pw->text_fields;
-
 	gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(pw->page_text), &start, &end);
 	tmp = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(pw->page_text), &start, &end, FALSE);
 	g_free(options->printer.page_text);
@@ -867,13 +881,14 @@
 	pw = g_new0(PrintWindow, 1);
 
 	pw->source_selection = file_data_process_groups_in_selection(selection, FALSE, NULL);
-	pw->text_fields = options->printer.text_fields;
 
 	if (print_layout_page_count(pw) == 0)
 		{
 		return;
 		}
 
+	pw->parent = parent;
+
 	vbox = gtk_vbox_new(FALSE, 0);
 	gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_BORDER);
 	gtk_widget_show(vbox);
--- a/src/rcfile.c	Sun Nov 18 17:38:10 2018 +0000
+++ b/src/rcfile.c	Thu Nov 22 15:08:54 2018 +0000
@@ -497,15 +497,18 @@
 	WRITE_NL(); WRITE_CHAR(*options, cp_mv_rn.auto_end);
 	WRITE_NL(); WRITE_INT(*options, cp_mv_rn.formatted_start);
 
-	/* printer */
+	WRITE_SEPARATOR();
+
+	/* Print Text */
+	WRITE_NL(); WRITE_CHAR(*options, printer.template_string);
 	WRITE_NL(); WRITE_CHAR(*options, printer.image_font);
 	WRITE_NL(); WRITE_CHAR(*options, printer.page_font);
 	WRITE_NL(); WRITE_CHAR(*options, printer.page_text);
-	WRITE_NL(); WRITE_INT(*options, printer.text_fields);
 	WRITE_NL(); WRITE_INT(*options, printer.image_text_position);
 	WRITE_NL(); WRITE_INT(*options, printer.page_text_position);
 	WRITE_NL(); WRITE_BOOL(*options, printer.show_image_text);
 	WRITE_NL(); WRITE_BOOL(*options, printer.show_page_text);
+	WRITE_SEPARATOR();
 }
 
 static void write_color_profile(GString *outstr, gint indent)
@@ -878,11 +881,11 @@
 		if (READ_CHAR(*options, cp_mv_rn.auto_end)) continue;
 		if (READ_INT(*options, cp_mv_rn.formatted_start)) continue;
 
-		/* printer */
+		/* Printer text */
+		if (READ_CHAR(*options, printer.template_string)) continue;
 		if (READ_CHAR(*options, printer.image_font)) continue;
 		if (READ_CHAR(*options, printer.page_font)) continue;
 		if (READ_CHAR(*options, printer.page_text)) continue;
-		if (READ_INT(*options, printer.text_fields)) continue;
 		if (READ_INT(*options, printer.image_text_position)) continue;
 		if (READ_INT(*options, printer.page_text_position)) continue;
 		if (READ_BOOL(*options, printer.show_image_text)) continue;