ui/gtk/image.c

8 days ago

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 29 Mar 2025 20:03:25 +0100 (8 days ago)
changeset 530
7992a44fe719
parent 529
0a4a6b0d1c82
child 532
80a6d8923d75
permissions
-rw-r--r--

implement imageviewer useradjustable setting in gtk4 implementation

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2024 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "image.h"

#include "container.h"
#include "menu.h"
#include "../common/context.h"
#include "../common/object.h"

static void imageviewer_destroy(UiImageViewer *iv) {
    if(iv->pixbuf) {
        g_object_unref(iv->pixbuf);
    }
    free(iv);
}

#if GTK_MAJOR_VERSION >= 4

static void imageviewer_draw(
        GtkDrawingArea *drawingarea,
        cairo_t *cr,
        int width,
        int height,
        gpointer userdata)
{
    ui_cairo_draw_image(userdata, cr, width, height);
}

#else

static gboolean imageviewer_draw(GtkWidget *widget, cairo_t *cr, gpointer userdata) {
    int width = gtk_widget_get_allocated_width(widget);
    int height = gtk_widget_get_allocated_height(widget);
    ui_cairo_draw_image(userdata, cr, width, height);
    return FALSE;
}

#endif

UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {
    UiObject *current = uic_current_obj(obj);
    
    GtkWidget *drawingarea = gtk_drawing_area_new();
    GtkWidget *toplevel;
    GtkWidget *widget = drawingarea;
      
    gtk_widget_set_size_request(drawingarea, 100, 100);
    
#if GTK_MAJOR_VERSION < 4
    GtkWidget *eventbox = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(eventbox), drawingarea);
    widget = eventbox;
#endif
    
    if(args.scrollarea) {
        toplevel = SCROLLEDWINDOW_NEW();
        SCROLLEDWINDOW_SET_CHILD(toplevel, widget);
        args.adjustsize = TRUE;
    } else {
        toplevel = widget;
    }
    
    UiImageViewer *imgviewer = malloc(sizeof(UiImageViewer));
    memset(imgviewer, 0, sizeof(UiImageViewer));
    if(args.image_padding > 0) {
        imgviewer->padding_left = args.image_padding;
        imgviewer->padding_right = args.image_padding;
        imgviewer->padding_top = args.image_padding;
        imgviewer->padding_bottom = args.image_padding;
    } else {
        imgviewer->padding_left = args.image_padding_left;
        imgviewer->padding_right = args.image_padding_right;
        imgviewer->padding_top = args.image_padding_top;
        imgviewer->padding_bottom = args.image_padding_bottom;
    }
    imgviewer->adjustsize = args.adjustsize;
    imgviewer->autoscale = args.autoscale;
    imgviewer->useradjustable = args.useradjustable;
    imgviewer->zoom_scale = 20;
    
    g_object_set_data_full(G_OBJECT(drawingarea), "uiimageviewer", imgviewer, (GDestroyNotify)imageviewer_destroy);
    
    UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);
    imgviewer->var = var;
    imgviewer->widget = drawingarea;
    
    if(var) {
        UiGeneric *value = var->value;
        value->get = ui_imageviewer_get;
        value->get_type = ui_imageviewer_get_type;
        value->set = ui_imageviewer_set;
        value->obj = imgviewer;
        if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) {
            GdkPixbuf *pixbuf = value->value;
            value->value = NULL;
            ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE);
        }
    }
    
#if GTK_MAJOR_VERSION >= 4
    gtk_drawing_area_set_draw_func(
            GTK_DRAWING_AREA(drawingarea),
            imageviewer_draw,
            imgviewer,
            NULL);
    
    if(args.useradjustable) {
        gtk_widget_set_focusable(drawingarea, TRUE);
    }
    
    GtkEventController *scrollcontroller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
    g_signal_connect(scrollcontroller, "scroll", G_CALLBACK(ui_imageviewer_scroll), imgviewer);
    gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(scrollcontroller));
    
    GtkGesture *drag = gtk_gesture_drag_new();
    g_signal_connect(drag, "drag-begin", G_CALLBACK(ui_imageviewer_drag_begin_cb), imgviewer);
    g_signal_connect(drag, "drag-end", G_CALLBACK(ui_imageviewer_drag_end_cb), imgviewer);
    g_signal_connect(drag, "drag-update", G_CALLBACK(ui_imageviewer_drag_update_cb), imgviewer);
    gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(drag));
    
#elif GTK_MAJOR_VERSION == 3
    g_signal_connect(
            drawingarea,
            "draw",
            G_CALLBACK(imageviewer_draw),
            imgviewer);
    
    gtk_widget_add_events(eventbox, GDK_SCROLL_MASK);
    
    g_signal_connect(
            eventbox,
            "scroll-event",
            G_CALLBACK(ui_imageviewer_scroll_event),
            imgviewer);
    g_signal_connect(
            eventbox,
            "button-press-event",
            G_CALLBACK(ui_imageviewer_button_press_event),
            imgviewer);
    g_signal_connect(
            eventbox,
            "button-release-event",
            G_CALLBACK(ui_imageviewer_button_release_event),
            imgviewer);

#endif
    
    if(args.contextmenu) {
        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, widget);
        ui_widget_set_contextmenu(widget, menu);
    }
       
    UI_APPLY_LAYOUT1(current, args);
    current->container->add(current->container, toplevel, TRUE);
    
    return toplevel;
}


void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height) {
    if(!imgviewer->pixbuf) {
        return;
    }
    
    GdkPixbuf *pixbuf = imgviewer->pixbuf;
    double dpixwidth = (double)gdk_pixbuf_get_width(pixbuf);
    double dpixheight = (double)gdk_pixbuf_get_height(pixbuf);
    
    double dwidth = width;
    double dheight = height;
    double scale = 1;
    if(imgviewer->autoscale) {
        scale = dwidth / dpixwidth;
        if(dpixheight * scale > dheight) {
            scale = dheight / dpixheight;
        }
    } else {
        scale = 1 + ((double)imgviewer->zoom / (double)imgviewer->zoom_scale);
    }

    dpixwidth *= scale;
    dpixheight *= scale;
    double x = (dwidth - dpixwidth) / 2;
    double y = (dheight - dpixheight) / 2;
    
    x += imgviewer->transx;
    y += imgviewer->transy;
    
    cairo_translate(cr, x, y);
    cairo_scale(cr, scale, scale);
    
    gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
    cairo_paint(cr);
    
    imgviewer->prev_scale = scale;
}

void* ui_imageviewer_get(UiGeneric *g) {
    UiImageViewer *imgviewer = g->obj;
    g->value = imgviewer->pixbuf;
    return g->value;
}

const char* ui_imageviewer_get_type(UiGeneric *g) {
    return UI_IMAGE_OBJECT_TYPE;
}

int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) {
    if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {
        return 1;
    }
    
    GdkPixbuf *pixbuf = value;
    
    UiImageViewer *imgviewer = g->obj;
    g->value = pixbuf;
    
    if(imgviewer->pixbuf) {
        g_object_unref(imgviewer->pixbuf);
    }
    imgviewer->pixbuf = pixbuf;
    
    if(imgviewer->adjustsize) {
        int width = gdk_pixbuf_get_width(pixbuf);
        int height = gdk_pixbuf_get_height(pixbuf);
        gtk_widget_set_size_request(imgviewer->widget, width, height);
    }
    gtk_widget_queue_draw(imgviewer->widget);
    
    return 0;
}



int ui_image_load_file(UiGeneric *obj, const char *path) {
    GError *error = NULL;
    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
    if(!pixbuf) {
        return 1;
    }
    
    if(obj->set) {
        obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE);
    } else {
        obj->value = pixbuf;
    }
       
    return 0;
}

#if GTK_MAJOR_VERSION >= 4

gboolean ui_imageviewer_scroll(
        GtkEventControllerScroll *widget,
        gdouble dx,
        gdouble dy,
        gpointer userdata)
{
    UiImageViewer *imgviewer = userdata;
    if(imgviewer->useradjustable) {
        imgviewer->zoom -= dy;
        if(imgviewer->zoom < -imgviewer->zoom_scale) {
            imgviewer->zoom = -imgviewer->zoom_scale;
        }
        gtk_widget_queue_draw(imgviewer->widget);
        return TRUE;
    }
    return FALSE;
}

void ui_imageviewer_drag_begin_cb(
        GtkGestureDrag* self,
        gdouble start_x,
        gdouble start_y,
        gpointer userdata)
{
    UiImageViewer *imgviewer = userdata;
    imgviewer->begin_transx = imgviewer->transx;
    imgviewer->begin_transy = imgviewer->transy;
}

void ui_imageviewer_drag_end_cb(
        GtkGestureDrag* self,
        gdouble x,
        gdouble y,
        gpointer userdata)
{
    
}

void ui_imageviewer_drag_update_cb(
        GtkGestureDrag* self,
        gdouble x,
        gdouble y,
        gpointer userdata)
{
    UiImageViewer *imgviewer = userdata;
    if(imgviewer->useradjustable) {
        imgviewer->transx = imgviewer->begin_transx + x;
        imgviewer->transy = imgviewer->begin_transy + y;
        gtk_widget_queue_draw(imgviewer->widget);
    }
}

#else

gboolean ui_imageviewer_scroll_event(
        GtkWidget *widget,
        GdkEventScroll event,
        gpointer userdata)
{
    printf("scroll event\n");
    return FALSE;
}

gboolean ui_imageviewer_button_press_event(
        GtkWidget *widget,
        GdkEventButton event,
        gpointer userdata)
{
    printf("button pressed\n");
}

gboolean ui_imageviewer_button_release_event(
        GtkWidget *widget,
        GdkEventButton event,
        gpointer userdata)
{
    printf("button released\n");
}

#endif

mercurial