Bladeren bron

feat: expressions, constraints, shortcuts, Graphene/GtkSnapshot

Expressions and sorters/filters:
- GtkExpression (property/object expressions)
- GtkStringSorter, GtkNumericSorter, GtkStringFilter, GtkBoolFilter
- test_expr (sorts and filters a GtkStringList through the models)

Constraints:
- GtkConstraintLayout, GtkConstraint (attributes/relations/strengths),
  GtkConstraintGuide
- test_constraints

Shortcuts:
- GtkShortcut (with trigger/action parsing and named actions),
  GtkShortcutController
- test_shortcuts

Graphene / GtkSnapshot:
- Graphene (Point/Point3D/Size/Rect/Matrix + init)
- GtkSnapshot (append_color/append_cairo, transforms, to_paintable)
- shim: m2_snapshot_paintable_new (snapshot-based GdkPaintable)
- test_snapshot, examples/snapshot.mod

39 tests pass; 156 src modules, 26 examples.
Eric Streit 2 dagen geleden
bovenliggende
commit
0a7848e256

+ 2 - 2
Makefile

@@ -20,8 +20,8 @@ OBJS    := $(OBJDIR)/GtkUtils.o $(OBJDIR)/GtkClosures.o $(OBJDIR)/m2gtkshim.o
 
 INCLUDES := -I$(SRC_DIR) -I$(LIB_DIR)
 
-TESTS    := test_glib test_version test_widgets test_inputs test_inputs2 test_containers test_containers2 test_actions test_closure test_headerbar test_gio test_gerror test_filedialog test_views test_builder test_utility test_css test_popover test_dialogs test_models test_cairo test_drawing test_gestures test_models2 test_widgets3 test_shim test_shim2 test_shim3 test_dnd test_media test_gl test_print test_text test_application
-EXAMPLES := hello counter inputs containers actions headerbar editor layouts files views builder feedback styling popover dialogs drawing demo gestures customwidget customlayout media clipboard gl richtext
+TESTS    := test_glib test_version test_widgets test_inputs test_inputs2 test_containers test_containers2 test_actions test_closure test_headerbar test_gio test_gerror test_filedialog test_views test_builder test_utility test_css test_popover test_dialogs test_models test_cairo test_drawing test_gestures test_models2 test_widgets3 test_shim test_shim2 test_shim3 test_dnd test_media test_gl test_print test_text test_expr test_constraints test_shortcuts test_snapshot test_application
+EXAMPLES := hello counter inputs containers actions headerbar editor layouts files views builder feedback styling popover dialogs drawing demo gestures customwidget customlayout media clipboard gl richtext snapshot
 
 # Include the libadwaita test/example only when libadwaita is installed.
 ifneq ($(ADWLIBS),)

+ 13 - 5
README.md

@@ -14,15 +14,15 @@ Verified with `gm2` 16.0.1 (experimental), GTK 4.18.6 and libadwaita 1.7.6.
 
 ## Features
 
-- **144 hand-written binding modules** across GLib, GObject, Gio, GTK4
+- **156 hand-written binding modules** across GLib, GObject, Gio, GTK4
   and libadwaita — windows, widgets, inputs, containers, model-backed
   views, actions/menus/accelerators, dialogs, CSS, drawing (Cairo/Pango),
-  rich text, gestures, drag-and-drop, media, OpenGL, printing, settings,
-  and more.
+  rich text, gestures, drag-and-drop, media, OpenGL, printing,
+  expressions/constraints/shortcuts, settings, and more.
 - **Pure-Modula-2 helpers**: `GtkUtils` (string/number conversion,
   `Connect`, `SetAccel`, ownership and `GError` helpers) and
   `GtkClosures` (per-connection signal contexts with cleanup).
-- **34 tests** and **24 runnable examples**.
+- **39 tests** and **26 runnable examples**.
 - **Generator + verifier**: `tools/gir2def.py` turns a `.gir` file into
   `.def` files; `tools/gir_verify.py` checks every generated identifier
   against the installed library. Verified clean across 8602 identifiers.
@@ -125,6 +125,8 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `customwidget` | custom `GtkWidget` and `GdkPaintable` via the C shim |
 | `customlayout` | custom `GtkLayoutManager` via the C shim |
 | `gl` | OpenGL clearing inside a `GtkGLArea` |
+| `richtext` | Pango markup, marks and entry completion in a text view |
+| `snapshot` | custom `GdkPaintable` drawn with `GtkSnapshot` + Graphene |
 | `media` | `GdkTexture` in a `GtkPicture`, plus `GtkVideo` |
 | `clipboard` | copy/paste text through `GdkClipboard` |
 | `adw` | libadwaita preferences app (toolbar view, rows, toast, about) |
@@ -153,6 +155,10 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `GtkStringList`, `GtkStringObject` | string model / item |
 | `GMenu`, `GMenuModel`, `GSimpleAction`, `GAction`, `GActionMap` | menus and actions |
 | `GtkFilterListModel`, `GtkCustomFilter`, `GtkSortListModel`, `GtkCustomSorter` | M2-callback filter/sort models |
+| `GtkExpression`, `GtkStringSorter`, `GtkNumericSorter`, `GtkStringFilter`, `GtkBoolFilter` | expression-based sorters/filters |
+| `GtkConstraintLayout`, `GtkConstraint`, `GtkConstraintGuide` | constraint layout |
+| `GtkShortcut`, `GtkShortcutController` | keyboard shortcuts |
+| `Graphene`, `GtkSnapshot` | geometry records and snapshot rendering |
 | `GtkMultiSelection`, `GtkBitset` | multiple selection and bitsets |
 | `GtkTreeListModel`, `GtkTreeListRow`, `GtkTreeExpander` | tree models and expanders |
 | `GtkDirectoryList`, `GtkBookmarkList` | directory / bookmark models |
@@ -171,6 +177,8 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `GtkBox`, `GtkGrid`, `GtkFrame`, `GtkExpander`, `GtkPaned`, `GtkOverlay`, `GtkNotebook`, `GtkStack`, `GtkStackSwitcher`, `GtkScrolledWindow` | layout containers |
 | `GtkLabel`, `GtkButton`, `GtkCheckButton`, `GtkSwitch`, `GtkScale`, `GtkRange`, `GtkSpinButton`, `GtkEntry`, `GtkEditable`, `GtkSearchEntry`, `GtkDropDown` | controls |
 | `GtkTextView`, `GtkTextBuffer`, `GtkTextIter` | multi-line text |
+| `GtkTextTag`, `GtkTextTagTable`, `GtkTextMark`, `GtkEntryBuffer` | rich-text tags, marks and entry buffer |
+| `GtkEntryCompletion` | entry completion (deprecated; GtkTreeModel-based) |
 | `GtkAdjustment`, `GtkProgressBar`, `GtkSpinner`, `GtkSeparator`, `GtkImage` | value model and feedback/decoration widgets |
 | `GtkHeaderBar`, `GtkMenuButton`, `GtkPopoverMenuBar` | window chrome and menus |
 | `GtkPopover`, `GtkPopoverMenu` | popovers and menu popovers |
@@ -201,7 +209,7 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 |---|---|
 | `GtkUtils` | `CStrToM2`, `StrAppend`, `IntToStr`, `Connect`, `SetAccel`, `Unref`, `Free`, `ErrorMessage`, `ClearError` |
 | `GtkClosures` | `Connect2`/`Connect3` (and owned variants) for per-connection signal state |
-| `M2GtkShim` (`lib/m2gtkshim.c`) | C shim for GObject subclassing: a custom `GtkWidget`, a `GdkPaintable`, a generic `GListModel`, and a custom `GtkLayoutManager`, all driven by Modula-2 callbacks |
+| `M2GtkShim` (`lib/m2gtkshim.c`) | C shim for GObject subclassing: a custom `GtkWidget`, `GdkPaintable`s (Cairo- and snapshot-based), a generic `GListModel`, and a custom `GtkLayoutManager`, all driven by Modula-2 callbacks |
 
 ## Signals, ownership and errors
 

+ 80 - 0
examples/snapshot.mod

@@ -0,0 +1,80 @@
+MODULE snapshot ;
+
+(*
+   m2-GTK4 example - a custom GdkPaintable drawn with GtkSnapshot and
+   Graphene from Modula-2 (via the shim).
+
+   Build:  make examples
+   Run:    ./build/examples/snapshot
+*)
+
+FROM Gio IMPORT g_application_run;
+FROM GtkApplication IMPORT gtk_application_new;
+FROM GtkWindow IMPORT gtk_application_window_new, gtk_window_set_title,
+   gtk_window_set_default_size, gtk_window_set_child, gtk_window_present;
+FROM GtkPicture IMPORT gtk_picture_new, gtk_picture_set_paintable;
+FROM GtkWidget IMPORT gtk_widget_set_size_request;
+FROM M2GtkShim IMPORT m2_snapshot_paintable_new;
+FROM GtkSnapshot IMPORT gtk_snapshot_append_color,
+   gtk_snapshot_append_cairo;
+FROM Graphene IMPORT GrapheneRect, graphene_rect_init;
+FROM Gdk IMPORT GdkRGBA;
+FROM Cairo IMPORT cairo_set_source_rgb, cairo_arc, cairo_fill, cairo_destroy;
+FROM GtkClosures IMPORT Connect2;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.Snapshot";
+
+PROCEDURE Draw (snapshot: ADDRESS; width, height: INTEGER; user: ADDRESS);
+VAR
+   rect: GrapheneRect;
+   color: GdkRGBA;
+   cr: ADDRESS;
+BEGIN
+   graphene_rect_init(rect, 0.0, 0.0, VAL(REAL, width), VAL(REAL, height));
+
+   color.red := 0.12;
+   color.green := 0.16;
+   color.blue := 0.22;
+   color.alpha := 1.0;
+   gtk_snapshot_append_color(snapshot, color, rect);
+
+   cr := gtk_snapshot_append_cairo(snapshot, rect);
+   IF cr # NIL THEN
+      cairo_set_source_rgb(cr, 0.20, 0.60, 0.90);
+      cairo_arc(cr, VAL(REAL, width) / 2.0, VAL(REAL, height) / 2.0, 60.0,
+                0.0, 6.2831853);
+      cairo_fill(cr);
+      cairo_destroy(cr)
+   END
+END Draw;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, picture, paintable: ADDRESS;
+BEGIN
+   paintable := m2_snapshot_paintable_new(ADR(Draw), 240, 160, NIL, NIL);
+   picture := gtk_picture_new();
+   gtk_picture_set_paintable(picture, paintable);
+   gtk_widget_set_size_request(picture, 240, 160);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_title(window, "m2-GTK4 Snapshot");
+   gtk_window_set_default_size(window, 280, 200);
+   gtk_window_set_child(window, picture);
+   gtk_window_present(window)
+END OnActivate;
+
+VAR
+   app: ADDRESS;
+BEGIN
+   app := gtk_application_new(AppId, 0);
+   IF app = NIL THEN
+      printf("gtk_application_new failed\n");
+      HALT(1)
+   END;
+   Connect2(app, "activate", OnActivate, NIL);
+   g_application_run(app, 0, NIL)
+END snapshot.

+ 13 - 0
lib/M2GtkShim.def

@@ -31,7 +31,9 @@ EXPORT UNQUALIFIED
    M2Paintable, M2PaintableDrawFunc,
    M2ListModel, M2ListModelGetNItemsFunc, M2ListModelGetItemFunc,
    M2LayoutManager, M2LayoutMeasureFunc, M2LayoutAllocateFunc,
+   M2SnapshotPaintable, M2SnapshotDrawFunc,
    m2_gtk_widget_new, m2_paintable_new,
+   m2_snapshot_paintable_new,
    m2_list_model_new, m2_list_model_items_changed,
    m2_layout_manager_new, m2_layout_allocate_child;
 
@@ -54,6 +56,9 @@ TYPE
    (* PROCEDURE (widget: ADDRESS; width, height, baseline: INTEGER;
                    user: ADDRESS) *)
    M2LayoutAllocateFunc = ADDRESS;
+   M2SnapshotPaintable = ADDRESS;
+   (* PROCEDURE (snapshot: ADDRESS; width, height: INTEGER; user: ADDRESS) *)
+   M2SnapshotDrawFunc = ADDRESS;
 
 (* GtkWidget *m2_gtk_widget_new(M2WidgetDrawFunc draw, gpointer user_data,
                                 GDestroyNotify destroy) *)
@@ -66,6 +71,14 @@ PROCEDURE m2_gtk_widget_new (draw: M2WidgetDrawFunc; user_data: ADDRESS;
 PROCEDURE m2_paintable_new (draw: M2PaintableDrawFunc; width, height: INTEGER;
                             user_data: ADDRESS; destroy: ADDRESS) : M2Paintable;
 
+(* GdkPaintable *m2_snapshot_paintable_new(M2SnapshotDrawFunc draw, int width,
+                                           int height, gpointer user_data,
+                                           GDestroyNotify destroy) *)
+PROCEDURE m2_snapshot_paintable_new (draw: M2SnapshotDrawFunc;
+                                     width, height: INTEGER;
+                                     user_data: ADDRESS;
+                                     destroy: ADDRESS) : M2SnapshotPaintable;
+
 (* GListModel *m2_list_model_new(M2ListModelGetNItemsFunc get_n_items,
                                  M2ListModelGetItemFunc get_item,
                                  GType item_type, gpointer user_data,

+ 111 - 0
lib/m2gtkshim.c

@@ -436,3 +436,114 @@ m2_layout_allocate_child (GtkWidget *child, int x, int y, int width, int height)
   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);
+}

+ 50 - 0
src/Graphene.def

@@ -0,0 +1,50 @@
+DEFINITION MODULE FOR "C" Graphene ;
+
+(*
+   m2-GTK4 - Graphene geometry value types.  Mapped onto <graphene.h>.
+
+   These are plain by-value structs, used by GtkSnapshot and
+   GtkConstraintGuide.  The init helpers return a pointer to the struct
+   (ignored here); pass the VAR record directly.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GraphenePoint, GraphenePoint3D, GrapheneSize, GrapheneRect,
+   GrapheneMatrix,
+   graphene_point_init, graphene_size_init, graphene_rect_init;
+
+
+TYPE
+   GraphenePoint = RECORD
+      x, y: SHORTREAL;
+   END;
+   GraphenePoint3D = RECORD
+      x, y, z: SHORTREAL;
+   END;
+   GrapheneSize = RECORD
+      width, height: SHORTREAL;
+   END;
+   GrapheneRect = RECORD
+      origin: GraphenePoint;
+      size: GrapheneSize;
+   END;
+   GrapheneMatrix = RECORD
+      xx, yx, xy, yy, x0, y0: SHORTREAL;
+   END;
+
+(* graphene_point_t *graphene_point_init(graphene_point_t *p,
+                                           float x, float y) *)
+PROCEDURE graphene_point_init (VAR p: GraphenePoint; x, y: SHORTREAL) : [ ADDRESS ];
+
+(* graphene_size_t *graphene_size_init(graphene_size_t *s,
+                                        float width, float height) *)
+PROCEDURE graphene_size_init (VAR s: GrapheneSize;
+                              width, height: SHORTREAL) : [ ADDRESS ];
+
+(* graphene_rect_t *graphene_rect_init(graphene_rect_t *r, float x, float y,
+                                        float width, float height) *)
+PROCEDURE graphene_rect_init (VAR r: GrapheneRect; x, y, width, height: SHORTREAL) : [ ADDRESS ];
+
+END Graphene.

+ 35 - 0
src/GtkBoolFilter.def

@@ -0,0 +1,35 @@
+DEFINITION MODULE FOR "C" GtkBoolFilter ;
+
+(*
+   m2-GTK4 - GtkBoolFilter: a filter matching a boolean expression.
+   Mapped onto <gtk/gtkboolfilter.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkBoolFilter,
+   gtk_bool_filter_new,
+   gtk_bool_filter_set_expression, gtk_bool_filter_get_expression,
+   gtk_bool_filter_set_invert, gtk_bool_filter_get_invert;
+
+
+TYPE
+   GtkBoolFilter = ADDRESS;
+
+(* GtkBoolFilter *gtk_bool_filter_new(GtkExpression *expression) *)
+PROCEDURE gtk_bool_filter_new (expression: ADDRESS) : GtkBoolFilter;
+
+(* void gtk_bool_filter_set_expression(GtkBoolFilter *self, GtkExpression *expression) *)
+PROCEDURE gtk_bool_filter_set_expression (self: GtkBoolFilter; expression: ADDRESS);
+
+(* GtkExpression *gtk_bool_filter_get_expression(GtkBoolFilter *self) *)
+PROCEDURE gtk_bool_filter_get_expression (self: GtkBoolFilter) : ADDRESS;
+
+(* void gtk_bool_filter_set_invert(GtkBoolFilter *self, gboolean invert) *)
+PROCEDURE gtk_bool_filter_set_invert (self: GtkBoolFilter; invert: INTEGER);
+
+(* gboolean gtk_bool_filter_get_invert(GtkBoolFilter *self) *)
+PROCEDURE gtk_bool_filter_get_invert (self: GtkBoolFilter) : [ INTEGER ];
+
+END GtkBoolFilter.

+ 83 - 0
src/GtkConstraint.def

@@ -0,0 +1,83 @@
+DEFINITION MODULE FOR "C" GtkConstraint ;
+
+(*
+   m2-GTK4 - GtkConstraint: one linear constraint for GtkConstraintLayout.
+   Mapped onto <gtk/gtkconstraint.h>.
+
+   Attributes and relations are CARDINAL constants (matching
+   GtkConstraintAttribute / GtkConstraintRelation); strengths are the
+   usual GTK_CONSTRAINT_STRENGTH_* values.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkConstraint, GtkConstraintAttribute, GtkConstraintRelation,
+   GtkConstraintStrength,
+   GtkConstraintAttributeNone, GtkConstraintAttributeLeft,
+   GtkConstraintAttributeRight, GtkConstraintAttributeTop,
+   GtkConstraintAttributeBottom, GtkConstraintAttributeStart,
+   GtkConstraintAttributeEnd, GtkConstraintAttributeWidth,
+   GtkConstraintAttributeHeight, GtkConstraintAttributeCenterX,
+   GtkConstraintAttributeCenterY, GtkConstraintAttributeBaseline,
+   GtkConstraintRelationLe, GtkConstraintRelationEq, GtkConstraintRelationGe,
+   GtkConstraintStrengthRequired, GtkConstraintStrengthStrong,
+   GtkConstraintStrengthMedium, GtkConstraintStrengthWeak,
+   gtk_constraint_new, gtk_constraint_new_constant;
+
+
+TYPE
+   GtkConstraint = ADDRESS;
+   GtkConstraintAttribute = CARDINAL;
+   GtkConstraintRelation = INTEGER;
+   GtkConstraintStrength = INTEGER;
+
+CONST
+   GtkConstraintAttributeNone   = 0;
+   GtkConstraintAttributeLeft   = 1;
+   GtkConstraintAttributeRight  = 2;
+   GtkConstraintAttributeTop    = 3;
+   GtkConstraintAttributeBottom = 4;
+   GtkConstraintAttributeStart  = 5;
+   GtkConstraintAttributeEnd    = 6;
+   GtkConstraintAttributeWidth  = 7;
+   GtkConstraintAttributeHeight = 8;
+   GtkConstraintAttributeCenterX = 9;
+   GtkConstraintAttributeCenterY = 10;
+   GtkConstraintAttributeBaseline = 11;
+
+   GtkConstraintRelationLe = -1;
+   GtkConstraintRelationEq = 0;
+   GtkConstraintRelationGe = 1;
+
+   GtkConstraintStrengthRequired = 1001001000;
+   GtkConstraintStrengthStrong   = 1000000000;
+   GtkConstraintStrengthMedium   = 1000;
+   GtkConstraintStrengthWeak     = 1;
+
+(* GtkConstraint *gtk_constraint_new(gpointer target,
+                                      GtkConstraintAttribute target_attribute,
+                                      GtkConstraintRelation relation,
+                                      gpointer source,
+                                      GtkConstraintAttribute source_attribute,
+                                      double multiplier, double constant,
+                                      int strength) *)
+PROCEDURE gtk_constraint_new (target: ADDRESS;
+                              target_attribute: GtkConstraintAttribute;
+                              relation: GtkConstraintRelation;
+                              source: ADDRESS;
+                              source_attribute: GtkConstraintAttribute;
+                              multiplier, constant: REAL;
+                              strength: GtkConstraintStrength) : GtkConstraint;
+
+(* GtkConstraint *gtk_constraint_new_constant(gpointer target,
+                                               GtkConstraintAttribute target_attribute,
+                                               GtkConstraintRelation relation,
+                                               double constant, int strength) *)
+PROCEDURE gtk_constraint_new_constant (target: ADDRESS;
+                                       target_attribute: GtkConstraintAttribute;
+                                       relation: GtkConstraintRelation;
+                                       constant: REAL;
+                                       strength: GtkConstraintStrength) : GtkConstraint;
+
+END GtkConstraint.

+ 64 - 0
src/GtkConstraintGuide.def

@@ -0,0 +1,64 @@
+DEFINITION MODULE FOR "C" GtkConstraintGuide ;
+
+(*
+   m2-GTK4 - GtkConstraintGuide: an invisible sizing guide for
+   GtkConstraintLayout.  Mapped onto <gtk/gtkconstraintguide.h>.
+
+   Sizes are GrapheneSize records (VAR); the strength is one of the
+   GtkConstraintStrength values (see GtkConstraint.def).
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkConstraintGuide,
+   gtk_constraint_guide_new,
+   gtk_constraint_guide_set_min_size, gtk_constraint_guide_get_min_size,
+   gtk_constraint_guide_set_nat_size, gtk_constraint_guide_get_nat_size,
+   gtk_constraint_guide_set_max_size, gtk_constraint_guide_get_max_size,
+   gtk_constraint_guide_set_strength,
+   gtk_constraint_guide_set_name, gtk_constraint_guide_get_name;
+
+
+TYPE
+   GtkConstraintGuide = ADDRESS;
+
+(* GtkConstraintGuide *gtk_constraint_guide_new(void) *)
+PROCEDURE gtk_constraint_guide_new () : GtkConstraintGuide;
+
+(* void gtk_constraint_guide_set_min_size(GtkConstraintGuide *guide,
+                                           int width, int height) *)
+PROCEDURE gtk_constraint_guide_set_min_size (guide: GtkConstraintGuide;
+                                             width, height: INTEGER);
+
+(* void gtk_constraint_guide_get_min_size(GtkConstraintGuide *guide,
+                                           int *width, int *height) *)
+PROCEDURE gtk_constraint_guide_get_min_size (guide: GtkConstraintGuide;
+                                             VAR width, height: INTEGER);
+
+(* void gtk_constraint_guide_set_nat_size(GtkConstraintGuide *guide,
+                                           int width, int height) *)
+PROCEDURE gtk_constraint_guide_set_nat_size (guide: GtkConstraintGuide;
+                                             width, height: INTEGER);
+PROCEDURE gtk_constraint_guide_get_nat_size (guide: GtkConstraintGuide;
+                                             VAR width, height: INTEGER);
+
+(* void gtk_constraint_guide_set_max_size(GtkConstraintGuide *guide,
+                                           int width, int height) *)
+PROCEDURE gtk_constraint_guide_set_max_size (guide: GtkConstraintGuide;
+                                             width, height: INTEGER);
+PROCEDURE gtk_constraint_guide_get_max_size (guide: GtkConstraintGuide;
+                                             VAR width, height: INTEGER);
+
+(* void gtk_constraint_guide_set_strength(GtkConstraintGuide *guide, int strength) *)
+PROCEDURE gtk_constraint_guide_set_strength (guide: GtkConstraintGuide;
+                                             strength: INTEGER);
+
+(* void gtk_constraint_guide_set_name(GtkConstraintGuide *guide, const char *name) *)
+PROCEDURE gtk_constraint_guide_set_name (guide: GtkConstraintGuide;
+                                         name: ARRAY OF CHAR);
+
+(* const char *gtk_constraint_guide_get_name(GtkConstraintGuide *guide) *)
+PROCEDURE gtk_constraint_guide_get_name (guide: GtkConstraintGuide) : ADDRESS;
+
+END GtkConstraintGuide.

+ 52 - 0
src/GtkConstraintLayout.def

@@ -0,0 +1,52 @@
+DEFINITION MODULE FOR "C" GtkConstraintLayout ;
+
+(*
+   m2-GTK4 - GtkConstraintLayout: a layout manager solving a system of
+   constraints.  Mapped onto <gtk/gtkconstraintlayout.h>.
+
+   Install it with GtkWidget.gtk_widget_set_layout_manager(), then add
+   GtkConstraint and GtkConstraintGuide objects.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkConstraintLayout,
+   gtk_constraint_layout_new,
+   gtk_constraint_layout_add_constraint,
+   gtk_constraint_layout_remove_constraint,
+   gtk_constraint_layout_add_guide,
+   gtk_constraint_layout_remove_guide,
+   gtk_constraint_layout_remove_all_constraints;
+
+
+TYPE
+   GtkConstraintLayout = ADDRESS;
+
+(* GtkLayoutManager *gtk_constraint_layout_new(void) *)
+PROCEDURE gtk_constraint_layout_new () : GtkConstraintLayout;
+
+(* void gtk_constraint_layout_add_constraint(GtkConstraintLayout *layout,
+                                              GtkConstraint *constraint) *)
+PROCEDURE gtk_constraint_layout_add_constraint (layout: GtkConstraintLayout;
+                                                constraint: ADDRESS);
+
+(* void gtk_constraint_layout_remove_constraint(GtkConstraintLayout *layout,
+                                                 GtkConstraint *constraint) *)
+PROCEDURE gtk_constraint_layout_remove_constraint (layout: GtkConstraintLayout;
+                                                   constraint: ADDRESS);
+
+(* void gtk_constraint_layout_add_guide(GtkConstraintLayout *layout,
+                                         GtkConstraintGuide *guide) *)
+PROCEDURE gtk_constraint_layout_add_guide (layout: GtkConstraintLayout;
+                                           guide: ADDRESS);
+
+(* void gtk_constraint_layout_remove_guide(GtkConstraintLayout *layout,
+                                            GtkConstraintGuide *guide) *)
+PROCEDURE gtk_constraint_layout_remove_guide (layout: GtkConstraintLayout;
+                                              guide: ADDRESS);
+
+(* void gtk_constraint_layout_remove_all_constraints(GtkConstraintLayout *layout) *)
+PROCEDURE gtk_constraint_layout_remove_all_constraints (layout: GtkConstraintLayout);
+
+END GtkConstraintLayout.

+ 54 - 0
src/GtkExpression.def

@@ -0,0 +1,54 @@
+DEFINITION MODULE FOR "C" GtkExpression ;
+
+(*
+   m2-GTK4 - GtkExpression: a deferred property lookup, used by the
+   GTK sorters and filters.  Mapped onto <gtk/gtkexpression.h>.
+
+   The common case is a property of the list item type, e.g. the "string"
+   property of a GtkStringObject:
+
+      expr := gtk_property_expression_new(gtk_string_object_get_type(),
+                                          NIL, "string", NIL);
+
+   Create the expression once, use it in a sorter/filter, then release it
+   with gtk_expression_unref (the sorter/filter keeps its own reference).
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkExpression,
+   gtk_property_expression_new, gtk_property_expression_new_for_pspec,
+   gtk_object_expression_new,
+   gtk_expression_ref, gtk_expression_unref,
+   gtk_expression_get_value_type;
+
+
+TYPE
+   GtkExpression = ADDRESS;
+
+(* GtkExpression *gtk_property_expression_new(GType this_type,
+                                              GtkExpression *expression,
+                                              const char *property_name, ...) *)
+PROCEDURE gtk_property_expression_new (this_type: LONGCARD; expression: ADDRESS;
+                                       property_name: ARRAY OF CHAR;
+                                       ...) : GtkExpression;
+
+(* GtkExpression *gtk_property_expression_new_for_pspec(GtkExpression *expression,
+                                                         GParamSpec *pspec) *)
+PROCEDURE gtk_property_expression_new_for_pspec (expression: ADDRESS;
+                                                 pspec: ADDRESS) : GtkExpression;
+
+(* GtkExpression *gtk_object_expression_new(GObject *object) *)
+PROCEDURE gtk_object_expression_new (object: ADDRESS) : GtkExpression;
+
+(* GtkExpression *gtk_expression_ref(GtkExpression *expression) *)
+PROCEDURE gtk_expression_ref (expression: GtkExpression) : GtkExpression;
+
+(* void gtk_expression_unref(GtkExpression *expression) *)
+PROCEDURE gtk_expression_unref (expression: GtkExpression);
+
+(* GType gtk_expression_get_value_type(GtkExpression *expression) *)
+PROCEDURE gtk_expression_get_value_type (expression: GtkExpression) : LONGCARD;
+
+END GtkExpression.

+ 40 - 0
src/GtkNumericSorter.def

@@ -0,0 +1,40 @@
+DEFINITION MODULE FOR "C" GtkNumericSorter ;
+
+(*
+   m2-GTK4 - GtkNumericSorter: a sorter comparing numeric expressions.
+   Mapped onto <gtk/gtknumericsorter.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkNumericSorter, GtkSortType, GtkSortAscending, GtkSortDescending,
+   gtk_numeric_sorter_new,
+   gtk_numeric_sorter_set_expression, gtk_numeric_sorter_get_expression,
+   gtk_numeric_sorter_set_sort_order, gtk_numeric_sorter_get_sort_order;
+
+
+TYPE
+   GtkNumericSorter = ADDRESS;
+   GtkSortType = CARDINAL;
+
+CONST
+   GtkSortAscending  = 0;
+   GtkSortDescending = 1;
+
+(* GtkNumericSorter *gtk_numeric_sorter_new(GtkExpression *expression) *)
+PROCEDURE gtk_numeric_sorter_new (expression: ADDRESS) : GtkNumericSorter;
+
+(* void gtk_numeric_sorter_set_expression(GtkNumericSorter *self, GtkExpression *expression) *)
+PROCEDURE gtk_numeric_sorter_set_expression (self: GtkNumericSorter; expression: ADDRESS);
+
+(* GtkExpression *gtk_numeric_sorter_get_expression(GtkNumericSorter *self) *)
+PROCEDURE gtk_numeric_sorter_get_expression (self: GtkNumericSorter) : ADDRESS;
+
+(* void gtk_numeric_sorter_set_sort_order(GtkNumericSorter *self, GtkSortType sort_order) *)
+PROCEDURE gtk_numeric_sorter_set_sort_order (self: GtkNumericSorter; sort_order: GtkSortType);
+
+(* GtkSortType gtk_numeric_sorter_get_sort_order(GtkNumericSorter *self) *)
+PROCEDURE gtk_numeric_sorter_get_sort_order (self: GtkNumericSorter) : GtkSortType;
+
+END GtkNumericSorter.

+ 61 - 0
src/GtkShortcut.def

@@ -0,0 +1,61 @@
+DEFINITION MODULE FOR "C" GtkShortcut ;
+
+(*
+   m2-GTK4 - GtkShortcut with its triggers and actions.  Mapped onto
+   <gtk/gtkshortcut.h>, <gtk/gtkshortcuttrigger.h> and
+   <gtk/gtknamedaction.h>.
+
+   A shortcut pairs a trigger (parsed from a string like "<Control>q")
+   with an action (e.g. a named application action):
+
+      trigger := gtk_shortcut_trigger_parse_string("<Control>q");
+      action  := gtk_named_action_new("app.quit");
+      shortcut := gtk_shortcut_new(trigger, action);
+
+   Add it to a GtkShortcutController.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkShortcut, GtkShortcutTrigger, GtkShortcutAction, GtkNamedAction,
+   gtk_shortcut_new,
+   gtk_shortcut_set_trigger, gtk_shortcut_get_trigger,
+   gtk_shortcut_set_action, gtk_shortcut_get_action,
+   gtk_shortcut_trigger_parse_string,
+   gtk_shortcut_action_parse_string,
+   gtk_named_action_new;
+
+
+TYPE
+   GtkShortcut = ADDRESS;
+   GtkShortcutTrigger = ADDRESS;
+   GtkShortcutAction = ADDRESS;
+   GtkNamedAction = ADDRESS;
+
+(* GtkShortcut *gtk_shortcut_new(GtkShortcutTrigger *trigger,
+                                 GtkShortcutAction *action) *)
+PROCEDURE gtk_shortcut_new (trigger: ADDRESS; action: ADDRESS) : GtkShortcut;
+
+(* void gtk_shortcut_set_trigger(GtkShortcut *self, GtkShortcutTrigger *trigger) *)
+PROCEDURE gtk_shortcut_set_trigger (self: GtkShortcut; trigger: ADDRESS);
+
+(* GtkShortcutTrigger *gtk_shortcut_get_trigger(GtkShortcut *self) *)
+PROCEDURE gtk_shortcut_get_trigger (self: GtkShortcut) : ADDRESS;
+
+(* void gtk_shortcut_set_action(GtkShortcut *self, GtkShortcutAction *action) *)
+PROCEDURE gtk_shortcut_set_action (self: GtkShortcut; action: ADDRESS);
+
+(* GtkShortcutAction *gtk_shortcut_get_action(GtkShortcut *self) *)
+PROCEDURE gtk_shortcut_get_action (self: GtkShortcut) : ADDRESS;
+
+(* GtkShortcutTrigger *gtk_shortcut_trigger_parse_string(const char *string) *)
+PROCEDURE gtk_shortcut_trigger_parse_string (string: ARRAY OF CHAR) : GtkShortcutTrigger;
+
+(* GtkShortcutAction *gtk_shortcut_action_parse_string(const char *string) *)
+PROCEDURE gtk_shortcut_action_parse_string (string: ARRAY OF CHAR) : GtkShortcutAction;
+
+(* GtkShortcutAction *gtk_named_action_new(const char *name) *)
+PROCEDURE gtk_named_action_new (name: ARRAY OF CHAR) : GtkNamedAction;
+
+END GtkShortcut.

+ 49 - 0
src/GtkShortcutController.def

@@ -0,0 +1,49 @@
+DEFINITION MODULE FOR "C" GtkShortcutController ;
+
+(*
+   m2-GTK4 - GtkShortcutController: an event controller holding
+   GtkShortcut objects.  Mapped onto <gtk/gtkshortcutcontroller.h>.
+
+   Add it to a widget with GtkWidget.gtk_widget_add_controller(); the
+   scope decides where the shortcuts are handled.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkShortcutController, GtkShortcutScope,
+   GtkShortcutScopeLocal, GtkShortcutScopeManaged, GtkShortcutScopeGlobal,
+   gtk_shortcut_controller_new,
+   gtk_shortcut_controller_add_shortcut,
+   gtk_shortcut_controller_remove_shortcut,
+   gtk_shortcut_controller_set_scope;
+
+
+TYPE
+   GtkShortcutController = ADDRESS;
+   GtkShortcutScope = CARDINAL;
+
+CONST
+   GtkShortcutScopeLocal   = 0;
+   GtkShortcutScopeManaged = 1;
+   GtkShortcutScopeGlobal  = 2;
+
+(* GtkEventController *gtk_shortcut_controller_new(void) *)
+PROCEDURE gtk_shortcut_controller_new () : GtkShortcutController;
+
+(* void gtk_shortcut_controller_add_shortcut(GtkShortcutController *self,
+                                             GtkShortcut *shortcut) *)
+PROCEDURE gtk_shortcut_controller_add_shortcut (self: GtkShortcutController;
+                                                shortcut: ADDRESS);
+
+(* void gtk_shortcut_controller_remove_shortcut(GtkShortcutController *self,
+                                                GtkShortcut *shortcut) *)
+PROCEDURE gtk_shortcut_controller_remove_shortcut (self: GtkShortcutController;
+                                                   shortcut: ADDRESS);
+
+(* void gtk_shortcut_controller_set_scope(GtkShortcutController *self,
+                                          GtkShortcutScope scope) *)
+PROCEDURE gtk_shortcut_controller_set_scope (self: GtkShortcutController;
+                                             scope: GtkShortcutScope);
+
+END GtkShortcutController.

+ 74 - 0
src/GtkSnapshot.def

@@ -0,0 +1,74 @@
+DEFINITION MODULE FOR "C" GtkSnapshot ;
+
+(*
+   m2-GTK4 - GtkSnapshot: records drawing operations into a render node /
+   paintable.  Mapped onto <gtk/gtksnapshot.h>.
+
+   Bound mainly to support a snapshot-based GdkPaintable via the shim:
+
+      VAR rect: GrapheneRect; color: GdkRGBA;
+      graphene_rect_init(rect, 0.0, 0.0, w, h);
+      color.red := 0.2; color.green := 0.5; color.blue := 0.9; color.alpha := 1.0;
+      gtk_snapshot_append_color(snapshot, color, rect);
+
+   gtk_snapshot_free_to_paintable() consumes (frees) the snapshot and
+   returns a new GdkPaintable.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM Gdk IMPORT GdkRGBA;
+FROM Graphene IMPORT GrapheneSize, GraphenePoint, GrapheneRect;
+
+EXPORT UNQUALIFIED
+   GtkSnapshot,
+   gtk_snapshot_new,
+   gtk_snapshot_to_paintable, gtk_snapshot_free_to_paintable,
+   gtk_snapshot_save, gtk_snapshot_restore,
+   gtk_snapshot_translate, gtk_snapshot_scale, gtk_snapshot_rotate,
+   gtk_snapshot_append_color, gtk_snapshot_append_cairo;
+
+
+TYPE
+   GtkSnapshot = ADDRESS;
+
+(* GtkSnapshot *gtk_snapshot_new(void) *)
+PROCEDURE gtk_snapshot_new () : GtkSnapshot;
+
+(* GdkPaintable *gtk_snapshot_to_paintable(GtkSnapshot *snapshot,
+                                            const graphene_size_t *size) *)
+PROCEDURE gtk_snapshot_to_paintable (snapshot: GtkSnapshot;
+                                     VAR size: GrapheneSize) : ADDRESS;
+
+(* GdkPaintable *gtk_snapshot_free_to_paintable(GtkSnapshot *snapshot,
+                                                 const graphene_size_t *size) *)
+PROCEDURE gtk_snapshot_free_to_paintable (snapshot: GtkSnapshot;
+                                          VAR size: GrapheneSize) : ADDRESS;
+
+(* void gtk_snapshot_save(GtkSnapshot *snapshot) *)
+PROCEDURE gtk_snapshot_save (snapshot: GtkSnapshot);
+
+(* void gtk_snapshot_restore(GtkSnapshot *snapshot) *)
+PROCEDURE gtk_snapshot_restore (snapshot: GtkSnapshot);
+
+(* void gtk_snapshot_translate(GtkSnapshot *snapshot,
+                               const graphene_point_t *point) *)
+PROCEDURE gtk_snapshot_translate (snapshot: GtkSnapshot;
+                                  VAR point: GraphenePoint);
+
+(* void gtk_snapshot_scale(GtkSnapshot *snapshot, float sx, float sy) *)
+PROCEDURE gtk_snapshot_scale (snapshot: GtkSnapshot; sx, sy: SHORTREAL);
+
+(* void gtk_snapshot_rotate(GtkSnapshot *snapshot, float angle) *)
+PROCEDURE gtk_snapshot_rotate (snapshot: GtkSnapshot; angle: SHORTREAL);
+
+(* void gtk_snapshot_append_color(GtkSnapshot *snapshot, const GdkRGBA *color,
+                                  const graphene_rect_t *bounds) *)
+PROCEDURE gtk_snapshot_append_color (snapshot: GtkSnapshot; VAR color: GdkRGBA;
+                                     VAR bounds: GrapheneRect);
+
+(* cairo_t *gtk_snapshot_append_cairo(GtkSnapshot *snapshot,
+                                       const graphene_rect_t *bounds) *)
+PROCEDURE gtk_snapshot_append_cairo (snapshot: GtkSnapshot;
+                                     VAR bounds: GrapheneRect) : ADDRESS;
+
+END GtkSnapshot.

+ 60 - 0
src/GtkStringFilter.def

@@ -0,0 +1,60 @@
+DEFINITION MODULE FOR "C" GtkStringFilter ;
+
+(*
+   m2-GTK4 - GtkStringFilter: a filter matching a string expression
+   against a search term.  Mapped onto <gtk/gtkstringfilter.h>.  Use with
+   GtkFilterListModel.
+
+   Signal: "changed" (re-evaluates the model when search/match mode
+   change).
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkStringFilter, GtkStringFilterMatchMode,
+   GtkStringFilterExact, GtkStringFilterSubstring, GtkStringFilterPrefix,
+   gtk_string_filter_new,
+   gtk_string_filter_set_expression, gtk_string_filter_get_expression,
+   gtk_string_filter_set_match_mode, gtk_string_filter_get_match_mode,
+   gtk_string_filter_set_ignore_case, gtk_string_filter_get_ignore_case,
+   gtk_string_filter_set_search, gtk_string_filter_get_search;
+
+
+TYPE
+   GtkStringFilter = ADDRESS;
+   GtkStringFilterMatchMode = CARDINAL;
+
+CONST
+   GtkStringFilterExact     = 0;
+   GtkStringFilterSubstring = 1;
+   GtkStringFilterPrefix    = 2;
+
+(* GtkStringFilter *gtk_string_filter_new(GtkExpression *expression) *)
+PROCEDURE gtk_string_filter_new (expression: ADDRESS) : GtkStringFilter;
+
+(* void gtk_string_filter_set_expression(GtkStringFilter *self, GtkExpression *expression) *)
+PROCEDURE gtk_string_filter_set_expression (self: GtkStringFilter; expression: ADDRESS);
+
+(* GtkExpression *gtk_string_filter_get_expression(GtkStringFilter *self) *)
+PROCEDURE gtk_string_filter_get_expression (self: GtkStringFilter) : ADDRESS;
+
+(* void gtk_string_filter_set_match_mode(GtkStringFilter *self, GtkStringFilterMatchMode mode) *)
+PROCEDURE gtk_string_filter_set_match_mode (self: GtkStringFilter; mode: GtkStringFilterMatchMode);
+
+(* GtkStringFilterMatchMode gtk_string_filter_get_match_mode(GtkStringFilter *self) *)
+PROCEDURE gtk_string_filter_get_match_mode (self: GtkStringFilter) : GtkStringFilterMatchMode;
+
+(* void gtk_string_filter_set_ignore_case(GtkStringFilter *self, gboolean ignore_case) *)
+PROCEDURE gtk_string_filter_set_ignore_case (self: GtkStringFilter; ignore_case: INTEGER);
+
+(* gboolean gtk_string_filter_get_ignore_case(GtkStringFilter *self) *)
+PROCEDURE gtk_string_filter_get_ignore_case (self: GtkStringFilter) : [ INTEGER ];
+
+(* void gtk_string_filter_set_search(GtkStringFilter *self, const char *search) *)
+PROCEDURE gtk_string_filter_set_search (self: GtkStringFilter; search: ARRAY OF CHAR);
+
+(* const char *gtk_string_filter_get_search(GtkStringFilter *self) *)
+PROCEDURE gtk_string_filter_get_search (self: GtkStringFilter) : ADDRESS;
+
+END GtkStringFilter.

+ 49 - 0
src/GtkStringSorter.def

@@ -0,0 +1,49 @@
+DEFINITION MODULE FOR "C" GtkStringSorter ;
+
+(*
+   m2-GTK4 - GtkStringSorter: a sorter comparing string expressions.
+   Mapped onto <gtk/gtkstringsorter.h>.  Use with GtkSortListModel.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkStringSorter, GtkCollation,
+   GtkCollationNone, GtkCollationUnicode, GtkCollationFilename,
+   gtk_string_sorter_new,
+   gtk_string_sorter_set_expression, gtk_string_sorter_get_expression,
+   gtk_string_sorter_set_ignore_case, gtk_string_sorter_get_ignore_case,
+   gtk_string_sorter_set_collation, gtk_string_sorter_get_collation;
+
+
+TYPE
+   GtkStringSorter = ADDRESS;
+   GtkCollation = CARDINAL;
+
+CONST
+   GtkCollationNone     = 0;
+   GtkCollationUnicode  = 1;
+   GtkCollationFilename = 2;
+
+(* GtkStringSorter *gtk_string_sorter_new(GtkExpression *expression) *)
+PROCEDURE gtk_string_sorter_new (expression: ADDRESS) : GtkStringSorter;
+
+(* void gtk_string_sorter_set_expression(GtkStringSorter *self, GtkExpression *expression) *)
+PROCEDURE gtk_string_sorter_set_expression (self: GtkStringSorter; expression: ADDRESS);
+
+(* GtkExpression *gtk_string_sorter_get_expression(GtkStringSorter *self) *)
+PROCEDURE gtk_string_sorter_get_expression (self: GtkStringSorter) : ADDRESS;
+
+(* void gtk_string_sorter_set_ignore_case(GtkStringSorter *self, gboolean ignore_case) *)
+PROCEDURE gtk_string_sorter_set_ignore_case (self: GtkStringSorter; ignore_case: INTEGER);
+
+(* gboolean gtk_string_sorter_get_ignore_case(GtkStringSorter *self) *)
+PROCEDURE gtk_string_sorter_get_ignore_case (self: GtkStringSorter) : [ INTEGER ];
+
+(* void gtk_string_sorter_set_collation(GtkStringSorter *self, GtkCollation collation) *)
+PROCEDURE gtk_string_sorter_set_collation (self: GtkStringSorter; collation: GtkCollation);
+
+(* GtkCollation gtk_string_sorter_get_collation(GtkStringSorter *self) *)
+PROCEDURE gtk_string_sorter_get_collation (self: GtkStringSorter) : GtkCollation;
+
+END GtkStringSorter.

+ 2 - 2
tests/run_tests.sh

@@ -13,7 +13,7 @@ CFLAGS=$(pkg-config --cflags gtk4)
 LIBS=$(pkg-config --libs gtk4)
 
 # libadwaita is optional: add its flags and test when available.
-TESTS="test_glib test_version test_widgets test_inputs test_inputs2 test_containers test_containers2 test_actions test_closure test_headerbar test_gio test_gerror test_filedialog test_views test_builder test_utility test_css test_popover test_dialogs test_models test_cairo test_drawing test_gestures test_models2 test_widgets3 test_shim test_shim2 test_shim3 test_dnd test_media test_gl test_print test_text test_application"
+TESTS="test_glib test_version test_widgets test_inputs test_inputs2 test_containers test_containers2 test_actions test_closure test_headerbar test_gio test_gerror test_filedialog test_views test_builder test_utility test_css test_popover test_dialogs test_models test_cairo test_drawing test_gestures test_models2 test_widgets3 test_shim test_shim2 test_shim3 test_dnd test_media test_gl test_print test_text test_expr test_constraints test_shortcuts test_snapshot test_application"
 if pkg-config --exists libadwaita-1 2>/dev/null; then
   CFLAGS="$CFLAGS $(pkg-config --cflags libadwaita-1)"
   LIBS="$LIBS $(pkg-config --libs libadwaita-1)"
@@ -53,7 +53,7 @@ for t in $TESTS; do
        "$ROOT/tests/$t.mod" "$OBJ/GtkUtils.o" "$OBJ/GtkClosures.o" \
        "$OBJ/m2gtkshim.o" -o "$BIN/$t" $LIBS
   case "$t" in
-    test_widgets|test_inputs|test_inputs2|test_containers|test_containers2|test_actions|test_closure|test_headerbar|test_filedialog|test_views|test_builder|test_utility|test_css|test_popover|test_dialogs|test_models|test_drawing|test_gestures|test_models2|test_widgets3|test_shim|test_shim2|test_shim3|test_dnd|test_media|test_gl|test_print|test_text|test_adw|test_application) needs_display=1 ;;
+    test_widgets|test_inputs|test_inputs2|test_containers|test_containers2|test_actions|test_closure|test_headerbar|test_filedialog|test_views|test_builder|test_utility|test_css|test_popover|test_dialogs|test_models|test_drawing|test_gestures|test_models2|test_widgets3|test_shim|test_shim2|test_shim3|test_dnd|test_media|test_gl|test_print|test_text|test_expr|test_constraints|test_shortcuts|test_snapshot|test_adw|test_application) needs_display=1 ;;
     *) needs_display=0 ;;
   esac
   if [ "$needs_display" = 1 ] && [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then

+ 107 - 0
tests/test_constraints.mod

@@ -0,0 +1,107 @@
+MODULE test_constraints ;
+
+(*
+   m2-GTK4 test - GtkConstraintLayout, GtkConstraint and
+   GtkConstraintGuide.  Requires a display.
+*)
+
+FROM Gio IMPORT g_application_run, g_application_quit;
+FROM GtkApplication IMPORT gtk_application_new;
+FROM GtkWindow IMPORT gtk_application_window_new, gtk_window_set_child,
+   gtk_window_present;
+FROM GtkLabel IMPORT gtk_label_new;
+FROM GtkWidget IMPORT gtk_widget_set_parent, gtk_widget_set_layout_manager;
+FROM M2GtkShim IMPORT m2_gtk_widget_new;
+FROM GtkConstraintLayout IMPORT gtk_constraint_layout_new,
+   gtk_constraint_layout_add_constraint, gtk_constraint_layout_add_guide,
+   gtk_constraint_layout_remove_all_constraints;
+FROM GtkConstraint IMPORT gtk_constraint_new, gtk_constraint_new_constant,
+   GtkConstraintAttributeWidth, GtkConstraintAttributeHeight,
+   GtkConstraintRelationEq, GtkConstraintStrengthRequired;
+FROM GtkConstraintGuide IMPORT gtk_constraint_guide_new,
+   gtk_constraint_guide_set_min_size, gtk_constraint_guide_get_min_size,
+   gtk_constraint_guide_set_strength, gtk_constraint_guide_set_name,
+   gtk_constraint_guide_get_name;
+FROM GtkUtils IMPORT Connect, CStrToM2;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestConstraints";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, parent, layout, label, c1, c2, guide: ADDRESS;
+   gw, gh: INTEGER;
+   buf: ARRAY [0..31] OF CHAR;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   layout := gtk_constraint_layout_new();
+   IF layout = NIL THEN printf("layout NIL\n"); ok := FALSE END;
+
+   parent := m2_gtk_widget_new(NIL, NIL, NIL);
+   gtk_widget_set_layout_manager(parent, layout);
+   label := gtk_label_new("constrained");
+   gtk_widget_set_parent(label, parent);
+
+   c1 := gtk_constraint_new(label, GtkConstraintAttributeWidth,
+                            GtkConstraintRelationEq, NIL, 0,
+                            1.0, 100.0, GtkConstraintStrengthRequired);
+   IF c1 = NIL THEN printf("constraint NIL\n"); ok := FALSE END;
+   gtk_constraint_layout_add_constraint(layout, c1);
+
+   c2 := gtk_constraint_new_constant(label, GtkConstraintAttributeHeight,
+                                     GtkConstraintRelationEq, 40.0,
+                                     GtkConstraintStrengthRequired);
+   gtk_constraint_layout_add_constraint(layout, c2);
+
+   guide := gtk_constraint_guide_new();
+   gtk_constraint_guide_set_min_size(guide, 50, 20);
+   gtk_constraint_guide_get_min_size(guide, gw, gh);
+   IF (gw # 50) OR (gh # 20) THEN
+      printf("guide min size wrong: %d x %d\n", gw, gh); ok := FALSE
+   END;
+   gtk_constraint_guide_set_strength(guide, 1);
+   gtk_constraint_guide_set_name(guide, "g1");
+   CStrToM2(gtk_constraint_guide_get_name(guide), buf);
+   IF (buf[0] # 'g') OR (buf[1] # '1') OR (buf[2] # 0C) THEN
+      printf("guide name wrong: [%s]\n", buf); ok := FALSE
+   END;
+   gtk_constraint_layout_add_guide(layout, guide);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, parent);
+   gtk_window_present(window);
+
+   gtk_constraint_layout_remove_all_constraints(layout);
+
+   passed := ok;
+   g_application_quit(application)
+END OnActivate;
+
+VAR
+   rc: INTEGER;
+BEGIN
+   passed := FALSE;
+   app := gtk_application_new(AppId, 0);
+   IF app = NIL THEN
+      printf("test_constraints: FAIL (no app)\n");
+      HALT(1)
+   END;
+   Connect(app, "activate", ADR(OnActivate), NIL);
+   rc := g_application_run(app, 0, NIL);
+
+   IF passed THEN
+      printf("test_constraints: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_constraints: FAIL\n");
+      HALT(1)
+   END
+END test_constraints.

+ 103 - 0
tests/test_expr.mod

@@ -0,0 +1,103 @@
+MODULE test_expr ;
+
+(*
+   m2-GTK4 test - expressions and the expression-based sorters/filters.
+
+   Builds a property expression over GtkStringObjects, uses it in a
+   GtkStringSorter and a GtkStringFilter, and checks sorting/filtering
+   through the list models.  Requires GTK initialised (and so a display).
+*)
+
+FROM Gtk IMPORT gtk_init;
+FROM GtkExpression IMPORT gtk_property_expression_new;
+FROM GtkStringSorter IMPORT gtk_string_sorter_new,
+   gtk_string_sorter_set_ignore_case, gtk_string_sorter_get_ignore_case,
+   gtk_string_sorter_set_collation, gtk_string_sorter_get_collation;
+FROM GtkNumericSorter IMPORT gtk_numeric_sorter_new,
+   gtk_numeric_sorter_set_sort_order, gtk_numeric_sorter_get_sort_order,
+   GtkSortDescending;
+FROM GtkStringFilter IMPORT gtk_string_filter_new,
+   gtk_string_filter_set_match_mode, gtk_string_filter_get_match_mode,
+   gtk_string_filter_set_search, GtkStringFilterSubstring;
+FROM GtkBoolFilter IMPORT gtk_bool_filter_new, gtk_bool_filter_set_invert,
+   gtk_bool_filter_get_invert;
+FROM GtkStringList IMPORT gtk_string_list_new, gtk_string_list_append;
+FROM GtkStringObject IMPORT gtk_string_object_get_type,
+   gtk_string_object_get_string;
+FROM GtkSortListModel IMPORT gtk_sort_list_model_new;
+FROM GtkFilterListModel IMPORT gtk_filter_list_model_new;
+FROM GListModel IMPORT g_list_model_get_n_items, g_list_model_get_item;
+FROM GObject IMPORT g_object_ref, g_object_unref;
+FROM GtkUtils IMPORT CStrToM2;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+VAR
+   failed: BOOLEAN;
+
+PROCEDURE Check (condition: BOOLEAN; what: ARRAY OF CHAR);
+BEGIN
+   IF NOT condition THEN
+      printf("FAIL: %s\n", what);
+      failed := TRUE
+   END
+END Check;
+
+VAR
+   expr, sorter, nsorter, filter, boolf, list, sortModel, filterModel,
+   item: ADDRESS;
+   buf: ARRAY [0..31] OF CHAR;
+BEGIN
+   failed := FALSE;
+   gtk_init();
+
+   expr := gtk_property_expression_new(gtk_string_object_get_type(), NIL,
+                                       "string", NIL);
+   Check(expr # NIL, "property expression");
+
+   sorter := gtk_string_sorter_new(expr);
+   gtk_string_sorter_set_ignore_case(sorter, 1);
+   Check(gtk_string_sorter_get_ignore_case(sorter) # 0, "sorter ignore case");
+   gtk_string_sorter_set_collation(sorter, 0);
+   Check(gtk_string_sorter_get_collation(sorter) = 0, "sorter collation");
+
+   nsorter := gtk_numeric_sorter_new(NIL);
+   gtk_numeric_sorter_set_sort_order(nsorter, GtkSortDescending);
+   Check(gtk_numeric_sorter_get_sort_order(nsorter) = GtkSortDescending,
+         "numeric sort order");
+
+   filter := gtk_string_filter_new(expr);
+   gtk_string_filter_set_match_mode(filter, GtkStringFilterSubstring);
+   Check(gtk_string_filter_get_match_mode(filter) = GtkStringFilterSubstring,
+         "filter match mode");
+
+   boolf := gtk_bool_filter_new(NIL);
+   gtk_bool_filter_set_invert(boolf, 1);
+   Check(gtk_bool_filter_get_invert(boolf) # 0, "bool filter invert");
+
+   list := gtk_string_list_new(NIL);
+   gtk_string_list_append(list, "banana");
+   gtk_string_list_append(list, "apple");
+   gtk_string_list_append(list, "cherry");
+
+   (* sorting: apple < banana < cherry *)
+   sortModel := gtk_sort_list_model_new(g_object_ref(list), sorter);
+   Check(g_list_model_get_n_items(sortModel) = 3, "sorted count");
+   item := g_list_model_get_item(sortModel, 0);
+   CStrToM2(gtk_string_object_get_string(item), buf);
+   Check(buf[0] = 'a', "sorted first is apple");
+   g_object_unref(item);
+
+   (* filtering: only "banana" contains "an" *)
+   gtk_string_filter_set_search(filter, "an");
+   filterModel := gtk_filter_list_model_new(g_object_ref(list), filter);
+   Check(g_list_model_get_n_items(filterModel) = 1, "filtered count");
+
+   IF failed THEN
+      printf("test_expr: FAIL\n");
+      HALT(1)
+   ELSE
+      printf("test_expr: PASS\n");
+      HALT(0)
+   END
+END test_expr.

+ 90 - 0
tests/test_shortcuts.mod

@@ -0,0 +1,90 @@
+MODULE test_shortcuts ;
+
+(*
+   m2-GTK4 test - shortcuts: trigger/action parsing, GtkShortcut and
+   GtkShortcutController.  Requires a display.
+*)
+
+FROM Gio IMPORT g_application_run, g_application_quit;
+FROM GtkApplication IMPORT gtk_application_new;
+FROM GtkWindow IMPORT gtk_application_window_new, gtk_window_set_child,
+   gtk_window_present;
+FROM GtkLabel IMPORT gtk_label_new;
+FROM GtkWidget IMPORT gtk_widget_add_controller;
+FROM GtkShortcut IMPORT gtk_shortcut_new, gtk_shortcut_get_trigger,
+   gtk_shortcut_get_action, gtk_shortcut_trigger_parse_string,
+   gtk_named_action_new;
+FROM GtkShortcutController IMPORT gtk_shortcut_controller_new,
+   gtk_shortcut_controller_add_shortcut,
+   gtk_shortcut_controller_remove_shortcut,
+   gtk_shortcut_controller_set_scope, GtkShortcutScopeLocal;
+FROM GtkUtils IMPORT Connect;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestShortcuts";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, label, controller, trigger, action, shortcut: ADDRESS;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   controller := gtk_shortcut_controller_new();
+   IF controller = NIL THEN printf("controller NIL\n"); ok := FALSE END;
+   gtk_shortcut_controller_set_scope(controller, GtkShortcutScopeLocal);
+
+   trigger := gtk_shortcut_trigger_parse_string("<Control>q");
+   IF trigger = NIL THEN printf("trigger NIL\n"); ok := FALSE END;
+   action := gtk_named_action_new("app.quit");
+   IF action = NIL THEN printf("action NIL\n"); ok := FALSE END;
+
+   shortcut := gtk_shortcut_new(trigger, action);
+   IF shortcut = NIL THEN printf("shortcut NIL\n"); ok := FALSE END;
+   IF gtk_shortcut_get_trigger(shortcut) # trigger THEN
+      printf("shortcut trigger wrong\n"); ok := FALSE
+   END;
+   IF gtk_shortcut_get_action(shortcut) # action THEN
+      printf("shortcut action wrong\n"); ok := FALSE
+   END;
+
+   gtk_shortcut_controller_add_shortcut(controller, shortcut);
+   gtk_shortcut_controller_remove_shortcut(controller, shortcut);
+
+   label := gtk_label_new("press Ctrl+Q");
+   gtk_widget_add_controller(label, controller);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, label);
+   gtk_window_present(window);
+
+   passed := ok;
+   g_application_quit(application)
+END OnActivate;
+
+VAR
+   rc: INTEGER;
+BEGIN
+   passed := FALSE;
+   app := gtk_application_new(AppId, 0);
+   IF app = NIL THEN
+      printf("test_shortcuts: FAIL (no app)\n");
+      HALT(1)
+   END;
+   Connect(app, "activate", ADR(OnActivate), NIL);
+   rc := g_application_run(app, 0, NIL);
+
+   IF passed THEN
+      printf("test_shortcuts: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_shortcuts: FAIL\n");
+      HALT(1)
+   END
+END test_shortcuts.

+ 106 - 0
tests/test_snapshot.mod

@@ -0,0 +1,106 @@
+MODULE test_snapshot ;
+
+(*
+   m2-GTK4 test - a GdkPaintable whose snapshot uses GtkSnapshot and
+   Graphene from Modula-2 (via the shim).  Requires a display.
+*)
+
+FROM Gio IMPORT g_application_run, g_application_quit;
+FROM GtkApplication IMPORT gtk_application_new;
+FROM GtkWindow IMPORT gtk_application_window_new, gtk_window_set_child,
+   gtk_window_present;
+FROM GtkPicture IMPORT gtk_picture_new, gtk_picture_set_paintable;
+FROM GtkWidget IMPORT gtk_widget_set_size_request;
+FROM M2GtkShim IMPORT m2_snapshot_paintable_new;
+FROM GtkSnapshot IMPORT gtk_snapshot_append_color,
+   gtk_snapshot_append_cairo;
+FROM Graphene IMPORT GrapheneRect, graphene_rect_init;
+FROM Gdk IMPORT GdkRGBA;
+FROM Cairo IMPORT cairo_set_source_rgb, cairo_rectangle, cairo_fill,
+   cairo_destroy;
+FROM GLib IMPORT g_timeout_add, GSourceRemove;
+FROM GtkUtils IMPORT Connect;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestSnapshot";
+
+VAR
+   app: ADDRESS;
+   passed, drew: BOOLEAN;
+
+(* void draw(GtkSnapshot *snapshot, int width, int height, gpointer user) *)
+PROCEDURE Draw (snapshot: ADDRESS; width, height: INTEGER; user: ADDRESS);
+VAR
+   rect: GrapheneRect;
+   color: GdkRGBA;
+   cr: ADDRESS;
+BEGIN
+   drew := TRUE;
+   graphene_rect_init(rect, 0.0, 0.0, VAL(REAL, width), VAL(REAL, height));
+
+   color.red := 0.16;
+   color.green := 0.50;
+   color.blue := 0.72;
+   color.alpha := 1.0;
+   gtk_snapshot_append_color(snapshot, color, rect);
+
+   cr := gtk_snapshot_append_cairo(snapshot, rect);
+   IF cr # NIL THEN
+      cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+      cairo_rectangle(cr, 8.0, 8.0, 24.0, 24.0);
+      cairo_fill(cr);
+      cairo_destroy(cr)
+   END
+END Draw;
+
+PROCEDURE AfterMap (data: ADDRESS) : INTEGER;
+BEGIN
+   IF NOT drew THEN
+      printf("snapshot draw did not run\n");
+      passed := FALSE
+   END;
+   g_application_quit(app);
+   RETURN GSourceRemove
+END AfterMap;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, picture, paintable: ADDRESS;
+BEGIN
+   drew := FALSE;
+
+   paintable := m2_snapshot_paintable_new(ADR(Draw), 80, 60, NIL, NIL);
+   picture := gtk_picture_new();
+   gtk_picture_set_paintable(picture, paintable);
+   gtk_widget_set_size_request(picture, 80, 60);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, picture);
+   gtk_window_present(window);
+
+   passed := TRUE;
+   g_timeout_add(300, AfterMap, NIL)
+END OnActivate;
+
+VAR
+   rc: INTEGER;
+BEGIN
+   passed := FALSE;
+   app := gtk_application_new(AppId, 0);
+   IF app = NIL THEN
+      printf("test_snapshot: FAIL (no app)\n");
+      HALT(1)
+   END;
+   Connect(app, "activate", ADR(OnActivate), NIL);
+   rc := g_application_run(app, 0, NIL);
+
+   IF passed THEN
+      printf("test_snapshot: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_snapshot: FAIL\n");
+      HALT(1)
+   END
+END test_snapshot.