d5bab4bf3ff8e695e4fa53f3a56b4b8fd0c746e8
[geeqie.git] / src / info.c
1 /*
2  * GQview
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 "gqview.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 "ui_bookmark.h"
23 #include "ui_fileops.h"
24 #include "ui_misc.h"
25
26 #include <pwd.h>
27 #include <grp.h>
28
29
30 #define IMAGE_SIZE_W 200
31 #define IMAGE_SIZE_H 200
32
33 #define DEF_PROPERTY_WIDTH  510
34 #define DEF_PROPERTY_HEIGHT 390
35
36 typedef struct _TabData TabData;
37 struct _TabData
38 {
39         void (*func_free)(gpointer data);
40         void (*func_sync)(InfoData *id, gpointer data);
41         void (*func_image)(InfoData *id, gpointer data);
42         gpointer data;
43 };
44
45
46 /*
47  *-------------------------------------------------------------------
48  * table utils
49  *-------------------------------------------------------------------
50  */
51
52 GtkWidget *table_add_line(GtkWidget *table, gint x, gint y,
53                           const gchar *description, const gchar *text)
54 {
55         GtkWidget *label;
56
57         if (!text) text = "";
58
59         label = pref_table_label(table, x, y, description, 1.0);
60         pref_label_bold(label, TRUE, FALSE);
61
62         label = pref_table_label(table, x + 1, y, text, 0.0);
63         return label;
64 }
65
66 /*
67  *-------------------------------------------------------------------
68  * EXIF tab
69  *-------------------------------------------------------------------
70  */
71
72 static void info_tab_exif_image(InfoData *id, gpointer data)
73 {
74         GtkWidget *bar = data;
75         const gchar *path;
76
77         if (id->image->unknown)
78                 {
79                 path = NULL;
80                 }
81         else
82                 {
83                 path = id->image->image_path;
84                 }
85
86         bar_exif_set(bar, path);
87 }
88
89 static void info_tab_exif_sync(InfoData *id, gpointer data)
90 {
91         GtkWidget *bar = data;
92
93         bar_exif_set(bar, NULL);
94 }
95
96 static TabData *info_tab_exif_new(InfoData *id)
97 {
98         TabData *td;
99         GtkWidget *bar;
100         GtkWidget *label;
101
102         bar = bar_exif_new(FALSE, NULL, FALSE, NULL);
103         gtk_container_set_border_width(GTK_CONTAINER(bar), PREF_PAD_BORDER);
104
105         label = gtk_label_new(_("Exif"));
106         gtk_notebook_append_page(GTK_NOTEBOOK(id->notebook), bar, label);
107         gtk_widget_show(bar);
108
109         /* register */
110         td = g_new0(TabData, 1);
111         td->func_free = NULL;
112         td->func_sync = info_tab_exif_sync;
113         td->func_image = info_tab_exif_image;
114         td->data = bar;
115
116         return td;
117 }
118
119 /*
120  *-------------------------------------------------------------------
121  * file attributes tab
122  *-------------------------------------------------------------------
123  */
124
125 typedef struct _InfoTabMeta InfoTabMeta;
126 struct _InfoTabMeta
127 {
128         GtkWidget *bar_info;
129 };
130
131 static void info_tab_meta_free(gpointer data)
132 {
133         InfoTabMeta *tab = data;
134
135         g_free(tab);
136 }
137
138 static void info_tab_meta_sync(InfoData *id, gpointer data)
139 {
140         InfoTabMeta *tab = data;
141
142         bar_info_set(tab->bar_info, id->path);
143 }
144
145 static GList *info_tab_meta_list_cb(gpointer data)
146 {
147         InfoData *id = data;
148
149         return path_list_copy(id->list);
150 }
151
152 static TabData *info_tab_meta_new(InfoData *id)
153 {
154         TabData *td;
155         InfoTabMeta *tab;
156         GtkWidget *label;
157
158         tab = g_new0(InfoTabMeta, 1);
159
160         tab->bar_info = bar_info_new(NULL, TRUE, NULL);
161         bar_info_set_selection_func(tab->bar_info, info_tab_meta_list_cb, id);
162         bar_info_selection(tab->bar_info, g_list_length(id->list) - 1);
163
164         gtk_container_set_border_width(GTK_CONTAINER(tab->bar_info), PREF_PAD_BORDER);
165
166         label = gtk_label_new(_("Keywords"));
167         gtk_notebook_append_page(GTK_NOTEBOOK(id->notebook), tab->bar_info, label);
168         gtk_widget_show(tab->bar_info);
169
170         /* register */
171         td = g_new0(TabData, 1);
172         td->func_free = info_tab_meta_free;
173         td->func_sync = info_tab_meta_sync;
174         td->func_image = NULL;
175         td->data = tab;
176
177         return td;
178 }
179
180 /*
181  *-------------------------------------------------------------------
182  * general tab
183  *-------------------------------------------------------------------
184  */
185
186 typedef struct _InfoTabGeneral InfoTabGeneral;
187 struct _InfoTabGeneral
188 {
189         GtkWidget *label_file_time;
190         GtkWidget *label_file_size;
191         GtkWidget *label_dimensions;
192         GtkWidget *label_transparent;
193         GtkWidget *label_image_size;
194         GtkWidget *label_compression;
195         GtkWidget *label_mime_type;
196
197         GtkWidget *label_user;
198         GtkWidget *label_group;
199         GtkWidget *label_perms;
200
201         gint compression_done;
202         gint64 byte_size;
203 };
204
205 static void info_tab_general_image(InfoData *id, gpointer data)
206 {
207         InfoTabGeneral *tab = data;
208         gchar *buf;
209         guint mem_size;
210         gint has_alpha;
211
212         if (id->image->unknown) return;
213
214         buf = g_strdup_printf("%d x %d", id->image->image_width, id->image->image_height);
215         gtk_label_set_text(GTK_LABEL(tab->label_dimensions), buf);
216         g_free(buf);
217
218         if (id->image->pixbuf)
219                 {
220                 has_alpha = gdk_pixbuf_get_has_alpha(id->image->pixbuf);
221                 }
222         else
223                 {
224                 has_alpha = FALSE;
225                 }
226         gtk_label_set_text(GTK_LABEL(tab->label_transparent), has_alpha ? _("yes") : _("no"));
227
228         mem_size = id->image->image_width * id->image->image_height * ((has_alpha) ? 4 : 3);
229         buf = text_from_size_abrev(mem_size);
230         gtk_label_set_text(GTK_LABEL(tab->label_image_size), buf);
231         g_free(buf);
232
233         if (!tab->compression_done && mem_size > 0)
234                 {
235                 buf = g_strdup_printf("%.1f%%", (float)tab->byte_size / mem_size * 100.0);
236                 gtk_label_set_text(GTK_LABEL(tab->label_compression), buf);
237                 g_free(buf);
238
239                 tab->compression_done = TRUE;
240                 }
241
242         buf = image_loader_get_format(id->image->il);
243         if (buf)
244         gtk_label_set_text(GTK_LABEL(tab->label_mime_type), buf);
245         g_free(buf);
246 }
247
248 static gchar *mode_number(mode_t m)
249 {
250         int mb, mu, mg, mo;
251
252         mb = mu = mg = mo = 0;
253
254         if (m & S_ISUID) mb |= 4;
255         if (m & S_ISGID) mb |= 2;
256         if (m & S_ISVTX) mb |= 1;
257
258         if (m & S_IRUSR) mu |= 4;
259         if (m & S_IWUSR) mu |= 2;
260         if (m & S_IXUSR) mu |= 1;
261
262         if (m & S_IRGRP) mg |= 4;
263         if (m & S_IWGRP) mg |= 2;
264         if (m & S_IXGRP) mg |= 1;
265
266         if (m & S_IROTH) mo |= 4;
267         if (m & S_IWOTH) mo |= 2;
268         if (m & S_IXOTH) mo |= 1;
269
270         return g_strdup_printf("%d%d%d%d", mb, mu, mg, mo);
271 }
272
273 static void info_tab_general_sync_perm(InfoTabGeneral *tab, InfoData *id)
274 {
275         struct stat st;
276
277         if (!stat_utf8(id->path, &st))
278                 {
279                 gtk_label_set_text(GTK_LABEL(tab->label_user), "");
280                 gtk_label_set_text(GTK_LABEL(tab->label_group), "");
281                 gtk_label_set_text(GTK_LABEL(tab->label_perms), "");
282                 }
283         else
284                 {
285                 struct passwd *user;
286                 struct group *grp;
287                 gchar pbuf[12];
288                 gchar *pmod;
289                 gchar *buf;
290
291                 user = getpwuid(st.st_uid);
292                 gtk_label_set_text(GTK_LABEL(tab->label_user), (user) ? user->pw_name : "");
293
294                 grp = getgrgid(st.st_gid);
295                 gtk_label_set_text(GTK_LABEL(tab->label_group), (grp) ? grp->gr_name : "");
296
297                 pbuf[0] = (st.st_mode & S_IRUSR) ? 'r' : '-';
298                 pbuf[1] = (st.st_mode & S_IWUSR) ? 'w' : '-';
299                 pbuf[2] = (st.st_mode & S_IXUSR) ? 'x' : '-';
300                 pbuf[3] = (st.st_mode & S_IRGRP) ? 'r' : '-';
301                 pbuf[4] = (st.st_mode & S_IWGRP) ? 'w' : '-';
302                 pbuf[5] = (st.st_mode & S_IXGRP) ? 'x' : '-';
303                 pbuf[6] = (st.st_mode & S_IROTH) ? 'r' : '-';
304                 pbuf[7] = (st.st_mode & S_IWOTH) ? 'w' : '-';
305                 pbuf[8] = (st.st_mode & S_IXOTH) ? 'x' : '-';
306                 pbuf[9] = '\0';
307
308                 pmod = mode_number(st.st_mode);
309                 buf = g_strdup_printf("%s (%s)", pbuf, pmod);
310                 gtk_label_set_text(GTK_LABEL(tab->label_perms), buf);
311                 g_free(buf);
312                 g_free(pmod);
313                 }
314 }
315
316 static void info_tab_general_sync(InfoData *id, gpointer data)
317 {
318         InfoTabGeneral *tab = data;
319         gchar *buf;
320
321         gtk_label_set_text(GTK_LABEL(tab->label_file_time), text_from_time(filetime(id->path)));
322
323         tab->byte_size = filesize(id->path);
324
325         buf = text_from_size(tab->byte_size);
326         gtk_label_set_text(GTK_LABEL(tab->label_file_size), buf);
327         g_free(buf);
328
329         gtk_label_set_text(GTK_LABEL(tab->label_dimensions), "");
330         gtk_label_set_text(GTK_LABEL(tab->label_transparent), "");
331         gtk_label_set_text(GTK_LABEL(tab->label_image_size), "");
332
333         gtk_label_set_text(GTK_LABEL(tab->label_compression), "");
334         gtk_label_set_text(GTK_LABEL(tab->label_mime_type), "");
335
336         info_tab_general_sync_perm(tab, id);
337
338         tab->compression_done = FALSE;
339 }
340
341 static void info_tab_general_free(gpointer data)
342 {
343         InfoTabGeneral *tab = data;
344
345         g_free(tab);
346 }
347
348 static TabData *info_tab_general_new(InfoData *id)
349 {
350         TabData *td;
351         InfoTabGeneral *tab;
352         GtkWidget *table;
353         GtkWidget *label;
354
355         tab = g_new0(InfoTabGeneral, 1);
356
357         table = pref_table_new(NULL, 2, 11, FALSE, FALSE);
358         gtk_container_set_border_width(GTK_CONTAINER(table), PREF_PAD_BORDER);
359
360         tab->label_file_time = table_add_line(table, 0, 0, _("File date:"), NULL);
361         tab->label_file_size = table_add_line(table, 0, 1, _("File size:"), NULL);
362
363         tab->label_dimensions = table_add_line(table, 0, 2, _("Dimensions:"), NULL);
364         tab->label_transparent = table_add_line(table, 0, 3, _("Transparent:"), NULL);
365         tab->label_image_size = table_add_line(table, 0, 4, _("Image size:"), NULL);
366
367         tab->label_compression = table_add_line(table, 0, 5, _("Compress ratio:"), NULL);
368         tab->label_mime_type = table_add_line(table, 0, 6, _("File type:"), NULL);
369
370         tab->label_user = table_add_line(table, 0, 7, _("Owner:"), NULL);
371         tab->label_group = table_add_line(table, 0, 8, _("Group:"), NULL);
372         tab->label_perms = table_add_line(table, 0, 9, "", NULL);
373
374         label = gtk_label_new(_("General"));
375         gtk_notebook_append_page(GTK_NOTEBOOK(id->notebook), table, label);
376         gtk_widget_show(table);
377
378         /* register */
379         td = g_new0(TabData, 1);
380         td->func_free = info_tab_general_free;
381         td->func_sync = info_tab_general_sync;
382         td->func_image = info_tab_general_image;
383         td->data = tab;
384
385         return td;
386 }
387
388 /*
389  *-------------------------------------------------------------------
390  * tabs
391  *-------------------------------------------------------------------
392  */
393
394 static void info_tabs_sync(InfoData *id, gint image)
395 {
396         GList *work;
397
398         work = id->tab_list;
399         while (work)
400                 {
401                 TabData *td = work->data;
402                 work = work->next;
403
404                 if (image)
405                         {
406                         if (td->func_image) td->func_image(id, td->data);
407                         }
408                 else
409                         {
410                         if (td->func_sync) td->func_sync(id, td->data);
411                         }
412                 }
413 }
414
415 static void info_tabs_free(InfoData *id)
416 {
417         GList *work;
418
419         work = id->tab_list;
420         while (work)
421                 {
422                 TabData *td = work->data;
423                 work = work->next;
424
425                 if (td->func_free) td->func_free(td->data);
426                 g_free(td);
427                 }
428         g_list_free(id->tab_list);
429         id->tab_list = NULL;
430 }
431
432 static void info_tabs_init(InfoData *id)
433 {
434         id->tab_list = g_list_append(id->tab_list, info_tab_general_new(id));
435         id->tab_list = g_list_append(id->tab_list, info_tab_meta_new(id));
436         id->tab_list = g_list_append(id->tab_list, info_tab_exif_new(id));
437 }
438
439 /*
440  *-------------------------------------------------------------------
441  * sync
442  *-------------------------------------------------------------------
443  */
444
445 static void info_window_sync(InfoData *id, const gchar *path)
446 {
447
448         if (!path) return;
449
450         gtk_entry_set_text(GTK_ENTRY(id->name_entry), filename_from_path(path));
451
452         if (id->label_count)
453                 {
454                 gchar *buf;
455                 buf = g_strdup_printf(_("Image %d of %d"),
456                                       g_list_index(id->list, (gpointer)path) + 1,
457                                       g_list_length(id->list));
458                 gtk_label_set_text(GTK_LABEL(id->label_count), buf);
459                 g_free(buf);
460                 }
461
462         info_tabs_sync(id, FALSE);
463
464         id->updated = FALSE;
465         image_change_path(id->image, path, 0.0);
466 }
467
468 /*
469  *-------------------------------------------------------------------
470  * drag n drop (dropping not supported!)
471  *-------------------------------------------------------------------
472  */
473
474 static void info_window_dnd_data_set(GtkWidget *widget, GdkDragContext *context,
475                                      GtkSelectionData *selection_data, guint info,
476                                      guint time, gpointer data)
477 {
478         InfoData *id = data;
479         const gchar *path;
480
481         path = image_get_path(id->image);
482         if (path)
483                 {
484                 gchar *text;
485                 gint len;
486                 GList *list;
487                 gint plain_text;
488
489                 switch (info)
490                         {
491                         case TARGET_URI_LIST:
492                                 plain_text = FALSE;
493                                 break;
494                         case TARGET_TEXT_PLAIN:
495                         default:
496                                 plain_text = TRUE;
497                                 break;
498                         }
499                 list = g_list_append(NULL, (gchar *)path);
500                 text = uri_text_from_list(list, &len, plain_text);
501                 g_list_free(list);
502
503                 gtk_selection_data_set(selection_data, selection_data->target,
504                                        8, text, len);
505                 g_free(text);
506                 }
507 }
508
509 static void info_window_dnd_init(InfoData *id)
510 {
511         ImageWindow *imd;
512
513         imd = id->image;
514
515         gtk_drag_source_set(imd->image, GDK_BUTTON2_MASK,
516                             dnd_file_drag_types, dnd_file_drag_types_count,
517                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
518         g_signal_connect(G_OBJECT(imd->image), "drag_data_get",
519                          G_CALLBACK(info_window_dnd_data_set), id);
520
521 #if 0
522         gtk_drag_dest_set(imd->image,
523                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
524                           dnd_file_drop_types, dnd_file_drop_types_count,
525                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
526         g_signal_connect(G_OBJECT(imd->image), "drag_data_received",
527                          G_CALLBACK(info_window_dnd_data_get), id);
528 #endif
529 }
530
531 /*
532  *-------------------------------------------------------------------
533  * base window
534  *-------------------------------------------------------------------
535  */
536
537 static gint info_window_last_width = DEF_PROPERTY_WIDTH;
538 static gint info_window_last_height = DEF_PROPERTY_HEIGHT;
539
540 static void info_window_image_update_cb(ImageWindow *imd, gpointer data)
541 {
542         InfoData *id = data;
543
544         /* only do this once after when loading a new image,
545          * for tabs that depend on image data (exif)
546          * Subsequent updates are ignored, as the image
547          * should not really changed if id->updated is TRUE.
548          */
549
550         if (id->updated) return;
551         if (imd->unknown) return;
552
553         info_tabs_sync(id, TRUE);
554         id->updated = TRUE;
555 }
556
557 static void info_window_back_cb(GtkWidget *widget, gpointer data)
558 {
559         InfoData *id = data;
560         GList *work;
561
562         work = g_list_find(id->list, (gpointer)id->path);
563         if (!work || !work->prev) return;
564
565         work = work->prev;
566         id->path = work->data;
567
568         info_window_sync(id, id->path);
569
570         gtk_widget_set_sensitive(id->button_back, (work->prev != NULL));
571         gtk_widget_set_sensitive(id->button_next, TRUE);
572 }
573
574 static void info_window_next_cb(GtkWidget *widget, gpointer data)
575 {
576         InfoData *id = data;
577         GList *work;
578
579         work = g_list_find(id->list, (gpointer)id->path);
580         if (!work || !work->next) return;
581
582         work = work->next;
583         id->path = work->data;
584
585         info_window_sync(id, id->path);
586
587         gtk_widget_set_sensitive(id->button_next, (work->next != NULL));
588         gtk_widget_set_sensitive(id->button_back, TRUE);
589 }
590
591 static void info_window_image_button_cb(ImageWindow *imd, gint button, guint32 time,
592                                         gdouble x, gdouble y, guint state, gpointer data)
593 {
594         if (button == 1)
595                 {
596                 info_window_next_cb(NULL, data);
597                 }
598         else if (button == 2 || button == 3)
599                 {
600                 info_window_back_cb(NULL, data);
601                 }
602 }
603
604 static void info_window_image_scroll_cb(ImageWindow *imd, GdkScrollDirection direction, guint32 time,
605                                         gdouble x, gdouble y, guint state, gpointer data)
606 {
607         if (direction == GDK_SCROLL_UP)
608                 {
609                 info_window_back_cb(NULL, data);
610                 }
611         else if (direction == GDK_SCROLL_DOWN)
612                 {
613                 info_window_next_cb(NULL, data);
614                 }
615 }
616
617 static void info_window_close(InfoData *id)
618 {
619         gdk_drawable_get_size(id->window->window, &info_window_last_width, &info_window_last_height);
620         info_window_last_width = MAX(info_window_last_width, DEF_PROPERTY_WIDTH);
621         info_window_last_height = MAX(info_window_last_height, DEF_PROPERTY_HEIGHT);
622
623         gtk_widget_destroy(id->window);
624 }
625
626 static void info_window_close_cb(GtkWidget *widget, gpointer data)
627 {
628         InfoData *id = data;
629
630         info_window_close(id);
631 }
632
633 static gint info_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
634 {
635         InfoData *id = data;
636
637         info_window_close(id);
638         return TRUE;
639 }
640
641 static void info_window_destroy_cb(GtkWidget *widget, gpointer data)
642 {
643         InfoData *id = data;
644
645         info_tabs_free(id);
646         path_list_free(id->list);
647         g_free(id);
648 }
649
650 void info_window_new(const gchar *path, GList *list)
651 {
652         InfoData *id;
653         GtkWidget *main_vbox;
654         GtkWidget *paned;
655         GtkWidget *hbox;
656         GtkWidget *button;
657         GtkWidget *label;
658         GdkGeometry geometry;
659
660         if (!path && !list) return;
661
662         if (!list)
663                 {
664                 list = g_list_append(NULL, g_strdup(path));
665                 }
666
667         id = g_new0(InfoData, 1);
668
669         id->list = list;
670         id->path = (gchar *)id->list->data;
671         id->updated = FALSE;
672
673         id->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
674         gtk_window_set_type_hint(GTK_WINDOW(id->window), GDK_WINDOW_TYPE_HINT_DIALOG);
675         window_set_icon(id->window, NULL, NULL);
676
677         gtk_window_set_resizable(GTK_WINDOW(id->window), TRUE);
678         gtk_window_set_title(GTK_WINDOW(id->window), _("Image properties - GQview"));
679         gtk_window_set_wmclass(GTK_WINDOW(id->window), "properties", "GQview");
680
681         geometry.min_width = 32;
682         geometry.min_height = 32;
683         geometry.base_width = DEF_PROPERTY_WIDTH;
684         geometry.base_height = DEF_PROPERTY_HEIGHT;
685         gtk_window_set_geometry_hints(GTK_WINDOW(id->window), NULL, &geometry,
686                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
687
688
689         gtk_window_set_default_size(GTK_WINDOW(id->window), info_window_last_width, info_window_last_height);
690         gtk_container_set_border_width(GTK_CONTAINER(id->window), PREF_PAD_BORDER);
691
692         g_signal_connect(G_OBJECT(id->window), "delete_event",
693                          G_CALLBACK(info_window_delete_cb), id);
694         g_signal_connect(G_OBJECT(id->window), "destroy",
695                          G_CALLBACK(info_window_destroy_cb), id);
696
697         paned = gtk_hpaned_new();
698         gtk_container_add(GTK_CONTAINER(id->window), paned);
699         gtk_widget_show(paned);
700
701         id->image = image_new(FALSE);
702         image_set_update_func(id->image, info_window_image_update_cb, id);
703
704         image_set_button_func(id->image, info_window_image_button_cb, id);
705         image_set_scroll_func(id->image, info_window_image_scroll_cb, id);
706
707         gtk_widget_set_size_request(id->image->widget, IMAGE_SIZE_W, IMAGE_SIZE_H);
708         gtk_paned_pack1(GTK_PANED(paned), id->image->widget, FALSE, TRUE);
709         gtk_widget_show(id->image->widget);
710
711         main_vbox = gtk_vbox_new(FALSE, 0);
712         gtk_paned_pack2(GTK_PANED(paned), main_vbox, TRUE, TRUE);
713         gtk_widget_show(main_vbox);
714
715         hbox = pref_box_new(main_vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
716         label = pref_label_new(hbox, _("Filename:"));
717         pref_label_bold(label, TRUE, FALSE);
718
719         id->name_entry = gtk_entry_new();
720         gtk_editable_set_editable(GTK_EDITABLE(id->name_entry), FALSE);
721         gtk_box_pack_start(GTK_BOX(hbox), id->name_entry, TRUE, TRUE, 0);
722         gtk_widget_show(id->name_entry);
723
724         id->notebook = gtk_notebook_new();
725         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(id->notebook), GTK_POS_TOP);
726         gtk_box_pack_start(GTK_BOX(main_vbox), id->notebook, TRUE, TRUE, 5);
727         gtk_widget_show(id->notebook);
728
729         pref_spacer(main_vbox, PREF_PAD_GAP);
730
731         hbox = pref_box_new(main_vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
732
733         id->button_back = pref_button_new(hbox, GTK_STOCK_GO_BACK, NULL, TRUE,
734                                           G_CALLBACK(info_window_back_cb), id);
735         gtk_widget_set_sensitive(id->button_back, FALSE);
736
737         id->button_next = pref_button_new(hbox, GTK_STOCK_GO_FORWARD, NULL, TRUE,
738                                           G_CALLBACK(info_window_next_cb), id);
739         gtk_widget_set_sensitive(id->button_next, (id->list->next != NULL));
740
741         if (id->list->next)
742                 {
743                 id->label_count = pref_label_new(hbox, "");
744                 }
745
746         button = pref_button_new(NULL, GTK_STOCK_CLOSE, NULL, FALSE,
747                                  G_CALLBACK(info_window_close_cb), id);
748         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
749         gtk_widget_show(button);
750
751         /* set up tabs */
752
753         info_tabs_init(id);
754
755         /* fill it */
756
757         info_window_sync(id, id->path);
758
759         /* finish */
760
761         info_window_dnd_init(id);
762
763         gtk_widget_show(id->window);
764 }
765