485168a6bf007feac7f40cd3c6f16315968b29b6
[geeqie.git] / src / info.c
1 /*
2  * Geeqie
3  * (C) 2004 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
14 #include "main.h"
15 #include "info.h"
16
17 #include "bar_info.h"
18 #include "bar_exif.h"
19 #include "dnd.h"
20 #include "filedata.h"
21 #include "image.h"
22 #include "image-load.h"
23 #include "pixbuf-renderer.h"
24 #include "ui_fileops.h"
25 #include "ui_misc.h"
26 #include "uri_utils.h"
27 #include "window.h"
28
29 #include <pwd.h>
30 #include <grp.h>
31
32
33 #define IMAGE_SIZE_W 200
34 #define IMAGE_SIZE_H 200
35
36
37 typedef struct _TabData TabData;
38 struct _TabData
39 {
40         void (*func_free)(gpointer data);
41         void (*func_sync)(InfoData *id, gpointer data);
42         void (*func_image)(InfoData *id, gpointer data);
43         gpointer data;
44         TabData *(*func_new)(InfoData *id);
45         GtkWidget *child;
46 };
47
48 typedef struct _InfoTabsPos InfoTabsPos;
49 struct _InfoTabsPos
50 {
51         TabData *(*func)(InfoData *id);
52         guint pos;
53 };
54
55 static GList *info_tabs_pos_list = NULL;
56
57 static void notebook_set_tab_reorderable(GtkNotebook *notebook, GtkWidget *child, gboolean reorderable)
58 {
59 #if GTK_CHECK_VERSION(2,10,0)
60         gtk_notebook_set_tab_reorderable(notebook, child, reorderable);
61 #endif
62 }
63
64 /*
65  *-------------------------------------------------------------------
66  * table utils
67  *-------------------------------------------------------------------
68  */
69
70 GtkWidget *table_add_line(GtkWidget *table, gint x, gint y,
71                           const gchar *description, const gchar *text)
72 {
73         GtkWidget *label;
74
75         if (!text) text = "";
76
77         label = pref_table_label(table, x, y, description, 1.0);
78         pref_label_bold(label, TRUE, FALSE);
79
80         label = pref_table_label(table, x + 1, y, text, 0.0);
81         return label;
82 }
83
84 /*
85  *-------------------------------------------------------------------
86  * EXIF tab
87  *-------------------------------------------------------------------
88  */
89
90 static void info_tab_exif_image(InfoData *id, gpointer data)
91 {
92         GtkWidget *bar = data;
93         FileData *fd;
94
95         if (id->image->unknown)
96                 {
97                 fd = NULL;
98                 }
99         else
100                 {
101                 fd = id->image->image_fd;
102                 }
103
104         bar_exif_set(bar, fd);
105 }
106
107 static void info_tab_exif_sync(InfoData *id, gpointer data)
108 {
109         GtkWidget *bar = data;
110
111         bar_exif_set(bar, NULL);
112 }
113
114 static TabData *info_tab_exif_new(InfoData *id)
115 {
116         TabData *td;
117         GtkWidget *bar;
118         GtkWidget *label;
119
120         bar = bar_exif_new(FALSE, NULL, FALSE, NULL);
121         gtk_container_set_border_width(GTK_CONTAINER(bar), PREF_PAD_BORDER);
122
123         label = gtk_label_new(_("Exif"));
124         gtk_notebook_append_page(GTK_NOTEBOOK(id->notebook), bar, label);
125         notebook_set_tab_reorderable(GTK_NOTEBOOK(id->notebook), bar, TRUE);
126         gtk_widget_show(bar);
127
128         /* register */
129         td = g_new0(TabData, 1);
130         td->func_free = NULL;
131         td->func_sync = info_tab_exif_sync;
132         td->func_image = info_tab_exif_image;
133         td->data = bar;
134         td->func_new = info_tab_exif_new;
135         td->child = bar;
136
137         return td;
138 }
139
140 /*
141  *-------------------------------------------------------------------
142  * file attributes tab
143  *-------------------------------------------------------------------
144  */
145
146 typedef struct _InfoTabMeta InfoTabMeta;
147 struct _InfoTabMeta
148 {
149         GtkWidget *bar_info;
150 };
151
152 static void info_tab_meta_free(gpointer data)
153 {
154         InfoTabMeta *tab = data;
155
156         g_free(tab);
157 }
158
159 static void info_tab_meta_sync(InfoData *id, gpointer data)
160 {
161         InfoTabMeta *tab = data;
162
163         bar_info_set(tab->bar_info, id->fd);
164 }
165
166 static GList *info_tab_meta_list_cb(gpointer data)
167 {
168         InfoData *id = data;
169
170         return filelist_copy(id->list);
171 }
172
173 static TabData *info_tab_meta_new(InfoData *id)
174 {
175         TabData *td;
176         InfoTabMeta *tab;
177         GtkWidget *label;
178
179         tab = g_new0(InfoTabMeta, 1);
180
181         tab->bar_info = bar_info_new(NULL, TRUE, NULL);
182         bar_info_set_selection_func(tab->bar_info, info_tab_meta_list_cb, id);
183         bar_info_selection(tab->bar_info, g_list_length(id->list) - 1);
184
185         gtk_container_set_border_width(GTK_CONTAINER(tab->bar_info), PREF_PAD_BORDER);
186
187         label = gtk_label_new(_("Keywords"));
188         gtk_notebook_append_page(GTK_NOTEBOOK(id->notebook), tab->bar_info, label);
189         notebook_set_tab_reorderable(GTK_NOTEBOOK(id->notebook), tab->bar_info, TRUE);
190         gtk_widget_show(tab->bar_info);
191
192         /* register */
193         td = g_new0(TabData, 1);
194         td->func_free = info_tab_meta_free;
195         td->func_sync = info_tab_meta_sync;
196         td->func_image = NULL;
197         td->data = tab;
198         td->func_new = info_tab_meta_new;
199         td->child = tab->bar_info;
200
201         return td;
202 }
203
204 /*
205  *-------------------------------------------------------------------
206  * general tab
207  *-------------------------------------------------------------------
208  */
209
210 typedef struct _InfoTabGeneral InfoTabGeneral;
211 struct _InfoTabGeneral
212 {
213         GtkWidget *label_file_time;
214         GtkWidget *label_file_size;
215         GtkWidget *label_dimensions;
216         GtkWidget *label_transparent;
217         GtkWidget *label_image_size;
218         GtkWidget *label_compression;
219         GtkWidget *label_mime_type;
220
221         GtkWidget *label_user;
222         GtkWidget *label_group;
223         GtkWidget *label_perms;
224
225         gint compression_done;
226         gint64 byte_size;
227 };
228
229 static void info_tab_general_image(InfoData *id, gpointer data)
230 {
231         InfoTabGeneral *tab = data;
232         gchar *buf;
233         guint mem_size;
234         gint has_alpha;
235         GdkPixbuf *pixbuf;
236         gint width, height;
237
238         if (id->image->unknown) return;
239
240         image_get_image_size(id->image, &width, &height);
241
242         buf = g_strdup_printf("%d x %d", width, height);
243         gtk_label_set_text(GTK_LABEL(tab->label_dimensions), buf);
244         g_free(buf);
245
246         pixbuf = image_get_pixbuf(id->image);
247         if (pixbuf)
248                 {
249                 has_alpha = gdk_pixbuf_get_has_alpha(pixbuf);
250                 }
251         else
252                 {
253                 has_alpha = FALSE;
254                 }
255         gtk_label_set_text(GTK_LABEL(tab->label_transparent), has_alpha ? _("yes") : _("no"));
256
257         mem_size = width * height * ((has_alpha) ? 4 : 3);
258         buf = text_from_size_abrev(mem_size);
259         gtk_label_set_text(GTK_LABEL(tab->label_image_size), buf);
260         g_free(buf);
261
262         if (!tab->compression_done && mem_size > 0)
263                 {
264                 buf = g_strdup_printf("%.1f%%", (gdouble)tab->byte_size / mem_size * 100.0);
265                 gtk_label_set_text(GTK_LABEL(tab->label_compression), buf);
266                 g_free(buf);
267
268                 tab->compression_done = TRUE;
269                 }
270
271         buf = image_loader_get_format(id->image->il);
272         if (buf)
273         gtk_label_set_text(GTK_LABEL(tab->label_mime_type), buf);
274         g_free(buf);
275 }
276
277 static gchar *mode_number(mode_t m)
278 {
279         gint mb, mu, mg, mo;
280
281         mb = mu = mg = mo = 0;
282
283         if (m & S_ISUID) mb |= 4;
284         if (m & S_ISGID) mb |= 2;
285         if (m & S_ISVTX) mb |= 1;
286
287         if (m & S_IRUSR) mu |= 4;
288         if (m & S_IWUSR) mu |= 2;
289         if (m & S_IXUSR) mu |= 1;
290
291         if (m & S_IRGRP) mg |= 4;
292         if (m & S_IWGRP) mg |= 2;
293         if (m & S_IXGRP) mg |= 1;
294
295         if (m & S_IROTH) mo |= 4;
296         if (m & S_IWOTH) mo |= 2;
297         if (m & S_IXOTH) mo |= 1;
298
299         return g_strdup_printf("%d%d%d%d", mb, mu, mg, mo);
300 }
301
302 static void info_tab_general_sync_perm(InfoTabGeneral *tab, InfoData *id)
303 {
304         struct stat st;
305
306         if (!stat_utf8(id->fd->path, &st))
307                 {
308                 gtk_label_set_text(GTK_LABEL(tab->label_user), "");
309                 gtk_label_set_text(GTK_LABEL(tab->label_group), "");
310                 gtk_label_set_text(GTK_LABEL(tab->label_perms), "");
311                 }
312         else
313                 {
314                 struct passwd *user;
315                 struct group *grp;
316                 gchar pbuf[12];
317                 gchar *pmod;
318                 gchar *buf;
319
320                 user = getpwuid(st.st_uid);
321                 gtk_label_set_text(GTK_LABEL(tab->label_user), (user) ? user->pw_name : "");
322
323                 grp = getgrgid(st.st_gid);
324                 gtk_label_set_text(GTK_LABEL(tab->label_group), (grp) ? grp->gr_name : "");
325
326                 pbuf[0] = (st.st_mode & S_IRUSR) ? 'r' : '-';
327                 pbuf[1] = (st.st_mode & S_IWUSR) ? 'w' : '-';
328                 pbuf[2] = (st.st_mode & S_IXUSR) ? 'x' : '-';
329                 pbuf[3] = (st.st_mode & S_IRGRP) ? 'r' : '-';
330                 pbuf[4] = (st.st_mode & S_IWGRP) ? 'w' : '-';
331                 pbuf[5] = (st.st_mode & S_IXGRP) ? 'x' : '-';
332                 pbuf[6] = (st.st_mode & S_IROTH) ? 'r' : '-';
333                 pbuf[7] = (st.st_mode & S_IWOTH) ? 'w' : '-';
334                 pbuf[8] = (st.st_mode & S_IXOTH) ? 'x' : '-';
335                 pbuf[9] = '\0';
336
337                 pmod = mode_number(st.st_mode);
338                 buf = g_strdup_printf("%s (%s)", pbuf, pmod);
339                 gtk_label_set_text(GTK_LABEL(tab->label_perms), buf);
340                 g_free(buf);
341                 g_free(pmod);
342                 }
343 }
344
345 static void info_tab_general_sync(InfoData *id, gpointer data)
346 {
347         InfoTabGeneral *tab = data;
348         gchar *buf;
349
350         gtk_label_set_text(GTK_LABEL(tab->label_file_time), text_from_time(id->fd->date));
351
352         tab->byte_size = id->fd->size;
353
354         buf = text_from_size(tab->byte_size);
355         gtk_label_set_text(GTK_LABEL(tab->label_file_size), buf);
356         g_free(buf);
357
358         gtk_label_set_text(GTK_LABEL(tab->label_dimensions), "");
359         gtk_label_set_text(GTK_LABEL(tab->label_transparent), "");
360         gtk_label_set_text(GTK_LABEL(tab->label_image_size), "");
361
362         gtk_label_set_text(GTK_LABEL(tab->label_compression), "");
363         gtk_label_set_text(GTK_LABEL(tab->label_mime_type), "");
364
365         info_tab_general_sync_perm(tab, id);
366
367         tab->compression_done = FALSE;
368 }
369
370 static void info_tab_general_free(gpointer data)
371 {
372         InfoTabGeneral *tab = data;
373
374         g_free(tab);
375 }
376
377 static TabData *info_tab_general_new(InfoData *id)
378 {
379         TabData *td;
380         InfoTabGeneral *tab;
381         GtkWidget *table;
382         GtkWidget *label;
383
384         tab = g_new0(InfoTabGeneral, 1);
385
386         table = pref_table_new(NULL, 2, 11, FALSE, FALSE);
387         gtk_container_set_border_width(GTK_CONTAINER(table), PREF_PAD_BORDER);
388
389         tab->label_file_time = table_add_line(table, 0, 0, _("File date:"), NULL);
390         tab->label_file_size = table_add_line(table, 0, 1, _("File size:"), NULL);
391
392         tab->label_dimensions = table_add_line(table, 0, 2, _("Dimensions:"), NULL);
393         tab->label_transparent = table_add_line(table, 0, 3, _("Transparent:"), NULL);
394         tab->label_image_size = table_add_line(table, 0, 4, _("Image size:"), NULL);
395
396         tab->label_compression = table_add_line(table, 0, 5, _("Compress ratio:"), NULL);
397         tab->label_mime_type = table_add_line(table, 0, 6, _("File type:"), NULL);
398
399         tab->label_user = table_add_line(table, 0, 7, _("Owner:"), NULL);
400         tab->label_group = table_add_line(table, 0, 8, _("Group:"), NULL);
401         tab->label_perms = table_add_line(table, 0, 9, "", NULL);
402
403         label = gtk_label_new(_("General"));
404         gtk_notebook_append_page(GTK_NOTEBOOK(id->notebook), table, label);
405         notebook_set_tab_reorderable(GTK_NOTEBOOK(id->notebook), table, TRUE);
406         gtk_widget_show(table);
407
408         /* register */
409         td = g_new0(TabData, 1);
410         td->func_free = info_tab_general_free;
411         td->func_sync = info_tab_general_sync;
412         td->func_image = info_tab_general_image;
413         td->data = tab;
414         td->func_new = info_tab_general_new;
415         td->child = table;
416
417         return td;
418 }
419
420 /*
421  *-------------------------------------------------------------------
422  * tabs
423  *-------------------------------------------------------------------
424  */
425
426 static void info_tabs_sync(InfoData *id, gint image)
427 {
428         GList *work;
429
430         work = id->tab_list;
431         while (work)
432                 {
433                 TabData *td = work->data;
434                 work = work->next;
435
436                 if (image)
437                         {
438                         if (td->func_image) td->func_image(id, td->data);
439                         }
440                 else
441                         {
442                         if (td->func_sync) td->func_sync(id, td->data);
443                         }
444                 }
445 }
446
447 static void info_tabs_free(InfoData *id)
448 {
449         GList *work;
450
451         work = id->tab_list;
452         while (work)
453                 {
454                 TabData *td = work->data;
455                 work = work->next;
456
457                 if (td->func_free) td->func_free(td->data);
458                 g_free(td);
459                 }
460         g_list_free(id->tab_list);
461         id->tab_list = NULL;
462 }
463
464 static InfoTabsPos *info_tabs_pos_new(gpointer func, gint pos)
465 {
466         InfoTabsPos *t = g_new0(InfoTabsPos, 1);
467         t->func = func;
468         t->pos = pos;
469
470         return t;
471 }
472
473 static void info_tabs_pos_list_append(gpointer func)
474 {
475         static gint pos = 0;
476
477         info_tabs_pos_list = g_list_append(info_tabs_pos_list, info_tabs_pos_new(func, pos++));
478 }
479
480 static gint compare_info_tabs_pos(gconstpointer a, gconstpointer b)
481 {
482         InfoTabsPos *ta = (InfoTabsPos *) a;
483         InfoTabsPos *tb = (InfoTabsPos *) b;
484
485         if (ta->pos > tb->pos) return 1;
486         return -1;
487 }
488
489 static gpointer info_tab_new_funcs[] = {
490         info_tab_general_new,
491         info_tab_meta_new,
492         info_tab_exif_new,
493 };
494
495 gchar *info_tab_default_order(void)
496 {
497         guint i;
498         static gchar str[G_N_ELEMENTS(info_tab_new_funcs) + 1];
499
500         for (i = 0; i < G_N_ELEMENTS(info_tab_new_funcs); i++)
501                 str[i] = i + '1';
502         str[i] = '\0';
503
504         return str;
505 }
506
507 static void info_tab_get_order_string(gchar **dest)
508 {
509         GList *work;
510         gchar str[G_N_ELEMENTS(info_tab_new_funcs) + 1];
511
512         g_assert(dest);
513
514         if (!info_tabs_pos_list)
515                 return;
516         
517         memset(str, 0, G_N_ELEMENTS(info_tab_new_funcs) + 1);
518
519         work = info_tabs_pos_list;
520         while (work)
521                 {
522                 guint i;
523                 InfoTabsPos *t = work->data;
524                 work = work->next;
525
526                 for (i = 0; i < G_N_ELEMENTS(info_tab_new_funcs); i++)
527                         {
528                         if (t->func == info_tab_new_funcs[i])
529                                 {
530                                 g_assert(t->pos < G_N_ELEMENTS(info_tab_new_funcs));
531                                 str[t->pos] = i + '1';
532                                 }
533                         }
534                 }
535         
536         if (strlen(str) != G_N_ELEMENTS(info_tab_new_funcs)) return;
537
538         g_free(*dest);
539         *dest = g_strdup(str);
540 }
541
542 static void info_tabs_init(InfoData *id)
543 {
544         GList *work;
545
546         if (!info_tabs_pos_list)
547                 {
548                 guint count = 0;
549                 guint i;
550                 gchar *order = options->properties.tabs_order;
551                 
552                 for (i = 0; i < strlen(order); i++)
553                         {
554                         guint n = order[i] - '1';
555
556                         if (n >= G_N_ELEMENTS(info_tab_new_funcs)) break;
557                         count++;
558                         }
559
560                 if (count != G_N_ELEMENTS(info_tab_new_funcs))
561                         order = info_tab_default_order();
562
563                 for (i = 0; i < strlen(order); i++)
564                         {
565                         guint n = order[i] - '1';
566
567                         if (n >= G_N_ELEMENTS(info_tab_new_funcs)) continue;
568                         if (g_list_find(info_tabs_pos_list, info_tab_new_funcs[n])) continue;
569                         info_tabs_pos_list_append(info_tab_new_funcs[n]);
570                         }
571                 }
572         else
573                 info_tabs_pos_list = g_list_sort(info_tabs_pos_list, compare_info_tabs_pos);
574
575         info_tab_get_order_string(&options->properties.tabs_order);
576
577         work = info_tabs_pos_list;
578         while (work)
579                 {
580                 InfoTabsPos *t = work->data;
581                 work = work->next;
582
583                 id->tab_list = g_list_append(id->tab_list, t->func(id));
584                 }
585 }
586
587 /*
588  *-------------------------------------------------------------------
589  * sync
590  *-------------------------------------------------------------------
591  */
592
593 static void info_window_sync(InfoData *id, FileData *fd)
594 {
595
596         if (!fd) return;
597
598         gtk_entry_set_text(GTK_ENTRY(id->name_entry), fd->name);
599
600         if (id->label_count)
601                 {
602                 gchar *buf;
603                 buf = g_strdup_printf(_("Image %d of %d"),
604                                       g_list_index(id->list, (gpointer)fd) + 1,
605                                       g_list_length(id->list));
606                 gtk_label_set_text(GTK_LABEL(id->label_count), buf);
607                 g_free(buf);
608                 }
609
610         info_tabs_sync(id, FALSE);
611
612         id->updated = FALSE;
613         image_change_fd(id->image, fd, 0.0);
614 }
615
616 static void info_notebook_reordered_cb(GtkNotebook *notebook, GtkWidget *child, guint page_num, gpointer data)
617 {
618         InfoData *id = data;
619         GList *work;
620
621         /* Save current tabs position to be able to restore them later. */
622         work = id->tab_list;
623         while (work)
624                 {
625                 GList *tabpos;
626                 TabData *td = work->data;
627                 gint pos = gtk_notebook_page_num(GTK_NOTEBOOK(id->notebook), GTK_WIDGET(td->child));
628                 work = work->next;
629
630                 tabpos = info_tabs_pos_list;
631                 while (tabpos)
632                         {
633                         InfoTabsPos *t = tabpos->data;
634                         tabpos = tabpos->next;
635
636                         if (t->func == td->func_new)
637                                 {
638                                 t->pos = pos;
639                                 break;
640                                 }
641                         }
642                 }
643         
644         info_tabs_pos_list = g_list_sort(info_tabs_pos_list, compare_info_tabs_pos);
645         info_tab_get_order_string(&options->properties.tabs_order);
646 }
647
648 /*
649  *-------------------------------------------------------------------
650  * drag n drop (dropping not supported!)
651  *-------------------------------------------------------------------
652  */
653
654 static void info_window_dnd_data_set(GtkWidget *widget, GdkDragContext *context,
655                                      GtkSelectionData *selection_data, guint info,
656                                      guint time, gpointer data)
657 {
658         InfoData *id = data;
659         FileData *fd;
660
661         fd = image_get_fd(id->image);
662         if (fd)
663                 {
664                 gchar *text;
665                 gint len;
666                 GList *list;
667                 gint plain_text;
668
669                 switch (info)
670                         {
671                         case TARGET_URI_LIST:
672                                 plain_text = FALSE;
673                                 break;
674                         case TARGET_TEXT_PLAIN:
675                         default:
676                                 plain_text = TRUE;
677                                 break;
678                         }
679                 list = g_list_append(NULL, fd);
680                 text = uri_text_from_filelist(list, &len, plain_text);
681                 g_list_free(list);
682
683                 gtk_selection_data_set(selection_data, selection_data->target,
684                                        8, (guchar *)text, len);
685                 g_free(text);
686                 }
687 }
688
689 static void info_window_dnd_init(InfoData *id)
690 {
691         ImageWindow *imd;
692
693         imd = id->image;
694
695         gtk_drag_source_set(imd->pr, GDK_BUTTON2_MASK,
696                             dnd_file_drag_types, dnd_file_drag_types_count,
697                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
698         g_signal_connect(G_OBJECT(imd->pr), "drag_data_get",
699                          G_CALLBACK(info_window_dnd_data_set), id);
700
701 #if 0
702         gtk_drag_dest_set(imd->pr,
703                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
704                           dnd_file_drop_types, dnd_file_drop_types_count,
705                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
706         g_signal_connect(G_OBJECT(imd->pr), "drag_data_received",
707                          G_CALLBACK(info_window_dnd_data_get), id);
708 #endif
709 }
710
711 /*
712  *-------------------------------------------------------------------
713  * base window
714  *-------------------------------------------------------------------
715  */
716
717 static void info_window_image_update_cb(ImageWindow *imd, gpointer data)
718 {
719         InfoData *id = data;
720
721         /* only do this once after when loading a new image,
722          * for tabs that depend on image data (exif)
723          * Subsequent updates are ignored, as the image
724          * should not really changed if id->updated is TRUE.
725          */
726
727         if (id->updated) return;
728         if (imd->unknown) return;
729
730         info_tabs_sync(id, TRUE);
731         id->updated = TRUE;
732 }
733
734 static void info_window_back_cb(GtkWidget *widget, gpointer data)
735 {
736         InfoData *id = data;
737         GList *work;
738
739         work = g_list_find(id->list, (gpointer)id->fd);
740         if (!work || !work->prev) return;
741
742         work = work->prev;
743         id->fd = work->data;
744
745         info_window_sync(id, id->fd);
746
747         gtk_widget_set_sensitive(id->button_back, (work->prev != NULL));
748         gtk_widget_set_sensitive(id->button_next, TRUE);
749 }
750
751 static void info_window_next_cb(GtkWidget *widget, gpointer data)
752 {
753         InfoData *id = data;
754         GList *work;
755
756         work = g_list_find(id->list, (gpointer)id->fd);
757         if (!work || !work->next) return;
758
759         work = work->next;
760         id->fd = work->data;
761
762         info_window_sync(id, id->fd);
763
764         gtk_widget_set_sensitive(id->button_next, (work->next != NULL));
765         gtk_widget_set_sensitive(id->button_back, TRUE);
766 }
767
768 static void info_window_image_button_cb(ImageWindow *imd, GdkEventButton *event, gpointer data)
769 {
770         if (event->button == MOUSE_BUTTON_LEFT)
771                 {
772                 info_window_next_cb(NULL, data);
773                 }
774         else if (event->button == MOUSE_BUTTON_MIDDLE || event->button == MOUSE_BUTTON_RIGHT)
775                 {
776                 info_window_back_cb(NULL, data);
777                 }
778 }
779
780 static void info_window_image_scroll_cb(ImageWindow *imd, GdkEventScroll *event, gpointer data)
781 {
782         if (event->direction == GDK_SCROLL_UP)
783                 {
784                 info_window_back_cb(NULL, data);
785                 }
786         else if (event->direction == GDK_SCROLL_DOWN)
787                 {
788                 info_window_next_cb(NULL, data);
789                 }
790 }
791
792 static void info_window_close(InfoData *id)
793 {
794         gdk_drawable_get_size(id->window->window, &options->layout.properties_window.w, &options->layout.properties_window.h);
795         options->layout.properties_window.w = MAX(options->layout.properties_window.w, DEF_PROPERTY_WIDTH);
796         options->layout.properties_window.h = MAX(options->layout.properties_window.h, DEF_PROPERTY_HEIGHT);
797
798         gtk_widget_destroy(id->window);
799 }
800
801 static void info_window_close_cb(GtkWidget *widget, gpointer data)
802 {
803         InfoData *id = data;
804
805         info_window_close(id);
806 }
807
808 static gint info_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
809 {
810         InfoData *id = data;
811
812         info_window_close(id);
813         return TRUE;
814 }
815
816 static void info_window_destroy_cb(GtkWidget *widget, gpointer data)
817 {
818         InfoData *id = data;
819
820         info_tabs_free(id);
821         filelist_free(id->list);
822         g_free(id);
823 }
824
825 void info_window_new(FileData *fd, GList *list, GtkWidget *parent)
826 {
827         InfoData *id;
828         GtkWidget *main_vbox;
829         GtkWidget *paned;
830         GtkWidget *hbox;
831         GtkWidget *button;
832         GtkWidget *label;
833         GdkGeometry geometry;
834         static gboolean run_once = FALSE;
835
836         if (!fd && !list) return;
837
838         run_once = TRUE;
839
840         if (!list)
841                 {
842                 list = g_list_append(NULL, file_data_ref(fd));
843                 }
844
845         id = g_new0(InfoData, 1);
846
847         id->list = list;
848         id->fd = (FileData *)id->list->data;
849         id->updated = FALSE;
850
851         id->window = window_new(GTK_WINDOW_TOPLEVEL, "properties", NULL, NULL, _("Image properties"));
852         gtk_window_set_type_hint(GTK_WINDOW(id->window), GDK_WINDOW_TYPE_HINT_DIALOG);
853         id->parent = parent;
854         if (GTK_IS_WINDOW(id->parent)) {
855                 gtk_window_set_keep_above(GTK_WINDOW(id->window), TRUE);
856 #if 0
857                 /* work, but behavior is not that great */
858                 gtk_window_set_transient_for(GTK_WINDOW(id->window), GTK_WINDOW(id->parent));
859 #endif
860         }
861         gtk_window_set_resizable(GTK_WINDOW(id->window), TRUE);
862
863         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
864         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
865         geometry.base_width = DEF_PROPERTY_WIDTH;
866         geometry.base_height = DEF_PROPERTY_HEIGHT;
867         gtk_window_set_geometry_hints(GTK_WINDOW(id->window), NULL, &geometry,
868                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
869
870         if (options->layout.save_window_positions || run_once)
871                 gtk_window_set_default_size(GTK_WINDOW(id->window), options->layout.properties_window.w, options->layout.properties_window.h);
872         else
873                 gtk_window_set_default_size(GTK_WINDOW(id->window), DEF_PROPERTY_WIDTH, DEF_PROPERTY_HEIGHT);
874
875         gtk_container_set_border_width(GTK_CONTAINER(id->window), PREF_PAD_BORDER);
876
877         g_signal_connect(G_OBJECT(id->window), "delete_event",
878                          G_CALLBACK(info_window_delete_cb), id);
879         g_signal_connect(G_OBJECT(id->window), "destroy",
880                          G_CALLBACK(info_window_destroy_cb), id);
881
882         paned = gtk_hpaned_new();
883         gtk_container_add(GTK_CONTAINER(id->window), paned);
884         gtk_widget_show(paned);
885
886         id->image = image_new(FALSE);
887         image_set_update_func(id->image, info_window_image_update_cb, id);
888
889         image_set_button_func(id->image, info_window_image_button_cb, id);
890         image_set_scroll_func(id->image, info_window_image_scroll_cb, id);
891
892         gtk_widget_set_size_request(id->image->widget, IMAGE_SIZE_W, IMAGE_SIZE_H);
893         gtk_paned_pack1(GTK_PANED(paned), id->image->widget, FALSE, TRUE);
894         gtk_widget_show(id->image->widget);
895
896         main_vbox = gtk_vbox_new(FALSE, 0);
897         gtk_paned_pack2(GTK_PANED(paned), main_vbox, TRUE, TRUE);
898         gtk_widget_show(main_vbox);
899
900         hbox = pref_box_new(main_vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
901         label = pref_label_new(hbox, _("Filename:"));
902         pref_label_bold(label, TRUE, FALSE);
903
904         id->name_entry = gtk_entry_new();
905         gtk_editable_set_editable(GTK_EDITABLE(id->name_entry), FALSE);
906         gtk_box_pack_start(GTK_BOX(hbox), id->name_entry, TRUE, TRUE, 0);
907         gtk_widget_show(id->name_entry);
908
909         id->notebook = gtk_notebook_new();
910         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(id->notebook), GTK_POS_TOP);
911         gtk_box_pack_start(GTK_BOX(main_vbox), id->notebook, TRUE, TRUE, 5);
912         g_signal_connect(G_OBJECT(id->notebook), "page-reordered",
913                          G_CALLBACK(info_notebook_reordered_cb), id);
914
915         gtk_widget_show(id->notebook);
916
917         pref_spacer(main_vbox, PREF_PAD_GAP);
918
919         hbox = pref_box_new(main_vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
920
921         id->button_back = pref_button_new(hbox, GTK_STOCK_GO_BACK, NULL, TRUE,
922                                           G_CALLBACK(info_window_back_cb), id);
923         gtk_widget_set_sensitive(id->button_back, FALSE);
924
925         id->button_next = pref_button_new(hbox, GTK_STOCK_GO_FORWARD, NULL, TRUE,
926                                           G_CALLBACK(info_window_next_cb), id);
927         gtk_widget_set_sensitive(id->button_next, (id->list->next != NULL));
928
929         if (id->list->next)
930                 {
931                 id->label_count = pref_label_new(hbox, "");
932                 }
933
934         button = pref_button_new(NULL, GTK_STOCK_CLOSE, NULL, FALSE,
935                                  G_CALLBACK(info_window_close_cb), id);
936         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
937         gtk_widget_show(button);
938
939         /* set up tabs */
940
941         info_tabs_init(id);
942
943         /* fill it */
944
945         info_window_sync(id, id->fd);
946
947         /* finish */
948
949         info_window_dnd_init(id);
950
951         gtk_widget_show(id->window);
952 }
953 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */