96b3535fdfb8477389aa06c4332ce744ce8c32bf
[geeqie.git] / src / info.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12
13 #include "main.h"
14 #include "info.h"
15
16 #include "bar_info.h"
17 #include "bar_exif.h"
18 #include "dnd.h"
19 #include "filelist.h"
20 #include "image.h"
21 #include "image-load.h"
22 #include "pixbuf-renderer.h"
23 #include "ui_bookmark.h"
24 #include "ui_fileops.h"
25 #include "ui_misc.h"
26
27 #include <pwd.h>
28 #include <grp.h>
29
30
31 #define IMAGE_SIZE_W 200
32 #define IMAGE_SIZE_H 200
33
34 #define DEF_PROPERTY_WIDTH  600
35 #define DEF_PROPERTY_HEIGHT 400
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         gint 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 path_list_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         pixbuf_renderer_get_image_size(PIXBUF_RENDERER(id->image->pr), &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%%", (double)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         int 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 void info_tabs_init(InfoData *id)
490 {
491         GList *work;
492
493         if (!info_tabs_pos_list)
494                 {
495                 /* First run, default tabs order is defined here. */
496                 info_tabs_pos_list_append(info_tab_general_new);
497                 info_tabs_pos_list_append(info_tab_meta_new);
498                 info_tabs_pos_list_append(info_tab_exif_new);
499                 }
500         else
501                 info_tabs_pos_list = g_list_sort(info_tabs_pos_list, compare_info_tabs_pos);
502
503         work = info_tabs_pos_list;
504         while (work)
505                 {
506                 InfoTabsPos *t = work->data;
507                 work = work->next;
508
509                 id->tab_list = g_list_append(id->tab_list, t->func(id));
510                 }
511 }
512
513 /*
514  *-------------------------------------------------------------------
515  * sync
516  *-------------------------------------------------------------------
517  */
518
519 static void info_window_sync(InfoData *id, FileData *fd)
520 {
521
522         if (!fd) return;
523
524         gtk_entry_set_text(GTK_ENTRY(id->name_entry), fd->name);
525
526         if (id->label_count)
527                 {
528                 gchar *buf;
529                 buf = g_strdup_printf(_("Image %d of %d"),
530                                       g_list_index(id->list, (gpointer)fd) + 1,
531                                       g_list_length(id->list));
532                 gtk_label_set_text(GTK_LABEL(id->label_count), buf);
533                 g_free(buf);
534                 }
535
536         info_tabs_sync(id, FALSE);
537
538         id->updated = FALSE;
539         image_change_fd(id->image, fd, 0.0);
540 }
541
542 static void info_notebook_reordered_cb(GtkNotebook *notebook, GtkWidget *child, guint page_num, gpointer data)
543 {
544         InfoData *id = data;
545         GList *work;
546
547         info_tabs_sync(id, 0);
548
549         /* Save current tabs position to be able to restore them later. */
550         work = id->tab_list;
551         while (work)
552                 {
553                 GList *tabpos;
554                 TabData *td = work->data;
555                 gint pos = gtk_notebook_page_num(GTK_NOTEBOOK(id->notebook), GTK_WIDGET(td->child));
556                 work = work->next;
557
558                 tabpos = info_tabs_pos_list;
559                 while (tabpos)
560                         {
561                         InfoTabsPos *t = tabpos->data;
562                         tabpos = tabpos->next;
563
564                         if (t->func == td->func_new)
565                                 {
566                                 t->pos = pos;
567                                 break;
568                                 }
569                         }
570         }
571 }
572
573 /*
574  *-------------------------------------------------------------------
575  * drag n drop (dropping not supported!)
576  *-------------------------------------------------------------------
577  */
578
579 static void info_window_dnd_data_set(GtkWidget *widget, GdkDragContext *context,
580                                      GtkSelectionData *selection_data, guint info,
581                                      guint time, gpointer data)
582 {
583         InfoData *id = data;
584         FileData *fd;
585
586         fd = image_get_fd(id->image);
587         if (fd)
588                 {
589                 gchar *text;
590                 gint len;
591                 GList *list;
592                 gint plain_text;
593
594                 switch (info)
595                         {
596                         case TARGET_URI_LIST:
597                                 plain_text = FALSE;
598                                 break;
599                         case TARGET_TEXT_PLAIN:
600                         default:
601                                 plain_text = TRUE;
602                                 break;
603                         }
604                 list = g_list_append(NULL, fd);
605                 text = uri_text_from_filelist(list, &len, plain_text);
606                 g_list_free(list);
607
608                 gtk_selection_data_set(selection_data, selection_data->target,
609                                        8, (guchar *)text, len);
610                 g_free(text);
611                 }
612 }
613
614 static void info_window_dnd_init(InfoData *id)
615 {
616         ImageWindow *imd;
617
618         imd = id->image;
619
620         gtk_drag_source_set(imd->pr, GDK_BUTTON2_MASK,
621                             dnd_file_drag_types, dnd_file_drag_types_count,
622                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
623         g_signal_connect(G_OBJECT(imd->pr), "drag_data_get",
624                          G_CALLBACK(info_window_dnd_data_set), id);
625
626 #if 0
627         gtk_drag_dest_set(imd->pr,
628                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
629                           dnd_file_drop_types, dnd_file_drop_types_count,
630                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
631         g_signal_connect(G_OBJECT(imd->pr), "drag_data_received",
632                          G_CALLBACK(info_window_dnd_data_get), id);
633 #endif
634 }
635
636 /*
637  *-------------------------------------------------------------------
638  * base window
639  *-------------------------------------------------------------------
640  */
641
642 static gint info_window_last_width = DEF_PROPERTY_WIDTH;
643 static gint info_window_last_height = DEF_PROPERTY_HEIGHT;
644
645 static void info_window_image_update_cb(ImageWindow *imd, gpointer data)
646 {
647         InfoData *id = data;
648
649         /* only do this once after when loading a new image,
650          * for tabs that depend on image data (exif)
651          * Subsequent updates are ignored, as the image
652          * should not really changed if id->updated is TRUE.
653          */
654
655         if (id->updated) return;
656         if (imd->unknown) return;
657
658         info_tabs_sync(id, TRUE);
659         id->updated = TRUE;
660 }
661
662 static void info_window_back_cb(GtkWidget *widget, gpointer data)
663 {
664         InfoData *id = data;
665         GList *work;
666
667         work = g_list_find(id->list, (gpointer)id->fd);
668         if (!work || !work->prev) return;
669
670         work = work->prev;
671         id->fd = work->data;
672
673         info_window_sync(id, id->fd);
674
675         gtk_widget_set_sensitive(id->button_back, (work->prev != NULL));
676         gtk_widget_set_sensitive(id->button_next, TRUE);
677 }
678
679 static void info_window_next_cb(GtkWidget *widget, gpointer data)
680 {
681         InfoData *id = data;
682         GList *work;
683
684         work = g_list_find(id->list, (gpointer)id->fd);
685         if (!work || !work->next) return;
686
687         work = work->next;
688         id->fd = work->data;
689
690         info_window_sync(id, id->fd);
691
692         gtk_widget_set_sensitive(id->button_next, (work->next != NULL));
693         gtk_widget_set_sensitive(id->button_back, TRUE);
694 }
695
696 static void info_window_image_button_cb(ImageWindow *imd, gint button, guint32 time,
697                                         gdouble x, gdouble y, guint state, gpointer data)
698 {
699         if (button == MOUSE_BUTTON_LEFT)
700                 {
701                 info_window_next_cb(NULL, data);
702                 }
703         else if (button == MOUSE_BUTTON_MIDDLE || button == MOUSE_BUTTON_RIGHT)
704                 {
705                 info_window_back_cb(NULL, data);
706                 }
707 }
708
709 static void info_window_image_scroll_cb(ImageWindow *imd, GdkScrollDirection direction, guint32 time,
710                                         gdouble x, gdouble y, guint state, gpointer data)
711 {
712         if (direction == GDK_SCROLL_UP)
713                 {
714                 info_window_back_cb(NULL, data);
715                 }
716         else if (direction == GDK_SCROLL_DOWN)
717                 {
718                 info_window_next_cb(NULL, data);
719                 }
720 }
721
722 static void info_window_close(InfoData *id)
723 {
724         gdk_drawable_get_size(id->window->window, &info_window_last_width, &info_window_last_height);
725         info_window_last_width = MAX(info_window_last_width, DEF_PROPERTY_WIDTH);
726         info_window_last_height = MAX(info_window_last_height, DEF_PROPERTY_HEIGHT);
727
728         gtk_widget_destroy(id->window);
729 }
730
731 static void info_window_close_cb(GtkWidget *widget, gpointer data)
732 {
733         InfoData *id = data;
734
735         info_window_close(id);
736 }
737
738 static gint info_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
739 {
740         InfoData *id = data;
741
742         info_window_close(id);
743         return TRUE;
744 }
745
746 static void info_window_destroy_cb(GtkWidget *widget, gpointer data)
747 {
748         InfoData *id = data;
749
750         info_tabs_free(id);
751         filelist_free(id->list);
752         g_free(id);
753 }
754
755 void info_window_new(FileData *fd, GList *list)
756 {
757         InfoData *id;
758         GtkWidget *main_vbox;
759         GtkWidget *paned;
760         GtkWidget *hbox;
761         GtkWidget *button;
762         GtkWidget *label;
763         GdkGeometry geometry;
764
765         if (!fd && !list) return;
766
767         if (!list)
768                 {
769                 list = g_list_append(NULL, file_data_ref(fd));
770                 }
771
772         id = g_new0(InfoData, 1);
773
774         id->list = list;
775         id->fd = (FileData *)id->list->data;
776         id->updated = FALSE;
777
778         id->window = window_new(GTK_WINDOW_TOPLEVEL, "properties", NULL, NULL, _("Image properties"));
779         gtk_window_set_type_hint(GTK_WINDOW(id->window), GDK_WINDOW_TYPE_HINT_DIALOG);
780
781         gtk_window_set_resizable(GTK_WINDOW(id->window), TRUE);
782
783         geometry.min_width = 32;
784         geometry.min_height = 32;
785         geometry.base_width = DEF_PROPERTY_WIDTH;
786         geometry.base_height = DEF_PROPERTY_HEIGHT;
787         gtk_window_set_geometry_hints(GTK_WINDOW(id->window), NULL, &geometry,
788                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
789
790
791         gtk_window_set_default_size(GTK_WINDOW(id->window), info_window_last_width, info_window_last_height);
792         gtk_container_set_border_width(GTK_CONTAINER(id->window), PREF_PAD_BORDER);
793
794         g_signal_connect(G_OBJECT(id->window), "delete_event",
795                          G_CALLBACK(info_window_delete_cb), id);
796         g_signal_connect(G_OBJECT(id->window), "destroy",
797                          G_CALLBACK(info_window_destroy_cb), id);
798
799         paned = gtk_hpaned_new();
800         gtk_container_add(GTK_CONTAINER(id->window), paned);
801         gtk_widget_show(paned);
802
803         id->image = image_new(FALSE);
804         image_set_update_func(id->image, info_window_image_update_cb, id);
805
806         image_set_button_func(id->image, info_window_image_button_cb, id);
807         image_set_scroll_func(id->image, info_window_image_scroll_cb, id);
808
809         gtk_widget_set_size_request(id->image->widget, IMAGE_SIZE_W, IMAGE_SIZE_H);
810         gtk_paned_pack1(GTK_PANED(paned), id->image->widget, FALSE, TRUE);
811         gtk_widget_show(id->image->widget);
812
813         main_vbox = gtk_vbox_new(FALSE, 0);
814         gtk_paned_pack2(GTK_PANED(paned), main_vbox, TRUE, TRUE);
815         gtk_widget_show(main_vbox);
816
817         hbox = pref_box_new(main_vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
818         label = pref_label_new(hbox, _("Filename:"));
819         pref_label_bold(label, TRUE, FALSE);
820
821         id->name_entry = gtk_entry_new();
822         gtk_editable_set_editable(GTK_EDITABLE(id->name_entry), FALSE);
823         gtk_box_pack_start(GTK_BOX(hbox), id->name_entry, TRUE, TRUE, 0);
824         gtk_widget_show(id->name_entry);
825
826         id->notebook = gtk_notebook_new();
827         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(id->notebook), GTK_POS_TOP);
828         gtk_box_pack_start(GTK_BOX(main_vbox), id->notebook, TRUE, TRUE, 5);
829         g_signal_connect(G_OBJECT(id->notebook), "page-reordered",
830                          G_CALLBACK(info_notebook_reordered_cb), id);
831
832         gtk_widget_show(id->notebook);
833
834         pref_spacer(main_vbox, PREF_PAD_GAP);
835
836         hbox = pref_box_new(main_vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
837
838         id->button_back = pref_button_new(hbox, GTK_STOCK_GO_BACK, NULL, TRUE,
839                                           G_CALLBACK(info_window_back_cb), id);
840         gtk_widget_set_sensitive(id->button_back, FALSE);
841
842         id->button_next = pref_button_new(hbox, GTK_STOCK_GO_FORWARD, NULL, TRUE,
843                                           G_CALLBACK(info_window_next_cb), id);
844         gtk_widget_set_sensitive(id->button_next, (id->list->next != NULL));
845
846         if (id->list->next)
847                 {
848                 id->label_count = pref_label_new(hbox, "");
849                 }
850
851         button = pref_button_new(NULL, GTK_STOCK_CLOSE, NULL, FALSE,
852                                  G_CALLBACK(info_window_close_cb), id);
853         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
854         gtk_widget_show(button);
855
856         /* set up tabs */
857
858         info_tabs_init(id);
859
860         /* fill it */
861
862         info_window_sync(id, id->fd);
863
864         /* finish */
865
866         info_window_dnd_init(id);
867
868         gtk_widget_show(id->window);
869 }