/* * m2gtkshim.c - small C support library for the m2-GTK4 Modula-2 bindings. * * GNU Modula-2 cannot register GObject subclasses (no class_init / * vfunc overrides), which blocks widgets and paintables that need a * custom snapshot. This shim provides two such types whose snapshot * vfuncs hand a Cairo context to a Modula-2 callback, so the drawing * itself reuses the existing Cairo binding: * * M2GtkWidget - a GtkWidget subclass; the M2 callback is * void draw(GtkWidget *w, cairo_t *cr, * int width, int height, gpointer user); * M2Paintable - a GdkPaintable implementation; the M2 callback is * void draw(cairo_t *cr, int width, int height, * gpointer user); * * The M2 callback addresses and user data are supplied at construction; * an optional destroy notify frees the user data. */ #include /* ------------------------------------------------------------------ */ /* M2GtkWidget */ /* ------------------------------------------------------------------ */ typedef void (*M2WidgetDrawFunc) (GtkWidget *widget, cairo_t *cr, int width, int height, gpointer user_data); typedef struct _M2GtkWidget M2GtkWidget; typedef struct _M2GtkWidgetClass M2GtkWidgetClass; struct _M2GtkWidget { GtkWidget parent_instance; M2WidgetDrawFunc draw; gpointer user_data; GDestroyNotify destroy; }; struct _M2GtkWidgetClass { GtkWidgetClass parent_class; }; G_DEFINE_TYPE (M2GtkWidget, m2_gtk_widget, GTK_TYPE_WIDGET) static void m2_gtk_widget_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { M2GtkWidget *self = (M2GtkWidget *) widget; int width = gtk_widget_get_width (widget); int height = gtk_widget_get_height (widget); if (self->draw == NULL || width <= 0 || height <= 0) return; graphene_rect_t bounds = GRAPHENE_RECT_INIT (0.f, 0.f, width, height); cairo_t *cr = gtk_snapshot_append_cairo (snapshot, &bounds); self->draw (widget, cr, width, height, self->user_data); cairo_destroy (cr); } static void m2_gtk_widget_finalize (GObject *object) { M2GtkWidget *self = (M2GtkWidget *) object; if (self->destroy != NULL) self->destroy (self->user_data); G_OBJECT_CLASS (m2_gtk_widget_parent_class)->finalize (object); } static void m2_gtk_widget_class_init (M2GtkWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = m2_gtk_widget_finalize; widget_class->snapshot = m2_gtk_widget_snapshot; } static void m2_gtk_widget_init (M2GtkWidget *self) { self->draw = NULL; self->user_data = NULL; self->destroy = NULL; } GtkWidget * m2_gtk_widget_new (M2WidgetDrawFunc draw, gpointer user_data, GDestroyNotify destroy) { M2GtkWidget *self = g_object_new (m2_gtk_widget_get_type (), NULL); self->draw = draw; self->user_data = user_data; self->destroy = destroy; return GTK_WIDGET (self); } /* ------------------------------------------------------------------ */ /* M2Paintable (implements GdkPaintable) */ /* ------------------------------------------------------------------ */ typedef void (*M2PaintableDrawFunc) (cairo_t *cr, int width, int height, gpointer user_data); typedef struct _M2Paintable M2Paintable; typedef struct _M2PaintableClass M2PaintableClass; struct _M2Paintable { GObject parent_instance; M2PaintableDrawFunc draw; gpointer user_data; GDestroyNotify destroy; int width; int height; }; struct _M2PaintableClass { GObjectClass parent_class; }; static void m2_paintable_snapshot (GdkPaintable *paintable, GtkSnapshot *snapshot, double width, double height) { M2Paintable *self = (M2Paintable *) paintable; graphene_rect_t bounds = GRAPHENE_RECT_INIT (0.f, 0.f, (float) width, (float) height); cairo_t *cr = gtk_snapshot_append_cairo (snapshot, &bounds); if (self->draw != NULL) self->draw (cr, (int) width, (int) height, self->user_data); cairo_destroy (cr); } static int m2_paintable_get_intrinsic_width (GdkPaintable *paintable) { return ((M2Paintable *) paintable)->width; } static int m2_paintable_get_intrinsic_height (GdkPaintable *paintable) { return ((M2Paintable *) paintable)->height; } static double m2_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { M2Paintable *self = (M2Paintable *) paintable; return self->height > 0 ? (double) self->width / (double) self->height : 0.0; } static void m2_paintable_paintable_init (GdkPaintableInterface *iface) { iface->snapshot = m2_paintable_snapshot; iface->get_intrinsic_width = m2_paintable_get_intrinsic_width; iface->get_intrinsic_height = m2_paintable_get_intrinsic_height; iface->get_intrinsic_aspect_ratio = m2_paintable_get_intrinsic_aspect_ratio; } G_DEFINE_TYPE_WITH_CODE (M2Paintable, m2_paintable, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, m2_paintable_paintable_init)) static void m2_paintable_finalize (GObject *object) { M2Paintable *self = (M2Paintable *) object; if (self->destroy != NULL) self->destroy (self->user_data); G_OBJECT_CLASS (m2_paintable_parent_class)->finalize (object); } static void m2_paintable_class_init (M2PaintableClass *klass) { G_OBJECT_CLASS (klass)->finalize = m2_paintable_finalize; } static void m2_paintable_init (M2Paintable *self) { self->draw = NULL; self->user_data = NULL; self->destroy = NULL; self->width = 0; self->height = 0; } GdkPaintable * m2_paintable_new (M2PaintableDrawFunc draw, int width, int height, gpointer user_data, GDestroyNotify destroy) { M2Paintable *self = g_object_new (m2_paintable_get_type (), NULL); self->draw = draw; self->width = width; self->height = height; self->user_data = user_data; self->destroy = destroy; return GDK_PAINTABLE (self); } /* ------------------------------------------------------------------ */ /* M2ListModel (implements GListModel) */ /* ------------------------------------------------------------------ */ typedef guint (*M2ListModelGetNItemsFunc) (gpointer user_data); typedef gpointer (*M2ListModelGetItemFunc) (guint position, gpointer user_data); typedef struct _M2ListModel M2ListModel; typedef struct _M2ListModelClass M2ListModelClass; struct _M2ListModel { GObject parent_instance; M2ListModelGetNItemsFunc get_n_items; M2ListModelGetItemFunc get_item; gpointer user_data; GDestroyNotify destroy; GType item_type; }; struct _M2ListModelClass { GObjectClass parent_class; }; static guint m2_list_model_get_n_items (GListModel *list) { M2ListModel *self = (M2ListModel *) list; return self->get_n_items != NULL ? self->get_n_items (self->user_data) : 0; } static GType m2_list_model_get_item_type (GListModel *list) { return ((M2ListModel *) list)->item_type; } static gpointer m2_list_model_get_item (GListModel *list, guint position) { M2ListModel *self = (M2ListModel *) list; return self->get_item != NULL ? self->get_item (position, self->user_data) : NULL; } static void m2_list_model_list_init (GListModelInterface *iface) { iface->get_n_items = m2_list_model_get_n_items; iface->get_item_type = m2_list_model_get_item_type; iface->get_item = m2_list_model_get_item; } G_DEFINE_TYPE_WITH_CODE (M2ListModel, m2_list_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, m2_list_model_list_init)) static void m2_list_model_finalize (GObject *object) { M2ListModel *self = (M2ListModel *) object; if (self->destroy != NULL) self->destroy (self->user_data); G_OBJECT_CLASS (m2_list_model_parent_class)->finalize (object); } static void m2_list_model_class_init (M2ListModelClass *klass) { G_OBJECT_CLASS (klass)->finalize = m2_list_model_finalize; } static void m2_list_model_init (M2ListModel *self) { self->get_n_items = NULL; self->get_item = NULL; self->user_data = NULL; self->destroy = NULL; self->item_type = G_TYPE_OBJECT; } GListModel * m2_list_model_new (M2ListModelGetNItemsFunc get_n_items, M2ListModelGetItemFunc get_item, GType item_type, gpointer user_data, GDestroyNotify destroy) { M2ListModel *self = g_object_new (m2_list_model_get_type (), NULL); self->get_n_items = get_n_items; self->get_item = get_item; self->item_type = item_type; self->user_data = user_data; self->destroy = destroy; return G_LIST_MODEL (self); } void m2_list_model_items_changed (GListModel *model, guint position, guint removed, guint added) { g_list_model_items_changed (model, position, removed, added); } /* ------------------------------------------------------------------ */ /* M2LayoutManager (subclass of GtkLayoutManager) */ /* ------------------------------------------------------------------ */ typedef void (*M2LayoutMeasureFunc) (GtkWidget *widget, int orientation, int for_size, int *minimum, int *natural, gpointer user_data); typedef void (*M2LayoutAllocateFunc) (GtkWidget *widget, int width, int height, int baseline, gpointer user_data); typedef struct _M2LayoutManager M2LayoutManager; typedef struct _M2LayoutManagerClass M2LayoutManagerClass; struct _M2LayoutManager { GtkLayoutManager parent_instance; M2LayoutMeasureFunc measure; M2LayoutAllocateFunc allocate; gpointer user_data; GDestroyNotify destroy; }; struct _M2LayoutManagerClass { GtkLayoutManagerClass parent_class; }; static void m2_layout_manager_measure (GtkLayoutManager *manager, GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { M2LayoutManager *self = (M2LayoutManager *) manager; *minimum = 0; *natural = 0; if (self->measure != NULL) self->measure (widget, (int) orientation, for_size, minimum, natural, self->user_data); *minimum_baseline = -1; *natural_baseline = -1; } static void m2_layout_manager_allocate (GtkLayoutManager *manager, GtkWidget *widget, int width, int height, int baseline) { M2LayoutManager *self = (M2LayoutManager *) manager; if (self->allocate != NULL) self->allocate (widget, width, height, baseline, self->user_data); } G_DEFINE_TYPE (M2LayoutManager, m2_layout_manager, GTK_TYPE_LAYOUT_MANAGER) static void m2_layout_manager_finalize (GObject *object) { M2LayoutManager *self = (M2LayoutManager *) object; if (self->destroy != NULL) self->destroy (self->user_data); G_OBJECT_CLASS (m2_layout_manager_parent_class)->finalize (object); } static void m2_layout_manager_class_init (M2LayoutManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkLayoutManagerClass *manager_class = GTK_LAYOUT_MANAGER_CLASS (klass); object_class->finalize = m2_layout_manager_finalize; manager_class->measure = m2_layout_manager_measure; manager_class->allocate = m2_layout_manager_allocate; } static void m2_layout_manager_init (M2LayoutManager *self) { self->measure = NULL; self->allocate = NULL; self->user_data = NULL; self->destroy = NULL; } GtkLayoutManager * m2_layout_manager_new (M2LayoutMeasureFunc measure, M2LayoutAllocateFunc allocate, gpointer user_data, GDestroyNotify destroy) { M2LayoutManager *self = g_object_new (m2_layout_manager_get_type (), NULL); self->measure = measure; self->allocate = allocate; self->user_data = user_data; self->destroy = destroy; return GTK_LAYOUT_MANAGER (self); } /* Position a child at (x, y) with the given size (helper for allocate). */ void m2_layout_allocate_child (GtkWidget *child, int x, int y, int width, int height) { graphene_point_t point = GRAPHENE_POINT_INIT (x, y); GskTransform *transform = gsk_transform_translate (NULL, &point); gtk_widget_allocate (child, width, height, -1, transform); gsk_transform_unref (transform); } /* ------------------------------------------------------------------ */ /* M2SnapshotPaintable (GdkPaintable using GtkSnapshot) */ /* ------------------------------------------------------------------ */ typedef void (*M2SnapshotDrawFunc) (GtkSnapshot *snapshot, int width, int height, gpointer user_data); typedef struct _M2SnapshotPaintable M2SnapshotPaintable; typedef struct _M2SnapshotPaintableClass M2SnapshotPaintableClass; struct _M2SnapshotPaintable { GObject parent_instance; M2SnapshotDrawFunc draw; gpointer user_data; GDestroyNotify destroy; int width; int height; }; struct _M2SnapshotPaintableClass { GObjectClass parent_class; }; static void m2_snapshot_paintable_snapshot (GdkPaintable *paintable, GtkSnapshot *snapshot, double width, double height) { M2SnapshotPaintable *self = (M2SnapshotPaintable *) paintable; if (self->draw != NULL) self->draw (snapshot, (int) width, (int) height, self->user_data); } static int m2_snapshot_paintable_get_intrinsic_width (GdkPaintable *paintable) { return ((M2SnapshotPaintable *) paintable)->width; } static int m2_snapshot_paintable_get_intrinsic_height (GdkPaintable *paintable) { return ((M2SnapshotPaintable *) paintable)->height; } static double m2_snapshot_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { M2SnapshotPaintable *self = (M2SnapshotPaintable *) paintable; return self->height > 0 ? (double) self->width / (double) self->height : 0.0; } static void m2_snapshot_paintable_paintable_init (GdkPaintableInterface *iface) { iface->snapshot = m2_snapshot_paintable_snapshot; iface->get_intrinsic_width = m2_snapshot_paintable_get_intrinsic_width; iface->get_intrinsic_height = m2_snapshot_paintable_get_intrinsic_height; iface->get_intrinsic_aspect_ratio = m2_snapshot_paintable_get_intrinsic_aspect_ratio; } G_DEFINE_TYPE_WITH_CODE (M2SnapshotPaintable, m2_snapshot_paintable, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, m2_snapshot_paintable_paintable_init)) static void m2_snapshot_paintable_finalize (GObject *object) { M2SnapshotPaintable *self = (M2SnapshotPaintable *) object; if (self->destroy != NULL) self->destroy (self->user_data); G_OBJECT_CLASS (m2_snapshot_paintable_parent_class)->finalize (object); } static void m2_snapshot_paintable_class_init (M2SnapshotPaintableClass *klass) { G_OBJECT_CLASS (klass)->finalize = m2_snapshot_paintable_finalize; } static void m2_snapshot_paintable_init (M2SnapshotPaintable *self) { self->draw = NULL; self->user_data = NULL; self->destroy = NULL; self->width = 0; self->height = 0; } GdkPaintable * m2_snapshot_paintable_new (M2SnapshotDrawFunc draw, int width, int height, gpointer user_data, GDestroyNotify destroy) { M2SnapshotPaintable *self = g_object_new (m2_snapshot_paintable_get_type (), NULL); self->draw = draw; self->width = width; self->height = height; self->user_data = user_data; self->destroy = destroy; return GDK_PAINTABLE (self); }