diff options
| -rw-r--r-- | TODO | 11 | ||||
| -rw-r--r-- | common.h | 4 | ||||
| -rw-r--r-- | config.mk | 4 | ||||
| -rw-r--r-- | files.c | 9 | ||||
| -rw-r--r-- | opening.c | 110 | ||||
| -rw-r--r-- | printing.c | 126 | ||||
| -rw-r--r-- | properties.c | 296 | ||||
| -rw-r--r-- | zoom.c | 1 |
8 files changed, 489 insertions, 72 deletions
@@ -1,8 +1,3 @@ -Add alignment buttons to the print dialogue. - -Some images may have a physical dimension specified. -This should at least be honoured during print, maybe even on display. - Optional (opt-in) side-pane ("_Property pane") with image properties (accelerator: F9) Optional (opt-in) bottom-pane ("Image _collection") with image thumbnails (accelerator: Control+F9) @@ -16,13 +11,15 @@ Automatically go to next picture Animation support +"Properties" shall display Exif thumbnail if embedded + Preferences for transparency Preferences for changling GDK_INTERP_BILINEAR -Fix "GFileInfo created without standard::is-hidden" error message +Preferences for scaling to physical size rather than 1:1 pixel mapping -Fix "GFileInfo created without standard::is-backup" error message +Perference for automatically applying embedded transformation ("orientation") Add support for farbfeld @@ -18,9 +18,11 @@ # pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif #include <gdk/gdkkeysyms.h> +#include <gexiv2/gexiv2.h> #include <gtk/gtk.h> #include <libsimple.h> #include <libsimple-arg.h> +#include <math.h> #if defined(__GNUC__) # pragma GCC diagnostic pop #endif @@ -79,6 +81,8 @@ struct file { char *path; off_t size; GdkPixbuf *image; + char **exif_tags; + GExiv2Metadata *exif; }; typedef double Zoom; /* TODO */ @@ -4,5 +4,5 @@ MANPREFIX = $(PREFIX)/share/man CC = c99 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -CFLAGS = $$(pkg-config --cflags gtk+-2.0) -LDFLAGS = $$(pkg-config --libs gtk+-2.0) -lsimple +CFLAGS = $$(pkg-config --cflags gtk+-2.0 gexiv2) +LDFLAGS = $$(pkg-config --libs gtk+-2.0 gexiv2) -lsimple -lm @@ -12,6 +12,9 @@ free_files(void) --nfiles; free(files[nfiles].path); g_clear_object(&files[nfiles].image); + if (files[nfiles].exif_tags) + g_strfreev(files[nfiles].exif_tags); + g_clear_object(&files[nfiles].exif); } free(files); files = NULL; @@ -25,6 +28,8 @@ files_from_array(struct file *selected_files, size_t nselected_files) for (i = 0u, n = 0u; i < nselected_files; i++) { selected_files[i].image = NULL; + selected_files[i].exif_tags = NULL; + selected_files[i].exif = NULL; if (load_image(&selected_files[i], 0)) { if (i != n) selected_files[n] = selected_files[i]; @@ -53,6 +58,8 @@ files_from_list(struct file **files_out, GSList *selected_files) for (file = selected_files, i = 0u; file; file = file->next) { new_files[i].path = estrdup(file->data); new_files[i].image = NULL; + new_files[i].exif_tags = NULL; + new_files[i].exif = NULL; g_free(file->data); if (load_image(&new_files[i], 0)) @@ -88,6 +95,8 @@ set_files_from_argv(int argc, char **argv) for (i = 0u; i < nfiles; i++) { files[i].path = estrdup(argv[i]); files[i].image = NULL; + files[i].exif_tags = NULL; + files[i].exif = NULL; if (!load_image(&files[i], 1)) { free(files[i].path); ok = 0; @@ -1,6 +1,69 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" +static GExiv2Metadata * +load_exif_metadata(const unsigned char *data, size_t length, char ***tags_out) +{ + GExiv2Metadata *metadata; + GError *error = NULL; + *tags_out = NULL; + metadata = gexiv2_metadata_new(); + if (gexiv2_metadata_open_buf(metadata, data, (long int)length, &error)) { + *tags_out = gexiv2_metadata_get_exif_tags(metadata); + return metadata; + } + g_clear_error(&error); + g_object_unref(metadata); + return NULL; +} + +static int +read_file(int fd, unsigned char **buf_out, size_t *size_out) +{ + unsigned char *buf = NULL, *new; + size_t off = 0u; + size_t size = 0u; + ssize_t r; + int saved_errno; + + *buf_out = NULL; + *size_out = 0u; + + for (;;) { + if (off == size) { + if (size > SIZE_MAX - (8u << 10)) { + errno = ENOMEM; + goto fail; + } + new = realloc(buf, size += 8u << 10); + if (!new) + goto fail; + buf = new; + } + + r = read(fd, &buf[off], size - off); + if (r <= 0) { + if (!r) + break; + if (errno == EINTR) + continue; + goto fail; + } + off += (size_t)r; + } + + *buf_out = buf; + *size_out = off; + + return 0; + +fail: + saved_errno = errno; + free(buf); + errno = saved_errno; + return -1; +} + int load_image(struct file *file, int errors_to_stderr) { @@ -10,8 +73,8 @@ load_image(struct file *file, int errors_to_stderr) GdkPixbuf *image; GdkPixbufLoader *loader = NULL; struct stat st; - unsigned char buffer[8 << 10]; - ssize_t r; + unsigned char *buffer = NULL; + size_t size; int fd, ret = 1; if (!file) @@ -20,53 +83,52 @@ load_image(struct file *file, int errors_to_stderr) fd = open(file->path, O_RDONLY | O_NOCTTY); if (fd < 0) goto fail_errno; - if (fstat(fd, &st)) goto fail_errno; - if (!S_ISREG(st.st_mode)) { error_message = "Not a regular file"; goto fail; } + if (read_file(fd, &buffer, &size)) + goto fail_errno; + file->size = st.st_size; loader = gdk_pixbuf_loader_new(); if (!loader) goto fail_errno; - for (;;) { - r = read(fd, buffer, sizeof(buffer)); - if (r <= 0) { - if (!r) - break; - if (errno == EINTR) - continue; - goto fail_errno; - } - if (!gdk_pixbuf_loader_write(loader, buffer, (size_t)r, &error)) - goto fail_gerror; - } - if (!gdk_pixbuf_loader_close(loader, &error)) { - fail_gerror: - error_message = error->message; - goto fail; - } + if (!gdk_pixbuf_loader_write(loader, buffer, size, &error)) + goto fail_gerror; + if (!gdk_pixbuf_loader_close(loader, &error)) + goto fail_gerror; image = gdk_pixbuf_loader_get_pixbuf(loader); if (!image) { error_message = "Unsupported image format"; goto fail; } g_object_ref(image); - g_clear_object(&file->image); - - file->size = st.st_size; file->image = image; + if (file->exif_tags) { + g_strfreev(file->exif_tags); + file->exif_tags = NULL; + } + g_clear_object(&file->exif); + + if (size <= (size_t)LONG_MAX) + file->exif = load_exif_metadata(buffer, size, &file->exif_tags); + out: + free(buffer); g_clear_object(&loader); if (fd >= 0) close(fd); return ret; +fail_gerror: + error_message = error->message; + goto fail; + fail_errno: error_message = strerror(errno); @@ -1,6 +1,5 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" -#include <math.h> #define LIST_UNITS(X, ...)\ X(UNIT_MILLIMETRES, "Millimetres", 72.0 / 25.4) __VA_ARGS__\ @@ -45,6 +44,7 @@ struct print_data { double height; double left; double top; + double aspect; }; struct positioning_widget { @@ -117,6 +117,35 @@ get_printable_size(GtkPageSetup *setup, double *width, double *height) *height = MAX(*height, 0.1); } +static int +get_dpi_option(GdkPixbuf *image, const char *name, double *dpi) +{ + const char *text = gdk_pixbuf_get_option(image, name); + char *end; + double value; + if (!text) + return 0; + value = strtod(text, &end); + while (isspace(*(unsigned char *)end)) + end++; + if (end == text || *end || !isfinite(value) || value <= 0.0) + return 0; + *dpi = value; + return 1; +} + +static int +get_print_size_from_resolution(GdkPixbuf *image, double *width, double *height) +{ + double x_dpi, y_dpi; + if (!get_dpi_option(image, "x-dpi", &x_dpi) || + !get_dpi_option(image, "y-dpi", &y_dpi)) + return 0; + *width = 72.0 * gdk_pixbuf_get_width(image) / x_dpi; + *height = 72.0 * gdk_pixbuf_get_height(image) / y_dpi; + return 1; +} + static double get_preview_scale(GtkWidget *preview, struct positioning_widget *widget) { @@ -146,8 +175,6 @@ static int set_field_value(struct positioning_widget *widget, enum field field, double value) { struct print_data *data = widget->data; - int image_width = gdk_pixbuf_get_width(data->image); - int image_height = gdk_pixbuf_get_height(data->image); double aspect, pos, size, page, pos_opp, size_opp, page_opp; double new_pos, new_size, new_pos_opp, new_size_opp; double *posp, *pos_oppp, *sizep, *size_oppp, limit; @@ -162,7 +189,7 @@ set_field_value(struct positioning_widget *widget, enum field field, double valu pos_oppp = &data->top; size_oppp = &data->height; page_opp = widget->page_height; - aspect = (double)image_height / (double)image_width; + aspect = 1.0 / data->aspect; break; case FIELD_HEIGHT: @@ -174,7 +201,7 @@ set_field_value(struct positioning_widget *widget, enum field field, double valu pos_oppp = &data->left; size_oppp = &data->width; page_opp = widget->page_width; - aspect = (double)image_width / (double)image_height; + aspect = data->aspect; break; } @@ -257,12 +284,14 @@ positioning_preview_expose(GtkWidget *preview, GdkEventExpose *event, void *user double scale = get_preview_scale(preview, widget); double w = scale * widget->page_width; double h = scale * widget->page_height; - double image_scale; double page_x = preview->allocation.width - 8.0 - w; double page_y = 8.0; - int image_width; + int width, height; cairo_t *cr; + width = gdk_pixbuf_get_width(data->image); + height = gdk_pixbuf_get_height(data->image); + cr = gdk_cairo_create(preview->window); cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height); @@ -278,10 +307,8 @@ positioning_preview_expose(GtkWidget *preview, GdkEventExpose *event, void *user cairo_rectangle(cr, page_x, page_y, w, h); cairo_clip(cr); - image_width = gdk_pixbuf_get_width(data->image); - image_scale = scale * data->width / (double)image_width; cairo_translate(cr, page_x + scale * data->left, page_y + scale * data->top); - cairo_scale(cr, image_scale, image_scale); + cairo_scale(cr, scale * data->width / width, scale * data->height / height); gdk_cairo_set_source_pixbuf(cr, data->image, 0.0, 0.0); cairo_paint(cr); @@ -409,7 +436,6 @@ positioning_preview_scrolled(GtkWidget *preview, GdkEventScroll *event, void *us { struct positioning_widget *widget = user; struct print_data *data = widget->data; - int image_width, image_height; double aspect, amount, scale, page_x; double pointer_x, pointer_y; double maximum, width, height; @@ -427,9 +453,7 @@ positioning_preview_scrolled(GtkWidget *preview, GdkEventScroll *event, void *us } amount *= widget->page_width; - image_width = gdk_pixbuf_get_width(data->image); - image_height = gdk_pixbuf_get_height(data->image); - aspect = (double)image_width / (double)image_height; + aspect = data->aspect; maximum = MIN(widget->page_width, widget->page_height * aspect); width = CLAMP(data->width + amount, 0.0, maximum); @@ -573,6 +597,24 @@ positioning_entry_focus_out(GtkWidget *entry, GdkEventFocus *event, void *user) return 0; } +static void +positioning_centre_horizontally(GtkButton *button, void *user) +{ + struct positioning_widget *widget = user; + (void) button; + widget->data->left = 0.5 * (widget->page_width - widget->data->width); + update_positioning_widget(widget, NFIELDS); +} + +static void +positioning_centre_vertically(GtkButton *button, void *user) +{ + struct positioning_widget *widget = user; + (void) button; + widget->data->top = 0.5 * (widget->page_height - widget->data->height); + update_positioning_widget(widget, NFIELDS); +} + static GtkWidget * create_positioning_widget(GtkPrintOperation *operation, void *user) { @@ -590,7 +632,7 @@ create_positioning_widget(GtkPrintOperation *operation, void *user) struct print_data *data = user; struct positioning_widget *positioning; GtkPageSetup *setup; - GtkWidget *box, *grid, *label; + GtkWidget *box, *grid, *label, *button; enum field field; size_t unit; unsigned int row, column; @@ -601,7 +643,7 @@ create_positioning_widget(GtkPrintOperation *operation, void *user) get_printable_size(setup, &positioning->page_width, &positioning->page_height); box = gtk_hbox_new(0, 12); - grid = gtk_table_new(4, 4, 0); + grid = gtk_table_new(5, 4, 0); gtk_container_set_border_width(GTK_CONTAINER(grid), 8); gtk_table_set_row_spacings(GTK_TABLE(grid), 6); gtk_table_set_col_spacings(GTK_TABLE(grid), 6); @@ -643,6 +685,12 @@ create_positioning_widget(GtkPrintOperation *operation, void *user) GTK_FILL, 0, 4u, 4u); } } + button = gtk_button_new_with_mnemonic("Centre h_orizontally"); + g_signal_connect(button, "clicked", G_CALLBACK(&positioning_centre_horizontally), positioning); + gtk_table_attach(GTK_TABLE(grid), button, 0, 2, 4, 5, GTK_EXPAND | GTK_FILL, 0, 4u, 4u); + button = gtk_button_new_with_mnemonic("Centre v_ertically"); + g_signal_connect(button, "clicked", G_CALLBACK(&positioning_centre_vertically), positioning); + gtk_table_attach(GTK_TABLE(grid), button, 2, 4, 4, 5, GTK_EXPAND | GTK_FILL, 0, 4u, 4u); positioning->preview = gtk_drawing_area_new(); gtk_widget_set_size_request(positioning->preview, 200, 200); gtk_widget_add_events(positioning->preview, @@ -695,16 +743,18 @@ draw_page(GtkPrintOperation *operation, GtkPrintContext *context, int page, void { struct print_data *data = user; GdkPixbuf *image = data->image; - double scale; + int width, height; cairo_t *cr; (void) operation; (void) page; - scale = data->width / gdk_pixbuf_get_width(image); + width = gdk_pixbuf_get_width(image); + height = gdk_pixbuf_get_height(image); + cr = gtk_print_context_get_cairo_context(context); cairo_translate(cr, data->left, data->top); - cairo_scale(cr, scale, scale); + cairo_scale(cr, data->width / (double)width, data->height / (double)height); gdk_cairo_set_source_pixbuf(cr, image, 0.0, 0.0); cairo_paint(cr); } @@ -715,8 +765,10 @@ open_print(GdkPixbuf *image) struct print_data data; GtkPrintOperation *operation; GtkPageSetup *setup; + GdkPixbuf *metadata_image; GtkPageOrientation orientation = GTK_PAGE_ORIENTATION_PORTRAIT; - double page_width, page_height, s, x, y; + double page_width, page_height, s, x, y, temporary; + int has_physical_size; int width, height; if (!image) @@ -727,19 +779,43 @@ open_print(GdkPixbuf *image) width = gdk_pixbuf_get_width(data.image); height = gdk_pixbuf_get_height(data.image); + metadata_image = get_original_image(); + if (!metadata_image) + metadata_image = data.image; + has_physical_size = get_print_size_from_resolution(metadata_image, &data.width, &data.height); + if (!has_physical_size) { + data.width = width; + data.height = height; + } else { + switch (trans & 7u) { + case TRANS_CLOCKWISE: + case TRANS_ANTICLOCKWISE: + case TRANS_TRANSPOSE: + case TRANS_ANTITRANSPOSE: + temporary = data.width; + data.width = data.height; + data.height = temporary; + break; + default: + break; + } + } + data.aspect = data.width / data.height; - if (width > height) + if (data.width > data.height) orientation = GTK_PAGE_ORIENTATION_LANDSCAPE; setup = gtk_page_setup_new(); gtk_page_setup_set_orientation(setup, orientation); get_printable_size(setup, &page_width, &page_height); - x = page_width / (double)width; - y = page_height / (double)height; + x = page_width / data.width; + y = page_height / data.height; s = MIN(x, y); - data.width = s * width; - data.height = s * height; + if (!has_physical_size || s < 1.0) { + data.width *= s; + data.height *= s; + } data.left = 0.5 * (page_width - data.width); data.top = 0.5 * (page_height - data.height); diff --git a/properties.c b/properties.c index b9ebce7..0fe02cc 100644 --- a/properties.c +++ b/properties.c @@ -1,28 +1,298 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" +static int +get_dpi_option(GdkPixbuf *image, const char *name, double *dpi) +{ + const char *text = gdk_pixbuf_get_option(image, name); + char *end; + double value; + if (!text) + return 0; + value = strtod(text, &end); + while (isspace(*(unsigned char *)end)) + end++; + if (end == text || *end || !isfinite(value) || value <= 0.0) + return 0; + *dpi = value; + return 1; +} + +static GtkWidget * +new_metadata_label(const char *text) +{ + GtkWidget *label = gtk_label_new(text); + gtk_label_set_selectable(GTK_LABEL(label), 1); + gtk_label_set_line_wrap(GTK_LABEL(label), 1); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + return label; +} + +static void +add_metadata_row(GtkTable *table, guint row, const char *name, const char *value) +{ + GtkWidget *name_label = new_metadata_label(name); + GtkWidget *value_label = new_metadata_label(value); + gtk_table_attach(table, name_label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach(table, value_label, 1, 2, row, row + 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 12, 0); +} + +static int +is_unrecognised_metadata(const char *name) +{ + return strcmp(name, "orientation") && + strcmp(name, "original-width") && + strcmp(name, "original-height"); +} + +static char * +format_title(struct file *file) +{ + char *title; + const char *name = strrchr(file->path, '/'); + name = name ? &name[1u] : file->path; + title = emalloc(sizeof("Properties: ") + strlen(name)); + stpcpy(stpcpy(title, "Properties: "), name); + return title; +} + +static char * +format_file_size(off_t size) +{ + static const char units[] = " KMGTPEZYRQ"; + uintmax_t bytes = size < 0 ? 0u : (uintmax_t)size; + uintmax_t bytes0 = bytes; + uintmax_t divisions[sizeof(units) - 1u]; + size_t ndivisions = 0u; + size_t low = SIZE_MAX; + char textbuf[sizeof("1024") * sizeof(units) + sizeof(" _B ( bytes)") + 7u * sizeof(bytes)]; + char subbuf[3u * sizeof(bytes) + 1u]; + size_t i, off = 0u; + int r, p, len; + + if (bytes < 1024u) { + if (sprintf(textbuf, "%ju bytes", bytes) < 0) + eprintf("sprintf:"); + return estrdup(textbuf); + } + + while (bytes && units[ndivisions]) { + divisions[ndivisions] = (bytes & 1023u); + bytes >>= 10; + if (divisions[ndivisions] && low > ndivisions) + low = ndivisions; + ndivisions++; + } + if (bytes) + divisions[ndivisions - 1] |= bytes << 10; + + for (i = ndivisions; i-- > low;) { + r = sprintf(&textbuf[off], ":%ju", divisions[i]); + if (r < 0) + eprintf("sprintf:"); + off += (size_t)r; + } + + r = sprintf(&textbuf[off], " %cB (", units[ndivisions - 1]); + if (r < 0) + eprintf("sprintf:"); + off += (size_t)r; + + len = sprintf(subbuf, "%ju", bytes0); + if (len < 0) + eprintf("sprintf:"); + r = len % 3; + r = r ? r : 3; + memcpy(&textbuf[off], subbuf, (size_t)r); + off += (size_t)r; + for (p = r; p < len; p += 3) { + textbuf[off++] = ' '; + memcpy(&textbuf[off], &subbuf[p], 3u); + off += 3u; + } + + r = sprintf(&textbuf[off], " bytes)"); + if (r < 0) + eprintf("sprintf:"); + + return estrdup(&textbuf[1u]); +} + +static char * +format_pixel_size(GdkPixbuf *image) +{ + int w = gdk_pixbuf_get_width(image); + int h = gdk_pixbuf_get_height(image); + char *text = emalloc(2u * 3u * sizeof(w) + sizeof(" × pixels")); + if (sprintf(text, "%i × %i pixels", w, h) < 0) + eprintf("sprintf:"); + return text; +} + +static char * +format_orientation(const char *value) +{ + static const char *const descriptions[] = { + "Normal", + "Flip horizontally", + "Rotate 180°", + "Flip vertically", + "Transpose", + "Rotate 90° clockwise", + "Antitranspose", + "Rotate 90° counterclockwise", + }; + char *end, *text; + long int orientation = strtol(value, &end, 10); + while (isspace((unsigned char)*end)) + end++; + if (end == value || *end || orientation < 1 || orientation > 8) + return estrdup(value); + if (asprintf(&text, "%s (%s)", descriptions[orientation - 1], value) < 0) + eprintf("asprintf:"); + return text; +} + +static char * +format_resolution(double dpi) +{ + char *text; + if (asprintf(&text, "%g DPI", dpi) < 0) + eprintf("asprintf:"); + return text; +} + +static char * +format_physical_size(int pixels, double resolution) +{ + char *text; + if (asprintf(&text, "%g cm", 2.54 * (double)pixels / resolution) < 0) + eprintf("asprintf:"); + return text; +} + +static char * +format_physical_width(GdkPixbuf *image, double resolution) +{ + return format_physical_size(gdk_pixbuf_get_width(image), resolution); +} + +static char * +format_physical_height(GdkPixbuf *image, double resolution) +{ + return format_physical_size(gdk_pixbuf_get_height(image), resolution); +} + +static GtkWidget * +create_property_table(struct file *file) +{ + GtkWidget *table; + unsigned i, row = 0u, rows = 3u; + const char *orientation; + char *text, orientation_buf[3u * sizeof(long int) + 1u]; + double x_dpi, y_dpi; + int has_x_dpi, has_y_dpi; + GList *keys, *key; + GHashTable *options; + GError *error; + long int li; + + options = gdk_pixbuf_get_options(file->image); + + keys = g_list_sort(g_hash_table_get_keys(options), (GCompareFunc)&strcmp); + for (key = keys; key; key = key->next) + rows += (unsigned)is_unrecognised_metadata(key->data); + + rows += file->exif_tags ? (unsigned)g_strv_length(file->exif_tags) : 0u; + + orientation = gdk_pixbuf_get_option(file->image, "orientation"); + if (!orientation && file->exif) { + error = NULL; + li = gexiv2_metadata_try_get_tag_long(file->exif, "Exif.Image.Orientation", &error); + if (error) + g_clear_error(&error); + else if (sprintf(orientation_buf, "%li", li) < 0) + eprintf("sprintf:"); + else + orientation = orientation_buf; + } + rows += orientation ? 1u : 0u; + + has_x_dpi = get_dpi_option(file->image, "x-dpi", &x_dpi); + has_y_dpi = get_dpi_option(file->image, "y-dpi", &y_dpi); + rows += 2u * (unsigned)(has_x_dpi + has_y_dpi); + + table = gtk_table_new(rows, 2, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 6); + gtk_table_set_col_spacings(GTK_TABLE(table), 12); + + add_metadata_row(GTK_TABLE(table), row++, "File", file->path); + add_metadata_row(GTK_TABLE(table), row++, "File size", text = format_file_size(file->size)); + free(text); + add_metadata_row(GTK_TABLE(table), row++, "Pixel dimensions", text = format_pixel_size(file->image)); + free(text); + if (orientation) { + add_metadata_row(GTK_TABLE(table), row++, "Orientation", text = format_orientation(orientation)); + free(text); + } + if (has_x_dpi) { + add_metadata_row(GTK_TABLE(table), row++, "Horizontal resolution", text = format_resolution(x_dpi)); + free(text); + } + if (has_y_dpi) { + add_metadata_row(GTK_TABLE(table), row++, "Vertical resolution", text = format_resolution(y_dpi)); + free(text); + } + if (has_x_dpi) { + add_metadata_row(GTK_TABLE(table), row++, "Physical width", text = format_physical_width(file->image, x_dpi)); + free(text); + } + if (has_y_dpi) { + add_metadata_row(GTK_TABLE(table), row++, "Physical height", text = format_physical_height(file->image, x_dpi)); + free(text); + } + + for (key = keys; key; key = key->next) + if (is_unrecognised_metadata(key->data)) + add_metadata_row(GTK_TABLE(table), row++, key->data, g_hash_table_lookup(options, key->data)); + g_list_free(keys); + + if (file->exif_tags) { + for (i = 0u; file->exif_tags[i]; i++) { + error = NULL; + text = gexiv2_metadata_try_get_tag_interpreted_string(file->exif, file->exif_tags[i], &error); + g_clear_error(&error); + + add_metadata_row(GTK_TABLE(table), row++, file->exif_tags[i], text ? text : ""); + g_free(text); + } + } + + return table; +} + void open_properties(struct file *file) { - /* TODO What shall "Properties" display? */ - - const char *name; - GtkWidget *dialog; + GtkWidget *dialog, *scrolled; + char *title; if (!file || !file->path || !file->image) return; - name = strrchr(file->path, '/'); - name = name ? &name[1u] : file->path; + dialog = gtk_dialog_new_with_buttons(title = format_title(file), + GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); + free(title); + gtk_window_set_default_size(GTK_WINDOW(dialog), 640, 480); - dialog = gtk_message_dialog_new(GTK_WINDOW(window), 0, GTK_MESSAGE_INFO, - GTK_BUTTONS_CLOSE, "%s", name); + scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_set_border_width(GTK_CONTAINER(scrolled), 12); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrolled, 1, 1, 0); - gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), - "%i × %i pixels", - gdk_pixbuf_get_width(file->image), - gdk_pixbuf_get_height(file->image)); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), create_property_table(file)); g_signal_connect_swapped(dialog, "response", G_CALLBACK(>k_widget_destroy), dialog); - gtk_widget_show(dialog); + gtk_widget_show_all(dialog); } @@ -1,6 +1,5 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" -#include <math.h> /* just for HUGE_VAL, no -lm needed */ Zoom zoom = {1.0}; enum zoom_mode zoom_mode = ZOOM_BEST_FIT; |
