aboutsummaryrefslogtreecommitdiffstats
path: root/gbild.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-10-11 23:01:38 +0200
committerMattias Andrée <m@maandree.se>2026-10-11 23:29:28 +0200
commit94659550993f454e12e4bcb8cf93a5ff258bc08c (patch)
treef8e1cfe9fbcdec6f09b6cc18c761684dc34505d2 /gbild.c
parentFirst commit (diff)
downloadgbild-94659550993f454e12e4bcb8cf93a5ff258bc08c.tar.gz
gbild-94659550993f454e12e4bcb8cf93a5ff258bc08c.tar.bz2
gbild-94659550993f454e12e4bcb8cf93a5ff258bc08c.tar.xz
Second commit
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--gbild.c1600
1 files changed, 250 insertions, 1350 deletions
diff --git a/gbild.c b/gbild.c
index 7ab22ee..ffe51b0 100644
--- a/gbild.c
+++ b/gbild.c
@@ -1,171 +1,9 @@
/* See LICENSE file for copyright and license details. */
-#if defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-# pragma GCC diagnostic ignored "-Wstrict-prototypes"
-#endif
-#include <gdk/gdkkeysyms.h>
-#include <gtk/gtk.h>
-#include <libsimple.h>
-#include <libsimple-arg.h>
-#if defined(__GNUC__)
-# pragma GCC diagnostic pop
-#endif
-
-#if defined(__GNUC__)
-# pragma GCC diagnostic ignored "-Wunsuffixed-float-constants"
-#endif
+#include "common.h"
USAGE("[-f] [file] ...");
-#define SCROLL_AMOUNT 30
-
-#define FLOAT_EQ(A, B) ((A) >= (B) && (A) <= (B))
-
-#define COMMA ,
-
-#define LIST_MENU(TOPLEVEL, SEPARATOR, ACTION, TOGGLE, D)\
- TOPLEVEL(0, "", "File", D)\
- ACTION(0, 0u, "", "Open", GTK_STOCK_OPEN, "<control>0", "Ctrl+0", open_action, D)\
- ACTION(0, 1u, "", "Reload", GTK_STOCK_REFRESH, "F5", "F5", reload_action, D)\
- SEPARATOR(0, D)\
- 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)\
- 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)\
- 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)\
- ACTION(0, 1u, "R", "otate 180°", "image-rotate", "<control>space", "Ctrl+Space", rotate_180_degrees_action, D)\
- ACTION(1, 1u, "", "Rotate clockwise", "image-rotate-right", "R", "R", rotate_clockwise_action, D)\
- ACTION(1, 1u, "", "Mirror horizontally", "object-flip-horizontal", "H", "H", flop_action, D)\
- ACTION(1, 1u, "Mirror ", "vertically", "object-flip-vertical", "V", "V", flip_action, D)\
- ACTION(1, 1u, "", "Transpose", "object-transpose", "T", "T", transpose_action, D)\
- ACTION(1, 1u, "", "Antitranspose", "object-transpose-mirrored", "<shift>T", "Shift+T", antitranspose_action, D)\
- SEPARATOR(1, D)\
- TOGGLE(1, 1u, "", "Invert colours", "filters-invert", "<control>I", "Ctrl+I", invert_action, GROUP_NONE, 0, D)\
- SEPARATOR(1, D)\
- /* TODO these are not properly honoured */\
- 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, "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)\
- TOGGLE(1, 1u, "", "Best fit", "zoom-fit-best", "<control>F", "Ctrl+F", best_fit_action, GROUP_ZOOM, 1, D)\
- TOPLEVEL(1, "", "Go", D)\
- ACTION(1, 2u, "", "Previous image", GTK_STOCK_GO_BACK, "<control>Left", "Ctrl+Left", previous_action, D)\
- ACTION(1, 2u, "", "Next image", GTK_STOCK_GO_FORWARD, "<control>Right", "Ctrl+Right", next_action, D)\
- SEPARATOR(0, D)\
- ACTION(1, 2u, "", "First image", GTK_STOCK_GOTO_FIRST, "<control>Home", "Ctrl+Home", first_action, D)\
- ACTION(1, 2u, "", "Last image", GTK_STOCK_GOTO_LAST, "<control>End", "Ctrl+End", last_action,)
-
-enum toggle_group {
- GROUP_NONE,
- GROUP_ZOOM
-};
-
-enum transformation {
- TRANS_NONE, /* 1 */
- TRANS_CLOCKWISE, /* r */
- TRANS_180_DEGREES, /* r² */
- TRANS_ANTICLOCKWISE, /* r³ */
- TRANS_TRANSPOSE, /* t */
- TRANS_FLOP, /* rt */
- TRANS_ANTITRANSPOSE, /* r²t */
- TRANS_FLIP, /* r³t */
- /*
- * t² = r⁴ = 1
- * trⁿ = r⁻ⁿt
- */
- TRANS_NEGATIVE = 0x08
-};
-
-struct file {
- char *path;
- off_t size;
- GdkPixbuf *image;
-};
-
-struct filter {
- char *name;
- size_t suffixes_start;
- size_t suffixes_end;
-};
-
-static struct file *files = NULL;
-static size_t nfiles = 0u;
-static size_t current_file = 0u;
-static enum transformation trans = TRANS_NONE;
-static GdkPixbuf *transformed_image = NULL;
-
-static double zoom = 1.0;
-static int clearing_toggles = 0;
-static int dragging = 0;
-static int drag_x, drag_y;
-
-static GtkWidget *window;
-static GtkWidget *menubar;
-static GtkWidget *statusbar;
-static GtkWidget *toolbar;
-static GtkWidget *display_area;
-static GtkWidget *scroll;
-static GtkToggleAction *fullscreen;
-static GtkActionGroup *actions;
-
-static const char *home_directory;
-static char *suffixes = NULL;
-static struct filter *filters = NULL;
-static size_t nfilters = 0u;
-static struct filter anyfilter = {NULL, 0u, 0u};
-static char image_target_name[] = "text/uri-list";
-static GtkTargetEntry image_target = {image_target_name, 0u, 0u};
-
-static double
-zoom_in(double z)
-{
- return z * 1.25;
-}
-
-static double
-zoom_out(double z)
-{
- return z * 0.8;
-}
-
-static GtkAction *
-get_action(const char *name)
-{
- return gtk_action_group_get_action(actions, name);
-}
-
-static void
-set_actions_sensitive(void)
-{
-#if defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wtype-limits"
-#endif
-
-#define X_SKIP(...)
-#define X_ACTION(T, N, A, B, ICON, ACCEL, ATEXT, F, ...)\
- if (N) gtk_action_set_sensitive(get_action(A B), nfiles >= N) __VA_ARGS__
-#define X_TOGGLE(T, N, A, B, ICON, ACCEL, ATEXT, F, GROUP, ACTIVE, ...)\
- if (N) gtk_action_set_sensitive(get_action(A B), nfiles >= N) __VA_ARGS__
-
- LIST_MENU(X_SKIP, X_SKIP, X_ACTION, X_TOGGLE, ;);
-
-#undef X_SKIP
-#undef X_ACTION
-#undef X_TOGGLE
-
-#if defined(__GNUC__)
-# pragma GCC diagnostic pop
-#endif
-}
+static int suspend_expose = 0;
static size_t
format_statusbar_message(char *buffer, size_t size, struct file *file)
@@ -194,11 +32,11 @@ format_statusbar_message(char *buffer, size_t size, struct file *file)
done:
r = snprintf(buffer, size,
- "Image %zu of %zu | %ju%s %c | %i × %i pixels", /* TODO LATER add zoom */
+ "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));
+ gdk_pixbuf_get_width(file->image), gdk_pixbuf_get_height(file->image),
+ 100.0 * get_zoom(zoom));
if (r < 0)
eprintf("snprintf:");
@@ -208,1245 +46,372 @@ done:
static void
update_status(void)
{
- char *message;
+ struct file *file = get_file();
+ char *message = NULL;
size_t len;
-
- gtk_statusbar_pop(GTK_STATUSBAR(statusbar), 0u);
-
- if (current_file >= nfiles || !files[current_file].image)
- return;
-
- len = format_statusbar_message(NULL, 0u, &files[current_file]);
- message = malloc(++len);
- format_statusbar_message(message, len, &files[current_file]);
-
- gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0u, message);
-
- free(message);
-}
-
-static void
-show_image(void)
-{
- GdkPixbuf *new_image, *current_image;
- int channels, height, rowstride, width, x, y;
- uint8_t *pixels;
-
- if (current_file >= nfiles || !files[current_file].image) {
- if (transformed_image)
- g_object_unref(transformed_image);
- gtk_image_clear(GTK_IMAGE(display_area));
- return;
+ if (file) {
+ len = format_statusbar_message(NULL, 0u, file);
+ message = malloc(++len);
+ format_statusbar_message(message, len, file);
}
-
- current_image = files[current_file].image;
-
- switch (trans & 7u) {
- case TRANS_NONE:
- new_image = NULL;
- goto transformed;
- case TRANS_CLOCKWISE:
- new_image = gdk_pixbuf_rotate_simple(current_image, GDK_PIXBUF_ROTATE_CLOCKWISE);
- break;
- case TRANS_180_DEGREES:
- new_image = gdk_pixbuf_rotate_simple(current_image, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
- break;
- case TRANS_ANTICLOCKWISE:
- new_image = gdk_pixbuf_rotate_simple(current_image, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
- break;
- case TRANS_FLIP:
- case TRANS_TRANSPOSE:
- new_image = gdk_pixbuf_flip(current_image, 0);
- break;
- case TRANS_FLOP:
- case TRANS_ANTITRANSPOSE:
- new_image = gdk_pixbuf_flip(current_image, 1);
- break;
- }
- if (!new_image)
- exit(1);
-
- switch (trans & 7u) {
- case TRANS_TRANSPOSE:
- case TRANS_ANTITRANSPOSE:
- current_image = new_image;
- new_image = gdk_pixbuf_rotate_simple(current_image, GDK_PIXBUF_ROTATE_CLOCKWISE);
- if (!new_image)
- exit(1);
- g_object_unref(current_image);
- break;
- default:
- break;
- }
-
-transformed:
- if (trans & TRANS_NEGATIVE) {
- if (!new_image) {
- new_image = gdk_pixbuf_copy(current_image);
- if (!new_image)
- 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);
-
- for (y = 0; y < height; y++) {
- for (x = 0; x < width * channels; x += channels) {
- /* TODO LATER perceptual inversion rather than sRGB */
- pixels[x + 0] = 255 - pixels[x + 0];
- pixels[x + 1] = 255 - pixels[x + 1];
- pixels[x + 2] = 255 - pixels[x + 2];
- }
- pixels = &pixels[rowstride];
- }
- }
-
- current_image = new_image ? new_image : files[current_file].image;
-
- if (FLOAT_EQ(zoom, 1.0)) {
- gtk_image_set_from_pixbuf(GTK_IMAGE(display_area), current_image);
- } else {
- /* TODO do not create an up scaled image, draw it dynamically upscaled (just the exposed region) */
- current_image = gdk_pixbuf_scale_simple(current_image,
- (int)(zoom * gdk_pixbuf_get_width(current_image)),
- (int)(zoom * gdk_pixbuf_get_height(current_image)),
- GDK_INTERP_BILINEAR);
- gtk_image_set_from_pixbuf(GTK_IMAGE(display_area), current_image);
- g_object_unref(current_image);
- }
-
- if (transformed_image)
- g_object_unref(transformed_image);
- transformed_image = new_image;
-}
-
-static void
-free_files(void)
-{
- while (nfiles) {
- --nfiles;
- free(files[nfiles].path);
- g_clear_object(&files[nfiles].image);
- }
- free(files);
- files = NULL;
- current_file = 0u;
-}
-
-static void
-clear_images(void)
-{
- gtk_image_clear(GTK_IMAGE(display_area));
- gtk_statusbar_pop(GTK_STATUSBAR(statusbar), 0u);
- g_clear_object(&transformed_image);
- free_files();
+ set_statusbar_message(message);
+ free(message);
}
-static int
-load_image(struct file *file, int errors_to_stderr)
+int
+expose_image(GtkWidget *widget, GdkEventExpose *event, void *user)
{
- const char *error_message;
- GError *error = NULL;
- GtkWidget *dialog;
GdkPixbuf *image;
- GdkPixbufLoader *loader = NULL;
- struct stat st;
- unsigned char buffer[8 << 10];
- ssize_t r;
- int fd, ret = 1;
+ cairo_t *cr;
+ double height, width, x, y, s;
- fd = open(file->path, O_RDONLY | O_NOCTTY);
- if (fd < 0)
- goto fail_errno;
+ (void) user;
- if (fstat(fd, &st))
- goto fail_errno;
+ if (suspend_expose)
+ return 1;
- if (!S_ISREG(st.st_mode)) {
- error_message = "Not a regular file";
- goto fail;
- }
+ cr = gdk_cairo_create(widget->window);
+ gdk_cairo_set_source_color(cr, &widget->style->bg[GTK_STATE_NORMAL]);
- 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;
- }
- image = gdk_pixbuf_loader_get_pixbuf(loader);
+ 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) {
- error_message = "Unsupported image format";
- goto fail;
+ cairo_fill(cr);
+ goto out;
}
- g_object_ref(image);
-
- g_clear_object(&file->image);
- file->size = st.st_size;
- file->image = image;
-
-out:
- g_clear_object(&loader);
- if (fd >= 0)
- close(fd);
- return ret;
+ s = get_zoom(zoom);
+ width = s * gdk_pixbuf_get_width(image);
+ height = s * gdk_pixbuf_get_height(image);
+ if (width < widget->allocation.width)
+ x = 0.5 * ((double)widget->allocation.width - width);
+ else
+ x = -logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll))->value;
+ if (height < widget->allocation.height)
+ y = 0.5 * ((double)widget->allocation.height - height);
+ else
+ y = -logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll))->value;
-fail_errno:
- error_message = strerror(errno);
+ cairo_fill(cr);
-fail:
- if (errors_to_stderr) {
- weprintf("Cannot open %s: %s", file->path, error_message);
- } else {
- dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
- GTK_BUTTONS_CLOSE, "Cannot open %s", file->path);
- gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", error_message);
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- }
+ cairo_translate(cr, x, y);
+ cairo_scale(cr, s, s);
+ gdk_cairo_set_source_pixbuf(cr, image, 0.0, 0.0);
- g_clear_error(&error);
- ret = 0;
- goto out;
+out:
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ return 1;
}
static void
-set_images_from_array(struct file *selected_files, size_t nselected_files)
+show_image(int update_zoom)
{
- size_t i, n;
+ GdkPixbuf *new_image, *original_image, *display_image;
+ double s;
- for (i = 0u, n = 0u; i < nselected_files; i++) {
- selected_files[i].image = NULL;
- if (load_image(&selected_files[i], 0)) {
- if (i != n)
- selected_files[n] = selected_files[i];
- n += 1u;
- } else {
- free(selected_files[i].path);
- }
- }
-
- if (!n) {
- free(selected_files);
+ original_image = get_original_image();
+ if (!original_image) {
+ logical_scroll_pane_set_content_size(LOGICAL_SCROLL_PANE(scroll), 1, 1);
+ gtk_widget_queue_draw(display_area);
return;
}
- clear_images();
- files = selected_files;
- nfiles = n;
- show_image();
- update_status();
- set_actions_sensitive();
-}
+ new_image = apply_transformations(original_image);
+ display_image = new_image ? new_image : original_image;
-static void
-set_images_from_list(GSList *selected_files)
-{
- struct file *new_files;
- GSList *file;
- size_t i;
+ if (update_zoom && zoom_mode != ZOOM_CUSTOM && zoom_mode != ZOOM_NORMAL)
+ zoom = calculate_zoom_mode(display_image, zoom_mode);
- for (file = selected_files, i = 0u; file; file = file->next)
- i += 1u;
- if (!i)
- return;
- new_files = ecalloc(i, sizeof(*files));
+ 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)));
- for (file = selected_files, i = 0u; file; file = file->next) {
- new_files[i].path = estrdup(file->data);
- new_files[i].image = NULL;
- g_free(file->data);
-
- if (load_image(&new_files[i], 0))
- i += 1u;
- else
- free(new_files[i].path);
- }
- g_slist_free(selected_files);
-
- if (!i) {
- free(new_files);
- return;
- }
-
- clear_images();
- files = new_files;
- nfiles = i;
- show_image();
- update_status();
- set_actions_sensitive();
-}
-
-static int
-file_filter(const GtkFileFilterInfo *info, void *user)
-{
- struct filter *filter = user;
- size_t pos, name_len, suffix_len;
- const char *name, *suffix;
- size_t backup;
-
- if (!info->filename)
- return 0;
- if (!filter || g_file_test(info->filename, G_FILE_TEST_IS_DIR))
- return 1;
-
- name = info->filename;
- name_len = strlen(name);
- if (!name_len)
- return 0;
-
- for (pos = filter->suffixes_start; pos != filter->suffixes_end; pos += suffix_len + 1u) {
- suffix = &suffixes[pos];
- suffix_len = strlen(suffix);
-
- backup = (size_t)(name[name_len - 1u] == '~');
-
- if (name_len <= 1u + suffix_len + backup)
- continue;
- if (name[name_len - (1u + suffix_len + backup)] != '.')
- continue;
- if (memcmp(&name[name_len - (suffix_len + backup)], suffix, suffix_len))
- continue;
-
- return 1;
- }
-
- return 0;
-}
-
-static int
-filtercmp(const void *a_void, const void *b_void)
-{
- const struct filter *a = a_void, *b = b_void;
- return strcmp(a->name, b->name);
-}
-
-static void
-populate_filters(void)
-{
- GSList *formats, *item;
- GdkPixbufFormat *format;
- char **extensions, **extension, *p;
- const char *name;
- size_t i, len = 0u;
- size_t ext_len, need, count;
-
- formats = gdk_pixbuf_get_formats();
-
- for (item = formats; item; item = item->next) {
- format = item->data;
- if (gdk_pixbuf_format_is_disabled(format))
- continue;
-
- name = gdk_pixbuf_format_get_description(format);
- extensions = gdk_pixbuf_format_get_extensions(format);
-
- need = 0u;
- count = 0u;
- for (extension = extensions; *extension; extension++) {
- ext_len = strlen(*extension);
- if (need > SIZE_MAX - 1u - ext_len)
- eprintf("Out of address space");
- need += ext_len + 1u;
- count += 1u;
- }
- if (need > SIZE_MAX - len)
- eprintf("Out of address space");
- if (!count)
- goto next;
-
- filters = ereallocarray(filters, nfilters + 1u, sizeof(*filters));
- filters[nfilters].suffixes_start = len;
- filters[nfilters].name = emalloc(strlen(name) + sizeof(" ()") + need + (count - 1u));
-
- p = stpcpy(stpcpy(filters[nfilters].name, name), " (");
- suffixes = erealloc(suffixes, need + len);
- for (extension = extensions;;) {
- ext_len = strlen(*extension);
- memcpy(&suffixes[len], *extension, ext_len + 1u);
- len += ext_len + 1u;
-
- memcpy(p, *extension, ext_len);
- p = &p[ext_len];
-
- if (!*++extension)
- break;
- p = stpcpy(p, ", ");
- }
- p = stpcpy(p, ")");
-
- for (i = 0u; i < nfilters; i++) {
- if (!strcmp(filters[nfilters].name, filters[i].name)) {
- free(filters[nfilters].name);
- len = filters[nfilters].suffixes_start;
- goto next;
- }
- }
-
- filters[nfilters].suffixes_end = len;
- nfilters += 1u;
-
- next:
- g_strfreev(extensions);
- }
-
- qsort(filters, nfilters, sizeof(*filters), &filtercmp);
-
- anyfilter.suffixes_end = len;
-
- g_slist_free(formats);
-}
-
-static void
-add_image_filters(GtkFileChooser *chooser)
-{
- GtkFileFilter *filter;
- size_t i;
-
- filter = gtk_file_filter_new();
- gtk_file_filter_set_name(filter, "All images");
- gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, file_filter, &anyfilter, NULL);
- gtk_file_chooser_add_filter(chooser, filter);
-
- for (i = 0u; i < nfilters; i++) {
- filter = gtk_file_filter_new();
- gtk_file_filter_set_name(filter, filters[i].name);
- gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, file_filter, &filters[i], NULL);
- gtk_file_chooser_add_filter(chooser, filter);
- }
-
- filter = gtk_file_filter_new();
- gtk_file_filter_set_name(filter, "All files");
- gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, file_filter, NULL, NULL);
- gtk_file_chooser_add_filter(chooser, filter);
-}
-
-static void
-preview_image(GtkFileChooser *chooser, GtkWidget *preview_widget)
-{
- char *filename;
- GdkPixbuf *preview;
-
- filename = gtk_file_chooser_get_preview_filename(chooser);
- preview = filename ? gdk_pixbuf_new_from_file_at_scale(filename, 192, 192, 1, NULL) : NULL;
- gtk_image_set_from_pixbuf(GTK_IMAGE(g_object_get_data(G_OBJECT(preview_widget), "image")), preview);
- if (preview)
- g_object_unref(preview);
- g_free(filename);
+ if (transformed_image)
+ g_object_unref(transformed_image);
+ transformed_image = new_image;
}
static void
-open_action(GtkAction *action, void *user)
+set_images(struct file *new_files, size_t nnew_files)
{
- /* TODO The preview widget should have a fixed size (although it may be subject
- * to manual resize by the user). When there is no preview the preview widge
- * shall just say "Preview" (centered on both dimensions) */
-
- /* TODO The preview widget shall not have a filename header */
-
- GtkWidget *chooser_widget, *image, *preview;
- GtkFileChooser *chooser;
-
- (void) action;
- (void) user;
-
- chooser_widget = gtk_file_chooser_dialog_new("Open",
- GTK_WINDOW(window),
- GTK_FILE_CHOOSER_ACTION_OPEN,
- GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
- GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
- NULL);
- chooser = GTK_FILE_CHOOSER(chooser_widget);
-
- g_object_set(gtk_settings_get_default(), "gtk-recent-files-enabled", 0, NULL);
- gtk_file_chooser_set_select_multiple(chooser, 1);
- gtk_file_chooser_set_current_folder(chooser, home_directory);
- gtk_file_chooser_set_local_only(chooser, 1);
- gtk_file_chooser_set_do_overwrite_confirmation(chooser, 0);
-
- preview = gtk_vbox_new(0, 6);
- image = gtk_image_new();
- gtk_box_pack_start(GTK_BOX(preview), image, 0, 0, 0u);
- gtk_box_pack_start(GTK_BOX(preview), gtk_label_new("Preview"), 0, 0, 0u);
- g_object_set_data(G_OBJECT(preview), "image", image);
- gtk_file_chooser_set_preview_widget(chooser, preview);
- gtk_file_chooser_set_preview_widget_active(chooser, 1);
- g_signal_connect(chooser, "update-preview", G_CALLBACK(preview_image), preview);
-
- add_image_filters(chooser);
- if (gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
- set_images_from_list(gtk_file_chooser_get_filenames(chooser));
- gtk_widget_destroy(GTK_WIDGET(chooser));
+ g_clear_object(&transformed_image);
+ free_files();
+ files = new_files;
+ nfiles = nnew_files;
+ navigate_redraw();
+ set_actions_sensitive();
}
-static void
-reload_action(GtkAction *action, void *user)
+void
+add_images(struct file *new_files, size_t nnew_files)
{
- (void) action;
- (void) user;
- if (current_file >= nfiles)
- return;
- if (load_image(&files[current_file], 0)) {
- show_image();
+ files = ereallocarray(files, nfiles + nnew_files, sizeof(*files));
+ memcpy(&files[nfiles], new_files, nnew_files * sizeof(*files));
+ nfiles += nnew_files;
+ if (nfiles == nnew_files)
+ navigate_redraw();
+ else
update_status();
- }
-}
-
-static void
-close_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- gtk_main_quit();
-}
-
-static void
-begin_print(GtkPrintOperation *operation, GtkPrintContext *context, void *user)
-{
- (void) context;
- (void) user;
- gtk_print_operation_set_n_pages(operation, 1);
-}
-
-static void
-draw_page(GtkPrintOperation *operation, GtkPrintContext *context, int page, void *user)
-{
- GdkPixbuf *image = user;
- double height, width, x, y;
- double scale_x, scale_y, scale;
- cairo_t *cr;
-
- (void) operation;
- (void) page;
-
- width = gtk_print_context_get_width(context);
- height = gtk_print_context_get_height(context);
- scale_x = width / gdk_pixbuf_get_width(image);
- scale_y = height / gdk_pixbuf_get_height(image);
- scale = MIN(scale_x, scale_y);
- x = 0.5 * (width - scale * gdk_pixbuf_get_width(image));
- y = 0.5 * (height - scale * gdk_pixbuf_get_height(image));
-
- cr = gtk_print_context_get_cairo_context(context);
- cairo_translate(cr, x, y);
- cairo_scale(cr, scale, scale);
- gdk_cairo_set_source_pixbuf(cr, image, 0.0f, 0.0f);
- cairo_paint(cr);
+ set_actions_sensitive();
}
-static void
-print_action(GtkAction *action, void *user)
+void
+set_images_from_array(struct file *selected_files, size_t nselected_files)
{
- /* TODO the print dialogue needs a positioning tab that lets the user scale and pan */
-
- GdkPixbuf *image;
- GtkPrintOperation *operation;
- GtkPageSetup *setup;
- GtkPageOrientation orientation = GTK_PAGE_ORIENTATION_PORTRAIT;
-
- (void) action;
- (void) user;
-
- if (current_file >= nfiles || !files[current_file].image)
- return;
-
- image = transformed_image ? transformed_image : files[current_file].image;
- g_object_ref(image);
- operation = gtk_print_operation_new();
-
- if (gdk_pixbuf_get_width(image) > gdk_pixbuf_get_height(image))
- orientation = GTK_PAGE_ORIENTATION_LANDSCAPE;
- setup = gtk_page_setup_new();
- gtk_page_setup_set_orientation(setup, orientation);
- gtk_print_operation_set_default_page_setup(operation, setup);
- g_object_unref(setup);
-
- gtk_print_operation_set_embed_page_setup(operation, 1);
- g_signal_connect(operation, "begin-print", G_CALLBACK(begin_print), image);
- g_signal_connect(operation, "draw-page", G_CALLBACK(draw_page), image);
- gtk_print_operation_run(operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(window), NULL);
- g_object_unref(operation);
- g_object_unref(image);
+ size_t n = files_from_array(selected_files, nselected_files);
+ if (n)
+ set_images(selected_files, n);
+ else
+ free(selected_files);
}
-static void
-properties_action(GtkAction *action, void *user)
+void
+set_images_from_list(GSList *selected_files)
{
- /* TODO LATER What shall "Properties" display? */
-
- GtkWidget *dialog;
- struct file *file;
- const char *name;
-
- (void) action;
- (void) user;
-
- if (current_file >= nfiles || !files[current_file].image)
- return;
-
- file = &files[current_file];
- name = strrchr(file->path, '/');
- name = name ? &name[1u] : file->path;
-
- dialog = gtk_message_dialog_new(GTK_WINDOW(window), 0, GTK_MESSAGE_INFO,
- GTK_BUTTONS_CLOSE, "%s", name);
- 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));
- g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog);
- gtk_widget_show(dialog);
+ struct file *new_files;
+ size_t n = files_from_list(&new_files, selected_files);
+ if (n)
+ set_images(new_files, n);
}
-static void
-scroll_by(int horizontally, double vertically)
+void
+scroll_by(double horizontally, double vertically)
{
GtkAdjustment *adjustment;
double min, val, max;
- adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll));
+ adjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
min = adjustment->lower;
- val = adjustment->value + (double)horizontally;
+ val = adjustment->value + horizontally;
max = adjustment->upper - adjustment->page_size;
val = MIN(MAX(min, val), max);
gtk_adjustment_set_value(adjustment, val);
- adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll));
+ adjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
min = adjustment->lower;
- val = adjustment->value + (double)vertically;
+ val = adjustment->value + vertically;
max = adjustment->upper - adjustment->page_size;
val = MIN(MAX(min, val), max);
gtk_adjustment_set_value(adjustment, val);
}
-static void
-set_zoom(double new_zoom, double x, double y)
+void
+set_zoom_around_point(Zoom new_zoom, double x, double y)
{
GtkAdjustment *hadjustment, *vadjustment;
- double old_zoom;
+ 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;
- if (current_file >= nfiles || !files[current_file].image)
- return;
- if (FLOAT_EQ(new_zoom, zoom))
+ if (zoom_eq(new_zoom, zoom))
return;
+ image = get_image();
+ if (!image)
+ goto out;
- hadjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll));
- vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll));
- old_zoom = zoom;
+ old_s = get_zoom(zoom);
+ new_s = get_zoom(new_zoom);
zoom = new_zoom;
- show_image();
- gtk_adjustment_set_value(hadjustment, (hadjustment->value + x) * zoom / old_zoom - x);
- gtk_adjustment_set_value(vadjustment, (vadjustment->value + y) * zoom / old_zoom - y);
- update_status();
-}
-
-static void
-clear_toggles(enum toggle_group group, GtkToggleAction *except)
-{
- GtkToggleAction *action;
- clearing_toggles = 1;
+ w = gdk_pixbuf_get_width(image);
+ h = gdk_pixbuf_get_height(image);
-#define X_SKIP(...)
-#define X_TOGGLE(T, N, A, B, ICON, ACCEL, ATEXT, F, GROUP, ACTIVE, ...)\
- do {\
- if (GROUP == group) {\
- action = GTK_TOGGLE_ACTION(get_action(A B));\
- if (action != except)\
- gtk_toggle_action_set_active(action, 0);\
- }\
- } while (0) __VA_ARGS__
+ 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;
- LIST_MENU(X_SKIP, X_SKIP, X_SKIP, X_TOGGLE, ;);
+ iw = old_s * (double)w;
+ ih = old_s * (double)h;
+ old_x0 = vw > iw ? 0.5 * (vw - iw) : 0.0;
+ old_y0 = vh > ih ? 0.5 * (vh - ih) : 0.0;
-#undef X_SKIP
-#undef X_TOGGLE
-
- clearing_toggles = 0;
-}
+ iw = new_s * (double)w;
+ ih = new_s * (double)h;
+ new_x0 = vw > iw ? 0.5 * (vw - iw) : 0.0;
+ new_y0 = vh > ih ? 0.5 * (vh - ih) : 0.0;
-static int
-scroll_event(GtkWidget *widget, GdkEventScroll *event, void *user)
-{
- int shift = !!(event->state & GDK_SHIFT_MASK);
- int ctrl = !!(event->state & GDK_CONTROL_MASK);
- int up = event->direction == GDK_SCROLL_UP;
- int down = event->direction == GDK_SCROLL_DOWN;
- int left = event->direction == GDK_SCROLL_LEFT;
- int right = event->direction == GDK_SCROLL_RIGHT;
+ hx = (x - old_x0 + hadjustment->value) / old_s;
+ hy = (y - old_y0 + vadjustment->value) / old_s;
+ adj_x = new_x0 + hx * new_s - x;
+ adj_y = new_y0 + hy * new_s - y;
- (void) widget;
- (void) user;
+ suspend_expose++;
- if (shift) {
- left = up, up = 0;
- right = down, down = 0;
- }
+ show_image(0);
- if (!ctrl) {
- scroll_by(left ? -SCROLL_AMOUNT : right ? +SCROLL_AMOUNT : 0,
- up ? -SCROLL_AMOUNT : down ? +SCROLL_AMOUNT : 0);
- } else if (up | down) {
- clear_toggles(GROUP_ZOOM, NULL);
- set_zoom(up ? zoom_in(zoom) : zoom_out(zoom), event->x, event->y);
- } else {
- return 0;
- }
+ max = hadjustment->upper - hadjustment->page_size;
+ x = MAX(hadjustment->lower, adj_x);
+ x = MIN(x, max);
+ gtk_adjustment_set_value(hadjustment, x);
- return 1;
-}
+ max = vadjustment->upper - vadjustment->page_size;
+ y = MAX(vadjustment->lower, adj_y);
+ y = MIN(y, max);
+ gtk_adjustment_set_value(vadjustment, y);
-static int
-button_pressed(GtkWidget *widget, GdkEventButton *event, void *user)
-{
- (void) widget;
- (void) user;
-
- if (event->button != 2u && !(event->state & GDK_CONTROL_MASK))
- return 0;
-
- dragging = 1;
- drag_x = (int)event->x;
- drag_y = (int)event->y;
- return 1;
-}
-
-static int
-button_released(GtkWidget *widget, GdkEventButton *event, void *user)
-{
- (void) widget;
- (void) event;
- (void) user;
- dragging = 0;
- return 0;
+ suspend_expose--;
+ if (!suspend_expose)
+ gtk_widget_queue_draw(display_area);
+out:
+ update_status();
}
-static int
-pointer_moved(GtkWidget *widget, GdkEventMotion *event, void *user)
+void
+set_zoom_around_centre(Zoom new_zoom)
{
- double x, y;
-
- (void) widget;
- (void) user;
-
- if (!dragging)
- return 0;
+ GtkAdjustment *hadjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
+ GtkAdjustment *vadjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
- gdk_event_get_coords((GdkEvent *)event, &x, &y);
- scroll_by(drag_x - (int)x, drag_y - (int)y);
- drag_x = (int)x;
- drag_y = (int)y;
-
- return 1;
+ set_zoom_around_point(new_zoom, 0.5 * hadjustment->page_size, 0.5 * vadjustment->page_size);
}
static void
-zoom_in_action(GtkAction *action, void *user)
+centre_image(void)
{
- (void) action;
- (void) user;
- clear_toggles(GROUP_ZOOM, NULL);
- set_zoom(zoom_in(zoom), 0.0, 0.0);
- /* TODO keep center point */
-}
+ GtkAdjustment *hadjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
+ GtkAdjustment *vadjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
-static void
-zoom_out_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- clear_toggles(GROUP_ZOOM, NULL);
- set_zoom(zoom_out(zoom), 0.0, 0.0);
- /* TODO keep center point */
+ gtk_adjustment_set_value(hadjustment, 0.5 * (hadjustment->upper - hadjustment->page_size));
+ gtk_adjustment_set_value(vadjustment, 0.5 * (vadjustment->upper - vadjustment->page_size));
}
-static void
-set_zoom_fit(GtkToggleAction *action, int xfit, int yfit, int cap)
+void
+set_zoom_and_recentre(Zoom new_zoom)
{
- GtkAdjustment *adjustment;
- GdkPixbuf *image;
- double s = 1.0, y;
- int w, h;
-
- if (!gtk_toggle_action_get_active(action) || current_file >= nfiles || !files[current_file].image) {
- if (!clearing_toggles)
- clear_toggles(GROUP_ZOOM, action);
+ if (zoom_eq(new_zoom, zoom))
return;
- }
- image = transformed_image ? transformed_image : files[current_file].image;
+ suspend_expose++;
- if (xfit) {
- adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll));
- w = gdk_pixbuf_get_width(image);
- s = adjustment->page_size / (double)w;
- }
-
- if (yfit) {
- adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll));
- h = gdk_pixbuf_get_height(image);
- y = adjustment->page_size / (double)h;
- s = xfit ? MIN(s, y) : y;
- }
-
- if (cap && s > 1.0)
- s = 1.0;
-
- clear_toggles(GROUP_ZOOM, action);
- set_zoom(s, 0.0, 0.0);
- /* TODO center image */
-}
-
-static void
-normal_size_action(GtkToggleAction *action, void *user)
-{
- (void) user;
- set_zoom_fit(action, 0, 0, 0);
-}
-
-static void
-width_fit_action(GtkToggleAction *action, void *user)
-{
- (void) user;
- set_zoom_fit(action, 1, 0, 0);
-}
-
-static void
-height_fit_action(GtkToggleAction *action, void *user)
-{
- (void) user;
- set_zoom_fit(action, 0, 1, 0);
-}
-
-static void
-window_fit_action(GtkToggleAction *action, void *user)
-{
- (void) user;
- set_zoom_fit(action, 1, 1, 0);
-}
+ zoom = new_zoom;
+ show_image(0);
+ centre_image();
-static void
-best_fit_action(GtkToggleAction *action, void *user)
-{
- (void) user;
- set_zoom_fit(action, 1, 1, 1);
+ suspend_expose--;
+ if (!suspend_expose)
+ gtk_widget_queue_draw(display_area);
+ update_status();
}
-static int
-key_pressed(GtkWidget *widget, GdkEventKey *event, void *user)
+void
+reload_redraw(void)
{
- int left, right, up, down, amount;
+ GtkAdjustment *hadjustment, *vadjustment;
+ double x, y, max;
- (void) widget;
- (void) user;
+ hadjustment = logical_scroll_pane_get_hadjustment(LOGICAL_SCROLL_PANE(scroll));
+ vadjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(scroll));
- if (event->keyval == GDK_Escape && gtk_toggle_action_get_active(fullscreen)) {
- gtk_toggle_action_set_active(fullscreen, 0);
- return 1;
- }
+ x = hadjustment->value + 0.5 * hadjustment->page_size;
+ x = hadjustment->upper > hadjustment->page_size ? x / hadjustment->upper : 0.5;
+ y = vadjustment->value + 0.5 * vadjustment->page_size;
+ y = vadjustment->upper > vadjustment->page_size ? y / vadjustment->upper : 0.5;
- if (event->state & GDK_CONTROL_MASK)
- return 0;
+ suspend_expose++;
- amount = (event->state & GDK_MOD1_MASK) ? 1 : SCROLL_AMOUNT;
+ show_image(0);
- left = event->keyval == GDK_Left;
- right = event->keyval == GDK_Right;
- up = event->keyval == GDK_Up;
- down = event->keyval == GDK_Down;
+ max = hadjustment->upper - hadjustment->page_size;
+ x = x * hadjustment->upper - 0.5 * hadjustment->page_size;
+ x = MIN(MAX(hadjustment->lower, x), max);
+ gtk_adjustment_set_value(hadjustment, x);
- if (left | right | up | down) {
- scroll_by(left ? -amount : right ? +amount : 0,
- up ? -amount : down ? +amount : 0);
- return 1;
- }
+ max = vadjustment->upper - vadjustment->page_size;
+ y = y * vadjustment->upper - 0.5 * vadjustment->page_size;
+ y = MIN(MAX(vadjustment->lower, y), max);
+ gtk_adjustment_set_value(vadjustment, y);
- return 0;
-}
-
-static int
-window_state_changed(GtkWidget *widget, GdkEventWindowState *event, void *user)
-{
- (void) widget;
- (void) user;
- gtk_toggle_action_set_active(fullscreen, !!(event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN));
- return 0;
+ suspend_expose--;
+ if (!suspend_expose)
+ gtk_widget_queue_draw(display_area);
+ update_status();
}
-static void
-fullscreen_action(GtkToggleAction *action, void *user)
+void
+transform_redraw(enum transformation augment)
{
- int active = gtk_toggle_action_get_active(action);
-
- (void) user;
+ GdkPixbuf *image;
+ GtkAdjustment *hadjustment, *vadjustment;
+ double s, x, y, width, height, max;
- if (active && current_file >= nfiles) {
- gtk_toggle_action_set_active(action, 0);
+ 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)
return;
- }
- gtk_widget_set_visible(menubar, !active);
- gtk_widget_set_visible(toolbar, !active);
- gtk_widget_set_visible(statusbar, !active);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- active ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS,
- active ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS);
+ s = get_zoom(zoom);
+ width = gdk_pixbuf_get_width(image);
+ height = gdk_pixbuf_get_height(image);
- if (active)
- gtk_window_fullscreen(GTK_WINDOW(window));
- else
- gtk_window_unfullscreen(GTK_WINDOW(window));
-}
+ 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;
+ y = s * height > vadjustment->page_size ? y / s : 0.5 * height;
+ transform_point(augment, &x, &y, &width, &height);
-static void
-reset_transformations_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- trans &= ~7u;
- show_image();
- /* TODO keep center point */
-}
+ suspend_expose++;
-static void
-transform(enum transformation augment)
-{
- if (augment & TRANS_TRANSPOSE)
- trans = ((augment - trans) & 3u) | ((augment ^ trans) & ~3u);
- else
- trans = ((augment + trans) & 3u) | ((augment ^ trans) & ~3u);
- show_image();
- /* TODO keep center point */
-}
+ show_image(0);
-static void
-rotate_clockwise_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- transform(TRANS_CLOCKWISE);
-}
+ max = hadjustment->upper - hadjustment->page_size;
+ x = s * x - 0.5 * hadjustment->page_size;
+ x = MIN(MAX(hadjustment->lower, x), max);
+ gtk_adjustment_set_value(hadjustment, x);
-static void
-rotate_180_degrees_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- transform(TRANS_180_DEGREES);
-}
+ max = vadjustment->upper - vadjustment->page_size;
+ y = s * y - 0.5 * vadjustment->page_size;
+ y = MIN(MAX(vadjustment->lower, y), max);
+ gtk_adjustment_set_value(vadjustment, y);
-static void
-rotate_anticlockwise_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- transform(TRANS_ANTICLOCKWISE);
+ suspend_expose--;
+ if (!suspend_expose)
+ gtk_widget_queue_draw(display_area);
}
-static void
-flop_action(GtkAction *action, void *user)
+void
+navigate_redraw(void)
{
- (void) action;
- (void) user;
- transform(TRANS_FLOP);
-}
+ suspend_expose++;
-static void
-flip_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- transform(TRANS_FLIP);
-}
-
-static void
-transpose_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- transform(TRANS_TRANSPOSE);
-}
-
-static void
-antitranspose_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- transform(TRANS_ANTITRANSPOSE);
-}
-
-static void
-invert_action(GtkToggleAction *action, void *user)
-{
+ show_image(1);
+ centre_image();
- enum transformation tr;
- (void) user;
- tr = gtk_toggle_action_get_active(action) ? TRANS_NEGATIVE : TRANS_NONE;
- transform(tr ^ (trans & TRANS_NEGATIVE));
-}
-
-static void
-navigate(size_t jump, size_t move)
-{
- if (!nfiles)
- return;
- current_file = jump;
- current_file += move;
- current_file %= nfiles;
- show_image();
+ suspend_expose--;
+ if (!suspend_expose)
+ gtk_widget_queue_draw(display_area);
update_status();
}
-static void
-next_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- navigate(current_file, +(size_t)1);
-}
-
-static void
-previous_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- navigate(current_file ? current_file : nfiles, -(size_t)1);
-}
-
-static void
-first_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- navigate(0u, 0u);
-}
-
-static void
-last_action(GtkAction *action, void *user)
-{
- (void) action;
- (void) user;
- navigate(nfiles, -(size_t)1);
-}
-
-static void
-drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data,
- unsigned int info, unsigned int time, void *user)
-{
- GError *error = NULL;
- char *uri;
-
- (void) widget;
- (void) context;
- (void) info;
- (void) time;
- (void) user;
-
- if (current_file >= nfiles || !files[current_file].path)
- return;
-
- uri = g_filename_to_uri(files[current_file].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
-drop_files(GtkWidget *widget, GdkDragContext *context, int x, int y,
- GtkSelectionData *data, unsigned int info, unsigned int time, void *user)
+int
+recalculate_zoom_mode(void *user)
{
- GError *error = NULL;
- char **uris, **uri, *path;
- struct file *dropped_files;
- size_t i, j;
- GdkModifierType modifiers;
- GdkWindow *event_window;
-
- (void) widget;
- (void) x;
- (void) y;
- (void) info;
+ GdkPixbuf *image = get_image();
(void) user;
-
- uris = gtk_selection_data_get_uris(data);
- if (!uris) {
- gtk_drag_finish(context, 0, 0, time);
- return;
- }
-
- for (uri = uris, i = 0u; *uri; uri++)
- i += 1u;
- if (!i)
- goto out;
- dropped_files = ecalloc(i, sizeof(*dropped_files));
-
- for (uri = uris, i = 0u; *uri; uri++) {
- error = NULL; /* g_filename_from_uri requires this initialisation */
- path = g_filename_from_uri(*uri, NULL, &error);
- if (!path) {
- weprintf("g_filename_from_uri: %s", error->message);
- g_error_free(error);
- continue;
- }
- dropped_files[i++].path = estrdup(path);
- g_free(path);
- /* the other fields are set by set_images_from_array */
- }
-
- event_window = gtk_widget_get_window(widget);
- if (!event_window)
- goto out;
- if (!gdk_window_get_pointer(event_window, NULL, NULL, &modifiers))
- goto out;
- if ((modifiers & GDK_CONTROL_MASK) && nfiles) {
- files = ereallocarray(files, nfiles + i, sizeof(*files));
- for (j = 0u; j < i; j++) {
- if (load_image(&dropped_files[j], 0))
- files[nfiles++] = dropped_files[j];
- else
- free(dropped_files[j].path);
- }
- free(dropped_files);
- update_status();
- } else {
- set_images_from_array(dropped_files, i);
- }
-
-out:
- gtk_drag_finish(context, 1, 0, time);
- g_strfreev(uris);
-}
-
-static GtkWidget *
-append_toplevel_menu(int add_separator_to_toolbar, const char *name)
-{
- GtkWidget *item, *menu;
-
- if (add_separator_to_toolbar)
- gtk_toolbar_insert(GTK_TOOLBAR(toolbar), gtk_separator_tool_item_new(), -1);
-
- item = gtk_action_create_menu_item(get_action(name));
- menu = gtk_menu_new();
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
- gtk_menu_shell_append(GTK_MENU_SHELL(menubar), item);
- return gtk_menu_item_get_submenu(GTK_MENU_ITEM(item));
-}
-
-static void
-append_separator(int add_to_toolbar, GtkWidget *menu)
-{
- if (add_to_toolbar)
- gtk_toolbar_insert(GTK_TOOLBAR(toolbar), gtk_separator_tool_item_new(), -1);
- if (menu)
- gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
-}
-
-static void
-append_action(int add_to_toolbar, GtkWidget *menu, const char *name, const char *tooltip)
-{
- GtkWidget *item;
- if (add_to_toolbar) {
- item = gtk_action_create_tool_item(get_action(name));
- gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(item), -1);
- if (tooltip)
- gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(item), tooltip);
- }
- if (menu) {
- item = gtk_action_create_menu_item(get_action(name));
- gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
- }
-}
-
-static void
-create_menu_and_toolbar(void)
-{
- GtkWidget *menu;
-
- menubar = gtk_menu_bar_new();
- toolbar = gtk_toolbar_new();
-
-#define X_TOPLEVEL(T, A, B, D) menu = append_toplevel_menu(T, A B) D
-#define X_SEPARTOR(T, D) append_separator(T, menu) D
-#define X_ACTION(T, N, A, B, ICON, ACCEL, ACCEL_TEXT, F, D) append_action(T, menu, A B, A B " (" ACCEL_TEXT ")") D
-#define X_TOGGLE(T, N, A, B, ICON, ACCEL, ACCEL_TEXT, F, G, ACTIVE, D) append_action(T, menu, A B, A B " (" ACCEL_TEXT ")") D
-
- LIST_MENU(X_TOPLEVEL, X_SEPARTOR, X_ACTION, X_TOGGLE, ;);
-
-#undef X_TOPLEVEL
-#undef X_SEPARTOR
-#undef X_ACTION
-#undef X_TOGGLE
-}
-
-static void
-connect_accelerators(GtkAccelGroup *accelerators)
-{
- GList *item, *list;
-
- list = gtk_action_group_list_actions(actions);
- for (item = list; item; item = item->next) {
- gtk_action_set_accel_group(item->data, accelerators);
- gtk_action_connect_accelerator(item->data);
- }
- g_list_free(list);
+ if (image && zoom_mode != ZOOM_CUSTOM && zoom_mode != ZOOM_NORMAL)
+ set_zoom_and_recentre(calculate_zoom_mode(image, zoom_mode));
+ return 0;
}
static int
-activate_fullscreen(void *user)
+initial_draw(void *user)
{
(void) user;
- gtk_toggle_action_set_active(fullscreen, 1);
+ navigate_redraw();
return 0;
}
-#define X_SKIP(...)
-#define X_TOPLEVEL(T, A, B, ...) {A B, NULL, A "_" B, NULL, NULL, NULL} __VA_ARGS__
-#define X_ACTION(T, N, A, B, ICON, ACCEL, ATEXT, F, ...) {A B, ICON, A "_" B, ACCEL, NULL, G_CALLBACK(F)} __VA_ARGS__
-#define X_TOGGLE(T, N, A, B, ICON, ACCEL, ATEXT, F, G, ACTIVE, ...) {A B, ICON, A "_" B, ACCEL, NULL, G_CALLBACK(F), ACTIVE} __VA_ARGS__
-
-static GtkActionEntry entries[] = {
- LIST_MENU(X_TOPLEVEL, X_SKIP, X_ACTION, X_SKIP, COMMA)
-};
-
-static GtkToggleActionEntry toggle_entries[] = {
- LIST_MENU(X_SKIP, X_SKIP, X_SKIP, X_TOGGLE, COMMA)
-};
-
-#undef X_SKIP
-#undef X_TOPLEVEL
-#undef X_ACTION
-#undef X_TOGGLE
-
int
main(int argc, char *argv[])
{
- struct passwd *pwd;
- GtkAccelGroup *accelerators;
- GtkWidget *box;
- size_t i;
- int failed = 0;
int start_fullscreen = 0;
+ int failed = 0;
ARGBEGIN {
case 'f':
@@ -1456,97 +421,32 @@ main(int argc, char *argv[])
usage();
} ARGEND;
- nfiles = (size_t)argc;
- if (nfiles) {
- files = ecalloc(nfiles, sizeof(*files));
- for (i = 0u; i < nfiles; i++) {
- files[i].path = estrdup(argv[i]);
- files[i].image = NULL;
- if (!load_image(&files[i], 1)) {
- free(files[i].path);
- failed = 1;
- }
- }
- }
- if (failed)
+ if (!set_files_from_argv(argc, argv))
goto fail;
- home_directory = getenv("HOME");
- if (!home_directory || !*home_directory) {
- pwd = getpwuid(getuid());
- if (pwd && pwd->pw_dir && *pwd->pw_dir)
- home_directory = pwd->pw_dir;
- else
- home_directory = ".";
- }
-
gtk_init(NULL, NULL);
- populate_filters();
-
- window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_window_set_title(GTK_WINDOW(window), "gbild");
- gtk_window_set_icon_name(GTK_WINDOW(window), "image-viewer");
- gtk_window_set_default_size(GTK_WINDOW(window), 900, 700);
- gtk_window_set_wmclass(GTK_WINDOW(window), "gbild", "gbild");
-
- g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
- g_signal_connect(window, "key-press-event", G_CALLBACK(key_pressed), NULL);
- g_signal_connect(window, "window-state-event", G_CALLBACK(window_state_changed), NULL);
-
- actions = gtk_action_group_new("gbild");
- gtk_action_group_add_actions(actions, entries, ELEMSOF(entries), NULL);
- gtk_action_group_add_toggle_actions(actions, toggle_entries, ELEMSOF(toggle_entries), NULL);
- fullscreen = GTK_TOGGLE_ACTION(gtk_action_group_get_action(actions, "Fullscreen"));
- accelerators = gtk_accel_group_new();
- gtk_window_add_accel_group(GTK_WINDOW(window), accelerators);
- connect_accelerators(accelerators);
- create_menu_and_toolbar();
-
- box = gtk_vbox_new(0, 0);
- gtk_container_add(GTK_CONTAINER(window), box);
- 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_image_new();
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS);
- gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(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);
- 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(button_pressed), NULL);
- g_signal_connect(display_area, "button-release-event", G_CALLBACK(button_released), NULL);
- g_signal_connect(display_area, "motion-notify-event", G_CALLBACK(pointer_moved), NULL);
-
- gtk_drag_source_set(display_area, GDK_BUTTON1_MASK, &image_target, 1u, GDK_ACTION_COPY);
- g_signal_connect(display_area, "drag-data-get", G_CALLBACK(drag_data_get), NULL);
+ set_up_ui();
+ set_up_dragging();
+ set_up_dropping();
- gtk_drag_dest_set(window, GTK_DEST_DEFAULT_ALL, &image_target, 1u, GDK_ACTION_COPY);
- g_signal_connect(window, "drag-data-received", G_CALLBACK(drop_files), NULL);
-
- show_image();
- update_status();
set_actions_sensitive();
gtk_widget_show_all(window);
+ g_idle_add(&initial_draw, NULL);
if (start_fullscreen && nfiles)
- g_idle_add(activate_fullscreen, NULL);
+ g_idle_add(&activate_fullscreen, NULL);
gtk_main();
- g_object_unref(actions);
if (transformed_image)
g_object_unref(transformed_image);
-fail:
+ free_ui();
+out:
free_files();
- free(suffixes);
- while (nfilters)
- free(filters[--nfilters].name);
- free(filters);
+ free_image_filters();
return failed;
+
+fail:
+ failed = 1;
+ goto out;
}