3525aeab5a6aec27d0d3a031819edfe1dfb56ac8
[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;
232         gint height;
233         PangoLayout *layout;
234         const gchar *name;
235         gchar *text;
236         gboolean with_hist;
237         const HistMap *histmap = nullptr;
238         ImageWindow *imd = osd->imd;
239         FileData *fd = image_get_fd(imd);
240         PangoFontDescription *font_desc;
241
242         if (!fd) return nullptr;
243
244         name = image_get_name(imd);
245         if (name)
246                 {
247                 gint n;
248                 gint t;
249                 CollectionData *cd;
250                 CollectInfo *info;
251                 GHashTable *vars;
252
253                 vars = g_hash_table_new_full(g_str_hash, g_str_equal, nullptr, g_free);
254
255                 cd = image_get_collection(imd, &info);
256                 if (cd)
257                         {
258                         t = g_list_length(cd->list);
259                         n = g_list_index(cd->list, info) + 1;
260                         if (cd->name)
261                                 {
262                                 if (file_extension_match(cd->name, GQ_COLLECTION_EXT))
263                                         osd_template_insert(vars, "collection", remove_extension_from_path(cd->name), OSDT_FREE);
264                                 else
265                                         osd_template_insert(vars, "collection", cd->name, OSDT_NONE);
266                                 }
267                         else
268                                 {
269                                 osd_template_insert(vars, "collection", _("Untitled"), OSDT_NONE);
270                                 }
271                         }
272                 else
273                         {
274                         LayoutWindow *lw = layout_find_by_image(imd);
275                         if (lw)
276                                 {
277                                 if (lw->slideshow)
278                                         {
279                                         n = g_list_length(lw->slideshow->list_done);
280                                         t = n + g_list_length(lw->slideshow->list);
281                                         if (n == 0) n = t;
282                                         }
283                                 else
284                                         {
285                                         t = layout_list_count(lw, nullptr);
286                                         n = layout_list_get_index(lw, image_get_fd(lw->image)) + 1;
287                                         }
288                                 }
289                         else if (view_window_find_image(imd, &n, &t))
290                                 {
291                                 n++;
292                                 }
293                         else
294                                 {
295                                 t = 1;
296                                 n = 1;
297                                 }
298
299                         if (n < 1) n = 1;
300                         if (t < 1) t = 1;
301
302                         osd_template_insert(vars, "collection", nullptr, OSDT_NONE);
303                         }
304
305                 osd_template_insert(vars, "number", g_strdup_printf("%d", n), OSDT_NO_DUP);
306                 osd_template_insert(vars, "total", g_strdup_printf("%d", t), OSDT_NO_DUP);
307                 osd_template_insert(vars, "name", const_cast<gchar *>(name), OSDT_NONE);
308                 osd_template_insert(vars, "path", const_cast<gchar *>(image_get_path(imd)), OSDT_NONE);
309                 osd_template_insert(vars, "date", imd->image_fd ? (const_cast<gchar *>(text_from_time(imd->image_fd->date))) : "", OSDT_NONE);
310                 osd_template_insert(vars, "size", imd->image_fd ? (text_from_size_abrev(imd->image_fd->size)) : g_strdup(""), OSDT_FREE);
311                 osd_template_insert(vars, "zoom", image_zoom_get_as_text(imd), OSDT_FREE);
312
313                 if (!imd->unknown)
314                         {
315                         gint w;
316                         gint h;
317                         GdkPixbuf *load_pixbuf = image_loader_get_pixbuf(imd->il);
318
319                         if (imd->delay_flip &&
320                             imd->il && load_pixbuf &&
321                             image_get_pixbuf(imd) != load_pixbuf)
322                                 {
323                                 w = gdk_pixbuf_get_width(load_pixbuf);
324                                 h = gdk_pixbuf_get_height(load_pixbuf);
325                                 }
326                         else
327                                 {
328                                 image_get_image_size(imd, &w, &h);
329                                 }
330
331
332                         osd_template_insert(vars, "width", g_strdup_printf("%d", w), OSDT_NO_DUP);
333                         osd_template_insert(vars, "height", g_strdup_printf("%d", h), OSDT_NO_DUP);
334                         osd_template_insert(vars, "res", g_strdup_printf("%d Ã— %d", w, h), OSDT_FREE);
335                         }
336                 else
337                         {
338                         osd_template_insert(vars, "width", nullptr, OSDT_NONE);
339                         osd_template_insert(vars, "height", nullptr, OSDT_NONE);
340                         osd_template_insert(vars, "res", nullptr, OSDT_NONE);
341                         }
342
343                 text = image_osd_mkinfo(options->image_overlay.template_string, imd->image_fd, vars);
344                 g_hash_table_destroy(vars);
345
346         } else {
347                 /* When does this occur ?? */
348                 text = g_markup_escape_text(_("Untitled"), -1);
349         }
350
351         with_hist = ((osd->show & OSD_SHOW_HISTOGRAM) && osd->histogram);
352         if (with_hist)
353                 {
354                 histmap = histmap_get(imd->image_fd);
355                 if (!histmap)
356                         {
357                         histmap_start_idle(imd->image_fd);
358                         with_hist = FALSE;
359                         }
360                 }
361
362
363         {
364                 gint active_marks = 0;
365                 gint mark;
366                 gchar *text2;
367
368                 for (mark = 0; mark < FILEDATA_MARKS_SIZE; mark++)
369                         {
370                         active_marks += file_data_get_mark(fd, mark);
371                         }
372
373                 if (active_marks > 0)
374                         {
375                         GString *buf = g_string_sized_new(strlen(text) + 1 + FILEDATA_MARKS_SIZE * 2);
376
377                         if (*text)
378                                 {
379                                 g_string_append_printf(buf, "%s\n", text);
380                                 }
381
382                         for (mark = 0; mark < FILEDATA_MARKS_SIZE; mark++)
383                                 {
384                                 g_string_append_printf(buf, file_data_get_mark(fd, mark) ? " <span background='#FF00FF'>%c</span>" : " %c", '1' + (mark < 9 ? mark : -1) );
385                                 }
386
387                         g_free(text);
388                         text = g_string_free(buf, FALSE);
389                         }
390
391                 if (with_hist)
392                         {
393                         gchar *escaped_histogram_label = g_markup_escape_text(histogram_label(osd->histogram), -1);
394                         if (*text)
395                                 text2 = g_strdup_printf("%s\n%s", text, escaped_histogram_label);
396                         else
397                                 text2 = g_strdup(escaped_histogram_label);
398                         g_free(escaped_histogram_label);
399                         g_free(text);
400                         text = text2;
401                         }
402         }
403
404         font_desc = pango_font_description_from_string(options->image_overlay.font);
405         layout = gtk_widget_create_pango_layout(imd->pr, nullptr);
406         pango_layout_set_font_description(layout, font_desc);
407
408         pango_layout_set_markup(layout, text, -1);
409         g_free(text);
410
411         pango_layout_get_pixel_size(layout, &width, &height);
412         /* with empty text width is set to 0, but not height) */
413         if (width == 0)
414                 height = 0;
415         else if (height == 0)
416                 width = 0;
417         if (width > 0) width += 10;
418         if (height > 0) height += 10;
419
420         if (with_hist)
421                 {
422                 if (width < HISTOGRAM_WIDTH + 10) width = HISTOGRAM_WIDTH + 10;
423                 height += HISTOGRAM_HEIGHT + 5;
424                 }
425
426         if (width > 0 && height > 0)
427                 {
428                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
429                 pixbuf_set_rect_fill(pixbuf, 3, 3, width-6, height-6, options->image_overlay.background_red, options->image_overlay.background_green,
430                                                                                                                         options->image_overlay.background_blue, options->image_overlay.background_alpha);
431                 pixbuf_set_rect(pixbuf, 0, 0, width, height, 240, 240, 240, 80, 1, 1, 1, 1);
432                 pixbuf_set_rect(pixbuf, 1, 1, width-2, height-2, 240, 240, 240, 130, 1, 1, 1, 1);
433                 pixbuf_set_rect(pixbuf, 2, 2, width-4, height-4, 240, 240, 240, 180, 1, 1, 1, 1);
434                 pixbuf_pixel_set(pixbuf, 0, 0, 0, 0, 0, 0);
435                 pixbuf_pixel_set(pixbuf, width - 1, 0, 0, 0, 0, 0);
436                 pixbuf_pixel_set(pixbuf, 0, height - 1, 0, 0, 0, 0);
437                 pixbuf_pixel_set(pixbuf, width - 1, height - 1, 0, 0, 0, 0);
438
439                 if (with_hist)
440                         {
441                         gint x = 5;
442                         gint y = height - HISTOGRAM_HEIGHT - 5;
443                         gint w = width - 10;
444
445                         pixbuf_set_rect_fill(pixbuf, x, y, w, HISTOGRAM_HEIGHT, 220, 220, 220, 210);
446                         histogram_draw(osd->histogram, histmap, pixbuf, x, y, w, HISTOGRAM_HEIGHT);
447                         }
448                 pixbuf_draw_layout(pixbuf, layout, imd->pr, 5, 5, options->image_overlay.text_red, options->image_overlay.text_green,
449                                                                                                                         options->image_overlay.text_blue, options->image_overlay.text_alpha);
450         }
451
452         g_object_unref(G_OBJECT(layout));
453
454         return pixbuf;
455 }
456
457 /**
458  * @brief Create non-standard icons for the OSD
459  * @param flag
460  * @returns
461  *
462  * IMAGE_OSD_COLOR
463  * \image html image-osd-color.png
464  * IMAGE_OSD_FIRST
465  * \image html image-osd-first.png
466  * IMAGE_OSD_ICON
467  * \image html image-osd-icon.png
468  * IMAGE_OSD_LAST
469  * \image html image-osd-last.png
470  * IMAGE_OSD_ROTATE_AUTO
471  * \image html image-osd-rotate-auto.png
472  *
473  */
474 static GdkPixbuf *image_osd_icon_pixbuf(ImageOSDFlag flag)
475 {
476         static GdkPixbuf **icons = nullptr;
477         GdkPixbuf *icon = nullptr;
478
479         if (!icons) icons = g_new0(GdkPixbuf *, IMAGE_OSD_COUNT);
480         if (icons[flag]) return icons[flag];
481
482         if (osd_icons[flag].key)
483                 {
484                 icon = pixbuf_inline(osd_icons[flag].key);
485                 }
486
487         if (!icon)
488                 {
489                 icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 24, 24);
490                 pixbuf_set_rect_fill(icon, 1, 1, 22, 22, 255, 255, 255, 200);
491                 pixbuf_set_rect(icon, 0, 0, 24, 24, 0, 0, 0, 128, 1, 1, 1, 1);
492                 switch (flag)
493                         {
494                         case IMAGE_OSD_ROTATE_AUTO:
495                                 pixbuf_set_rect(icon, 3, 8, 11, 12,
496                                                 0, 0, 0, 255,
497                                                 3, 0, 3, 0);
498                                 pixbuf_draw_triangle(icon, 14, 3, 6, 12,
499                                                      20, 9, 14, 15, 14, 3,
500                                                      0, 0, 0, 255);
501                                 break;
502                         case IMAGE_OSD_ROTATE_USER:
503                                 break;
504                         case IMAGE_OSD_COLOR:
505                                 pixbuf_set_rect_fill(icon, 3, 3, 18, 6, 200, 0, 0, 255);
506                                 pixbuf_set_rect_fill(icon, 3, 9, 18, 6, 0, 200, 0, 255);
507                                 pixbuf_set_rect_fill(icon, 3, 15, 18, 6, 0, 0, 200, 255);
508                                 break;
509                         case IMAGE_OSD_FIRST:
510                                 pixbuf_set_rect(icon, 3, 3, 18, 18, 0, 0, 0, 200, 3, 3, 3, 0);
511                                 pixbuf_draw_triangle(icon, 6, 5, 12, 6,
512                                                      12, 5, 18, 11, 6, 11,
513                                                      0, 0, 0, 255);
514                                 break;
515                         case IMAGE_OSD_LAST:
516                                 pixbuf_set_rect(icon, 3, 3, 18, 18, 0, 0, 0, 200, 3, 3, 0, 3);
517                                 pixbuf_draw_triangle(icon, 6, 12, 12, 6,
518                                                      12, 18, 6, 12, 18, 12,
519                                                      0, 0, 0, 255);
520                                 break;
521                         case IMAGE_OSD_ICON:
522                                 pixbuf_set_rect_fill(icon, 11, 3, 3, 12, 0, 0, 0, 255);
523                                 pixbuf_set_rect_fill(icon, 11, 17, 3, 3, 0, 0, 0, 255);
524                                 break;
525                         default:
526                                 break;
527                         }
528                 }
529
530         icons[flag] = icon;
531
532         return icon;
533 }
534
535 static gint image_overlay_add(ImageWindow *imd, GdkPixbuf *pixbuf, gint x, gint y,
536                               OverlayRendererFlags flags)
537 {
538         return pixbuf_renderer_overlay_add(reinterpret_cast<PixbufRenderer *>(imd->pr), pixbuf, x, y, flags);
539 }
540
541 static void image_overlay_set(ImageWindow *imd, gint id, GdkPixbuf *pixbuf, gint x, gint y)
542 {
543         pixbuf_renderer_overlay_set(reinterpret_cast<PixbufRenderer *>(imd->pr), id, pixbuf, x, y);
544 }
545
546 static void image_overlay_remove(ImageWindow *imd, gint id)
547 {
548         pixbuf_renderer_overlay_remove(reinterpret_cast<PixbufRenderer *>(imd->pr), id);
549 }
550
551 static void image_osd_icon_show(OverlayStateData *osd, ImageOSDFlag flag)
552 {
553         GdkPixbuf *pixbuf;
554
555         if (osd->icon_id[flag]) return;
556
557         pixbuf = image_osd_icon_pixbuf(flag);
558         if (!pixbuf) return;
559
560         osd->icon_id[flag] = image_overlay_add(osd->imd, pixbuf,
561                                                osd_icons[flag].x, osd_icons[flag].y,
562                                                OVL_RELATIVE);
563 }
564
565 static void image_osd_icon_hide(OverlayStateData *osd, ImageOSDFlag flag)
566 {
567         if (osd->icon_id[flag])
568                 {
569                 image_overlay_remove(osd->imd, osd->icon_id[flag]);
570                 osd->icon_id[flag] = 0;
571                 }
572 }
573
574 static void image_osd_icons_reset_time(OverlayStateData *osd)
575 {
576         gint i;
577
578         for (i = 0; i < IMAGE_OSD_COUNT; i++)
579                 {
580                 if (osd_icons[i].reset)
581                         {
582                         osd->icon_time[i] = 0;
583                         }
584                 }
585 }
586
587 static void image_osd_icons_update(OverlayStateData *osd)
588 {
589         gint i;
590
591         for (i = 0; i < IMAGE_OSD_COUNT; i++)
592                 {
593                 if (osd->icon_time[i] > 0)
594                         {
595                         image_osd_icon_show(osd, static_cast<ImageOSDFlag>(i));
596                         }
597                 else
598                         {
599                         image_osd_icon_hide(osd, static_cast<ImageOSDFlag>(i));
600                         }
601                 }
602 }
603
604 static void image_osd_icons_hide(OverlayStateData *osd)
605 {
606         gint i;
607
608         for (i = 0; i < IMAGE_OSD_COUNT; i++)
609                 {
610                 image_osd_icon_hide(osd, static_cast<ImageOSDFlag>(i));
611                 }
612 }
613
614 static void image_osd_info_show(OverlayStateData *osd, GdkPixbuf *pixbuf)
615 {
616         if (osd->ovl_info == 0)
617                 {
618                 osd->ovl_info = image_overlay_add(osd->imd, pixbuf, osd->x, osd->y, osd->origin);
619                 }
620         else
621                 {
622                 image_overlay_set(osd->imd, osd->ovl_info, pixbuf, osd->x, osd->y);
623                 }
624 }
625
626 static void image_osd_info_hide(OverlayStateData *osd)
627 {
628         if (osd->ovl_info == 0) return;
629
630         image_overlay_remove(osd->imd, osd->ovl_info);
631         osd->ovl_info = 0;
632 }
633
634 static gboolean image_osd_update_cb(gpointer data)
635 {
636         auto osd = static_cast<OverlayStateData *>(data);
637
638         if (osd->show & OSD_SHOW_INFO)
639                 {
640                 /* redraw when the image was changed,
641                    with histogram we have to redraw also when loading is finished */
642                 if (osd->changed_states & IMAGE_STATE_IMAGE ||
643                     (osd->changed_states & IMAGE_STATE_LOADING && osd->show & OSD_SHOW_HISTOGRAM) ||
644                     osd->notify & NOTIFY_HISTMAP)
645                         {
646                         GdkPixbuf *pixbuf;
647
648                         pixbuf = image_osd_info_render(osd);
649                         if (pixbuf)
650                                 {
651                                 image_osd_info_show(osd, pixbuf);
652                                 g_object_unref(pixbuf);
653                                 }
654                         else
655                                 {
656                                 image_osd_info_hide(osd);
657                                 }
658                         }
659                 }
660         else
661                 {
662                 image_osd_info_hide(osd);
663                 }
664
665         if (osd->show & OSD_SHOW_STATUS)
666                 {
667                 if (osd->changed_states & IMAGE_STATE_IMAGE)
668                         image_osd_icons_reset_time(osd);
669
670                 if (osd->changed_states & IMAGE_STATE_COLOR_ADJ)
671                         {
672                         osd->icon_time[IMAGE_OSD_COLOR] = IMAGE_OSD_DEFAULT_DURATION + 1;
673                         image_osd_timer_schedule(osd);
674                         }
675
676                 if (osd->changed_states & IMAGE_STATE_ROTATE_AUTO)
677                         {
678                         gint n = 0;
679
680                         if (osd->imd->state & IMAGE_STATE_ROTATE_AUTO)
681                                 {
682                                 n = 1;
683                                 if (!osd->imd->cm) n += IMAGE_OSD_DEFAULT_DURATION;
684                                 }
685
686                         osd->icon_time[IMAGE_OSD_ROTATE_AUTO] = n;
687                         image_osd_timer_schedule(osd);
688                         }
689
690                 image_osd_icons_update(osd);
691                 }
692         else
693                 {
694                 image_osd_icons_hide(osd);
695                 }
696
697         osd->changed_states = IMAGE_STATE_NONE;
698         osd->notify = static_cast<NotifyType>(0);
699         osd->idle_id = 0;
700         return G_SOURCE_REMOVE;
701 }
702
703 static void image_osd_update_schedule(OverlayStateData *osd, gboolean force)
704 {
705         if (force) osd->changed_states = static_cast<ImageState>(osd->changed_states | IMAGE_STATE_IMAGE);
706
707         if (!osd->idle_id)
708                 {
709                 osd->idle_id = g_idle_add_full(G_PRIORITY_HIGH, image_osd_update_cb, osd, nullptr);
710                 }
711 }
712
713 void image_osd_update(ImageWindow *imd)
714 {
715         OverlayStateData *osd = image_get_osd_data(imd);
716
717         if (!osd) return;
718
719         image_osd_update_schedule(osd, TRUE);
720 }
721
722 static gboolean image_osd_timer_cb(gpointer data)
723 {
724         auto osd = static_cast<OverlayStateData *>(data);
725         gboolean done = TRUE;
726         gboolean changed = FALSE;
727         gint i;
728
729         for (i = 0; i < IMAGE_OSD_COUNT; i++)
730                 {
731                 if (osd->icon_time[i] > 1)
732                         {
733                         osd->icon_time[i]--;
734                         if (osd->icon_time[i] < 2)
735                                 {
736                                 osd->icon_time[i] = 0;
737                                 changed = TRUE;
738                                 }
739                         else
740                                 {
741                                 done = FALSE;
742                                 }
743                         }
744                 }
745
746         if (changed) image_osd_update_schedule(osd, FALSE);
747
748         if (done)
749                 {
750                 osd->timer_id = 0;
751                 return FALSE;
752                 }
753
754         return TRUE;
755 }
756
757 static void image_osd_timer_schedule(OverlayStateData *osd)
758 {
759         if (!osd->timer_id)
760                 {
761                 osd->timer_id = g_timeout_add(100, image_osd_timer_cb, osd);
762                 }
763 }
764
765 static void image_osd_state_cb(ImageWindow *, ImageState state, gpointer data)
766 {
767         auto osd = static_cast<OverlayStateData *>(data);
768
769         osd->changed_states = static_cast<ImageState>(osd->changed_states | state);
770         image_osd_update_schedule(osd, FALSE);
771 }
772
773 static void image_osd_notify_cb(FileData *fd, NotifyType type, gpointer data)
774 {
775         auto osd = static_cast<OverlayStateData *>(data);
776
777         if ((type & (NOTIFY_HISTMAP)) && osd->imd && fd == osd->imd->image_fd)
778                 {
779                 DEBUG_1("Notify osd: %s %04x", fd->path, type);
780                 osd->notify = static_cast<NotifyType>(osd->notify | type);
781                 image_osd_update_schedule(osd, FALSE);
782                 }
783 }
784
785
786 static void image_osd_free(OverlayStateData *osd)
787 {
788         if (!osd) return;
789
790         if (osd->idle_id) g_source_remove(osd->idle_id);
791         if (osd->timer_id) g_source_remove(osd->timer_id);
792
793         file_data_unregister_notify_func(image_osd_notify_cb, osd);
794
795         if (osd->imd)
796                 {
797                 image_set_osd_data(osd->imd, nullptr);
798                 g_signal_handler_disconnect(osd->imd->pr, osd->destroy_id);
799
800                 image_set_state_func(osd->imd, nullptr, nullptr);
801
802                 image_osd_info_hide(osd);
803                 image_osd_icons_hide(osd);
804                 }
805
806         if (osd->histogram) histogram_free(osd->histogram);
807
808         g_free(osd);
809 }
810
811 static void image_osd_destroy_cb(GtkWidget *, gpointer data)
812 {
813         auto osd = static_cast<OverlayStateData *>(data);
814
815         osd->imd = nullptr;
816         image_osd_free(osd);
817 }
818
819 static void image_osd_enable(ImageWindow *imd, OsdShowFlags show)
820 {
821         OverlayStateData *osd = image_get_osd_data(imd);
822
823         if (!osd)
824                 {
825                 osd = g_new0(OverlayStateData, 1);
826                 osd->imd = imd;
827                 osd->show = OSD_SHOW_NOTHING;
828                 osd->x = options->image_overlay.x;
829                 osd->y = options->image_overlay.y;
830                 osd->origin = OVL_RELATIVE;
831
832                 osd->histogram = histogram_new();
833
834                 osd->destroy_id = g_signal_connect(G_OBJECT(imd->pr), "destroy",
835                                                    G_CALLBACK(image_osd_destroy_cb), osd);
836                 image_set_osd_data(imd, osd);
837
838                 image_set_state_func(osd->imd, image_osd_state_cb, osd);
839                 file_data_register_notify_func(image_osd_notify_cb, osd, NOTIFY_PRIORITY_LOW);
840                 }
841
842         if (show & OSD_SHOW_STATUS)
843                 image_osd_icon(imd, IMAGE_OSD_ICON, -1);
844
845         if (show != osd->show)
846                 image_osd_update_schedule(osd, TRUE);
847
848         osd->show = show;
849 }
850
851 void image_osd_set(ImageWindow *imd, OsdShowFlags show)
852 {
853         if (!imd) return;
854
855         image_osd_enable(imd, show);
856 }
857
858 OsdShowFlags image_osd_get(ImageWindow *imd)
859 {
860         OverlayStateData *osd = image_get_osd_data(imd);
861
862         return osd ? osd->show : OSD_SHOW_NOTHING;
863 }
864
865 Histogram *image_osd_get_histogram(ImageWindow *imd)
866 {
867         OverlayStateData *osd = image_get_osd_data(imd);
868
869         return osd ? osd->histogram : nullptr;
870 }
871
872 void image_osd_copy_status(ImageWindow *src, ImageWindow *dest)
873 {
874         Histogram *h_src;
875         Histogram *h_dest;
876         image_osd_set(dest, image_osd_get(src));
877
878         h_src = image_osd_get_histogram(src);
879         h_dest = image_osd_get_histogram(dest);
880
881         h_dest->histogram_mode = h_src->histogram_mode;
882         h_dest->histogram_channel = h_src->histogram_channel;
883
884 }
885
886 /* duration:
887     0 = hide
888     1 = show
889    2+ = show for duration tenths of a second
890    -1 = use default duration
891  */
892 void image_osd_icon(ImageWindow *imd, ImageOSDFlag flag, gint duration)
893 {
894         OverlayStateData *osd = image_get_osd_data(imd);
895
896         if (!osd) return;
897
898         if (flag >= IMAGE_OSD_COUNT) return;
899         if (duration < 0) duration = IMAGE_OSD_DEFAULT_DURATION;
900         if (duration > 1) duration += 1;
901
902         osd->icon_time[flag] = duration;
903
904         image_osd_update_schedule(osd, FALSE);
905         image_osd_timer_schedule(osd);
906 }
907 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */