Fix deprecation warning for poppler >= 0.82
[geeqie.git] / src / image-overlay.cc
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 "image-overlay.h"
23
24 #include <cstring>
25
26 #include <gdk-pixbuf/gdk-pixbuf.h>
27 #include <glib-object.h>
28 #include <gtk/gtk.h>
29 #include <pango/pango.h>
30
31 #include "collect.h"
32 #include "debug.h"
33 #include "filedata.h"
34 #include "histogram.h"
35 #include "image-load.h"
36 #include "image.h"
37 #include "img-view.h"
38 #include "intl.h"
39 #include "layout.h"
40 #include "main-defines.h"
41 #include "options.h"
42 #include "osd.h"
43 #include "pixbuf-renderer.h"
44 #include "pixbuf-util.h"
45 #include "slideshow.h"
46 #include "typedefs.h"
47 #include "ui-fileops.h"
48
49 struct HistMap;
50
51 /*
52  *----------------------------------------------------------------------------
53  * image overlay
54  *----------------------------------------------------------------------------
55  */
56
57
58 struct OverlayStateData {
59         ImageWindow *imd;
60         ImageState changed_states;
61         NotifyType notify;
62
63         Histogram *histogram;
64
65         OsdShowFlags show;
66         OverlayRendererFlags origin;
67
68         gint ovl_info;
69
70         gint x;
71         gint y;
72
73         gint icon_time[IMAGE_OSD_COUNT];
74         gint icon_id[IMAGE_OSD_COUNT];
75
76         guint idle_id; /* event source id */
77         guint timer_id; /* event source id */
78         gulong destroy_id;
79 };
80
81
82 struct OSDIcon {
83         gboolean reset; /* reset on new image */
84         gint x;         /* x, y offset */
85         gint y;
86         gchar *key;     /* inline pixbuf */
87 };
88
89 static OSDIcon osd_icons[] = {
90         {  TRUE,   0,   0, nullptr },                   /* none */
91         {  TRUE, -10, -10, nullptr },                   /* auto rotated */
92         {  TRUE, -10, -10, nullptr },                   /* user rotated */
93         {  TRUE, -40, -10, nullptr },                   /* color embedded */
94         {  TRUE, -70, -10, nullptr },                   /* first image */
95         {  TRUE, -70, -10, nullptr },                   /* last image */
96         { FALSE, -70, -10, nullptr },                   /* osd enabled */
97         { FALSE, 0, 0, nullptr }
98 };
99
100 #define OSD_DATA "overlay-data"
101
102 enum {
103         IMAGE_OSD_DEFAULT_DURATION = 30
104 };
105
106 enum {
107         HISTOGRAM_HEIGHT = 140,
108         HISTOGRAM_WIDTH =  256
109 };
110
111 static void image_osd_timer_schedule(OverlayStateData *osd);
112
113 void set_image_overlay_template_string(gchar **template_string, const gchar *value)
114 {
115         g_assert(template_string);
116
117         g_free(*template_string);
118         *template_string = g_strdup(value);
119 }
120
121
122 void set_default_image_overlay_template_string(gchar **template_string)
123 {
124         set_image_overlay_template_string(template_string, DEFAULT_OVERLAY_INFO);
125 }
126
127 void set_image_overlay_font_string(gchar **font_string, const gchar *value)
128 {
129         g_assert(font_string);
130
131         g_free(*font_string);
132         *font_string = g_strdup(value);
133 }
134
135 static OverlayStateData *image_get_osd_data(ImageWindow *imd)
136 {
137         OverlayStateData *osd;
138
139         if (!imd) return nullptr;
140
141         g_assert(imd->pr);
142
143         osd = static_cast<OverlayStateData *>(g_object_get_data(G_OBJECT(imd->pr), "IMAGE_OVERLAY_DATA"));
144         return osd;
145 }
146
147 static void image_set_osd_data(ImageWindow *imd, OverlayStateData *osd)
148 {
149         g_assert(imd);
150         g_assert(imd->pr);
151         g_object_set_data(G_OBJECT(imd->pr), "IMAGE_OVERLAY_DATA", osd);
152 }
153
154 /*
155  *----------------------------------------------------------------------------
156  * image histogram
157  *----------------------------------------------------------------------------
158  */
159
160
161 void image_osd_histogram_toggle_channel(ImageWindow *imd)
162 {
163         OverlayStateData *osd = image_get_osd_data(imd);
164
165         if (!osd || !osd->histogram) return;
166
167         histogram_toggle_channel(osd->histogram);
168         image_osd_update(imd);
169 }
170
171 void image_osd_histogram_toggle_mode(ImageWindow *imd)
172 {
173         OverlayStateData *osd = image_get_osd_data(imd);
174
175         if (!osd || !osd->histogram) return;
176
177         histogram_toggle_mode(osd->histogram);
178         image_osd_update(imd);
179 }
180
181 void image_osd_histogram_set_channel(ImageWindow *imd, gint chan)
182 {
183         OverlayStateData *osd = image_get_osd_data(imd);
184
185         if (!osd || !osd->histogram) return;
186
187         histogram_set_channel(osd->histogram, chan);
188         image_osd_update(imd);
189 }
190
191 void image_osd_histogram_set_mode(ImageWindow *imd, gint mode)
192 {
193         OverlayStateData *osd = image_get_osd_data(imd);
194
195         if (!osd || !osd->histogram) return;
196
197         histogram_set_mode(osd->histogram, mode);
198         image_osd_update(imd);
199 }
200
201 gint image_osd_histogram_get_channel(ImageWindow *imd)
202 {
203         OverlayStateData *osd = image_get_osd_data(imd);
204
205         if (!osd || !osd->histogram) return HCHAN_DEFAULT;
206
207         return histogram_get_channel(osd->histogram);
208 }
209
210 gint image_osd_histogram_get_mode(ImageWindow *imd)
211 {
212         OverlayStateData *osd = image_get_osd_data(imd);
213
214         if (!osd || !osd->histogram) return 0;
215
216         return histogram_get_mode(osd->histogram);
217 }
218
219 void image_osd_toggle(ImageWindow *imd)
220 {
221         OsdShowFlags show;
222         if (!imd) return;
223
224         show = image_osd_get(imd);
225         if (show == OSD_SHOW_NOTHING)
226                 {
227                 image_osd_set(imd, static_cast<OsdShowFlags>(OSD_SHOW_INFO | OSD_SHOW_STATUS));
228                 return;
229                 }
230
231         if (show & OSD_SHOW_HISTOGRAM)
232                 {
233                 image_osd_set(imd, OSD_SHOW_NOTHING);
234                 }
235         else
236                 {
237                 image_osd_set(imd, static_cast<OsdShowFlags>(show | OSD_SHOW_HISTOGRAM));
238                 }
239 }
240
241 static GdkPixbuf *image_osd_info_render(OverlayStateData *osd)
242 {
243         GdkPixbuf *pixbuf = nullptr;
244         gint width;
245         gint height;
246         PangoLayout *layout;
247         const gchar *name;
248         gchar *text;
249         gboolean with_hist;
250         const HistMap *histmap = nullptr;
251         ImageWindow *imd = osd->imd;
252         FileData *fd = image_get_fd(imd);
253         PangoFontDescription *font_desc;
254
255         if (!fd) return nullptr;
256
257         name = image_get_name(imd);
258         if (name)
259                 {
260                 gint n;
261                 gint t;
262                 CollectionData *cd;
263                 CollectInfo *info;
264                 GHashTable *vars;
265
266                 vars = g_hash_table_new_full(g_str_hash, g_str_equal, nullptr, g_free);
267
268                 cd = image_get_collection(imd, &info);
269                 if (cd)
270                         {
271                         t = g_list_length(cd->list);
272                         n = g_list_index(cd->list, info) + 1;
273                         if (cd->name)
274                                 {
275                                 if (file_extension_match(cd->name, GQ_COLLECTION_EXT))
276                                         osd_template_insert(vars, "collection", remove_extension_from_path(cd->name), OSDT_FREE);
277                                 else
278                                         osd_template_insert(vars, "collection", cd->name, OSDT_NONE);
279                                 }
280                         else
281                                 {
282                                 osd_template_insert(vars, "collection", _("Untitled"), OSDT_NONE);
283                                 }
284                         }
285                 else
286                         {
287                         LayoutWindow *lw = layout_find_by_image(imd);
288                         if (lw)
289                                 {
290                                 if (lw->slideshow)
291                                         {
292                                         n = g_list_length(lw->slideshow->list_done);
293                                         t = n + g_list_length(lw->slideshow->list);
294                                         if (n == 0) n = t;
295                                         }
296                                 else
297                                         {
298                                         t = layout_list_count(lw, nullptr);
299                                         n = layout_list_get_index(lw, image_get_fd(lw->image)) + 1;
300                                         }
301                                 }
302                         else if (view_window_find_image(imd, &n, &t))
303                                 {
304                                 n++;
305                                 }
306                         else
307                                 {
308                                 t = 1;
309                                 n = 1;
310                                 }
311
312                         if (n < 1) n = 1;
313                         if (t < 1) t = 1;
314
315                         osd_template_insert(vars, "collection", nullptr, OSDT_NONE);
316                         }
317
318                 osd_template_insert(vars, "number", g_strdup_printf("%d", n), OSDT_NO_DUP);
319                 osd_template_insert(vars, "total", g_strdup_printf("%d", t), OSDT_NO_DUP);
320                 osd_template_insert(vars, "name", const_cast<gchar *>(name), OSDT_NONE);
321                 osd_template_insert(vars, "path", const_cast<gchar *>(image_get_path(imd)), OSDT_NONE);
322                 osd_template_insert(vars, "date", imd->image_fd ? (const_cast<gchar *>(text_from_time(imd->image_fd->date))) : "", OSDT_NONE);
323                 osd_template_insert(vars, "size", imd->image_fd ? (text_from_size_abrev(imd->image_fd->size)) : g_strdup(""), OSDT_FREE);
324                 osd_template_insert(vars, "zoom", image_zoom_get_as_text(imd), OSDT_FREE);
325
326                 if (!imd->unknown)
327                         {
328                         gint w;
329                         gint h;
330                         GdkPixbuf *load_pixbuf = image_loader_get_pixbuf(imd->il);
331
332                         if (imd->delay_flip &&
333                             imd->il && load_pixbuf &&
334                             image_get_pixbuf(imd) != load_pixbuf)
335                                 {
336                                 w = gdk_pixbuf_get_width(load_pixbuf);
337                                 h = gdk_pixbuf_get_height(load_pixbuf);
338                                 }
339                         else
340                                 {
341                                 image_get_image_size(imd, &w, &h);
342                                 }
343
344
345                         osd_template_insert(vars, "width", g_strdup_printf("%d", w), OSDT_NO_DUP);
346                         osd_template_insert(vars, "height", g_strdup_printf("%d", h), OSDT_NO_DUP);
347                         osd_template_insert(vars, "res", g_strdup_printf("%d Ã— %d", w, h), OSDT_FREE);
348                         }
349                 else
350                         {
351                         osd_template_insert(vars, "width", nullptr, OSDT_NONE);
352                         osd_template_insert(vars, "height", nullptr, OSDT_NONE);
353                         osd_template_insert(vars, "res", nullptr, OSDT_NONE);
354                         }
355
356                 text = image_osd_mkinfo(options->image_overlay.template_string, imd->image_fd, vars);
357                 g_hash_table_destroy(vars);
358
359         } else {
360                 /* When does this occur ?? */
361                 text = g_markup_escape_text(_("Untitled"), -1);
362         }
363
364         with_hist = ((osd->show & OSD_SHOW_HISTOGRAM) && osd->histogram);
365         if (with_hist)
366                 {
367                 histmap = histmap_get(imd->image_fd);
368                 if (!histmap)
369                         {
370                         histmap_start_idle(imd->image_fd);
371                         with_hist = FALSE;
372                         }
373                 }
374
375
376         {
377                 gint active_marks = 0;
378                 gint mark;
379                 gchar *text2;
380
381                 for (mark = 0; mark < FILEDATA_MARKS_SIZE; mark++)
382                         {
383                         active_marks += file_data_get_mark(fd, mark);
384                         }
385
386                 if (active_marks > 0)
387                         {
388                         GString *buf = g_string_sized_new(strlen(text) + 1 + FILEDATA_MARKS_SIZE * 2);
389
390                         if (*text)
391                                 {
392                                 g_string_append_printf(buf, "%s\n", text);
393                                 }
394
395                         for (mark = 0; mark < FILEDATA_MARKS_SIZE; mark++)
396                                 {
397                                 g_string_append_printf(buf, file_data_get_mark(fd, mark) ? " <span background='#FF00FF'>%c</span>" : " %c", '1' + (mark < 9 ? mark : -1) );
398                                 }
399
400                         g_free(text);
401                         text = g_string_free(buf, FALSE);
402                         }
403
404                 if (with_hist)
405                         {
406                         gchar *escaped_histogram_label = g_markup_escape_text(histogram_label(osd->histogram), -1);
407                         if (*text)
408                                 text2 = g_strdup_printf("%s\n%s", text, escaped_histogram_label);
409                         else
410                                 text2 = g_strdup(escaped_histogram_label);
411                         g_free(escaped_histogram_label);
412                         g_free(text);
413                         text = text2;
414                         }
415         }
416
417         font_desc = pango_font_description_from_string(options->image_overlay.font);
418         layout = gtk_widget_create_pango_layout(imd->pr, nullptr);
419         pango_layout_set_font_description(layout, font_desc);
420
421         pango_layout_set_markup(layout, text, -1);
422         g_free(text);
423
424         pango_layout_get_pixel_size(layout, &width, &height);
425         /* with empty text width is set to 0, but not height) */
426         if (width == 0)
427                 height = 0;
428         else if (height == 0)
429                 width = 0;
430         if (width > 0) width += 10;
431         if (height > 0) height += 10;
432
433         if (with_hist)
434                 {
435                 if (width < HISTOGRAM_WIDTH + 10) width = HISTOGRAM_WIDTH + 10;
436                 height += HISTOGRAM_HEIGHT + 5;
437                 }
438
439         if (width > 0 && height > 0)
440                 {
441                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
442                 pixbuf_set_rect_fill(pixbuf, 3, 3, width-6, height-6, options->image_overlay.background_red, options->image_overlay.background_green,
443                                                                                                                         options->image_overlay.background_blue, options->image_overlay.background_alpha);
444                 pixbuf_set_rect(pixbuf, 0, 0, width, height, 240, 240, 240, 80, 1, 1, 1, 1);
445                 pixbuf_set_rect(pixbuf, 1, 1, width-2, height-2, 240, 240, 240, 130, 1, 1, 1, 1);
446                 pixbuf_set_rect(pixbuf, 2, 2, width-4, height-4, 240, 240, 240, 180, 1, 1, 1, 1);
447                 pixbuf_pixel_set(pixbuf, 0, 0, 0, 0, 0, 0);
448                 pixbuf_pixel_set(pixbuf, width - 1, 0, 0, 0, 0, 0);
449                 pixbuf_pixel_set(pixbuf, 0, height - 1, 0, 0, 0, 0);
450                 pixbuf_pixel_set(pixbuf, width - 1, height - 1, 0, 0, 0, 0);
451
452                 if (with_hist)
453                         {
454                         gint x = 5;
455                         gint y = height - HISTOGRAM_HEIGHT - 5;
456                         gint w = width - 10;
457
458                         pixbuf_set_rect_fill(pixbuf, x, y, w, HISTOGRAM_HEIGHT, 220, 220, 220, 210);
459                         histogram_draw(osd->histogram, histmap, pixbuf, x, y, w, HISTOGRAM_HEIGHT);
460                         }
461                 pixbuf_draw_layout(pixbuf, layout, imd->pr, 5, 5, options->image_overlay.text_red, options->image_overlay.text_green,
462                                                                                                                         options->image_overlay.text_blue, options->image_overlay.text_alpha);
463         }
464
465         g_object_unref(G_OBJECT(layout));
466
467         return pixbuf;
468 }
469
470 /**
471  * @brief Create non-standard icons for the OSD
472  * @param flag
473  * @returns
474  *
475  * IMAGE_OSD_COLOR
476  * \image html image-osd-color.png
477  * IMAGE_OSD_FIRST
478  * \image html image-osd-first.png
479  * IMAGE_OSD_ICON
480  * \image html image-osd-icon.png
481  * IMAGE_OSD_LAST
482  * \image html image-osd-last.png
483  * IMAGE_OSD_ROTATE_AUTO
484  * \image html image-osd-rotate-auto.png
485  *
486  */
487 static GdkPixbuf *image_osd_icon_pixbuf(ImageOSDFlag flag)
488 {
489         static GdkPixbuf **icons = nullptr;
490         GdkPixbuf *icon = nullptr;
491
492         if (!icons) icons = g_new0(GdkPixbuf *, IMAGE_OSD_COUNT);
493         if (icons[flag]) return icons[flag];
494
495         if (osd_icons[flag].key)
496                 {
497                 icon = pixbuf_inline(osd_icons[flag].key);
498                 }
499
500         if (!icon)
501                 {
502                 icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 24, 24);
503                 pixbuf_set_rect_fill(icon, 1, 1, 22, 22, 255, 255, 255, 200);
504                 pixbuf_set_rect(icon, 0, 0, 24, 24, 0, 0, 0, 128, 1, 1, 1, 1);
505                 switch (flag)
506                         {
507                         case IMAGE_OSD_ROTATE_AUTO:
508                                 pixbuf_set_rect(icon, 3, 8, 11, 12,
509                                                 0, 0, 0, 255,
510                                                 3, 0, 3, 0);
511                                 pixbuf_draw_triangle(icon, 14, 3, 6, 12,
512                                                      20, 9, 14, 15, 14, 3,
513                                                      0, 0, 0, 255);
514                                 break;
515                         case IMAGE_OSD_ROTATE_USER:
516                                 break;
517                         case IMAGE_OSD_COLOR:
518                                 pixbuf_set_rect_fill(icon, 3, 3, 18, 6, 200, 0, 0, 255);
519                                 pixbuf_set_rect_fill(icon, 3, 9, 18, 6, 0, 200, 0, 255);
520                                 pixbuf_set_rect_fill(icon, 3, 15, 18, 6, 0, 0, 200, 255);
521                                 break;
522                         case IMAGE_OSD_FIRST:
523                                 pixbuf_set_rect(icon, 3, 3, 18, 18, 0, 0, 0, 200, 3, 3, 3, 0);
524                                 pixbuf_draw_triangle(icon, 6, 5, 12, 6,
525                                                      12, 5, 18, 11, 6, 11,
526                                                      0, 0, 0, 255);
527                                 break;
528                         case IMAGE_OSD_LAST:
529                                 pixbuf_set_rect(icon, 3, 3, 18, 18, 0, 0, 0, 200, 3, 3, 0, 3);
530                                 pixbuf_draw_triangle(icon, 6, 12, 12, 6,
531                                                      12, 18, 6, 12, 18, 12,
532                                                      0, 0, 0, 255);
533                                 break;
534                         case IMAGE_OSD_ICON:
535                                 pixbuf_set_rect_fill(icon, 11, 3, 3, 12, 0, 0, 0, 255);
536                                 pixbuf_set_rect_fill(icon, 11, 17, 3, 3, 0, 0, 0, 255);
537                                 break;
538                         default:
539                                 break;
540                         }
541                 }
542
543         icons[flag] = icon;
544
545         return icon;
546 }
547
548 static gint image_overlay_add(ImageWindow *imd, GdkPixbuf *pixbuf, gint x, gint y,
549                               OverlayRendererFlags flags)
550 {
551         return pixbuf_renderer_overlay_add(reinterpret_cast<PixbufRenderer *>(imd->pr), pixbuf, x, y, flags);
552 }
553
554 static void image_overlay_set(ImageWindow *imd, gint id, GdkPixbuf *pixbuf, gint x, gint y)
555 {
556         pixbuf_renderer_overlay_set(reinterpret_cast<PixbufRenderer *>(imd->pr), id, pixbuf, x, y);
557 }
558
559 static void image_overlay_remove(ImageWindow *imd, gint id)
560 {
561         pixbuf_renderer_overlay_remove(reinterpret_cast<PixbufRenderer *>(imd->pr), id);
562 }
563
564 static void image_osd_icon_show(OverlayStateData *osd, ImageOSDFlag flag)
565 {
566         GdkPixbuf *pixbuf;
567
568         if (osd->icon_id[flag]) return;
569
570         pixbuf = image_osd_icon_pixbuf(flag);
571         if (!pixbuf) return;
572
573         osd->icon_id[flag] = image_overlay_add(osd->imd, pixbuf,
574                                                osd_icons[flag].x, osd_icons[flag].y,
575                                                OVL_RELATIVE);
576 }
577
578 static void image_osd_icon_hide(OverlayStateData *osd, ImageOSDFlag flag)
579 {
580         if (osd->icon_id[flag])
581                 {
582                 image_overlay_remove(osd->imd, osd->icon_id[flag]);
583                 osd->icon_id[flag] = 0;
584                 }
585 }
586
587 static void image_osd_icons_reset_time(OverlayStateData *osd)
588 {
589         gint i;
590
591         for (i = 0; i < IMAGE_OSD_COUNT; i++)
592                 {
593                 if (osd_icons[i].reset)
594                         {
595                         osd->icon_time[i] = 0;
596                         }
597                 }
598 }
599
600 static void image_osd_icons_update(OverlayStateData *osd)
601 {
602         gint i;
603
604         for (i = 0; i < IMAGE_OSD_COUNT; i++)
605                 {
606                 if (osd->icon_time[i] > 0)
607                         {
608                         image_osd_icon_show(osd, static_cast<ImageOSDFlag>(i));
609                         }
610                 else
611                         {
612                         image_osd_icon_hide(osd, static_cast<ImageOSDFlag>(i));
613                         }
614                 }
615 }
616
617 static void image_osd_icons_hide(OverlayStateData *osd)
618 {
619         gint i;
620
621         for (i = 0; i < IMAGE_OSD_COUNT; i++)
622                 {
623                 image_osd_icon_hide(osd, static_cast<ImageOSDFlag>(i));
624                 }
625 }
626
627 static void image_osd_info_show(OverlayStateData *osd, GdkPixbuf *pixbuf)
628 {
629         if (osd->ovl_info == 0)
630                 {
631                 osd->ovl_info = image_overlay_add(osd->imd, pixbuf, osd->x, osd->y, osd->origin);
632                 }
633         else
634                 {
635                 image_overlay_set(osd->imd, osd->ovl_info, pixbuf, osd->x, osd->y);
636                 }
637 }
638
639 static void image_osd_info_hide(OverlayStateData *osd)
640 {
641         if (osd->ovl_info == 0) return;
642
643         image_overlay_remove(osd->imd, osd->ovl_info);
644         osd->ovl_info = 0;
645 }
646
647 static gboolean image_osd_update_cb(gpointer data)
648 {
649         auto osd = static_cast<OverlayStateData *>(data);
650
651         if (osd->show & OSD_SHOW_INFO)
652                 {
653                 /* redraw when the image was changed,
654                    with histogram we have to redraw also when loading is finished */
655                 if (osd->changed_states & IMAGE_STATE_IMAGE ||
656                     (osd->changed_states & IMAGE_STATE_LOADING && osd->show & OSD_SHOW_HISTOGRAM) ||
657                     osd->notify & NOTIFY_HISTMAP)
658                         {
659                         GdkPixbuf *pixbuf;
660
661                         pixbuf = image_osd_info_render(osd);
662                         if (pixbuf)
663                                 {
664                                 image_osd_info_show(osd, pixbuf);
665                                 g_object_unref(pixbuf);
666                                 }
667                         else
668                                 {
669                                 image_osd_info_hide(osd);
670                                 }
671                         }
672                 }
673         else
674                 {
675                 image_osd_info_hide(osd);
676                 }
677
678         if (osd->show & OSD_SHOW_STATUS)
679                 {
680                 if (osd->changed_states & IMAGE_STATE_IMAGE)
681                         image_osd_icons_reset_time(osd);
682
683                 if (osd->changed_states & IMAGE_STATE_COLOR_ADJ)
684                         {
685                         osd->icon_time[IMAGE_OSD_COLOR] = IMAGE_OSD_DEFAULT_DURATION + 1;
686                         image_osd_timer_schedule(osd);
687                         }
688
689                 if (osd->changed_states & IMAGE_STATE_ROTATE_AUTO)
690                         {
691                         gint n = 0;
692
693                         if (osd->imd->state & IMAGE_STATE_ROTATE_AUTO)
694                                 {
695                                 n = 1;
696                                 if (!osd->imd->cm) n += IMAGE_OSD_DEFAULT_DURATION;
697                                 }
698
699                         osd->icon_time[IMAGE_OSD_ROTATE_AUTO] = n;
700                         image_osd_timer_schedule(osd);
701                         }
702
703                 image_osd_icons_update(osd);
704                 }
705         else
706                 {
707                 image_osd_icons_hide(osd);
708                 }
709
710         osd->changed_states = IMAGE_STATE_NONE;
711         osd->notify = static_cast<NotifyType>(0);
712         osd->idle_id = 0;
713         return G_SOURCE_REMOVE;
714 }
715
716 static void image_osd_update_schedule(OverlayStateData *osd, gboolean force)
717 {
718         if (force) osd->changed_states = static_cast<ImageState>(osd->changed_states | IMAGE_STATE_IMAGE);
719
720         if (!osd->idle_id)
721                 {
722                 osd->idle_id = g_idle_add_full(G_PRIORITY_HIGH, image_osd_update_cb, osd, nullptr);
723                 }
724 }
725
726 void image_osd_update(ImageWindow *imd)
727 {
728         OverlayStateData *osd = image_get_osd_data(imd);
729
730         if (!osd) return;
731
732         image_osd_update_schedule(osd, TRUE);
733 }
734
735 static gboolean image_osd_timer_cb(gpointer data)
736 {
737         auto osd = static_cast<OverlayStateData *>(data);
738         gboolean done = TRUE;
739         gboolean changed = FALSE;
740         gint i;
741
742         for (i = 0; i < IMAGE_OSD_COUNT; i++)
743                 {
744                 if (osd->icon_time[i] > 1)
745                         {
746                         osd->icon_time[i]--;
747                         if (osd->icon_time[i] < 2)
748                                 {
749                                 osd->icon_time[i] = 0;
750                                 changed = TRUE;
751                                 }
752                         else
753                                 {
754                                 done = FALSE;
755                                 }
756                         }
757                 }
758
759         if (changed) image_osd_update_schedule(osd, FALSE);
760
761         if (done)
762                 {
763                 osd->timer_id = 0;
764                 return FALSE;
765                 }
766
767         return TRUE;
768 }
769
770 static void image_osd_timer_schedule(OverlayStateData *osd)
771 {
772         if (!osd->timer_id)
773                 {
774                 osd->timer_id = g_timeout_add(100, image_osd_timer_cb, osd);
775                 }
776 }
777
778 static void image_osd_state_cb(ImageWindow *, ImageState state, gpointer data)
779 {
780         auto osd = static_cast<OverlayStateData *>(data);
781
782         osd->changed_states = static_cast<ImageState>(osd->changed_states | state);
783         image_osd_update_schedule(osd, FALSE);
784 }
785
786 static void image_osd_notify_cb(FileData *fd, NotifyType type, gpointer data)
787 {
788         auto osd = static_cast<OverlayStateData *>(data);
789
790         if ((type & (NOTIFY_HISTMAP)) && osd->imd && fd == osd->imd->image_fd)
791                 {
792                 DEBUG_1("Notify osd: %s %04x", fd->path, type);
793                 osd->notify = static_cast<NotifyType>(osd->notify | type);
794                 image_osd_update_schedule(osd, FALSE);
795                 }
796 }
797
798
799 static void image_osd_free(OverlayStateData *osd)
800 {
801         if (!osd) return;
802
803         if (osd->idle_id) g_source_remove(osd->idle_id);
804         if (osd->timer_id) g_source_remove(osd->timer_id);
805
806         file_data_unregister_notify_func(image_osd_notify_cb, osd);
807
808         if (osd->imd)
809                 {
810                 image_set_osd_data(osd->imd, nullptr);
811                 g_signal_handler_disconnect(osd->imd->pr, osd->destroy_id);
812
813                 image_set_state_func(osd->imd, nullptr, nullptr);
814
815                 image_osd_info_hide(osd);
816                 image_osd_icons_hide(osd);
817                 }
818
819         if (osd->histogram) histogram_free(osd->histogram);
820
821         g_free(osd);
822 }
823
824 static void image_osd_destroy_cb(GtkWidget *, gpointer data)
825 {
826         auto osd = static_cast<OverlayStateData *>(data);
827
828         osd->imd = nullptr;
829         image_osd_free(osd);
830 }
831
832 static void image_osd_enable(ImageWindow *imd, OsdShowFlags show)
833 {
834         OverlayStateData *osd = image_get_osd_data(imd);
835
836         if (!osd)
837                 {
838                 osd = g_new0(OverlayStateData, 1);
839                 osd->imd = imd;
840                 osd->show = OSD_SHOW_NOTHING;
841                 osd->x = options->image_overlay.x;
842                 osd->y = options->image_overlay.y;
843                 osd->origin = OVL_RELATIVE;
844
845                 osd->histogram = histogram_new();
846
847                 osd->destroy_id = g_signal_connect(G_OBJECT(imd->pr), "destroy",
848                                                    G_CALLBACK(image_osd_destroy_cb), osd);
849                 image_set_osd_data(imd, osd);
850
851                 image_set_state_func(osd->imd, image_osd_state_cb, osd);
852                 file_data_register_notify_func(image_osd_notify_cb, osd, NOTIFY_PRIORITY_LOW);
853                 }
854
855         if (show & OSD_SHOW_STATUS)
856                 image_osd_icon(imd, IMAGE_OSD_ICON, -1);
857
858         if (show != osd->show)
859                 image_osd_update_schedule(osd, TRUE);
860
861         osd->show = show;
862 }
863
864 void image_osd_set(ImageWindow *imd, OsdShowFlags show)
865 {
866         if (!imd) return;
867
868         image_osd_enable(imd, show);
869 }
870
871 OsdShowFlags image_osd_get(ImageWindow *imd)
872 {
873         OverlayStateData *osd = image_get_osd_data(imd);
874
875         return osd ? osd->show : OSD_SHOW_NOTHING;
876 }
877
878 Histogram *image_osd_get_histogram(ImageWindow *imd)
879 {
880         OverlayStateData *osd = image_get_osd_data(imd);
881
882         return osd ? osd->histogram : nullptr;
883 }
884
885 void image_osd_copy_status(ImageWindow *src, ImageWindow *dest)
886 {
887         Histogram *h_src;
888         Histogram *h_dest;
889         image_osd_set(dest, image_osd_get(src));
890
891         h_src = image_osd_get_histogram(src);
892         h_dest = image_osd_get_histogram(dest);
893
894         h_dest->histogram_mode = h_src->histogram_mode;
895         h_dest->histogram_channel = h_src->histogram_channel;
896
897 }
898
899 /* duration:
900     0 = hide
901     1 = show
902    2+ = show for duration tenths of a second
903    -1 = use default duration
904  */
905 void image_osd_icon(ImageWindow *imd, ImageOSDFlag flag, gint duration)
906 {
907         OverlayStateData *osd = image_get_osd_data(imd);
908
909         if (!osd) return;
910
911         if (flag >= IMAGE_OSD_COUNT) return;
912         if (duration < 0) duration = IMAGE_OSD_DEFAULT_DURATION;
913         if (duration > 1) duration += 1;
914
915         osd->icon_time[flag] = duration;
916
917         image_osd_update_schedule(osd, FALSE);
918         image_osd_timer_schedule(osd);
919 }
920 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */