aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--TODO42
-rw-r--r--collection.c364
-rw-r--r--common.h67
-rw-r--r--config.mk4
-rw-r--r--dragging.c69
-rw-r--r--dropping.c65
-rw-r--r--events.c37
-rw-r--r--files.c46
-rw-r--r--gbild.c189
-rw-r--r--image.c70
-rw-r--r--opening.c49
-rw-r--r--preferences.c223
-rw-r--r--printing.c95
-rw-r--r--properties.c159
-rw-r--r--render.c118
-rw-r--r--transform.c145
-rw-r--r--ui.c71
-rw-r--r--zoom.c31
19 files changed, 1470 insertions, 377 deletions
diff --git a/Makefile b/Makefile
index 11f1356..213a3fa 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,7 @@ CONFIGFILE = config.mk
include $(CONFIGFILE)
OBJ =\
+ collection.o\
dragging.o\
dropping.o\
events.o\
@@ -11,8 +12,10 @@ OBJ =\
filters.o\
image.o\
opening.o\
+ preferences.o\
printing.o\
properties.o\
+ render.o\
transform.o\
ui.o\
zoom.o\
diff --git a/TODO b/TODO
index 395b963..f0da97e 100644
--- a/TODO
+++ b/TODO
@@ -1,11 +1,10 @@
-Optional (opt-in) side-pane ("_Property pane") with image properties (accelerator: F9)
+Use hand cursor when dragging image to move it
-Optional (opt-in) bottom-pane ("Image _collection") with image thumbnails (accelerator: Control+F9)
- The bottom-pane shall support:
- * drag and drop to reorder
- * drag to another application to drop the file by path there
- * drop form another application to add the image to the collection
- * right-clicking on a thumbnail and "_Remove from collection" (icon: edit-delete)
+Properties shall display SVG data
+
+Render icon of image when also when dragging from the image collection
+
+Save preferences to file
Automatically go to next picture
@@ -13,18 +12,23 @@ Animation support
"Properties" shall display Exif thumbnail if embedded
-Preferences for transparency
-
-Preferences for changling GDK_INTERP_BILINEAR
-
-Preferences for scaling to physical size rather than 1:1 pixel mapping
-
-Perference for automatically applying embedded transformation ("orientation")
-
-Add support for farbfeld
+Colour space support
-Add support for lss16
+Add support for bicubic interpolation
-Add support for pam (portable anymap)
+Add support for Lanczos resampling
-Colour space support
+Add command line options for settings and panes:
+ +f = no fullscreen
+ -p = show property pane
+ +p = hide property pane
+ -c = show image collection
+ +c = hide image collection
+ -t = apply embedded orientation
+ +t = do not embedded orientation
+ -n = apply normal size is physical size
+ +n = do not apply normal size is physical size
+ -s NAME = apply scaling algorithm NAME
+ -S SIZE = apply chequer size SIZE, 0 for no chequered background
+ -C COLOUR1[:COLOUR2] = use COLOUR1 and COLOUR2 (= COLOUR1 if omitted) for the chequered background
+ +C = do not apply chequered background
diff --git a/collection.c b/collection.c
new file mode 100644
index 0000000..c39f80b
--- /dev/null
+++ b/collection.c
@@ -0,0 +1,364 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+GtkWidget *collection_view;
+
+static GtkWidget *collection_pane;
+static GtkCellRenderer *collection_renderer;
+static GtkListStore *collection_model;
+static int collection_height = -1;
+static size_t popup_file;
+
+static char url_list_name[] = "text/uri-list";
+static GtkTargetEntry url_list = {url_list_name, 0u, 0u};
+
+enum {
+ COLLECTION_THUMBNAIL,
+ COLLECTION_INDEX,
+ COLLECTION_COLUMNS
+};
+
+static void
+free_paths(GList *paths)
+{
+ GList *path;
+ for (path = paths; path; path = path->next)
+ gtk_tree_path_free(path->data);
+ g_list_free(paths);
+}
+
+static int
+collection_thumbnail_size(void)
+{
+ return MAX(1, collection_view->allocation.height - 16);
+}
+
+static GdkPixbuf *
+create_thumbnail(struct file *file, int maximum_size)
+{
+ GdkPixbuf *thumbnail = NULL;
+ int width = file->image.width;
+ int height = file->image.height;
+ double w = (double)width;
+ double h = (double)height;
+ double s = (double)maximum_size / MAX(w, h);
+ s = MIN(1.0, s);
+ width = (int)(s * w);
+ height = (int)(s * h);
+ width = MAX(1, width);
+ height = MAX(1, height);
+ if (file->image.vector) {
+ thumbnail = rasterise(file->image.vector, width, height);
+ if (thumbnail)
+ return thumbnail;
+ }
+ if (file->image.raster)
+ return gdk_pixbuf_scale_simple(file->image.raster, width, height, GDK_INTERP_BILINEAR);
+ return NULL;
+}
+
+static void
+populate_collection(void)
+{
+ GtkTreeIter iter;
+ GdkPixbuf *thumbnail;
+ size_t i;
+ int thumbnail_size = collection_thumbnail_size();
+ int item_size = thumbnail_size + 2 * gtk_icon_view_get_item_padding(GTK_ICON_VIEW(collection_view));
+ gtk_icon_view_set_columns(GTK_ICON_VIEW(collection_view), nfiles ? (int)nfiles : 1);
+ gtk_icon_view_set_item_width(GTK_ICON_VIEW(collection_view), item_size);
+ gtk_cell_renderer_set_fixed_size(collection_renderer, thumbnail_size, thumbnail_size);
+ gtk_list_store_clear(collection_model);
+ for (i = 0u; i < nfiles; i++) {
+ thumbnail = create_thumbnail(&files[i], thumbnail_size);
+ if (!thumbnail)
+ continue;
+ gtk_list_store_append(collection_model, &iter);
+ gtk_list_store_set(collection_model, &iter, COLLECTION_THUMBNAIL,
+ thumbnail, COLLECTION_INDEX, (unsigned long)i, -1);
+ g_object_unref(thumbnail);
+ }
+}
+
+static void
+select_current_file(void)
+{
+ GtkTreeIter iter;
+ unsigned long index;
+ GtkTreePath *path;
+ if (!nfiles || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(collection_model), &iter))
+ return;
+ do {
+ gtk_tree_model_get(GTK_TREE_MODEL(collection_model), &iter, COLLECTION_INDEX, &index, -1);
+ if ((size_t)index == current_file) {
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(collection_model), &iter);
+ gtk_icon_view_select_path(GTK_ICON_VIEW(collection_view), path);
+ gtk_icon_view_scroll_to_path(GTK_ICON_VIEW(collection_view), path, 1, 0.5, 0.5);
+ gtk_tree_path_free(path);
+ return;
+ }
+ } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(collection_model), &iter));
+}
+
+static int
+get_selected_index(size_t *index)
+{
+ GList *paths;
+ GtkTreeIter iter;
+ unsigned long value;
+ int found = 0;
+ paths = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(collection_view));
+ if (!paths)
+ return 0;
+ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(collection_model), &iter, paths->data)) {
+ gtk_tree_model_get(GTK_TREE_MODEL(collection_model), &iter, COLLECTION_INDEX, &value, -1);
+ *index = (size_t)value;
+ found = 1;
+ }
+ free_paths(paths);
+ return found;
+}
+
+static void
+collection_size_allocated(GtkWidget *widget, GtkAllocation *allocation, void *user)
+{
+ (void) widget;
+ (void) user;
+ if (allocation->height == collection_height)
+ return;
+ collection_height = allocation->height;
+ populate_collection();
+ select_current_file();
+}
+
+static int
+collection_index_at(int x, int y, size_t *index)
+{
+ GtkTreeIter iter;
+ unsigned long value;
+ int found = 0;
+ GtkTreePath *path = gtk_icon_view_get_path_at_pos(GTK_ICON_VIEW(collection_view), x, y);
+ if (!path)
+ return 0;
+ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(collection_model), &iter, path)) {
+ gtk_tree_model_get(GTK_TREE_MODEL(collection_model), &iter, COLLECTION_INDEX, &value, -1);
+ *index = (size_t)value;
+ found = 1;
+ }
+ gtk_tree_path_free(path);
+ return found;
+}
+
+void
+navigate_collection_drag(void)
+{
+ size_t index;
+ if (get_selected_index(&index) && navigate_without_redraw(index, 0u))
+ navigate_redraw();
+}
+
+static void
+collection_selection_changed(GtkIconView *view, void *user)
+{
+ (void) view;
+ (void) user;
+ navigate_collection_drag();
+}
+
+static void
+reorder_file(size_t source, size_t target)
+{
+ struct file moved;
+ if (target >= nfiles)
+ target = nfiles - 1u;
+ if (!nfiles || source >= nfiles || source == target)
+ return;
+ moved = files[source];
+ if (source < target) {
+ memmove(&files[source], &files[source + 1u], (target - source) * sizeof(*files));
+ if (current_file == source)
+ current_file = target;
+ else if (current_file > source && current_file <= target)
+ current_file -= 1u;
+ } else {
+ memmove(&files[target + 1u], &files[target], (source - target) * sizeof(*files));
+ if (current_file == source)
+ current_file = target;
+ else if (current_file >= target && current_file < source)
+ current_file += 1u;
+ }
+ files[target] = moved;
+ collection_files_changed();
+ navigate_redraw();
+}
+
+static void
+drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data,
+ unsigned int info, unsigned int time, void *user)
+{
+ GError *error = NULL;
+ size_t index;
+ char *uri;
+ (void) widget;
+ (void) context;
+ (void) info;
+ (void) time;
+ (void) user;
+ if (!get_selected_index(&index) || index >= nfiles || !files[index].path)
+ return;
+ uri = g_filename_to_uri(files[index].path, NULL, &error);
+ if (!uri) {
+ weprintf("g_filename_to_uri: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+ gtk_selection_data_set_uris(data, (char *[]){uri, NULL});
+ g_free(uri);
+}
+
+static void
+drag_data_received(GtkWidget *widget, GdkDragContext *context, int x, int y,
+ GtkSelectionData *data, unsigned int info, unsigned int time, void *user)
+{
+ struct file *dropped_files;
+ char **uris;
+ size_t source_index, n;
+ GtkTreePath *path = NULL;
+ GtkWidget *source = gtk_drag_get_source_widget(context);
+ size_t target;
+ (void) info;
+ (void) user;
+ if (source == collection_view) {
+ if (get_selected_index(&source_index))
+ goto reorder;
+ } else if (source == display_area) {
+ if (source == display_area)
+ source_index = current_file;
+ reorder:
+ if (gtk_icon_view_get_dest_item_at_pos(GTK_ICON_VIEW(widget), x, y, &path, NULL))
+ target = (size_t)gtk_tree_path_get_indices(path)[0];
+ else
+ target = nfiles;
+ if (path)
+ gtk_tree_path_free(path);
+ reorder_file(source_index, target);
+ } else {
+ uris = gtk_selection_data_get_uris(data);
+ n = get_dropped_files(&dropped_files, uris);
+ if (n) {
+ add_images(dropped_files, files_from_array(dropped_files, n));
+ free(dropped_files);
+ }
+ if (uris)
+ g_strfreev(uris);
+ }
+ gtk_drag_finish(context, 1, 0, time);
+}
+
+static void
+remove_from_collection(GtkMenuItem *item, void *user)
+{
+ struct file removed;
+ (void) item;
+ (void) user;
+ if (popup_file >= nfiles)
+ return;
+ removed = files[popup_file];
+ if (popup_file + 1u < nfiles)
+ memmove(&files[popup_file], &files[popup_file + 1u], (nfiles - (popup_file + 1u)) * sizeof(*files));
+ nfiles -= 1u;
+ destroy_file(&removed);
+ forget_transformed_raster();
+ if (current_file > popup_file)
+ current_file -= 1u;
+ else if (current_file >= nfiles)
+ current_file = nfiles ? nfiles - 1u : 0u;
+ collection_files_changed();
+ navigate_redraw();
+ set_actions_sensitive();
+}
+
+static int
+collection_button_pressed(GtkWidget *widget, GdkEventButton *event, void *user)
+{
+ GtkWidget *menu, *item;
+ size_t index;
+ (void) widget;
+ (void) user;
+ if (event->button != 3u || !collection_index_at((int)event->x, (int)event->y, &index))
+ return 0;
+ popup_file = index;
+ menu = gtk_menu_new();
+ item = gtk_image_menu_item_new_from_stock(GTK_STOCK_DELETE, NULL);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(item), "_Remove from collection");
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ g_signal_connect(item, "activate", G_CALLBACK(&remove_from_collection), NULL);
+ gtk_widget_show_all(menu);
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
+ return 1;
+}
+
+GtkWidget *
+create_collection_pane(void)
+{
+ collection_pane = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(collection_pane), GTK_POLICY_ALWAYS, GTK_POLICY_NEVER);
+ gtk_widget_set_size_request(collection_pane, -1, INITIAL_COLLECTION_PANE_HEIGHT);
+ gtk_widget_set_no_show_all(collection_pane, 1);
+
+ collection_model = gtk_list_store_new(COLLECTION_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_ULONG);
+ collection_view = gtk_icon_view_new_with_model(GTK_TREE_MODEL(collection_model));
+ gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(collection_view), GTK_SELECTION_SINGLE);
+ gtk_icon_view_set_orientation(GTK_ICON_VIEW(collection_view), GTK_ORIENTATION_HORIZONTAL);
+
+ collection_renderer = gtk_cell_renderer_pixbuf_new();
+ g_object_set(collection_renderer, "xalign", 0.5, "yalign", 0.5, NULL);
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(collection_view), collection_renderer, 0);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(collection_view), collection_renderer, "pixbuf", COLLECTION_THUMBNAIL);
+ gtk_container_add(GTK_CONTAINER(collection_pane), collection_view);
+
+ gtk_widget_add_events(collection_view, GDK_BUTTON_PRESS_MASK);
+ gtk_drag_source_set(collection_view, GDK_BUTTON1_MASK, &url_list, 1u, GDK_ACTION_COPY | GDK_ACTION_MOVE);
+ gtk_drag_dest_set(collection_view, GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+ &url_list, 1u, GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect(collection_view, "size-allocate", G_CALLBACK(&collection_size_allocated), NULL);
+ g_signal_connect(collection_view, "selection-changed", G_CALLBACK(&collection_selection_changed), NULL);
+ g_signal_connect(collection_view, "button-press-event", G_CALLBACK(&collection_button_pressed), NULL);
+ g_signal_connect(collection_view, "drag-data-get", G_CALLBACK(&drag_data_get), NULL);
+ g_signal_connect(collection_view, "drag-data-received", G_CALLBACK(&drag_data_received), NULL);
+
+ return collection_pane;
+}
+
+void
+set_collection_pane_visible(int visible)
+{
+ if (visible) {
+ gtk_widget_show_all(collection_view);
+ gtk_widget_show(collection_pane);
+ populate_collection();
+ select_current_file();
+ gtk_widget_grab_focus(collection_view);
+ } else {
+ gtk_widget_hide(collection_pane);
+ gtk_widget_grab_focus(display_area);
+ }
+ g_idle_add(&recalculate_zoom_mode, NULL);
+}
+
+void
+collection_files_changed(void)
+{
+ if (collection_pane && gtk_widget_get_visible(collection_pane)) {
+ populate_collection();
+ select_current_file();
+ }
+}
+
+void
+update_collection(void)
+{
+ if (collection_pane && gtk_widget_get_visible(collection_pane))
+ select_current_file();
+}
diff --git a/common.h b/common.h
index 3b48191..66462b0 100644
--- a/common.h
+++ b/common.h
@@ -11,6 +11,12 @@
#ifndef PREVIEW_HEIGHT
# define PREVIEW_HEIGHT 256
#endif
+#ifndef INITIAL_COLLECTION_PANE_HEIGHT
+# define INITIAL_COLLECTION_PANE_HEIGHT 160
+#endif
+#ifndef INITIAL_PROPERTY_PANE_HEIGHT
+# define INITIAL_PROPERTY_PANE_HEIGHT 300
+#endif
#if defined(__GNUC__)
# pragma GCC diagnostic push
@@ -20,6 +26,7 @@
#include <gdk/gdkkeysyms.h>
#include <gexiv2/gexiv2.h>
#include <gtk/gtk.h>
+#include <librsvg/rsvg.h>
#include <libsimple.h>
#include <libsimple-arg.h>
#include <math.h>
@@ -77,10 +84,23 @@ enum transformation {
TRANS_NEGATIVE = 0x08
};
+struct image {
+ GdkPixbuf *raster;
+ RsvgHandle *vector;
+ int width;
+ int height;
+ int has_x_dpi;
+ int has_y_dpi;
+ int has_transformer;
+ cairo_matrix_t transformer;
+ double x_dpi;
+ double y_dpi;
+};
+
struct file {
char *path;
off_t size;
- GdkPixbuf *image;
+ struct image image;
char **exif_tags;
GExiv2Metadata *exif;
};
@@ -88,10 +108,19 @@ struct file {
typedef double Zoom; /* TODO */
+/* collection.c */
+extern GtkWidget *collection_view;
+GtkWidget *create_collection_pane(void);
+void set_collection_pane_visible(int visible);
+void navigate_collection_drag(void);
+void collection_files_changed(void);
+void update_collection(void);
+
/* dragging.c */
void set_up_dragging(void);
/* dropping.c */
+size_t get_dropped_files(struct file **files_out, char **uris);
void set_up_dropping(void);
/* events.c */
@@ -99,6 +128,9 @@ void open_action(GtkAction *action, void *user);
void reload_action(GtkAction *action, void *user);
void print_action(GtkAction *action, void *user);
void properties_action(GtkAction *action, void *user);
+void property_pane_action(GtkToggleAction *action, void *user);
+void collection_pane_action(GtkToggleAction *action, void *user);
+void preferences_action(GtkAction *action, void *user);
void close_action(GtkAction *action, void *user);
void fullscreen_action(GtkToggleAction *action, void *user);
void reset_transformations_action(GtkAction *action, void *user);
@@ -128,6 +160,7 @@ int scroll_event(GtkWidget *widget, GdkEventScroll *event, void *user);
extern struct file *files;
extern size_t nfiles;
extern size_t current_file;
+void destroy_file(struct file *file);
void free_files(void);
size_t files_from_array(struct file *selected_files, size_t nselected_files);
size_t files_from_list(struct file **files_out, GSList *selected_files);
@@ -140,26 +173,42 @@ void add_image_filters(GtkFileChooser *chooser);
void free_image_filters(void);
/* image.c */
-extern GdkPixbuf *transformed_image;
-GdkPixbuf *get_image(void);
-GdkPixbuf *get_original_image(void);
+extern GdkPixbuf *transformed_raster;
+void forget_transformed_raster(void);
+int get_image(struct image *image);
+int get_original_image(struct image *image);
/* opening.c */
int load_image(struct file *file, int errors_to_stderr);
GtkFileChooser *construct_chooser(void);
/* printing.c */
-void open_print(GdkPixbuf *image);
+void open_print(struct image *image);
+
+/* render.c */
+GdkPixbuf *rasterise(RsvgHandle *vector, int width, int height);
+void paint_image(cairo_t *cr, struct image *image);
+
+/* preferences.c */
+extern cairo_filter_t scaling_filter;
+extern GdkColor chequer_colours[2];
+extern uint16_t chequer_alphas[2];
+extern int chequered_background;
+extern int chequer_size;
+void open_preferences(void);
/* properties.c */
+GtkWidget *create_property_table(struct file *file);
void open_properties(struct file *file);
/* transform.c */
extern enum transformation trans;
-GdkPixbuf *apply_transformations(GdkPixbuf *image);
+int apply_transformations(struct image *image, struct image *image_out);
enum transformation reset_transformations_without_redraw(void);
int add_transformation_without_redraw(enum transformation augment);
+int swaps_dimensions(enum transformation t);
void transform_point(enum transformation t, double *x, double *y, double *w, double *h);
+int get_affine_matrix(cairo_matrix_t *matrix_out, enum transformation t, int width, int height);
/* ui.c */
extern GtkWidget *window;
@@ -170,6 +219,8 @@ void set_up_ui(void);
void set_actions_sensitive(void);
void clear_toggles(enum toggle_group group, GtkToggleAction *except);
void set_fullscreen_mode(int active);
+void set_property_pane_visible(int visible);
+void update_property_pane(void);
int activate_fullscreen(void *user);
int deactivate_fullscreen(void *user);
void set_statusbar_message(const char *message);
@@ -183,8 +234,8 @@ double get_zoom(Zoom z);
Zoom make_zoom(double s);
Zoom zoom_in(Zoom z);
Zoom zoom_out(Zoom z);
-Zoom calculate_fit(GdkPixbuf *image, int xfit, int yfit, double cap);
-Zoom calculate_zoom_mode(GdkPixbuf *image, enum zoom_mode mode);
+Zoom calculate_fit(struct image *image, int xfit, int yfit, double cap);
+Zoom calculate_zoom_mode(struct image *image, enum zoom_mode mode);
/* gbild.c */
int expose_image(GtkWidget *widget, GdkEventExpose *event, void *user);
diff --git a/config.mk b/config.mk
index bfc55fa..6d5c613 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 gexiv2)
-LDFLAGS = $$(pkg-config --libs gtk+-2.0 gexiv2) -lsimple -lm
+CFLAGS = $$(pkg-config --cflags gtk+-2.0 gexiv2 librsvg-2.0)
+LDFLAGS = $$(pkg-config --libs gtk+-2.0 gexiv2 librsvg-2.0) -lsimple -lm
diff --git a/dragging.c b/dragging.c
index 3ac3ff5..4a2da95 100644
--- a/dragging.c
+++ b/dragging.c
@@ -19,16 +19,27 @@ static int
button_pressed(GtkWidget *widget, GdkEventButton *event, void *user)
{
(void) user;
- if (event->button == 2u || (event->state & GDK_CONTROL_MASK)) {
- if (event->button == 1u) {
- gtk_drag_source_unset(widget);
- drag_source_disabled = 1;
- }
+ switch (event->button) {
+ case 1u:
+ if (~event->state & GDK_CONTROL_MASK)
+ return 0;
+ gtk_drag_source_unset(widget);
+ drag_source_disabled = 1;
+ /* fall through */
+ case 2u:
dragging = 1;
drag_x = (int)event->x_root;
drag_y = (int)event->y_root;
+ return 1;
+ case 8u:
+ previous_action(NULL, NULL);
+ return 1;
+ case 9u:
+ next_action(NULL, NULL);
+ return 1;
+ default:
+ return 0;
}
- return dragging;
}
static int
@@ -62,36 +73,40 @@ pointer_moved(GtkWidget *widget, GdkEventMotion *event, void *user)
static void
drag_begin(GtkWidget *widget, GdkDragContext *context, void *user)
{
- GdkPixbuf *image, *icon;
+ struct image image;
+ GdkPixbuf *icon;
intmax_t height, width;
+ int w, h;
(void) widget;
(void) user;
- image = get_original_image();
- if (!image)
+ if (!get_original_image(&image))
return;
- width = gdk_pixbuf_get_width(image);
- height = gdk_pixbuf_get_height(image);
- if (width <= DRAG_ICON_SIZE && height <= DRAG_ICON_SIZE) {
- gtk_drag_set_icon_pixbuf(context, image, 0, 0);
- return;
+ width = (intmax_t)image.width;
+ height = (intmax_t)image.height;
+ if (width > DRAG_ICON_SIZE || height > DRAG_ICON_SIZE) {
+ if (width >= height) {
+ height = height * DRAG_ICON_SIZE / width;
+ height = MAX(1, height);
+ width = DRAG_ICON_SIZE;
+ } else {
+ width = width * DRAG_ICON_SIZE / height;
+ width = MAX(1, width);
+ height = DRAG_ICON_SIZE;
+ }
}
- if (width >= height) {
- height = height * DRAG_ICON_SIZE / width;
- height = MAX(1, height);
- width = DRAG_ICON_SIZE;
- } else {
- width = width * DRAG_ICON_SIZE / height;
- width = MAX(1, width);
- height = DRAG_ICON_SIZE;
+ w = (int)width;
+ h = (int)height;
+
+ if (image.raster && width == image.width && height == image.height) {
+ gtk_drag_set_icon_pixbuf(context, image.raster, 0, 0);
+ } else if ((image.vector && (icon = rasterise(image.vector, w, h))) ||
+ (image.raster && (icon = gdk_pixbuf_scale_simple(image.raster, w, h, GDK_INTERP_BILINEAR)))) {
+ gtk_drag_set_icon_pixbuf(context, icon, 0, 0);
+ g_object_unref(icon);
}
- icon = gdk_pixbuf_scale_simple(image, (int)width, (int)height, GDK_INTERP_BILINEAR);
- if (!icon)
- return;
- gtk_drag_set_icon_pixbuf(context, icon, 0, 0);
- g_object_unref(icon);
}
static void
diff --git a/dropping.c b/dropping.c
index 412f0ab..e7bf20f 100644
--- a/dropping.c
+++ b/dropping.c
@@ -4,7 +4,12 @@
static char url_list_name[] = "text/uri-list";
static GtkTargetEntry url_list = {url_list_name, 0u, 0u};
-static size_t
+static unsigned int active_drop_timer;
+static GdkDragContext *active_drop_context;
+static GtkWidget *active_drop_widget;
+static GdkDragAction active_drop_action;
+
+size_t
get_dropped_files(struct file **files_out, char **uris)
{
GError *error = NULL;
@@ -55,6 +60,18 @@ drop_files(GtkWidget *widget, GdkDragContext *context, int x, int y,
(void) info;
(void) user;
+ if (active_drop_timer) {
+ g_source_remove(active_drop_timer);
+ active_drop_timer = 0;
+ }
+
+ if (gtk_drag_get_source_widget(context) == collection_view) {
+ if (widget == display_area)
+ navigate_collection_drag();
+ else
+ accepted = 0;
+ goto out;
+ }
if (gtk_drag_get_source_widget(context) == display_area) {
accepted = 0;
goto out;
@@ -85,6 +102,31 @@ out:
}
static int
+poll_modifiers(void *user)
+{
+ GdkModifierType modifiers;
+ GdkDragAction action = 0;
+
+ (void) user;
+
+ if (!GTK_WIDGET_REALIZED(active_drop_widget))
+ return 1;
+
+ gdk_window_get_pointer(active_drop_widget->window, NULL, NULL, &modifiers);
+
+ action = (modifiers & GDK_CONTROL_MASK) ? GDK_ACTION_COPY : GDK_ACTION_MOVE;
+ if (!(gdk_drag_context_get_actions(active_drop_context) & action))
+ action = 0;
+
+ if (action != active_drop_action) {
+ active_drop_action = action;
+ gdk_drag_status(active_drop_context, action, GDK_CURRENT_TIME);
+ }
+
+ return 1;
+}
+
+static int
drop_motion(GtkWidget *widget, GdkDragContext *context, int x, int y, unsigned int time, void *user)
{
GdkWindow *event_window = gtk_widget_get_window(widget);
@@ -107,14 +149,35 @@ drop_motion(GtkWidget *widget, GdkDragContext *context, int x, int y, unsigned i
out:
gdk_drag_status(context, action, time);
+
+ if (!active_drop_timer) {
+ active_drop_context = context;
+ active_drop_widget = widget;
+ active_drop_action = action;
+ active_drop_timer = g_timeout_add(50, &poll_modifiers, NULL);
+ }
return 1;
}
+static void
+drop_leave(GtkWidget *widget, GdkDragContext *context, unsigned int time, void *user)
+{
+ (void) widget;
+ (void) context;
+ (void) time;
+ (void) user;
+ if (active_drop_timer) {
+ g_source_remove(active_drop_timer);
+ active_drop_timer = 0;
+ }
+}
+
void
set_up_dropping(void)
{
gtk_drag_dest_set(window, GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
&url_list, 1u, GDK_ACTION_COPY | GDK_ACTION_MOVE);
g_signal_connect(window, "drag-motion", G_CALLBACK(&drop_motion), NULL);
+ g_signal_connect(window, "drag-leave", G_CALLBACK(&drop_leave), NULL);
g_signal_connect(window, "drag-data-received", G_CALLBACK(&drop_files), NULL);
}
diff --git a/events.c b/events.c
index 4c5910e..7a45d66 100644
--- a/events.c
+++ b/events.c
@@ -26,16 +26,14 @@ transform(enum transformation augment)
static void
set_zoom_fit(GtkToggleAction *action, enum zoom_mode mode)
{
- GdkPixbuf *image;
-
+ struct image image;
if (gtk_toggle_action_get_active(action)) {
zoom_mode = mode;
- if ((image = get_image()))
- set_zoom_and_recentre(calculate_zoom_mode(image, zoom_mode));
+ if (get_image(&image))
+ set_zoom_and_recentre(calculate_zoom_mode(&image, zoom_mode));
} else if (zoom_mode == mode) {
zoom_mode = ZOOM_CUSTOM; /* deselect zoom */
}
-
clear_toggles(GROUP_ZOOM, action);
}
@@ -70,9 +68,11 @@ reload_action(GtkAction *action, void *user)
void
print_action(GtkAction *action, void *user)
{
+ struct image image;
(void) action;
(void) user;
- open_print(get_image());
+ if (get_image(&image))
+ open_print(&image);
}
void
@@ -84,6 +84,28 @@ properties_action(GtkAction *action, void *user)
}
void
+preferences_action(GtkAction *action, void *user)
+{
+ (void) action;
+ (void) user;
+ open_preferences();
+}
+
+void
+property_pane_action(GtkToggleAction *action, void *user)
+{
+ (void) user;
+ set_property_pane_visible(gtk_toggle_action_get_active(action));
+}
+
+void
+collection_pane_action(GtkToggleAction *action, void *user)
+{
+ (void) user;
+ set_collection_pane_visible(gtk_toggle_action_get_active(action));
+}
+
+void
close_action(GtkAction *action, void *user)
{
(void) action;
@@ -278,6 +300,9 @@ key_pressed(GtkWidget *widget, GdkEventKey *event, void *user)
if (event->state & GDK_CONTROL_MASK)
return 0;
+ if (gtk_window_get_focus(GTK_WINDOW(window)) != display_area)
+ return 0;
+
amount = (event->state & GDK_MOD1_MASK) ? 1 : SCROLL_AMOUNT;
hamount = (double)amount;
vamount = (double)amount;
diff --git a/files.c b/files.c
index 262b768..fe96e82 100644
--- a/files.c
+++ b/files.c
@@ -5,17 +5,31 @@ struct file *files = NULL;
size_t nfiles = 0u;
size_t current_file = 0u;
+static void
+null_image_data(struct file *file)
+{
+ file->image.raster = NULL;
+ file->image.vector = NULL;
+ file->exif_tags = NULL;
+ file->exif = NULL;
+}
+
+void
+destroy_file(struct file *file)
+{
+ free(file->path);
+ g_clear_object(&file->image.raster);
+ g_clear_object(&file->image.vector);
+ if (file->exif_tags)
+ g_strfreev(file->exif_tags);
+ g_clear_object(&file->exif);
+}
+
void
free_files(void)
{
- while (nfiles) {
- --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);
- }
+ while (nfiles)
+ destroy_file(&files[--nfiles]);
free(files);
files = NULL;
current_file = 0u;
@@ -27,9 +41,7 @@ files_from_array(struct file *selected_files, size_t nselected_files)
size_t i, n;
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;
+ null_image_data(&selected_files[i]);
if (load_image(&selected_files[i], 0)) {
if (i != n)
selected_files[n] = selected_files[i];
@@ -57,9 +69,7 @@ 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;
+ null_image_data(&new_files[i]);
g_free(file->data);
if (load_image(&new_files[i], 0))
@@ -94,9 +104,7 @@ set_files_from_argv(int argc, char **argv)
files = ecalloc(nfiles, sizeof(*files));
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;
+ null_image_data(&files[i]);
if (!load_image(&files[i], 1)) {
free(files[i].path);
ok = 0;
@@ -110,9 +118,7 @@ set_files_from_argv(int argc, char **argv)
struct file *
get_file(void)
{
- if (current_file >= nfiles)
- return NULL;
- return &files[current_file];
+ return current_file < nfiles ? &files[current_file] : NULL;
}
int
diff --git a/gbild.c b/gbild.c
index ffe51b0..79b356a 100644
--- a/gbild.c
+++ b/gbild.c
@@ -5,8 +5,13 @@ USAGE("[-f] [file] ...");
static int suspend_expose = 0;
+static cairo_pattern_t *chequer_pattern;
+static GdkColor cached_chequer_colours[2];
+static uint16_t cached_chequer_alphas[2];
+static int cached_chequer_size;
+
static size_t
-format_statusbar_message(char *buffer, size_t size, struct file *file)
+format_statusbar_message(char *buffer, size_t size, struct file *file, struct image *image)
{
char fsize_decimals[3];
const char units[] = "BKMGTPEZYRQ";
@@ -35,7 +40,7 @@ done:
"Image %zu of %zu | %ju%s %c | %i × %i pixels | %g%% scale",
current_file + 1u, nfiles,
fsize, fsize_decimals, units[unit],
- gdk_pixbuf_get_width(file->image), gdk_pixbuf_get_height(file->image),
+ image->width, image->height,
100.0 * get_zoom(zoom));
if (r < 0)
@@ -49,21 +54,104 @@ update_status(void)
struct file *file = get_file();
char *message = NULL;
size_t len;
- if (file) {
- len = format_statusbar_message(NULL, 0u, file);
+ struct image image;
+ if (file && get_original_image(&image)) {
+ len = format_statusbar_message(NULL, 0u, file, &image);
message = malloc(++len);
- format_statusbar_message(message, len, file);
+ format_statusbar_message(message, len, file, &image);
}
set_statusbar_message(message);
free(message);
}
+static cairo_pattern_t *
+get_chequer_pattern(void)
+{
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ int i, row, column;
+ double r[2], g[2], b[2], a[2], s;
+
+ if (!chequer_pattern || cached_chequer_size != chequer_size)
+ goto changed;
+ for (i = 0; i < 2; i++) {
+ if (cached_chequer_colours[i].red != chequer_colours[i].red ||
+ cached_chequer_colours[i].green != chequer_colours[i].green ||
+ cached_chequer_colours[i].blue != chequer_colours[i].blue ||
+ cached_chequer_alphas[i] != chequer_alphas[i])
+ goto changed;
+ }
+ return chequer_pattern;
+
+changed:
+ for (i = 0; i < 2; i++) {
+ cached_chequer_colours[i] = chequer_colours[i];
+ cached_chequer_alphas[i] = chequer_alphas[i];
+ }
+ cached_chequer_size = chequer_size;
+
+ for (i = 0; i < 2; i++) {
+ r[i] = (1.0 / 65535.0) * chequer_colours[i].red;
+ g[i] = (1.0 / 65535.0) * chequer_colours[i].green;
+ b[i] = (1.0 / 65535.0) * chequer_colours[i].blue;
+ a[i] = (1.0 / 65535.0) * chequer_alphas[i];
+ }
+
+ s = (double)chequer_size;
+
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2 * chequer_size, 2 * chequer_size);
+ cr = cairo_create(surface);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+
+ for (row = 0; row < 2; row++) {
+ for (column = 0; column < 2; column++) {
+ i = (row ^ column) & 1;
+ cairo_set_source_rgba(cr, r[i], g[i], b[i], a[i]);
+ cairo_rectangle(cr, (double)column * s, (double)row * s, s, s);
+ cairo_fill(cr);
+ }
+ }
+ cairo_destroy(cr);
+
+ if (chequer_pattern)
+ cairo_pattern_destroy(chequer_pattern);
+ chequer_pattern = cairo_pattern_create_for_surface(surface);
+ cairo_pattern_set_extend(chequer_pattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(chequer_pattern, CAIRO_FILTER_NEAREST);
+
+ cairo_surface_destroy(surface);
+ return chequer_pattern;
+}
+
+static void
+draw_transparency_background(cairo_t *cr, struct image *image, double x0, double y0, double width, double height)
+{
+ cairo_matrix_t matrix;
+
+ if (!chequered_background || gtk_toggle_action_get_active(fullscreen))
+ return;
+ if (!image->vector && !gdk_pixbuf_get_has_alpha(image->raster))
+ return;
+
+ cairo_matrix_init_identity(&matrix);
+ cairo_pattern_set_matrix(get_chequer_pattern(), &matrix);
+
+ cairo_save(cr);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+ cairo_rectangle(cr, x0, y0, width, height);
+ cairo_clip(cr);
+ cairo_set_source(cr, chequer_pattern);
+ cairo_paint(cr);
+ cairo_restore(cr);
+}
+
int
expose_image(GtkWidget *widget, GdkEventExpose *event, void *user)
{
- GdkPixbuf *image;
+ struct image image;
cairo_t *cr;
double height, width, x, y, s;
+ RsvgRectangle viewport;
(void) user;
@@ -76,15 +164,14 @@ expose_image(GtkWidget *widget, GdkEventExpose *event, void *user)
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height);
- image = get_image();
- if (!image) {
+ if (!get_image(&image)) {
cairo_fill(cr);
goto out;
}
s = get_zoom(zoom);
- width = s * gdk_pixbuf_get_width(image);
- height = s * gdk_pixbuf_get_height(image);
+ width = s * (double)image.width;
+ height = s * (double)image.height;
if (width < widget->allocation.width)
x = 0.5 * ((double)widget->allocation.width - width);
else
@@ -95,13 +182,24 @@ expose_image(GtkWidget *widget, GdkEventExpose *event, void *user)
y = -logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll))->value;
cairo_fill(cr);
+ draw_transparency_background(cr, &image, x, y, width, height);
cairo_translate(cr, x, y);
cairo_scale(cr, s, s);
- gdk_cairo_set_source_pixbuf(cr, image, 0.0, 0.0);
+ if (image.vector) {
+ viewport = (RsvgRectangle){0.0, 0.0, image.width, image.height};
+ cairo_save(cr);
+ if (image.has_transformer)
+ cairo_transform(cr, &image.transformer);
+ if (rsvg_handle_render_document(image.vector, cr, &viewport, NULL))
+ goto out;
+ cairo_restore(cr);
+ }
+ gdk_cairo_set_source_pixbuf(cr, image.raster, 0.0, 0.0);
+ cairo_pattern_set_filter(cairo_get_source(cr), scaling_filter);
+ cairo_paint(cr);
out:
- cairo_paint(cr);
cairo_destroy(cr);
return 1;
}
@@ -109,39 +207,40 @@ out:
static void
show_image(int update_zoom)
{
- GdkPixbuf *new_image, *original_image, *display_image;
+ struct image original_image, new_image, *display_image;
+ int transformed;
double s;
- original_image = get_original_image();
- if (!original_image) {
+ if (!get_original_image(&original_image)) {
logical_scroll_pane_set_content_size(LOGICAL_SCROLL_PANE(scroll), 1, 1);
gtk_widget_queue_draw(display_area);
return;
}
- new_image = apply_transformations(original_image);
- display_image = new_image ? new_image : original_image;
+ transformed = apply_transformations(&original_image, &new_image);
+ display_image = transformed ? &new_image : &original_image;
if (update_zoom && zoom_mode != ZOOM_CUSTOM && zoom_mode != ZOOM_NORMAL)
zoom = calculate_zoom_mode(display_image, zoom_mode);
s = get_zoom(zoom);
logical_scroll_pane_set_content_size(LOGICAL_SCROLL_PANE(scroll),
- (int)(s * gdk_pixbuf_get_width(display_image)),
- (int)(s * gdk_pixbuf_get_height(display_image)));
+ (int)(s * display_image->width),
+ (int)(s * display_image->height));
- if (transformed_image)
- g_object_unref(transformed_image);
- transformed_image = new_image;
+ forget_transformed_raster();
+ if (transformed)
+ transformed_raster = new_image.raster;
}
static void
set_images(struct file *new_files, size_t nnew_files)
{
- g_clear_object(&transformed_image);
+ forget_transformed_raster();
free_files();
files = new_files;
nfiles = nnew_files;
+ collection_files_changed();
navigate_redraw();
set_actions_sensitive();
}
@@ -156,6 +255,7 @@ add_images(struct file *new_files, size_t nnew_files)
navigate_redraw();
else
update_status();
+ collection_files_changed();
set_actions_sensitive();
}
@@ -205,34 +305,29 @@ set_zoom_around_point(Zoom new_zoom, double x, double y)
GtkAdjustment *hadjustment, *vadjustment;
double old_s, new_s, old_x0, old_y0, new_x0, new_y0;
double iw, ih, vw, vh, hx, hy, adj_x, adj_y, max;
- int w, h;
- GdkPixbuf *image;
+ struct image image;
if (zoom_eq(new_zoom, zoom))
return;
- image = get_image();
- if (!image)
+ if (!get_image(&image))
goto out;
old_s = get_zoom(zoom);
new_s = get_zoom(new_zoom);
zoom = new_zoom;
- w = gdk_pixbuf_get_width(image);
- h = gdk_pixbuf_get_height(image);
-
hadjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
vadjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
vw = hadjustment->page_size;
vh = vadjustment->page_size;
- iw = old_s * (double)w;
- ih = old_s * (double)h;
+ iw = old_s * (double)image.width;
+ ih = old_s * (double)image.height;
old_x0 = vw > iw ? 0.5 * (vw - iw) : 0.0;
old_y0 = vh > ih ? 0.5 * (vh - ih) : 0.0;
- iw = new_s * (double)w;
- ih = new_s * (double)h;
+ iw = new_s * (double)image.width;
+ ih = new_s * (double)image.height;
new_x0 = vw > iw ? 0.5 * (vw - iw) : 0.0;
new_y0 = vh > ih ? 0.5 * (vh - ih) : 0.0;
@@ -330,29 +425,30 @@ reload_redraw(void)
suspend_expose--;
if (!suspend_expose)
gtk_widget_queue_draw(display_area);
+ update_property_pane();
+ collection_files_changed();
update_status();
}
void
transform_redraw(enum transformation augment)
{
- GdkPixbuf *image;
+ struct image image;
GtkAdjustment *hadjustment, *vadjustment;
double s, x, y, width, height, max;
hadjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
vadjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
- image = get_image();
- if (!image)
+ if (!get_image(&image))
return;
s = get_zoom(zoom);
- width = gdk_pixbuf_get_width(image);
- height = gdk_pixbuf_get_height(image);
+ width = (double)image.width;
+ height = (double)image.height;
x = hadjustment->value + 0.5 * hadjustment->page_size;
- x = s * width > hadjustment->page_size ? x / s : 0.5 * width;
y = vadjustment->value + 0.5 * vadjustment->page_size;
+ x = s * width > hadjustment->page_size ? x / s : 0.5 * width;
y = s * height > vadjustment->page_size ? y / s : 0.5 * height;
transform_point(augment, &x, &y, &width, &height);
@@ -386,16 +482,18 @@ navigate_redraw(void)
suspend_expose--;
if (!suspend_expose)
gtk_widget_queue_draw(display_area);
+ update_property_pane();
+ update_collection();
update_status();
}
int
recalculate_zoom_mode(void *user)
{
- GdkPixbuf *image = get_image();
+ struct image image;
(void) user;
- if (image && zoom_mode != ZOOM_CUSTOM && zoom_mode != ZOOM_NORMAL)
- set_zoom_and_recentre(calculate_zoom_mode(image, zoom_mode));
+ if (get_image(&image) && zoom_mode != ZOOM_CUSTOM && zoom_mode != ZOOM_NORMAL)
+ set_zoom_and_recentre(calculate_zoom_mode(&image, zoom_mode));
return 0;
}
@@ -438,8 +536,9 @@ main(int argc, char *argv[])
gtk_main();
- if (transformed_image)
- g_object_unref(transformed_image);
+ if (chequer_pattern)
+ cairo_pattern_destroy(chequer_pattern);
+ forget_transformed_raster();
free_ui();
out:
free_files();
diff --git a/image.c b/image.c
index ebfb778..d6b8c0c 100644
--- a/image.c
+++ b/image.c
@@ -1,35 +1,65 @@
/* See LICENSE file for copyright and license details. */
#include "common.h"
-GdkPixbuf *transformed_image = NULL;
+GdkPixbuf *transformed_raster = NULL;
-static void
-forget_transformed_image(void)
+void
+forget_transformed_raster(void)
{
- if (transformed_image) {
- g_object_unref(transformed_image);
- transformed_image = NULL;
- }
+ g_clear_object(&transformed_raster);
}
-GdkPixbuf *
-get_image(void)
+int
+get_image(struct image *image)
{
struct file *file = get_file();
- if (!file || !file->image) {
- forget_transformed_image();
- return NULL;
+ if (!file || !file->image.raster)
+ forget_transformed_raster();
+ if (!file)
+ return 0;
+ image->raster = transformed_raster ? transformed_raster : file->image.raster;
+ image->vector = (trans & ~7u) ? NULL : file->image.vector; /* TODO implement support for colour manipulation */
+ if (swaps_dimensions(trans)) {
+ image->width = file->image.height;
+ image->height = file->image.width;
+ image->has_x_dpi = file->image.has_y_dpi;
+ image->has_y_dpi = file->image.has_x_dpi;
+ image->x_dpi = file->image.y_dpi;
+ image->y_dpi = file->image.x_dpi;
+ } else {
+ image->width = file->image.width;
+ image->height = file->image.height;
+ image->has_x_dpi = file->image.has_x_dpi;
+ image->has_y_dpi = file->image.has_y_dpi;
+ image->x_dpi = file->image.x_dpi;
+ image->y_dpi = file->image.y_dpi;
}
- return transformed_image ? transformed_image : file->image;
+ image->has_transformer = get_affine_matrix(&image->transformer, trans, file->image.width, file->image.height);
+
+ /* TODO there is a bug in cairo or rsvg that prevents rendering when the
+ * linear part of the transformation has a negative component */
+ if (trans)
+ image->vector = NULL;
+
+ return 1;
}
-GdkPixbuf *
-get_original_image(void)
+int
+get_original_image(struct image *image)
{
struct file *file = get_file();
- if (!file || !file->image) {
- forget_transformed_image();
- return NULL;
- }
- return file->image;
+ if (!file || !file->image.raster)
+ forget_transformed_raster();
+ if (!file)
+ return 0;
+ image->raster = file->image.raster;
+ image->vector = file->image.vector;
+ image->width = file->image.width;
+ image->height = file->image.height;
+ image->has_x_dpi = file->image.has_x_dpi;
+ image->has_y_dpi = file->image.has_y_dpi;
+ image->has_transformer = 0;
+ image->x_dpi = file->image.x_dpi;
+ image->y_dpi = file->image.y_dpi;
+ return 1;
}
diff --git a/opening.c b/opening.c
index 44dc587..1552b18 100644
--- a/opening.c
+++ b/opening.c
@@ -64,14 +64,33 @@ fail:
return -1;
}
+static int
+get_raster_dpi_option(GdkPixbuf *raster, const char *name, double *dpi)
+{
+ const char *text = gdk_pixbuf_get_option(raster, 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;
+}
+
int
load_image(struct file *file, int errors_to_stderr)
{
const char *error_message;
GError *error = NULL;
GtkWidget *dialog;
- GdkPixbuf *image;
+ GdkPixbuf *raster;
GdkPixbufLoader *loader = NULL;
+ GdkPixbufFormat *format;
+ RsvgHandle *vector = NULL;
struct stat st;
unsigned char *buffer = NULL;
size_t size;
@@ -100,14 +119,31 @@ load_image(struct file *file, int errors_to_stderr)
goto fail_gerror;
if (!gdk_pixbuf_loader_close(loader, &error))
goto fail_gerror;
- image = gdk_pixbuf_loader_get_pixbuf(loader);
- if (!image) {
+ raster = gdk_pixbuf_loader_get_pixbuf(loader);
+ if (!raster) {
error_message = "Unsupported image format";
goto fail;
}
- g_object_ref(image);
- g_clear_object(&file->image);
- file->image = image;
+ format = gdk_pixbuf_loader_get_format(loader);
+ if (format) {
+ char *name = gdk_pixbuf_format_get_name(format);
+ if (!strcmp(name, "svg"))
+ vector = rsvg_handle_new_from_file(file->path, &error);
+ g_free(name);
+ if (!vector)
+ g_clear_error(&error);
+ }
+ g_object_ref(raster);
+ g_clear_object(&file->image.raster);
+ g_clear_object(&file->image.vector);
+ file->image.raster = raster;
+ file->image.vector = vector;
+ vector = NULL;
+
+ file->image.width = gdk_pixbuf_get_width(file->image.raster);
+ file->image.height = gdk_pixbuf_get_height(file->image.raster);
+ file->image.has_x_dpi = get_raster_dpi_option(file->image.raster, "x-dpi", &file->image.x_dpi);
+ file->image.has_y_dpi = get_raster_dpi_option(file->image.raster, "y-dpi", &file->image.y_dpi);
if (file->exif_tags) {
g_strfreev(file->exif_tags);
@@ -120,6 +156,7 @@ load_image(struct file *file, int errors_to_stderr)
out:
free(buffer);
+ g_clear_object(&vector);
g_clear_object(&loader);
if (fd >= 0)
close(fd);
diff --git a/preferences.c b/preferences.c
new file mode 100644
index 0000000..e4d73e2
--- /dev/null
+++ b/preferences.c
@@ -0,0 +1,223 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+#define LIST_SCALING_FILTERS(X, ...)\
+ X(0, CAIRO_FILTER_NEAREST, "Nearest") __VA_ARGS__\
+ X(1, CAIRO_FILTER_BILINEAR, "Bilinear") __VA_ARGS__\
+ X(2, CAIRO_FILTER_GAUSSIAN, "Gaussian")
+
+cairo_filter_t scaling_filter = CAIRO_FILTER_BILINEAR;
+GdkColor chequer_colours[2] = {
+ {0u, 0xD3D3u, 0xD3D3u, 0xD3D3u},
+ {0u, 0xFFFFu, 0xFFFFu, 0xFFFFu}
+};
+uint16_t chequer_alphas[2] = {0xFFFFu, 0xFFFFu};
+int chequered_background = 0;
+int chequer_size = 16;
+
+static GtkWidget *preferences_window;
+static GtkWidget *transparency_controls;
+
+static void
+preferences_response(GtkDialog *dialog, int response, void *user)
+{
+ (void) response;
+ (void) user;
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void
+preferences_destroyed(GtkWidget *widget, void *user)
+{
+ (void) widget;
+ (void) user;
+ preferences_window = NULL;
+}
+
+static void
+scaling_filter_changed(GtkComboBox *box, void *user)
+{
+ (void) user;
+ switch (gtk_combo_box_get_active(box)) {
+#define X(I, V, T) case I: scaling_filter = V; break
+ LIST_SCALING_FILTERS(X, ;);
+#undef X
+ default:
+ abort();
+ }
+ gtk_widget_queue_draw(display_area);
+}
+
+static void
+chequered_background_changed(GtkToggleButton *button, void *user)
+{
+ (void) user;
+ chequered_background = gtk_toggle_button_get_active(button);
+ gtk_widget_set_sensitive(transparency_controls, chequered_background);
+ gtk_widget_queue_draw(display_area);
+}
+
+static void
+transparency_colour_changed(GtkColorButton *button, void *user)
+{
+ GdkColor *colour = user;
+ size_t index = (size_t)(colour - chequer_colours);
+ gtk_color_button_get_color(button, colour);
+ chequer_alphas[index] = gtk_color_button_get_alpha(button);
+ gtk_widget_queue_draw(display_area);
+}
+
+static void
+chequer_size_changed(GtkSpinButton *button, void *user)
+{
+ (void) user;
+ chequer_size = gtk_spin_button_get_value_as_int(button);
+ gtk_widget_queue_draw(display_area);
+}
+
+static GtkWidget *
+new_section(const char *title)
+{
+ GtkWidget *frame = gtk_frame_new(title);
+ GtkWidget *box = gtk_vbox_new(0, 6);
+ gtk_container_set_border_width(GTK_CONTAINER(box), 6u);
+ gtk_container_add(GTK_CONTAINER(frame), box);
+ return frame;
+}
+
+static GtkWidget *
+new_scaling_section(void)
+{
+ GtkWidget *section = new_section("Scaling");
+ GtkWidget *box = gtk_bin_get_child(GTK_BIN(section));
+ GtkWidget *row = gtk_hbox_new(0, 6);
+ GtkWidget *label = gtk_label_new_with_mnemonic("_Algorithm:");
+ GtkWidget *filters = gtk_combo_box_new_text();
+
+#define X(I, V, T) gtk_combo_box_append_text(GTK_COMBO_BOX(filters), T)
+ LIST_SCALING_FILTERS(X, ;);
+#undef X
+
+ switch (scaling_filter) {
+#define X(I, V, T) case V: gtk_combo_box_set_active(GTK_COMBO_BOX(filters), I); break
+ LIST_SCALING_FILTERS(X, ;);
+#undef X
+ case CAIRO_FILTER_FAST:
+ case CAIRO_FILTER_GOOD:
+ case CAIRO_FILTER_BEST:
+ default:
+ abort();
+ }
+
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), filters);
+ gtk_box_pack_start(GTK_BOX(row), label, 0, 0, 0u);
+ gtk_box_pack_start(GTK_BOX(row), filters, 0, 0, 0u);
+ gtk_box_pack_start(GTK_BOX(box), row, 0, 0, 0u);
+ g_signal_connect(filters, "changed", G_CALLBACK(&scaling_filter_changed), NULL);
+ return section;
+}
+
+#ifdef TODO
+static GtkWidget *
+new_unimplemented_section(void)
+{
+ GtkWidget *section = new_section("Image handling");
+ GtkWidget *box = gtk_bin_get_child(GTK_BIN(section));
+ GtkWidget *orientation;
+ GtkWidget *physical_size;
+
+ orientation = gtk_check_button_new_with_mnemonic("Apply embedded _orientation");
+ physical_size = gtk_check_button_new_with_mnemonic("Normal size is _physical size");
+ gtk_box_pack_start(GTK_BOX(box), orientation, 0, 0, 0u);
+ gtk_box_pack_start(GTK_BOX(box), physical_size, 0, 0, 0u);
+ return section;
+}
+#endif
+
+static GtkWidget *
+new_transparency_section(void)
+{
+ GtkWidget *section = new_section("Transparent images");
+ GtkWidget *box = gtk_bin_get_child(GTK_BIN(section));
+ GtkWidget *enabled;
+ GtkWidget *first;
+ GtkWidget *second;
+ GtkWidget *size;
+ GtkWidget *label;
+ GtkWidget *units;
+
+ enabled = gtk_check_button_new_with_mnemonic("Use che_quered background");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enabled), chequered_background);
+ gtk_box_pack_start(GTK_BOX(box), enabled, 0, 0, 0u);
+ transparency_controls = gtk_table_new(3u, 3u, 0);
+ gtk_table_set_row_spacings(GTK_TABLE(transparency_controls), 3u);
+ gtk_table_set_col_spacings(GTK_TABLE(transparency_controls), 6u);
+ gtk_box_pack_start(GTK_BOX(box), transparency_controls, 0, 0, 0u);
+
+ label = gtk_label_new_with_mnemonic("_First colour:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ first = gtk_color_button_new_with_color(&chequer_colours[0]);
+ gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(first), 1);
+ gtk_color_button_set_alpha(GTK_COLOR_BUTTON(first), chequer_alphas[0]);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), first);
+ gtk_table_attach(GTK_TABLE(transparency_controls), label, 0u, 1u, 0u, 1u, GTK_FILL, GTK_FILL, 0u, 0u);
+ gtk_table_attach(GTK_TABLE(transparency_controls), first, 1u, 2u, 0u, 1u, GTK_FILL, GTK_FILL, 0u, 0u);
+
+ label = gtk_label_new_with_mnemonic("_Second colour:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ second = gtk_color_button_new_with_color(&chequer_colours[1]);
+ gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(second), 1);
+ gtk_color_button_set_alpha(GTK_COLOR_BUTTON(second), chequer_alphas[1]);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), second);
+ gtk_table_attach(GTK_TABLE(transparency_controls), label, 0u, 1u, 1u, 2u, GTK_FILL, GTK_FILL, 0u, 0u);
+ gtk_table_attach(GTK_TABLE(transparency_controls), second, 1u, 2u, 1u, 2u, GTK_FILL, GTK_FILL, 0u, 0u);
+
+ label = gtk_label_new_with_mnemonic("Chequer si_ze:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ size = gtk_spin_button_new_with_range(1.0, 256.0, 1.0);
+ units = gtk_label_new("pixels");
+ gtk_misc_set_alignment(GTK_MISC(units), 0.0, 0.5);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(size), (double)chequer_size);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), size);
+ gtk_table_attach(GTK_TABLE(transparency_controls), label, 0u, 1u, 2u, 3u, GTK_FILL, GTK_FILL, 0u, 0u);
+ gtk_table_attach(GTK_TABLE(transparency_controls), size, 1u, 2u, 2u, 3u, GTK_FILL, GTK_FILL, 0u, 0u);
+ gtk_table_attach(GTK_TABLE(transparency_controls), units, 2u, 3u, 2u, 3u, GTK_FILL, GTK_FILL, 0u, 0u);
+
+ gtk_widget_set_sensitive(transparency_controls, chequered_background);
+ g_signal_connect(enabled, "toggled", G_CALLBACK(&chequered_background_changed), NULL);
+ g_signal_connect(first, "color-set", G_CALLBACK(&transparency_colour_changed), &chequer_colours[0]);
+ g_signal_connect(second, "color-set", G_CALLBACK(&transparency_colour_changed), &chequer_colours[1]);
+ g_signal_connect(size, "value-changed", G_CALLBACK(&chequer_size_changed), NULL);
+ return section;
+}
+
+void
+open_preferences(void)
+{
+ GtkWidget *alignment, *box;
+
+ if (preferences_window) {
+ gtk_window_present(GTK_WINDOW(preferences_window));
+ return;
+ }
+
+ preferences_window = gtk_dialog_new_with_buttons("Preferences", GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
+
+ alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 6u, 6u, 6u, 6u);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(preferences_window))), alignment, 1, 1, 0u);
+ box = gtk_vbox_new(0, 6);
+ gtk_container_add(GTK_CONTAINER(alignment), box);
+
+ gtk_box_pack_start(GTK_BOX(box), new_scaling_section(), 0, 0, 0u);
+#ifdef TODO
+ gtk_box_pack_start(GTK_BOX(box), new_unimplemented_section(), 0, 0, 0u);
+#endif
+ gtk_box_pack_start(GTK_BOX(box), new_transparency_section(), 0, 0, 0u);
+
+ g_signal_connect(preferences_window, "response", G_CALLBACK(&preferences_response), NULL);
+ g_signal_connect(preferences_window, "destroy", G_CALLBACK(&preferences_destroyed), NULL);
+
+ gtk_widget_show_all(preferences_window);
+}
diff --git a/printing.c b/printing.c
index d75a68e..69c7e0b 100644
--- a/printing.c
+++ b/printing.c
@@ -39,7 +39,7 @@ enum field {
#define NFIELDS (LIST_FIELDS(X_COUNT, +))
struct print_data {
- GdkPixbuf *image;
+ struct image *image;
double width;
double height;
double left;
@@ -118,31 +118,12 @@ get_printable_size(GtkPageSetup *setup, double *width, double *height)
}
static int
-get_dpi_option(GdkPixbuf *image, const char *name, double *dpi)
+get_physical_size(struct image *image, double *width, double *height)
{
- 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))
+ if (!image->has_x_dpi || !image->has_y_dpi)
return 0;
- *width = 72.0 * gdk_pixbuf_get_width(image) / x_dpi;
- *height = 72.0 * gdk_pixbuf_get_height(image) / y_dpi;
+ *width = 72.0 * (double)image->width / image->x_dpi;
+ *height = 72.0 * (double)image->height / image->y_dpi;
return 1;
}
@@ -284,14 +265,13 @@ 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 page_x = preview->allocation.width - 8.0 - w;
+ double page_x;
double page_y = 8.0;
- int width, height;
cairo_t *cr;
- width = gdk_pixbuf_get_width(data->image);
- height = gdk_pixbuf_get_height(data->image);
-
+ w = MAX(round(w), 1.0);
+ h = MAX(round(h), 1.0);
+ page_x = round(preview->allocation.width - 8.0 - w);
cr = gdk_cairo_create(preview->window);
cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height);
@@ -299,25 +279,30 @@ positioning_preview_expose(GtkWidget *preview, GdkEventExpose *event, void *user
gdk_cairo_set_source_color(cr, &preview->style->bg[GTK_STATE_NORMAL]);
cairo_paint(cr);
+ cairo_save(cr);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
cairo_rectangle(cr, page_x, page_y, w, h);
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_fill(cr);
+ cairo_restore(cr);
cairo_save(cr);
cairo_rectangle(cr, page_x, page_y, w, h);
cairo_clip(cr);
cairo_translate(cr, page_x + scale * data->left, page_y + scale * data->top);
- 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);
+ cairo_scale(cr, scale * data->width / (double)data->image->width, scale * data->height / (double)data->image->height);
+ paint_image(cr, data->image);
cairo_restore(cr);
- cairo_rectangle(cr, page_x, page_y, w, h);
+ cairo_save(cr);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+ cairo_rectangle(cr, page_x + 0.5, page_y + 0.5, w - 1.0, h - 1.0);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_set_line_width(cr, 1.0);
cairo_stroke(cr);
+ cairo_restore(cr);
cairo_destroy(cr);
return 0;
}
@@ -742,63 +727,34 @@ static void
draw_page(GtkPrintOperation *operation, GtkPrintContext *context, int page, void *user)
{
struct print_data *data = user;
- GdkPixbuf *image = data->image;
- int width, height;
cairo_t *cr;
-
(void) operation;
(void) page;
-
- 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, data->width / (double)width, data->height / (double)height);
- gdk_cairo_set_source_pixbuf(cr, image, 0.0, 0.0);
- cairo_paint(cr);
+ cairo_scale(cr, data->width / (double)data->image->width, data->height / (double)data->image->height);
+ paint_image(cr, data->image);
}
void
-open_print(GdkPixbuf *image)
+open_print(struct image *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, temporary;
+ double page_width, page_height, s, x, y;
int has_physical_size;
- int width, height;
if (!image)
return;
data.image = image;
- g_object_ref(data.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);
+ has_physical_size = get_physical_size(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.width = (double)image->width;
+ data.height = (double)image->height;
}
data.aspect = data.width / data.height;
@@ -833,5 +789,4 @@ open_print(GdkPixbuf *image)
gtk_print_operation_run(operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(window), NULL);
g_object_unref(operation);
- g_object_unref(data.image);
}
diff --git a/properties.c b/properties.c
index 0fe02cc..edf2536 100644
--- a/properties.c
+++ b/properties.c
@@ -1,23 +1,6 @@
/* 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)
{
@@ -29,12 +12,15 @@ new_metadata_label(const char *text)
}
static void
-add_metadata_row(GtkTable *table, guint row, const char *name, const char *value)
+add_metadata_row(GtkTable *table, guint row, PangoAttrList *name_attributes, const char *name, const char *value)
{
+ GtkWidget *property = gtk_vbox_new(0, 0);
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);
+ gtk_label_set_attributes(GTK_LABEL(name_label), name_attributes);
+ gtk_box_pack_start(GTK_BOX(property), name_label, 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(property), value_label, 0, 0, 0);
+ gtk_table_attach(table, property, 0, 1, row, row + 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
}
static int
@@ -119,10 +105,10 @@ format_file_size(off_t size)
}
static char *
-format_pixel_size(GdkPixbuf *image)
+format_pixel_size(GdkPixbuf *raster)
{
- int w = gdk_pixbuf_get_width(image);
- int h = gdk_pixbuf_get_height(image);
+ int w = gdk_pixbuf_get_width(raster);
+ int h = gdk_pixbuf_get_height(raster);
char *text = emalloc(2u * 3u * sizeof(w) + sizeof(" × pixels"));
if (sprintf(text, "%i × %i pixels", w, h) < 0)
eprintf("sprintf:");
@@ -171,33 +157,26 @@ format_physical_size(int pixels, double resolution)
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 *
+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;
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ PangoAttrList *title_attributes;
+ unsigned i, row = 0u, rows = 2u;
+ const char *orientation = NULL;
+ char *text, *tag, orientation_buf[3u * sizeof(long int) + 1u];
+ int spacing;
GList *keys, *key;
GHashTable *options;
GError *error;
long int li;
- options = gdk_pixbuf_get_options(file->image);
+ title_attributes = pango_attr_list_new();
+ pango_attr_list_insert(title_attributes, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+
+ options = gdk_pixbuf_get_options(file->image.raster);
keys = g_list_sort(g_hash_table_get_keys(options), (GCompareFunc)&strcmp);
for (key = keys; key; key = key->next)
@@ -205,69 +184,69 @@ create_property_table(struct file *file)
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;
+ if (file->image.raster && !file->image.vector) {
+ rows += 1u;
+
+ orientation = gdk_pixbuf_get_option(file->image.raster, "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;
}
- 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);
+ table = gtk_table_new(rows, 1, 0);
+ context = gtk_widget_get_pango_context(table);
+ metrics = pango_context_get_metrics(context, NULL, pango_context_get_language(context));
+ spacing = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent(metrics));
+ spacing /= 2;
+ pango_font_metrics_unref(metrics);
+ gtk_table_set_row_spacings(GTK_TABLE(table), (guint)spacing);
- 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);
- }
+#define ROW(NAME, VALUE)\
+ (text = NULL, add_metadata_row(GTK_TABLE(table), row++, title_attributes, NAME, VALUE), free(text))
+
+ ROW("File", file->path);
+ ROW("File size", text = format_file_size(file->size));
+ if (file->image.raster && !file->image.vector)
+ ROW("Pixel dimensions", text = format_pixel_size(file->image.raster));
+ if (orientation)
+ ROW("Orientation", text = format_orientation(orientation));
+ if (file->image.has_x_dpi)
+ ROW("Horizontal resolution", text = format_resolution(file->image.x_dpi));
+ if (file->image.has_y_dpi)
+ ROW("Vertical resolution", text = format_resolution(file->image.y_dpi));
+ if (file->image.has_x_dpi)
+ ROW("Physical width", text = format_physical_size(file->image.width, file->image.x_dpi));
+ if (file->image.has_y_dpi)
+ ROW("Physical height", text = format_physical_size(file->image.height, file->image.y_dpi));
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));
+ 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);
+ tag = 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);
+ if (tag)
+ ROW(file->exif_tags[i], tag);
+ g_free(tag);
}
}
+#undef ROW
+
+ pango_attr_list_unref(title_attributes);
return table;
}
@@ -277,7 +256,7 @@ open_properties(struct file *file)
GtkWidget *dialog, *scrolled;
char *title;
- if (!file || !file->path || !file->image)
+ if (!file || !file->path)
return;
dialog = gtk_dialog_new_with_buttons(title = format_title(file),
diff --git a/render.c b/render.c
new file mode 100644
index 0000000..fdc2f0c
--- /dev/null
+++ b/render.c
@@ -0,0 +1,118 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+#include <string.h>
+
+static uint8_t
+unpremultiply(double colour, double alpha)
+{
+ if (colour <= 0.04045)
+ colour = colour / 12.92;
+ else
+ colour = pow((colour + 0.055) / 1.055, 2.4);
+
+ colour /= alpha;
+
+ if (colour <= 0.0031308)
+ colour = 12.92 * colour;
+ else
+ colour = fma(1.055, pow(colour, 1.0 / 2.4), -0.055);
+
+ colour = fma(255.0, colour, 0.5);
+ if (colour <= 0.0)
+ return 0u;
+ if (colour >= 255.0)
+ return 255u;
+ return (uint8_t)colour;
+}
+
+GdkPixbuf *
+rasterise(RsvgHandle *vector, int width, int height)
+{
+ RsvgRectangle viewport = {0.0, 0.0, width, height};
+ cairo_surface_t *surface = NULL;
+ cairo_t *cr = NULL;
+ GdkPixbuf *image;
+ int x, y, sstride, dstride;
+ const uint8_t *s, *sy;
+ uint8_t *d, *dy;
+ double a, r, g, b;
+ uint32_t pixel;
+
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS)
+ goto fail;
+
+ cr = cairo_create(surface);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ goto fail;
+
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
+ cairo_paint(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+ if (!rsvg_handle_render_document(vector, cr, &viewport, NULL))
+ goto fail;
+ cairo_destroy(cr);
+ cr = NULL;
+
+ image = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 1, 8, width, height);
+ if (!image)
+ goto fail;
+
+ /* TODO accelerate */
+ cairo_surface_flush(surface);
+ sy = s = cairo_image_surface_get_data(surface);
+ dy = d = gdk_pixbuf_get_pixels(image);
+ sstride = cairo_image_surface_get_stride(surface);
+ dstride = gdk_pixbuf_get_rowstride(image);
+ for (y = 0; y < height; y++, sy = s = &s[sstride], dy = d = &d[dstride]) {
+ for (x = 0; x < width; x++, sy = &sy[4u], dy = &dy[4u]) {
+ memcpy(&pixel, sy, 4u);
+ if ((uint8_t)(pixel >> 24)) {
+ a = (double)(uint8_t)(pixel >> 24) / 255.0;
+ r = (double)(uint8_t)(pixel >> 16) / 255.0;
+ g = (double)(uint8_t)(pixel >> 8) / 255.0;
+ b = (double)(uint8_t)(pixel >> 0) / 255.0;
+ dy[0] = unpremultiply(r, a);
+ dy[1] = unpremultiply(g, a);
+ dy[2] = unpremultiply(b, a);
+ dy[3] = (uint8_t)(pixel >> 24);
+ } else {
+ dy[0] = dy[1] = dy[2] = dy[3] = 0u;
+ }
+ }
+ }
+
+ cairo_surface_destroy(surface);
+ return image;
+
+fail:
+ if (cr)
+ cairo_destroy(cr);
+ if (surface)
+ cairo_surface_destroy(surface);
+ return NULL;
+}
+
+void
+paint_image(cairo_t *cr, struct image *image)
+{
+ RsvgRectangle viewport = {0.0, 0.0, image->width, image->height};
+ int rendered;
+
+ if (image->vector) {
+ cairo_save(cr);
+ if (image->has_transformer)
+ cairo_transform(cr, &image->transformer);
+ rendered = rsvg_handle_render_document(image->vector, cr, &viewport, NULL);
+ cairo_restore(cr);
+ if (rendered)
+ return;
+ }
+
+ if (image->raster) {
+ gdk_cairo_set_source_pixbuf(cr, image->raster, 0.0, 0.0);
+ cairo_paint(cr);
+ }
+}
diff --git a/transform.c b/transform.c
index b9461d7..4bc4e6e 100644
--- a/transform.c
+++ b/transform.c
@@ -4,21 +4,21 @@
enum transformation trans = TRANS_NONE;
static GdkPixbuf *
-make_negative(GdkPixbuf *image)
+make_negative_raster(GdkPixbuf *raster)
{
- GdkPixbuf *new_image;
+ GdkPixbuf *new_raster;
uint8_t *pixels;
int width, height, rowstride, channels, x, y;
- new_image = gdk_pixbuf_copy(image);
- if (!new_image)
+ new_raster = gdk_pixbuf_copy(raster);
+ if (!new_raster)
exit(1);
- pixels = gdk_pixbuf_get_pixels(new_image);
- width = gdk_pixbuf_get_width(new_image);
- height = gdk_pixbuf_get_height(new_image);
- rowstride = gdk_pixbuf_get_rowstride(new_image);
- channels = gdk_pixbuf_get_n_channels(new_image);
+ pixels = gdk_pixbuf_get_pixels(new_raster);
+ width = gdk_pixbuf_get_width(new_raster);
+ height = gdk_pixbuf_get_height(new_raster);
+ rowstride = gdk_pixbuf_get_rowstride(new_raster);
+ channels = gdk_pixbuf_get_n_channels(new_raster);
for (y = 0; y < height; y++) {
for (x = 0; x < width * channels; x += channels) {
@@ -48,62 +48,91 @@ make_negative(GdkPixbuf *image)
pixels = &pixels[rowstride];
}
- return new_image;
+ return new_raster;
}
static GdkPixbuf *
-apply_rotation_and_reflection(GdkPixbuf *image)
+apply_rotation_and_reflection(GdkPixbuf *raster)
{
- GdkPixbuf *new_image, *newer_image;
+ GdkPixbuf *new_raster, *newer_raster;
switch (trans & 7u) {
case TRANS_NONE:
return NULL;
case TRANS_CLOCKWISE:
- new_image = gdk_pixbuf_rotate_simple(image, GDK_PIXBUF_ROTATE_CLOCKWISE);
+ new_raster = gdk_pixbuf_rotate_simple(raster, GDK_PIXBUF_ROTATE_CLOCKWISE);
break;
case TRANS_180_DEGREES:
- new_image = gdk_pixbuf_rotate_simple(image, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
+ new_raster = gdk_pixbuf_rotate_simple(raster, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
break;
case TRANS_ANTICLOCKWISE:
- new_image = gdk_pixbuf_rotate_simple(image, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
+ new_raster = gdk_pixbuf_rotate_simple(raster, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
break;
case TRANS_FLIP:
case TRANS_TRANSPOSE:
- new_image = gdk_pixbuf_flip(image, 0);
+ new_raster = gdk_pixbuf_flip(raster, 0);
break;
case TRANS_FLOP:
case TRANS_ANTITRANSPOSE:
- new_image = gdk_pixbuf_flip(image, 1);
+ new_raster = gdk_pixbuf_flip(raster, 1);
break;
}
- if (!new_image)
+ if (!new_raster)
exit(1);
switch (trans & 7u) {
case TRANS_TRANSPOSE:
case TRANS_ANTITRANSPOSE:
- newer_image = gdk_pixbuf_rotate_simple(new_image, GDK_PIXBUF_ROTATE_CLOCKWISE);
- if (!newer_image)
+ newer_raster = gdk_pixbuf_rotate_simple(new_raster, GDK_PIXBUF_ROTATE_CLOCKWISE);
+ if (!newer_raster)
exit(1);
- g_object_unref(new_image);
- return newer_image;
+ g_object_unref(new_raster);
+ return newer_raster;
default:
- return new_image;
+ return new_raster;
}
}
-GdkPixbuf *
-apply_transformations(GdkPixbuf *image)
+int
+apply_transformations(struct image *image, struct image *image_out)
{
- GdkPixbuf *negative_image, *new_image;
- negative_image = (trans & TRANS_NEGATIVE) ? make_negative(image) : NULL;
- new_image = apply_rotation_and_reflection(negative_image ? negative_image : image);
- if (!new_image)
- new_image = negative_image ? negative_image : image;
- else if (negative_image)
- g_object_unref(negative_image);
- return new_image == image ? NULL : new_image;
+ GdkPixbuf *negative_raster, *new_raster;
+
+ if (trans == TRANS_NONE)
+ return 0;
+
+ if (image->raster) {
+ negative_raster = (trans & TRANS_NEGATIVE) ? make_negative_raster(image->raster) : NULL;
+ new_raster = apply_rotation_and_reflection(negative_raster ? negative_raster : image->raster);
+ if (!new_raster)
+ new_raster = negative_raster;
+ else if (negative_raster)
+ g_object_unref(negative_raster);
+ image_out->raster = new_raster;
+ if (!image_out->raster)
+ return 0;
+ }
+
+ image_out->vector = image->vector;
+
+ if (swaps_dimensions(trans)) {
+ image_out->width = image->height;
+ image_out->height = image->width;
+ image_out->has_x_dpi = image->has_y_dpi;
+ image_out->has_y_dpi = image->has_x_dpi;
+ image_out->x_dpi = image->y_dpi;
+ image_out->y_dpi = image->x_dpi;
+ } else {
+ image_out->width = image->width;
+ image_out->height = image->height;
+ image_out->has_x_dpi = image->has_x_dpi;
+ image_out->has_y_dpi = image->has_y_dpi;
+ image_out->x_dpi = image->x_dpi;
+ image_out->y_dpi = image->y_dpi;
+ }
+ image_out->has_transformer = get_affine_matrix(&image_out->transformer, trans, image->width, image->height);
+
+ return 1;
}
static enum transformation
@@ -131,14 +160,31 @@ add_transformation_without_redraw(enum transformation augment)
return trans != old;
}
+int
+swaps_dimensions(enum transformation t)
+{
+ switch (t & 7u) {
+ case TRANS_NONE:
+ case TRANS_180_DEGREES:
+ case TRANS_FLIP:
+ case TRANS_FLOP:
+ return 0;
+ case TRANS_CLOCKWISE:
+ case TRANS_ANTICLOCKWISE:
+ case TRANS_TRANSPOSE:
+ case TRANS_ANTITRANSPOSE:
+ return 1;
+ }
+ abort();
+}
+
void
transform_point(enum transformation t, double *x, double *y, double *w, double *h)
{
double x1 = *x, y1 = *y, x2 = *w, y2 = *h;
-
switch (t & 7u) {
case TRANS_NONE:
- return;
+ break;
case TRANS_CLOCKWISE:
*x = y2 - y1;
*y = x1;
@@ -146,7 +192,7 @@ transform_point(enum transformation t, double *x, double *y, double *w, double *
case TRANS_180_DEGREES:
*x = x2 - x1;
*y = y2 - y1;
- return;
+ break;
case TRANS_ANTICLOCKWISE:
*x = y1;
*y = x2 - x1;
@@ -157,16 +203,35 @@ transform_point(enum transformation t, double *x, double *y, double *w, double *
break;
case TRANS_FLIP:
*y = y2 - y1;
- return;
+ break;
case TRANS_ANTITRANSPOSE:
*x = y2 - y1;
*y = x2 - x1;
break;
case TRANS_FLOP:
*x = x2 - x1;
- return;
+ break;
}
+ if (swaps_dimensions(t)) {
+ *w = y2;
+ *h = x2;
+ }
+}
- *w = y2;
- *h = x2;
+int
+get_affine_matrix(cairo_matrix_t *matrix_out, enum transformation t, int width, int height)
+{
+ double w = (double)width;
+ double h = (double)height;
+ switch (t & 7u) {
+ case TRANS_NONE: cairo_matrix_init(matrix_out, +1.0, 0.0, 0.0, +1.0, 0.0, 0.0); return 0;
+ case TRANS_CLOCKWISE: cairo_matrix_init(matrix_out, 0.0, +1.0, -1.0, 0.0, h, 0.0); break;
+ case TRANS_180_DEGREES: cairo_matrix_init(matrix_out, -1.0, 0.0, 0.0, -1.0, w, h); break;
+ case TRANS_ANTICLOCKWISE: cairo_matrix_init(matrix_out, 0.0, -1.0, +1.0, 0.0, 0.0, w); break;
+ case TRANS_TRANSPOSE: cairo_matrix_init(matrix_out, 0.0, +1.0, +1.0, 0.0, 0.0, 0.0); break;
+ case TRANS_FLOP: cairo_matrix_init(matrix_out, -1.0, 0.0, 0.0, +1.0, w, 0.0); break;
+ case TRANS_ANTITRANSPOSE: cairo_matrix_init(matrix_out, 0.0, -1.0, -1.0, 0.0, h, w); break;
+ case TRANS_FLIP: cairo_matrix_init(matrix_out, +1.0, 0.0, 0.0, -1.0, 0.0, h); break;
+ }
+ return 1;
}
diff --git a/ui.c b/ui.c
index 8184d68..09edf1e 100644
--- a/ui.c
+++ b/ui.c
@@ -9,10 +9,13 @@
ACTION(0, 1u, "", "Print", GTK_STOCK_PRINT, "<control>P", "Ctrl+P", print_action, D)\
SEPARATOR(0, D)\
ACTION(0, 1u, "Proper", "ties", GTK_STOCK_PROPERTIES, "<alt>Return", "Alt+Return", properties_action, D)\
+ ACTION(0, 0u, "Pr", "eferences", GTK_STOCK_PREFERENCES, NULL, "", preferences_action, D)\
SEPARATOR(0, D)\
ACTION(0, 0u, "", "Close", GTK_STOCK_CLOSE, "<control>W", "Ctrl+W", close_action, D)\
TOPLEVEL(0, "", "View", D)\
TOGGLE(1, 1u, "F", "ullscreen", GTK_STOCK_FULLSCREEN, "F11", "F11", fullscreen_action, GROUP_NONE, 0, D)\
+ TOGGLE(0, 1u, "", "Property pane", GTK_STOCK_PROPERTIES, "F9", "F9", property_pane_action, GROUP_NONE, 0, D)\
+ TOGGLE(0, 0u, "Image ", "collection", GTK_STOCK_INDEX, "<control>F9", "Ctrl+F9", collection_pane_action, GROUP_NONE, 0, D)\
SEPARATOR(1, D)\
ACTION(0, 1u, "Re", "set transformations", "document-revert", "<alt>R", "Alt+R", reset_transformations_action, D)\
ACTION(1, 1u, "Rotate anti", "clockwise", "image-rotate-left", "<shift>R", "Shift+R", rotate_anticlockwise_action, D)\
@@ -27,7 +30,7 @@
SEPARATOR(1, D)\
ACTION(1, 1u, "", "Zoom in", "zoom-in", "plus", "+", zoom_in_action, D)\
ACTION(1, 1u, "Zoom ", "out", "zoom-out", "minus", "-", zoom_out_action, D)\
- TOGGLE(1, 1u, "", "Normal size", "zoom-original", "0", "0", normal_size_action, GROUP_ZOOM, 0, D)\
+ TOGGLE(1, 1u, "", "Normal size", "zoom-original", "1", "1", normal_size_action, GROUP_ZOOM, 0, D)\
TOGGLE(1, 1u, "Fit to ", "width", "zoom-fit-width", "<control>H", "Ctrl+H", width_fit_action, GROUP_ZOOM, 0, D)\
TOGGLE(1, 1u, "Fit to ", "height", "zoom-fit-height", "<control>V", "Ctrl+V", height_fit_action, GROUP_ZOOM, 0, D)\
TOGGLE(1, 1u, "", "Fit to window", "zoom-fit-page", "<control><shift>F", "Ctrl+Shift+F", window_fit_action, GROUP_ZOOM, 0, D)\
@@ -47,7 +50,9 @@ GtkToggleAction *fullscreen;
static GtkWidget *statusbar;
static GtkWidget *menubar;
static GtkWidget *toolbar;
+static GtkWidget *property_pane;
static GtkActionGroup *actions;
+static GdkColor fullscreen_background = {0u, 0u, 0u, 0u};
#define X_SKIP(...)
#define X_TOPLEVEL(T, A, B, ...) {A B, NULL, A "_" B, NULL, NULL, NULL} __VA_ARGS__
@@ -158,11 +163,20 @@ window_state_changed(GtkWidget *widget, GdkEventWindowState *event, void *user)
return 0;
}
+static int
+display_area_button_pressed(GtkWidget *widget, GdkEventButton *event, void *user)
+{
+ (void) event;
+ (void) user;
+ gtk_widget_grab_focus(widget);
+ return 0;
+}
+
void
set_up_ui(void)
{
GtkAccelGroup *accelerators;
- GtkWidget *box;
+ GtkWidget *box, *paned, *content;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "gbild");
@@ -188,20 +202,31 @@ set_up_ui(void)
gtk_box_pack_start(GTK_BOX(box), menubar, 0, 0, 0u);
gtk_box_pack_start(GTK_BOX(box), toolbar, 0, 0, 0u);
display_area = gtk_drawing_area_new();
+ gtk_widget_set_can_focus(display_area, 1);
gtk_widget_set_app_paintable(display_area, 1);
gtk_widget_set_double_buffered(display_area, 0);
scroll = logical_scroll_pane_new();
logical_scroll_pane_set_child(LOGICAL_SCROLL_PANE(scroll), display_area);
gtk_widget_add_events(scroll, GDK_SCROLL_MASK);
- g_signal_connect(scroll, "scroll-event", G_CALLBACK(&scroll_event), NULL);
- gtk_box_pack_start(GTK_BOX(box), scroll, 1, 1, 0u);
+ paned = gtk_hpaned_new();
+ property_pane = gtk_vbox_new(0, 0);
+ gtk_widget_set_no_show_all(property_pane, 1);
+ gtk_paned_pack1(GTK_PANED(paned), property_pane, 0, 1);
+ gtk_paned_pack2(GTK_PANED(paned), scroll, 1, 0);
+ gtk_paned_set_position(GTK_PANED(paned), INITIAL_PROPERTY_PANE_HEIGHT);
+ content = gtk_vbox_new(0, 0);
+ gtk_box_pack_start(GTK_BOX(content), paned, 1, 1, 0u);
+ gtk_box_pack_end(GTK_BOX(content), create_collection_pane(), 0, 0, 0u);
+ gtk_box_pack_start(GTK_BOX(box), content, 1, 1, 0u);
statusbar = gtk_statusbar_new();
gtk_box_pack_end(GTK_BOX(box), statusbar, 0, 0, 0u);
gtk_widget_add_events(display_area, GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK);
+ g_signal_connect(display_area, "button-press-event", G_CALLBACK(&display_area_button_pressed), NULL);
g_signal_connect(display_area, "expose-event", G_CALLBACK(&expose_image), NULL);
+ g_signal_connect(scroll, "scroll-event", G_CALLBACK(&scroll_event), NULL);
}
void
@@ -262,6 +287,8 @@ clear_toggles(enum toggle_group group, GtkToggleAction *except)
void
set_fullscreen_mode(int active)
{
+ gtk_widget_modify_bg(display_area, GTK_STATE_NORMAL, active ? &fullscreen_background : NULL);
+ gtk_widget_queue_draw(display_area);
gtk_widget_set_visible(menubar, !active);
gtk_widget_set_visible(toolbar, !active);
gtk_widget_set_visible(statusbar, !active);
@@ -274,6 +301,42 @@ set_fullscreen_mode(int active)
gtk_window_unfullscreen(GTK_WINDOW(window));
}
+void
+set_property_pane_visible(int visible)
+{
+ struct file *file;
+ GtkWidget *properties;
+ GList *children, *child;
+
+ if (!visible) {
+ gtk_widget_hide(property_pane);
+ g_idle_add(&recalculate_zoom_mode, NULL);
+ return;
+ }
+
+ children = gtk_container_get_children(GTK_CONTAINER(property_pane));
+ for (child = children; child; child = child->next)
+ gtk_widget_destroy(child->data);
+ g_list_free(children);
+ file = get_file();
+ if (file && file->path) {
+ properties = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(properties), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start(GTK_BOX(property_pane), properties, 1, 1, 0);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(properties), create_property_table(file));
+ gtk_widget_show_all(properties);
+ }
+ gtk_widget_show(property_pane);
+ g_idle_add(&recalculate_zoom_mode, NULL);
+}
+
+void
+update_property_pane(void)
+{
+ if (gtk_widget_get_visible(property_pane))
+ set_property_pane_visible(1);
+}
+
int
activate_fullscreen(void *user)
{
diff --git a/zoom.c b/zoom.c
index 7fa617a..aeae820 100644
--- a/zoom.c
+++ b/zoom.c
@@ -35,22 +35,21 @@ zoom_out(Zoom z)
}
Zoom
-calculate_fit(GdkPixbuf *image, int xfit, int yfit, double cap)
+calculate_fit(struct image *image, int xfit, int yfit, double cap)
{
GtkAdjustment *adjustment;
double s = 1.0, y;
- int w, h;
if (xfit) {
adjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
- w = gdk_pixbuf_get_width(image);
- s = adjustment->page_size / (double)w;
+ s = (double)image->width;
+ s = adjustment->page_size / s;
}
if (yfit) {
adjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
- h = gdk_pixbuf_get_height(image);
- y = adjustment->page_size / (double)h;
+ y = (double)image->height;
+ y = adjustment->page_size / y;
s = xfit ? MIN(s, y) : y;
}
@@ -61,21 +60,15 @@ calculate_fit(GdkPixbuf *image, int xfit, int yfit, double cap)
}
Zoom
-calculate_zoom_mode(GdkPixbuf *image, enum zoom_mode mode)
+calculate_zoom_mode(struct image *image, enum zoom_mode mode)
{
switch (mode) {
- case ZOOM_CUSTOM:
- break;
- case ZOOM_NORMAL:
- return calculate_fit(image, 0, 0, 1.0);
- case ZOOM_FIT_WIDTH:
- return calculate_fit(image, 1, 0, HUGE_VAL);
- case ZOOM_FIT_HEIGHT:
- return calculate_fit(image, 0, 1, HUGE_VAL);
- case ZOOM_FIT_WINDOW:
- return calculate_fit(image, 1, 1, HUGE_VAL);
- case ZOOM_BEST_FIT:
- return calculate_fit(image, 1, 1, 1.0);
+ case ZOOM_CUSTOM: break;
+ case ZOOM_NORMAL: return calculate_fit(image, 0, 0, 1.0);
+ case ZOOM_FIT_WIDTH: return calculate_fit(image, 1, 0, HUGE_VAL);
+ case ZOOM_FIT_HEIGHT: return calculate_fit(image, 0, 1, HUGE_VAL);
+ case ZOOM_FIT_WINDOW: return calculate_fit(image, 1, 1, HUGE_VAL);
+ case ZOOM_BEST_FIT: return calculate_fit(image, 1, 1, 1.0);
}
return zoom;
}