Browse Source

feat: CSS styling, popovers and modern dialogs

CSS:
- src/Gdk.def (default display, GdkRGBA), src/GtkCssProvider.def,
  src/GtkStyleContext.def (provider-for-display + priorities)
- GtkWidget: add/remove/has_css_class, set/get_parent, unparent
- test_css, examples/styling.mod

Popovers:
- src/GtkPopover.def, src/GtkPopoverMenu.def
- GtkMenuButton: set/get_popover
- test_popover, examples/popover.mod

Dialogs:
- src/GtkAlertDialog.def (variadic new, show, async choose)
- src/GtkColorDialog.def (async choose_rgba)
- test_dialogs, examples/dialogs.mod

21 tests pass; 95 src modules, 16 examples.
Eric Streit 2 days ago
parent
commit
980a4b2cc7

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

+ 8 - 1
README.md

@@ -20,7 +20,7 @@ Verified with `gm2` 16.0.1 (experimental), GTK 4.18.6 and libadwaita 1.7.6.
 - **Pure-Modula-2 helpers**: `GtkUtils` (string/number conversion,
   `Connect`, `SetAccel`, ownership and `GError` helpers) and
   `GtkClosures` (per-connection signal contexts with cleanup).
-- **18 tests** and **13 runnable examples**.
+- **21 tests** and **16 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.
@@ -114,6 +114,9 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `views` | `GtkListView` / `GtkGridView` / `GtkColumnView` from a store |
 | `builder` | load a window from a `.ui` file with `GtkBuilder` |
 | `feedback` | progress bar, spinner, separator, image |
+| `styling` | CSS provider and per-widget CSS classes |
+| `popover` | popover and menu popover |
+| `dialogs` | `GtkAlertDialog` and `GtkColorDialog` |
 | `adw` | libadwaita preferences app (toolbar view, rows, toast, about) |
 
 ## Module reference
@@ -129,6 +132,7 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `GError` | `<glib/gerror.h>` | error value (message/domain/code) |
 | `GioError` | `<gio/gioerror.h>` | GIO error domain and codes |
 | `Gtk` | `<gtk/gtk.h>` | `gtk_init`, version helpers |
+| `Gdk` | `<gdk/gdkdisplay.h>` | default display, `GdkRGBA` |
 | `GtkEnums` | `<gtk/gtkenums.h>` | orientation, alignment, wrap, selection, position |
 
 ### GIO objects and models
@@ -151,7 +155,10 @@ gm2 -fiso -Isrc -Ilib myprog.mod \
 | `GtkTextView`, `GtkTextBuffer`, `GtkTextIter` | multi-line text |
 | `GtkAdjustment`, `GtkProgressBar`, `GtkSpinner`, `GtkSeparator`, `GtkImage` | value model and feedback/decoration widgets |
 | `GtkHeaderBar`, `GtkMenuButton`, `GtkPopoverMenuBar` | window chrome and menus |
+| `GtkPopover`, `GtkPopoverMenu` | popovers and menu popovers |
 | `GtkBuilder` | load widget trees from `.ui` XML (with `GError`) |
+| `GtkAlertDialog`, `GtkColorDialog` | modern alert and colour dialogs |
+| `GtkCssProvider`, `GtkStyleContext` | CSS styling for the display |
 | `GtkListBox`, `GtkListView`, `GtkGridView`, `GtkColumnView`, `GtkColumnViewColumn`, `GtkColumnViewCell`, `GtkListItem`, `GtkSignalListItemFactory`, `GtkSelectionModel`, `GtkSingleSelection`, `GtkNoSelection` | lists, grids and tables |
 
 ### libadwaita

+ 136 - 0
examples/dialogs.mod

@@ -0,0 +1,136 @@
+MODULE dialogs ;
+
+(*
+   m2-GTK4 example - modern dialogs.
+
+   "Alert" shows a GtkAlertDialog with custom buttons; "Choose colour"
+   opens a GtkColorDialog asynchronously and reports the chosen GdkRGBA
+   in a label.
+
+   Build:  make examples
+   Run:    ./build/examples/dialogs
+*)
+
+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 GtkButton IMPORT gtk_button_new_with_label;
+FROM GtkWidget IMPORT gtk_widget_set_margin_top,
+   gtk_widget_set_margin_bottom, gtk_widget_set_margin_start,
+   gtk_widget_set_margin_end;
+FROM GtkAlertDialog IMPORT gtk_alert_dialog_new,
+   gtk_alert_dialog_set_message, gtk_alert_dialog_set_detail,
+   gtk_alert_dialog_set_buttons, gtk_alert_dialog_set_default_button,
+   gtk_alert_dialog_show;
+FROM GtkColorDialog IMPORT gtk_color_dialog_new, gtk_color_dialog_set_title,
+   gtk_color_dialog_set_modal, gtk_color_dialog_choose_rgba,
+   gtk_color_dialog_choose_rgba_finish;
+FROM GError IMPORT GError;
+FROM Gdk IMPORT gdk_rgba_free, gdk_rgba_to_string, GdkRGBAPtr;
+FROM GtkUtils IMPORT CStrToM2, Free, ClearError;
+FROM GtkClosures IMPORT Connect2;
+FROM SYSTEM IMPORT ADDRESS, ADR, CAST;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.Dialogs";
+
+VAR
+   window, status, alert, colorDialog: ADDRESS;
+   okLabel: ARRAY [0..2] OF CHAR;
+   cancelLabel: ARRAY [0..7] OF CHAR;
+   buttons: ARRAY [0..2] OF ADDRESS;
+
+PROCEDURE OnAlert (button: ADDRESS; user: ADDRESS);
+BEGIN
+   gtk_alert_dialog_show(alert, window)
+END OnAlert;
+
+(* void chosen(GObject *source, GAsyncResult *result, gpointer user_data) *)
+PROCEDURE OnColorChosen (source: ADDRESS; result: ADDRESS; user: ADDRESS);
+VAR
+   rgba: ADDRESS;
+   p: GdkRGBAPtr;
+   str: ADDRESS;
+   buf: ARRAY [0..63] OF CHAR;
+   err: GError;
+BEGIN
+   err := NIL;
+   rgba := gtk_color_dialog_choose_rgba_finish(colorDialog, result, err);
+   IF rgba # NIL THEN
+      p := CAST(GdkRGBAPtr, rgba);
+      str := gdk_rgba_to_string(p^);
+      CStrToM2(str, buf);
+      gtk_label_set_text(status, buf);
+      Free(str);
+      gdk_rgba_free(rgba)
+   ELSE
+      gtk_label_set_text(status, "No colour chosen");
+      ClearError(err)
+   END
+END OnColorChosen;
+
+PROCEDURE OnColor (button: ADDRESS; user: ADDRESS);
+BEGIN
+   gtk_color_dialog_choose_rgba(colorDialog, window, NIL, NIL,
+                                ADR(OnColorChosen), NIL)
+END OnColor;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   box, alertButton, colorButton: ADDRESS;
+BEGIN
+   okLabel := "OK";
+   cancelLabel := "Cancel";
+   buttons[0] := ADR(okLabel);
+   buttons[1] := ADR(cancelLabel);
+   buttons[2] := NIL;
+
+   alert := gtk_alert_dialog_new("This is a test alert");
+   gtk_alert_dialog_set_message(alert, "Delete the file?");
+   gtk_alert_dialog_set_detail(alert, "This cannot be undone.");
+   gtk_alert_dialog_set_buttons(alert, ADR(buttons));
+   gtk_alert_dialog_set_default_button(alert, 0);
+
+   colorDialog := gtk_color_dialog_new();
+   gtk_color_dialog_set_title(colorDialog, "Pick a colour");
+   gtk_color_dialog_set_modal(colorDialog, 1);
+
+   alertButton := gtk_button_new_with_label("Alert");
+   Connect2(alertButton, "clicked", OnAlert, NIL);
+   colorButton := gtk_button_new_with_label("Choose colour");
+   Connect2(colorButton, "clicked", OnColor, NIL);
+   status := gtk_label_new("No colour chosen");
+
+   box := gtk_box_new(GtkVertical, 12);
+   gtk_box_set_spacing(box, 12);
+   gtk_widget_set_margin_top(box, 14);
+   gtk_widget_set_margin_bottom(box, 14);
+   gtk_widget_set_margin_start(box, 14);
+   gtk_widget_set_margin_end(box, 14);
+   gtk_box_append(box, alertButton);
+   gtk_box_append(box, colorButton);
+   gtk_box_append(box, status);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_title(window, "m2-GTK4 Dialogs");
+   gtk_window_set_default_size(window, 400, 200);
+   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;
+   Connect2(app, "activate", OnActivate, NIL);
+   g_application_run(app, 0, NIL)
+END dialogs.

+ 99 - 0
examples/popover.mod

@@ -0,0 +1,99 @@
+MODULE popover ;
+
+(*
+   m2-GTK4 example - popovers.
+
+   A button pops up a GtkPopover with some content, and a GtkMenuButton
+   opens a GtkPopoverMenu built from a GMenu (with a working Quit item).
+
+   Build:  make examples
+   Run:    ./build/examples/popover
+*)
+
+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_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;
+FROM GtkButton IMPORT gtk_button_new_with_label;
+FROM GtkPopover IMPORT gtk_popover_new, gtk_popover_set_child,
+   gtk_popover_popup;
+FROM GtkPopoverMenu IMPORT gtk_popover_menu_new_from_model;
+FROM GtkMenuButton IMPORT gtk_menu_button_new,
+   gtk_menu_button_set_icon_name, gtk_menu_button_set_popover;
+FROM GtkWidget IMPORT gtk_widget_set_parent, gtk_widget_set_margin_top,
+   gtk_widget_set_margin_bottom, gtk_widget_set_margin_start,
+   gtk_widget_set_margin_end;
+FROM GMenu IMPORT g_menu_new, g_menu_append;
+FROM GSimpleAction IMPORT g_simple_action_new;
+FROM GActionMap IMPORT g_action_map_add_action;
+FROM GtkClosures IMPORT Connect2;
+FROM GtkUtils IMPORT Connect;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.Popover";
+
+VAR
+   app, popover: ADDRESS;
+
+PROCEDURE OnQuit (action: ADDRESS; param: ADDRESS; data: ADDRESS);
+BEGIN
+   g_application_quit(app)
+END OnQuit;
+
+PROCEDURE OnShow (button: ADDRESS; user: ADDRESS);
+BEGIN
+   gtk_popover_popup(popover)
+END OnShow;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, button, content, menu, menuButton, action: ADDRESS;
+BEGIN
+   action := g_simple_action_new("quit", NIL);
+   Connect(action, "activate", ADR(OnQuit), NIL);
+   g_action_map_add_action(application, action);
+
+   button := gtk_button_new_with_label("Show popover");
+   content := gtk_label_new("Popover content");
+   popover := gtk_popover_new();
+   gtk_popover_set_child(popover, content);
+   gtk_widget_set_parent(popover, button);
+   Connect2(button, "clicked", OnShow, NIL);
+
+   menu := g_menu_new();
+   g_menu_append(menu, "Quit", "app.quit");
+   menuButton := gtk_menu_button_new();
+   gtk_menu_button_set_icon_name(menuButton, "open-menu-symbolic");
+   gtk_menu_button_set_popover(menuButton,
+      gtk_popover_menu_new_from_model(menu));
+
+   box := gtk_box_new(GtkVertical, 12);
+   gtk_box_set_spacing(box, 12);
+   gtk_widget_set_margin_top(box, 14);
+   gtk_widget_set_margin_bottom(box, 14);
+   gtk_widget_set_margin_start(box, 14);
+   gtk_widget_set_margin_end(box, 14);
+   gtk_box_append(box, button);
+   gtk_box_append(box, menuButton);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_title(window, "m2-GTK4 Popover");
+   gtk_window_set_default_size(window, 360, 160);
+   gtk_window_set_child(window, box);
+   gtk_window_present(window)
+END OnActivate;
+
+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 popover.

+ 92 - 0
examples/styling.mod

@@ -0,0 +1,92 @@
+MODULE styling ;
+
+(*
+   m2-GTK4 example - CSS styling.
+
+   Loads a stylesheet from a string and installs it for the display,
+   then toggles a CSS class on a label.
+
+   Build:  make examples
+   Run:    ./build/examples/styling
+*)
+
+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;
+FROM GtkButton IMPORT gtk_button_new_with_label;
+FROM GtkWidget IMPORT gtk_widget_add_css_class, gtk_widget_remove_css_class,
+   gtk_widget_set_margin_top, gtk_widget_set_margin_bottom,
+   gtk_widget_set_margin_start, gtk_widget_set_margin_end;
+FROM GtkCssProvider IMPORT gtk_css_provider_new,
+   gtk_css_provider_load_from_string;
+FROM GtkStyleContext IMPORT gtk_style_context_add_provider_for_display,
+   GtkStyleProviderPriorityApplication;
+FROM Gdk IMPORT gdk_display_get_default;
+FROM GtkClosures IMPORT Connect2;
+FROM SYSTEM IMPORT ADDRESS;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.Styling";
+   Css = ".m2accent { color: #e01b24; font-weight: bold; }";
+
+VAR
+   label: ADDRESS;
+   accented: BOOLEAN;
+
+PROCEDURE OnToggle (button: ADDRESS; user: ADDRESS);
+BEGIN
+   IF accented THEN
+      gtk_widget_remove_css_class(label, "m2accent");
+      accented := FALSE
+   ELSE
+      gtk_widget_add_css_class(label, "m2accent");
+      accented := TRUE
+   END
+END OnToggle;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, provider, toggle: ADDRESS;
+BEGIN
+   provider := gtk_css_provider_new();
+   gtk_css_provider_load_from_string(provider, Css);
+   gtk_style_context_add_provider_for_display(gdk_display_get_default(),
+      provider, GtkStyleProviderPriorityApplication);
+
+   label := gtk_label_new("Styled label");
+   accented := FALSE;
+   toggle := gtk_button_new_with_label("Toggle accent");
+   Connect2(toggle, "clicked", OnToggle, NIL);
+
+   box := gtk_box_new(GtkVertical, 12);
+   gtk_box_set_spacing(box, 12);
+   gtk_widget_set_margin_top(box, 14);
+   gtk_widget_set_margin_bottom(box, 14);
+   gtk_widget_set_margin_start(box, 14);
+   gtk_widget_set_margin_end(box, 14);
+   gtk_box_append(box, label);
+   gtk_box_append(box, toggle);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_title(window, "m2-GTK4 Styling");
+   gtk_window_set_default_size(window, 360, 160);
+   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;
+   Connect2(app, "activate", OnActivate, NIL);
+   g_application_run(app, 0, NIL)
+END styling.

+ 36 - 0
src/Gdk.def

@@ -0,0 +1,36 @@
+DEFINITION MODULE FOR "C" Gdk ;
+
+(*
+   m2-GTK4 - minimal GDK: the default display and GdkRGBA.  Mapped onto
+   <gdk/gdkdisplay.h> and <gdk/gdkrgba.h>.
+
+   Enough to install a CSS provider for the display and to handle the
+   colours returned by GtkColorDialog.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GdkDisplay, GdkRGBA, GdkRGBAPtr,
+   gdk_display_get_default,
+   gdk_rgba_free, gdk_rgba_to_string;
+
+
+TYPE
+   GdkDisplay = ADDRESS;
+   (* struct { float red, green, blue, alpha; } *)
+   GdkRGBA = RECORD
+      red, green, blue, alpha: SHORTREAL;
+   END;
+   GdkRGBAPtr = POINTER TO GdkRGBA;
+
+(* GdkDisplay *gdk_display_get_default(void) *)
+PROCEDURE gdk_display_get_default () : GdkDisplay;
+
+(* void gdk_rgba_free(GdkRGBA *rgba) *)
+PROCEDURE gdk_rgba_free (rgba: ADDRESS);
+
+(* char *gdk_rgba_to_string(const GdkRGBA *rgba) *)
+PROCEDURE gdk_rgba_to_string (VAR rgba: GdkRGBA) : ADDRESS;
+
+END Gdk.

+ 95 - 0
src/GtkAlertDialog.def

@@ -0,0 +1,95 @@
+DEFINITION MODULE FOR "C" GtkAlertDialog ;
+
+(*
+   m2-GTK4 - GtkAlertDialog: the modern message/confirmation dialog
+   (GTK 4.10+).  Mapped onto <gtk/gtkalertdialog.h>.
+
+   gtk_alert_dialog_new() takes a printf-style format; call it with a
+   plain message and no arguments (or pass format arguments directly).
+   Add buttons with a NULL-terminated array of strings, then either
+   gtk_alert_dialog_show() (fire and forget) or the asynchronous
+   gtk_alert_dialog_choose()/choose_finish() pair.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM GError IMPORT GError;
+
+EXPORT UNQUALIFIED
+   GtkAlertDialog,
+   gtk_alert_dialog_new,
+   gtk_alert_dialog_set_message, gtk_alert_dialog_get_message,
+   gtk_alert_dialog_set_detail, gtk_alert_dialog_get_detail,
+   gtk_alert_dialog_set_buttons, gtk_alert_dialog_get_buttons,
+   gtk_alert_dialog_set_cancel_button, gtk_alert_dialog_get_cancel_button,
+   gtk_alert_dialog_set_default_button, gtk_alert_dialog_get_default_button,
+   gtk_alert_dialog_set_modal, gtk_alert_dialog_get_modal,
+   gtk_alert_dialog_show,
+   gtk_alert_dialog_choose, gtk_alert_dialog_choose_finish;
+
+
+TYPE
+   GtkAlertDialog = ADDRESS;
+
+(* GtkAlertDialog *gtk_alert_dialog_new(const char *format, ...) *)
+PROCEDURE gtk_alert_dialog_new (format: ARRAY OF CHAR; ...) : GtkAlertDialog;
+
+(* void gtk_alert_dialog_set_message(GtkAlertDialog *self, const char *message) *)
+PROCEDURE gtk_alert_dialog_set_message (self: GtkAlertDialog;
+                                        message: ARRAY OF CHAR);
+
+(* const char *gtk_alert_dialog_get_message(GtkAlertDialog *self) *)
+PROCEDURE gtk_alert_dialog_get_message (self: GtkAlertDialog) : ADDRESS;
+
+(* void gtk_alert_dialog_set_detail(GtkAlertDialog *self, const char *detail) *)
+PROCEDURE gtk_alert_dialog_set_detail (self: GtkAlertDialog;
+                                       detail: ARRAY OF CHAR);
+
+(* const char *gtk_alert_dialog_get_detail(GtkAlertDialog *self) *)
+PROCEDURE gtk_alert_dialog_get_detail (self: GtkAlertDialog) : ADDRESS;
+
+(* void gtk_alert_dialog_set_buttons(GtkAlertDialog *self,
+                                     const char * const *labels) *)
+PROCEDURE gtk_alert_dialog_set_buttons (self: GtkAlertDialog;
+                                        labels: ADDRESS);
+
+(* const char * const *gtk_alert_dialog_get_buttons(GtkAlertDialog *self) *)
+PROCEDURE gtk_alert_dialog_get_buttons (self: GtkAlertDialog) : ADDRESS;
+
+(* void gtk_alert_dialog_set_cancel_button(GtkAlertDialog *self, int button) *)
+PROCEDURE gtk_alert_dialog_set_cancel_button (self: GtkAlertDialog;
+                                              button: INTEGER);
+
+(* int gtk_alert_dialog_get_cancel_button(GtkAlertDialog *self) *)
+PROCEDURE gtk_alert_dialog_get_cancel_button (self: GtkAlertDialog) : INTEGER;
+
+(* void gtk_alert_dialog_set_default_button(GtkAlertDialog *self, int button) *)
+PROCEDURE gtk_alert_dialog_set_default_button (self: GtkAlertDialog;
+                                               button: INTEGER);
+
+(* int gtk_alert_dialog_get_default_button(GtkAlertDialog *self) *)
+PROCEDURE gtk_alert_dialog_get_default_button (self: GtkAlertDialog) : INTEGER;
+
+(* void gtk_alert_dialog_set_modal(GtkAlertDialog *self, gboolean modal) *)
+PROCEDURE gtk_alert_dialog_set_modal (self: GtkAlertDialog; modal: INTEGER);
+
+(* gboolean gtk_alert_dialog_get_modal(GtkAlertDialog *self) *)
+PROCEDURE gtk_alert_dialog_get_modal (self: GtkAlertDialog) : [ INTEGER ];
+
+(* void gtk_alert_dialog_show(GtkAlertDialog *self, GtkWindow *parent) *)
+PROCEDURE gtk_alert_dialog_show (self: GtkAlertDialog; parent: ADDRESS);
+
+(* void gtk_alert_dialog_choose(GtkAlertDialog *self, GtkWindow *parent,
+                                 GCancellable *cancellable,
+                                 GAsyncReadyCallback callback,
+                                 gpointer user_data) *)
+PROCEDURE gtk_alert_dialog_choose (self: GtkAlertDialog; parent: ADDRESS;
+                                   cancellable: ADDRESS;
+                                   callback: ADDRESS; user_data: ADDRESS);
+
+(* int gtk_alert_dialog_choose_finish(GtkAlertDialog *self,
+                                       GAsyncResult *result, GError **error) *)
+PROCEDURE gtk_alert_dialog_choose_finish (self: GtkAlertDialog;
+                                          result: ADDRESS;
+                                          VAR error: GError) : INTEGER;
+
+END GtkAlertDialog.

+ 62 - 0
src/GtkColorDialog.def

@@ -0,0 +1,62 @@
+DEFINITION MODULE FOR "C" GtkColorDialog ;
+
+(*
+   m2-GTK4 - GtkColorDialog: the modern colour chooser (GTK 4.10+).
+   Mapped onto <gtk/gtkcolordialog.h>.
+
+   gtk_color_dialog_choose_rgba() is asynchronous: pass a
+   GAsyncReadyCallback (as ADDRESS, i.e. ADR(proc)); in the callback
+   call choose_rgba_finish() to obtain a GdkRGBA* (release it with
+   Gdk.gdk_rgba_free).  Pass NIL as the initial colour for none.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM GError IMPORT GError;
+
+EXPORT UNQUALIFIED
+   GtkColorDialog,
+   gtk_color_dialog_new,
+   gtk_color_dialog_set_title, gtk_color_dialog_get_title,
+   gtk_color_dialog_set_modal, gtk_color_dialog_set_with_alpha,
+   gtk_color_dialog_choose_rgba, gtk_color_dialog_choose_rgba_finish;
+
+
+TYPE
+   GtkColorDialog = ADDRESS;
+
+(* GtkColorDialog *gtk_color_dialog_new(void) *)
+PROCEDURE gtk_color_dialog_new () : GtkColorDialog;
+
+(* void gtk_color_dialog_set_title(GtkColorDialog *self, const char *title) *)
+PROCEDURE gtk_color_dialog_set_title (self: GtkColorDialog;
+                                      title: ARRAY OF CHAR);
+
+(* const char *gtk_color_dialog_get_title(GtkColorDialog *self) *)
+PROCEDURE gtk_color_dialog_get_title (self: GtkColorDialog) : ADDRESS;
+
+(* void gtk_color_dialog_set_modal(GtkColorDialog *self, gboolean modal) *)
+PROCEDURE gtk_color_dialog_set_modal (self: GtkColorDialog; modal: INTEGER);
+
+(* void gtk_color_dialog_set_with_alpha(GtkColorDialog *self, gboolean with_alpha) *)
+PROCEDURE gtk_color_dialog_set_with_alpha (self: GtkColorDialog;
+                                           with_alpha: INTEGER);
+
+(* void gtk_color_dialog_choose_rgba(GtkColorDialog *self, GtkWindow *parent,
+                                      const GdkRGBA *initial_color,
+                                      GCancellable *cancellable,
+                                      GAsyncReadyCallback callback,
+                                      gpointer user_data) *)
+PROCEDURE gtk_color_dialog_choose_rgba (self: GtkColorDialog; parent: ADDRESS;
+                                        initial_color: ADDRESS;
+                                        cancellable: ADDRESS;
+                                        callback: ADDRESS;
+                                        user_data: ADDRESS);
+
+(* GdkRGBA *gtk_color_dialog_choose_rgba_finish(GtkColorDialog *self,
+                                                 GAsyncResult *result,
+                                                 GError **error) *)
+PROCEDURE gtk_color_dialog_choose_rgba_finish (self: GtkColorDialog;
+                                               result: ADDRESS;
+                                               VAR error: GError) : ADDRESS;
+
+END GtkColorDialog.

+ 63 - 0
src/GtkCssProvider.def

@@ -0,0 +1,63 @@
+DEFINITION MODULE FOR "C" GtkCssProvider ;
+
+(*
+   m2-GTK4 - GtkCssProvider: a source of CSS for GTK.  Mapped onto
+   <gtk/gtkcssprovider.h>.
+
+   Load a stylesheet from a string, a path or a GFile, then install it
+   for the display with GtkStyleContext.gtk_style_context_add_provider_for_display:
+
+      provider := gtk_css_provider_new();
+      gtk_css_provider_load_from_string(provider,
+         ".card { background: #3584e4; color: white; }");
+      gtk_style_context_add_provider_for_display(gdk_display_get_default(),
+         provider, GtkStyleProviderPriorityApplication);
+
+   gtk_css_provider_to_string() returns a newly allocated string to be
+   freed with GtkUtils.Free.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkCssProvider,
+   gtk_css_provider_new,
+   gtk_css_provider_load_from_string,
+   gtk_css_provider_load_from_path,
+   gtk_css_provider_load_from_file,
+   gtk_css_provider_load_from_data,
+   gtk_css_provider_to_string;
+
+
+TYPE
+   GtkCssProvider = ADDRESS;
+
+(* GtkCssProvider *gtk_css_provider_new(void) *)
+PROCEDURE gtk_css_provider_new () : GtkCssProvider;
+
+(* void gtk_css_provider_load_from_string(GtkCssProvider *css_provider,
+                                          const char *string) *)
+PROCEDURE gtk_css_provider_load_from_string (css_provider: GtkCssProvider;
+                                             string: ARRAY OF CHAR);
+
+(* void gtk_css_provider_load_from_path(GtkCssProvider *css_provider,
+                                        const char *path) *)
+PROCEDURE gtk_css_provider_load_from_path (css_provider: GtkCssProvider;
+                                           path: ARRAY OF CHAR);
+
+(* void gtk_css_provider_load_from_file(GtkCssProvider *css_provider,
+                                        GFile *file) *)
+PROCEDURE gtk_css_provider_load_from_file (css_provider: GtkCssProvider;
+                                           file: ADDRESS);
+
+(* void gtk_css_provider_load_from_data(GtkCssProvider *css_provider,
+                                        const char *data, gssize length)
+   Deprecated since GTK 4.12 in favour of load_from_string. *)
+PROCEDURE gtk_css_provider_load_from_data (css_provider: GtkCssProvider;
+                                           data: ARRAY OF CHAR;
+                                           length: LONGINT);
+
+(* char *gtk_css_provider_to_string(GtkCssProvider *provider) *)
+PROCEDURE gtk_css_provider_to_string (provider: GtkCssProvider) : ADDRESS;
+
+END GtkCssProvider.

+ 9 - 0
src/GtkMenuButton.def

@@ -17,6 +17,7 @@ EXPORT UNQUALIFIED
    gtk_menu_button_set_menu_model, gtk_menu_button_get_menu_model,
    gtk_menu_button_set_label, gtk_menu_button_get_label,
    gtk_menu_button_set_icon_name,
+   gtk_menu_button_set_popover, gtk_menu_button_get_popover,
    gtk_menu_button_popup, gtk_menu_button_popdown;
 
 
@@ -47,6 +48,14 @@ PROCEDURE gtk_menu_button_get_label (menu_button: GtkMenuButton) : ADDRESS;
 PROCEDURE gtk_menu_button_set_icon_name (menu_button: GtkMenuButton;
                                          icon_name: ARRAY OF CHAR);
 
+(* void gtk_menu_button_set_popover(GtkMenuButton *menu_button,
+                                    GtkWidget *popover) *)
+PROCEDURE gtk_menu_button_set_popover (menu_button: GtkMenuButton;
+                                       popover: ADDRESS);
+
+(* GtkPopover *gtk_menu_button_get_popover(GtkMenuButton *menu_button) *)
+PROCEDURE gtk_menu_button_get_popover (menu_button: GtkMenuButton) : ADDRESS;
+
 (* void gtk_menu_button_popup(GtkMenuButton *menu_button) *)
 PROCEDURE gtk_menu_button_popup (menu_button: GtkMenuButton);
 

+ 76 - 0
src/GtkPopover.def

@@ -0,0 +1,76 @@
+DEFINITION MODULE FOR "C" GtkPopover ;
+
+(*
+   m2-GTK4 - GtkPopover: a floating container anchored to a widget.
+   Mapped onto <gtk/gtkpopover.h>.
+
+   A popover is not added to a layout: give it a parent with
+   GtkWidget.gtk_widget_set_parent(), then gtk_popover_popup() /
+   gtk_popover_popdown().  With autohide (the default) it closes when
+   clicked outside.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkPopover,
+   gtk_popover_new,
+   gtk_popover_set_child, gtk_popover_get_child,
+   gtk_popover_popup, gtk_popover_popdown,
+   gtk_popover_set_autohide, gtk_popover_get_autohide,
+   gtk_popover_set_has_arrow,
+   gtk_popover_set_mnemonics_visible,
+   gtk_popover_set_offset, gtk_popover_get_offset,
+   gtk_popover_set_cascade_popdown,
+   gtk_popover_set_default_widget;
+
+
+TYPE
+   GtkPopover = ADDRESS;
+
+(* GtkWidget *gtk_popover_new(void) *)
+PROCEDURE gtk_popover_new () : GtkPopover;
+
+(* void gtk_popover_set_child(GtkPopover *popover, GtkWidget *child) *)
+PROCEDURE gtk_popover_set_child (popover: GtkPopover; child: ADDRESS);
+
+(* GtkWidget *gtk_popover_get_child(GtkPopover *popover) *)
+PROCEDURE gtk_popover_get_child (popover: GtkPopover) : ADDRESS;
+
+(* void gtk_popover_popup(GtkPopover *popover) *)
+PROCEDURE gtk_popover_popup (popover: GtkPopover);
+
+(* void gtk_popover_popdown(GtkPopover *popover) *)
+PROCEDURE gtk_popover_popdown (popover: GtkPopover);
+
+(* void gtk_popover_set_autohide(GtkPopover *popover, gboolean autohide) *)
+PROCEDURE gtk_popover_set_autohide (popover: GtkPopover; autohide: INTEGER);
+
+(* gboolean gtk_popover_get_autohide(GtkPopover *popover) *)
+PROCEDURE gtk_popover_get_autohide (popover: GtkPopover) : [ INTEGER ];
+
+(* void gtk_popover_set_has_arrow(GtkPopover *popover, gboolean has_arrow) *)
+PROCEDURE gtk_popover_set_has_arrow (popover: GtkPopover; has_arrow: INTEGER);
+
+(* void gtk_popover_set_mnemonics_visible(GtkPopover *popover,
+                                          gboolean mnemonics_visible) *)
+PROCEDURE gtk_popover_set_mnemonics_visible (popover: GtkPopover;
+                                             mnemonics_visible: INTEGER);
+
+(* void gtk_popover_set_offset(GtkPopover *popover, int x_offset, int y_offset) *)
+PROCEDURE gtk_popover_set_offset (popover: GtkPopover;
+                                  x_offset, y_offset: INTEGER);
+
+(* void gtk_popover_get_offset(GtkPopover *popover, int *x_offset, int *y_offset) *)
+PROCEDURE gtk_popover_get_offset (popover: GtkPopover;
+                                  VAR x_offset, y_offset: INTEGER);
+
+(* void gtk_popover_set_cascade_popdown(GtkPopover *popover,
+                                         gboolean cascade_popdown) *)
+PROCEDURE gtk_popover_set_cascade_popdown (popover: GtkPopover;
+                                           cascade_popdown: INTEGER);
+
+(* void gtk_popover_set_default_widget(GtkPopover *popover, GtkWidget *widget) *)
+PROCEDURE gtk_popover_set_default_widget (popover: GtkPopover; widget: ADDRESS);
+
+END GtkPopover.

+ 63 - 0
src/GtkPopoverMenu.def

@@ -0,0 +1,63 @@
+DEFINITION MODULE FOR "C" GtkPopoverMenu ;
+
+(*
+   m2-GTK4 - GtkPopoverMenu: a popover whose content is a GMenuModel.
+   Mapped onto <gtk/gtkpopovermenu.h>.
+
+   Build it from a GMenu (GMenu.def) and attach it to a widget with
+   GtkWidget.gtk_widget_set_parent() or GtkMenuButton.gtk_menu_button_set_popover().
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkPopoverMenu, GtkPopoverMenuFlags, GtkPopoverMenuNested,
+   gtk_popover_menu_new_from_model,
+   gtk_popover_menu_new_from_model_full,
+   gtk_popover_menu_set_menu_model, gtk_popover_menu_get_menu_model,
+   gtk_popover_menu_set_flags, gtk_popover_menu_get_flags,
+   gtk_popover_menu_add_child, gtk_popover_menu_remove_child;
+
+
+TYPE
+   GtkPopoverMenu = ADDRESS;
+   GtkPopoverMenuFlags = CARDINAL;
+
+CONST
+   GtkPopoverMenuNested = 1;   (* GTK_POPOVER_MENU_NESTED *)
+
+(* GtkWidget *gtk_popover_menu_new_from_model(GMenuModel *model) *)
+PROCEDURE gtk_popover_menu_new_from_model (model: ADDRESS) : GtkPopoverMenu;
+
+(* GtkWidget *gtk_popover_menu_new_from_model_full(GMenuModel *model,
+                                                    GtkPopoverMenuFlags flags) *)
+PROCEDURE gtk_popover_menu_new_from_model_full (model: ADDRESS;
+                                                flags: GtkPopoverMenuFlags) : GtkPopoverMenu;
+
+(* void gtk_popover_menu_set_menu_model(GtkPopoverMenu *popover,
+                                         GMenuModel *model) *)
+PROCEDURE gtk_popover_menu_set_menu_model (popover: GtkPopoverMenu;
+                                           model: ADDRESS);
+
+(* GMenuModel *gtk_popover_menu_get_menu_model(GtkPopoverMenu *popover) *)
+PROCEDURE gtk_popover_menu_get_menu_model (popover: GtkPopoverMenu) : ADDRESS;
+
+(* void gtk_popover_menu_set_flags(GtkPopoverMenu *popover,
+                                    GtkPopoverMenuFlags flags) *)
+PROCEDURE gtk_popover_menu_set_flags (popover: GtkPopoverMenu;
+                                      flags: GtkPopoverMenuFlags);
+
+(* GtkPopoverMenuFlags gtk_popover_menu_get_flags(GtkPopoverMenu *popover) *)
+PROCEDURE gtk_popover_menu_get_flags (popover: GtkPopoverMenu) : GtkPopoverMenuFlags;
+
+(* gboolean gtk_popover_menu_add_child(GtkPopoverMenu *popover,
+                                        GtkWidget *child, const char *id) *)
+PROCEDURE gtk_popover_menu_add_child (popover: GtkPopoverMenu; child: ADDRESS;
+                                      id: ARRAY OF CHAR) : [ INTEGER ];
+
+(* gboolean gtk_popover_menu_remove_child(GtkPopoverMenu *popover,
+                                           GtkWidget *child) *)
+PROCEDURE gtk_popover_menu_remove_child (popover: GtkPopoverMenu;
+                                         child: ADDRESS) : [ INTEGER ];
+
+END GtkPopoverMenu.

+ 52 - 0
src/GtkStyleContext.def

@@ -0,0 +1,52 @@
+DEFINITION MODULE FOR "C" GtkStyleContext ;
+
+(*
+   m2-GTK4 - style provider installation.  Mapped onto
+   <gtk/gtkstyleprovider.h>.
+
+   GtkStyleProvider is an interface implemented by GtkCssProvider.
+   Install one for the whole display with:
+
+      gtk_style_context_add_provider_for_display(gdk_display_get_default(),
+         provider, GtkStyleProviderPriorityApplication);
+
+   Higher priority numbers win.  The usual values are given below.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkStyleProvider, GtkStyleProviderPriority,
+   GtkStyleProviderPriorityFallback,
+   GtkStyleProviderPriorityTheme,
+   GtkStyleProviderPrioritySettings,
+   GtkStyleProviderPriorityApplication,
+   GtkStyleProviderPriorityUser,
+   gtk_style_context_add_provider_for_display,
+   gtk_style_context_remove_provider_for_display;
+
+
+TYPE
+   GtkStyleProvider = ADDRESS;
+   GtkStyleProviderPriority = CARDINAL;
+
+CONST
+   GtkStyleProviderPriorityFallback    = 1;
+   GtkStyleProviderPriorityTheme       = 200;
+   GtkStyleProviderPrioritySettings    = 400;
+   GtkStyleProviderPriorityApplication = 600;
+   GtkStyleProviderPriorityUser        = 800;
+
+(* void gtk_style_context_add_provider_for_display(GdkDisplay *display,
+                                                    GtkStyleProvider *provider,
+                                                    guint priority) *)
+PROCEDURE gtk_style_context_add_provider_for_display (display: ADDRESS;
+                                                      provider: GtkStyleProvider;
+                                                      priority: GtkStyleProviderPriority);
+
+(* void gtk_style_context_remove_provider_for_display(GdkDisplay *display,
+                                                       GtkStyleProvider *provider) *)
+PROCEDURE gtk_style_context_remove_provider_for_display (display: ADDRESS;
+                                                         provider: GtkStyleProvider);
+
+END GtkStyleContext.

+ 24 - 1
src/GtkWidget.def

@@ -24,7 +24,10 @@ EXPORT UNQUALIFIED
    gtk_widget_set_hexpand, gtk_widget_set_vexpand,
    gtk_widget_set_halign, gtk_widget_set_valign,
    gtk_widget_set_size_request, gtk_widget_grab_focus,
-   gtk_widget_get_width, gtk_widget_get_height;
+   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;
 
 
 TYPE
@@ -79,4 +82,24 @@ PROCEDURE gtk_widget_get_width (widget: GtkWidget) : INTEGER;
 (* int gtk_widget_get_height(GtkWidget *widget) *)
 PROCEDURE gtk_widget_get_height (widget: GtkWidget) : INTEGER;
 
+(* void gtk_widget_add_css_class(GtkWidget *widget, const char *css_class) *)
+PROCEDURE gtk_widget_add_css_class (widget: GtkWidget; css_class: ARRAY OF CHAR);
+
+(* void gtk_widget_remove_css_class(GtkWidget *widget, const char *css_class) *)
+PROCEDURE gtk_widget_remove_css_class (widget: GtkWidget;
+                                       css_class: ARRAY OF CHAR);
+
+(* gboolean gtk_widget_has_css_class(GtkWidget *widget, const char *css_class) *)
+PROCEDURE gtk_widget_has_css_class (widget: GtkWidget;
+                                    css_class: ARRAY OF CHAR) : [ INTEGER ];
+
+(* void gtk_widget_set_parent(GtkWidget *widget, GtkWidget *parent) *)
+PROCEDURE gtk_widget_set_parent (widget: GtkWidget; parent: ADDRESS);
+
+(* GtkWidget *gtk_widget_get_parent(GtkWidget *widget) *)
+PROCEDURE gtk_widget_get_parent (widget: GtkWidget) : ADDRESS;
+
+(* void gtk_widget_unparent(GtkWidget *widget) *)
+PROCEDURE gtk_widget_unparent (widget: GtkWidget);
+
 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_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_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_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_adw|test_application) needs_display=1 ;;
     *) needs_display=0 ;;
   esac
   if [ "$needs_display" = 1 ] && [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then

+ 86 - 0
tests/test_css.mod

@@ -0,0 +1,86 @@
+MODULE test_css ;
+
+(*
+   m2-GTK4 test - CSS styling: GtkCssProvider + provider-for-display +
+   per-widget CSS classes.  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 GtkWidget IMPORT gtk_widget_add_css_class, gtk_widget_remove_css_class,
+   gtk_widget_has_css_class;
+FROM GtkLabel IMPORT gtk_label_new;
+FROM GtkCssProvider IMPORT gtk_css_provider_new,
+   gtk_css_provider_load_from_string;
+FROM GtkStyleContext IMPORT gtk_style_context_add_provider_for_display,
+   gtk_style_context_remove_provider_for_display,
+   GtkStyleProviderPriorityApplication;
+FROM Gdk IMPORT gdk_display_get_default;
+FROM GtkUtils IMPORT Connect, Unref;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestCss";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, label, provider, display: ADDRESS;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   provider := gtk_css_provider_new();
+   gtk_css_provider_load_from_string (provider,
+      ".m2card { color: red; }");
+   display := gdk_display_get_default();
+   gtk_style_context_add_provider_for_display (display, provider,
+      GtkStyleProviderPriorityApplication);
+
+   label := gtk_label_new("styled");
+   gtk_widget_add_css_class(label, "m2card");
+   IF gtk_widget_has_css_class(label, "m2card") = 0 THEN
+      printf("css class not applied\n");
+      ok := FALSE
+   END;
+   gtk_widget_remove_css_class(label, "m2card");
+   IF gtk_widget_has_css_class(label, "m2card") # 0 THEN
+      printf("css class not removed\n");
+      ok := FALSE
+   END;
+
+   gtk_style_context_remove_provider_for_display (display, provider);
+   Unref(provider);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, label);
+
+   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_css: 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_css: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_css: FAIL\n");
+      HALT(1)
+   END
+END test_css.

+ 109 - 0
tests/test_dialogs.mod

@@ -0,0 +1,109 @@
+MODULE test_dialogs ;
+
+(*
+   m2-GTK4 test - modern dialogs: GtkAlertDialog and GtkColorDialog
+   property round-trips (dialogues are not shown).  Requires a display.
+*)
+
+FROM Gio IMPORT g_application_run, g_application_quit;
+FROM GtkApplication IMPORT gtk_application_new;
+FROM GtkAlertDialog IMPORT gtk_alert_dialog_new,
+   gtk_alert_dialog_set_message, gtk_alert_dialog_get_message,
+   gtk_alert_dialog_set_detail, gtk_alert_dialog_get_detail,
+   gtk_alert_dialog_set_buttons, gtk_alert_dialog_get_buttons,
+   gtk_alert_dialog_set_cancel_button, gtk_alert_dialog_get_cancel_button,
+   gtk_alert_dialog_set_default_button, gtk_alert_dialog_get_default_button,
+   gtk_alert_dialog_set_modal, gtk_alert_dialog_get_modal;
+FROM GtkColorDialog IMPORT gtk_color_dialog_new,
+   gtk_color_dialog_set_title, gtk_color_dialog_get_title,
+   gtk_color_dialog_set_modal, gtk_color_dialog_set_with_alpha;
+FROM GtkUtils IMPORT Connect, CStrToM2, Unref;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestDialogs";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   alert, color: ADDRESS;
+   buf: ARRAY [0..63] OF CHAR;
+   s0, s1: ARRAY [0..7] OF CHAR;
+   buttons: ARRAY [0..2] OF ADDRESS;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   (* --- GtkAlertDialog --- *)
+   alert := gtk_alert_dialog_new("Heads up");
+   CStrToM2(gtk_alert_dialog_get_message(alert), buf);
+   IF (buf[0] # 'H') OR (buf[8] # 0C) THEN
+      printf("alert message wrong: [%s]\n", buf); ok := FALSE
+   END;
+
+   gtk_alert_dialog_set_message(alert, "Changed");
+   CStrToM2(gtk_alert_dialog_get_message(alert), buf);
+   IF (buf[0] # 'C') OR (buf[7] # 0C) THEN ok := FALSE END;
+
+   gtk_alert_dialog_set_detail(alert, "More info");
+   CStrToM2(gtk_alert_dialog_get_detail(alert), buf);
+   IF (buf[0] # 'M') OR (buf[9] # 0C) THEN ok := FALSE END;
+
+   s0 := "OK";
+   s1 := "Cancel";
+   buttons[0] := ADR(s0);
+   buttons[1] := ADR(s1);
+   buttons[2] := NIL;
+   gtk_alert_dialog_set_buttons(alert, ADR(buttons));
+   IF gtk_alert_dialog_get_buttons(alert) = NIL THEN
+      printf("alert buttons not set\n"); ok := FALSE
+   END;
+
+   gtk_alert_dialog_set_default_button(alert, 0);
+   IF gtk_alert_dialog_get_default_button(alert) # 0 THEN ok := FALSE END;
+   gtk_alert_dialog_set_cancel_button(alert, 1);
+   IF gtk_alert_dialog_get_cancel_button(alert) # 1 THEN ok := FALSE END;
+   gtk_alert_dialog_set_modal(alert, 1);
+   IF gtk_alert_dialog_get_modal(alert) # 1 THEN ok := FALSE END;
+
+   (* --- GtkColorDialog --- *)
+   color := gtk_color_dialog_new();
+   gtk_color_dialog_set_title(color, "Pick a colour");
+   CStrToM2(gtk_color_dialog_get_title(color), buf);
+   IF (buf[0] # 'P') OR (buf[13] # 0C) THEN
+      printf("color title wrong: [%s]\n", buf); ok := FALSE
+   END;
+   gtk_color_dialog_set_modal(color, 1);
+   gtk_color_dialog_set_with_alpha(color, 1);
+
+   Unref(alert);
+   Unref(color);
+
+   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_dialogs: 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_dialogs: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_dialogs: FAIL\n");
+      HALT(1)
+   END
+END test_dialogs.

+ 123 - 0
tests/test_popover.mod

@@ -0,0 +1,123 @@
+MODULE test_popover ;
+
+(*
+   m2-GTK4 test - GtkPopover, GtkPopoverMenu and GtkMenuButton popovers.
+
+   Presents a window, then (from a timeout, once widgets are mapped)
+   opens and closes a popover.  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 GtkBox IMPORT gtk_box_new, gtk_box_append;
+FROM GtkEnums IMPORT GtkVertical;
+FROM GtkButton IMPORT gtk_button_new_with_label;
+FROM GtkLabel IMPORT gtk_label_new;
+FROM GtkPopover IMPORT gtk_popover_new, gtk_popover_set_child,
+   gtk_popover_get_child, gtk_popover_popup, gtk_popover_popdown,
+   gtk_popover_set_autohide, gtk_popover_get_autohide,
+   gtk_popover_set_has_arrow, gtk_popover_set_offset,
+   gtk_popover_get_offset;
+FROM GtkPopoverMenu IMPORT gtk_popover_menu_new_from_model,
+   gtk_popover_menu_get_menu_model, gtk_popover_menu_set_flags,
+   gtk_popover_menu_get_flags, GtkPopoverMenuNested;
+FROM GtkMenuButton IMPORT gtk_menu_button_new,
+   gtk_menu_button_set_popover, gtk_menu_button_get_popover;
+FROM GtkWidget IMPORT gtk_widget_set_parent, gtk_widget_get_parent;
+FROM GMenu IMPORT g_menu_new, g_menu_append;
+FROM GtkUtils IMPORT Connect;
+FROM GLib IMPORT g_timeout_add, GSourceRemove;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestPopover";
+
+VAR
+   app, popover: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE AfterMap (data: ADDRESS) : INTEGER;
+BEGIN
+   gtk_popover_popup(popover);
+   gtk_popover_popdown(popover);
+   g_application_quit(app);
+   RETURN GSourceRemove
+END AfterMap;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, button, child, menu, pm, mb: ADDRESS;
+   x, y: INTEGER;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   child := gtk_label_new("popover content");
+   popover := gtk_popover_new();
+   gtk_popover_set_child(popover, child);
+   IF gtk_popover_get_child(popover) # child THEN ok := FALSE END;
+   gtk_popover_set_autohide(popover, 0);
+   IF gtk_popover_get_autohide(popover) # 0 THEN ok := FALSE END;
+   gtk_popover_set_has_arrow(popover, 0);
+   gtk_popover_set_offset(popover, 4, 8);
+   gtk_popover_get_offset(popover, x, y);
+   IF (x # 4) OR (y # 8) THEN
+      printf("popover offset wrong\n");
+      ok := FALSE
+   END;
+
+   button := gtk_button_new_with_label("Open");
+   gtk_widget_set_parent(popover, button);
+   IF gtk_widget_get_parent(popover) # button THEN
+      printf("popover parent wrong\n");
+      ok := FALSE
+   END;
+
+   menu := g_menu_new();
+   g_menu_append(menu, "Item", "app.quit");
+   pm := gtk_popover_menu_new_from_model(menu);
+   IF gtk_popover_menu_get_menu_model(pm) # menu THEN ok := FALSE END;
+   gtk_popover_menu_set_flags(pm, GtkPopoverMenuNested);
+   IF gtk_popover_menu_get_flags(pm) # GtkPopoverMenuNested THEN ok := FALSE END;
+
+   mb := gtk_menu_button_new();
+   gtk_menu_button_set_popover(mb, pm);
+   IF gtk_menu_button_get_popover(mb) # pm THEN
+      printf("menu button popover wrong\n");
+      ok := FALSE
+   END;
+
+   box := gtk_box_new(GtkVertical, 6);
+   gtk_box_append(box, button);
+   gtk_box_append(box, mb);
+   window := gtk_application_window_new(application);
+   gtk_window_set_child(window, box);
+   gtk_window_present(window);
+
+   passed := ok;
+   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_popover: 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_popover: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_popover: FAIL\n");
+      HALT(1)
+   END
+END test_popover.