b0585f6cf9be9ccdc7ab83063bb3b272ba9fe91b
[geeqie.git] / src / image-overlay.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "image-overlay.h"
24
25 #include "collect.h"
26 #include "exif.h"
27 #include "filedata.h"
28 #include "histogram.h"
29 #include "image.h"
30 #include "img-view.h"
31 #include "layout.h"
32 #include "metadata.h"
33 #include "pixbuf-renderer.h"
34 #include "pixbuf_util.h"
35 #include "ui_fileops.h"
36 #include "image-load.h"
37 #include "glua.h"
38
39 /*
40  *----------------------------------------------------------------------------
41  * image overlay
42  *----------------------------------------------------------------------------
43  */
44
45
46 typedef struct _OverlayStateData OverlayStateData;
47 struct _OverlayStateData {
48         ImageWindow *imd;
49         ImageState changed_states;
50         NotifyType notify;
51
52         Histogram *histogram;
53
54         OsdShowFlags show;
55         OverlayRendererFlags origin;
56
57         gint ovl_info;
58
59         gint x;
60         gint y;
61
62         gint icon_time[IMAGE_OSD_COUNT];
63         gint icon_id[IMAGE_OSD_COUNT];
64
65         guint idle_id; /* event source id */
66         guint timer_id; /* event source id */
67         gulong destroy_id;
68 };
69
70
71 typedef struct _OSDIcon OSDIcon;
72 struct _OSDIcon {
73         gboolean reset; /* reset on new image */
74         gint x;         /* x, y offset */
75         gint y;
76         gchar *key;     /* inline pixbuf */
77 };
78
79 static OSDIcon osd_icons[] = {
80         {  TRUE,   0,   0, NULL },                      /* none */
81         {  TRUE, -10, -10, NULL },                      /* auto rotated */
82         {  TRUE, -10, -10, NULL },                      /* user rotated */
83         {  TRUE, -40, -10, NULL },                      /* color embedded */
84         {  TRUE, -70, -10, NULL },                      /* first image */
85         {  TRUE, -70, -10, NULL },                      /* last image */
86         { FALSE, -70, -10, NULL },                      /* osd enabled */
87         { FALSE, 0, 0, NULL }
88 };
89
90 #define OSD_DATA "overlay-data"
91
92 #define IMAGE_OSD_DEFAULT_DURATION 30
93
94 #define HISTOGRAM_HEIGHT 140
95 #define HISTOGRAM_WIDTH  256
96
97 static void image_osd_timer_schedule(OverlayStateData *osd);
98
99 void set_image_overlay_template_string(gchar **template_string, const gchar *value)
100 {
101         g_assert(template_string);
102
103         g_free(*template_string);
104         *template_string = g_strdup(value);
105 }
106
107
108 void set_default_image_overlay_template_string(gchar **template_string)
109 {
110         set_image_overlay_template_string(template_string, DEFAULT_OVERLAY_INFO);
111 }
112
113 void set_image_overlay_font_string(gchar **font_string, const gchar *value)
114 {
115         g_assert(font_string);
116
117         g_free(*font_string);
118         *font_string = g_strdup(value);
119 }
120
121 static OverlayStateData *image_get_osd_data(ImageWindow *imd)
122 {
123         OverlayStateData *osd;
124
125         if (!imd) return NULL;
126
127         g_assert(imd->pr);
128
129         osd = g_object_get_data(G_OBJECT(imd->pr), "IMAGE_OVERLAY_DATA");
130         return osd;
131 }
132
133 static void image_set_osd_data(ImageWindow *imd, OverlayStateData *osd)
134 {
135         g_assert(imd);
136         g_assert(imd->pr);
137         g_object_set_data(G_OBJECT(imd->pr), "IMAGE_OVERLAY_DATA", osd);
138 }
139
140 /*
141  *----------------------------------------------------------------------------
142  * image histogram
143  *----------------------------------------------------------------------------
144  */
145
146
147 void image_osd_histogram_toggle_channel(ImageWindow *imd)
148 {
149         OverlayStateData *osd = image_get_osd_data(imd);
150
151         if (!osd || !osd->histogram) return;
152
153         histogram_toggle_channel(osd->histogram);
154         image_osd_update(imd);
155 }
156
157 void image_osd_histogram_toggle_mode(ImageWindow *imd)
158 {
159         OverlayStateData *osd = image_get_osd_data(imd);
160
161         if (!osd || !osd->histogram) return;
162
163         histogram_toggle_mode(osd->histogram);
164         image_osd_update(imd);
165 }
166
167 void image_osd_histogram_set_channel(ImageWindow *imd, gint chan)
168 {
169         OverlayStateData *osd = image_get_osd_data(imd);
170
171         if (!osd || !osd->histogram) return;
172
173         histogram_set_channel(osd->histogram, chan);
174         image_osd_update(imd);
175 }
176
177 void image_osd_histogram_set_mode(ImageWindow *imd, gint mode)
178 {
179         OverlayStateData *osd = image_get_osd_data(imd);
180
181         if (!osd || !osd->histogram) return;
182
183         histogram_set_mode(osd->histogram, mode);
184         image_osd_update(imd);
185 }
186
187 gint image_osd_histogram_get_channel(ImageWindow *imd)
188 {
189         OverlayStateData *osd = image_get_osd_data(imd);
190
191         if (!osd || !osd->histogram) return HCHAN_DEFAULT;
192
193         return histogram_get_channel(osd->histogram);
194 }
195
196 gint image_osd_histogram_get_mode(ImageWindow *imd)
197 {
198         OverlayStateData *osd = image_get_osd_data(imd);
199
200         if (!osd || !osd->histogram) return 0;
201
202         return histogram_get_mode(osd->histogram);
203 }
204
205 void image_osd_toggle(ImageWindow *imd)
206 {
207         OsdShowFlags show;
208
209         if (!imd) return;
210
211         show = image_osd_get(imd);
212         if (show == OSD_SHOW_NOTHING)
213                 {
214                 image_osd_set(imd, OSD_SHOW_INFO | OSD_SHOW_STATUS);
215                 return;
216                 }
217         else
218                 {
219                 if (show & OSD_SHOW_GUIDELINES)
220                         {
221                         image_osd_set(imd, OSD_SHOW_NOTHING);
222                         }
223                 else if (show & OSD_SHOW_HISTOGRAM)
224                         {
225                         image_osd_set(imd, OSD_SHOW_GUIDELINES);
226                         image_osd_set(imd, show | ~OSD_SHOW_HISTOGRAM);
227                         }
228                 else
229                         {
230                         image_osd_set(imd, show | OSD_SHOW_HISTOGRAM);
231                         }
232                 }
233 }
234
235 static gchar *keywords_to_string(FileData *fd)
236 {
237         GList *keywords;
238         GString *kwstr = NULL;
239         gchar *ret = NULL;
240
241         g_assert(fd);
242
243         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
244
245         if (keywords)
246                 {
247                 GList *work = keywords;
248
249                 while (work)
250                         {
251                         gchar *kw = work->data;
252                         work = work->next;
253
254                         if (!kw) continue;
255                         if (!kwstr)
256                                 kwstr = g_string_new("");
257                         else
258                                 g_string_append(kwstr, ", ");
259
260                         g_string_append(kwstr, kw);
261                         }
262                 string_list_free(keywords);
263                 }
264
265         if (kwstr)
266                 {
267                 ret = kwstr->str;
268                 g_string_free(kwstr, FALSE);
269                 }
270
271         return ret;
272 }
273
274 static gchar *image_osd_mkinfo(const gchar *str, ImageWindow *imd, GHashTable *vars)
275 {
276         gchar delim = '%', imp = '|', sep[] = " - ";
277         gchar *start, *end;
278         guint pos, prev;
279         gboolean want_separator = FALSE;
280         gchar *name, *data;
281         GString *new;
282         gchar *ret;
283
284         if (!str || !*str) return g_strdup("");
285
286         new = g_string_new(str);
287
288         prev = -1;
289
290         while (TRUE)
291                 {
292                 guint limit = 0;
293                 gchar *trunc = NULL;
294                 gchar *limpos = NULL;
295                 gchar *extra = NULL;
296                 gchar *extrapos = NULL;
297                 gchar *p;
298
299                 start = strchr(new->str + (prev + 1), delim);
300                 if (!start)
301                         break;
302                 end = strchr(start+1, delim);
303                 if (!end)
304                         break;
305
306                 /* Search for optionnal modifiers
307                  * %name:99:extra% -> name = "name", limit=99, extra = "extra"
308                  */
309                 for (p = start + 1; p < end; p++)
310                         {
311                         if (p[0] == ':')
312                                 {
313                                 if (g_ascii_isdigit(p[1]) && !limpos)
314                                         {
315                                         limpos = p + 1;
316                                         if (!trunc) trunc = p;
317                                         }
318                                 else
319                                         {
320                                         extrapos = p + 1;
321                                         if (!trunc) trunc = p;
322                                         break;
323                                         }
324                                 }
325                         }
326
327                 if (limpos)
328                         limit = (guint) atoi(limpos);
329
330                 if (extrapos)
331                         extra = g_strndup(extrapos, end - extrapos);
332
333                 name = g_strndup(start+1, (trunc ? trunc : end)-start-1);
334                 pos = start - new->str;
335                 data = NULL;
336
337                 if (strcmp(name, "keywords") == 0)
338                         {
339                         data = keywords_to_string(imd->image_fd);
340                         }
341                 else if (strcmp(name, "comment") == 0)
342                         {
343                         data = metadata_read_string(imd->image_fd, COMMENT_KEY, METADATA_PLAIN);
344                         }
345                 else if (strcmp(name, "imagecomment") == 0)
346                         {
347                         data = exif_get_image_comment(imd->image_fd);
348                         }
349                 else if (strcmp(name, "rating") == 0)
350                         {
351                         data = metadata_read_string(imd->image_fd, RATING_KEY, METADATA_PLAIN);
352                         }
353 #ifdef HAVE_LUA
354                 else if (strncmp(name, "lua/", 4) == 0)
355                         {
356                         gchar *tmp;
357                         tmp = strchr(name+4, '/');
358                         if (!tmp)
359                                 break;
360                         *tmp = '\0';
361                         data = lua_callvalue(imd->image_fd, name+4, tmp+1);
362                         }
363 #endif
364                 else
365                         {
366                         data = g_strdup(g_hash_table_lookup(vars, name));
367                         if (!data)
368                                 data = metadata_read_string(imd->image_fd, name, METADATA_FORMATTED);
369                         }
370
371                 if (data && *data && limit > 0 && strlen(data) > limit + 3)
372                         {
373                         gchar *new_data = g_strdup_printf("%-*.*s...", limit, limit, data);
374                         g_free(data);
375                         data = new_data;
376                         }
377
378                 if (data)
379                         {
380                         /* Since we use pango markup to display, we need to escape here */
381                         gchar *escaped = g_markup_escape_text(data, -1);
382                         g_free(data);
383                         data = escaped;
384                         }
385
386                 if (extra)
387                         {
388                         if (data && *data)
389                                 {
390                                 /* Display data between left and right parts of extra string
391                                  * the data is expressed by a '*' character. A '*' may be escaped
392                                  * by a \. You should escape all '*' characters, do not rely on the
393                                  * current implementation which only replaces the first unescaped '*'.
394                                  * If no "*" is present, the extra string is just appended to data string.
395                                  * Pango mark up is accepted in left and right parts.
396                                  * Any \n is replaced by a newline
397                                  * Examples:
398                                  * "<i>*</i>\n" -> data is displayed in italics ended with a newline
399                                  * "\n"         -> ended with newline
400                                  * "ISO *"      -> prefix data with "ISO " (ie. "ISO 100")
401                                  * "\**\*"      -> prefix data with a star, and append a star (ie. "*100*")
402                                  * "\\*"        -> prefix data with an anti slash (ie "\100")
403                                  * "Collection <b>*</b>\n" -> display data in bold prefixed by "Collection " and a newline is appended
404                                  *
405                                  * FIXME: using background / foreground colors lead to weird results.
406                                  */
407                                 gchar *new_data;
408                                 gchar *left = NULL;
409                                 gchar *right = extra;
410                                 gchar *p;
411                                 guint len = strlen(extra);
412
413                                 /* Search for left and right parts and unescape characters */
414                                 for (p = extra; *p; p++, len--)
415                                         if (p[0] == '\\')
416                                                 {
417                                                 if (p[1] == 'n')
418                                                         {
419                                                         memmove(p+1, p+2, --len);
420                                                         p[0] = '\n';
421                                                         }
422                                                 else if (p[1] != '\0')
423                                                         memmove(p, p+1, len--); // includes \0
424                                                 }
425                                         else if (p[0] == '*' && !left)
426                                                 {
427                                                 right = p + 1;
428                                                 left = extra;
429                                                 }
430
431                                 if (left) right[-1] = '\0';
432
433                                 new_data = g_strdup_printf("%s%s%s", left ? left : "", data, right);
434                                 g_free(data);
435                                 data = new_data;
436                                 }
437                         g_free(extra);
438                         }
439
440                 g_string_erase(new, pos, end-start+1);
441                 if (data && *data)
442                         {
443                         if (want_separator)
444                                 {
445                                 /* insert separator */
446                                 g_string_insert(new, pos, sep);
447                                 pos += strlen(sep);
448                                 want_separator = FALSE;
449                                 }
450
451                         g_string_insert(new, pos, data);
452                         pos += strlen(data);
453                 }
454
455                 if (pos-prev >= 1 && new->str[pos] == imp)
456                         {
457                         /* pipe character is replaced by a separator, delete it
458                          * and raise a flag if needed */
459                         g_string_erase(new, pos--, 1);
460                         want_separator |= (data && *data);
461                         }
462
463                 if (new->str[pos] == '\n') want_separator = FALSE;
464
465                 prev = pos - 1;
466
467                 g_free(name);
468                 g_free(data);
469                 }
470
471         /* search and destroy empty lines */
472         end = new->str;
473         while ((start = strchr(end, '\n')))
474                 {
475                 end = start;
476                 while (*++(end) == '\n')
477                         ;
478                 g_string_erase(new, start-new->str, end-start-1);
479                 }
480
481         g_strchomp(new->str);
482
483         ret = new->str;
484         g_string_free(new, FALSE);
485
486         return ret;
487 }
488
489 typedef enum {
490         OSDT_NONE       = 0,
491         OSDT_FREE       = 1 << 0,
492         OSDT_NO_DUP     = 1 << 1
493 } OsdTemplateFlags;
494
495 static void osd_template_insert(GHashTable *vars, gchar *keyword, gchar *value, OsdTemplateFlags flags)
496 {
497         if (!value)
498                 {
499                 g_hash_table_insert(vars, keyword, g_strdup(""));
500                 return;
501                 }
502
503         if (flags & OSDT_NO_DUP)
504                 {
505                 g_hash_table_insert(vars, keyword, value);
506                 return;
507                 }
508         else
509                 {
510                 g_hash_table_insert(vars, keyword, g_strdup(value));
511                 }
512
513         if (flags & OSDT_FREE) g_free((gpointer) value);
514 }
515
516 static GdkPixbuf *image_osd_info_render(OverlayStateData *osd)
517 {
518         GdkPixbuf *pixbuf = NULL;
519         gint width, height;
520         PangoLayout *layout;
521         const gchar *name;
522         gchar *text;
523         gboolean with_hist;
524         const HistMap *histmap = NULL;
525         ImageWindow *imd = osd->imd;
526         FileData *fd = image_get_fd(imd);
527         PangoFontDescription *font_desc;
528
529         if (!fd) return NULL;
530
531         name = image_get_name(imd);
532         if (name)
533                 {
534                 gint n, t;
535                 CollectionData *cd;
536                 CollectInfo *info;
537                 GHashTable *vars;
538
539                 vars = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
540
541                 cd = image_get_collection(imd, &info);
542                 if (cd)
543                         {
544                         t = g_list_length(cd->list);
545                         n = g_list_index(cd->list, info) + 1;
546                         if (cd->name)
547                                 {
548                                 if (file_extension_match(cd->name, GQ_COLLECTION_EXT))
549                                         osd_template_insert(vars, "collection", remove_extension_from_path(cd->name), OSDT_FREE);
550                                 else
551                                         osd_template_insert(vars, "collection", cd->name, OSDT_NONE);
552                                 }
553                         else
554                                 {
555                                 osd_template_insert(vars, "collection", _("Untitled"), OSDT_NONE);
556                                 }
557                         }
558                 else
559                         {
560                         LayoutWindow *lw = layout_find_by_image(imd);
561                         if (lw)
562                                 {
563                                 if (lw->slideshow)
564                                         {
565                                         n = g_list_length(lw->slideshow->list_done);
566                                         t = n + g_list_length(lw->slideshow->list);
567                                         if (n == 0) n = t;
568                                         }
569                                 else
570                                         {
571                                         t = layout_list_count(lw, NULL);
572                                         n = layout_list_get_index(lw, image_get_fd(lw->image)) + 1;
573                                         }
574                                 }
575                         else if (view_window_find_image(imd, &n, &t))
576                                 {
577                                 n++;
578                                 }
579                         else
580                                 {
581                                 t = 1;
582                                 n = 1;
583                                 }
584
585                         if (n < 1) n = 1;
586                         if (t < 1) t = 1;
587
588                         osd_template_insert(vars, "collection", NULL, OSDT_NONE);
589                         }
590
591                 osd_template_insert(vars, "number", g_strdup_printf("%d", n), OSDT_NO_DUP);
592                 osd_template_insert(vars, "total", g_strdup_printf("%d", t), OSDT_NO_DUP);
593                 osd_template_insert(vars, "name", (gchar *) name, OSDT_NONE);
594                 osd_template_insert(vars, "date", imd->image_fd ? ((gchar *) text_from_time(imd->image_fd->date)) : "", OSDT_NONE);
595                 osd_template_insert(vars, "size", imd->image_fd ? (text_from_size_abrev(imd->image_fd->size)) : g_strdup(""), OSDT_FREE);
596                 osd_template_insert(vars, "zoom", image_zoom_get_as_text(imd), OSDT_FREE);
597
598                 if (!imd->unknown)
599                         {
600                         gint w, h;
601                         GdkPixbuf *load_pixbuf = image_loader_get_pixbuf(imd->il);
602
603                         if (imd->delay_flip &&
604                             imd->il && load_pixbuf &&
605                             image_get_pixbuf(imd) != load_pixbuf)
606                                 {
607                                 w = gdk_pixbuf_get_width(load_pixbuf);
608                                 h = gdk_pixbuf_get_height(load_pixbuf);
609                                 }
610                         else
611                                 {
612                                 image_get_image_size(imd, &w, &h);
613                                 }
614
615
616                         osd_template_insert(vars, "width", g_strdup_printf("%d", w), OSDT_NO_DUP);
617                         osd_template_insert(vars, "height", g_strdup_printf("%d", h), OSDT_NO_DUP);
618                         osd_template_insert(vars, "res", g_strdup_printf("%d Ã— %d", w, h), OSDT_FREE);
619                         }
620                 else
621                         {
622                         osd_template_insert(vars, "width", NULL, OSDT_NONE);
623                         osd_template_insert(vars, "height", NULL, OSDT_NONE);
624                         osd_template_insert(vars, "res", NULL, OSDT_NONE);
625                         }
626
627                 text = image_osd_mkinfo(options->image_overlay.template_string, imd, vars);
628                 g_hash_table_destroy(vars);
629
630         } else {
631                 /* When does this occur ?? */
632                 text = g_markup_escape_text(_("Untitled"), -1);
633         }
634
635         with_hist = ((osd->show & OSD_SHOW_HISTOGRAM) && osd->histogram);
636         if (with_hist)
637                 {
638                 histmap = histmap_get(imd->image_fd);
639                 if (!histmap)
640                         {
641                         histmap_start_idle(imd->image_fd);
642                         with_hist = FALSE;
643                         }
644                 }
645
646
647         {
648                 gint active_marks = 0;
649                 gint mark;
650                 gchar *text2;
651
652                 for (mark = 0; mark < FILEDATA_MARKS_SIZE; mark++)
653                         {
654                         active_marks += file_data_get_mark(fd, mark);
655                         }
656
657                 if (active_marks > 0)
658                         {
659                         GString *buf = g_string_sized_new(FILEDATA_MARKS_SIZE * 2);
660
661                         for (mark = 0; mark < FILEDATA_MARKS_SIZE; mark++)
662                                 {
663                                 g_string_append_printf(buf, file_data_get_mark(fd, mark) ? " <span background='#FF00FF'>%c</span>" : " %c", '1' + (mark < 9 ? mark : -1) );
664                                 }
665
666                         if (*text)
667                                 text2 = g_strdup_printf("%s\n%s", text, buf->str);
668                         else
669                                 text2 = g_strdup(buf->str);
670                         g_string_free(buf, TRUE);
671                         g_free(text);
672                         text = text2;
673                         }
674
675                 if (with_hist)
676                         {
677                         gchar *escaped_histogram_label = g_markup_escape_text(histogram_label(osd->histogram), -1);
678                         if (*text)
679                                 text2 = g_strdup_printf("%s\n%s", text, escaped_histogram_label);
680                         else
681                                 text2 = g_strdup(escaped_histogram_label);
682                         g_free(escaped_histogram_label);
683                         g_free(text);
684                         text = text2;
685                         }
686         }
687
688         font_desc = pango_font_description_from_string(options->image_overlay.font);
689         layout = gtk_widget_create_pango_layout(imd->pr, NULL);
690         pango_layout_set_font_description(layout, font_desc);
691
692         pango_layout_set_markup(layout, text, -1);
693         g_free(text);
694
695         pango_layout_get_pixel_size(layout, &width, &height);
696         /* with empty text width is set to 0, but not height) */
697         if (width == 0)
698                 height = 0;
699         else if (height == 0)
700                 width = 0;
701         if (width > 0) width += 10;
702         if (height > 0) height += 10;
703
704         if (with_hist)
705                 {
706                 if (width < HISTOGRAM_WIDTH + 10) width = HISTOGRAM_WIDTH + 10;
707                 height += HISTOGRAM_HEIGHT + 5;
708                 }
709
710         if (width > 0 && height > 0)
711                 {
712                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
713                 pixbuf_set_rect_fill(pixbuf, 3, 3, width-6, height-6, options->image_overlay.background_red, options->image_overlay.background_green,
714                                                                                                                         options->image_overlay.background_blue, options->image_overlay.background_alpha);
715                 pixbuf_set_rect(pixbuf, 0, 0, width, height, 240, 240, 240, 80, 1, 1, 1, 1);
716                 pixbuf_set_rect(pixbuf, 1, 1, width-2, height-2, 240, 240, 240, 130, 1, 1, 1, 1);
717                 pixbuf_set_rect(pixbuf, 2, 2, width-4, height-4, 240, 240, 240, 180, 1, 1, 1, 1);
718                 pixbuf_pixel_set(pixbuf, 0, 0, 0, 0, 0, 0);
719                 pixbuf_pixel_set(pixbuf, width - 1, 0, 0, 0, 0, 0);
720                 pixbuf_pixel_set(pixbuf, 0, height - 1, 0, 0, 0, 0);
721                 pixbuf_pixel_set(pixbuf, width - 1, height - 1, 0, 0, 0, 0);
722
723                 if (with_hist)
724                         {
725                         gint x = 5;
726                         gint y = height - HISTOGRAM_HEIGHT - 5;
727                         gint w = width - 10;
728
729                         pixbuf_set_rect_fill(pixbuf, x, y, w, HISTOGRAM_HEIGHT, 220, 220, 220, 210);
730                         histogram_draw(osd->histogram, histmap, pixbuf, x, y, w, HISTOGRAM_HEIGHT);
731                         }
732                 pixbuf_draw_layout(pixbuf, layout, imd->pr, 5, 5, options->image_overlay.text_red, options->image_overlay.text_green,
733                                                                                                                         options->image_overlay.text_blue, options->image_overlay.text_alpha);
734         }
735
736         g_object_unref(G_OBJECT(layout));
737
738         return pixbuf;
739 }
740
741 static GdkPixbuf *image_osd_icon_pixbuf(ImageOSDFlag flag)
742 {
743         static GdkPixbuf **icons = NULL;
744         GdkPixbuf *icon = NULL;
745
746         if (!icons) icons = g_new0(GdkPixbuf *, IMAGE_OSD_COUNT);
747         if (icons[flag]) return icons[flag];
748
749         if (osd_icons[flag].key)
750                 {
751                 icon = pixbuf_inline(osd_icons[flag].key);
752                 }
753
754         if (!icon)
755                 {
756                 icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 24, 24);
757                 pixbuf_set_rect_fill(icon, 1, 1, 22, 22, 255, 255, 255, 200);
758                 pixbuf_set_rect(icon, 0, 0, 24, 24, 0, 0, 0, 128, 1, 1, 1, 1);
759                 switch (flag)
760                         {
761                         case IMAGE_OSD_ROTATE_AUTO:
762                                 pixbuf_set_rect(icon, 3, 8, 11, 12,
763                                                 0, 0, 0, 255,
764                                                 3, 0, 3, 0);
765                                 pixbuf_draw_triangle(icon, 14, 3, 6, 12,
766                                                      20, 9, 14, 15, 14, 3,
767                                                      0, 0, 0, 255);
768                                 break;
769                         case IMAGE_OSD_ROTATE_USER:
770                                 break;
771                         case IMAGE_OSD_COLOR:
772                                 pixbuf_set_rect_fill(icon, 3, 3, 18, 6, 200, 0, 0, 255);
773                                 pixbuf_set_rect_fill(icon, 3, 9, 18, 6, 0, 200, 0, 255);
774                                 pixbuf_set_rect_fill(icon, 3, 15, 18, 6, 0, 0, 200, 255);
775                                 break;
776                         case IMAGE_OSD_FIRST:
777                                 pixbuf_set_rect(icon, 3, 3, 18, 18, 0, 0, 0, 200, 3, 3, 3, 0);
778                                 pixbuf_draw_triangle(icon, 6, 5, 12, 6,
779                                                      12, 5, 18, 11, 6, 11,
780                                                      0, 0, 0, 255);
781                                 break;
782                         case IMAGE_OSD_LAST:
783                                 pixbuf_set_rect(icon, 3, 3, 18, 18, 0, 0, 0, 200, 3, 3, 0, 3);
784                                 pixbuf_draw_triangle(icon, 6, 12, 12, 6,
785                                                      12, 18, 6, 12, 18, 12,
786                                                      0, 0, 0, 255);
787                                 break;
788                         case IMAGE_OSD_ICON:
789                                 pixbuf_set_rect_fill(icon, 11, 3, 3, 12, 0, 0, 0, 255);
790                                 pixbuf_set_rect_fill(icon, 11, 17, 3, 3, 0, 0, 0, 255);
791                                 break;
792                         default:
793                                 break;
794                         }
795                 }
796
797         icons[flag] = icon;
798
799         return icon;
800 }
801
802 static GdkPixbuf *image_osd_guidelines_render(OverlayStateData *osd)
803 {
804         gint width, height;
805         GdkPixbuf *rectangles;
806         ImageWindow *imd = osd->imd;
807
808         pixbuf_renderer_get_scaled_size((PixbufRenderer *)imd->pr, &width, &height);
809
810         if (width && height)
811                 {
812                 rectangles = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
813                 if (rectangles)
814                         {
815                         pixbuf_set_rect_fill(rectangles, 0, 0, width, height, 255, 255, 255, 0);
816                         pixbuf_set_rect(rectangles, 0, 0 + (height / 3), width, height / 3,
817                                                                 0, 0, 0, 255,
818                                                                 1, 1, 1, 1);
819                         pixbuf_set_rect(rectangles, 0, 0 + (height / 3 + 1), width, height / 3 - 2,
820                                                                 255, 255, 255, 255,
821                                                                 1, 1, 1, 1);
822
823                         pixbuf_set_rect(rectangles, 0 + width / 3, 0 , width / 3, height,
824                                                                 0, 0, 0, 255,
825                                                                 1, 1, 1, 1);
826                         pixbuf_set_rect(rectangles, 0 + width / 3 + 1, 0, width / 3 - 2, height,
827                                                                 255, 255, 255, 255,
828                                                                 1, 1, 1, 1);
829                         return rectangles;
830                         }
831                 }
832
833         return NULL;
834 }
835
836 static gint image_overlay_add(ImageWindow *imd, GdkPixbuf *pixbuf, gint x, gint y,
837                               OverlayRendererFlags flags)
838 {
839         return pixbuf_renderer_overlay_add((PixbufRenderer *)imd->pr, pixbuf, x, y, flags);
840 }
841
842 static void image_overlay_set(ImageWindow *imd, gint id, GdkPixbuf *pixbuf, gint x, gint y)
843 {
844         pixbuf_renderer_overlay_set((PixbufRenderer *)imd->pr, id, pixbuf, x, y);
845 }
846
847 static void image_overlay_remove(ImageWindow *imd, gint id)
848 {
849         pixbuf_renderer_overlay_remove((PixbufRenderer *)imd->pr, id);
850 }
851
852 static void image_osd_icon_show(OverlayStateData *osd, ImageOSDFlag flag)
853 {
854         GdkPixbuf *pixbuf;
855
856         if (osd->icon_id[flag]) return;
857
858         pixbuf = image_osd_icon_pixbuf(flag);
859         if (!pixbuf) return;
860
861         osd->icon_id[flag] = image_overlay_add(osd->imd, pixbuf,
862                                                osd_icons[flag].x, osd_icons[flag].y,
863                                                OVL_RELATIVE);
864 }
865
866 static void image_osd_icon_hide(OverlayStateData *osd, ImageOSDFlag flag)
867 {
868         if (osd->icon_id[flag])
869                 {
870                 image_overlay_remove(osd->imd, osd->icon_id[flag]);
871                 osd->icon_id[flag] = 0;
872                 }
873 }
874
875 static void image_osd_icons_reset_time(OverlayStateData *osd)
876 {
877         gint i;
878
879         for (i = 0; i < IMAGE_OSD_COUNT; i++)
880                 {
881                 if (osd_icons[i].reset)
882                         {
883                         osd->icon_time[i] = 0;
884                         }
885                 }
886 }
887
888 static void image_osd_icons_update(OverlayStateData *osd)
889 {
890         gint i;
891
892         for (i = 0; i < IMAGE_OSD_COUNT; i++)
893                 {
894                 if (osd->icon_time[i] > 0)
895                         {
896                         image_osd_icon_show(osd, i);
897                         }
898                 else
899                         {
900                         image_osd_icon_hide(osd, i);
901                         }
902                 }
903 }
904
905 static void image_osd_icons_hide(OverlayStateData *osd)
906 {
907         gint i;
908
909         for (i = 0; i < IMAGE_OSD_COUNT; i++)
910                 {
911                 image_osd_icon_hide(osd, i);
912                 }
913 }
914
915 static void image_osd_info_show(OverlayStateData *osd, GdkPixbuf *pixbuf)
916 {
917         if (osd->ovl_info == 0)
918                 {
919                 osd->ovl_info = image_overlay_add(osd->imd, pixbuf, osd->x, osd->y, osd->origin);
920                 }
921         else
922                 {
923                 image_overlay_set(osd->imd, osd->ovl_info, pixbuf, osd->x, osd->y);
924                 }
925 }
926
927 static void image_osd_info_hide(OverlayStateData *osd)
928 {
929         if (osd->ovl_info == 0) return;
930
931         image_overlay_remove(osd->imd, osd->ovl_info);
932         osd->ovl_info = 0;
933 }
934
935 static gboolean image_osd_update_cb(gpointer data)
936 {
937         OverlayStateData *osd = data;
938
939         if (osd->show & OSD_SHOW_INFO)
940                 {
941                 /* redraw when the image was changed,
942                    with histogram we have to redraw also when loading is finished */
943                 if (osd->changed_states & IMAGE_STATE_IMAGE ||
944                     (osd->changed_states & IMAGE_STATE_LOADING && osd->show & OSD_SHOW_HISTOGRAM) ||
945                     (osd->changed_states & IMAGE_STATE_LOADING && osd->show & OSD_SHOW_GUIDELINES) ||
946                     osd->notify & NOTIFY_HISTMAP)
947                         {
948                         GdkPixbuf *pixbuf;
949
950                         if (osd->show & OSD_SHOW_GUIDELINES)
951                                 {
952                                 ImageWindow *imd = osd->imd;
953                                 osd->x = ((PixbufRenderer *)imd->pr)->x_offset;
954                                 osd->y = ((PixbufRenderer *)imd->pr)->y_offset;
955                                 osd->origin = OVL_NORMAL;
956
957                                 pixbuf = image_osd_guidelines_render(osd);
958                                 if (pixbuf)
959                                         {
960                                         image_osd_info_show(osd, pixbuf);
961                                         g_object_unref(pixbuf);
962                                         }
963
964                                 osd->x = options->image_overlay.x;
965                                 osd->y = options->image_overlay.y;
966                                 osd->origin = OVL_RELATIVE;
967                                 }
968                         else
969                                 {
970                                 pixbuf = image_osd_info_render(osd);
971                                 if (pixbuf)
972                                         {
973                                         image_osd_info_show(osd, pixbuf);
974                                         g_object_unref(pixbuf);
975                                         }
976                                 else
977                                         {
978                                         image_osd_info_hide(osd);
979                                         }
980                                 }
981                         }
982                 }
983         else
984                 {
985                 image_osd_info_hide(osd);
986                 }
987
988         if (osd->show & OSD_SHOW_STATUS)
989                 {
990                 if (osd->changed_states & IMAGE_STATE_IMAGE)
991                         image_osd_icons_reset_time(osd);
992
993                 if (osd->changed_states & IMAGE_STATE_COLOR_ADJ)
994                         {
995                         osd->icon_time[IMAGE_OSD_COLOR] = IMAGE_OSD_DEFAULT_DURATION + 1;
996                         image_osd_timer_schedule(osd);
997                         }
998
999                 if (osd->changed_states & IMAGE_STATE_ROTATE_AUTO)
1000                         {
1001                         gint n = 0;
1002
1003                         if (osd->imd->state & IMAGE_STATE_ROTATE_AUTO)
1004                                 {
1005                                 n = 1;
1006                                 if (!osd->imd->cm) n += IMAGE_OSD_DEFAULT_DURATION;
1007                                 }
1008
1009                         osd->icon_time[IMAGE_OSD_ROTATE_AUTO] = n;
1010                         image_osd_timer_schedule(osd);
1011                         }
1012
1013                 image_osd_icons_update(osd);
1014                 }
1015         else
1016                 {
1017                 image_osd_icons_hide(osd);
1018                 }
1019
1020         osd->changed_states = IMAGE_STATE_NONE;
1021         osd->notify = 0;
1022         osd->idle_id = 0;
1023         return FALSE;
1024 }
1025
1026 static void image_osd_update_schedule(OverlayStateData *osd, gboolean force)
1027 {
1028         if (force) osd->changed_states |= IMAGE_STATE_IMAGE;
1029
1030         if (!osd->idle_id)
1031                 {
1032                 osd->idle_id = g_idle_add_full(G_PRIORITY_HIGH, image_osd_update_cb, osd, NULL);
1033                 }
1034 }
1035
1036 void image_osd_update(ImageWindow *imd)
1037 {
1038         OverlayStateData *osd = image_get_osd_data(imd);
1039
1040         if (!osd) return;
1041
1042         image_osd_update_schedule(osd, TRUE);
1043 }
1044
1045 static gboolean image_osd_timer_cb(gpointer data)
1046 {
1047         OverlayStateData *osd = data;
1048         gboolean done = TRUE;
1049         gboolean changed = FALSE;
1050         gint i;
1051
1052         for (i = 0; i < IMAGE_OSD_COUNT; i++)
1053                 {
1054                 if (osd->icon_time[i] > 1)
1055                         {
1056                         osd->icon_time[i]--;
1057                         if (osd->icon_time[i] < 2)
1058                                 {
1059                                 osd->icon_time[i] = 0;
1060                                 changed = TRUE;
1061                                 }
1062                         else
1063                                 {
1064                                 done = FALSE;
1065                                 }
1066                         }
1067                 }
1068
1069         if (changed) image_osd_update_schedule(osd, FALSE);
1070
1071         if (done)
1072                 {
1073                 osd->timer_id = 0;
1074                 return FALSE;
1075                 }
1076
1077         return TRUE;
1078 }
1079
1080 static void image_osd_timer_schedule(OverlayStateData *osd)
1081 {
1082         if (!osd->timer_id)
1083                 {
1084                 osd->timer_id = g_timeout_add(100, image_osd_timer_cb, osd);
1085                 }
1086 }
1087
1088 static void image_osd_state_cb(ImageWindow *imd, ImageState state, gpointer data)
1089 {
1090         OverlayStateData *osd = data;
1091
1092         osd->changed_states |= state;
1093         image_osd_update_schedule(osd, FALSE);
1094 }
1095
1096 static void image_osd_notify_cb(FileData *fd, NotifyType type, gpointer data)
1097 {
1098         OverlayStateData *osd = data;
1099
1100         if ((type & (NOTIFY_HISTMAP)) && osd->imd && fd == osd->imd->image_fd)
1101                 {
1102                 DEBUG_1("Notify osd: %s %04x", fd->path, type);
1103                 osd->notify |= type;
1104                 image_osd_update_schedule(osd, FALSE);
1105                 }
1106 }
1107
1108
1109 static void image_osd_free(OverlayStateData *osd)
1110 {
1111         if (!osd) return;
1112
1113         if (osd->idle_id) g_source_remove(osd->idle_id);
1114         if (osd->timer_id) g_source_remove(osd->timer_id);
1115
1116         file_data_unregister_notify_func(image_osd_notify_cb, osd);
1117
1118         if (osd->imd)
1119                 {
1120                 image_set_osd_data(osd->imd, NULL);
1121                 g_signal_handler_disconnect(osd->imd->pr, osd->destroy_id);
1122
1123                 image_set_state_func(osd->imd, NULL, NULL);
1124
1125                 image_osd_info_hide(osd);
1126                 image_osd_icons_hide(osd);
1127                 }
1128
1129         if (osd->histogram) histogram_free(osd->histogram);
1130
1131         g_free(osd);
1132 }
1133
1134 static void image_osd_destroy_cb(GtkWidget *widget, gpointer data)
1135 {
1136         OverlayStateData *osd = data;
1137
1138         osd->imd = NULL;
1139         image_osd_free(osd);
1140 }
1141
1142 static void image_osd_enable(ImageWindow *imd, OsdShowFlags show)
1143 {
1144         OverlayStateData *osd = image_get_osd_data(imd);
1145
1146         if (!osd)
1147                 {
1148                 osd = g_new0(OverlayStateData, 1);
1149                 osd->imd = imd;
1150                 osd->show = OSD_SHOW_NOTHING;
1151                 osd->x = options->image_overlay.x;
1152                 osd->y = options->image_overlay.y;
1153                 osd->origin = OVL_RELATIVE;
1154
1155                 osd->histogram = histogram_new();
1156
1157                 osd->destroy_id = g_signal_connect(G_OBJECT(imd->pr), "destroy",
1158                                                    G_CALLBACK(image_osd_destroy_cb), osd);
1159                 image_set_osd_data(imd, osd);
1160
1161                 image_set_state_func(osd->imd, image_osd_state_cb, osd);
1162                 file_data_register_notify_func(image_osd_notify_cb, osd, NOTIFY_PRIORITY_LOW);
1163                 }
1164
1165         if (show & OSD_SHOW_STATUS)
1166                 image_osd_icon(imd, IMAGE_OSD_ICON, -1);
1167
1168         if (show != osd->show)
1169                 image_osd_update_schedule(osd, TRUE);
1170
1171         osd->show = show;
1172 }
1173
1174 void image_osd_set(ImageWindow *imd, OsdShowFlags show)
1175 {
1176         if (!imd) return;
1177
1178         image_osd_enable(imd, show);
1179 }
1180
1181 OsdShowFlags image_osd_get(ImageWindow *imd)
1182 {
1183         OverlayStateData *osd = image_get_osd_data(imd);
1184
1185         return osd ? osd->show : OSD_SHOW_NOTHING;
1186 }
1187
1188 Histogram *image_osd_get_histogram(ImageWindow *imd)
1189 {
1190         OverlayStateData *osd = image_get_osd_data(imd);
1191
1192         return osd ? osd->histogram : NULL;
1193 }
1194
1195 void image_osd_copy_status(ImageWindow *src, ImageWindow *dest)
1196 {
1197         Histogram *h_src, *h_dest;
1198         image_osd_set(dest, image_osd_get(src));
1199
1200         h_src = image_osd_get_histogram(src);
1201         h_dest = image_osd_get_histogram(dest);
1202
1203         h_dest->histogram_mode = h_src->histogram_mode;
1204         h_dest->histogram_channel = h_src->histogram_channel;
1205
1206 }
1207
1208 /* duration:
1209     0 = hide
1210     1 = show
1211    2+ = show for duration tenths of a second
1212    -1 = use default duration
1213  */
1214 void image_osd_icon(ImageWindow *imd, ImageOSDFlag flag, gint duration)
1215 {
1216         OverlayStateData *osd = image_get_osd_data(imd);
1217
1218         if (!osd) return;
1219
1220         if (flag >= IMAGE_OSD_COUNT) return;
1221         if (duration < 0) duration = IMAGE_OSD_DEFAULT_DURATION;
1222         if (duration > 1) duration += 1;
1223
1224         osd->icon_time[flag] = duration;
1225
1226         image_osd_update_schedule(osd, FALSE);
1227         image_osd_timer_schedule(osd);
1228 }
1229 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */