Просмотр исходного кода

feat: gestures, model stack and common widgets (toward gtk-demo)

Gestures / event controllers:
- GtkEventController (+ motion), GtkGestures (click, drag, swipe, zoom,
  rotate, pan, long-press; GtkPanDirection)
- GtkWidget.add_controller/remove_controller
- test_gestures (connects a 5-arg "pressed" handler, emits it to check
  the callback ABI), examples/gestures.mod

Model stack:
- GtkMultiSelection, GtkBitset
- GtkTreeListModel/GtkTreeListRow, GtkTreeExpander
- GtkDirectoryList, GtkBookmarkList
- test_models2

Common widgets:
- GtkFlowBox/GtkFlowBoxChild, GtkFixed, GtkPicture, GtkToggleButton,
  GtkLinkButton, GtkCalendar, GtkLevelBar, GtkScrollbar
- test_widgets3

27 tests pass; 123 src modules, 19 examples.
Eric Streit 5 дней назад
Родитель
Сommit
de29c0c46b

+ 2 - 2
Makefile

@@ -18,8 +18,8 @@ OBJS    := $(OBJDIR)/GtkUtils.o $(OBJDIR)/GtkClosures.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_application
-EXAMPLES := hello counter inputs containers actions headerbar editor layouts files views builder feedback styling popover dialogs drawing demo
+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_application
+EXAMPLES := hello counter inputs containers actions headerbar editor layouts files views builder feedback styling popover dialogs drawing demo gestures
 
 # Include the libadwaita test/example only when libadwaita is installed.
 ifneq ($(ADWLIBS),)

+ 9 - 3
README.md

@@ -14,14 +14,14 @@ Verified with `gm2` 16.0.1 (experimental), GTK 4.18.6 and libadwaita 1.7.6.
 
 ## Features
 
-- **107 hand-written binding modules** across GLib, GObject, Gio, GTK4
+- **123 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),
-  settings, and more.
+  gestures, 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).
-- **24 tests** and **18 runnable examples**.
+- **27 tests** and **19 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.
@@ -120,6 +120,7 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `dialogs` | `GtkAlertDialog` and `GtkColorDialog` |
 | `drawing` | `GtkDrawingArea` + Cairo + Pango |
 | `demo` | searchable demo browser (filter model + stack of pages) |
+| `gestures` | click/drag gestures on a drawing area |
 | `adw` | libadwaita preferences app (toolbar view, rows, toast, about) |
 
 ## Module reference
@@ -146,6 +147,9 @@ 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 |
+| `GtkMultiSelection`, `GtkBitset` | multiple selection and bitsets |
+| `GtkTreeListModel`, `GtkTreeListRow`, `GtkTreeExpander` | tree models and expanders |
+| `GtkDirectoryList`, `GtkBookmarkList` | directory / bookmark models |
 | `GFile`, `GtkFileDialog` | file handles and the native dialog |
 | `GSettings`, `GSettingsSchema` | typed settings and schema lookup |
 
@@ -164,6 +168,8 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `GtkAlertDialog`, `GtkColorDialog` | modern alert and colour dialogs |
 | `GtkCssProvider`, `GtkStyleContext` | CSS styling for the display |
 | `GtkStackSidebar`, `GtkSearchBar`, `GtkRevealer`, `GtkAspectFrame` | sidebar/search/animation/layout helpers |
+| `GtkFlowBox`, `GtkFixed`, `GtkPicture`, `GtkToggleButton`, `GtkLinkButton`, `GtkCalendar`, `GtkLevelBar`, `GtkScrollbar` | reflowing grid, fixed/abs, image, toggle/link, date, level, scrollbar |
+| `GtkEventController`, `GtkGestures` | event controllers and gestures (click/drag/swipe/zoom/rotate/pan/long-press) |
 | `GtkDrawingArea` + `Cairo` + `Pango` + `PangoCairo` | custom drawing with Cairo and text layout |
 | `GtkListBox`, `GtkListView`, `GtkGridView`, `GtkColumnView`, `GtkColumnViewColumn`, `GtkColumnViewCell`, `GtkListItem`, `GtkSignalListItemFactory`, `GtkSelectionModel`, `GtkSingleSelection`, `GtkNoSelection` | lists, grids and tables |
 

+ 116 - 0
examples/gestures.mod

@@ -0,0 +1,116 @@
+MODULE gestures ;
+
+(*
+   m2-GTK4 example - gestures on a drawing area.
+
+   A GtkGestureClick reports the click position and a GtkGestureDrag
+   reports the drag offset, both in a label.  Demonstrates connecting
+   signals with the exact C callback signatures.
+
+   Build:  make examples
+   Run:    ./build/examples/gestures
+*)
+
+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 GtkBox IMPORT gtk_box_new, gtk_box_append, gtk_box_set_spacing;
+FROM GtkEnums IMPORT GtkVertical;
+FROM GtkLabel IMPORT gtk_label_new, gtk_label_set_text;
+FROM GtkDrawingArea IMPORT gtk_drawing_area_new,
+   gtk_drawing_area_set_content_width,
+   gtk_drawing_area_set_content_height, gtk_drawing_area_set_draw_func;
+FROM GtkWidget IMPORT gtk_widget_add_controller;
+FROM GtkGestures IMPORT gtk_gesture_click_new, gtk_gesture_drag_new;
+FROM Cairo IMPORT cairo_set_source_rgb, cairo_rectangle, cairo_fill;
+FROM GObject IMPORT g_signal_connect_data, GConnectDefault;
+FROM GtkUtils IMPORT Connect, IntToStr, StrAppend;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.Gestures";
+
+VAR
+   label: ADDRESS;
+
+PROCEDURE Show (kind: ARRAY OF CHAR; a, b: REAL);
+VAR
+   num: ARRAY [0..15] OF CHAR;
+   line: ARRAY [0..63] OF CHAR;
+BEGIN
+   line[0] := 0C;
+   StrAppend(line, kind);
+   IntToStr(TRUNC(a), num);
+   StrAppend(line, num);
+   StrAppend(line, ", ");
+   IntToStr(TRUNC(b), num);
+   StrAppend(line, num);
+   gtk_label_set_text(label, line)
+END Show;
+
+(* void pressed(GtkGestureClick *g, int n, double x, double y, gpointer u) *)
+PROCEDURE OnPressed (g: ADDRESS; n_press: INTEGER; x, y: REAL; user: ADDRESS);
+BEGIN
+   Show("click at ", x, y)
+END OnPressed;
+
+(* void drag-update(GtkGestureDrag *g, double ox, double oy, gpointer u) *)
+PROCEDURE OnDragUpdate (g: ADDRESS; offset_x, offset_y: REAL; user: ADDRESS);
+BEGIN
+   Show("drag offset ", offset_x, offset_y)
+END OnDragUpdate;
+
+PROCEDURE Draw (area: ADDRESS; cr: ADDRESS; width, height: INTEGER;
+                data: ADDRESS);
+BEGIN
+   cairo_set_source_rgb(cr, 0.90, 0.93, 0.98);
+   cairo_rectangle(cr, 0.0, 0.0, VAL(REAL, width), VAL(REAL, height));
+   cairo_fill(cr)
+END Draw;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, area, click, drag: ADDRESS;
+BEGIN
+   area := gtk_drawing_area_new();
+   gtk_drawing_area_set_content_width(area, 320);
+   gtk_drawing_area_set_content_height(area, 200);
+   gtk_drawing_area_set_draw_func(area, ADR(Draw), NIL, NIL);
+
+   click := gtk_gesture_click_new();
+   g_signal_connect_data(click, "pressed", ADR(OnPressed), NIL, NIL,
+                         GConnectDefault);
+   gtk_widget_add_controller(area, click);
+
+   drag := gtk_gesture_drag_new();
+   g_signal_connect_data(drag, "drag-update", ADR(OnDragUpdate), NIL, NIL,
+                         GConnectDefault);
+   gtk_widget_add_controller(area, drag);
+
+   label := gtk_label_new("click or drag the area");
+
+   box := gtk_box_new(GtkVertical, 10);
+   gtk_box_set_spacing(box, 10);
+   gtk_box_append(box, area);
+   gtk_box_append(box, label);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_title(window, "m2-GTK4 Gestures");
+   gtk_window_set_default_size(window, 360, 260);
+   gtk_window_set_child(window, box);
+   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;
+   Connect(app, "activate", ADR(OnActivate), NIL);
+   g_application_run(app, 0, NIL)
+END gestures.

+ 49 - 0
src/GtkBitset.def

@@ -0,0 +1,49 @@
+DEFINITION MODULE FOR "C" GtkBitset ;
+
+(*
+   m2-GTK4 - GtkBitset: a compact set of unsigned integers.  Mapped onto
+   <gtk/gtkbitset.h>.
+
+   Used by the selection models (GtkSelectionModel.get_selection) and by
+   GtkTreeListModel/etc.  gtk_bitset_get_nth returns
+   GTK_INVALID_LIST_POSITION (0xFFFFFFFF) when nth is out of range.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkBitset, GtkInvalidListPosition,
+   gtk_bitset_new_empty,
+   gtk_bitset_ref, gtk_bitset_unref,
+   gtk_bitset_add, gtk_bitset_contains,
+   gtk_bitset_get_size, gtk_bitset_get_nth;
+
+
+TYPE
+   GtkBitset = ADDRESS;
+
+CONST
+   GtkInvalidListPosition = 0FFFFFFFFH;
+
+(* GtkBitset *gtk_bitset_new_empty(void) *)
+PROCEDURE gtk_bitset_new_empty () : GtkBitset;
+
+(* GtkBitset *gtk_bitset_ref(GtkBitset *self) *)
+PROCEDURE gtk_bitset_ref (self: GtkBitset) : GtkBitset;
+
+(* void gtk_bitset_unref(GtkBitset *self) *)
+PROCEDURE gtk_bitset_unref (self: GtkBitset);
+
+(* gboolean gtk_bitset_add(GtkBitset *self, guint pos) *)
+PROCEDURE gtk_bitset_add (self: GtkBitset; pos: CARDINAL) : [ INTEGER ];
+
+(* gboolean gtk_bitset_contains(const GtkBitset *self, guint pos) *)
+PROCEDURE gtk_bitset_contains (self: GtkBitset; pos: CARDINAL) : [ INTEGER ];
+
+(* guint64 gtk_bitset_get_size(const GtkBitset *self) *)
+PROCEDURE gtk_bitset_get_size (self: GtkBitset) : LONGCARD;
+
+(* guint gtk_bitset_get_nth(const GtkBitset *self, guint nth) *)
+PROCEDURE gtk_bitset_get_nth (self: GtkBitset; nth: CARDINAL) : CARDINAL;
+
+END GtkBitset.

+ 36 - 0
src/GtkBookmarkList.def

@@ -0,0 +1,36 @@
+DEFINITION MODULE FOR "C" GtkBookmarkList ;
+
+(*
+   m2-GTK4 - GtkBookmarkList: a GListModel of GTK bookmarks (as GFile).
+   Mapped onto <gtk/gtkbookmarklist.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkBookmarkList,
+   gtk_bookmark_list_new,
+   gtk_bookmark_list_get_filename,
+   gtk_bookmark_list_set_attributes, gtk_bookmark_list_get_attributes;
+
+
+TYPE
+   GtkBookmarkList = ADDRESS;
+
+(* GtkBookmarkList *gtk_bookmark_list_new(const char *filename,
+                                           const char *attributes) *)
+PROCEDURE gtk_bookmark_list_new (filename: ARRAY OF CHAR;
+                                 attributes: ARRAY OF CHAR) : GtkBookmarkList;
+
+(* const char *gtk_bookmark_list_get_filename(GtkBookmarkList *self) *)
+PROCEDURE gtk_bookmark_list_get_filename (self: GtkBookmarkList) : ADDRESS;
+
+(* void gtk_bookmark_list_set_attributes(GtkBookmarkList *self,
+                                          const char *attributes) *)
+PROCEDURE gtk_bookmark_list_set_attributes (self: GtkBookmarkList;
+                                            attributes: ARRAY OF CHAR);
+
+(* const char *gtk_bookmark_list_get_attributes(GtkBookmarkList *self) *)
+PROCEDURE gtk_bookmark_list_get_attributes (self: GtkBookmarkList) : ADDRESS;
+
+END GtkBookmarkList.

+ 28 - 0
src/GtkCalendar.def

@@ -0,0 +1,28 @@
+DEFINITION MODULE FOR "C" GtkCalendar ;
+
+(*
+   m2-GTK4 - GtkCalendar: a month display / date picker.  Mapped onto
+   <gtk/gtkcalendar.h>.
+
+   gtk_calendar_get_date returns a newly allocated GDateTime (release
+   with GObject.g_object_unref).  Signals: "day-selected", "month-changed".
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkCalendar,
+   gtk_calendar_new,
+   gtk_calendar_get_date;
+
+
+TYPE
+   GtkCalendar = ADDRESS;
+
+(* GtkWidget *gtk_calendar_new(void) *)
+PROCEDURE gtk_calendar_new () : GtkCalendar;
+
+(* GDateTime *gtk_calendar_get_date(GtkCalendar *self) *)
+PROCEDURE gtk_calendar_get_date (self: GtkCalendar) : ADDRESS;
+
+END GtkCalendar.

+ 43 - 0
src/GtkDirectoryList.def

@@ -0,0 +1,43 @@
+DEFINITION MODULE FOR "C" GtkDirectoryList ;
+
+(*
+   m2-GTK4 - GtkDirectoryList: a GListModel of the contents of a
+   directory (as GFile/GFileInfo).  Mapped onto <gtk/gtkdirectorylist.h>.
+
+   gtk_directory_list_new(attributes, file) takes a comma-separated
+   attribute list (e.g. "standard::name,standard::icon") and an initial
+   GFile (or NIL).  Loading is asynchronous.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkDirectoryList,
+   gtk_directory_list_new,
+   gtk_directory_list_set_file, gtk_directory_list_get_file,
+   gtk_directory_list_set_attributes, gtk_directory_list_get_attributes;
+
+
+TYPE
+   GtkDirectoryList = ADDRESS;
+
+(* GtkDirectoryList *gtk_directory_list_new(const char *attributes,
+                                             GFile *file) *)
+PROCEDURE gtk_directory_list_new (attributes: ARRAY OF CHAR;
+                                  file: ADDRESS) : GtkDirectoryList;
+
+(* void gtk_directory_list_set_file(GtkDirectoryList *self, GFile *file) *)
+PROCEDURE gtk_directory_list_set_file (self: GtkDirectoryList; file: ADDRESS);
+
+(* GFile *gtk_directory_list_get_file(GtkDirectoryList *self) *)
+PROCEDURE gtk_directory_list_get_file (self: GtkDirectoryList) : ADDRESS;
+
+(* void gtk_directory_list_set_attributes(GtkDirectoryList *self,
+                                           const char *attributes) *)
+PROCEDURE gtk_directory_list_set_attributes (self: GtkDirectoryList;
+                                             attributes: ARRAY OF CHAR);
+
+(* const char *gtk_directory_list_get_attributes(GtkDirectoryList *self) *)
+PROCEDURE gtk_directory_list_get_attributes (self: GtkDirectoryList) : ADDRESS;
+
+END GtkDirectoryList.

+ 50 - 0
src/GtkEventController.def

@@ -0,0 +1,50 @@
+DEFINITION MODULE FOR "C" GtkEventController ;
+
+(*
+   m2-GTK4 - GtkEventController and GtkEventControllerMotion.  Mapped
+   onto <gtk/gtkeventcontroller.h> and <gtk/gtkeventcontrollermotion.h>.
+
+   Event controllers (gestures are a subclass) are added to a widget with
+   GtkWidget.gtk_widget_add_controller().  The propagation phase controls
+   whether the controller runs during capture, in the target, or bubbling.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkEventController, GtkPropagationPhase,
+   GtkPhaseNone, GtkPhaseCapture, GtkPhaseBubble, GtkPhaseTarget,
+   gtk_event_controller_set_propagation_phase,
+   gtk_event_controller_get_propagation_phase,
+   gtk_event_controller_set_name,
+   gtk_event_controller_motion_new;
+
+
+TYPE
+   GtkEventController = ADDRESS;
+   GtkPropagationPhase = CARDINAL;
+
+CONST
+   GtkPhaseNone    = 0;
+   GtkPhaseCapture = 1;
+   GtkPhaseBubble  = 2;
+   GtkPhaseTarget  = 3;
+
+(* void gtk_event_controller_set_propagation_phase(GtkEventController *controller,
+                                                    GtkPropagationPhase phase) *)
+PROCEDURE gtk_event_controller_set_propagation_phase (controller: GtkEventController;
+                                                      phase: GtkPropagationPhase);
+
+(* GtkPropagationPhase gtk_event_controller_get_propagation_phase(
+                          GtkEventController *controller) *)
+PROCEDURE gtk_event_controller_get_propagation_phase (controller: GtkEventController) : GtkPropagationPhase;
+
+(* void gtk_event_controller_set_name(GtkEventController *controller,
+                                       const char *name) *)
+PROCEDURE gtk_event_controller_set_name (controller: GtkEventController;
+                                         name: ARRAY OF CHAR);
+
+(* GtkEventController *gtk_event_controller_motion_new(void) *)
+PROCEDURE gtk_event_controller_motion_new () : ADDRESS;
+
+END GtkEventController.

+ 27 - 0
src/GtkFixed.def

@@ -0,0 +1,27 @@
+DEFINITION MODULE FOR "C" GtkFixed ;
+
+(*
+   m2-GTK4 - GtkFixed: a container that places children at fixed
+   coordinates.  Mapped onto <gtk/gtkfixed.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkFixed,
+   gtk_fixed_new, gtk_fixed_put, gtk_fixed_move;
+
+
+TYPE
+   GtkFixed = ADDRESS;
+
+(* GtkWidget *gtk_fixed_new(void) *)
+PROCEDURE gtk_fixed_new () : GtkFixed;
+
+(* void gtk_fixed_put(GtkFixed *fixed, GtkWidget *widget, double x, double y) *)
+PROCEDURE gtk_fixed_put (fixed: GtkFixed; widget: ADDRESS; x, y: REAL);
+
+(* void gtk_fixed_move(GtkFixed *fixed, GtkWidget *widget, double x, double y) *)
+PROCEDURE gtk_fixed_move (fixed: GtkFixed; widget: ADDRESS; x, y: REAL);
+
+END GtkFixed.

+ 51 - 0
src/GtkFlowBox.def

@@ -0,0 +1,51 @@
+DEFINITION MODULE FOR "C" GtkFlowBox ;
+
+(*
+   m2-GTK4 - GtkFlowBox and GtkFlowBoxChild: a reflowing grid of
+   widgets.  Mapped onto <gtk/gtkflowbox.h>.
+
+   Children are added directly (gtk_flow_box_append) or wrapped in a
+   GtkFlowBoxChild for custom content.  Signal: "child-activated".
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM GtkEnums IMPORT GtkSelectionMode;
+
+EXPORT UNQUALIFIED
+   GtkFlowBox, GtkFlowBoxChild,
+   gtk_flow_box_new,
+   gtk_flow_box_append, gtk_flow_box_insert,
+   gtk_flow_box_set_max_children_per_line,
+   gtk_flow_box_set_selection_mode,
+   gtk_flow_box_child_new, gtk_flow_box_child_set_child;
+
+
+TYPE
+   GtkFlowBox = ADDRESS;
+   GtkFlowBoxChild = ADDRESS;
+
+(* GtkWidget *gtk_flow_box_new(void) *)
+PROCEDURE gtk_flow_box_new () : GtkFlowBox;
+
+(* void gtk_flow_box_append(GtkFlowBox *self, GtkWidget *child) *)
+PROCEDURE gtk_flow_box_append (self: GtkFlowBox; child: ADDRESS);
+
+(* void gtk_flow_box_insert(GtkFlowBox *box, GtkWidget *widget, int position) *)
+PROCEDURE gtk_flow_box_insert (box: GtkFlowBox; widget: ADDRESS;
+                               position: INTEGER);
+
+(* void gtk_flow_box_set_max_children_per_line(GtkFlowBox *box, guint n_children) *)
+PROCEDURE gtk_flow_box_set_max_children_per_line (box: GtkFlowBox;
+                                                  n_children: CARDINAL);
+
+(* void gtk_flow_box_set_selection_mode(GtkFlowBox *box, GtkSelectionMode mode) *)
+PROCEDURE gtk_flow_box_set_selection_mode (box: GtkFlowBox;
+                                           mode: GtkSelectionMode);
+
+(* GtkWidget *gtk_flow_box_child_new(void) *)
+PROCEDURE gtk_flow_box_child_new () : GtkFlowBoxChild;
+
+(* void gtk_flow_box_child_set_child(GtkFlowBoxChild *self, GtkWidget *child) *)
+PROCEDURE gtk_flow_box_child_set_child (self: GtkFlowBoxChild; child: ADDRESS);
+
+END GtkFlowBox.

+ 83 - 0
src/GtkGestures.def

@@ -0,0 +1,83 @@
+DEFINITION MODULE FOR "C" GtkGestures ;
+
+(*
+   m2-GTK4 - the GtkGesture family.  Mapped onto the <gtk/gtkgesture*.h>
+   headers.
+
+   All constructors return a GtkGesture * (GtkEventController subclass),
+   to be added to a widget with GtkWidget.gtk_widget_add_controller().
+
+   Connect the signals with GObject.g_signal_connect_data() and a
+   procedure of the exact C signature, e.g.
+
+      (* click "pressed": void (GtkGestureClick*, int n_press,
+                                double x, double y, gpointer) *)
+      PROCEDURE OnPressed (g: ADDRESS; n: INTEGER; x, y: REAL; user: ADDRESS);
+      ...
+      g_signal_connect_data(click, "pressed", ADR(OnPressed), NIL, NIL, 0);
+
+   Common signals:
+     GtkGestureClick   "pressed"(n_press:int,x:double,y:double)
+                       "released"(n_press:int,x:double,y:double)
+     GtkGestureDrag    "drag-begin"/"drag-update"/"drag-end"(offset_x:double,
+                                                              offset_y:double)
+     GtkGestureSwipe   "swipe"(velocity_x:double, velocity_y:double)
+     GtkGestureZoom    "scale-changed"(scale:double)
+     GtkGestureRotate  "angle-changed"(angle:double, angle_delta:double)
+     GtkGesturePan     "pan"(direction:GtkPanDirection, offset:double)
+     GtkGestureLongPress "pressed"(x:double, y:double)
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM GtkEnums IMPORT GtkOrientation;
+
+EXPORT UNQUALIFIED
+   GtkGesture, GtkGestureSingle, GtkPanDirection,
+   GtkPanDirectionLeft, GtkPanDirectionRight,
+   GtkPanDirectionUp, GtkPanDirectionDown,
+   gtk_gesture_click_new, gtk_gesture_drag_new, gtk_gesture_swipe_new,
+   gtk_gesture_zoom_new, gtk_gesture_rotate_new, gtk_gesture_pan_new,
+   gtk_gesture_long_press_new,
+   gtk_gesture_single_set_button, gtk_gesture_single_get_button;
+
+
+TYPE
+   GtkGesture = ADDRESS;
+   GtkGestureSingle = ADDRESS;
+   GtkPanDirection = CARDINAL;
+
+CONST
+   GtkPanDirectionLeft  = 0;
+   GtkPanDirectionRight = 1;
+   GtkPanDirectionUp    = 2;
+   GtkPanDirectionDown  = 3;
+
+(* GtkGesture *gtk_gesture_click_new(void) *)
+PROCEDURE gtk_gesture_click_new () : GtkGesture;
+
+(* GtkGesture *gtk_gesture_drag_new(void) *)
+PROCEDURE gtk_gesture_drag_new () : GtkGesture;
+
+(* GtkGesture *gtk_gesture_swipe_new(void) *)
+PROCEDURE gtk_gesture_swipe_new () : GtkGesture;
+
+(* GtkGesture *gtk_gesture_zoom_new(void) *)
+PROCEDURE gtk_gesture_zoom_new () : GtkGesture;
+
+(* GtkGesture *gtk_gesture_rotate_new(void) *)
+PROCEDURE gtk_gesture_rotate_new () : GtkGesture;
+
+(* GtkGesture *gtk_gesture_pan_new(GtkOrientation orientation) *)
+PROCEDURE gtk_gesture_pan_new (orientation: GtkOrientation) : GtkGesture;
+
+(* GtkGesture *gtk_gesture_long_press_new(void) *)
+PROCEDURE gtk_gesture_long_press_new () : GtkGesture;
+
+(* void gtk_gesture_single_set_button(GtkGestureSingle *gesture, guint button) *)
+PROCEDURE gtk_gesture_single_set_button (gesture: GtkGestureSingle;
+                                         button: CARDINAL);
+
+(* guint gtk_gesture_single_get_button(GtkGestureSingle *gesture) *)
+PROCEDURE gtk_gesture_single_get_button (gesture: GtkGestureSingle) : CARDINAL;
+
+END GtkGestures.

+ 35 - 0
src/GtkLevelBar.def

@@ -0,0 +1,35 @@
+DEFINITION MODULE FOR "C" GtkLevelBar ;
+
+(*
+   m2-GTK4 - GtkLevelBar: a segmented level/usage indicator.  Mapped
+   onto <gtk/gtklevelbar.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkLevelBar,
+   gtk_level_bar_new,
+   gtk_level_bar_set_value, gtk_level_bar_get_value,
+   gtk_level_bar_set_min_value, gtk_level_bar_set_max_value;
+
+
+TYPE
+   GtkLevelBar = ADDRESS;
+
+(* GtkWidget *gtk_level_bar_new(void) *)
+PROCEDURE gtk_level_bar_new () : GtkLevelBar;
+
+(* void gtk_level_bar_set_value(GtkLevelBar *self, double value) *)
+PROCEDURE gtk_level_bar_set_value (self: GtkLevelBar; value: REAL);
+
+(* double gtk_level_bar_get_value(GtkLevelBar *self) *)
+PROCEDURE gtk_level_bar_get_value (self: GtkLevelBar) : REAL;
+
+(* void gtk_level_bar_set_min_value(GtkLevelBar *self, double value) *)
+PROCEDURE gtk_level_bar_set_min_value (self: GtkLevelBar; value: REAL);
+
+(* void gtk_level_bar_set_max_value(GtkLevelBar *self, double value) *)
+PROCEDURE gtk_level_bar_set_max_value (self: GtkLevelBar; value: REAL);
+
+END GtkLevelBar.

+ 33 - 0
src/GtkLinkButton.def

@@ -0,0 +1,33 @@
+DEFINITION MODULE FOR "C" GtkLinkButton ;
+
+(*
+   m2-GTK4 - GtkLinkButton: a button that opens a URI.  Mapped onto
+   <gtk/gtklinkbutton.h>.
+
+   Signal: "activate-link" (return TRUE to block the default handler).
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkLinkButton,
+   gtk_link_button_new, gtk_link_button_new_with_label,
+   gtk_link_button_set_uri, gtk_link_button_get_uri;
+
+
+TYPE
+   GtkLinkButton = ADDRESS;
+
+(* GtkWidget *gtk_link_button_new(const char *uri) *)
+PROCEDURE gtk_link_button_new (uri: ARRAY OF CHAR) : GtkLinkButton;
+
+(* GtkWidget *gtk_link_button_new_with_label(const char *uri, const char *label) *)
+PROCEDURE gtk_link_button_new_with_label (uri, label: ARRAY OF CHAR) : GtkLinkButton;
+
+(* void gtk_link_button_set_uri(GtkLinkButton *self, const char *uri) *)
+PROCEDURE gtk_link_button_set_uri (self: GtkLinkButton; uri: ARRAY OF CHAR);
+
+(* const char *gtk_link_button_get_uri(GtkLinkButton *self) *)
+PROCEDURE gtk_link_button_get_uri (self: GtkLinkButton) : ADDRESS;
+
+END GtkLinkButton.

+ 32 - 0
src/GtkMultiSelection.def

@@ -0,0 +1,32 @@
+DEFINITION MODULE FOR "C" GtkMultiSelection ;
+
+(*
+   m2-GTK4 - GtkMultiSelection: a GtkSelectionModel allowing several
+   selected items.  Mapped onto <gtk/gtkmultiselection.h>.
+
+   Like GtkSingleSelection but for multiple selection; combine with the
+   GtkSelectionModel and GtkBitset APIs.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkMultiSelection,
+   gtk_multi_selection_new,
+   gtk_multi_selection_set_model, gtk_multi_selection_get_model;
+
+
+TYPE
+   GtkMultiSelection = ADDRESS;
+
+(* GtkMultiSelection *gtk_multi_selection_new(GListModel *model)
+   Takes ownership of model. *)
+PROCEDURE gtk_multi_selection_new (model: ADDRESS) : GtkMultiSelection;
+
+(* void gtk_multi_selection_set_model(GtkMultiSelection *self, GListModel *model) *)
+PROCEDURE gtk_multi_selection_set_model (self: GtkMultiSelection; model: ADDRESS);
+
+(* GListModel *gtk_multi_selection_get_model(GtkMultiSelection *self) *)
+PROCEDURE gtk_multi_selection_get_model (self: GtkMultiSelection) : ADDRESS;
+
+END GtkMultiSelection.

+ 46 - 0
src/GtkPicture.def

@@ -0,0 +1,46 @@
+DEFINITION MODULE FOR "C" GtkPicture ;
+
+(*
+   m2-GTK4 - GtkPicture: displays a GdkPaintable or an image file.
+   Mapped onto <gtk/gtkpicture.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkPicture, GtkContentFit,
+   GtkContentFitFill, GtkContentFitContain, GtkContentFitCover,
+   GtkContentFitScaleDown,
+   gtk_picture_new, gtk_picture_new_for_filename,
+   gtk_picture_set_filename, gtk_picture_set_can_shrink,
+   gtk_picture_set_content_fit;
+
+
+TYPE
+   GtkPicture = ADDRESS;
+   GtkContentFit = CARDINAL;
+
+CONST
+   GtkContentFitFill      = 0;
+   GtkContentFitContain   = 1;
+   GtkContentFitCover     = 2;
+   GtkContentFitScaleDown = 3;
+
+(* GtkWidget *gtk_picture_new(void) *)
+PROCEDURE gtk_picture_new () : GtkPicture;
+
+(* GtkWidget *gtk_picture_new_for_filename(const char *filename) *)
+PROCEDURE gtk_picture_new_for_filename (filename: ARRAY OF CHAR) : GtkPicture;
+
+(* void gtk_picture_set_filename(GtkPicture *self, const char *filename) *)
+PROCEDURE gtk_picture_set_filename (self: GtkPicture;
+                                    filename: ARRAY OF CHAR);
+
+(* void gtk_picture_set_can_shrink(GtkPicture *self, gboolean can_shrink) *)
+PROCEDURE gtk_picture_set_can_shrink (self: GtkPicture; can_shrink: INTEGER);
+
+(* void gtk_picture_set_content_fit(GtkPicture *self, GtkContentFit content_fit) *)
+PROCEDURE gtk_picture_set_content_fit (self: GtkPicture;
+                                       content_fit: GtkContentFit);
+
+END GtkPicture.

+ 27 - 0
src/GtkScrollbar.def

@@ -0,0 +1,27 @@
+DEFINITION MODULE FOR "C" GtkScrollbar ;
+
+(*
+   m2-GTK4 - GtkScrollbar: a scrollbar driven by a GtkAdjustment.
+   Mapped onto <gtk/gtkscrollbar.h>.
+
+   GtkScrollbar derives from GtkRange, so read/write its value with the
+   GtkRange functions.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM GtkEnums IMPORT GtkOrientation;
+
+EXPORT UNQUALIFIED
+   GtkScrollbar,
+   gtk_scrollbar_new;
+
+
+TYPE
+   GtkScrollbar = ADDRESS;
+
+(* GtkWidget *gtk_scrollbar_new(GtkOrientation orientation,
+                                GtkAdjustment *adjustment) *)
+PROCEDURE gtk_scrollbar_new (orientation: GtkOrientation;
+                             adjustment: ADDRESS) : GtkScrollbar;
+
+END GtkScrollbar.

+ 33 - 0
src/GtkToggleButton.def

@@ -0,0 +1,33 @@
+DEFINITION MODULE FOR "C" GtkToggleButton ;
+
+(*
+   m2-GTK4 - GtkToggleButton: a button with an active/inactive state.
+   Mapped onto <gtk/gtktogglebutton.h>.
+
+   Signal: "toggled".
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkToggleButton,
+   gtk_toggle_button_new, gtk_toggle_button_new_with_label,
+   gtk_toggle_button_set_active, gtk_toggle_button_get_active;
+
+
+TYPE
+   GtkToggleButton = ADDRESS;
+
+(* GtkWidget *gtk_toggle_button_new(void) *)
+PROCEDURE gtk_toggle_button_new () : GtkToggleButton;
+
+(* GtkWidget *gtk_toggle_button_new_with_label(const char *label) *)
+PROCEDURE gtk_toggle_button_new_with_label (label: ARRAY OF CHAR) : GtkToggleButton;
+
+(* void gtk_toggle_button_set_active(GtkToggleButton *self, gboolean is_active) *)
+PROCEDURE gtk_toggle_button_set_active (self: GtkToggleButton; is_active: INTEGER);
+
+(* gboolean gtk_toggle_button_get_active(GtkToggleButton *self) *)
+PROCEDURE gtk_toggle_button_get_active (self: GtkToggleButton) : [ INTEGER ];
+
+END GtkToggleButton.

+ 33 - 0
src/GtkTreeExpander.def

@@ -0,0 +1,33 @@
+DEFINITION MODULE FOR "C" GtkTreeExpander ;
+
+(*
+   m2-GTK4 - GtkTreeExpander: a widget with an expander arrow that
+   drives a GtkTreeListRow.  Mapped onto <gtk/gtktreeexpander.h>.
+
+   Use it inside a list/column view factory: in "bind", set the row with
+   gtk_tree_expander_set_list_row() and the visible content with
+   gtk_tree_expander_set_child().
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkTreeExpander,
+   gtk_tree_expander_new,
+   gtk_tree_expander_set_child, gtk_tree_expander_set_list_row;
+
+
+TYPE
+   GtkTreeExpander = ADDRESS;
+
+(* GtkWidget *gtk_tree_expander_new(void) *)
+PROCEDURE gtk_tree_expander_new () : GtkTreeExpander;
+
+(* void gtk_tree_expander_set_child(GtkTreeExpander *self, GtkWidget *child) *)
+PROCEDURE gtk_tree_expander_set_child (self: GtkTreeExpander; child: ADDRESS);
+
+(* void gtk_tree_expander_set_list_row(GtkTreeExpander *self,
+                                        GtkTreeListRow *list_row) *)
+PROCEDURE gtk_tree_expander_set_list_row (self: GtkTreeExpander; list_row: ADDRESS);
+
+END GtkTreeExpander.

+ 68 - 0
src/GtkTreeListModel.def

@@ -0,0 +1,68 @@
+DEFINITION MODULE FOR "C" GtkTreeListModel ;
+
+(*
+   m2-GTK4 - GtkTreeListModel and GtkTreeListRow: a tree model built from
+   a flat model via a "create children" callback.  Mapped onto
+   <gtk/gtktreelistmodel.h> (which also declares GtkTreeListRow).
+
+   The create callback has the C shape
+
+      GListModel *create(gpointer item, gpointer user_data);
+
+   returning the child model for an item (or NIL if it can never have
+   children).  Pass a top-level Modula-2 PROCEDURE via ADR.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkTreeListModel, GtkTreeListRow,
+   gtk_tree_list_model_new,
+   gtk_tree_list_model_get_model, gtk_tree_list_model_get_child_row,
+   gtk_tree_list_row_get_item, gtk_tree_list_row_get_children,
+   gtk_tree_list_row_set_expanded, gtk_tree_list_row_get_expanded,
+   gtk_tree_list_row_is_expandable, gtk_tree_list_row_get_depth;
+
+
+TYPE
+   GtkTreeListModel = ADDRESS;
+   GtkTreeListRow = ADDRESS;
+
+(* GtkTreeListModel *gtk_tree_list_model_new(GListModel *root,
+                                             gboolean passthrough,
+                                             gboolean autoexpand,
+                                             GtkTreeListModelCreateModelFunc create_func,
+                                             gpointer user_data,
+                                             GDestroyNotify user_destroy) *)
+PROCEDURE gtk_tree_list_model_new (root: ADDRESS; passthrough: INTEGER;
+                                   autoexpand: INTEGER; create_func: ADDRESS;
+                                   user_data: ADDRESS;
+                                   user_destroy: ADDRESS) : GtkTreeListModel;
+
+(* GListModel *gtk_tree_list_model_get_model(GtkTreeListModel *self) *)
+PROCEDURE gtk_tree_list_model_get_model (self: GtkTreeListModel) : ADDRESS;
+
+(* GtkTreeListRow *gtk_tree_list_model_get_child_row(GtkTreeListModel *self,
+                                                      guint position) *)
+PROCEDURE gtk_tree_list_model_get_child_row (self: GtkTreeListModel;
+                                             position: CARDINAL) : GtkTreeListRow;
+
+(* gpointer gtk_tree_list_row_get_item(GtkTreeListRow *self) *)
+PROCEDURE gtk_tree_list_row_get_item (self: GtkTreeListRow) : ADDRESS;
+
+(* GListModel *gtk_tree_list_row_get_children(GtkTreeListRow *self) *)
+PROCEDURE gtk_tree_list_row_get_children (self: GtkTreeListRow) : ADDRESS;
+
+(* void gtk_tree_list_row_set_expanded(GtkTreeListRow *self, gboolean expanded) *)
+PROCEDURE gtk_tree_list_row_set_expanded (self: GtkTreeListRow; expanded: INTEGER);
+
+(* gboolean gtk_tree_list_row_get_expanded(GtkTreeListRow *self) *)
+PROCEDURE gtk_tree_list_row_get_expanded (self: GtkTreeListRow) : [ INTEGER ];
+
+(* gboolean gtk_tree_list_row_is_expandable(GtkTreeListRow *self) *)
+PROCEDURE gtk_tree_list_row_is_expandable (self: GtkTreeListRow) : [ INTEGER ];
+
+(* guint gtk_tree_list_row_get_depth(GtkTreeListRow *self) *)
+PROCEDURE gtk_tree_list_row_get_depth (self: GtkTreeListRow) : CARDINAL;
+
+END GtkTreeListModel.

+ 8 - 1
src/GtkWidget.def

@@ -27,7 +27,8 @@ EXPORT UNQUALIFIED
    gtk_widget_get_width, gtk_widget_get_height,
    gtk_widget_add_css_class, gtk_widget_remove_css_class,
    gtk_widget_has_css_class,
-   gtk_widget_set_parent, gtk_widget_get_parent, gtk_widget_unparent;
+   gtk_widget_set_parent, gtk_widget_get_parent, gtk_widget_unparent,
+   gtk_widget_add_controller, gtk_widget_remove_controller;
 
 
 TYPE
@@ -102,4 +103,10 @@ PROCEDURE gtk_widget_get_parent (widget: GtkWidget) : ADDRESS;
 (* void gtk_widget_unparent(GtkWidget *widget) *)
 PROCEDURE gtk_widget_unparent (widget: GtkWidget);
 
+(* void gtk_widget_add_controller(GtkWidget *widget, GtkEventController *controller) *)
+PROCEDURE gtk_widget_add_controller (widget: GtkWidget; controller: ADDRESS);
+
+(* void gtk_widget_remove_controller(GtkWidget *widget, GtkEventController *controller) *)
+PROCEDURE gtk_widget_remove_controller (widget: GtkWidget; controller: ADDRESS);
+
 END GtkWidget.

+ 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_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_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)"
@@ -45,7 +45,7 @@ for t in $TESTS; do
   $GM2 -I"$ROOT/src" -I"$ROOT/lib" $GM2FLAGS $CFLAGS \
        "$ROOT/tests/$t.mod" "$OBJ/GtkUtils.o" "$OBJ/GtkClosures.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_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_adw|test_application) needs_display=1 ;;
     *) needs_display=0 ;;
   esac
   if [ "$needs_display" = 1 ] && [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then

+ 130 - 0
tests/test_gestures.mod

@@ -0,0 +1,130 @@
+MODULE test_gestures ;
+
+(*
+   m2-GTK4 test - event controllers and gestures.
+
+   Adds several gestures to a widget, checks the controller properties,
+   connects a 5-argument "pressed" handler to a GtkGestureClick and emits
+   the signal to verify the callback ABI.  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;
+FROM GtkBox IMPORT gtk_box_new, gtk_box_append;
+FROM GtkLabel IMPORT gtk_label_new;
+FROM GtkEnums IMPORT GtkVertical, GtkHorizontal;
+FROM GtkWidget IMPORT gtk_widget_add_controller;
+FROM GtkEventController IMPORT gtk_event_controller_set_propagation_phase,
+   gtk_event_controller_get_propagation_phase, gtk_event_controller_set_name,
+   GtkPhaseBubble, gtk_event_controller_motion_new;
+FROM GtkGestures IMPORT gtk_gesture_click_new, gtk_gesture_drag_new,
+   gtk_gesture_swipe_new, gtk_gesture_zoom_new, gtk_gesture_rotate_new,
+   gtk_gesture_pan_new, gtk_gesture_long_press_new,
+   gtk_gesture_single_set_button, gtk_gesture_single_get_button;
+FROM GObject IMPORT g_signal_connect_data, g_signal_emit_by_name,
+   GConnectDefault;
+FROM GtkUtils IMPORT Connect;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestGestures";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+   gotN: INTEGER;
+   gotX, gotY: REAL;
+
+(* void pressed(GtkGestureClick *g, int n_press, double x, double y, gpointer u) *)
+PROCEDURE OnPressed (g: ADDRESS; n_press: INTEGER; x, y: REAL; user: ADDRESS);
+BEGIN
+   gotN := n_press;
+   gotX := x;
+   gotY := y
+END OnPressed;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, click, drag, swipe, zoom, rotate, pan, longpress, motion: ADDRESS;
+   ex, ey: REAL;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+   gotN := 0;
+   gotX := 0.0;
+   gotY := 0.0;
+
+   box := gtk_box_new(GtkVertical, 4);
+
+   click := gtk_gesture_click_new();
+   IF click = NIL THEN printf("click NIL\n"); ok := FALSE END;
+   gtk_widget_add_controller(box, click);
+   gtk_event_controller_set_propagation_phase(click, GtkPhaseBubble);
+   IF gtk_event_controller_get_propagation_phase(click) # GtkPhaseBubble THEN
+      printf("propagation phase wrong\n"); ok := FALSE
+   END;
+   gtk_event_controller_set_name(click, "click-1");
+   gtk_gesture_single_set_button(click, 1);
+   IF gtk_gesture_single_get_button(click) # 1 THEN
+      printf("gesture button wrong\n"); ok := FALSE
+   END;
+
+   drag := gtk_gesture_drag_new();
+   swipe := gtk_gesture_swipe_new();
+   zoom := gtk_gesture_zoom_new();
+   rotate := gtk_gesture_rotate_new();
+   pan := gtk_gesture_pan_new(GtkHorizontal);
+   longpress := gtk_gesture_long_press_new();
+   motion := gtk_event_controller_motion_new();
+   gtk_widget_add_controller(box, drag);
+   gtk_widget_add_controller(box, swipe);
+   gtk_widget_add_controller(box, zoom);
+   gtk_widget_add_controller(box, rotate);
+   gtk_widget_add_controller(box, pan);
+   gtk_widget_add_controller(box, longpress);
+   gtk_widget_add_controller(box, motion);
+   IF (drag = NIL) OR (swipe = NIL) OR (zoom = NIL) OR (rotate = NIL)
+      OR (pan = NIL) OR (longpress = NIL) OR (motion = NIL) THEN
+      printf("a gesture is NIL\n"); ok := FALSE
+   END;
+
+   g_signal_connect_data(click, "pressed", ADR(OnPressed), NIL, NIL,
+                         GConnectDefault);
+   ex := 10.0;
+   ey := 20.0;
+   g_signal_emit_by_name(click, "pressed", 1, ex, ey);
+   IF gotN # 1 THEN printf("n_press wrong\n"); ok := FALSE END;
+   IF ABS(gotX - 10.0) > 0.001 THEN printf("x wrong\n"); ok := FALSE END;
+   IF ABS(gotY - 20.0) > 0.001 THEN printf("y wrong\n"); ok := FALSE END;
+
+   gtk_box_append(box, gtk_label_new("gestures"));
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, box);
+
+   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_gestures: 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_gestures: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_gestures: FAIL\n");
+      HALT(1)
+   END
+END test_gestures.

+ 96 - 0
tests/test_models2.mod

@@ -0,0 +1,96 @@
+MODULE test_models2 ;
+
+(*
+   m2-GTK4 test - multi-selection, bitsets and tree/directory/bookmark
+   models.  Requires GTK initialised (and so a display).
+*)
+
+FROM Gtk IMPORT gtk_init;
+FROM GtkStringList IMPORT gtk_string_list_new, gtk_string_list_append;
+FROM GtkMultiSelection IMPORT gtk_multi_selection_new,
+   gtk_multi_selection_get_model;
+FROM GtkSelectionModel IMPORT gtk_selection_model_select_item,
+   gtk_selection_model_is_selected;
+FROM GtkBitset IMPORT gtk_bitset_new_empty, gtk_bitset_add,
+   gtk_bitset_contains, gtk_bitset_get_size, gtk_bitset_get_nth,
+   gtk_bitset_unref;
+FROM GtkTreeListModel IMPORT gtk_tree_list_model_new,
+   gtk_tree_list_model_get_model, gtk_tree_list_model_get_child_row,
+   gtk_tree_list_row_get_item, gtk_tree_list_row_get_depth;
+FROM GtkDirectoryList IMPORT gtk_directory_list_new;
+FROM GtkBookmarkList IMPORT gtk_bookmark_list_new;
+FROM GObject IMPORT g_object_ref, g_object_unref;
+FROM GListModel IMPORT g_list_model_get_n_items;
+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;
+
+(* GListModel *create(gpointer item, gpointer user_data): no children *)
+PROCEDURE CreateFunc (item: ADDRESS; user: ADDRESS) : ADDRESS;
+BEGIN
+   RETURN NIL
+END CreateFunc;
+
+VAR
+   list, ms, bs, tree, row, dir, book: ADDRESS;
+BEGIN
+   failed := FALSE;
+   gtk_init();
+
+   list := gtk_string_list_new(NIL);
+   gtk_string_list_append(list, "one");
+   gtk_string_list_append(list, "two");
+   gtk_string_list_append(list, "three");
+
+   (* multi-selection: takes ownership of the model *)
+   ms := gtk_multi_selection_new(g_object_ref(list));
+   Check(gtk_multi_selection_get_model(ms) # NIL, "multi selection model");
+   gtk_selection_model_select_item(ms, 1, 0);
+   Check(gtk_selection_model_is_selected(ms, 1) # 0, "item 1 selected");
+   Check(gtk_selection_model_is_selected(ms, 0) = 0, "item 0 not selected");
+
+   (* bitset *)
+   bs := gtk_bitset_new_empty();
+   Check(gtk_bitset_add(bs, 3) # 0, "bitset add");
+   Check(gtk_bitset_contains(bs, 3) # 0, "bitset contains");
+   Check(gtk_bitset_get_size(bs) = 1, "bitset size");
+   Check(gtk_bitset_get_nth(bs, 0) = 3, "bitset nth");
+   gtk_bitset_unref(bs);
+
+   (* tree list model over the same list *)
+   tree := gtk_tree_list_model_new(g_object_ref(list), 0, 1,
+                                   ADR(CreateFunc), NIL, NIL);
+   Check(gtk_tree_list_model_get_model(tree) # NIL, "tree model");
+   Check(g_list_model_get_n_items(gtk_tree_list_model_get_model(tree)) = 3,
+         "tree count");
+   row := gtk_tree_list_model_get_child_row(tree, 0);
+   Check(row # NIL, "tree child row");
+   IF row # NIL THEN
+      Check(gtk_tree_list_row_get_item(row) # NIL, "tree row item");
+      Check(gtk_tree_list_row_get_depth(row) = 0, "tree row depth")
+   END;
+
+   (* directory + bookmark lists construct *)
+   dir := gtk_directory_list_new("standard::name", NIL);
+   Check(dir # NIL, "directory list");
+   book := gtk_bookmark_list_new("/nonexistent-bookmarks", "standard::name");
+   Check(book # NIL, "bookmark list");
+
+   IF failed THEN
+      printf("test_models2: FAIL\n");
+      HALT(1)
+   ELSE
+      printf("test_models2: PASS\n");
+      HALT(0)
+   END
+END test_models2.

+ 123 - 0
tests/test_widgets3.mod

@@ -0,0 +1,123 @@
+MODULE test_widgets3 ;
+
+(*
+   m2-GTK4 test - common widgets: GtkFlowBox, GtkFixed, GtkPicture,
+   GtkToggleButton, GtkLinkButton, GtkCalendar, GtkLevelBar and
+   GtkScrollbar.  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;
+FROM GtkBox IMPORT gtk_box_new, gtk_box_append;
+FROM GtkLabel IMPORT gtk_label_new;
+FROM GtkEnums IMPORT GtkVertical, GtkHorizontal, GtkSelectionSingle;
+FROM GtkFlowBox IMPORT gtk_flow_box_new, gtk_flow_box_append,
+   gtk_flow_box_set_max_children_per_line, gtk_flow_box_set_selection_mode,
+   gtk_flow_box_child_new, gtk_flow_box_child_set_child;
+FROM GtkFixed IMPORT gtk_fixed_new, gtk_fixed_put;
+FROM GtkPicture IMPORT gtk_picture_new, gtk_picture_set_can_shrink,
+   gtk_picture_set_content_fit, GtkContentFitContain;
+FROM GtkToggleButton IMPORT gtk_toggle_button_new_with_label,
+   gtk_toggle_button_set_active, gtk_toggle_button_get_active;
+FROM GtkLinkButton IMPORT gtk_link_button_new, gtk_link_button_get_uri;
+FROM GtkCalendar IMPORT gtk_calendar_new;
+FROM GtkLevelBar IMPORT gtk_level_bar_new, gtk_level_bar_set_value,
+   gtk_level_bar_get_value;
+FROM GtkScrollbar IMPORT gtk_scrollbar_new;
+FROM GtkUtils IMPORT Connect, CStrToM2;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestWidgets3";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, flow, child, fixed, picture, toggle, link, calendar,
+   level, scrollbar: ADDRESS;
+   buf: ARRAY [0..63] OF CHAR;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   flow := gtk_flow_box_new();
+   gtk_flow_box_append(flow, gtk_label_new("a"));
+   gtk_flow_box_append(flow, gtk_label_new("b"));
+   child := gtk_flow_box_child_new();
+   gtk_flow_box_child_set_child(child, gtk_label_new("c"));
+   gtk_flow_box_append(flow, child);
+   gtk_flow_box_set_max_children_per_line(flow, 3);
+   gtk_flow_box_set_selection_mode(flow, GtkSelectionSingle);
+
+   fixed := gtk_fixed_new();
+   gtk_fixed_put(fixed, gtk_label_new("at 10,20"), 10.0, 20.0);
+
+   picture := gtk_picture_new();
+   gtk_picture_set_can_shrink(picture, 1);
+   gtk_picture_set_content_fit(picture, GtkContentFitContain);
+
+   toggle := gtk_toggle_button_new_with_label("Toggle");
+   gtk_toggle_button_set_active(toggle, 1);
+   IF gtk_toggle_button_get_active(toggle) # 1 THEN
+      printf("toggle active wrong\n"); ok := FALSE
+   END;
+
+   link := gtk_link_button_new("https://example.org");
+   CStrToM2(gtk_link_button_get_uri(link), buf);
+   IF (buf[0] # 'h') OR (buf[4] # 's') OR (buf[19] # 0C) THEN
+      printf("link uri wrong: [%s]\n", buf); ok := FALSE
+   END;
+
+   calendar := gtk_calendar_new();
+   IF calendar = NIL THEN printf("calendar NIL\n"); ok := FALSE END;
+
+   level := gtk_level_bar_new();
+   gtk_level_bar_set_value(level, 0.5);
+   IF ABS(gtk_level_bar_get_value(level) - 0.5) > 0.001 THEN
+      printf("level value wrong\n"); ok := FALSE
+   END;
+
+   scrollbar := gtk_scrollbar_new(GtkHorizontal, NIL);
+   IF scrollbar = NIL THEN printf("scrollbar NIL\n"); ok := FALSE END;
+
+   box := gtk_box_new(GtkVertical, 6);
+   gtk_box_append(box, flow);
+   gtk_box_append(box, fixed);
+   gtk_box_append(box, picture);
+   gtk_box_append(box, toggle);
+   gtk_box_append(box, link);
+   gtk_box_append(box, calendar);
+   gtk_box_append(box, level);
+   gtk_box_append(box, scrollbar);
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, box);
+
+   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_widgets3: 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_widgets3: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_widgets3: FAIL\n");
+      HALT(1)
+   END
+END test_widgets3.