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