How to wrap words on custom character (␣) instead of whitespace in a TextView?

I’m creating a touch typing application in gtk4 with vala in Builder. The idea is basically to create something similar to the keybr.com website.

It worked out reasonably well so far. However, replacing whitespaces with ␣ resulted in the wrapping not working anymore inside a TextView.buffer, because it now sees the whole string (of 100 words) as one word. A screenshot of the current situation:

It is however necessary to display this character, so that users can see when they mistyped a whitespace. Below is how it was before, without the custom whitespace character:

To be more precise, I want it so that when it wraps on a ␣, that character is still shown at the end of the line. So in fact, it’s not really the same as wrapping on a whitespace, but than with a different character, I guess. An example from the keybr.com site, to illustrate what I mean:

(I’m a new user, so I can’t post another image apparently, but just follow the link.)

Is there anyway I can specify to get the behavior I want, without doing the wrapping manually (i.e. using newlines and calculating the width of the the textview and so on)? Or perhaps something that looks like it? And if not, what do you suggest to use to do the wrapping manually?


For reference, I’m using a TextView and manipulating its buffer. The repository of the code can be found here (it’s a small app), and here is some code to get an idea:

...
<object class="GtkBox">
  <property name="hexpand">True</property>
  <property name="margin-end">8</property>
  <property name="margin-start">8</property>
  <property name="vexpand">True</property>
  <child>
    <object class="GtkTextView" id="exercise_view">
      <property name="editable">false</property>
      <property name="monospace">true</property>
      <property name="hexpand">true</property>
      <property name="wrap-mode">word</property>
      <style>
        <class name="title-2" />
      </style>
    </object>
  </child>
</object>
...

namespace Clavier {
    [GtkTemplate (ui = "/org/codeberg/vendillah/Clavier/gtk/typing-text-view.ui")]
    public class ExerciseBin : Adw.Bin {

        private const string[] WHITESPACE_CHARACTERS = { "␣" };

        private string whitespace_character;

        [GtkChild]
        private unowned Gtk.TextView exercise_view;

        public string exercise_string {
            get {
                return this.exercise.character_string;
            }
            set {
                this.exercise = new Exercise (value);
                this.exercise_view.buffer.text = value.replace(" ", this.whitespace_character);
            }
        }

        public ExerciseBin () {
            Object ();
        }

        construct {
            this.whitespace_character = ExerciseBin.WHITESPACE_CHARACTERS[0];
            this.exercise_string = "no exercise yet";
            this.connect_listeners ();
        }
        
        ...
}

There is a Gnome library called GtkSourceView that extends Gtk.TextView, and one of the features it has is representing whitespaces with a symbol, which seems to be exactly what you want.

https://wiki.gnome.org/Projects/GtkSourceView

1 Like

Thanks, that seems helpful. Before you answered, I already was searching how I could determine the line size of the display lines by temporarily enabling CHAR wrap. I got stuck at catching resize events and forcing a rerender. The invisible tag disappointing at doing what I hoped it would.

Anyway, I found that GtkSource.View has a property of type GtkSource.SpaceDrawer, which has properties matrix and enable_matrix that I can use. I’m not sure of the exact solution, but I will post it later.


(This is me having trouble installing a library, it’s fixed now. See below.)

First however, as I’m a complete newbie, I don’t know how to install this library. I searched the internet for general approaches with very little useful results. I found the repository, but it doesn’t have an ‘INSTALL’ (even though the README says so) and neither do the commands make sense to me.

I usually do something like

./configure --prefix=/home/your_user/
make
make install

But well, there is no ./configure. I’m wondering if I downloaded the right tar at all. It’s just the repository in the tar. I downloaded the file from here.

Ignoring that I am not sure what to do. I tried the commands from the README mkdir build; meson build; ... on a few locations, this was my best bet:

The Meson build system
Version: 0.63.2
Source dir: /home/vendillah/Projets/Clavier/builddir/gtksourceview-5.8.0
Build dir: /home/vendillah/Projets/Clavier/builddir/gtksourceview-5.8.0/build
Build type: native build
Project name: gtksourceview
Project version: 5.8.0
C compiler for the host machine: cc (gcc 12.2.0 "cc (Ubuntu 12.2.0-3ubuntu1) 12.2.0")
C linker for the host machine: cc ld.bfd 2.39
Host machine cpu family: x86_64
Host machine cpu: x86_64
Program g-ir-scanner found: NO
Library m found: YES
Found pkg-config: /usr/bin/pkg-config (0.29.2)
Run-time dependency glib-2.0 found: YES 2.74.3
Run-time dependency gobject-2.0 found: YES 2.74.3
Run-time dependency gio-2.0 found: YES 2.74.3
Did not find CMake 'cmake'
Found CMake: NO
Run-time dependency gtk4 found: NO (tried pkgconfig and cmake)
Looking for a fallback subproject for the dependency gtk4

meson.build:86:0: ERROR: Git program not found, cannot download gtk.wrap via git.

A full log can be found at /home/vendillah/Projets/Clavier/builddir/gtksourceview-5.8.0/build/meson-logs/meson-log.txt

Can you tell me how I install this package (or direct me to a useful source explaining it)? I created the application so far with Gnome Builder.


Edit 1:

Okay, because I’m stuborn, I couldn’t help but continue searching for it. It appears I had a wrong idea about what meson can do by reading meson’s documentation. As I was biased that it wouldn’t work, I didn’t check the error messages. It clearly said no cmake was found, and that’s an easy fix to try. I discovered it never was a problem, because in the application build, it was never required so far. (Hence I installed cmake through apt.)

I also had to install the libgtk-4-dev (apt) package, because gtk-4 wasn’t found either and necessary to build. As I didn’t want to mess up my system, I installed it in a distrobox container and exported all matches of “gtk4” in /usr/bin:

$ ls /usr/bin/ | grep gtk4
gtk4-broadwayd
gtk4-builder-tool
gtk4-encode-symbolic-svg
gtk4-launch
gtk4-query-settings
gtk4-update-icon-cache
$ distrobox-export --bin /usr/bin/gtk4-broadwayd           --export-path ~/.local/bin          
distrobox-export --bin /usr/bin/gtk4-builder-tool        --export-path ~/.local/bin             
distrobox-export --bin /usr/bin/gtk4-encode-symbolic-svg --export-path ~/.local/bin                    
distrobox-export --bin /usr/bin/gtk4-launch              --export-path ~/.local/bin       
distrobox-export --bin /usr/bin/gtk4-query-settings      --export-path ~/.local/bin               
distrobox-export --bin /usr/bin/gtk4-update-icon-cache   --export-path ~/.local/bin                  
/usr/bin/gtk4-broadwayd from ubuntu-22-10 exported successfully in /home/vendillah/.local/bin.
OK!
/usr/bin/gtk4-builder-tool from ubuntu-22-10 exported successfully in /home/vendillah/.local/bin.
OK!
/usr/bin/gtk4-encode-symbolic-svg from ubuntu-22-10 exported successfully in /home/vendillah/.local/bin.
OK!
/usr/bin/gtk4-launch from ubuntu-22-10 exported successfully in /home/vendillah/.local/bin.
OK!
/usr/bin/gtk4-query-settings from ubuntu-22-10 exported successfully in /home/vendillah/.local/bin.
OK!
/usr/bin/gtk4-update-icon-cache from ubuntu-22-10 exported successfully in /home/vendillah/.local/bin.
OK!

Then, I managed to install the package in user space as follows:

# extract the sources anywhere on your filesystem
cd gtksourceview-5.8.0/
mkdir build
meson -Dprefix=$HOME/.local/bin build # --reconfigure
ninja install -C build

It seemed to have worked!

Next, I modified the dependencies in src/meson.build as follows:


clavier_deps = [
  dependency('gtk4'),
  dependency('libadwaita-1', version: '>= 1.3'),
  dependency('gtksourceview-5.0'),
]

Finally I built the project through Builder. It didn’t work :frowning:

The Meson build system
Version: 0.63.3
Source dir: /home/vendillah/Projets/Clavier
Build dir: /home/vendillah/.var/app/org.gnome.Builder/cache/gnome-builder/projects/Clavier/builds/org.codeberg.vendillah.Clavier.json-flatpak-org.gnome.Platform-44-x86_64-main
Build type: native build
Project name: clavier
Project version: 0.1.0
C compiler for the host machine: ccache cc (gcc 12.2.0 "cc (GCC) 12.2.0")
C linker for the host machine: cc ld.bfd 2.38
Vala compiler for the host machine: valac (valac 0.56.8)
Host machine cpu family: x86_64
Host machine cpu: x86_64
Program msgfmt found: YES (/usr/bin/msgfmt)
Program desktop-file-validate found: YES (/usr/bin/desktop-file-validate)
Program appstream-util found: YES (/usr/bin/appstream-util)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Dependency gtk4 found: YES 4.10.3 (cached)
Dependency libadwaita-1 found: YES 1.3.2 (cached)
Found pkg-config: /usr/bin/pkg-config (1.9.5)
Found CMake: /usr/bin/cmake (3.26.4)
WARNING: CMake Toolchain: Failed to determine CMake compilers state
Run-time dependency gtksourceview-5.0 found: NO (tried pkgconfig and cmake)

../../../../../../../../../Projets/Clavier/src/meson.build:10:0: ERROR: Dependency "gtksourceview-5.0" not found, tried pkgconfig and cmake

A full log can be found at /home/vendillah/.var/app/org.gnome.Builder/cache/gnome-builder/projects/Clavier/builds/org.codeberg.vendillah.Clavier.json-flatpak-org.gnome.Platform-44-x86_64-main/meson-logs/meson-log.txt
FAILED: build.ninja 
/usr/bin/meson --internal regenerate /home/vendillah/Projets/Clavier /home/vendillah/.var/app/org.gnome.Builder/cache/gnome-builder/projects/Clavier/builds/org.codeberg.vendillah.Clavier.json-flatpak-org.gnome.Platform-44-x86_64-main --backend ninja
ninja: error: rebuilding 'build.ninja': subcommand failed

Can anyone help me further?


Edit 2

Okay, so I was impatient (again) and continued searching. I noticed I had share/ and include/ folders in my ~/.local/bin and I couldn’t find gtksourceview-5 in that folder. I retried installing it, omitting bin as follows:

cd gtksourceview-5.8.0/
mkdir build
meson -Dprefix=$HOME/.local build # --reconfigure
ninja install -C build

Next, I tried again a few estimated guesses on what the dependency should be ('gtksourceview-5.0', 'gtksource-5.0', 'gtksourceview-5.8', …), after all, they called it 'gtksourceview-3.0' in the meson tutorial. Turns out 'gtksourceview-5' works, which makes sense as it’s listed in ls ~/.local/include/ - I’m learning new things everyday. Thus, src/meson.build looks like this:


clavier_deps = [
  dependency('gtk4'),
  dependency('libadwaita-1', version: '>= 1.3'),
  dependency('gtksourceview-5.0'),
]

Building in Builder:

The Meson build system
Version: 0.63.3
Source dir: /home/vendillah/Projets/Clavier
Build dir: /home/vendillah/.var/app/org.gnome.Builder/cache/gnome-builder/projects/Clavier/builds/org.codeberg.vendillah.Clavier.json-flatpak-org.gnome.Platform-44-x86_64-main
Build type: native build
Project name: clavier
Project version: 0.1.0
C compiler for the host machine: ccache cc (gcc 12.2.0 "cc (GCC) 12.2.0")
C linker for the host machine: cc ld.bfd 2.38
Vala compiler for the host machine: valac (valac 0.56.8)
Host machine cpu family: x86_64
Host machine cpu: x86_64
Program msgfmt found: YES (/usr/bin/msgfmt)
Program desktop-file-validate found: YES (/usr/bin/desktop-file-validate)
Program appstream-util found: YES (/usr/bin/appstream-util)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Dependency gtk4 found: YES 4.10.3 (cached)
Dependency libadwaita-1 found: YES 1.3.2 (cached)
Found pkg-config: /usr/bin/pkg-config (1.9.5)
Run-time dependency gtksourceview-5 found: YES 5.8.0
Found pkg-config: /usr/bin/pkg-config (1.9.5)
Program glib-compile-resources found: YES (/usr/bin/glib-compile-resources)
Program msginit found: YES (/usr/bin/msginit)
Program msgmerge found: YES (/usr/bin/msgmerge)
Program xgettext found: YES (/usr/bin/xgettext)
Dependency gio-2.0 found: YES 2.76.3 (cached)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Program gtk4-update-icon-cache found: YES (/usr/bin/gtk4-update-icon-cache)
Program update-desktop-database found: YES (/usr/bin/update-desktop-database)
Build targets in project: 8

clavier 0.1.0

  User defined options
    backend: ninja
    prefix : /app

Found ninja-1.11.1 at /usr/bin/ninja
Cleaning... 0 files.
[0/9] Compiling Vala source ../../../../../../../../../Projets/Clavier/src/main.vala ../../../../../../../../../Projets/Clavier/src/view/application.vala ../../../../../../../../../Projets/Clavier/src/view/window.vala ../../../../../../../../../Projets/Clavier/src/view/exercise-bin.vala ../../../../../../../../../Projets/Clavier/src/view/preferences-window.vala ../../../../[2/9] Compiling Vala source ../../../../../../../../../Projets/Clavier/src/main.vala ../../../../../../../../../Projets/Clavier/src/view/application.vala ../../../../../../../../../Projets/Clavier/src/view/window.vala ../../../../../../../../../Projets/Clavier/src/view/exercise-bin.vala ../../../../../../../../../Projets/Clavier/src/view/preferences-window.vala ../../../../../../../../../Projets/Clavier/src/model/exercise.vala
../../../../../../../../../Projets/Clavier/src/view/exercise-bin.vala:85.17-85.27: warning: unreachable code detected
   85 |                 return true; // ???
      |                 ^~~~~~~~~~~        
Compilation succeeded - 1 warning(s)
[9/9] Linking target src/clavier
[0/1] Installing files.
Installing data/org.codeberg.vendillah.Clavier.desktop to /app/share/applications
Installing data/org.codeberg.vendillah.Clavier.appdata.xml to /app/share/appdata
Installing src/clavier to /app/bin
Installing /home/vendillah/Projets/Clavier/data/org.codeberg.vendillah.Clavier.gschema.xml to /app/share/glib-2.0/schemas
Installing /home/vendillah/Projets/Clavier/data/icons/hicolor/scalable/apps/org.codeberg.vendillah.Clavier.svg to /app/share/icons/hicolor/scalable/apps
Installing /home/vendillah/Projets/Clavier/data/icons/hicolor/symbolic/apps/org.codeberg.vendillah.Clavier-symbolic.svg to /app/share/icons/hicolor/symbolic/apps
Running custom install script '/usr/bin/glib-compile-schemas /app/share/glib-2.0/schemas'
Running custom install script '/usr/bin/gtk4-update-icon-cache -q -t -f /app/share/icons/hicolor'
Running custom install script '/usr/bin/update-desktop-database -q /app/share/applications'

It works :tada: (or well, it builds) :wink: Hopefully this setup guide helps out other people that are having trouble installing a library.


I will now try to use gtksourceview-5 to display custom whitespace characters so I can post the solution and close this thread - if all goes well.

Well damn.

It appears I can only choose which type of space characters I want to have drawn - didn’t check the methods on how to manipulate that matrix itself, sadly. I cannot map them to other characters. More importantly, I can’t seem to color them with tags, they just remain the same color, so the original problem remains: I can’t show to the users that they mistyped a whitespace.

I set a tag from position 0 to 20 to be absolutely sure, and this was the result:

Perhaps I can file a feature request so they allow custom drawing of a whitespace symbol; however, I’m still awaiting gnome-sysadmin@gnome.org to resolve my account creation issue…

I’m leaving the discussion open, in case somebody else knows other solutions.


For reference to those that installed the library and get errors: it appears that if you’d just replace "GtkTextView" with "GtkSourceView" in the .ui file and GtkSource.View with Gtk.TextView in the vala file, it’s not enough, even though the GtkSource.View is derived from Gtk.TextView. I solved it on accident by constructing another TextView inside the class constructor as follows:

namespace Clavier {

    [GtkTemplate (ui = "/org/codeberg/vendillah/Clavier/gtk/exercise-bin.ui")]
    public class ExerciseBin : Adw.Bin {

        [GtkChild]
        private unowned GtkSource.View exercise_view;
        public ExerciseBin () {
            Object ();
            /** The next line is just to prevent some random, unclear errors from occuring. */
            var source_view = new GtkSource.View ();
        }
... }
}

So, if you aren’t using flatpak, I heavily recommend you do. With the standard Builder python example project, no modifications, all I have to do to get GtkSourceView is this:

gi.require_version('GtkSource', '5')

from gi.repository import Gtk, GtkSource

About the .ui files, if you are using GtkSourceView as part of a custom widget, like a main window, you have to instantiate GtkSourceView in the same file you instantiate the custom widget to get it working. This is the basic: (python examples, but very simple to translate to Vala)

class MySourceView(GtkSource.View):

    __gtype_name__ = 'MySourceView'

    def __init__(self):

        super().__init__(self)

If you create a custom widget based directly on GtkSourceView, you have to instantiate it linking the ui file like this:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  <template class="MySourceView" parent="GtkSourceView">
    <property name="vexpand">true</property>
    <property name="valign">0</property>
    <property name="hexpand">true</property>
    <property name="halign">0</property>    
  </template>
</interface>
@Gtk.Template(resource_path="/myapp/appid/src/gtk/ui/MySourceView.ui")
class MySourceView(GtkSource.View):

    __gtype_name__ = 'MySourceView'

    def __init__(self):

        super().__init__(self)

That said, I was reading GtkSourceView and Tag:draw-spaces doesn’t work like I thought it would. I think you won’t even need GtkSourceView. I have another suggestion.

Pango has an attribute that lets you request visible rendering of whitespaces (and others non visible marks), and you can select the glyph to show based on the text font, and the color. I don’t have any code to suggest because I myself have no experience with Pango, but the feature exists.

There is a possibility that I never tried dependency('gtksourceview-5'), until I figured out how to install it and Builder (yes, the flatpak version) doesn’t use the package at all - I don’t know, but it seemed not to work. I’m not sure if the simplicity of programming in a python environment applies to vala environments as well, which has vapi bindings. (Again, I’m merely a beginner using this, so no idea.)

Besides __gtype_name__ = 'MySourceView' (which I don’t know what it would do, just type it?), the vala class looks to do the same (the code is in the first post). I didn’t give an updated version as not to produce too much text blocks. And yes, Tag.draw_spaces was for more fine grained control of the locations that would have drawing the symbols enabled or not, I believe, reading the documentation.

Anyway, about the last paragraph you wrote: I don’t know what glyphs are - I will search in meanwhile. I also don’t know where to look for this functionality, even though I would first need to understand exactly what it does. Searching the documentation for ‘space’ didn’t result in anything. Can you link to some sources?

Do you mean ShowFlags.SPACES by any chance? In case so, from what I understand, it will take the specified characters (SPACES, NEWLINES, and so on) in hidden_characters (hidden by a tag) and display them after all. I tried to use it together with tags (as briefly stated before), but it didn’t do anything. The other option did show NEWLINEs, so I’m confused. I think that’s where I encountered “glyphs” or “grapheme” for the first time. Eitherway, with the option enabled it did not use different (visible) tokens to represent a newline.

I really don’t find anything. Maybe I encountered what you meant, but didn’t recognize it being useful yet. (In mean while, I finally searched what “glyphs” or “grapheme” are - wikipedia helped out.

So, Pango render all type of things on textview: different fonts, font colours, bold, italic, etc. To do this, in general, we apply a GtkTextTag to a text. There are various pre-built tags to deal with the most common pango elements, but it is possible just insert pango markup directly in the TextView by TextBuffer.

Example of add a text as italic:

# Position to insert text
iter = YourTextbuffer.get_iter_at_line(0) 

# Inserting text         
YourTextbuffer.insert_markup(iter[1], "<span style=\"italic\">Text to be showed as italic</span>", -1)

The pango markup to show white-spaces representation is:

<span show="spaces">Some space here</span>

This will show the symbol determined by the font used, some fonts have symbol to represent white-spaces, others not, a simple dot is a common representation case.

Off course, its possible to change the text font by the same means, and at the same time, just constructing a markup string to do it.

1 Like

Wow, that’s great! It’s almost what I need.

The result now:

As you can see, it wraps at the spaces (between “quince” and “grape”) and shows spaces (between the words on each line. The only thing missing is the space that is being wrapped - it’s not visible :confused: If that would be shown at the end explicitly, it would be perfect.

Also unfortunate is that I can’t set a custom space character (it’s the hardcoded), but this one is the one I desired to use the most, so I can settle with it.

Any ideas? I tried all flags:

this.exercise_view.buffer.insert_markup (
  ref iter, "<span show=\"spaces|line-breaks|ignorables\">" + value.character_string + "\nhello</span>", -1
);

(Also, it’s interesting/confusing that using the span does show these spaces with a symbol, whereas using custom tags on the buffer with the ShowFlags (that have the exact same possible properties as the show-attribute) as I did before do not.)

Indeed, the character is font hard-coded, but you can change the font just to the for this character using pango if you want a different character to white-spaces maintaining the standard text font.

About the missing spaces, I don’t know what’s happening, to me it’s working.

iter = self._buffer.get_iter_at_line(0)

self._buffer.insert_markup(iter[1], "<span show=\"spaces\">Some space here and \n break line with spaces</span>", -1)

Maybe something related with the way you are doing the insertion, I really don’t have enough knowledge to be certain.

Good to hear I can use custom fonts - I may search for that later on if this approach is what I’ll settle with!

About the missing spaces. Yes, I have your behaviour as well. If I manually add a newline, then it does show the space. However, if I don’t, I won’t see a space when it wraps. I make sure it wraps by making the window short enough, just for demonstration purposes, but the idea is that the text view will contain about 30-40 words to type, wrapped.

this.exercise_view.buffer.insert_markup (ref iter,
    "<span show=\"spaces\">" +
    "a line with a few words that can be wrapped " +
    "\nhello \n world \n." + 
    "</span>", -1
);

image

The whole point is that I don’t have to manually add newlines and instead let the wrapping do it for me, because if I will be manually adding these newlines, then I might as well just manually replace the whitespaces with whatever character I want in the buffer (which was the approach so far) and try to add newlines!

The problem with this manual approach is that I don’t know the amount of characters that fit in a line, as long as the line has not been displayed, plus, I haven’t found yet how to force a redisplay, nor do I know how to catch a resize event - perhaps the latter is something on the window class. Now I think of it, I can fill it with whitespaces to measure the size of the display line! :stuck_out_tongue: which will make sure that the user wouldn’t see things redisplaying, only appearing. It all seems still a bit hacky for just displaying spaces with a symbol.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.