From 40ee2ff3d50b2a9491837a037aea8004f12da4fe Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Thu, 22 Nov 2018 15:08:54 +0000 Subject: [PATCH] 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) --- po/POTFILES.in | 1 + src/Makefile.am | 2 + src/image-overlay.c | 288 +------------------------- src/options.c | 2 +- src/options.h | 2 +- src/osd.c | 486 ++++++++++++++++++++++++++++++++++++++++++++ src/osd.h | 34 ++++ src/preferences.c | 145 +------------ src/print.c | 239 ++++++++++++---------- src/rcfile.c | 11 +- 10 files changed, 669 insertions(+), 541 deletions(-) create mode 100644 src/osd.c create mode 100644 src/osd.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 74ecb06e..5db60358 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -70,6 +70,7 @@ src/menu.c 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 diff --git a/src/Makefile.am b/src/Makefile.am index 8354c30e..f5d88844 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -220,6 +220,8 @@ geeqie_SOURCES = \ misc.h \ options.c \ options.h \ + osd.c \ + osd.h \ pan-view.h \ pixbuf-renderer.c \ pixbuf-renderer.h \ diff --git a/src/image-overlay.c b/src/image-overlay.c index b0585f6c..e94d7529 100644 --- a/src/image-overlay.c +++ b/src/image-overlay.c @@ -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 @@ void image_osd_toggle(ImageWindow *imd) } } -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: - * "*\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 *\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 @@ static GdkPixbuf *image_osd_info_render(OverlayStateData *osd) 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 { diff --git a/src/options.c b/src/options.c index 6c665e85..4502d689 100644 --- a/src/options.c +++ b/src/options.c @@ -197,10 +197,10 @@ ConfOptions *init_options(ConfOptions *options) 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; diff --git a/src/options.h b/src/options.h index 9f45ec52..e4da0858 100644 --- a/src/options.h +++ b/src/options.h @@ -307,12 +307,12 @@ struct _ConfOptions 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; diff --git a/src/osd.c b/src/osd.c new file mode 100644 index 00000000..1f8ccd31 --- /dev/null +++ b/src/osd.c @@ -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 + +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: + * "*\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 *\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: */ diff --git a/src/osd.h b/src/osd.h new file mode 100644 index 00000000..886359ed --- /dev/null +++ b/src/osd.h @@ -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: */ diff --git a/src/preferences.c b/src/preferences.c index ae959551..b89c2c15 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -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 - #ifdef HAVE_LCMS #ifdef HAVE_LCMS2 #include @@ -2056,116 +2055,7 @@ static void config_tab_windows(GtkWidget *notebook) 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 @@ static void config_tab_osd(GtkWidget *notebook) GtkWidget *button; GtkWidget *image_overlay_template_view; GtkWidget *scrolled; + GtkWidget *scrolled_pre_formatted; GtkTextBuffer *buffer; GtkWidget *label; GtkWidget * subgroup; @@ -2188,33 +2079,13 @@ static void config_tab_osd(GtkWidget *notebook) 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); diff --git a/src/print.c b/src/print.c index 8032ac33..516afa91 100644 --- a/src/print.c +++ b/src/print.c @@ -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 @@ struct _PrintWindow 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 @@ static gboolean print_job_render_image(PrintWindow *pw) 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 page_text_position_f2_cb(GtkWidget *widget, gpointer data) } } +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 @@ static void print_text_menu(GtkWidget *box, PrintWindow *pw) 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 @@ static void print_text_menu(GtkWidget *box, PrintWindow *pw) 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 @@ static void print_text_menu(GtkWidget *box, PrintWindow *pw) 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 @@ static gboolean paginate_cb(GtkPrintOperation *operation, } } +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 @@ static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context, 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 @@ static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context, 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 @@ static void print_pref_store(PrintWindow *pw) 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 @@ void print_window_new(FileData *fd, GList *selection, GList *list, GtkWidget *pa 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); diff --git a/src/rcfile.c b/src/rcfile.c index 6dcbae4c..99794d52 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -497,15 +497,18 @@ static void write_global_attributes(GString *outstr, gint indent) 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 @@ static gboolean load_global_params(const gchar **attribute_names, const gchar ** 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; -- 2.20.1