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