|
@@ -0,0 +1,421 @@
|
|
|
|
|
+# m2-GTK4 — GTK4 bindings for GNU Modula-2
|
|
|
|
|
+
|
|
|
|
|
+Thin `DEFINITION MODULE FOR "C"` bindings of **GTK 4** for `gm2`
|
|
|
|
|
+(GCC 16 / GNU Modula-2), plus a small pure-Modula-2 helper layer.
|
|
|
|
|
+
|
|
|
|
|
+The first slice covers the canonical GTK4 application shape
|
|
|
|
|
+(`GtkApplication` + `GtkWindow` + `GtkLabel` / `GtkButton` / `GtkBox`),
|
|
|
|
|
+a working `"activate"` / `"clicked"` signal path, and the GLib/GObject
|
|
|
|
|
+runtime underneath it. The second slice adds the common input widgets:
|
|
|
|
|
+`GtkEntry`/`GtkEditable`, `GtkCheckButton`, `GtkSwitch`, `GtkScale`
|
|
|
|
|
+(`GtkRange`), plus a shared `GtkEnums` module. The third slice adds the
|
|
|
|
|
+layout containers: `GtkGrid`, `GtkStack` + `GtkStackSwitcher`,
|
|
|
|
|
+`GtkNotebook`, `GtkScrolledWindow`. The fourth slice adds actions,
|
|
|
|
|
+menus and accelerators: GIO's `GAction` / `GActionMap` /
|
|
|
|
|
+`GSimpleAction`, `GMenu` / `GMenuModel` / `GVariant`, plus
|
|
|
|
|
+`GtkApplicationWindow` and `GtkPopoverMenuBar`. The fifth slice switches
|
|
|
|
|
+the project to the ISO dialect (`-fiso`) and adds per-connection signal
|
|
|
|
|
+closures (`GtkClosures`) and window chrome (`GtkHeaderBar`,
|
|
|
|
|
+`GtkMenuButton`, `gtk_window_set_titlebar`). The sixth slice adds more
|
|
|
|
|
+inputs: `GtkSpinButton`, `GtkSearchEntry`, `GtkDropDown` (with
|
|
|
|
|
+`GtkStringList` / `GListModel`) and `GtkTextView` + `GtkTextBuffer`.
|
|
|
|
|
+The seventh slice adds more containers: `GtkFrame`, `GtkExpander`,
|
|
|
|
|
+`GtkPaned`, `GtkOverlay`, `GtkListBox` (+ `GtkListBoxRow`). The eighth
|
|
|
|
|
+slice adds GIO extras: `GListStore` (+ `GtkStringObject`), `GFile`,
|
|
|
|
|
+`GtkFileDialog` and `GSettings` (+ `GSettingsSchema`). The ninth slice
|
|
|
|
|
+adds model-backed views: `GtkListView`, `GtkGridView`, `GtkColumnView`
|
|
|
|
|
+(+ `GtkColumnViewColumn` / `GtkColumnViewCell`), the selection models
|
|
|
|
|
+`GtkSingleSelection` / `GtkNoSelection`, and the
|
|
|
|
|
+`GtkSignalListItemFactory` / `GtkListItem` pair. A
|
|
|
|
|
+[`tools/gir2def.py`](#generator-broad-coverage) generator additionally
|
|
|
|
|
+emits broad `.def` coverage from GObject-Introspection `.gir` XML.
|
|
|
|
|
+A tenth slice adds **libadwaita** (AdwApplication, header/toolbar,
|
|
|
|
|
+preferences rows, toast, about/message dialogs) and **GError** handling
|
|
|
|
|
+plus an ownership helper layer.
|
|
|
|
|
+
|
|
|
|
|
+Verified with gm2 16.0.1 (experimental) and GTK 4.18.6 on Linux.
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+
|
|
|
|
|
+| Module | C header | Contents |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `GLib` | `<glib.h>` | base C types, `GMainLoop`, timeouts/idle sources, memory, strings, clock |
|
|
|
|
|
+| `GObject` | `<glib-object.h>` | `ref`/`unref`, `GType`, `g_object_new`, `g_signal_connect_data`, handler disconnect, properties |
|
|
|
|
|
+| `Gio` | `<gio/gapplication.h>` | `GApplication` flags, `run`/`quit`/`hold`/`release` |
|
|
|
|
|
+| `Gtk` | `<gtk/gtk.h>` | `gtk_init`, library version helpers |
|
|
|
|
|
+| `GtkApplication` | `<gtk/gtkapplication.h>` | create/add/remove windows |
|
|
|
|
|
+| `GtkWidget` | `<gtk/gtkwidget.h>` | visibility, sensitivity, margins, expand, align, size |
|
|
|
|
|
+| `GtkWindow` | `<gtk/gtkwindow.h>` | create, title, size, child, present/close/destroy |
|
|
|
|
|
+| `GtkBox` | `<gtk/gtkbox.h>` | orientation, append/prepend/remove, spacing |
|
|
|
|
|
+| `GtkLabel` | `<gtk/gtklabel.h>` | text, markup |
|
|
|
|
|
+| `GtkButton` | `<gtk/gtkbutton.h>` | label buttons |
|
|
|
|
|
+| `GtkEnums` | `<gtk/gtkenums.h>` | orientation, alignment, position constants |
|
|
|
|
|
+| `GtkEditable` | `<gtk/gtkeditable.h>` | text get/set, editable, alignment, selection |
|
|
|
|
|
+| `GtkEntry` | `<gtk/gtkentry.h>` | placeholder, max length, visibility, progress |
|
|
|
|
|
+| `GtkCheckButton` | `<gtk/gtkcheckbutton.h>` | active, label, inconsistent |
|
|
|
|
|
+| `GtkSwitch` | `<gtk/gtkswitch.h>` | active / state |
|
|
|
|
|
+| `GtkRange` | `<gtk/gtkrange.h>` | numeric value (base of GtkScale) |
|
|
|
|
|
+| `GtkScale` | `<gtk/gtkscale.h>` | slider, digits, value position |
|
|
|
|
|
+| `GtkGrid` | `<gtk/gtkgrid.h>` | cell attach, spacing, homogeneous |
|
|
|
|
|
+| `GtkStack` | `<gtk/gtkstack.h>` | named/titled pages, visible child, transitions |
|
|
|
|
|
+| `GtkStackSwitcher` | `<gtk/gtkstackswitcher.h>` | button row that drives a stack |
|
|
|
|
|
+| `GtkNotebook` | `<gtk/gtknotebook.h>` | tabbed pages |
|
|
|
|
|
+| `GtkScrolledWindow` | `<gtk/gtkscrolledwindow.h>` | scrollable viewport, policy |
|
|
|
|
|
+| `GtkApplicationWindow` | `<gtk/gtkapplicationwindow.h>` | app window, window-scoped actions, show-menubar |
|
|
|
|
|
+| `GtkPopoverMenuBar` | `<gtk/gtkpopovermenubar.h>` | in-window menu bar from a model |
|
|
|
|
|
+| `GtkHeaderBar` | `<gtk/gtkheaderbar.h>` | title widget, pack start/end, title buttons, decoration layout |
|
|
|
|
|
+| `GtkMenuButton` | `<gtk/gtkmenubutton.h>` | button that pops up a menu model |
|
|
|
|
|
+| `GAction` | `<gio/gaction.h>` | activate, name, enabled, state |
|
|
|
|
|
+| `GActionMap` | `<gio/gactionmap.h>` | add / lookup / remove named actions |
|
|
|
|
|
+| `GSimpleAction` | `<gio/gsimpleaction.h>` | parameterless and stateful actions |
|
|
|
|
|
+| `GMenu` | `<gio/gmenu.h>` | GMenu + GMenuItem builder |
|
|
|
|
|
+| `GMenuModel` | `<gio/gmenumodel.h>` | model item count |
|
|
|
|
|
+| `GVariant` | `<glib/gvariant.h>` | boolean / string / int32 values |
|
|
|
|
|
+| `GtkSpinButton` | `<gtk/gtkspinbutton.h>` | numeric entry, range/increments/digits/wrap |
|
|
|
|
|
+| `GtkSearchEntry` | `<gtk/gtksearchentry.h>` | search-styled entry, placeholder |
|
|
|
|
|
+| `GListModel` | `<gio/glistmodel.h>` | indexed model interface |
|
|
|
|
|
+| `GtkStringList` | `<gtk/gtkstringlist.h>` | simple string model |
|
|
|
|
|
+| `GtkDropDown` | `<gtk/gtkdropdown.h>` | choose one item from a model |
|
|
|
|
|
+| `GtkTextIter` | `<gtk/gtktextiter.h>` | opaque 80-byte iterator (by address) |
|
|
|
|
|
+| `GtkTextBuffer` | `<gtk/gtktextbuffer.h>` | text model for a text view |
|
|
|
|
|
+| `GtkTextView` | `<gtk/gtktextview.h>` | multi-line text widget |
|
|
|
|
|
+| `GtkFrame` | `<gtk/gtkframe.h>` | labelled border around one child |
|
|
|
|
|
+| `GtkExpander` | `<gtk/gtkexpander.h>` | collapsible section |
|
|
|
|
|
+| `GtkPaned` | `<gtk/gtkpaned.h>` | two panes with a draggable divider |
|
|
|
|
|
+| `GtkOverlay` | `<gtk/gtkoverlay.h>` | stack overlays on a main child |
|
|
|
|
|
+| `GtkListBox` | `<gtk/gtklistbox.h>` | vertical row list + `GtkListBoxRow` |
|
|
|
|
|
+| `GListStore` | `<gio/gliststore.h>` | in-memory list model |
|
|
|
|
|
+| `GtkStringObject` | `<gtk/gtkstringobject.h>` | GObject string wrapper (item type) |
|
|
|
|
|
+| `GtkSignalListItemFactory` | `<gtk/gtksignallistitemfactory.h>` | setup/bind/teardown item factory |
|
|
|
|
|
+| `GtkListItem` | `<gtk/gtklistitem.h>` | per-row object passed to a factory |
|
|
|
|
|
+| `GtkSelectionModel` | `<gtk/gtkselectionmodel.h>` | selection interface for a view |
|
|
|
|
|
+| `GtkSingleSelection` | `<gtk/gtksingleselection.h>` | zero/one selected item |
|
|
|
|
|
+| `GtkNoSelection` | `<gtk/gtknoselection.h>` | display-only (no selection) |
|
|
|
|
|
+| `GtkListView` | `<gtk/gtklistview.h>` | model-driven list |
|
|
|
|
|
+| `GtkGridView` | `<gtk/gtkgridview.h>` | model-driven wrapping grid |
|
|
|
|
|
+| `GtkColumnView` | `<gtk/gtkcolumnview.h>` | model-driven multi-column table |
|
|
|
|
|
+| `GtkColumnViewColumn` | `<gtk/gtkcolumnviewcolumn.h>` | one column |
|
|
|
|
|
+| `GtkColumnViewCell` | `<gtk/gtkcolumnviewcell.h>` | column cell (`GtkListItem` subclass) |
|
|
|
|
|
+| `GFile` | `<gio/gfile.h>` | path/URI file handle |
|
|
|
|
|
+| `GtkFileDialog` | `<gtk/gtkfiledialog.h>` | async native open/save/folder dialog |
|
|
|
|
|
+| `GSettings` | `<gio/gsettings.h>` | typed schema keys |
|
|
|
|
|
+| `GSettingsSchema` | `<gio/gsettingsschema.h>` | schema lookup / key check |
|
|
|
|
|
+| `GError` | `<glib/gerror.h>` | error value (message/domain/code) |
|
|
|
|
|
+| `GioError` | `<gio/gioerror.h>` | GIO error domain and codes |
|
|
|
|
|
+| `AdwApplication` | `<libadwaita-1/adw-application.h>` | Adwaita application |
|
|
|
|
|
+| `AdwApplicationWindow` | `<libadwaita-1/adw-application-window.h>` | Adwaita window |
|
|
|
|
|
+| `AdwHeaderBar` | `<libadwaita-1/adw-header-bar.h>` | header bar |
|
|
|
|
|
+| `AdwToolbarView` | `<libadwaita-1/adw-toolbar-view.h>` | top/bottom bars + content |
|
|
|
|
|
+| `AdwPreferencesPage` | `<libadwaita-1/adw-preferences-page.h>` | page of groups |
|
|
|
|
|
+| `AdwPreferencesGroup` | `<libadwaita-1/adw-preferences-group.h>` | titled group of rows |
|
|
|
|
|
+| `AdwPreferencesRow` | `<libadwaita-1/adw-preferences-row.h>` | base row (title) |
|
|
|
|
|
+| `AdwActionRow` | `<libadwaita-1/adw-action-row.h>` | row with subtitle/prefix/suffix |
|
|
|
|
|
+| `AdwSwitchRow` | `<libadwaita-1/adw-switch-row.h>` | row with a switch |
|
|
|
|
|
+| `AdwEntryRow` | `<libadwaita-1/adw-entry-row.h>` | row with an entry |
|
|
|
|
|
+| `AdwComboRow` | `<libadwaita-1/adw-combo-row.h>` | row with a drop-down |
|
|
|
|
|
+| `AdwExpanderRow` | `<libadwaita-1/adw-expander-row.h>` | row that expands |
|
|
|
|
|
+| `AdwStatusPage` | `<libadwaita-1/adw-status-page.h>` | placeholder page |
|
|
|
|
|
+| `AdwToast` | `<libadwaita-1/adw-toast.h>` | transient notification |
|
|
|
|
|
+| `AdwToastOverlay` | `<libadwaita-1/adw-toast-overlay.h>` | toast host |
|
|
|
|
|
+| `AdwBanner` | `<libadwaita-1/adw-banner.h>` | dismissible info bar |
|
|
|
|
|
+| `AdwClamp` | `<libadwaita-1/adw-clamp.h>` | max-size centred container |
|
|
|
|
|
+| `AdwAboutWindow` | `<libadwaita-1/adw-about-window.h>` | about dialog |
|
|
|
|
|
+| `AdwMessageDialog` | `<libadwaita-1/adw-message-dialog.h>` | message / confirm dialog |
|
|
|
|
|
+| `GtkUtils` (`lib/`) | — | `CStrToM2`, `StrAppend`, `IntToStr`, `Connect`, `SetAccel`, `Unref`, `Free`, `ErrorMessage`, `ClearError` |
|
|
|
|
|
+| `GtkClosures` (`lib/`) | — | per-connection signal contexts (`Connect2`/`Connect3`, owned variants) |
|
|
|
|
|
+
|
|
|
|
|
+## Layout
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+m2-GTK4/
|
|
|
|
|
+ src/ FOR "C" .def bindings (one per GTK/GIO object)
|
|
|
|
|
+ lib/ pure-M2 helpers built on the bindings (GtkUtils, GtkClosures)
|
|
|
|
|
+ tests/ gm2 smoke tests + run_tests.sh (+ a private GSettings schema)
|
|
|
|
|
+ examples/ runnable GTK4 programs (hello, counter, ...)
|
|
|
|
|
+ tools/ gir2def.py (GIR -> .def generator) + run_gir_check.sh
|
|
|
|
|
+ gen/ generator output (git-ignored)
|
|
|
|
|
+ build/ generated binaries (git-ignored)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Prerequisites
|
|
|
|
|
+
|
|
|
|
|
+- `gm2` (tested: 16.0.1 experimental, `~/bin/Modula2/Gm2/bin/gm2`)
|
|
|
|
|
+- GTK4 dev files (`pkg-config --cflags --libs gtk4`, tested: 4.18.6)
|
|
|
|
|
+- libadwaita dev files (`pkg-config --cflags --libs libadwaita-1`, tested:
|
|
|
|
|
+ 1.7.6) — optional; enables the `Adw*` modules, `test_adw` and the
|
|
|
|
|
+ `adw` example. The build detects them automatically.
|
|
|
|
|
+
|
|
|
|
|
+The build uses the **ISO dialect** (`-fiso`). That is required for
|
|
|
|
|
+`SYSTEM.CAST` and for `NEW`/`DISPOSE` (which need `ALLOCATE`/`DEALLOCATE`
|
|
|
|
|
+from the ISO `Storage` module), both used by `GtkClosures`. `make`
|
|
|
|
|
+passes `-fiso` by default.
|
|
|
|
|
+
|
|
|
|
|
+```sh
|
|
|
|
|
+export PATH=$HOME/bin/Modula2/Gm2/bin:$PATH
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Build & test
|
|
|
|
|
+
|
|
|
|
|
+```sh
|
|
|
|
|
+make # tests + examples -> build/
|
|
|
|
|
+./tests/run_tests.sh
|
|
|
|
|
+./build/examples/hello
|
|
|
|
|
+./build/examples/counter
|
|
|
|
|
+./build/examples/inputs
|
|
|
|
|
+./build/examples/containers
|
|
|
|
|
+./build/examples/actions
|
|
|
|
|
+./build/examples/headerbar
|
|
|
|
|
+./build/examples/editor
|
|
|
|
|
+./build/examples/layouts
|
|
|
|
|
+./build/examples/files
|
|
|
|
|
+./build/examples/views
|
|
|
|
|
+./build/examples/adw
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`make check` is `make tests` + the runner script. The GUI tests are
|
|
|
|
|
+skipped automatically when neither `DISPLAY` nor `WAYLAND_DISPLAY` is
|
|
|
|
|
+set. The runner also compiles `tests/schemas/*.gschema.xml` into
|
|
|
|
|
+`build/schemas` and points `GSETTINGS_SCHEMA_DIR` at it, so `test_gio`
|
|
|
|
|
+can exercise `GSettings` without touching real user settings.
|
|
|
|
|
+
|
|
|
|
|
+## Generator (broad coverage)
|
|
|
|
|
+
|
|
|
|
|
+The hand-written modules in `src/` are the idiomatic API. For breadth,
|
|
|
|
|
+`tools/gir2def.py` generates `FOR "C"` `.def` files directly from
|
|
|
|
|
+GObject-Introspection `.gir` XML:
|
|
|
|
|
+
|
|
|
|
|
+```sh
|
|
|
|
|
+make gir # Gtk-4.0 -> gen/ (301 modules, ~3.7k procedures)
|
|
|
|
|
+make gir GIR=Gio-2.0 # any namespace
|
|
|
|
|
+make gir-check # generate + compile/link a sample
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+It emits one module per class/interface/record (named `<Namespace><Name>`,
|
|
|
|
|
+e.g. `GtkButton`), namespace functions in `<Namespace>Functions.def`, and
|
|
|
|
|
+enums/bitfields in `<Namespace>Enums.def`. Procedure names are the exact
|
|
|
|
|
+GIR `c:identifier`, so the output links straight against the C library.
|
|
|
|
|
+All 633 generated modules across Gtk/GLib/Gio/Adw parse under `gm2 -fiso`.
|
|
|
|
|
+
|
|
|
|
|
+`make gir-verify` closes the loop by checking that every generated
|
|
|
|
|
+identifier actually exists as an exported symbol in the installed
|
|
|
|
|
+library (via `nm`); it reports 0 unexpected across Gtk, GLib, Gio and
|
|
|
|
|
+Adw (a short allowlist covers macros/deprecated no-ops).
|
|
|
|
|
+
|
|
|
|
|
+See `tools/README.md` for the type mapping and the known limits (value
|
|
|
|
|
+structs are opaque, signals/properties are not generated, variadics and
|
|
|
|
|
+`GError**` are dropped). Output goes to `gen/` and is meant to be
|
|
|
|
|
+cherry-picked, not to replace `src/`.
|
|
|
|
|
+
|
|
|
|
|
+## Usage
|
|
|
|
|
+
|
|
|
|
|
+```modula2
|
|
|
|
|
+MODULE hello ;
|
|
|
|
|
+
|
|
|
|
|
+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 GtkLabel IMPORT gtk_label_new;
|
|
|
|
|
+FROM GtkUtils IMPORT Connect;
|
|
|
|
|
+FROM SYSTEM IMPORT ADDRESS, ADR;
|
|
|
|
|
+
|
|
|
|
|
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
|
|
|
|
|
+VAR window, label: ADDRESS;
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ window := gtk_application_window_new(application);
|
|
|
|
|
+ gtk_window_set_title(window, "Hello");
|
|
|
|
|
+ gtk_window_set_default_size(window, 360, 120);
|
|
|
|
|
+ label := gtk_label_new("Hello, GNU Modula-2 + GTK4!");
|
|
|
|
|
+ gtk_window_set_child(window, label);
|
|
|
|
|
+ gtk_window_present(window)
|
|
|
|
|
+END OnActivate;
|
|
|
|
|
+
|
|
|
|
|
+VAR app: ADDRESS;
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ app := gtk_application_new("org.example.Hello", 0);
|
|
|
|
|
+ Connect(app, "activate", ADR(OnActivate), NIL);
|
|
|
|
|
+ g_application_run(app, 0, NIL)
|
|
|
|
|
+END hello.
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Compile a program against the bindings:
|
|
|
|
|
+
|
|
|
|
|
+```sh
|
|
|
|
|
+gm2 -fiso -Isrc -Ilib myprog.mod build/objs/GtkUtils.o \
|
|
|
|
|
+ build/objs/GtkClosures.o -o myprog \
|
|
|
|
|
+ $(pkg-config --cflags --libs gtk4)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## How it was built, step by step
|
|
|
|
|
+
|
|
|
|
|
+**Step 0 — feasibility spike.** Before writing anything, the riskiest
|
|
|
|
|
+interop points were proved against the real library: creating a GTK4
|
|
|
|
|
+window from Modula-2, running the GLib main loop, and invoking a
|
|
|
|
|
+Modula-2 procedure from C as a `GSourceFunc`. This fixed the design.
|
|
|
|
|
+
|
|
|
|
|
+**Step 1 — discover the GNU Modula-2 ↔ C rules.** See the next section.
|
|
|
|
|
+These rules decide every line of a binding `.def`.
|
|
|
|
|
+
|
|
|
|
|
+**Step 2 — GLib and GObject.** Everything else sits on the base types,
|
|
|
|
|
+the main loop and the signal machinery, so these came first.
|
|
|
|
|
+
|
|
|
|
|
+**Step 3 — application and window.** `GtkApplication` + `GApplication`
|
|
|
|
|
+drive the loop; `GtkWindow` gives something to show. Using
|
|
|
|
|
+`gtk_application_window_new(app)` (not `gtk_window_new()`) is what
|
|
|
|
|
+keeps the application alive while the window is open.
|
|
|
|
|
+
|
|
|
|
|
+**Step 4 — widgets and layout.** `GtkBox`, `GtkLabel`, `GtkButton`, and
|
|
|
|
|
+the per-widget `GtkWidget` layout helpers.
|
|
|
|
|
+
|
|
|
|
|
+**Step 5 — helpers and signal ergonomics.** `GtkUtils.Connect` wraps the
|
|
|
|
|
+`g_signal_connect` macro (which has no C symbol) by calling the real
|
|
|
|
|
+`g_signal_connect_data`; `CStrToM2` and `IntToStr` handle the
|
|
|
|
|
+string/number conversions GTK code always needs.
|
|
|
|
|
+
|
|
|
|
|
+**Step 6 — tests and examples.** A headless GLib loop test, a
|
|
|
|
|
+version/link test, a widget + signal round-trip test, and an
|
|
|
|
|
+application test; plus `hello` and `counter` examples.
|
|
|
|
|
+
|
|
|
|
|
+**Step 7 — input widgets.** `GtkEnums` was extracted as the shared home
|
|
|
|
|
+for enum constants, then `GtkEditable`/`GtkEntry`,
|
|
|
|
|
+`GtkCheckButton`, `GtkSwitch`, `GtkRange`/`GtkScale` were added,
|
|
|
|
|
+with a `test_inputs` round-trip test and the `inputs` example. This
|
|
|
|
|
+step also pinned down the real-number mapping (`REAL` = C `double`).
|
|
|
|
|
+
|
|
|
|
|
+**Step 8 — layout containers.** `GtkGrid`, `GtkStack` +
|
|
|
|
|
+`GtkStackSwitcher`, `GtkNotebook` and `GtkScrolledWindow`, with a
|
|
|
|
|
+`test_containers` test and the `containers` example (a notebook whose
|
|
|
|
|
+pages are a grid, a switcher-driven stack, and a scrolled list).
|
|
|
|
|
+
|
|
|
|
|
+**Step 9 — actions, menus, accelerators.** GIO's action/menu stack
|
|
|
|
|
+(`GAction`, `GActionMap`, `GSimpleAction`, `GMenu`, `GMenuModel`,
|
|
|
|
|
+`GVariant`), `GtkApplicationWindow`, and `GtkPopoverMenuBar`, wired
|
|
|
|
|
+together in the `actions` example (menu bar + app/win actions +
|
|
|
|
|
+Ctrl+N/Q/B accelerators via `GtkUtils.SetAccel`) and checked by
|
|
|
|
|
+`test_actions`.
|
|
|
|
|
+
|
|
|
|
|
+**Step 10 — ISO dialect and per-connection closures.** The project was
|
|
|
|
|
+switched to `-fiso`, which provides `SYSTEM.CAST` and intrinsic
|
|
|
|
|
+`NEW`/`DISPOSE` (with `Storage`'s `ALLOCATE`/`DEALLOCATE`). That made
|
|
|
|
|
+the `GtkClosures` module possible: each signal connection gets its own
|
|
|
|
|
+heap context holding the handler and payload, a trampoline forwards the
|
|
|
|
|
+call, and a GClosure destroy notify frees the context - and optionally
|
|
|
|
|
+the payload - on disconnect or finalize. Covered by `test_closure`.
|
|
|
|
|
+
|
|
|
|
|
+**Step 11 — header bar and window controls.** `GtkHeaderBar`,
|
|
|
|
|
+`GtkMenuButton`, and `gtk_window_set_titlebar()` were added, then
|
|
|
|
|
+combined in the `headerbar` example (title widget, a connected "New"
|
|
|
|
|
+button packed start, a menu button packed end) and checked by
|
|
|
|
|
+`test_headerbar`.
|
|
|
|
|
+
|
|
|
|
|
+**Step 12 — more inputs.** `GtkSpinButton`, `GtkSearchEntry`,
|
|
|
|
|
+`GtkStringList` + `GListModel`, `GtkDropDown`, and `GtkTextView` +
|
|
|
|
|
+`GtkTextBuffer` with an opaque 80-byte `GtkTextIter` marshalled by
|
|
|
|
|
+address. Checked by `test_inputs2` and shown in the `editor` example
|
|
|
|
|
+(search entry, text view, wrap-mode drop-down, padding spin button).
|
|
|
|
|
+
|
|
|
|
|
+**Step 13 — more containers.** `GtkFrame`, `GtkExpander`, `GtkPaned`,
|
|
|
|
|
+`GtkOverlay`, and `GtkListBox` + `GtkListBoxRow` (with
|
|
|
|
|
+`GtkSelectionMode` in `GtkEnums`). Checked by `test_containers2` and
|
|
|
|
|
+shown in the `layouts` example (a paned split, frame/expander on the
|
|
|
|
|
+left, overlay + list box on the right, `row-activated` via
|
|
|
|
|
+`GtkClosures.Connect3`).
|
|
|
|
|
+
|
|
|
|
|
+**Step 14 — GIO extras.** `GListStore` + `GtkStringObject` (a concrete
|
|
|
|
|
+list model), `GFile`, the asynchronous `GtkFileDialog`, and `GSettings`
|
|
|
|
|
++ `GSettingsSchema`. Checked by `test_gio` (headless) and
|
|
|
|
|
+`test_filedialog`; shown in the `files` example (button -> native open
|
|
|
|
|
+dialog -> `open_finish` -> `GFile` basename). The GSettings test uses a
|
|
|
|
|
+private schema compiled by the runner.
|
|
|
|
|
+
|
|
|
|
|
+**Step 15 — model-backed views.** `GtkSignalListItemFactory` +
|
|
|
|
|
+`GtkListItem`, the selection models `GtkSingleSelection` /
|
|
|
|
|
+`GtkNoSelection`, and the views `GtkListView`, `GtkGridView`,
|
|
|
|
|
+`GtkColumnView` (+ `GtkColumnViewColumn`, `GtkColumnViewCell`). Checked
|
|
|
|
|
+by `test_views`, which presents a window and asserts (from the main
|
|
|
|
|
+loop) that the factory "setup"/"bind" callbacks ran, and shown in the
|
|
|
|
|
+`views` example (one store feeding list, grid and column views).
|
|
|
|
|
+
|
|
|
|
|
+**Step 16 — generator verification.** `tools/gir_verify.py` collects every
|
|
|
|
|
+`c:identifier` that `gir2def.py` would emit and checks it against the
|
|
|
|
|
+exported symbols of the libraries `pkg-config` links for the namespace
|
|
|
|
|
+(symbol-version suffixes stripped). `make gir-verify` reports 0
|
|
|
|
|
+unexpected across Gtk/GLib/Gio/Adw, with a small allowlist for GIR-listed
|
|
|
|
|
+macros and per-module ABI functions. `tools/run_gir_check.sh` also
|
|
|
|
|
+bulk-parses every generated module.
|
|
|
|
|
+
|
|
|
|
|
+**Step 17 — libadwaita.** Curated `Adw*` modules (application, window,
|
|
|
|
|
+header bar, toolbar view, preferences page/group and the action/switch/
|
|
|
|
|
+entry/combo/expander rows, status page, toast/overlay, banner, clamp,
|
|
|
|
|
+about and message dialogs), checked by `test_adw` and shown in the `adw`
|
|
|
|
|
+example. libadwaita is optional in the build.
|
|
|
|
|
+
|
|
|
|
|
+**Step 18 — GError and ownership.** `GError` (with its public struct
|
|
|
|
|
+layout) and `GioError` (domain + codes), plus `GtkUtils.Unref`/`Free`/
|
|
|
|
|
+`ErrorMessage`/`ClearError` and a documented ownership discipline.
|
|
|
|
|
+Checked by `test_gerror` (a real `GIO_ERROR_NOT_FOUND` from
|
|
|
|
|
+`g_file_delete`).
|
|
|
|
|
+
|
|
|
|
|
+## GNU Modula-2 interop rules learned
|
|
|
|
|
+
|
|
|
|
|
+1. Declare the module `DEFINITION MODULE FOR "C" Name ;` and list every
|
|
|
|
|
+ export in `EXPORT UNQUALIFIED`. Without `EXPORT UNQUALIFIED` the
|
|
|
|
|
+ compiler prefixes symbols with `Module_`, and the link fails.
|
|
|
|
|
+2. Formal parameters in a **procedure type** must be unnamed:
|
|
|
|
|
+ `GSourceFunc = PROCEDURE (ADDRESS) : INTEGER;` (named parameters are
|
|
|
|
|
+ a syntax error in gm2's default dialect).
|
|
|
|
|
+3. A function result that callers may ignore is declared `: [ T ]`
|
|
|
|
|
+ (e.g. `PROCEDURE g_timeout_add (...) : [ guint ];`). Without the
|
|
|
|
|
+ brackets, ignoring the result is a hard error. This also works in
|
|
|
|
|
+ ordinary modules — `GtkUtils.Connect` uses it.
|
|
|
|
|
+4. `ARRAY OF CHAR` maps onto `char *`, so string literals and `CHAR`
|
|
|
|
|
+ arrays can be passed straight to `const char *` parameters.
|
|
|
|
|
+5. `VAR` record/string parameters map onto `T *`; opaque handles
|
|
|
|
|
+ (`GObject *`, `GtkWidget *`, ...) travel as `ADDRESS` (use `NIL` to
|
|
|
|
|
+ test for `NULL`).
|
|
|
|
|
+6. `BOOLEAN` is not `gboolean`: pass `INTEGER` with `GLib.gTrue` /
|
|
|
|
|
+ `GLib.gFalse`, or use `INTEGER` directly.
|
|
|
|
|
+7. A top-level Modula-2 procedure is a plain C function pointer; pass
|
|
|
|
|
+ it directly to a callback-typed parameter, or use `ADR(proc)` where
|
|
|
|
|
+ the binding takes an `ADDRESS`/`GCallback`.
|
|
|
|
|
+8. **Real numbers:** gm2 `REAL` is 8 bytes and matches C `double`;
|
|
|
|
|
+ gm2 `SHORTREAL` is 4 bytes and matches C `float`; gm2 `LONGREAL` is
|
|
|
|
|
+ 16 bytes and matches nothing in GTK. Use `REAL` for `gdouble` /
|
|
|
|
|
+ `double` (e.g. `GtkRange` values, `GtkScale` ranges).
|
|
|
|
|
+9. Beware `*)` and `(*` inside comments — they close/open a comment
|
|
|
|
|
+ early. Write `void ptr`, not `void *`, in prose.
|
|
|
|
|
+10. gm2 has no pointer arithmetic and its `T(x)` cast requires equal
|
|
|
|
|
+ sizes; `CStrToM2` therefore copies with libc `strncpy` instead of
|
|
|
|
|
+ walking a `char *`.
|
|
|
|
|
+11. `GVariant` values from `g_variant_new_*` are *floating*: ownership
|
|
|
|
|
+ transfers to the callee (e.g. `g_simple_action_new_stateful`).
|
|
|
|
|
+ Values returned by `g_action_get_state()` / `g_variant_get_*` own a
|
|
|
|
|
+ reference and must be released with `g_variant_unref()`.
|
|
|
|
|
+12. **ISO dialect.** Compiled with `-fiso`: `CAST(T, expr)` (ISO
|
|
|
|
|
+ `SYSTEM`) and `NEW`/`DISPOSE` (with `Storage`'s
|
|
|
|
|
+ `ALLOCATE`/`DEALLOCATE`) are available there but not in PIM.
|
|
|
|
|
+13. When a parameter is typed as a **procedure type**, pass the procedure
|
|
|
|
|
+ directly (`Connect2(b, "clicked", OnClick, ...)`), not `ADR(OnClick)`
|
|
|
|
|
+ — gm2 type-checks it. Use `ADR(proc)` only where the parameter is a
|
|
|
|
|
+ plain `ADDRESS` / `GCallback`.
|
|
|
|
|
+14. A large opaque C struct that is always passed by address (e.g.
|
|
|
|
|
+ `GtkTextIter`) can be modelled as a record of `ADDRESS` words sized
|
|
|
|
|
+ to `sizeof` it: that gives the right size *and* 64-bit alignment.
|
|
|
|
|
+ `GtkTextIter` is 80 bytes, hence `ARRAY [0..9] OF ADDRESS`.
|
|
|
|
|
+
|
|
|
|
|
+## Roadmap
|
|
|
|
|
+
|
|
|
|
|
+The original roadmap is complete: GLib/GObject core, widgets, inputs,
|
|
|
|
|
+containers, actions/menus/accelerators, header bars, GIO extras,
|
|
|
|
|
+model-backed views, and the GIR generator are all in. On top of that:
|
|
|
|
|
+generator verification (`make gir-verify`), curated **libadwaita**, and
|
|
|
|
|
+**GError** + ownership helpers.
|
|
|
|
|
+
|
|
|
|
|
+Possible next steps:
|
|
|
|
|
+
|
|
|
|
|
+1. Hand-curate more of the generated surface (signals, properties,
|
|
|
|
|
+ value-struct records, `GtkCssProvider` styling).
|
|
|
|
|
+2. Turn the generator's `ADDRESS` placeholders into cross-module type
|
|
|
|
|
+ aliases and `[ ]`/`VAR` refinements using the GIR annotations.
|
|
|
|
|
+3. Grow the test suite around the newly generated modules.
|
|
|
|
|
+4. Prune/promote useful `gen/` output into `src/` as it proves out.
|