Silence GTK deprecation warning in unused function
[geeqie.git] / src / osd.cc
1 /*
2  * Copyright (C) 2018 The Geeqie Team
3  *
4  * Author: Colin Clark
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 /* Routines for creating the Overlay Screen Display text. Also
22  * used for the same purposes by the Print routines
23  */
24
25 #include "osd.h"
26
27 #include <cmath>
28 #include <cstdlib>
29 #include <cstring>
30
31 #include <gdk/gdk.h>
32 #include <glib-object.h>
33
34 #include <config.h>
35
36 #include "compat.h"
37 #include "dnd.h"
38 #include "exif.h"
39 #include "glua.h"
40 #include "intl.h"
41 #include "metadata.h"
42 #include "typedefs.h"
43 #include "ui-misc.h"
44
45 static const gchar *predefined_tags[][2] = {
46         {"%name%",                                                      N_("Name")},
47         {"%path:60%",                                           N_("Path")},
48         {"%date%",                                                      N_("Date")},
49         {"%size%",                                                      N_("Size")},
50         {"%zoom%",                                                      N_("Zoom")},
51         {"%dimensions%",                                        N_("Dimensions")},
52         {"%collection%",                                        N_("Collection")},
53         {"%number%",                                            N_("Image index")},
54         {"%total%",                                                     N_("Images total")},
55         {"%comment%",                                           N_("Comment")},
56         {"%keywords%",                                          N_("Keywords")},
57         {"%file.ctime%",                                        N_("File ctime")},
58         {"%file.mode%",                                         N_("File mode")},
59         {"%file.owner%",                                        N_("File owner")},
60         {"%file.group%",                                        N_("File group")},
61         {"%file.link%",                                         N_("File link")},
62         {"%file.class%",                                        N_("File class")},
63         {"%file.page_no%",                                      N_("File page no.")},
64         {"%formatted.DateTime%",                        N_("Image date")},
65         {"%formatted.DateTimeDigitized%",       N_("Date digitized")},
66         {"%formatted.ShutterSpeed%",            N_("ShutterSpeed")},
67         {"%formatted.Aperture%",                        N_("Aperture")},
68         {"%formatted.ExposureBias%",            N_("Exposure bias")},
69         {"%formatted.Resolution%",                      N_("Resolution")},
70         {"%formatted.Camera%",                          N_("Camera")},
71         {"%lua.lensID%",                                        N_("Lens")},
72         {"%formatted.ISOSpeedRating%",          N_("ISO")},
73         {"%formatted.FocalLength%",                     N_("Focal length")},
74         {"%formatted.FocalLength35mmFilm%",     N_("Focal len. 35mm")},
75         {"%formatted.SubjectDistance%",         N_("Subject distance")},
76         {"%formatted.Flash%",                           N_("Flash")},
77         {"%formatted.ColorProfile%",            N_("Color profile")},
78         {"%formatted.GPSPosition%",                     N_("Lat, Long")},
79         {"%formatted.GPSAltitude%",                     N_("Altitude")},
80         {"%formatted.localtime%",                       N_("Local time")},
81         {"%formatted.timezone%",                        N_("Timezone")},
82         {"%formatted.countryname%",                     N_("Country name")},
83         {"%formatted.countrycode%",                     N_("Country code")},
84         {"%rating%",                                            N_("Rating")},
85         {"%formatted.star_rating%",                     N_("Star rating")},
86         {"%Xmp.dc.creator%",                            N_("© Creator")},
87         {"%Xmp.dc.contributor%",                        N_("© Contributor")},
88         {"%Xmp.dc.rights%",                                     N_("© Rights")},
89         {nullptr, nullptr}};
90
91 static GtkTargetEntry osd_drag_types[] = {
92         { const_cast<gchar *>("text/plain"), GTK_TARGET_SAME_APP, TARGET_TEXT_PLAIN }
93 };
94
95 struct TagData
96 {
97         gchar *key;
98         gchar *title;
99 };
100
101 static void tag_button_cb(GtkWidget *widget, gpointer data)
102 {
103         auto image_overlay_template_view = static_cast<GtkTextView *>(data);
104         GtkTextBuffer *buffer;
105         TagData *td;
106
107         buffer = gtk_text_view_get_buffer(image_overlay_template_view);
108         td = static_cast<TagData *>(g_object_get_data(G_OBJECT(widget), "tag_data"));
109         gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(buffer), td->key, -1);
110
111         gtk_widget_grab_focus(GTK_WIDGET(image_overlay_template_view));
112 }
113
114 static void osd_dnd_get_cb(GtkWidget *btn, GdkDragContext *, GtkSelectionData *selection_data, guint, guint, gpointer data)
115 {
116         TagData *td;
117         auto image_overlay_template_view = static_cast<GtkTextView *>(data);
118
119         td = static_cast<TagData *>(g_object_get_data(G_OBJECT(btn), "tag_data"));
120         gtk_selection_data_set_text(selection_data, td->key, -1);
121
122         gtk_widget_grab_focus(GTK_WIDGET(image_overlay_template_view));
123 }
124
125 static void osd_btn_destroy_cb(GtkWidget *btn, GdkDragContext *, GtkSelectionData *, guint, guint, gpointer)
126 {
127         TagData *td;
128
129         td = static_cast<TagData *>(g_object_get_data(G_OBJECT(btn), "tag_data"));
130         g_free(td->key);
131         g_free(td->title);
132 }
133
134 static void set_osd_button(GtkGrid *grid, const gint rows, const gint cols, const gchar *key, const gchar *title, GtkWidget *template_view)
135 {
136         GtkWidget *new_button;
137         TagData *td;
138
139         new_button = gtk_button_new_with_label(title);
140         g_signal_connect(G_OBJECT(new_button), "clicked", G_CALLBACK(tag_button_cb), template_view);
141         gtk_widget_show(new_button);
142
143         td = g_new0(TagData, 1);
144         td->key = g_strdup(key);
145         td->title = g_strdup(title);
146
147         g_object_set_data(G_OBJECT(new_button), "tag_data", td);
148
149         gtk_drag_source_set(new_button, GDK_BUTTON1_MASK, osd_drag_types, 1, GDK_ACTION_COPY);
150         g_signal_connect(G_OBJECT(new_button), "drag_data_get",
151                                                         G_CALLBACK(osd_dnd_get_cb), template_view);
152         g_signal_connect(G_OBJECT(new_button), "destroy",
153                                                         G_CALLBACK(osd_btn_destroy_cb), new_button);
154
155         gtk_grid_attach(grid, new_button, cols, rows, 1, 1);
156
157 }
158
159 GtkWidget *osd_new(gint max_cols, GtkWidget *template_view)
160 {
161         GtkWidget *vbox;
162         GtkWidget *scrolled;
163         gint i = 0;
164         gint rows = 0;
165         gint max_rows = 0;
166         gint cols = 0;
167         gdouble entries;
168         GtkWidget *viewport;
169
170         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
171
172         pref_label_new(vbox, _("To include predefined tags in the template, click a button or drag-and-drop"));
173
174         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
175         gq_gtk_box_pack_start(GTK_BOX(vbox), scrolled, FALSE, FALSE, 0);
176         gtk_container_set_border_width(GTK_CONTAINER(scrolled), PREF_PAD_BORDER);
177         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
178                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
179         gtk_widget_show(scrolled);
180         gtk_widget_set_size_request(scrolled, -1, 140);
181
182         viewport = gtk_viewport_new(nullptr, nullptr);
183         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
184         gq_gtk_container_add(GTK_WIDGET(scrolled), viewport);
185         gtk_widget_show(viewport);
186
187         entries = ((gdouble)sizeof(predefined_tags) / sizeof(predefined_tags[0])) - 1;
188         max_rows = ceil(entries / max_cols);
189
190         GtkGrid *grid;
191         grid = GTK_GRID(gtk_grid_new());
192         gq_gtk_container_add(GTK_WIDGET(viewport), GTK_WIDGET(grid));
193         gtk_widget_show(GTK_WIDGET(grid));
194
195         for (rows = 0; rows < max_rows; rows++)
196                 {
197                 cols = 0;
198
199                 while (cols < max_cols && predefined_tags[i][0])
200                         {
201                         set_osd_button(grid, rows, cols, predefined_tags[i][0], predefined_tags[i][1], template_view);
202                         i = i + 1;
203                         cols++;
204                         }
205                 }
206         return vbox;
207 }
208 static gchar *keywords_to_string(FileData *fd)
209 {
210         GList *keywords;
211         GString *kwstr = nullptr;
212
213         g_assert(fd);
214
215         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
216
217         if (keywords)
218                 {
219                 GList *work = keywords;
220
221                 while (work)
222                         {
223                         auto kw = static_cast<gchar *>(work->data);
224                         work = work->next;
225
226                         if (!kw) continue;
227                         if (!kwstr)
228                                 kwstr = g_string_new("");
229                         else
230                                 g_string_append(kwstr, ", ");
231
232                         g_string_append(kwstr, kw);
233                         }
234                 g_list_free_full(keywords, g_free);
235                 }
236
237         if (kwstr)
238                 {
239                 return g_string_free(kwstr, FALSE);
240                 }
241
242         return nullptr;
243 }
244
245 gchar *image_osd_mkinfo(const gchar *str, FileData *fd, GHashTable *vars)
246 {
247         gchar delim = '%';
248         gchar imp = '|';
249         gchar sep[] = " - ";
250         gchar *start;
251         gchar *end;
252         guint pos;
253         guint prev;
254         gboolean want_separator = FALSE;
255         gchar *name;
256         gchar *data;
257         GString *osd_info;
258         gchar *ret;
259
260         if (!str || !*str) return g_strdup("");
261
262         osd_info = g_string_new(str);
263
264         prev = -1;
265
266         while (TRUE)
267                 {
268                 guint limit = 0;
269                 gchar *trunc = nullptr;
270                 gchar *limpos = nullptr;
271                 gchar *extra = nullptr;
272                 gchar *extrapos = nullptr;
273                 gchar *p;
274
275                 start = strchr(osd_info->str + (prev + 1), delim);
276                 if (!start)
277                         break;
278                 end = strchr(start+1, delim);
279                 if (!end)
280                         break;
281
282                 /* Search for optional modifiers
283                  * %name:99:extra% -> name = "name", limit=99, extra = "extra"
284                  */
285                 for (p = start + 1; p < end; p++)
286                         {
287                         if (p[0] == ':')
288                                 {
289                                 if (g_ascii_isdigit(p[1]) && !limpos)
290                                         {
291                                         limpos = p + 1;
292                                         if (!trunc) trunc = p;
293                                         }
294                                 else
295                                         {
296                                         extrapos = p + 1;
297                                         if (!trunc) trunc = p;
298                                         break;
299                                         }
300                                 }
301                         }
302
303                 if (limpos)
304                         limit = static_cast<guint>(atoi(limpos));
305
306                 if (extrapos)
307                         extra = g_strndup(extrapos, end - extrapos);
308
309                 name = g_strndup(start+1, (trunc ? trunc : end)-start-1);
310                 pos = start - osd_info->str;
311                 data = nullptr;
312
313                 if (strcmp(name, "keywords") == 0)
314                         {
315                         data = keywords_to_string(fd);
316                         }
317                 else if (strcmp(name, "comment") == 0)
318                         {
319                         data = metadata_read_string(fd, COMMENT_KEY, METADATA_PLAIN);
320                         }
321                 else if (strcmp(name, "imagecomment") == 0)
322                         {
323                         data = exif_get_image_comment(fd);
324                         }
325                 else if (strcmp(name, "rating") == 0)
326                         {
327                         data = metadata_read_string(fd, RATING_KEY, METADATA_PLAIN);
328                         }
329 #if HAVE_LUA
330                 else if (strncmp(name, "lua/", 4) == 0)
331                         {
332                         gchar *tmp;
333                         tmp = strchr(name+4, '/');
334                         if (!tmp)
335                                 break;
336                         *tmp = '\0';
337                         data = lua_callvalue(fd, name+4, tmp+1);
338                         }
339 #endif
340                 else
341                         {
342                         data = g_strdup(static_cast<const gchar *>(g_hash_table_lookup(vars, static_cast<gconstpointer>(name))));
343                         if (!data)
344                                 data = metadata_read_string(fd, name, METADATA_FORMATTED);
345                         }
346
347                 if (data && *data && limit > 0 && strlen(data) > limit + 3)
348                         {
349                         gchar *new_data = g_strdup_printf("%-*.*s...", limit, limit, data);
350                         g_free(data);
351                         data = new_data;
352                         }
353
354                 if (data)
355                         {
356                         /* Since we use pango markup to display, we need to escape here */
357                         gchar *escaped = g_markup_escape_text(data, -1);
358                         g_free(data);
359                         data = escaped;
360                         }
361
362                 if (extra)
363                         {
364                         if (data && *data)
365                                 {
366                                 /* Display data between left and right parts of extra string
367                                  * the data is expressed by a '*' character. A '*' may be escaped
368                                  * by a \. You should escape all '*' characters, do not rely on the
369                                  * current implementation which only replaces the first unescaped '*'.
370                                  * If no "*" is present, the extra string is just appended to data string.
371                                  * Pango mark up is accepted in left and right parts.
372                                  * Any \n is replaced by a newline
373                                  * Examples:
374                                  * "<i>*</i>\n" -> data is displayed in italics ended with a newline
375                                  * "\n"         -> ended with newline
376                                  * 'ISO *'      -> prefix data with 'ISO ' (ie. 'ISO 100')
377                                  * "\**\*"      -> prefix data with a star, and append a star (ie. "*100*")
378                                  * "\\*"        -> prefix data with an anti slash (ie "\100")
379                                  * 'Collection <b>*</b>\n' -> display data in bold prefixed by 'Collection ' and a newline is appended
380                                  */
381                                 /** @FIXME using background / foreground colors lead to weird results.
382                                  */
383                                 gchar *new_data;
384                                 gchar *left = nullptr;
385                                 gchar *right = extra;
386                                 gchar *p;
387                                 guint len = strlen(extra);
388
389                                 /* Search for left and right parts and unescape characters */
390                                 for (p = extra; *p; p++, len--)
391                                         if (p[0] == '\\')
392                                                 {
393                                                 if (p[1] == 'n')
394                                                         {
395                                                         memmove(p+1, p+2, --len);
396                                                         p[0] = '\n';
397                                                         }
398                                                 else if (p[1] != '\0')
399                                                         memmove(p, p+1, len--); // includes \0
400                                                 }
401                                         else if (p[0] == '*' && !left)
402                                                 {
403                                                 right = p + 1;
404                                                 left = extra;
405                                                 }
406
407                                 if (left) right[-1] = '\0';
408
409                                 new_data = g_strdup_printf("%s%s%s", left ? left : "", data, right);
410                                 g_free(data);
411                                 data = new_data;
412                                 }
413                         g_free(extra);
414                         }
415
416                 g_string_erase(osd_info, pos, end-start+1);
417                 if (data && *data)
418                         {
419                         if (want_separator)
420                                 {
421                                 /* insert separator */
422                                 g_string_insert(osd_info, pos, sep);
423                                 pos += strlen(sep);
424                                 want_separator = FALSE;
425                                 }
426
427                         g_string_insert(osd_info, pos, data);
428                         pos += strlen(data);
429                 }
430
431                 if (pos-prev >= 1 && osd_info->str[pos] == imp)
432                         {
433                         /* pipe character is replaced by a separator, delete it
434                          * and raise a flag if needed */
435                         g_string_erase(osd_info, pos--, 1);
436                         want_separator |= (data && *data);
437                         }
438
439                 if (osd_info->str[pos] == '\n') want_separator = FALSE;
440
441                 prev = pos - 1;
442
443                 g_free(name);
444                 g_free(data);
445                 }
446
447         /* search and destroy empty lines */
448         end = osd_info->str;
449         while ((start = strchr(end, '\n')))
450                 {
451                 end = start;
452                 while (*++(end) == '\n')
453                         ;
454                 g_string_erase(osd_info, start-osd_info->str, end-start-1);
455                 }
456
457         ret = g_string_free(osd_info, FALSE);
458
459         return g_strchomp(ret);
460 }
461
462 void osd_template_insert(GHashTable *vars, const gchar *keyword, const gchar *value, OsdTemplateFlags flags)
463 {
464         if (!value)
465                 {
466                 g_hash_table_insert(vars, const_cast<gchar *>(keyword), g_strdup(""));
467                 return;
468                 }
469
470         if (flags & OSDT_NO_DUP)
471                 {
472                 g_hash_table_insert(vars, const_cast<gchar *>(keyword), const_cast<gchar *>(value));
473                 return;
474                 }
475
476         g_hash_table_insert(vars, const_cast<gchar *>(keyword), g_strdup(value));
477
478         if (flags & OSDT_FREE) g_free(const_cast<gchar *>(value));
479 }
480 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */