Sfoglia il codice sorgente

feat: text extras (tags, marks, entry buffer, entry completion)

- GtkTextTag, GtkTextTagTable, GtkTextMark
- GtkTextBuffer: create_mark/get_mark, create_tag/get_tag_table,
  apply_tag/remove_tag/remove_all_tags, insert_at_cursor/insert_markup,
  place_cursor, get_iter_at_offset/get_iter_at_mark
- GtkTextIter: get_offset/get_line/get_char
- GtkEntryBuffer; GtkEntry: get_buffer, set/get_completion
- GtkEntryCompletion (deprecated, GtkTreeModel-based)
- GtkTextView: scroll_to_mark
- test_text, examples/richtext.mod

35 tests pass; 144 src modules, 25 examples.
Eric Streit 2 giorni fa
parent
commit
4004b3c809

+ 2 - 2
Makefile

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

+ 3 - 2
README.md

@@ -14,10 +14,11 @@ Verified with `gm2` 16.0.1 (experimental), GTK 4.18.6 and libadwaita 1.7.6.
 
 ## Features
 
-- **139 hand-written binding modules** across GLib, GObject, Gio, GTK4
+- **144 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),
-  gestures, drag-and-drop, media, OpenGL, printing, settings, and more.
+  rich text, gestures, drag-and-drop, media, OpenGL, printing, 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).

+ 90 - 0
examples/richtext.mod

@@ -0,0 +1,90 @@
+MODULE richtext ;
+
+(*
+   m2-GTK4 example - text extras.
+
+   A GtkTextView whose buffer is filled with Pango markup (bold/colour),
+   with a named mark used to scroll back to the top, plus a GtkEntry with
+   an attached GtkEntryCompletion.
+
+   Build:  make examples
+   Run:    ./build/examples/richtext
+*)
+
+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, GtkWrapWordChar;
+FROM GtkScrolledWindow IMPORT gtk_scrolled_window_new,
+   gtk_scrolled_window_set_child;
+FROM GtkTextView IMPORT gtk_text_view_new, gtk_text_view_get_buffer,
+   gtk_text_view_set_wrap_mode, gtk_text_view_set_monospace,
+   gtk_text_view_scroll_to_mark;
+FROM GtkTextBuffer IMPORT gtk_text_buffer_get_start_iter,
+   gtk_text_buffer_create_mark, gtk_text_buffer_insert_markup;
+FROM GtkTextIter IMPORT GtkTextIter;
+FROM GtkEntry IMPORT gtk_entry_new, gtk_entry_set_completion;
+FROM GtkEntryCompletion IMPORT gtk_entry_completion_new,
+   gtk_entry_completion_set_text_column,
+   gtk_entry_completion_set_minimum_key_length,
+   gtk_entry_completion_set_popup_completion;
+FROM GtkWidget IMPORT gtk_widget_set_vexpand;
+FROM GtkClosures IMPORT Connect2;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.RichText";
+   Markup = "<b>Rich text</b> from GNU Modula-2 - <span foreground='#e01b24'>red</span>, <i>italic</i> and <tt>monospace</tt>.";
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, sw, view, buffer, entry, completion, mark: ADDRESS;
+   start: GtkTextIter;
+BEGIN
+   view := gtk_text_view_new();
+   gtk_text_view_set_wrap_mode(view, GtkWrapWordChar);
+   gtk_text_view_set_monospace(view, 0);
+
+   buffer := gtk_text_view_get_buffer(view);
+   gtk_text_buffer_get_start_iter(buffer, start);
+   gtk_text_buffer_insert_markup(buffer, start, Markup, -1);
+   mark := gtk_text_buffer_create_mark(buffer, "top", start, 1);
+   gtk_text_view_scroll_to_mark(view, mark, 0.0, 0, 0.0, 0.0);
+
+   entry := gtk_entry_new();
+   completion := gtk_entry_completion_new();
+   gtk_entry_completion_set_text_column(completion, 0);
+   gtk_entry_completion_set_minimum_key_length(completion, 2);
+   gtk_entry_completion_set_popup_completion(completion, 1);
+   gtk_entry_set_completion(entry, completion);
+
+   sw := gtk_scrolled_window_new();
+   gtk_scrolled_window_set_child(sw, view);
+   gtk_widget_set_vexpand(sw, 1);
+
+   box := gtk_box_new(GtkVertical, 8);
+   gtk_box_set_spacing(box, 8);
+   gtk_box_append(box, entry);
+   gtk_box_append(box, sw);
+
+   window := gtk_application_window_new(application);
+   gtk_window_set_title(window, "m2-GTK4 Rich Text");
+   gtk_window_set_default_size(window, 460, 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;
+   Connect2(app, "activate", OnActivate, NIL);
+   g_application_run(app, 0, NIL)
+END richtext.

+ 12 - 1
src/GtkEntry.def

@@ -23,7 +23,9 @@ EXPORT UNQUALIFIED
    gtk_entry_get_text_length,
    gtk_entry_set_visibility, gtk_entry_get_visibility,
    gtk_entry_set_activates_default, gtk_entry_get_activates_default,
-   gtk_entry_set_progress_fraction, gtk_entry_get_progress_fraction;
+   gtk_entry_set_progress_fraction, gtk_entry_get_progress_fraction,
+   gtk_entry_get_buffer,
+   gtk_entry_set_completion, gtk_entry_get_completion;
 
 
 TYPE
@@ -67,4 +69,13 @@ PROCEDURE gtk_entry_set_progress_fraction (entry: GtkEntry;
 (* double gtk_entry_get_progress_fraction(GtkEntry *entry) *)
 PROCEDURE gtk_entry_get_progress_fraction (entry: GtkEntry) : REAL;
 
+(* GtkEntryBuffer *gtk_entry_get_buffer(GtkEntry *entry) *)
+PROCEDURE gtk_entry_get_buffer (entry: GtkEntry) : ADDRESS;
+
+(* void gtk_entry_set_completion(GtkEntry *entry, GtkEntryCompletion *completion) *)
+PROCEDURE gtk_entry_set_completion (entry: GtkEntry; completion: ADDRESS);
+
+(* GtkEntryCompletion *gtk_entry_get_completion(GtkEntry *entry) *)
+PROCEDURE gtk_entry_get_completion (entry: GtkEntry) : ADDRESS;
+
 END GtkEntry.

+ 62 - 0
src/GtkEntryBuffer.def

@@ -0,0 +1,62 @@
+DEFINITION MODULE FOR "C" GtkEntryBuffer ;
+
+(*
+   m2-GTK4 - GtkEntryBuffer: the text model behind GtkEntry.  Mapped onto
+   <gtk/gtkentrybuffer.h>.
+
+   Most code can use GtkEditable on the entry; this is useful when
+   sharing one buffer between widgets or for length limits.  Position 0
+   is the start; positions are in characters.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkEntryBuffer,
+   gtk_entry_buffer_new,
+   gtk_entry_buffer_get_text, gtk_entry_buffer_set_text,
+   gtk_entry_buffer_get_length,
+   gtk_entry_buffer_get_max_length, gtk_entry_buffer_set_max_length,
+   gtk_entry_buffer_insert_text, gtk_entry_buffer_delete_text;
+
+
+TYPE
+   GtkEntryBuffer = ADDRESS;
+
+(* GtkEntryBuffer *gtk_entry_buffer_new(const char *initial_chars,
+                                         int n_initial_chars) *)
+PROCEDURE gtk_entry_buffer_new (initial_chars: ADDRESS;
+                                n_initial_chars: INTEGER) : GtkEntryBuffer;
+
+(* const char *gtk_entry_buffer_get_text(GtkEntryBuffer *buffer) *)
+PROCEDURE gtk_entry_buffer_get_text (buffer: GtkEntryBuffer) : ADDRESS;
+
+(* void gtk_entry_buffer_set_text(GtkEntryBuffer *buffer, const char *chars,
+                                   int n_chars) *)
+PROCEDURE gtk_entry_buffer_set_text (buffer: GtkEntryBuffer;
+                                     chars: ARRAY OF CHAR; n_chars: INTEGER);
+
+(* guint gtk_entry_buffer_get_length(GtkEntryBuffer *buffer) *)
+PROCEDURE gtk_entry_buffer_get_length (buffer: GtkEntryBuffer) : CARDINAL;
+
+(* int gtk_entry_buffer_get_max_length(GtkEntryBuffer *buffer) *)
+PROCEDURE gtk_entry_buffer_get_max_length (buffer: GtkEntryBuffer) : INTEGER;
+
+(* void gtk_entry_buffer_set_max_length(GtkEntryBuffer *buffer, int max_length) *)
+PROCEDURE gtk_entry_buffer_set_max_length (buffer: GtkEntryBuffer;
+                                           max_length: INTEGER);
+
+(* guint gtk_entry_buffer_insert_text(GtkEntryBuffer *buffer, guint position,
+                                       const char *chars, int n_chars) *)
+PROCEDURE gtk_entry_buffer_insert_text (buffer: GtkEntryBuffer;
+                                        position: CARDINAL;
+                                        chars: ARRAY OF CHAR;
+                                        n_chars: INTEGER) : [ CARDINAL ];
+
+(* guint gtk_entry_buffer_delete_text(GtkEntryBuffer *buffer, guint position,
+                                       int n_chars) *)
+PROCEDURE gtk_entry_buffer_delete_text (buffer: GtkEntryBuffer;
+                                        position: CARDINAL;
+                                        n_chars: INTEGER) : [ CARDINAL ];
+
+END GtkEntryBuffer.

+ 70 - 0
src/GtkEntryCompletion.def

@@ -0,0 +1,70 @@
+DEFINITION MODULE FOR "C" GtkEntryCompletion ;
+
+(*
+   m2-GTK4 - GtkEntryCompletion: inline completion for a GtkEntry.
+   Mapped onto <gtk/deprecated/gtkentrycompletion.h> (deprecated; it uses
+   the deprecated GtkTreeModel as its model).
+
+   gtk_entry_completion_set_model() takes a GtkTreeModel * (an ADDRESS;
+   NIL for none).  The text column is the model column holding the
+   string, set with set_text_column.  Attach to an entry with
+   GtkEntry.gtk_entry_set_completion().
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkEntryCompletion,
+   gtk_entry_completion_new,
+   gtk_entry_completion_set_model, gtk_entry_completion_get_model,
+   gtk_entry_completion_set_text_column, gtk_entry_completion_get_text_column,
+   gtk_entry_completion_set_minimum_key_length,
+   gtk_entry_completion_get_minimum_key_length,
+   gtk_entry_completion_set_popup_completion,
+   gtk_entry_completion_complete,
+   gtk_entry_completion_get_completion_prefix;
+
+
+TYPE
+   GtkEntryCompletion = ADDRESS;
+
+(* GtkEntryCompletion *gtk_entry_completion_new(void) *)
+PROCEDURE gtk_entry_completion_new () : GtkEntryCompletion;
+
+(* void gtk_entry_completion_set_model(GtkEntryCompletion *completion,
+                                        GtkTreeModel *model) *)
+PROCEDURE gtk_entry_completion_set_model (completion: GtkEntryCompletion;
+                                          model: ADDRESS);
+
+(* GtkTreeModel *gtk_entry_completion_get_model(GtkEntryCompletion *completion) *)
+PROCEDURE gtk_entry_completion_get_model (completion: GtkEntryCompletion) : ADDRESS;
+
+(* void gtk_entry_completion_set_text_column(GtkEntryCompletion *completion,
+                                              int column) *)
+PROCEDURE gtk_entry_completion_set_text_column (completion: GtkEntryCompletion;
+                                                column: INTEGER);
+
+(* int gtk_entry_completion_get_text_column(GtkEntryCompletion *completion) *)
+PROCEDURE gtk_entry_completion_get_text_column (completion: GtkEntryCompletion) : INTEGER;
+
+(* void gtk_entry_completion_set_minimum_key_length(GtkEntryCompletion *completion,
+                                                     int length) *)
+PROCEDURE gtk_entry_completion_set_minimum_key_length (completion: GtkEntryCompletion;
+                                                       length: INTEGER);
+
+(* int gtk_entry_completion_get_minimum_key_length(GtkEntryCompletion *completion) *)
+PROCEDURE gtk_entry_completion_get_minimum_key_length (completion: GtkEntryCompletion) : INTEGER;
+
+(* void gtk_entry_completion_set_popup_completion(GtkEntryCompletion *completion,
+                                                   gboolean popup_completion) *)
+PROCEDURE gtk_entry_completion_set_popup_completion (completion: GtkEntryCompletion;
+                                                     popup_completion: INTEGER);
+
+(* void gtk_entry_completion_complete(GtkEntryCompletion *completion) *)
+PROCEDURE gtk_entry_completion_complete (completion: GtkEntryCompletion);
+
+(* const char *gtk_entry_completion_get_completion_prefix(
+                  GtkEntryCompletion *completion) *)
+PROCEDURE gtk_entry_completion_get_completion_prefix (completion: GtkEntryCompletion) : ADDRESS;
+
+END GtkEntryCompletion.

+ 78 - 1
src/GtkTextBuffer.def

@@ -23,7 +23,14 @@ EXPORT UNQUALIFIED
    gtk_text_buffer_get_line_count, gtk_text_buffer_get_char_count,
    gtk_text_buffer_get_start_iter, gtk_text_buffer_get_end_iter,
    gtk_text_buffer_get_bounds, gtk_text_buffer_get_iter_at_line,
-   gtk_text_buffer_get_modified, gtk_text_buffer_set_modified;
+   gtk_text_buffer_get_modified, gtk_text_buffer_set_modified,
+   gtk_text_buffer_create_mark, gtk_text_buffer_get_mark,
+   gtk_text_buffer_get_iter_at_offset, gtk_text_buffer_get_iter_at_mark,
+   gtk_text_buffer_place_cursor,
+   gtk_text_buffer_apply_tag, gtk_text_buffer_remove_tag,
+   gtk_text_buffer_remove_all_tags,
+   gtk_text_buffer_create_tag, gtk_text_buffer_get_tag_table,
+   gtk_text_buffer_insert_at_cursor, gtk_text_buffer_insert_markup;
 
 
 TYPE
@@ -86,4 +93,74 @@ PROCEDURE gtk_text_buffer_get_modified (buffer: GtkTextBuffer) : [ INTEGER ];
 PROCEDURE gtk_text_buffer_set_modified (buffer: GtkTextBuffer;
                                         setting: INTEGER);
 
+(* GtkTextMark *gtk_text_buffer_create_mark(GtkTextBuffer *buffer,
+                                             const char *mark_name,
+                                             const GtkTextIter *where,
+                                             gboolean left_gravity) *)
+PROCEDURE gtk_text_buffer_create_mark (buffer: GtkTextBuffer;
+                                       mark_name: ARRAY OF CHAR;
+                                       VAR where: GtkTextIter;
+                                       left_gravity: INTEGER) : ADDRESS;
+
+(* GtkTextMark *gtk_text_buffer_get_mark(GtkTextBuffer *buffer, const char *name) *)
+PROCEDURE gtk_text_buffer_get_mark (buffer: GtkTextBuffer;
+                                    name: ARRAY OF CHAR) : ADDRESS;
+
+(* void gtk_text_buffer_get_iter_at_offset(GtkTextBuffer *buffer,
+                                            GtkTextIter *iter, int char_offset) *)
+PROCEDURE gtk_text_buffer_get_iter_at_offset (buffer: GtkTextBuffer;
+                                              VAR iter: GtkTextIter;
+                                              char_offset: INTEGER);
+
+(* void gtk_text_buffer_get_iter_at_mark(GtkTextBuffer *buffer,
+                                          GtkTextIter *iter, GtkTextMark *mark) *)
+PROCEDURE gtk_text_buffer_get_iter_at_mark (buffer: GtkTextBuffer;
+                                            VAR iter: GtkTextIter; mark: ADDRESS);
+
+(* void gtk_text_buffer_place_cursor(GtkTextBuffer *buffer,
+                                      const GtkTextIter *where) *)
+PROCEDURE gtk_text_buffer_place_cursor (buffer: GtkTextBuffer;
+                                        VAR where: GtkTextIter);
+
+(* void gtk_text_buffer_apply_tag(GtkTextBuffer *buffer, GtkTextTag *tag,
+                                   const GtkTextIter *start,
+                                   const GtkTextIter *end) *)
+PROCEDURE gtk_text_buffer_apply_tag (buffer: GtkTextBuffer; tag: ADDRESS;
+                                     VAR start, theEnd: GtkTextIter);
+
+(* void gtk_text_buffer_remove_tag(GtkTextBuffer *buffer, GtkTextTag *tag,
+                                    const GtkTextIter *start,
+                                    const GtkTextIter *end) *)
+PROCEDURE gtk_text_buffer_remove_tag (buffer: GtkTextBuffer; tag: ADDRESS;
+                                      VAR start, theEnd: GtkTextIter);
+
+(* void gtk_text_buffer_remove_all_tags(GtkTextBuffer *buffer,
+                                         const GtkTextIter *start,
+                                         const GtkTextIter *end) *)
+PROCEDURE gtk_text_buffer_remove_all_tags (buffer: GtkTextBuffer;
+                                           VAR start, theEnd: GtkTextIter);
+
+(* GtkTextTag *gtk_text_buffer_create_tag(GtkTextBuffer *buffer,
+                                           const char *tag_name,
+                                           const char *first_property_name, ...) *)
+PROCEDURE gtk_text_buffer_create_tag (buffer: GtkTextBuffer;
+                                      tag_name: ARRAY OF CHAR;
+                                      first_property_name: ADDRESS;
+                                      ...) : ADDRESS;
+
+(* GtkTextTagTable *gtk_text_buffer_get_tag_table(GtkTextBuffer *buffer) *)
+PROCEDURE gtk_text_buffer_get_tag_table (buffer: GtkTextBuffer) : ADDRESS;
+
+(* void gtk_text_buffer_insert_at_cursor(GtkTextBuffer *buffer,
+                                          const char *text, int len) *)
+PROCEDURE gtk_text_buffer_insert_at_cursor (buffer: GtkTextBuffer;
+                                            text: ARRAY OF CHAR; len: INTEGER);
+
+(* void gtk_text_buffer_insert_markup(GtkTextBuffer *buffer,
+                                       GtkTextIter *iter,
+                                       const char *markup, int len) *)
+PROCEDURE gtk_text_buffer_insert_markup (buffer: GtkTextBuffer;
+                                         VAR iter: GtkTextIter;
+                                         markup: ARRAY OF CHAR; len: INTEGER);
+
 END GtkTextBuffer.

+ 12 - 1
src/GtkTextIter.def

@@ -17,11 +17,22 @@ DEFINITION MODULE FOR "C" GtkTextIter ;
 
 FROM SYSTEM IMPORT ADDRESS;
 
-EXPORT UNQUALIFIED GtkTextIter;
+EXPORT UNQUALIFIED GtkTextIter,
+   gtk_text_iter_get_offset, gtk_text_iter_get_line,
+   gtk_text_iter_get_char;
 
 TYPE
    GtkTextIter = RECORD
       opaque: ARRAY [0..9] OF ADDRESS;
    END;
 
+(* int gtk_text_iter_get_offset(const GtkTextIter *iter) *)
+PROCEDURE gtk_text_iter_get_offset (VAR iter: GtkTextIter) : INTEGER;
+
+(* int gtk_text_iter_get_line(const GtkTextIter *iter) *)
+PROCEDURE gtk_text_iter_get_line (VAR iter: GtkTextIter) : INTEGER;
+
+(* gunichar gtk_text_iter_get_char(const GtkTextIter *iter) *)
+PROCEDURE gtk_text_iter_get_char (VAR iter: GtkTextIter) : CARDINAL;
+
 END GtkTextIter.

+ 43 - 0
src/GtkTextMark.def

@@ -0,0 +1,43 @@
+DEFINITION MODULE FOR "C" GtkTextMark ;
+
+(*
+   m2-GTK4 - GtkTextMark: a position in a GtkTextBuffer that moves with
+   edits.  Mapped onto <gtk/gtktextmark.h>.
+
+   Create with GtkTextBuffer.gtk_text_buffer_create_mark(), retrieve with
+   get_mark(); a buffer keeps a reference to marks living in it.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkTextMark,
+   gtk_text_mark_new,
+   gtk_text_mark_set_visible, gtk_text_mark_get_visible,
+   gtk_text_mark_get_name, gtk_text_mark_get_buffer,
+   gtk_text_mark_get_deleted;
+
+
+TYPE
+   GtkTextMark = ADDRESS;
+
+(* GtkTextMark *gtk_text_mark_new(const char *name, gboolean left_gravity) *)
+PROCEDURE gtk_text_mark_new (name: ARRAY OF CHAR;
+                             left_gravity: INTEGER) : GtkTextMark;
+
+(* void gtk_text_mark_set_visible(GtkTextMark *mark, gboolean setting) *)
+PROCEDURE gtk_text_mark_set_visible (mark: GtkTextMark; setting: INTEGER);
+
+(* gboolean gtk_text_mark_get_visible(GtkTextMark *mark) *)
+PROCEDURE gtk_text_mark_get_visible (mark: GtkTextMark) : [ INTEGER ];
+
+(* const char *gtk_text_mark_get_name(GtkTextMark *mark) *)
+PROCEDURE gtk_text_mark_get_name (mark: GtkTextMark) : ADDRESS;
+
+(* GtkTextBuffer *gtk_text_mark_get_buffer(GtkTextMark *mark) *)
+PROCEDURE gtk_text_mark_get_buffer (mark: GtkTextMark) : ADDRESS;
+
+(* gboolean gtk_text_mark_get_deleted(GtkTextMark *mark) *)
+PROCEDURE gtk_text_mark_get_deleted (mark: GtkTextMark) : [ INTEGER ];
+
+END GtkTextMark.

+ 34 - 0
src/GtkTextTag.def

@@ -0,0 +1,34 @@
+DEFINITION MODULE FOR "C" GtkTextTag ;
+
+(*
+   m2-GTK4 - GtkTextTag: a set of formatting attributes attached to
+   ranges of a GtkTextBuffer.  Mapped onto <gtk/gtktexttag.h>.
+
+   Create with a name, add it to a buffer's tag table (or use
+   GtkTextBuffer.gtk_text_buffer_create_tag), then apply it to a range
+   with gtk_text_buffer_apply_tag().  Visual attributes are set through
+   GObject properties (g_object_set); the Pango markup functions are
+   often simpler.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkTextTag,
+   gtk_text_tag_new,
+   gtk_text_tag_get_priority, gtk_text_tag_set_priority;
+
+
+TYPE
+   GtkTextTag = ADDRESS;
+
+(* GtkTextTag *gtk_text_tag_new(const char *name) *)
+PROCEDURE gtk_text_tag_new (name: ARRAY OF CHAR) : GtkTextTag;
+
+(* int gtk_text_tag_get_priority(GtkTextTag *tag) *)
+PROCEDURE gtk_text_tag_get_priority (tag: GtkTextTag) : INTEGER;
+
+(* void gtk_text_tag_set_priority(GtkTextTag *tag, int priority) *)
+PROCEDURE gtk_text_tag_set_priority (tag: GtkTextTag; priority: INTEGER);
+
+END GtkTextTag.

+ 36 - 0
src/GtkTextTagTable.def

@@ -0,0 +1,36 @@
+DEFINITION MODULE FOR "C" GtkTextTagTable ;
+
+(*
+   m2-GTK4 - GtkTextTagTable: the registry of tags for a buffer.  Mapped
+   onto <gtk/gtktexttagtable.h>.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   GtkTextTagTable,
+   gtk_text_tag_table_new,
+   gtk_text_tag_table_add, gtk_text_tag_table_remove,
+   gtk_text_tag_table_lookup, gtk_text_tag_table_get_size;
+
+
+TYPE
+   GtkTextTagTable = ADDRESS;
+
+(* GtkTextTagTable *gtk_text_tag_table_new(void) *)
+PROCEDURE gtk_text_tag_table_new () : GtkTextTagTable;
+
+(* gboolean gtk_text_tag_table_add(GtkTextTagTable *table, GtkTextTag *tag) *)
+PROCEDURE gtk_text_tag_table_add (table: GtkTextTagTable; tag: ADDRESS) : [ INTEGER ];
+
+(* void gtk_text_tag_table_remove(GtkTextTagTable *table, GtkTextTag *tag) *)
+PROCEDURE gtk_text_tag_table_remove (table: GtkTextTagTable; tag: ADDRESS);
+
+(* GtkTextTag *gtk_text_tag_table_lookup(GtkTextTagTable *table, const char *name) *)
+PROCEDURE gtk_text_tag_table_lookup (table: GtkTextTagTable;
+                                     name: ARRAY OF CHAR) : ADDRESS;
+
+(* int gtk_text_tag_table_get_size(GtkTextTagTable *table) *)
+PROCEDURE gtk_text_tag_table_get_size (table: GtkTextTagTable) : INTEGER;
+
+END GtkTextTagTable.

+ 9 - 1
src/GtkTextView.def

@@ -25,7 +25,7 @@ EXPORT UNQUALIFIED
    gtk_text_view_set_monospace, gtk_text_view_get_monospace,
    gtk_text_view_set_left_margin, gtk_text_view_set_right_margin,
    gtk_text_view_set_top_margin, gtk_text_view_set_bottom_margin,
-   gtk_text_view_scroll_to_iter;
+   gtk_text_view_scroll_to_iter, gtk_text_view_scroll_to_mark;
 
 
 TYPE
@@ -97,4 +97,12 @@ PROCEDURE gtk_text_view_scroll_to_iter (text_view: GtkTextView;
                                         use_align: INTEGER;
                                         xalign, yalign: REAL) : [ INTEGER ];
 
+(* void gtk_text_view_scroll_to_mark(GtkTextView *text_view, GtkTextMark *mark,
+                                     double within_margin, gboolean use_align,
+                                     double xalign, double yalign) *)
+PROCEDURE gtk_text_view_scroll_to_mark (text_view: GtkTextView; mark: ADDRESS;
+                                        within_margin: REAL;
+                                        use_align: INTEGER;
+                                        xalign, yalign: REAL);
+
 END GtkTextView.

+ 2 - 2
tests/run_tests.sh

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

+ 181 - 0
tests/test_text.mod

@@ -0,0 +1,181 @@
+MODULE test_text ;
+
+(*
+   m2-GTK4 test - text extras: tags, tag table, marks, iterators,
+   GtkEntryBuffer and GtkEntryCompletion.  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 GtkEnums IMPORT GtkVertical;
+FROM GtkTextView IMPORT gtk_text_view_new_with_buffer;
+FROM GtkTextBuffer IMPORT gtk_text_buffer_new, gtk_text_buffer_set_text,
+   gtk_text_buffer_get_char_count, gtk_text_buffer_get_tag_table,
+   gtk_text_buffer_create_tag, gtk_text_buffer_create_mark,
+   gtk_text_buffer_get_mark, gtk_text_buffer_get_iter_at_offset,
+   gtk_text_buffer_get_iter_at_mark, gtk_text_buffer_apply_tag,
+   gtk_text_buffer_remove_tag, gtk_text_buffer_remove_all_tags,
+   gtk_text_buffer_insert_at_cursor, gtk_text_buffer_insert_markup;
+FROM GtkTextIter IMPORT gtk_text_iter_get_offset, GtkTextIter;
+FROM GtkTextTag IMPORT gtk_text_tag_new, gtk_text_tag_set_priority,
+   gtk_text_tag_get_priority;
+FROM GtkTextTagTable IMPORT gtk_text_tag_table_add, gtk_text_tag_table_lookup,
+   gtk_text_tag_table_get_size;
+FROM GtkTextMark IMPORT gtk_text_mark_get_name, gtk_text_mark_set_visible,
+   gtk_text_mark_get_visible, gtk_text_mark_get_deleted;
+FROM GtkEntry IMPORT gtk_entry_new, gtk_entry_get_buffer,
+   gtk_entry_set_completion, gtk_entry_get_completion;
+FROM GtkEntryBuffer IMPORT gtk_entry_buffer_set_text, gtk_entry_buffer_get_text,
+   gtk_entry_buffer_get_length, gtk_entry_buffer_set_max_length,
+   gtk_entry_buffer_get_max_length;
+FROM GtkEntryCompletion IMPORT gtk_entry_completion_new,
+   gtk_entry_completion_set_text_column, gtk_entry_completion_get_text_column,
+   gtk_entry_completion_set_minimum_key_length,
+   gtk_entry_completion_get_minimum_key_length,
+   gtk_entry_completion_set_popup_completion;
+FROM GtkUtils IMPORT Connect, CStrToM2;
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM libc IMPORT printf;
+
+CONST
+   AppId = "org.example.m2gtk4.TestText";
+
+VAR
+   app: ADDRESS;
+   passed: BOOLEAN;
+
+PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
+VAR
+   window, box, view, buffer, table, tag, tag2, tag3, mark, entry, eb, completion,
+   it0, it1, it2: ADDRESS;
+   iter0, iter1, iter2: GtkTextIter;
+   buf: ARRAY [0..63] OF CHAR;
+   size0: INTEGER;
+   ok: BOOLEAN;
+BEGIN
+   ok := TRUE;
+
+   buffer := gtk_text_buffer_new(NIL);
+   table := gtk_text_buffer_get_tag_table(buffer);
+   IF table = NIL THEN printf("tag table NIL\n"); ok := FALSE END;
+   size0 := gtk_text_tag_table_get_size(table);
+
+   (* tag + tag table *)
+   tag := gtk_text_tag_new("bold");
+   gtk_text_tag_table_add(table, tag);
+   IF gtk_text_tag_table_lookup(table, "bold") # tag THEN
+      printf("tag lookup wrong\n"); ok := FALSE
+   END;
+   IF gtk_text_tag_table_get_size(table) # size0 + 1 THEN
+      printf("tag table size wrong\n"); ok := FALSE
+   END;
+   tag3 := gtk_text_tag_new("italic");
+   gtk_text_tag_table_add(table, tag3);
+   IF gtk_text_tag_table_get_size(table) # size0 + 2 THEN ok := FALSE END;
+   (* priority must be < the tag table size *)
+   gtk_text_tag_set_priority(tag, 1);
+   IF gtk_text_tag_get_priority(tag) # 1 THEN
+      printf("tag priority wrong\n"); ok := FALSE
+   END;
+
+   (* text + iterators + tags *)
+   gtk_text_buffer_set_text(buffer, "hello world", -1);
+   IF gtk_text_buffer_get_char_count(buffer) # 11 THEN
+      printf("char count wrong\n"); ok := FALSE
+   END;
+   gtk_text_buffer_get_iter_at_offset(buffer, iter0, 0);
+   gtk_text_buffer_get_iter_at_offset(buffer, iter1, 5);
+   IF gtk_text_iter_get_offset(iter0) # 0 THEN ok := FALSE END;
+   IF gtk_text_iter_get_offset(iter1) # 5 THEN ok := FALSE END;
+   gtk_text_buffer_apply_tag(buffer, tag, iter0, iter1);
+   gtk_text_buffer_remove_tag(buffer, tag, iter0, iter1);
+   gtk_text_buffer_remove_all_tags(buffer, iter0, iter1);
+
+   (* mark *)
+   mark := gtk_text_buffer_create_mark(buffer, "m1", iter0, 1);
+   IF gtk_text_buffer_get_mark(buffer, "m1") # mark THEN
+      printf("mark lookup wrong\n"); ok := FALSE
+   END;
+   CStrToM2(gtk_text_mark_get_name(mark), buf);
+   IF (buf[0] # 'm') OR (buf[2] # 0C) THEN
+      printf("mark name wrong: [%s]\n", buf); ok := FALSE
+   END;
+   gtk_text_mark_set_visible(mark, 1);
+   IF gtk_text_mark_get_visible(mark) # 1 THEN ok := FALSE END;
+   IF gtk_text_mark_get_deleted(mark) # 0 THEN ok := FALSE END;
+   gtk_text_buffer_get_iter_at_mark(buffer, iter2, mark);
+   IF gtk_text_iter_get_offset(iter2) # 0 THEN ok := FALSE END;
+
+   (* inserts *)
+   gtk_text_buffer_insert_at_cursor(buffer, "X", -1);
+   IF gtk_text_buffer_get_char_count(buffer) # 12 THEN
+      printf("insert_at_cursor wrong\n"); ok := FALSE
+   END;
+   gtk_text_buffer_get_iter_at_offset(buffer, iter0, 0);
+   gtk_text_buffer_insert_markup(buffer, iter0, "<b>hi</b>", -1);
+   IF gtk_text_buffer_get_char_count(buffer) # 14 THEN
+      printf("insert_markup wrong\n"); ok := FALSE
+   END;
+   tag2 := gtk_text_buffer_create_tag(buffer, "red", NIL);
+   IF tag2 = NIL THEN printf("create_tag NIL\n"); ok := FALSE END;
+
+   (* entry buffer *)
+   entry := gtk_entry_new();
+   eb := gtk_entry_get_buffer(entry);
+   IF eb = NIL THEN printf("entry buffer NIL\n"); ok := FALSE END;
+   gtk_entry_buffer_set_text(eb, "abcdef", -1);
+   IF gtk_entry_buffer_get_length(eb) # 6 THEN
+      printf("entry buffer length wrong\n"); ok := FALSE
+   END;
+   CStrToM2(gtk_entry_buffer_get_text(eb), buf);
+   IF (buf[0] # 'a') OR (buf[5] # 'f') OR (buf[6] # 0C) THEN
+      printf("entry buffer text wrong: [%s]\n", buf); ok := FALSE
+   END;
+   gtk_entry_buffer_set_max_length(eb, 10);
+   IF gtk_entry_buffer_get_max_length(eb) # 10 THEN ok := FALSE END;
+
+   (* entry completion *)
+   completion := gtk_entry_completion_new();
+   gtk_entry_completion_set_text_column(completion, 0);
+   IF gtk_entry_completion_get_text_column(completion) # 0 THEN ok := FALSE END;
+   gtk_entry_completion_set_minimum_key_length(completion, 2);
+   IF gtk_entry_completion_get_minimum_key_length(completion) # 2 THEN ok := FALSE END;
+   gtk_entry_completion_set_popup_completion(completion, 1);
+   gtk_entry_set_completion(entry, completion);
+   IF gtk_entry_get_completion(entry) # completion THEN
+      printf("entry completion wrong\n"); ok := FALSE
+   END;
+
+   view := gtk_text_view_new_with_buffer(buffer);
+   box := gtk_box_new(GtkVertical, 4);
+   gtk_box_append(box, view);
+   gtk_box_append(box, entry);
+   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_text: 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_text: PASS\n");
+      HALT(0)
+   ELSE
+      printf("test_text: FAIL\n");
+      HALT(1)
+   END
+END test_text.