aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO11
-rw-r--r--common.h4
-rw-r--r--config.mk4
-rw-r--r--files.c9
-rw-r--r--opening.c110
-rw-r--r--printing.c126
-rw-r--r--properties.c296
-rw-r--r--zoom.c1
8 files changed, 489 insertions, 72 deletions
diff --git a/TODO b/TODO
index 829d95e..395b963 100644
--- a/TODO
+++ b/TODO
@@ -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
diff --git a/common.h b/common.h
index aaf4b09..3b48191 100644
--- a/common.h
+++ b/common.h
@@ -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 */
diff --git a/config.mk b/config.mk
index 6a31794..bfc55fa 100644
--- a/config.mk
+++ b/config.mk
@@ -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
diff --git a/files.c b/files.c
index 2db0b17..262b768 100644
--- a/files.c
+++ b/files.c
@@ -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;
diff --git a/opening.c b/opening.c
index 3e98740..44dc587 100644
--- a/opening.c
+++ b/opening.c
@@ -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);
diff --git a/printing.c b/printing.c
index e3b5431..d75a68e 100644
--- a/printing.c
+++ b/printing.c
@@ -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(&gtk_widget_destroy), dialog);
- gtk_widget_show(dialog);
+ gtk_widget_show_all(dialog);
}
diff --git a/zoom.c b/zoom.c
index caa9884..7fa617a 100644
--- a/zoom.c
+++ b/zoom.c
@@ -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;