ui/gtk/image.c

changeset 103
6606616eca9f
parent 88
e27526429d85
child 108
77254bd6dccb
--- a/ui/gtk/image.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/image.c	Sat Apr 05 16:46:11 2025 +0200
@@ -33,59 +33,265 @@
 #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 *scrolledwindow = SCROLLEDWINDOW_NEW();
-#if GTK_CHECK_VERSION(4, 0, 0)
-    GtkWidget *image = gtk_picture_new();
-#else
-    GtkWidget *image = gtk_image_new();
-#endif
-    
-    ui_set_name_and_style(image, args.name, args.style_class);
+    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();
-    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox);
-    gtk_container_add(GTK_CONTAINER(eventbox), image);
-#else
-    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
-    GtkWidget *eventbox = image;
+    gtk_container_add(GTK_CONTAINER(eventbox), drawingarea);
+    widget = eventbox;
 #endif
     
-    UI_APPLY_LAYOUT1(current, args);
-    current->container->add(current->container, scrolledwindow, TRUE);
+    if(args.scrollarea) {
+        toplevel = SCROLLEDWINDOW_NEW();
+        SCROLLEDWINDOW_SET_CHILD(toplevel, widget);
+        args.adjustwidgetsize = TRUE;
+    } else {
+        toplevel = widget;
+    }
+    
+    UiImageViewer *imgviewer = malloc(sizeof(UiImageViewer));
+    memset(imgviewer, 0, sizeof(UiImageViewer));
+    imgviewer->obj = obj;
+    imgviewer->onbuttonpress = args.onbuttonpress;
+    imgviewer->onbuttonpressdata = args.onbuttonpressdata;
+    imgviewer->onbuttonrelease = args.onbuttonrelease;
+    imgviewer->onbuttonreleasedata = args.onbuttonreleasedata;
+    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->adjustwidgetsize = args.adjustwidgetsize;
+    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 = image;
+        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);
+            g_object_unref(pixbuf);
         }
     }
     
-    if(args.contextmenu) {
-        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox);
-        ui_widget_set_contextmenu(eventbox, menu);
+#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);
     }
     
-    return scrolledwindow;
+    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));
+    
+    GtkGesture *click = gtk_gesture_click_new();
+    g_signal_connect(click, "pressed", G_CALLBACK(ui_imageviewer_pressed_cb), imgviewer);
+    g_signal_connect(click, "released", G_CALLBACK(ui_imageviewer_released_cb), imgviewer);
+    gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(click));
+    
+#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;
+}
+
+static void imageviewer_reset(UiImageViewer *imgviewer) {
+    imgviewer->isautoscaled = FALSE;
+    imgviewer->transx = 0;
+    imgviewer->transy;
+    imgviewer->begin_transx = 0;
+    imgviewer->begin_transy = 0;
+    imgviewer->scale = 1;
+    imgviewer->user_scale = 1;
+}
+
+UIWIDGET ui_imageviewer_reset(UIWIDGET w) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imageviewer_reset(imgviewer);
+        gtk_widget_queue_draw(w);
+    }
+}
+
+UIWIDGET ui_imageviewer_set_autoscale(UIWIDGET w, UiBool set) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imgviewer->autoscale = set;
+    }
+}
+
+UIWIDGET ui_imageviewer_set_adjustwidgetsize(UIWIDGET w, UiBool set) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imgviewer->adjustwidgetsize = set;
+    }
+}
+
+UIWIDGET ui_imageviewer_set_useradjustable(UIWIDGET w, UiBool set) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imgviewer->useradjustable = set;
+    }
+}
+
+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 autoscale is enabled, scale the image to fill available space
+    // if useradjustable is also enabled, the autoscaling is only done once
+    if(imgviewer->autoscale && imgviewer->scale != 0) {
+        if(!imgviewer->isautoscaled) {
+            scale = dwidth / dpixwidth;
+            if(dpixheight * scale > dheight) {
+                scale = dheight / dpixheight;
+            }
+
+            if(imgviewer->useradjustable) {
+                imgviewer->isautoscaled = TRUE;
+            }
+            
+            imgviewer->scale = scale;
+        } else {
+            scale = imgviewer->scale;
+        }
+        
+        imgviewer->user_scale = scale;
+    } else {
+        // user-adjusted scaling
+        //scale = 1 + ((double)imgviewer->zoom / (double)imgviewer->zoom_scale);
+        scale = imgviewer->user_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);
 }
 
 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) {
@@ -93,25 +299,25 @@
         return 1;
     }
     
-    // TODO: do we need to free the previous value here?
+    GdkPixbuf *pixbuf = value;
+    g_object_ref(pixbuf);
+    
+    UiImageViewer *imgviewer = g->obj;
+    g->value = pixbuf;
     
-    g->value = value;
-    g->type = type;
-    GdkPixbuf *pixbuf = value;
+    imageviewer_reset(imgviewer);
     
-    if(pixbuf) {
+    if(imgviewer->pixbuf) {
+        g_object_unref(imgviewer->pixbuf);
+    }
+    imgviewer->pixbuf = pixbuf;
+    
+    if(imgviewer->adjustwidgetsize && !imgviewer->autoscale) {
         int width = gdk_pixbuf_get_width(pixbuf);
         int height = gdk_pixbuf_get_height(pixbuf);
-        
-#if GTK_CHECK_VERSION(4, 0, 0)
-        GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
-        gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture));
-#else
-        gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf);
-#endif
-        gtk_widget_set_size_request(g->obj, width, height);
+        gtk_widget_set_size_request(imgviewer->widget, width, height);
     }
-
+    gtk_widget_queue_draw(imgviewer->widget);
     
     return 0;
 }
@@ -127,9 +333,147 @@
     
     if(obj->set) {
         obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE);
+        g_object_unref(pixbuf);
     } else {
         obj->value = pixbuf;
     }
-    
+          
     return 0;
 }
+
+void ui_image_ref(UIIMAGE img) {
+    g_object_ref(img);
+}
+
+void ui_image_unref(UIIMAGE img) {
+    g_object_unref(img);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+gboolean ui_imageviewer_scroll(
+        GtkEventControllerScroll *widget,
+        gdouble dx,
+        gdouble dy,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->useradjustable) {
+        double step = dy / imgviewer->zoom_scale;
+        if(imgviewer->user_scale - step > 0) {
+            imgviewer->user_scale -= step;
+        }
+        
+        imgviewer->scale = 0; // disable autoscale
+        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);
+    }
+}
+
+static void imgviewer_button_event(
+        GtkGestureClick *gesture,
+        UiImageViewer *imgviewer,
+        ui_callback callback,
+        void *userdata)
+{
+    UiEvent event;
+    event.obj = imgviewer->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.intval = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture));
+    event.set = 0;
+    callback(&event, userdata);
+}
+
+void ui_imageviewer_pressed_cb(
+        GtkGestureClick *self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->onbuttonpress) {
+        imgviewer_button_event(self, imgviewer, imgviewer->onbuttonpress, imgviewer->onbuttonpressdata);
+    }
+}
+
+void ui_imageviewer_released_cb(
+        GtkGestureClick *self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->onbuttonrelease) {
+        imgviewer_button_event(self, imgviewer, imgviewer->onbuttonrelease, imgviewer->onbuttonreleasedata);
+    }
+}
+
+#else
+
+gboolean ui_imageviewer_scroll_event(
+        GtkWidget *widget,
+        GdkEventScroll event,
+        gpointer userdata)
+{
+    // TODO
+    return FALSE;
+}
+
+gboolean ui_imageviewer_button_press_event(
+        GtkWidget *widget,
+        GdkEventButton event,
+        gpointer userdata)
+{
+    // TODO
+    return FALSE;
+}
+
+gboolean ui_imageviewer_button_release_event(
+        GtkWidget *widget,
+        GdkEventButton event,
+        gpointer userdata)
+{
+    // TODO
+    return FALSE;
+}
+
+#endif

mercurial