This book gives an introduction into design and creation of graphical user interfaces using the GTK widget tool kit and the Nim programming language. The book has its focus on the Linux operating system (OS). While the Nim programming language does support all mayor operating systems, GTK has it main emphasis on the Linux OS. Windows and macOS are supported by GTK, but without true native look and feel. Android and iOS is not supported by GTK, but there is some early experimental support for the Librem mobile devices manufactured by the Purism company. As GTK is compact and has a modular design, it can be also used on devices with restricted resources like the Raspberry Pi family. While GTK is generally not used to create web applications, it may be possible to run GTK applications locally in a web browser by using the broadway GTK backend.

The examples in this book uses the Nim implementation of the team around Mr. A. Rumpf in version v1.4, the first GTK 4 version that is available in late 2020 and the Nim GTK bindings provided by the gintro package in version 0.8.5. For other Nim implementations or different GTK bindings modifications of the provided examples can be necessary.

Graphical User Interfaces

GTK is the name of a toolkit for the design and the creation of graphical user interfaces (GUIs) that allows users to interact with computer programs by use of graphical elements like buttons, sliders, drop-down menus and input fields. These elements are called widgets. Widgets can be grouped to build larger entities like file or message dialogs. The top level widget is generally a rectangular container called a window that contains all the other widgets. The initial release of GTK appeared in the year 1998 named GIMP tool kit and was labeled GTK+. As the Name GTK implies it was closely bound to the famous GIMP drawing program (GNU image manipulation program) and was intended to replace the older Motif Unix GUI for GIMP.

Graphical user interfaces were introduced already a few decades after the invention of computers with the goal to simplify the interaction between humans and computers by replacing the traditional terminal based textual user interfaces. GUIs allowed even untrained people the intuitive interaction with computers without the need to learn and remember many textual command. Closely coupled to graphical user interfaces is the computer mouse, a small gadget that rests on the table and maps its movements to a pointer drawn on the computer screen allowing the interaction with the widgets. Today the computer mouse is often supported or substituted by touch pads or touch displays.

After the release of GTK that toolkit was used by other software too, and in 2002 Version 2.0 of GTK appeared. GTK 2 had already a more modular design and was not that tight couple to GIMP. In 2011 GTK 3.0 appeared, which provided many new features. Most important was a new customizable design supported by cascading style sheets (CSS), and the use of libraries like cairo for drawing the graphical elements and of pango for font rendering. In late 2020 official release of GTK 4 will appear, which has again an improved internal design, an improved application programming interface (API) and which supports OpenGL and Vulkan hardware drawing for the widgets to maximize performance while keeping CPU load low.

Unfortunately GTK is currently not in a very active state. There seems to be one or two paid full-time developers which try to finish official release 4.0 and a few volunteers which support the development. The number of active GTK programmers, partly mirrored by the traffic of the GTK/Gnome forum, seems to be tiny, and most of them use GTK still direct from C. Maybe because they have learned C decades ago and never tried a modern language, maybe because GTK itself is written in C and its native API documentation and examples are based on C language, or maybe because some bindings to other languages are of bad quality or have not enough documentation and examples. Writing tiny GTK apps in plain C may be OK, but for larger programs plain C becomes unmaintainable very fast. At least for Python, JavaScript, Rust, C++ and D there seems to exists a few GTK users. But still the number of non trivial GTK apps which appeared in the last decade is very small, and some existing apps did not manage to move from GTK 2 to GTK 3 at all. But there is some hope that with the new GTK 4 things will improve. Maybe with the official release of GTK 4 at the end of 2020 the quality of languages bindings will increase and maybe some new good books and tutorials will appear making learning GTK programming easier and more fun.

While GTK can be used on Windows and macOS computers, it is generally used on Linux, and there often in conjunction with the Gnome desktop environment. The Gnome foundation is the most important supporter of GTK development. GTK does not support the Android or iOS operating systems for mobile devices. The GTK related libraries uses the LGPL software license, while the Nim compiler and most of Nim’s external packages are using the MIT software license. Both licenses allows the creation of proprietary closed source software, as long as for the LGPL licensed libraries dynamic linking is used.

Like most traditional GUI toolkits GTK uses a retained mode, where the graphical scene is updated and redrawn only when necessary. In contrast to retained mode GUIs in the last years immediate mode GUIs has become popular. These GUIs often have their origin in simple GUIs for games and redraw the whole scene permanently, generally synchronized with the screen refresh rate. The permanent redraws create some CPU load of course, but for games that does generally not matter, as CPU and GPU load are dominated by the game itself, and with OpenGL or Vulkan hardware support drawing the GUI does not cause high CPU load. And finally the modern retained mode GUIs like GTK are not really that static any more as they contain many animations. So the distinction between retained and immediate mode GUIs is not that sharp.

The GTK toolkit has a modular design with these main components:

GTK

Initialy GTK+, the GIMP tool kit. The GTK module builds the core of the GTK widget tool kit and contains all the widgets.

GDK

The gimp drawing kit. High level drawing related functions and data types.

GdkPixbuf

Loading and manipulation of images.

GObject

The GObject module provides an API for object orientated programming (OOP) in the C programming language.

GLib

GLib provides many supporting functions and advanced data types.

GIO

Support for input and output operations including asynchronous operations.

GSK

The GTK Scene Graph Kit is used to optimize the drawing and the widget refresh.

Graphene

Math support like vectors and matrices.

ATK

Accessibility support like screen readers or text magnifiers.

Other GTK related modules are GtkSourceView for advanced text layout support as used for text editors like gedit, the rsvg module for support of scaleable vector graphics (SVG) and the VTE module for the creation of terminal windows. GtkSourceView and VTE are not yet available for GTK4.

Additional GTK uses these libraries for drawing and font rendering:

Cairo

Scaleable vector drawing

Pango

Font rendering

OpenGL, Vulkan

GPU supported graphics

For Linux there is one more abstraction layer between the GTK toolkit and the computer hardware, which is the wayland display server, a modern implementation of the original X Window System.

All these components are written in the C programming language. C is a very old, restricted and sometimes unsecure language, which can lead to very verbose code, which is difficult to maintain. As GTK has an object-orientated design, but C language does not support OOP style, a whole object system called gobject was written for GTK from scratch. And as C does not support high level data structures like resizeable strings, hash maps, asynchronous in out operations and much more important functionality which modern languages generally provide, this was also written from scratch and is provided in supporting libraries like glib and gio. As C does not support automatic memory management, in GTK it is sometimes necessary to release memory manually, which may lead to the well known problems like memory leaks or use after free issues.

It seems to be obvious that all these bloated legacy stuff is nearly unmaintainable considering the tiny GTK and Gnome community. And today, when we have so many nice modern programming languages available, nearly no one intends to write apps in C. When we take into account the fact that GTK does not even supports the popular Android OS for mobile devices, we may ask why we should care for GTK at all still.

Indeed a popular competitor of GTK is the Qt GUI toolkit with its KDE Linux desktop environment. Qt appeared already in 1995 with a license model not well suited for free open source software (FOSS), and is now available in version 6 with much less restricted licenses. Qt is written in C++ and is unfortunately even much more bloated than GTK, and it uses a so called meta object compiler (MOC) as some form of C++ preprocessor. Qt is really very large and includes a lot stuff which is not really GUI related like network, web and database functionality or support for many custom data types. All that is also available by modern C++ or specialized libraries, so Qt can be regarded as a bloated application framework that is nearly a whole operating system. The advantage of Qt is that it is active developed and supports all important operating systems including the mobile Android and iOS systems with a native look and feel.

As the proprietary operating systems like Windows, macOS, Android and iOS have all their own native GUI, we do not need a separate toolkit when we plan to develop apps for only one of these systems. And indeed users generally prefer apps that only use the native GUI and avoid additional layers like GTK or Qt.

For many Windows or macOS users GTK has the disadvantage that GTK draws all it widgets itself, it does not use the native graphical elements of the proprietary systems. GTK allows theming by use of cascading style sheets (CSS) so it can be tuned to look not too strange on Windows and macOS, but look and feel generally does not really map to native apps. Qt draws its widgets itself on Linux, but can try to use native elements on Windows or macOS since version 4.0, which may provide a more native look and feel.

One more important GUI toolkit is wxWidgets, which uses GTK on Linux and native GUI elements on Windows and macOS. Some people like wxWidgets as it is a really cross platform GUI toolkit with native look and feel, but at least for Linux it is just one more layer on top of GTK. And it does not support the mobile operating systems Android and iOS.

Beside the large toolkits Qt and GTK there exists many more smaller ones, as the already mentioned wxWidgets, the FLTK toolkit written in C++, or the old and plain ones like LessTif or TK.

And finally we have always the option not to use a GUI toolkit at all but to create a GUI based on HTML and JavaScript which can be used with web browsers.

The fact that GTK is written in C and so is very hard to maintain is at the same time a large benefit: As C is a simple languages without advanced concepts like classes, templates, inheritance or automatic memory managements it is generally very easy to create bindings to C libraries from other programming languages. For GTK this fact is even supported by the GTK gobject-introspection database which allows to create bindings to all the GTK related libraries in a semi-automatic process.

So the majority of all the new modern computer programming languages have bindings to the GTK toolkit. For Qt which is written in C++ it is much more difficult to create bindings, as C concepts like C classes, templates and the MOC preprocessor makes automatic bindings generation difficult.

So Qt is mostly used direct from C++, or its well supported Python bindings are used. Qt language bindings for many other programming languages exists, but it is hard to keep them up to date. Sometimes Qt GUIs are also created with QML, which allows to create user interfaces in a declarative manner. QML bindings are available for various programming languages.

While GTK is still used often directly from C, it provides a larger set of official supported languages bindings which include C++ (gtkmm), JavaScript, Python, Rust, Perl and Vala. D and Go are also well supported, and for many other programming languages at least bindings for a subset of GTK exists.

In this book we will use gobject-introspection based bindings to write GTK apps in the Nim programming languages. Nim is a modern compiled statically typed language, that can generate fast native executables from clean high level source code. As Nim does not enforce OOP design with inheritance as languages like Java do, our Nim examples follow the original C examples provided by GTK core developers. Some other modern languages like Go or Rust use generally a similar approach and do not enforce OOP and inheritance, while classical OOP languages like Java, Python or Ruby generally enforce the use of classes and inheritance for GTK apps. C++ with its gtkmm GTK bindings also push its users to OOP design.

We will use for this book semi-automatic generated GTK 4 and GTK 3 bindings which are generated by the gintro package, where g stands for all the gtk related libraries and intro for introspection as the bindings are generated by use of gobject-introspection.

You should be aware that for the Nim programming language many more GUI toolkits are available, some based also on GTK but with a different API design, and some based on other libraries or written directly in Nim like the NimX module.

wNim

Nim’s Microsoft Windows GUI Framework

wxnim

Nim wrapper for wxWidgets

fidget

Figma based cross platform UI library

nigui

Cross-platform desktop GUI toolkit

genui

Cross-platform native UI toolkit

nimx

Cross-platform GUI framework in pure Nim

webgui

Web Technologies based Crossplatform GUI Framework

nimgui

Cimgui bindings (dear imgui immediate mode lib)

nfltk

A wrapper for the Fast Light Toolkit

iup

Iup wrapper for Nim

nimqml

Qt Qml bindings

ui

Beginnings of what might become Nim’s official UI library

uibuilder

UI prototyping with Glade

sciter

Nim bindings are work in progress

nanovg

Nim wrapper for the C NanoVG antialiased vector graphics rendering library for OpenGL

rdgui

A modular GUI toolkit for rapid

nodesnim

The Nim GUI/2D framework based on OpenGL and SDL2

neel

making lightweight Electron-like HTML/JS GUI apps with C, C++, or Objective-C backends

mui

A tiny, portable, immediate-mode UI library written in ANSI C

Some of these bindings may currently not compile with the latest Nim compiler or may not support the new ARC memory management. But we recommend to investigate them before you decide to use gintro, maybe one of them fits better you needs. wNim should be a good choice when you intent to develop for windows only, nimx may be the most fun as it is pure Nim, fidget looks really nice, nigui supports native look for Windows, and finally nimgui is a bindings to the dear imgui immediate mode library. Most of above bindings are hosted at github, you can use github, google or nimble search to locate the packages.

Introducing GTK

Note that we assume for this book that you are already familiar with computer programming in general and with the Nim programming language. At least you should be able to open a terminal window and to enter and execute some commands. Some basic knowledge of the C language would also help, as we sometimes use C code as a starting point for our Nim programs.

GTK is an event driven toolkit. That is we create widgets like buttons or text entry fields and connect them with one or multiple functions, which are then automatically called when an input event like a button press or a text entry is discovered by GTK.

For creating a GUI we create and arrange all our widgets, and then connect widget actions with our handler functions, called callbacks. The callback can perform arbitrary tasks, this includes modifying the GUI by changing the appearance of widgets, or by removing widgets or by adding new widgets.

Generally GTK does manage the actual layout of the widgets automatically for us, that is widgets are automatically arranged and resized to create a clean nice look, and when we resize the top level window or add or remove widgets, the layout adapts itself automatically. This behavior is archived by the boxes in boxes concept represented by GtkBox — we create vertical or horizontal boxes, which we can fill with widgets, and we can put these boxes again in other larger boxes in a recursive manner. In this way we can specify the desired layout, but the concrete layout is done automatically. For example buttons can resize automatically when the label text or font size change. The horizontal or vertical boxes are supported by two dimensional grids or by special containers like header bars. We can tune the layout by specifying margins or distances between widgets, or we can modify the visual appearance with CSS. But generally we do not create layouts where we specify exact pixel positions for GUI elements. GTK also offers a fixed positioning and sizing model, using the GtkFixed and the GtkLayout containers, but that is used only in rare cases. Recently GTK also got a new constraint-based layout manager developed by Emmanuele Bassi, which may allow to easily create even more flexible layouts.

We can create the desired widgets directly in our Nim source code, for example by a call of newButton("Sort List"), or we can decide to create all the widgets in a declarative fashion in external XML files. In the XML files we can arrange and group all of our widgets in hierarchical layouts, and we can attach attributes like size, color or textual labels to the widgets. We can create that XML file manually, or we can decide to use the interactive Glade tool to create the XML file.

Using XML files and the Glade tool may appear simpler, more intuitive and more flexible. When we create GTK programs directly in the C languages that may be true, as C is a cryptic and verbose languages, which makes changes really difficult. For high level languages like Nim or Python that is not really the case, so it is not always clear if use of external XML files really have a benefit. XML based layouts have the advantage that the GUI layout can be modified without recompiling the program source code, so even users that do not have the source code of a program can modify the GUI layout. But this is only an advantage when we do ship our software without source code, and when we use the XML files in its original form as external text files. But in most cases we integrate the XML files again into our main executable to simplify the deployment. An additional disadvantage of the use of XML files is that the Glade tool may not support all widget types and their properties well, so that manual modifications of the XML files can be necessary.

So for the first part of this book we will create our GUI layout directly in the Nim source code. Later we will introduce the use and layout of the XML files, and we will describe how the GTK builder library component is used to import the XML files and to access the widgets.

Installation of GTK

When you are interested in using GTK with Nim, them we should assume that you have both already installed on your computer and played with them.

For Nim you will find detailed installation instructions on the Nim homepage: https://nim-lang.org/install.html

On Linux computers GTK is generally installed by default, or at least available by the package manager of your Linux distribution. If you should still have an old Linux system which does not yet provide GTK4, you may install it beside your GTK3. For example you may install the latest GTK4 from git which these commands entered in a Linux terminal window:

# https://discourse.gnome.org/t/installing-gtk4-for-testing-on-opt-ii/3349/4
git clone https://gitlab.gnome.org/GNOME/gtk.git
cd gtk
meson --prefix /opt/gtk builddir
ninja -C builddir
ninja -C builddir install

# maybe also necessary:
export GI_TYPELIB_PATH=/opt/gtk/lib64/girepository-1.0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/gtk/lib64/
export PKG_CONFIG_PATH="/opt/gtk/lib64/pkgconfig/"

# you may test your installation with:
GSETTINGS_SCHEMA_DIR=/opt/gtk/share/glib-2.0/schemas /opt/gtk/bin/gtk4-demo

The installation of GTK for Microsoft Windows is described on the GTK home page:

and for macOS:

If you have problems with the installation then you may ask for support at the GTK internet forum:

In the rest of this book we assume that you have also installed the Nim compiler and a C compiler like gcc or clang.

When you have not yet installed the Nim GTK bindings then you may enter in a terminal window:

nimble install gintro

The gintro package generates the bindings between the GTK libraries and the Nim language locally on your computer by querying the gobject-introspection data base. The generated modules depend on your operating system (Linux, Windows, Mac, 32 bit, 64 bit) and on the available GTK version. If you update your GTK system it may be necessary to update gintro by nimble uninstall gintro; nimble install gintro. Executing that sequence is also recommended when a new gintro release is available. You can also use nimble install gintro@head to get the latest gintro with latest, less tested fixes.

The GTK Nim Bindings

The Nim GTK relation has a long history. It started with low level bindings created by the c2nim tool many years ago. In 2015 we then got low level, c2nim generated GTK3 bindings, which are still available in the oldgtk3 nimble package. But it was obvious that low level GTK bindings are more than useless — they transfer all the ugly aspects of plain C into the Nim world, without transferring the few benefits of the GTK C API like elaborated C GTK macros and well documented and tested API. Nim coding using low level GTK bindings is a pain compared to using C directly. So it was considered to use GTK’s gobject-introspection API to generate high level Nim bindings. A first experimental attempt was made already in 2015 by Mr. Jason Mansour (https://github.com/jdmansour/nim-smartgi), but the project was aborted soon. At the same time Mr. Jonne Haß started to create gobject-introspection based bindings for the new Crystal programming language (https://github.com/jhass/crystal-gobject), and the Rust project spent much work in creating gobject-introspection based bindings to the Rust language. In 2016 Dr. Salewski started a second try to write a gobject-introspection based bindings generator in Nim and for Nim from scratch, with the initial goal to create some working bindings similar to the oldgtk3 ones. In the following years work on the new bindings continued, with the goal to provide really high level and high quality bindings covering nearly all GTK related functions and data types. The nimble package containing the bindings generator was called gintro, and in 2020 support for Nim’s new ARC memory management and for GTK4 was added.

From time to time there are request to provide pre-built bindings instead of generating them locally for each nimble package install. One often raised argument is quality insurance and audit support. Well we would have to provide at least 6 different sets of the bindings — for Linux, Windows, Mac, each in 32 and 64 bit variant. And as GTK 4 is actively developed, we would have to update and test all of them regularly. Still it would be possible that the newest modules would not work for people with older GTK versions. This does not mean that this solution is bad and will not be supported in future, but the required work load to maintain it would be really large. Maybe a group of really active volunteers using various operating system could manage it. Another often requested solution is providing machine independent bindings similar as the c2nim program tries to provide. But the fact is that gobject-introspection is designed to provide machine dependent information only. So the solution would be to generate machine dependent files for all supported targets first, and then to compare the files for differences and try to unify them by including machine sensitive when statements. Maybe that would be possible. Unfortunately the initial gobject-introspection based files vary drastically with each new GTK release, so we would need a permanent unifying and testing process. Maybe we could fully automate that in some way? If not then again the work load for the maintainers would be very high.

Maybe in future we will get also high level GTK bindings from other sources as an alternative to the gintro based ones. Beside gobject-introspection based ones other C header based approaches using libclang or using the tree-sitter library would be possible. Such ideas have been discussed, but we should not have too high expectations. The information which can be extracted from header files is generally not sufficient for high level bindings, and using gobject-introspection is not really easy and much work. But maybe someone will convert a well working gobject-introspection based bindings generator to Nim, maybe one which is used by languages like Go or Rust. As gintro generates high quality idiomatic bindings, all bindings generated in alternative manner should be fully compatible, but maybe would detect some hidden bugs.

Instead of using gobject-introspection it was suggested also to directly inspect the XML GIR files to gain information for the binding generation process. But that seems to be a bad idea, even considering the fact that the gobject-introspection API is not well explained and difficult to use.

Finally one may ask why the bindings are at all generated during the install process, and not on the fly during the compilation of user programs. Theoretically on the fly generation may be possible — Nim macros may be able to query the gobject-introspection database during the compile process for required data types and functions. The benefit would be that always the latest GIR files where used, the user would never have to update the gintro nimble package. And for each compile of the user program only the really needed data would be processed, while with the pre-generated module files the whole GTK interface is compiled each time. But for statically typed languages on the fly bindings generation seems to be strange and probably is impossible. Compiling an average Nim GTK program takes about 3 seconds with current Nim compiler, and will become faster when the experimental incremental compilation will work reliable. So there is no real reason to complain.

Legacy Program Layout

GTK 3 introduces the GtkApplication framework, which is continued by GTK 4 and is generally the recommended way to create GTK applications. Programs based on GtkApplication seems to be a bit more complicated than the ones with legacy GTK 2 startup code, but the GtkApplication style offers some benefits like management of multiple program instances, parameter passing, and it enables new modern layouts with header bars and hamburger menus. So we will use the GtkApplication style in the rest of this book.

As you will still find many example programs that still uses the old GTK 2 program startup code, we will present that program shape here first. The following C program called simplegtk3.c uses the old GTK 2 style and can be compiled with this command:

gcc -o simplegtk3 simplegtk3.c `pkg-config --libs --cflags gtk+-3.0`

You can run it from a terminal window with this command:

./simplegtk3

The program will open a tiny window containing a single push button. Clicking that button will write a message to the terminal window. You can terminate the program by clicking with the mouse on the cross on the upper right corner of the program window.

simplegtk3c
simplegtk3.c
// based on https://gitlab.gnome.org/GNOME/gtk/-/blob/master/tests/simple.c
// gcc -o simplegtk3 simplegtk3.c `pkg-config --libs --cflags gtk+-3.0`

#include <gtk/gtk.h>

static void
hello (void)
{
  g_print ("hello world\n");
}

int
main (int argc, char *argv[])
{
  GtkWidget *window, *button;
  gtk_init(&argc, &argv);
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "hello world");
  gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
  g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
  button = gtk_button_new ();
  gtk_button_set_label (GTK_BUTTON (button), "hello world");
  gtk_widget_set_margin_top (button, 10);
  gtk_widget_set_margin_bottom (button, 10);
  gtk_widget_set_margin_start (button, 10);
  gtk_widget_set_margin_end (button, 10);
  g_signal_connect (button, "clicked", G_CALLBACK (hello), NULL);
  gtk_container_add (GTK_CONTAINER (window), button);
  gtk_widget_show_all (window);
  gtk_main();
  return 0;
}

The source code has the typical structure of GTK 2 programs written in C language: The first two lines are only comments, it follows an include directive to make the gtk library available. The program consists of two functions, the C main() function which is executed at program startup automatically and a callback function called hello(). As usual for C programs the main() function has two parameters, an array of optional command line parameters and the number of parameters. These two parameters are passed to the gtk_init() function which has to be called at the beginning of an old style GTK program. In the main() function a new top level window instance is created by calling gtk_window_new(). Then we set the window title and we set the resizable property to false to give that window a fixed size. Then the function g_signal_connect() is called to connect the "destroy" signal to the predefined callback function gtk_main_quit() provided by gtk. The destroy signal is emitted for the window by GTK when we click with the mouse on the window close symbol. In this case gtk_main_quit() terminates the whole program. After this we create a button instance and set some properties of the button like its label text and its margins to reserve some space between the button and the border of the enclosing window. We connect the "clicked" signal of the button instance to our hello() callback and add the button to the window. We have to call gtk_widget_show_all() to make the window and its parents visible. Finally we call gtk_main() to transfer control to the GTK main loop. That loop now runs as some form of supervisor waiting for user actions and calling the connected callback when appropriate. When the user clicks the close button of the window the program terminates, the top level window is closed, the GTK main loops stops and the last line of the C main() function returns the value 0 to the operating system to indicate that no error has occurred.

A few remarks to above program: All the GTK widgets are objects which GTK creates for us by calls like gtk_button_new(). These "constructor" calls returns a pointer to the widget and we use this pointer to access and interact with the widget later. The GTK widgets build a hierarchy with parent/child inheritance in OOP fashion. The basic GTK widget is a sub class of the gobject object, and other widgets like windows or buttons are again sub classes of widget. In GTK C code the widget is generally used as the static base type. So when a button widget is used, then a variable of type widget is declared and gtk_button_new() returns not a button instance, but the plain widget type. This has the consequence that whenever we use a button function on that instance, we have to cast the widget to a button type as in gtk_button_set_label (GTK_BUTTON (button), "hello world)". That is a convention chosen by the initial GTK creators. Note that in C casts like GTK_BUTTON() do type checks at runtime and give runtime warnings when the types do not match. We may wonder if we have to free widgets when we do not need them any longer. Indeed in C code that can be necessary in some cases. GTK uses reference counting for its objects, that is that each object has a reference counter. In C we can increase that counter to reference an object, that is to ensure that it is kept alive and is not destroyed by GTK. When we do not need that object any more we can decrease the reference counter. If the reference counter drops to zero then GTK destroys the object, that is GTK frees its memory and closes related resources. But often we do not have to really care for that. The reason for that is that GTK uses a special variant of reference counting: When we create a widget with a constructor like gtk_button_new() we get an instance which is market as "floating" indicating that the instance is not already owned by someone. Generally we insert each widget that we create into another widget, like a window or another container widget, and that container widget then takes ownership of its child. When we destroy a container or when our program terminates and the top level window is destroyed, then all its children are automatically freed. So we have not to care about all that memory management in this case. But there are exceptions to this process, so C programmers sometimes have to carefully check when they have to ref() and unref() resources. Fortunately high level languages like Nim or Python have a garbage collector which frees all objects when appropriate, so we have not to care for this. Nim with gintro supports even the new ARC memory management, which is deterministic and scope based: When a widget or another object goes out of scope it is immediately freed and all related resources are closed or released.

In the code above we use the function g_signal_connect() to connect widgets to a user defined callback function. The signal type like "clicked" is not an enumeration type as we may have expected but a string. The string data type shall enable extending of the signal system — with enums that would not be possible. The g_signal_connect() function allows to pass additional user data in form of a plain void pointer to the callback functions. If there is no data parameter then NULL is passed. Fortunately in Nim we can do the optional parameter passing in a type save way.

Another aspect that we should discuss is the margin size which we have specified for our button. The margin is the void area around a widget. The literal value 10 used in the set_margin() functions is a pixel size, as the GTK API is for historic reason pixel based. Today where displays with very high DPI resolution are available, the pixel is not always a good size unit. Distances like margins are generally related to text size, so size units like em or ex for the size of letters as used in HTML and CSS would be a more flexible size unit. To allow using of GTK on screens with very high DPI value GTK3 and GTK4 use logical pixels, as opposed to physical ones. This is, the user can configure the desktop environment to scale the pixel size, generally by factor 1 for ordinary displays and by 2 for high DPI displays. Fractional scaling factors are not yet supported, so this does not really allow a fine tuning of the visual layout. Generally you should know that what really matters is not the DPI value but viewing angle: When you have a large display with low DPI value and you move it away from your eyes, it will appear like a smaller display with higher DPI value.

Now let us investigate how above C program looks for GTK4:

simple.c
// https://gitlab.gnome.org/GNOME/gtk/-/blob/master/tests/simple.c
// gcc -Wall simple.c -o simple `pkg-config --cflags --libs gtk4`

#include <gtk/gtk.h>

static void
hello (void)
{
  g_print ("hello world\n");
}

static void
quit_cb (GtkWidget *widget,
         gpointer data)
{
  gboolean *done = data;
  *done = TRUE;
  g_main_context_wakeup (NULL);
}

int
main (int argc, char *argv[])
{
  GtkWidget *window, *button;
  gboolean done = FALSE;
  gtk_init ();
  window = gtk_window_new ();
  gtk_window_set_title (GTK_WINDOW (window), "hello world");
  gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
  g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
  button = gtk_button_new ();
  gtk_button_set_label (GTK_BUTTON (button), "hello world");
  gtk_widget_set_margin_top (button, 10);
  gtk_widget_set_margin_bottom (button, 10);
  gtk_widget_set_margin_start (button, 10);
  gtk_widget_set_margin_end (button, 10);
  g_signal_connect (button, "clicked", G_CALLBACK (hello), NULL);
  gtk_window_set_child (GTK_WINDOW (window), button);
  gtk_widget_show (window);
  while (!done)
    g_main_context_iteration (NULL, TRUE);
  return 0;
}

The most important difference is the fact that gtk_main() is not called at the end of the C main() function, but g_main_context_iteration() is called in a loop. The user has to provide a way to terminate that loop to exit the program. Above program does that by calling an additional function called quit_cb(), that is called when the top level window is going to be destroyed (user clicks on the x symbol of the main window) and that sets the done variable of the C main() function to the value true. The function g_main_context_iteration() has two parameters, a GMainContext for which we pass NULL to get the default one and a boolean value which determines if that function may block or not. In the quit_cb() callback the function g_main_context_wakeup() is called. That functions also has a parameter named context of type GMainContext — here NULL is again passed to use the default one. The function g_main_context_wakeup() ensures that context is not blocking in the g_main_context_iteration function.

Other less important differences are that gtk_init() and gtk_window_new() do not have function parameters in GTK4, that gtk_window_set_child() is used instead of gtk_container_add() to set the child widget of the top level window, and that gtk_widget_show() is used instead of gtk_widget_show_all() to make the widgets visible.

Now let us create a Nim version of the C code above: We may use the tool c2nim to generate a nimified version of the C source code, and tune it a bit manually resulting in this program:

simple.nim
##  https://gitlab.gnome.org/GNOME/gtk/-/blob/master/tests/simple.c
##  nim c simple.nim

import gintro/[gtk4, glib, gobject]

proc hello(b: Button) =
  echo "hello world"

proc quit_cb(window: Window; done: ref bool) =
  done[] = true
  wakeup(defaultMainContext())

proc main =
  var done = new bool
  gtk4.init()
  let window = newWindow()
  window.title = "hello world"
  window.resizable = false
  window.connect("destroy", quit_cb, done)
  let button = newButton()
  button.label = "hello world"
  button.marginTop = 10
  button.marginBottom = 10
  button.marginStart = 10
  button.marginEnd = 10
  button.connect("clicked", hello)
  window.setChild(button)
  window.show
  while not done[]:
    discard iteration(defaultMainContext(), mayBlock = true)

main()

The program structure follows closely the C program, there is no need to press the code in classes. The first two lines are only comments. It follows an import statement, we import the modules gtk4, glib and gobject unqualified into the global name space, as common for Nim.[1] We have decided to call the function that contains the largest code part main(), but that name can be freely selected in Nim. And we have to call that function explicitly, there is no function that is called automatically in Nim. Most statements in the Nim program directly corresponds to the statements in the C code. We use method call syntax for most function calls as common in Nim, that is instead of setChild(window, button) we write window.setChild(button). That may look like OOP style, but it is at the end just a syntax variant. The gintro module uses generally short unqualified function names, that is newWindow() instead of gtk_window_new(). We could use a module qualifier like gtk4.newWindow(), but that is only necessary if some of the imported modules export the same symbol (with same signature) so that name conflicts occur. The Nim compiler reports the rare name conflicts as errors, and we can add module prefixes in our Nim source code then. For the init() function of the gtk4 module we have decided to use a module prefix from the beginning — for functions without parameters and with very short trivial names the chance for name conflicts increase. And sometimes it is useful to indicate the origin of a function by use of a module qualifier. GTK widgets and the other gobject based types in GTK are objects that are dynamically created on the heap and accessed by pointers in C code. The gintro Nim bindings creates a Nim proxy object for each instance of these types. Nim constructors like newWindow() or newButton() creates a Nim proxy object on the heap and return its reference —  the proxy objects is automatically destroyed when it is not needed any longer by our Nim code and by GTK itself. The proxy object contains a pointer to the GTK object and some more fields for internal use. While the internal relationship between Nim’s proxy objects and GTK’s widgets and other gobject based types is not trivial, for the gintro user these types behave like ordinary Nim objects handled by Nim’s memory management system.

Opposite to GTK itself the gintro constructors do not always return a reference to a plain widget, but they return the actual ref type like Button or Window. For connecting GTK signals the type safe connect() macro call is used, which accepts an optional typed argument. Currently that optional argument can be a plain value like int or a reference to an arbitrary type, but var parameters are currently not supported. So we had to use a ref bool for the parameter of the quit_cb() callback function, as we want to modify the boolean value in the quit_cb() callback and access the modified value in the main() procedure. We have to de-reference the done variable by the dereference operator [] to access the content. The var parameter type should be needed only in very rare cases as the optional parameter of the connect macro — maybe gintro will support them later. The gintro connect macro is type safe, the data types of all parameters have to match with the data types used in the connected callback function. That is we have to pass a window or button parameter in the code above. The data type of the optional parameter has to match also of course. For most GTK signals the parameter list of the callbacks consists only of the object itself and optional one more parameter, but there exists some signals which have more parameters. One way to learn about these signals is to inspect the GTK C API. But we have to remember that the GTK widget family build a hierarchy, so we may have to look for the signals also in parent classes. For example when we inspect the GtkButton API we will find only two signals, clicked and activate: https://developer.gnome.org/gtk4/stable/GtkButton.html#GtkButton.signals. But as GtkButton is a child of GtkWidget we could also use signals from https://developer.gnome.org/gtk4/stable/GtkWidget.html#GtkWidget.signals for our button.

When we set properties or attributes we have generally various options, we can use function or method call syntax and we can assign the value using the equal sign. For the setter procedure we can generally use the short name without the set name component:

setTitle(window, "Hello")
title(window, "Hello")
window.setTitle("Hello")
window.title("Hello")
window.title = "Hello"

For setting some properties like the default size of widgets we can use also tuple assignment as in the last two lines of this code:

setDefaultSize(window, 200, 200) (1)
gtk.setDefaultSize(window, 200, 200) (2)
window.setDefaultSize(200, 200) (3)
window.setDefaultSize(width = 200, height = 200) (4)
window.defaultSize = (200, 200) (5)
window.defaultSize = (width: 200, height: 200) (6)
1 proc call syntax
2 optional qualified with module name prefix
3 method call syntax
4 named parameters
5 tupel assignment
6 tupel assignment with named members

The Nim program above looks a bit bloated still due to the 4 set margin calls, each with the same literal value 10. Well that program shape is a result of the initial C code, and often the 4 values may be not really all identical. But when such code fragments should occur often in our code then we would define our own setMargin() procedure that would get one parameter and assign all four values for us, and we may define another procedure with four parameters to assign all 4 margins, we could call it with button.setMartin(10) and button.setMargin(top = 5, bottom = 5, left = 20, right = 20). Note that Nim support default values for procedure parameters. The gintro package uses that fact for boolean properties which generally have the default value true, so we can use a plain window.setResizable instead of window.setResizable(true). To set that property to false we still have to use window.setResizable(false) or window.resizable = false.

Application Style

Now let us investigate the new application program style that was introduced with GTK 3 and is continued in GTK 4 nearly unchanged. We start with the GTK 4 variant of the example that is presented at the GTK homepage, its C code has this shape:

hello-world.c
// https://gitlab.gnome.org/GNOME/gtk/-/blob/master/examples/hello-world.c
// gcc -Wall hello-world.c -o hello-world `pkg-config --cflags --libs gtk4`
#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget, gpointer data)
{
  g_print ("Hello World\n");
}

static void
activate (GtkApplication *app, gpointer user_data)
{
  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *box;
  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 20, 20);
  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_window_set_child (GTK_WINDOW (window), box);
  button = gtk_button_new_with_label ("Hello World");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
  g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);
  gtk_box_append (GTK_BOX (box), button);
  gtk_widget_show (window);
}

int
main (int argc, char **argv)
{
  GtkApplication *app;
  int status;
  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return status;
}

The main difference of the new application program style to the old GTK 2 style is, that the C main() function now creates an application, connects the application to various callbacks and then calls g_application_run() to execute it. The most important callback is the activate callback that creates the application window with all its widgets and connects callback functions to the widgets.

We can compile and run above C program when we enter these commands in the terminal window:

gcc -Wall hello-world.c -o hello-world `pkg-config --cflags --libs gtk4`
./hello-world
hello world

The GTK3 variant of above program is nearly identical, instead of gtk_window_set_child(GTK_WINDOW(window), box) we would use the old gtk_container_add(GTK_CONTAINER(window), box) to set the box as content for the window, and to set the button as content of the box we would replace gtk_box_append(GTK_BOX(box), button) by gtk_container_add(GTK_CONTAINER(box), button). Another small difference is that GTK3 uses gtk_widget_destroy() instead of gtk_window_destroy() and gtk_widget_show_all() instead of gtk_widget_show().

After applying that modifications you could compile the program for GTK3 with

gcc -Wall hello-world-gtk3.c -o hello-world-gtk3 `pkg-config --cflags --libs gtk+-3.0`

Note that we do not have to call gtk_init() when we use the application style.

In the C main() function we create our application by calling the function gtk_application_new(). We pass a string which is used as an application id and some flag parameter. After we have connected the application variable to our activate callback function we run the application by calling g_application_run() of the gio library. The application then runs until the application window is closed or until we call gtk_window_destroy() on it. We can pass the command line arguments as parameters to g_application_run(). The function returns an integer value as status result, which is used as the return value of the main() function and passed to the operating system as the result of the program execution. In the C code g_object_unref(app) is called before the status value is returned to the OS and the program is terminated. Earlier we said that even in C code we generally do not have to free objects or resources, because most objects like widgets are initially unowned after creation, and when we add them to containers the container takes ownership. For top level windows or the GTK application that is not the case, so their constructors return a none floating object with reference count set to one, and we have to destroy() or unref() them.

In the activate() callback we call gtk_application_window_new(app) to create a top level application window, which is a subclass of a GTK window. In the activate() callback we create a box as a container for our button widget. Containers like boxes are used to arrange and group widgets. The GTK box constructor gtk_box_new() has two parameters, an orientation and a spacing value. The orientation determines if the contained widgets should be arranged vertically or horizontally. The spacing is an integer value which determines the distance between the contained widgets, the value is given in logical pixels. The box widget is then set as a child of the application window by calling the function gtk_window_set_child(). After that we create a button widget with a "Hello World" label text and connect that button to a callback function called print_hello() which shall print a message to the terminal window when we click with the mouse on that button. This program connects another callback function to our button in a very special fashion: We want that our application window is closed and the program terminates when we click on the button. For that we want to directly call the gtk_window_destroy() function on our application window as a callback function. The problem is, that when we connect a callback function to a button, then GTK would pass the button instance to the callback as first parameter. But we intent to call gtk_window_destroy() as callback with our application window as parameter. For this rarely used special case GTK offers a variant of g_signal_connect() which is called g_signal_connect_swapped() and which passes the optional user_data parameter to the callback. In this way we can pass the application window as user_data parameter directly to the gtk_window_destroy() function. In Nim this form of swapped parameter passing is currently not supported, so we have to define our own function, which gets the window as optional parameter and then calls destroy() on it. After we have connected all the callback functions to our button we call gtk_box_append() to insert the button widget into the box. Finally we call gtk_widget_show() on our application window to make it and all of its children visible and we are done.

We have created our application window, a box widget and a button widget. We inserted the box as child into the window, and we inserted the button widget into the box. Note that the order in which we build that hierarchy is not important, we can first insert the button into the box, or first insert the box into the window. Also note that we can connect multiple callback functions to the same widget. In this case the order is important, as the callback functions are called in the order as they were connected. For our button, if we had connected the print_hello() callback function last, that one would never get called, as the window would be destroyed before. Also note that we can connect different widgets to the same callback function, i.e. we could create multiple button widgets and connect them all to our print_hello() callback function.

Now let use see how the above program looks in the Nim programming language by using the gintro bindings. We applied the conversion tool c2nim on above C code and slightly edited the result manually:

c2nim -o hello_world.nim hello-world.c
hello_world.nim
##  https://gitlab.gnome.org/GNOME/gtk/-/blob/master/examples/hello-world.c
##  nim c helloWorld.nim

import gintro/[gtk4, gobject, gio]

proc destroyWindow(b: Button; w: gtk4.ApplicationWindow) =
  gtk4.destroy(w)

proc printHello(widget: Button) =
  echo("Hello World")

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  window.title = "Window"
  window.defaultSize = (20, 20)
  let box = newBox(Orientation.horizontal, 0)
  window.setChild( box)
  let button = newButton("Hello World")
  button.connect("clicked", printHello)
  button.connect("clicked", destroyWindow, window)
  box.append(button)
  window.show

proc main =
  let app = newApplication("org.gtk.example", {})
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The Nim source code fully matches the C code. We use in most cases method call syntax, and for window title and default size we use an assignment instead of a procedure call to set the properties. For the newApplication() call we explicitly specify the empty set for the flag parameter, but we could have leave that out as it is the default. In the C code gtk_application_new() passes plain integer flag values which can be combined by bit wise or operations, and G_APPLICATION_FLAGS_NONE is passed when no bit flag should be set. In Nim we use a bitset with a {} default for the empty set. Finally we used the quit procedure of system module to return the status result to the OS. The only small difference of the Nim code to the C code is that we do not use connectSwapped() but call an intermediate destroyWindow() procedure that obtains the application window as an additional parameter and calls destroy() on it to close the top level window and to terminate the program. Providing a type safe connectSwapped() procedure for the Nim bindings seems to be hard, and we would need it only in rare cases in real world code. Note that for the connect() macro the type of the optional parameter has to match exactly the data type used in the callback signature, that is while the body of the destroyWindow() procedure would work with a plain GTK window, which is a parent type of GTK application window, we have to use still GTK application window in the procedure signature, otherwise the compiler would complain about incompatible types. That is a limitation of current gintro bindings and results from the fact that the connect macro simple enforce type matching, it does not actually invest the actual types of the provided callback function and checks for type compatibility. If we have to use a plain GTK window type for the second parameter of the destroyWindow() callback for some reason, then we can make it work again with a type conversion like button.connect("clicked", destroyWindow, gtk4.Window(window)).

We can compile and run our Nim program with following commands from a terminal window:

nim c hello_world.nim
./hello_world

The above compiler invocation builds the executable in the default debug mode with a lot of runtime checks enabled and without enabled optimizations for the C compiler back end, so the executable size is large and the program would run not very fast. Generally we compile our Nim programs with the option -d:release to restrict checks to most important ones and to enable back end optimizations after we have tested our program well in debug mode. That results in a smaller and faster executable. We can further reduce the executable size by compiling our Nim program with the new Nim ARC memory management and by enabling link time optimization for the C compiler back end:

nim c -d:release --gc:arc -d:useMalloc --passC:-flto hello_world.nim

Here we additional use -d:useMalloc to use plain malloc() instead of Nim’s own memory allocation. That commands gives us an executable size of about 40 kByte with gcc 10 back end, which is still larger than the C executable, but not that much. We could disable all checks by specifying -d:danger instead of -d:release to further decrease the executable size. Note that with above options our program is compiled for optimal performance. If executable size is more important than performance then we could try other compiler options like --opt:size, but for GUI desktops applications that makes not much sense.

Nim API Docs

Unfortunately it is nearly impossible to provide a full set of commented API docs for the gintro Nim GTK bindings. The GTK related modules consists of more than 10000 functions and about 2000 data types, constants and enums. It is planed to list them all on some HTML pages, but that would provide only the symbol names and the signature for procedure. Copying the C comments verbatim would not make much sense, and rewriting all comments for Nim would be a gigantic effort. Generally the best solution for Nim is to follow the C API docs, which are generated by GTK directly from the GTK C source code. The C API docs are in most cases of good quality and not outdated, and the differences to the Nim API are generally obvious. For example if you are interested in using GTK buttons, you can enter "GtkButton", "GTKButton gtk4" or "GTKButton API" into the search field of an internet search engine and you should get the matching GTK API page like https://developer.gnome.org/gtk4/stable/GtkButton.html. You may also consider installing the GTK devhelp tool which provides the GTK C API without generating Internet traffic.

For stubborn cases it may be useful to use the Linux grep tool from the terminal window. Let us assume that you want to create a new button widget with a label and you know that for C https://developer.gnome.org/gtk4/stable/GtkButton.html#gtk-button-new-with-label is used for that. So maybe you tried from Nim let button = newButtonWithLabel("Run program") but the Nim compiler tells you that this function is not available. Well, the problem is obvious — Nim supports function overloading, so we have newButton(): Button and newButton(label: string): Button. But sometimes we are just too tired. We know the name of the C function, so let us use that as a starting point:

grep -C3 gtk_button_new_with_l ~/.nimble/pkgs/gintro-#head/gintro/*
...
proc gtk_button_new_with_label(label: cstring): ptr Button00 {.importc, libprag.}

proc newButton*(label: cstring): Button =
  let gobj = gtk_button_new_with_label(label)
  let qdata = g_object_get_qdata(gobj, Quark)
...

The gintro generated modules are generally located in ~/.nimble/pkgs/gintro-#head/gintro/ and contain clean and ordered code. Data types and methods working on these types are grouped together. Let us assume that you want to create a new GTK application but you are not sure which flags are available. Two grep calls should give us all what we need:

grep -C3 gtk_application_new ~/.nimble/pkgs/gintro-#head/gintro/gtk4.nim
...
proc gtk_application_new(applicationId: cstring; flags: gio.ApplicationFlags): ptr Application00 {.
    importc, libprag.}

proc newApplication*(applicationId: cstring = ""; flags: gio.ApplicationFlags = {}): Application =
  let gobj = gtk_application_new(safeStringToCString(applicationId), flags)
  let qdata = g_object_get_qdata(gobj, Quark)
  if qdata != nil:
...
grep -B12 "ApplicationFlags\*" ~/.nimble/pkgs/gintro-#head/gintro/gio.nim
type
  ApplicationFlag* {.size: sizeof(cint), pure.} = enum
    isService = 0
    isLauncher = 1
    handlesOpen = 2
    handlesCommandLine = 3
    sendEnvironment = 4
    nonUnique = 5
    canOverrideAppId = 6
    allowReplacement = 7
    replace = 8

  ApplicationFlags* {.size: sizeof(cint).} = set[ApplicationFlag]

For the second grep call we took advantage of the fact that the flags are exported, so an export marker must follow the name. We had to put quotes around the search string and to escape the asterisk.

GtkApplication and the Application Program Style

For GTK 3 and GTK 4 programs we generally use the application program style. In this style we use a small arbitrary named main procedure which creates our application by calling newApplication(), then connect the application to a set of callback procedure with application specific signals and finally calls run() to run the GTK main loop. All further program execution is now guided by GTK signals which causes execution of our callback functions. The GtkApplication class is a subclass of GApplication of module gio and supports signals like "startup", "activate", "open", "shutdown" and some more.

Understanding the GtkApplication class is maybe the most demanding task for new GTK programmers. Indeed it is not easy to understand the whole GtkApplication API, the API docs are extensive and information is distributed over many places:

Some beginner fear the application style and fall back to the old GTK 2 shape of programming with its gtk.init() and gtk.main() calls. But the application style offers a lot of benefits, that includes the new look with hamburger menus and the GTK menubar, the GActions which decouples user actions from concrete input sources like keyboard or mouse, and the automatic handling of program parameters and arguments and handling of single or multiple windows or program instances.

For the beginning you can ignore most of the signals of the GTKApplication class and connect your activate() procedure only to the activate signal of the GtkApliclation class as we did in our previous examples. Later you can add more signals and distribute your whole startup code on multiple callback procedure.

The most important GtkApplication signals are:

startup

set up and initialize the application

activate

program launch without file arguments, so open a default initial window

open

launch with file arguments, display file content

shutdown

do cleanup work, closing files or saving documents

When our application program starts, then the startup signal is emitted. We can connect a startup callback procedure to this signal that can perform some initialization tasks that are not directly related to showing a new window. When our program is invoked without file parameters then the activate signal is emitted next, and our activate callback procedure may open an empty window for the user. For the case that the user passes some file parameters, the open signal is emitted instead of the activate signal, and we have to open the specified files. Generally GTK applications uses only a single program instance. If the user attempts to start a second instance of a single-instance application then GtkApplication will send signals to the already running first instance and we will receive additional activate or open signals. In this case, the second instance will exit immediately, without calling startup or shutdown. Our application programs generally terminates when we close all open windows, but we can use the function g_application_hold() to prevent terminating of our program. When our program finally terminates, we get the shutdown signal, and our connected shutdown callback function can do some cleanup work or maybe save all open files.

Primary and Remote Instances

One important decision we have to make when we write a program is how the program should behave when we start it with and without arguments and when we start it multiple times. The most basic solution would be to open a separate window for each passed file argument, and to open more distinct windows when the program is started multiple times. But that is not always what the user may expect: For a text editor or image processing program the user may desire only one large window which is divided into multiple areas for each passed file, or maybe some sort of stacked display. And when a new program instance is launched, then the user may expect that the provided file arguments are passed to the already running program instance. The GtkApplication class can handle all this for us.

When we start our application then the first program instance is called the primary instance. When we launch the program again, than that program instance is called a remote instance. GTK uses the term local instance to refer to the current process, which can be the primary instance or a remote one.

Signals are always emitted in the primary instance only. For remote instances messages are send to the primary instance and signals are then emitted in the primary instance.

Dealing with the Command Line

Normally, GtkApplication programs will assume that arguments passed on the command line are files to be opened. In the case that files were given, our GtkApplication program will receive these files in the form of GFile objects from the open signal. If no arguments are passed, then the activate signal is emitted and the activate callback procedure may open its main window with an empty document.

The GtkApplication class supports also more advanced command line handling like the processing of --help, --version and other program options. We will not discuss these advanced options here, you may consult the API documentation for details:

Minimal Application Example

The following code example is the skeleton of a text editor program. We use the signals startup, activate, open and shutdown. We also define callback procedures for some of the other signals available for the GtkApplication class to show their shape, but they are not really active. Our program shall open an empty text window when launched with no argument, and open a text file when a file argument is available. When we call the program again with a file argument, then the existing text window is reused for the new text file. As GTK 4 may not yet support the GtkSourceView widget, we have used a plain GtkTextView for displaying the text. That widget is embedded in a GtkScrolledWindow to provide scrollbars and scrolling functionality. With some minimal changes you can use the code below for GTK 3 also: Replace setChild() with add() calls, and show() with showAll(). For GTK 3 you can also replace the TextView widget type with SourceView and then use the advanced functionality of the gtksource module to support stuff like syntax highlighting for program files.

As before our main() procedure creates the application, connects the callback procedures to signals and runs the application program. As we want to support the open signal, we have to pass the command line parameters to the run() procedure. As Nim does not give us direct access to the command line argument string array, we have to construct it by querying paramStr() for each argument. Note that we pass the flag ApplicationFlag.handlesOpen to the newApplication() call to tell GTK that it should not ignore file arguments. To keep the example short we made the activate procedure dumb. It creates a textview, a scrolled widget and the main window and inserts the widgets into each other. A smarter activate() procedure should try to detect an already existing window of an already running primary program instance as it does the open() callback. The open() callback procedure uses app.getActiveWindow() to check if a primary instance of our program is already running and reuses that window if possible. Otherwise it creates new widgets in the same way as the activate() procedure does. Then it calls loadContents() to load the textual content from the provided GFile into a string, and sets that text as buffer content of the textview widget.

Note that this is only a minimal skeleton. For a real text editor program we would have to do much more checks, and we may want to handle multiple file arguments. We will learn in later sections of this book how we can do that and which widgets support the display of multiple texts.

textview.nim
# nim c textview.nim
# ./textview textview.nim
# minimal GtkApplication example
import gintro/[gtk4, gobject, glib, gio] # , gtksource] # gtksource is not yet available for GTK4

from OS import paramCount, paramStr

proc shutdown(app: Application) =
  echo "shutdown"

proc startup(app: Application) =
  echo "startup"

proc handleLocalOptions(app: Application; vd: VariantDict): int =
  echo "handle-local-options"

proc nameLost(app: Application): bool =
  echo "name-lost"

proc open(app: Application; files: seq[GFile]; hint: string) =
  var
    contents: string
    etagOut: string
    length: uint64
    buffer: TextBuffer
    window: gtk4.Window
    view: gtk4.TextView
  echo "open"
  for f in files:
    echo f.uri
  window = app.getActiveWindow
  if window != nil: # instead of opening a new window reuse existing one
    let h = ScrolledWindow(window.getChild)
    view = TextView(h.getChild)
  else:
    window = newApplicationWindow(app)
    window.title = "Text View"
    window.defaultSize = (800, 600)
    let scrolledWindow = newScrolledWindow()
    view = newTextView() # gtksource.newView()
    window.setChild(scrolledWindow) # add() for GTK3
    scrolledWindow.setChild(view) # add() for GTK3
  if files.len > 0:
    if loadContents(files[0], cancellable = nil, contents, length, etagOut):
      assert length.int == contents.len
      echo "hint: ", hint
      echo "etag: ", etagOut
      buffer = view.getBuffer
      buffer.setText(contents, contents.len)

  show(window) # showAll() for GTK3

proc commandLine(app: Application; cl: ApplicationCommandLine): int =
  echo "command-line"

proc activate(app: Application) =
  echo "activate"
  let window = newApplicationWindow(app)
  window.title = "Empty Text View"
  window.defaultSize = (800, 600)
  let scrolledWindow = newScrolledWindow()
  let view = newTextView() # gtksource.newView()
  window.setChild(scrolledWindow) # add() for GTK3
  scrolledWindow.setChild(view)
  show(window) # showAll() for GTK3

proc main =
  let app = newApplication("org.gtk.example", {ApplicationFlag.handlesOpen})#, handlesCommandLine})
  app.connect("startup", startup)
  app.connect("activate", activate)
  app.connect("command-line", commandLine)
  # app.connect("handle_local_options", handleLocalOptions)
  app.connect("open", open)
  app.connect("name-lost", nameLost)
  app.connect("shutdown", shutdown)
  let argLen = paramCount() + 1
  var argStr = newSeq[string](argLen)
  for i in 0 ..< argLen:
    argStr[i] = paramStr(i)
  discard run(app, argLen, argStr) # we have to pass an argString to support open signal handling files

main()

You can launch that program with or without a file argument, and launch it again with a different file argument to replace the text shown in the textview widget.

nim c textview.nim
./textview &
./textview textview.nim
./textview anothertext.txt

We do not provide a picture for this program as it is not very interesting, it is only a window with some textual content and some optional scrollbars at the right and at the bottom of the window.

Basic Widgets

In this chapter we will present some simple widgets that are useful and easy to understand and to use. We have already used the toplevel widgets GtkWindow and GtkApplicationWindow that build generally the outer rectangular container for our whole graphical user interface. Windows normally have a title and decorations that are under the control of the windowing system and allow the user to manipulate the window (resize it, move it, close it,…​). In GTK 3 and GTK 4 windows can have only one single child, but this child can be a container widget which can hold many widgets including more container widgets. So all the widgets are arranged in a hierarchical fashion starting at the toplevel window widget.

GtkBox

Let us assume that we want to create some sort of buying app, that in its simplest form may contain a text entry field where we can type in what we want to buy, and a button to order that article. And we may want to have a textual label beside our text entry field. So a sketch of our widget arrangement may look like this:

 label entry

   button

The label and the text entry should be arranged horizontally beside each other, and centered below these two widgets there should be the buy button. GTK offers various container widgets to create such a layout. We will start with the GtkBox container which can arrange widgets horizontally beside each other, or vertically below each other. For the label and the entry we create a horizontal box and insert these widgets in that box. Then we create another vertically box in which we first insert the first box, and then the button. And we are done.

  -----------------
 |                 |
 |  -------------  |
 | | label entry | |
 |  -------------  |
 |                 |
 |      button     |
  -----------------
basicWidgets1
basicWidgets.nim
##  nim c --gc:arc basicWidgets1.nim

import gintro/[gtk4, gobject, gio]
import std/with

proc buttonCB(button: Button; entry: Entry) =
  let input = entry.text
  if input.len == 0:
    echo "Ordered a big bag of nothing!"
  else:
    echo "Ordered some ", input
    entry.setText("") # clear entry for new input
    discard entry.grabFocus # let keyboard input go again to this entry widget

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let vbox = newBox(Orientation.vertical, 25) # outer box
  let hbox = newBox(Orientation.horizontal, 25) # inner box above button
  let label = newLabel("Food:")
  let entry = newEntry()
  entry.widthChars = 32 # widthChars function is from GtkEditable interface
  let button = newButton("Buy it now!")
  hbox.append(label)
  hbox.append(entry)
  vbox.append(hbox)
  vbox.append(button)
  button.connect("clicked", buttonCB, entry)
  with vbox:
    setMarginStart(25)
    setMarginEnd(25)
    marginTop = 10 # with a recent Nim compiler assignment inside with block works also
    marginBottom = 10
  with window:
    setChild(vbox)
    title = "Mississippi App"
    defaultSize = (400, 100)
    # show # works
  window.show # but this is more clear

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The basic shape of above program is again similar to our first hello_world.nim example: We have a main() procedure that creates our application, connects it to the activate callback procedure and finally runs the app. The activate callback creates all of our widgets and inserts them in a hierarchical way into the container widgets. The button widget is connected to a callback procedure that gets the entry widget as an additional parameter, so that this procedure can access our textual input by calling getText(entry), which is equivalent to entry.text with method call syntax and without the optional get prefix for the procedure name. In the code above we use the new "with" macro introduced in Nim version 1.2 which saves us from typing the widget names many times.

The box containers are created with a call of newBox() which needs an Orientation enum parameter and an integer parameter specifying the spacing between the widgets in the container in logical pixels. We insert our child widgets into the GtkBox container using the append() procedure. We could have also used prepend(). To learn more about the GtkBox class you may visit

or invoke the devhelp tool.

The GtkLabel is a plain mostly passive widget which is used to display some textual descriptions. It offers many functions to modify its appearance or to change the textual content, for more info you may consult

The GtkEntry widget is used for entering single lines of text. GtkEntry offers a large set of functions and properties to modify its appearance. We can set the maximum number of characters, make the text invisible for password queries or set the alignment of the text when the text is smaller than the widget size. The widgets allows simple editing with keys like left, right, backspace, you can click on individual characters with the mouse to modify the insert position, or you can use the default popup menu when you press the right mouse button when the mouse pointer hovers above that widget. You can also connect to the "activate" signal of the GtkWidget to activate a callback procedure when the user presses the enter key to confirm his textual input.

For more information see

One special property of the GtkEntry widget is the fact that it implements the GtkEditable interface, see

So all the functions of GtkEditable can be used on GtkEntry widgets as well. We use in our example above the function setWidthChars() in the form entry.widthChars = 32 to give it the right size to show up to 32 characters — you can type in longer text, it scrolls.

Don’t forget that all these widgets are children of the parent GtkWidget class, so you can use all the GtkWidget functions also. We use grabFocus() in the buttonCB() procedure to let keyboard input go continuously to this widget, so that the user has not to click with the mouse pointer into the entry widget before it accepts keyboard input again.

GtkGrid

grid

The GtkGrid is a container widget that is used to arrange child widgets in a rectangular shape like a table or a matrix. In GTK 3 a similar container called GtkTable was available, but GtkTable is now deprecated. We create a new grid widget with the newGrid() constructor and we insert arbitrary other widgets by using the attach() procedure. As parameters of attach() we pass the child widget, the column and row coordinate where we want to insert the child, and optional a width and height if that child should span more than one single cell. The GtkGrid accepts also negative position coordinates, what is useful when we have already created a grid with coordinates starting at zero and then want to add a header label at the top or other widgets at the left. We have not to modify our existing code, we can just use negative coordinates for our forgotten stuff. GtkGrid offers some more functions, for example to set the spacing between children or to remove attached widgets again, see

The following example creates a plain employees status table. We use GtkCheckButtons as child widgets, that are widgets which uses a visible check mark to indicate a boolean state. In the example we use a label widget spanning all columns to display a headline, and at the left a label widget for each employee to display the name. We connect each CheckButton widget to a toggled() callback procedure using the "toggled" signal. The GtkCheckButton is a child of the GtkToggleButton which provides the "toggled" signal. We use two distinct callback functions for this signal so that we can differentiate between vacation and retirement status. But still we need the name of the employee in the callback procedure to display the new status. We have different ways to enable this, we could sub-type our CheckButton class to store additional information or we could pass an optional parameter when we connect to the toggled callback. We will explain sub-typing in later sections when we have to store addition information in our widgets. For now we can also use the fact that we can give widgets names using the setName() function. So we can just attach the name of the employee direct to the widget. To make the code below not too verbose we have not cared much about the visual appearance. For a real application we would care more for alignment, justification and separation of the various widgets and maybe style some labels using CSS or pango text attributes. We will learn how to do that in later sections.

grid.nim
##  nim c --gc:arc grid.nim
import gintro/[gtk4, gobject, gio]
import strutils

proc toggledVacCB(b: CheckButton) =
  echo "Vacation state: ", b.name, if b.active: " Yes" else: " No"

proc toggledRetCB(b: CheckButton) =
  echo "Retirement state: ", b.name, if b.active: " Yes" else: " No"

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let grid = newGrid()
  let head = newLabel("Available Devs")
  let name = newLabel("Name")
  let vacation = newlabel("Vacation")
  let retired = newLabel("Retired")
  window.defaultSize = (40, 60)
  grid.columnSpacing = 25
  grid.attach(head, column = 0, row = -2, width = 3, height = 1)
  grid.attach(name, 0, -1)
  grid.attach(vacation, 1, -1)
  grid.attach(retired, 2, -1)
  for i, p in pairs("araq mratsim bassi clasen".split):
    let lab = newLabel(p)
    let vac = gtk4.newCheckButton("Vac.")
    vac.setName(p)
    vac.connect("toggled", toggledVacCB)
    let ret = gtk4.newCheckButton("Ret.")
    ret.setName(p)
    ret.connect("toggled", toggledRetCB)
    grid.attach(lab, column = 0, row = i)
    grid.attach(vac, column = 1, row = i)
    grid.attach(ret, column = 2, row = i)
  window.setChild(grid)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The main() procedure is again identical to the ones in our former examples. In the activate procedure we create the window, the grid and some labels and a few CheckButtons. We use the overloaded function of newCheckButton() which accepts a string which is displayed on the right of the check box. The C name for that function is gtk_check_button_new_with_label(). We attach the head label at column 0 and row -2 at the top of our grid and let it extend over 3 columns by specifying width = 3. Next we set column headers for all 3 columns by attaching labels. It follows a loop where we iterate over all our employees, create a label widget with the name of the employee and two status widgets for vacation and retirement state and attach them to the grid. Finally we set the grid as child of our window and show() the window with all its child widgets. We have connected our ToggleButton widgets to two distinct callbacks for vacation and retirement state. When we click with the mouse on a check box to toggle the current state, then our callback functions print the new state to the terminal window. The callback retrieves the name of the employee from the widget by calling getName() on the widget and the new state by calling getAcctive() — we used method call syntax and left out the get prefix here. In the code above we set the default window size to a really small value, so the window extents automatically to the required size to contain the grid with all its child widgets. This ensures that the toplevel window has no unused void areas. And we use setColumnSpacing() to separate the children of the grid horizontally. Note that we use named parameters for the first attach() call when we attach the head widget. For the later attach() calls we use positional arguments and use the default 1 for width and height value. For more info about the GtkCheckButton see

GAction

Before we continue with more widgets we will introduce you to the concept of actions.

In the previous example programs we connected widgets directly to our callback functions using the connect() macro call. This is easy but not very flexible. Maybe we want the user to call the same callback function also from a popdown menu item or from a keyboard shortcut?

The concept of actions avoids a tight coupling of functionality to actual GUI elements. Actions are a way to tell the GTK toolkit about a piece of functionality in our program and to give it a name. We can map that actions to GUI elements like widgets, popup menu items or keyboard key sequences to give the user access to that functionality. The connection to the GUI elements can occur directly in our program code, or we may do the connections through XML files.

The GTK 3 library had an own action type called GtkAction, which is deprecated since version 3.10 and should not be used any more. Instead we use the GAction class which is provided by the GIO library and which is used for GTK 3 and GTK 4. GAction is generally used together with the GtkApplication class which we introduced earlier.

Indeed GAction is merely the interface to the concept of an action. Various implementations of GActions exist, including GSimpleAction which we will use in the following examples. Another important implementation of GAction is GPropertyAction which can be used to control properties of GObjects.

An action has four pieces of information associated with it:

  • a name as an identifier (usually all-lowercase, untranslated English string)

  • an enabled flag indicating if the action can be activated or not

  • an optional state value for stateful actions

  • an optional parameter type, used when activating the action

An action supports two operations:

  • activation, invoked with an optional parameter

  • state change request for stateful actions, invoked with a new requested state value

Most actions in our GTK {apps} will be stateless actions with no parameters. These actions can be represented by plain menu items without special decoration, like a "quit", "print" or "new document" menu item.

Stateful actions can have a plain boolean state like on/off or yes/no or a state with multiple possibilities like left/center/right for text justification in an editor.

Stateful actions with a boolean state are used when the actions should modify a state of the whole app or of a window like "display line numbers" in a text editor or "fullscreen" for a window. This type of actions is called a toggle action as it toggles the boolean state (true/false). Toggle actions use no parameters, the activation always toggles the state. In menu items the "true" state is represented by a visible check mark.

If the state of a stateful action can not be represented by a boolean state then an enumeration of the possible values is used as state indicator, typically as string like left/center/right for text justification. These actions are also called radio actions and are represented by radio buttons or radio menu items. These actions have a parameter type equal to their state type, and activating them with a particular parameter value changes the state to that value.

Actions can be bound or scoped to the whole app, or to single windows. For example the "fullscreen" action or "save" and "print" actions for windows containing a document impact only a single window, while actions like "about" or "preferences" impact the whole application. Actions scoped to single window instances allows each window to have its own state independently from the other window instances. We use the function addAction() with a window as first parameter to add an action to a window instance or with our GtkApplication as first parameter to add the action to the whole application.

To specify the scope when we map the action to widgets, menu items or keyboard keys, we have additional to prefix the action name with the prefix "win." for window bound actions and with "app." for actions bound to the whole app.

References:

A tiny GAction Example

The use of GAction seems to be complicated, and so some people still avoids it. But it is flexible and currently the best supported way to create interactions with the user, so we will use it in the rest of this book. We will start with a very simple application with only a single action (save) which we map to a button widget and at the same time to a key sequence (control shift s). That example is similar to a Python code listing from https://developer.gnome.org/GAction/. In the next section we will then create a larger app with an overlay menu based on a C example provided by the GTK developers (testgaction.c).

C code usually uses the function g_action_map_add_action_entries() with an array of GActionEntry structs as parameter to create the desired action like

 static GActionEntry app_entries[] =
{
  { "preferences", preferences_activated, NULL, NULL, NULL },
  { "quit", quit_activated, NULL, NULL, NULL }
};

static void
example_app_startup (GApplication *app)
{
  ...
  g_action_map_add_action_entries (G_ACTION_MAP (app),
                                   app_entries, G_N_ELEMENTS (app_entries),
                                   app);
  ...
}

This is comfortable but not really type safe and is not available in Nim. In Nim we use the function newSimpleAction() to create stateless actions and then use the connect() macro to connect that action to a callback function. The callback function receives the action and a variable of GVariant type as parameters, and can accept one more arbitrary optional parameter. The GVariant parameter would contain the actual state for stateful actions, for stateless actions it is generally ignored. After we have created the action we connect it with the addAction() function to the ActionMap of our GTK window. The GtkApplicationWindow provides an interface to GActionMap, but as the interface itself and the interface provider are defined in different modules (GIO vs GTK), we have to convert the ApplicationWindow to ActionMap with a call of actionMap(window) before we can add the action. Finally we call the function setActionName() to map the save action to our MenuButton and setAccelsForAction() to map the save action also to a keyboard key sequence. We prefix the action name with "win." to indicate that the action is bound to the current active window.

gaction0.nim
# nim c --gc:arc gaction0.nim
import gintro/[gtk4, glib, gobject, gio]

proc saveCb(action: SimpleAction; v: Variant) =
  echo "saveCb"

proc appActivate(app: Application) =
  let window = newApplicationWindow(app)
  let action = newSimpleAction("save")
  discard action.connect("activate", saveCB)
  window.actionMap.addAction(action)
  let button = newButton()
  button.label = "Save"
  window.setChild(button)
  button.setActionName("win.save")
  setAccelsForAction(app, "win.save", "<Control><Shift>S")
  show(window)

proc main =
  let app = newApplication("org.gtk.example")
  connect(app, "activate", appActivate)
  discard run(app)

main()

Our save callback function contains only an echo statement which writes a message to the terminal window when the action is activated. In a real application that function would save the content of the currently active window.

In the example code above we used actions bound to single window instances. We added our action to the action map of our window and we used the prefix "win." when we mapped the action to a button widget and to a keystroke sequence. We can easily modify the code to bind the action to the whole app: We call the addAction() function on the GtkApplication instance and use the "app." prefix for the action name when we map it to the button widget and to the key sequence:

app.addAction(action)
...
  button.setActionName("app.save")
  setAccelsForAction(app, "app.save", "<Control><Shift>S")

For stateless actions it does not really matter if we use actions scoped to single window instances or to the whole app, but for stateful actions it can make a difference: Only actions bound to window instances can have checkmarks or radio buttons which differ for each window. Note that when we create the action with the newSimpleAction() call we use the action name without a prefix, but for the setActionName() call as well as for the setAccelsForAction() call a prefix is necessary and it has to exactly match the action scope: We select a global scope by calling addAction() on the app instance and have to use "app." prefixes then. Or we call addAction() on a window instance and have to use "win." prefix. If the prefix would not match the action scope or if we use no prefix at all, then keyboard shortcuts would not work and buttons or menu items would be displayed greyed out and would not work also.

GAction and GMenu

Our next example program creates a popup menu bound to a menu button. We map a set of actions to the items of our menu. This includes simple stateless actions, a toggle action with boolean state, and a stateful action with three states numbered 1, 2 and 3. The menu items displays a checkmark for the toggle action when enabled and the stateful action with the three states is displayed with corresponding radio menu items. The example is based on a C language GAction example called testgaction.c found in the GTK4 tests directory.

gaction
gaction.nim
import gintro/[gtk4, glib, gobject, gio]

const menuData = """
  <interface>
    <menu id="menuModel">
      <section>
        <item>
          <attribute name="label">Normal Menu Item</attribute>
          <attribute name="action">win.normal-menu-item</attribute>
        </item>
        <submenu>
          <attribute name="label">Submenu</attribute>
          <item>
            <attribute name="label">Submenu Item</attribute>
            <attribute name="action">win.submenu-item</attribute>
          </item>
        </submenu>
        <item>
          <attribute name="label">Toggle Menu Item</attribute>
          <attribute name="action">win.toggle-menu-item</attribute>
        </item>
      </section>
      <section>
        <item>
          <attribute name="label">Radio 1</attribute>
          <attribute name="action">win.radio</attribute>
          <attribute name="target">1</attribute>
        </item>
        <item>
          <attribute name="label">Radio 2</attribute>
          <attribute name="action">win.radio</attribute>
          <attribute name="target">2</attribute>
        </item>
        <item>
          <attribute name="label">Radio 3</attribute>
          <attribute name="action">win.radio</attribute>
          <attribute name="target">3</attribute>
        </item>
      </section>
    </menu>
  </interface>"""

proc changeLabelButton(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  label.setLabel("Text set from button")

proc normalMenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  label.setLabel("Text set from normal menu item")

proc toggleMenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  let newState = newVariantBoolean(not action.getState.getBoolean)
  action.changeState(newState)
  label.setLabel("Text set from toggle menu item. Toggle state: " & $newState.getBoolean)

proc submenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  label.setlabel("Text set from submenu item")

proc radio(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  var l: uint64
  let newState: glib.Variant = newVariantString(parameter.getString(l))
  action.changeState(parameter)
  let str: string = "From Radio menu item " & getString(newState, l)
  label.setLabel(str)

proc activate(app: gtk4.Application) =
  let
    window = newApplicationWindow(app)
    box = newBox(gtk4.Orientation.vertical, 12)
    menubutton = newMenuButton()
    button1 = newButton("Change Label Text")
    actionGroup: gio.SimpleActionGroup = newSimpleActionGroup()
    label: Label = newLabel("Initial Text")

  var action: SimpleAction
  action = newSimpleAction("change-label-button")
  discard action.connect("activate", changeLabelButton, label)
  actionGroup.addAction(action)

  action = newSimpleAction("normal-menu-item")
  discard action.connect("activate", normalMenuItem, label)
  actionGroup.addAction(action)

  var v = newVariantBoolean(true)
  action = newSimpleActionStateful("toggle-menu-item", nil, v)
  discard action.connect("activate", toggleMenuItem, label)
  actionGroup.addAction(action)

  action = newSimpleAction("submenu-item")
  discard action.connect("activate", submenuItem, label)
  actionGroup.addAction(action)

  v = newVariantString("1")
  let vt = newVariantType("s")
  action = newSimpleActionStateful("radio", vt, v)
  discard action.connect("activate", radio, label)
  actionGroup.addAction(action)
  window.insertActionGroup("win", actionGroup)

  label.setMarginTop(12)
  label.setMarginBottom(12)
  box.append(label)
  menuButton.setHalign(gtk4.Align.center)

  var builder = newBuilderFromString(menuData)
  var menuModel: gio.MenuModel = builder.getMenuModel("menuModel")
  var menu = newPopoverMenuFromModel(menuModel)
  menuButton.setPopover(menu)
  box.append(menubutton)
  button1.setHalign(gtk4.Align.center)
  button1.setActionName("win.change-label-button")
  box.append(button1)
  window.setChild(box)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

When you run this example program you will get a window with a label displaying a textual message, a plain button widget which updates the label message when you click the button, and a menu button which displays a popup menu when you click it. Each menu item allows you also to update the label message.

For this example we use a XML string constant to construct our menu. The GTK developers generally recommends use of XML files for the description of the GUI layout whenever possible. We noticed already earlier in this book that the advantages of XML files are not always so obvious when using high level languages like Nim. But for menu construction XML files are indeed helpful, and as the C code on which this example is based also use XML for the menu we do the same. In the next section we then construct the same menu without XML directly with elementary GTK function calls. One disadvantage of XML files is that they are plain multi line text strings, so the C or Nim compiler can not validate them in advance. We have to run the code to see if all is correct, or maybe use other validating tools for the XML. Or we may try to create the XML string with tools like Glade from the beginning. For now we just take the XML menu string directly from the provided testgaction.c example program. That string has the well known shape of ordinary XML files. For processing and accessing XML GUI definitions GTK provides the GtkBuilder library. We call newBuilderFromString() with our XML string as argument to open the XML file, and them builder.getMenuModel() to access the whole menu construct. As argument of getMenuModel() we pass an id string which we had defined inside our XML string constant as <menu id="menuModel">. Finally we map that menuModel to a GTK menu button by a call of setPopover(). The XML menu definition is divided in multiple sections, which contains each one or more item definitions. Each item has the two attributes label and action. The attribute label specifies the string which is displayed as menu item text, and the attribute action is the name of a GAction that we define in our program. We prefix the action name with "win." to indicate that it is scoped to the current window. For the radio item entries we use an additional attribute called target that specifies the actual argument which is passed to the action callback function when the action is activated. And finally one item of our menu is enclosed in a submenu section to create a submenu.

In the activate() procedure we create all our desired actions and connect them to callback functions in a way similar as we did it in the previous example. One difference is, that we add the created actions not directly to the application or to the application window, but we create an ActionGroup by a call of newSimpleActionGroup() first, add all the actions to that group, and finally call the statement window.insertActionGroup("win", actionGroup) to add the group with all our actions to our application window. Grouping actions in this way can have some advantages, e.g. we can easily deactivate an action group or remove an action group again from a window or from the whole app.

Additional to some stateless simple actions which we create again with a call of newSimpleAction() this example uses actions with state. We use a call of newSimpleActionStateful("toggle-menu-item", nil, v) to create a toggle action with boolean state. We have to pass the initial boolean state to this call by a GVariant data type which we create with a call of newVariantBoolean(). The reason that we can not just pass the actual boolean value directly to that procedure but have to use a GVariant is, that C and Nim are both typed languages, which means that all three parameters of proc newSimpleActionStateful() must have a well defined data type, which is GVariant for the last parameter. A Variant is a special container type which has a well defined data type but can wrap other data types. Don’t confuse the GVariant which we use here with Nim’s own variant data type. Both have a similar purpose but are completely different. As Nim supports procedure overloading, we would indeed be free to define our own newSimpleActionStateful() procedure which accepts a plain bool as third parameter and the call the GTK proc with same name but with a variant type passed as last parameter. But this is not yet supported by current gintro bindings, we would have to write the necessary code manually.

For creating our radio actions some more code is necessary: We use again a call of newSimpleActionStateful() to create the radio actions. But for this type of stateful action we have to pass the initial state as well as the data type of the state parameter. For passing the parameter type we have to create a GVariantType variable with a call of newVariantType("s"). Here we pass the string "s" as parameter to indicate that we want a string variant type. You can find the strings which we have to pass for desired types at https://developer.gnome.org/glib/stable/glib-GVariantType.html. For a double type we would have to pass "d" for example. As last parameter we have to pass a GVariant again to specify the initial state. In this case we create a string variant with a call of newVariantString("1") with initial state "1".

The need of GVariant and GVariantType data types makes our code a bit verbose unfortunately, but the automatically created bindings of the gintro package support no simpler method currently. Maybe later versions will do, but that involves manually work which of course need documentation as well. Of course you can write your own helper procedures. Unfortunately that can make it harder for others to understand your code, when the reader knows GTK well but not your customization procs. So we leave that out for now.

At the end of this section we have to discuss the callback functions which we connect with our actions. For the stateless actions it is not very interesting. The parameter list of the callback functions contain a variable of GVariant type, but that variable has no content for stateless actions. For the callbacks connected to the stateful actions we have more work to do:

proc toggleMenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  let newState = newVariantBoolean(not action.getState.getBoolean)
  action.changeState(newState)
  label.setLabel("Text set from toggle menu item. Toggle state: " & $newState.getBoolean)

proc radio(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  var l: uint64
  let newState: glib.Variant = newVariantString(parameter.getString(l))
  action.changeState(parameter)
  let str: string = "From Radio menu item " & getString(newState, l)
  label.setLabel(str)

The most important point is, that we have to call action.changeState(newState) to set the new state. Without that call the state is not updated and the check mark and the radio buttons would not update their visual appearance. Unfortunately changeState() needs again a parameter of variant type. For the toggleMenuItem() proc the provided variant parameter is not used, as it is a plain boolean toggle action. So we extract the actual state from the action itself, invert the boolean state and create a new boolean variant of that state which we pass to the changeState() function. For the radio() callback it is similar, but the variant parameter contains already the actual new state, which we specified in the menu items in the XML string. We can pass that variant parameter directly to changeState() to change the visual appearance of the radio menu items, or we can extract the actual string from the variant parameter by calling getString() on it to extract the string. We use that string to update our label widget. You may wonder why the getString() procedure has an additional parameter of type uint64. In C this is an optional out parameter which is used to retrieve the actual string length.[2] In C for such out parameters a pointer to a storage location is passed where the library can store the value, and GTK allows passing NULL (nil) in case that the parameter should not be used. The gobject-introspection generated gintro bindings map such out parameters to Nim’s var parameter, which avoid ugly and dangerous use of pointers. But unfortunately the optionality of parameters is lost in the process of conversion to var type. There is an open issue about this topic in the gintro github issue tracker, but still there is no good solution. One suggestion was to convert procs with optional var parameters to functions which returns these data as function results, where the function result is a tuple in case of more than one optional var parameter or in case that there is already a non void function result in the C library. But doing that conversion fully automatically is not that easy, and the result may be confusing for the user. So we may create some overloaded functions manually when necessary.

For connecting the actions to our callback functions we used the "activate" signal which is provided by the GSimpleAction class. GSimpleAction provides also a "change-state" signal and we may be tempted to use that one instead for stateful actions. But that signal is a bit problematic as it may lead to infinite recursion when we call changeState() in our callback functions. As a final note we should mention that stateful actions can be also used when no callback is actually connected, as for this case GTK calls changeState() directly and we may query the actual state then from the action variable. But whenever we connect our own callback function, then we have to call changeState() our self to update the state.

References:

GSettings

The GSettings class provides a convenient way to permanently storing configuration data, and to bind them to properties of widgets or other gobject based data types. The configuration data are described in a XML file which is then converted into a binary database.

For using GSettings in our own programs, we have first to create a XML file which defines name and type of each configuration entry, and additional provides a default value and optionally a summary and a description. The file name of such XML files must always end with ".gschema.xml". The following example has only one field called like-nim of type boolean (b):

<schemalist>
  <schema path="/org/gnome/recipes/"
         id="org.gnome.Recipes">
    <key type="b" name="like-nim">
      <default>false</default>
      <summary>I like Nim</summary>
      <description>
        I like or like not
        the Nim programming language.
      </description>
    </key>
  </schema>
</schemalist>

To use such a configuration we have to install it properly on our computer. But before we describe that installation process in more detail let us see how we can use the gsettings configuration in our app. The code below creates a check button and binds its boolean state to the "like-nim" property of above configuration. When you run the app you can click on the check button to toggle its state. The new state is automatically stored in the gsettings configuration. If you terminate the app and launch it again the check button shows its previous state, even after a reboot of your computer.

gsettings.nim
# gsettings.nim -- basic use of gsettings
# nim c --gc:arc gsettings.nim
# https://blog.gtk.org/2017/05/01/first-steps-with-gsettings/
import gintro/[gtk4, gobject, gio]

# unused
#proc toggle(b: CheckButton) =
#  echo b.active
#  let s = newSettings("org.gnome.Recipes")
#  discard s.setBoolean("like-nim", b.active)

proc activate(app: Application) =
  let window = newApplicationWindow(app)
  window.title = "GSettings"
  window.defaultSize = (200, 200)
  let b = newCheckButton("I like Nim")
  b.halign = Align.center
  #b.connect("toggled", toggle) # we don't need this for plain binding!
  let s = newSettings("org.gnome.Recipes")
  if s.getBoolean("like-nim"):
    echo "I like Nim language"
  `bind`(s, "like-nim", b, "active", {SettingsBindFlag.set, get})
  window.setChild(b)
  show(window)

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

Above program has again the well know application style. In the activate() proc we first create our application window and a new check button. Then we load our gsetting with the newSettings() function to which we pass the id string "org.gnome.Recipes" which we specified in our XML file. We can query the boolean state of the "like-nim" property with a call of s.getBoolean(). Finally we bind that property to our check button using the bind() proc. As bind is a Nim keyword we have to enclose that proc names in backtics, which is sometimes also called stropping.[3] The bind proc requires five arguments — the settings instance, a settings property, a widget, a widget property and a set with the bind flags. For the settings propety we use the "like-nim" one, which is the only one which is already declared in our XML file. The widgets is our check button with its "active" property. Our check button is a subclass of a toggle button, and from https://developer.gnome.org/gtk4/stable/GtkToggleButton.html#GtkToggleButton.properties we know that it has this property which is mapped to its boolean state and the visibility of its check mark. Finally we set the set and get flags in the bind flags parameter to bind our check button bidirectional to that settings property. This simple bind call does only work due to the fact that the settings property as well as the check button property have the same data type, boolean in this case. If we want to bind properties with different data types, then we have to use converter procs, which you may find in the gsettings C API docs. If the plain binding of a gesettings property to a widget property is not sufficient for a more advanced use case, then you can connect your widget to ordinary signals like the "toggled" signal and a matching callback functions which can retieve and set the gsettings properties directly as shown in the commented out toggle() proc in above code example.

Now let us investigate how we can install the configuration file on our computer. At runtime, GSettings looks for configurations in the glib-2.0/schemas subdirectories of all directories specified in the XDG_DATA_DIRS environment variable. The usual location to install schema files is /usr/share/glib-2.0/schemas. But gsettings loads not the XML files directly, but a "compiled binary" called gschemas.compiled which is generated from all XML files in that directory. In principle we could copy our XML file into /usr/share/glib-2.0/schemas directory, cd into that directory and type "glib-compile-schemas ." to "compile" all the XML files including our own to the "gschemas.compiled" database. We would need root privileges for that. But that is a dangerous operation, if for some reason the resulting gschemas.compiled database is corrupted then most of our GTK/Gnome programs would not work any more. So we better keep our own database separate. For that there exists at least two ways, which we can do as ordinary user without root privileges.

Unfortunately it is not enough to put our XML file or the compiled version of that file just into the same directory where our executable is located, as gsettings does not load schema files from the current directory automatically. But we can tell gsettings to load additional schemas from a specific directory by setting the GSETTINGS_SCHEMA_DIR environment variable, which is generally empty by default. So one method for a fast test of our program is

cd
mkdir testdir
cp gsettings.nim testdir
cp test.gschema.xml testdir
glib-compile-schemas testdir
testdir/gsettings

We create a new directory, copy our nim source code and the XML file there and call glib-compile-schemas for the whole directory. Finally we can launch our application.

In a similar way we can create a directory which shall permanently store our own schema files, maybe named mySchemas. For that you have to make an entry like

export GSETTINGS_SCHEMA_DIR="~/mySchemas"

in one of your shell startup scripts like .bashrc or similar which are automatically executed at computer startup. Of course you have to execute "glib-compile-schemas" for that directory whenever you add more XML files.

Finally a similar method is to add one more custom path to the XDG_DATA_DIRS environment variable. But then we have to respect the fact that the actual path is the glib-2.0/schemas subdirectory of the entries.

So we can add a line like

XDG_DATA_DIRS=$XDG_DATA_DIRS:~/myGsettingsStore/

to ~/.bashrc or equivalent files and populate that directory by commands like

mkdir -p ~/myGsettingsStore/glib-2.0/schemas
cp test.gschema.xml ~/myGsettingsStore/glib-2.0/schemas
glib-compile-schemas ~/myGsettingsStore/glib-2.0/schemas

Unfortunately these explanations about storing the gsetting configurations are valid only for Linux systems and you have to know which startup script is used by your Linux distribution. For other operating systems you may have to consult internet search engines or the GTK/Gnome forum for more details.

References:

GTK Basic Widgets

In this chapter we will introduce simple but useful basic widgets and explain the widgets which we introduced earlier in more detail. All these widgets are really easy to use. We have constructor functions with names which starts with "new" like newButton(), we can add these widgets to containers, and connect them directly to signals or map them to GActions. You should also study the C API documentation of these simple widgets to learn about all the other functionality that is also available, like provided functions, available signals or widget properties. Remember that GTK widgets build a class hierarchy so functionality may be provided by parent classes. For example the GtkCheckButton is a subclass of GtkToggleButton and both are of course widgets, so you may have to consult all that API documentation to find functions or properties that you may desire. Also consider using the devhelp tool to navigate the C API docs. For the more advanced and complicated widgets like GtkTreeView, GtkListView and GtkDrawingArea we will give only minimal examples in this chapter to give you a basic feeling what they are and how they can be used. GtkTreeView and GtkListView are powerfull widgets to display larger textual and other data sets. And the GtkDrawingArea is a widget where we can draw arbitrary two dimensional graphics by using drawing functions of the cairo graphics library. We can use the GtkDrawingArea for pure display purpose, or we can combine it with user input activities to create advanced CAD tools. One more advanced GTK widget is the GtkGLArea which can be used to display three-dimensional OpenGL graphics. But as creating graphics with OpenGL is a wide area for which many whole books have been written, we will not try to cover that topic in this book.

The GTK widgets can be grouped into container widgets which arrange other widgets like GtkBox or GtkGrid, into mostly passive widgets for displaying data like the GtkLabel, and into the active widgets which accept user input like GtkEntry. For an overview you may visit the widget gallery page:

Label

The GtkLabel is a simple widget that can display some text. The text can have multiple lines when desired, can wrap automatically and can support style attributes like italic or boldface. The label widget is mostly used to label other widgets or to display some textual messages to the user. GtkButtons use a label widgets to display its text. For the actual text display the pango library is used, which supports many advanced text renderings like right to left text and many attributes like strike-though, underline, overline (since pango v 1.46) and many more. GtkLabels like all GTK widgets support utf8 unicode text which allows display of glyphs of exotic languages as well as a large range of symbols.

The GtkLabel is one of the simplest GTK widgets and the C API documentation should be easy to understand and can be used in Nim straight forward:

Properties

GTK uses the concept of properties to set or modify the internal state of GTK widgets or other entities. For most properties GTK offers setter and getter functions which we use when available, so we have not to deal with properties that often. But in some cases it is necessary, so we will introduce the basics at this point already. GTK properties have textual names like "wrap" or "gtk-application-prefer-dark-theme" and a typed state like boolean true/false, integer states or states of other types. In C code most often the function g_object_set() is used to set one or multiple properties. That function uses multiple untyped arguments. As it is not type safe it is not provided by gobject-introspection and is not available in most high level GTK bindings including Nim. Instead we use g_object_set_property() called just setProperty() to set single properties in Nim. Unfortunately that procedure needs a GValue as third parameter which makes it use a bit complicated.

The following example program defines a helper procedure called toBoolVal() to create a GValue with matching type and content from a Nim bool value. We use the resulting GValue to set the wrap property of our label and the "gtk-application-prefer-dark-theme" property of our whole application. Both properties expects a boolean GValue.

label
label.nim
import gintro/[gtk4, gobject, gio]

proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

proc activate(app: gtk4.Application) =
  let d = gtk4.getDefaultSettings()
  setProperty(d, "gtk-application-prefer-dark-theme", toBoolVal(true))
  let window = newApplicationWindow(app)
  window.title = "Window"
  window.defaultSize = (100, 20)
  let box = newBox(Orientation.vertical, 20)
  window.setChild( box)
  let label1 = newLabel("This text does not wrap")
  let label2 = newLabel("But this very long text can wrap automatically")
  #label2.setWrap(true)
  label2.setProperty("wrap", toBoolVal(true))
  box.append(label1)
  box.append(label2)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

In our toBoolVal() proc we have to create a GType variable first. For this we can use the typeFromName() proc to which we have to pass the correct GTK type name, or with recent gintro versions we could use getType() procs like gBooleanGetType() directly. Guessing the right name is not always easy — for string types we would have to use gStringGetType() or typeFromName("gchararray"). With that GType we can first init() a GValue variable and then set its value. GValues are used a lot in gtkListView and GtkThreeView widgets, we will learn more about GTypes the GValue variables later when we explain these widgets. In the above example program we use the function setProperty() two times. First we set the property "gtk-application-prefer-dark-theme" for our whole application to true. To do that we query the default settings with a call of getDefaultSettings() and then set that boolean value. The result is that our whole app uses a dark color scheme when available. The second use of setProperty(), again for a boolean variable, is to set the "wrap" property of one of our label widgets. For this the label class provides the function setWrap() which we generally would use. We used a GtkBox to arrange our two labels vertically in our window. We will learn more details about the GtkBox containers in the next section.

While GtkLabels are most of the time passive entities, they provide some signals like "activate-link" and "move-cursor". By default label text can not be copied to the clipboard, but we can call setSelectable() on a label widget to make it selectable. Then we can click with the mouse on the text, we can select all or part of the text and the label widget will get a popup menu which pops up when we press the right mouse button allowing to select and copy the label text to the clipboard. This can be useful when a label displays messages which we may want to copy elsewhere. One of the many useful functions offered by labels is setEllipsize() which determines how text not fitting into the available are is handled. That function is often used in conjunction with setMaxWidhChars() or setWidhChars(). Our second example program below displays some chess pieces using utf8 unicode glyphs, and an ordinary text which is displayed in a small font and which is shortened by showing ellipsis when the enclosing window is small. The label widget with the chess pieces is made selectable, so you can copy the content into another window, maybe into an instance of the the gedit text editor.

label2
label2.nim
import gintro/[gtk4, gobject, gio, pango]

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.vertical, 20)
  window.setChild( box)
  let label1 = newLabel()
  label1.setUseMarkup
  label1.setSelectable
  label1.setMarkup("<big>\u2654\u2655\u2656\u2657\u2658\u2659\u265A\u265B\u265C\u265D\u265E\u265F</big>")
  let label2 = newLabel("<small>Some less important message that nobody really reads</small>")
  label2.setUseMarkup
  label2.setEllipsize(pango.EllipsizeMode.end)
  box.append(label1)
  box.append(label2)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

In both label texts we use html tags like <big> or <small> to modify the text display. Currently we have to additional call setUseMarkup() on the label to tell it that html tags should be used. We may expect that for the case that we use setMarkup() that should be not necessary — maybe it is a bug of early GTK 4 release? Modifying the label display with enclosed tags directly is convenient, but restricted to only a few attributes. In the next section we will lean how we can use XML UI files to do more advanced text customization.

GtkLabels and XML UI files

Earlier in this book we used already XML files to describe the structure of popup menus. And we said at the beginning of the book that GTK allows to declare the whole GUI or parts of it with XML UI files which is then loaded by the GTK builder. Below you can see such an XML UI file which describes a label and two buttons, which are arranged into a GtkGrid widget.

builder
builder.ui
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <object id="window" class="GtkWindow">
  <!--
  <object id="window" class="GtkApplicationWindow">
  -->
    <property name="title">Grid</property>
    <child>
      <object id="grid" class="GtkGrid">
         <child>
          <object id="label" class="GtkLabel">
            <property name="label">Label with red background</property>
            <attributes>
              <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
              <attribute name="background" value="red" start="11" end="14"/>
            </attributes>
            <layout>
              <property name="column">0</property>
              <property name="row">-1</property>
              <property name="column-span">2</property>
            </layout>
          </object>
        </child>
        <child>
          <object id="button1" class="GtkButton">
            <property name="label">Button 1</property>
            <layout>
              <property name="column">0</property>
              <property name="row">0</property>
            </layout>
          </object>
        </child>
        <child>
          <object id="button2" class="GtkButton">
            <property name="label">Button 2</property>
            <layout>
              <property name="column">1</property>
              <property name="row">0</property>
            </layout>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

This UI file has the common shape of XML files. Contained Widgets have a class which determines the type of the widget and an id which allows access to that widget from our program file. The outermost entity is the GtkWindow, with a GtkGrid as child, which again has a label and two buttons as children. If using such XML files has more advantages than disadvantages is not always that clear, but the GTK developers generally recomment using such files instead of creating the whole GUI structure in program code. Indeed the XML UI file looks clean and simple most of the time, but creating valid files or debugging corrupted files is not that simple. We took our above file from the GTK4 source distribution and added only the label section which is again taken from the GtkLabel API docs. GTK offers the tool Glade which allows to create such XML UI files interactively, we may explain that tool at the end of this book. But you should find some tutorials or videos about the Glade tool in the internet. One advantage of XML UI files is that by modifying that file we can tune the GUI without recompiling the source code of the program. As least when the XML UI file is really shipped as a separate text file. But often it is shipped as a compressed binary resource file or it is included as string in the program source file. In the XML UI file above we can change the label text, its appearance or its presents at all, and our program should still work! And the XML file makes it easy to tune the visible appearance of our label widget by use of attributes like "weight" or "background". Now let use investigate the program source code which uses above XML UI description:

builder.nim
# https://developer.gnome.org/gtk4/unstable/ch01s05.html
# builder.nim -- application style example using builder/glade xml file for user interface
# nim c --gc:arc builder.nim
import gintro/[gtk4, gobject, gio]

proc hello(b: Button; msg: string) =
  echo "Hello", msg

proc activate(app: Application) =
  let builder = newBuilder()
  discard builder.addFromFile("builder.ui")
  #let window = builder.getApplicationWindow("window")
  let window = builder.getWindow("window")
  window.setApplication(app)
  let label = builder.getLabel("label") # not really necessary, out label is fully passive
  var button = builder.getButton("button1")
  button.connect("clicked", hello, "")
  button = builder.getButton("button2")
  button.connect("clicked", hello, " again...")
  show(window)

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

Our Program has again the well known app structure. In the activate proc we create a builder object by calling newBuilder() and then load the GUI structure by a call of addFromFile(). After that we can access the widgets by calls like getWindow(), getButton() or getLabel(). For the window we have to set the application with a call of setApplication(). In the above code we connect our widgets to our callback functions by use of the connect macro. In principle that connection could be done in the XML UI files as well, but the gintro bindings do not support that currently and probably never will. Note that we call show(window) at the end of the activate() proc as we did in all of our apps before. It would be possible to save that proc call when we add the <property name="visible">True</property> to the window class in the XML file, but GTK developers regard this as a bad practice.

Mnemonics in Label Text

Mnemonics can be used in label widgets as some form of keyboard shortcuts: We can put an underscore character '_' in the text of a label to indicate that the following character should be a keyboard shortcut to activate a widget. That can be useful if we have a GUI window with multiple buttons or multiple text entry fields and we want to control the GUI from the keyboard without use of the mouse device. So we just have to press the left "alt" key and a matching key to activate a button or to activate an entry field to accept keyboard input. Mnemonics are not used that much in GTK 4 as GAction provides a more flexible way to map actions to various user input, but as mnemonics are still available and really easy to use we will give a short example:

label3
label3.nim
import gintro/[gtk4, gobject, gio]

proc entryCb(e: Entry) =
  echo "Searching for: ", e.text

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.horizontal, 10)
  window.setChild(box)
  let label = newLabelWithMnemonic("_Find")
  label.setUseUnderline(true)
  let entry = newEntry()
  let dummy = newEntry()
  label.setMnemonicWidget(entry)
  entry.connect("activate", entryCb)
  box.append(label)
  box.append(entry)
  box.append(dummy)
  discard dummy.grabFocus
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The code above creates a label with Mnemonics support and two text entry fields. We would need only the search entry field, but as long as it is the only one it would always get user input, so we add one more dummy entry which can get keyboard focus. We create the label widget with a call of newLabelWithMnemonic("_Find"). The underscore in front of the 'F' character indicates that this character should be used as keyboard shortcut. The call of setUseUnderline(true) tells the label widget that keyboard shortcuts should be really used, and setMnemonicWidget() tells which other widget should be activated by the label. You may wonder why the call of setUseUnderline(true) is necessary when it is obvious from the use of newLabelWithMnemonic() that shortcuts should be used. But it is indeed necessary currently, maybe it is still a early GTK 4 bug. Often label widgets are contained in other widgets, i.e. in Button widgets. In that case the call of setMnemonicWidget() is not needed, the enclosing widget is activated in that case. To use the shortcut you press the left "alt" key and the letter "f", which will make the seach widget active accepting keyboard input. Maybe click with the mouse into the dummy widget before to see the effect better. Pressing left "alt" key underlines the action key in the label widget to give you a hint which key is in use. In our example we put the underscore in from of the first capital letter of our label text. But we can put it everywhere in the text string, and lower and upper case characters work the same. A call of newLabelWithMnemonic("Fi_nd") would make "alt" "n" the shortcut. Unfortunately these form of shortcut definitions can easily lead to conflicting characters when many many shortcuts are in use or when the program is translated into other languages.

Pango Text Attributes

At the end of this section we will investigate how we can modify our label text by use of Pango attributes. The following example program creates two Pango attributes, one to modify the background color of our label, and another one which display the label text as strike through. For both attributes we set the range where we want to apply these attributes. Attributes can overlap. Then we create an attribute list, add our two attributes to the list and finally call setAttributes() to apply the attribute list to our label.

label4
label4.nim
import gintro/[gtk4, gobject, gio, pango]

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let label = newLabel("Some text with red background and overlapping strikethrough")
  let attrColor = newAttrBackground(uint16.high, 0, 0) (1)
  let attrStr = newAttrStrikethrough(true) (2)
  attrColor.startIndex = ATTR_INDEX_FROM_TEXT_BEGINNING + 15 (3)
  attrColor.setEndIndex(29.uint32) (4)
  attrStr.indices = (24.uint32, 32.uint32) (5)
  let attrList = newAttrList() (6)
  attrList.insert(attrColor) (7)
  attrList.insert(attrStr)
  label.setAttributes(attrList) (8)
  window.setChild(label)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()
1 create a background color attribute using RGB color values with range 0 .. uint16.high
2 create a strike through attribute
3 set start index. Index starts at zero, we can use the constant ATTR_INDEX_FROM_TEXT_BEGINNING (0.uint32)
4 set end index, the constant ATTR_INDEX_TO_TEXT_END is an alias for uint32.high
5 we can also use a tuple assignment
6 create a new attribute list
7 insert our two attributes into the attribute list
8 apply the attribute list to our label

For setting the index ranges of our attributes we can use set functions, value assignment or tuple assignment. Setting the indices in the call of the attribute constructor directly as is used in Python GTK bindings is not yet supported by the gintro language bindings. We may declare such extended constructor manually if we should really need them often, but generally labels with many Pango text attributes are not used that often. Note that the indices uses the data type uint32, and that the indices count byte positions, not unicode glyphs.

Another option to modify the visual appearance of GTK labels and other widgets is the use of CSS (Cascading Style Sheets). We will learn more about CSS later in this book.

References:

Box

box

The GtkBox is a simple container widget which is used to arrange child widgets into a single row or column, depending upon the value of its “orientation” property.

We used box widgets already in the introducing sections of this book. In this section we will explain some properties and functions of the GtkBox widget in some more detail. The following example program creates a horizontal box, which will arrange its child widgets in a single row.

box.nim
import gintro/[gtk4, gobject, gio, pango]

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.horizontal, 20)
  #box.baseLinePosition = BaselinePosition.bottom
  #box.setHomogeneous
  window.setChild( box)
  let label1 = newLabel("<small>Available\nchess pieces</small>")
  label1.setYalign(1) # bottom
  label1.setUseMarkup
  let label2 = newLabel()
  label2.setMarkup("<big>\u2654\u2655\u2656\u2657\u2658\u2659\u265A\u265B\u265C\u265D\u265E\u265F</big>")
  label2.setYalign(1)
  #label2.setUseMarkup # set from setMarkup() call
  box.append(label1)
  box.append(label2)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

To create the container widget we call newBox(Orientation.horizontal, 20). The first parameter determines the orientation and the optional second parameter the empty space between its children in logical pixels. We add that box as child of our window by a call of setChild(). Finally we create two label widgets, append both to the box and call show() on our window to display all of our widgets. The first label has two rows of text (\n inserts a line break) and we apply the pango attribute <small> to decrease the text size. The second label has only one text line with unicode glyphs, and we apply the attribute <big> to make it appear larger.

The GtkBox class provides the functions append() and prepend() to add new child widgets at the end (right/bottom) or at the start (left/top) of the container. We can also use insertChildAfter() to insert a new widget after an already inserted one. Removing children from a box widgets or reordering of children is also possible by using the remove() or reorderChildAfter() functions. But these operations are not that common and we would need access to the already contained children for these operations. The widget parent class of the box widget provides various functions to access contained children, we will learn more about that later in this book. When we use insertChildAfter() to insert a widget, then we have to be sure that the "after" widget is really a child of that box, and when we use reorderChildAfter() then of course both child widgets have to be in the box. If that is not the case then a GTK runtime error will occur. An useful function of the GtkBox widget is setHomogeneous() which can be used to give all children in the box the same size extend. The function setSpacing() can be used to modify the pixel spacing for all the child widgets. Note that GTK uses logical pixels — for high resolution displays one logical pixel can correspond to multiple physical pixels depending on the user’s GUI settings. For horizontal boxes the GtkBox class provides the function setBaselinePosition(), which is not used that often and has only an effect when at least one of the widgets in the box has set its valign property to Align.baseline. For our example we do not use the function setBaselinePosition(), but we use the fact that label widgets by default use all available space in a box, and we call the function setYalign(1) for both labels to align the text to the bottom.

Aligning widgets in containers is a bit complicated. Often the default alignment is OK, but when not it can be difficult. First we have to decide if the children in the container should expand to take all available space or not. And then the GtkWidget class provides various functions and properties to modify the actual alignment. We will describe all the various options later in this book in a separate section.

As the children of GtkBox widgets can be again boxes, we can construct interesting two-dimensional layouts. At the beginning of the book we have already used the GtkGrid widget to arrange children in a tabular layout, and you may wonder if the GtkGrid can completely replace nested boxes. But that is not always possible, as the GtkGrid generates layouts that look like a regular matrix, with optionally joined cells. And sometimes using boxes is simpler or more flexible than using grid widgets. The following program generates a layout with three nested box widgets which would be not possible to archive with a single grid widget:

box2
box2.nim
import gintro/[gtk4, gobject, gio]

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let hbox1 = newBox(Orientation.horizontal, 25)
  let hbox2 = newBox(Orientation.horizontal, 25)
  let vbox = newBox(Orientation.vertical)
  var l: Label
  for el in ["A", "B", "C"]:
    l = newLabel(el)
    l.hexpand = true
    hbox1.append(l)
  for el in ["D", "E"]:
    l = newLabel(el)
    l.hexpand = true
    hbox2.append(l)
  vbox.append(hbox1)
  vbox.append(hbox2)
  window.setChild(vbox)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

Finally we should mention that the GtkBox widget implements the GtkOrientable interface, so that we can flip existing boxes dynamically at run-time of our program. Here flip stand for rotate by 90 degree, we switch between Orientation.horizontal and Orientation.vertical. That can be useful in rare cases, e.g. when the containing window is resized by the user. The following app flips the box when the user clicks on the button widget. We will learn more details about the GtkButton widget later in this book.

box3.nim
import gintro/[gtk4, gobject, gio]

proc flipBox(b: Button; box: Box) =
  box.setOrientation(Orientation(1 - box.getOrientation.ord))

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.vertical, 20)
  window.setChild( box)
  let label = newLabel("You can flip this box")
  let button = newButton("Flip")
  button.connect("clicked", flipBox, box)
  box.append(label)
  box.append(button)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()
Remember that each GtkWidget can be put at least one time into a GTK container widget. Whenever you try to insert the same widget again in the same or into another container, then you will get a GTK run-time error. This means that you would have to create multiple label widgets for the case that you wants to display the same text multiple times. You can not reuse the same widget. Also you should avoid trying to copy widgets, use one more constructor call like newLabel("My message") instead. Maybe later the gintro bindings will support copying of widgets, but currently they do not support it. This does not mean that you can not reuse widgets: You can remove widgets from containers and insert them in another container or again in the same container. In C some caution is needed, as widgets may get destroyed when we remove them from containers due to GTK’s refcounting system. So in C we generally call g_object_ref() on the widget before we remove it from a container to keep it alive until we insert it again. In Nim referencing widgets that way should not be needed. We may give examples for removing and re-inserting widgets with the gintro bindings later in the book.

References:

Button

button

The GtkButton widget is used to trigger a callback function that is called when the button is pressed. Generally the GtkButton widgets contains a label widget which is used to display a short text, or a symbolic icon. But the GtkButton can also contain most other GTK widgets. The GtkButton widget supports only two signals, the "clicked" and the "activate" signal. We use only the "clicked" signal to call our callback function when the button is pressed. The "activate" signal is for GTK internal use only. The program below creates a box container widget and fills it with various button variants:

button.nim
import gintro/[gtk4, gobject, gio]

proc buttonCb(b: Button) =
  echo "click"

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.horizontal, 10)
  window.setChild( box)
  let b1 = newButton("Click Me")
  let b2 = newButtonWithMnemonic("_Mnemonic")
  let b3 = newButtonFromIconName("document-print")
  let b4 = newButton("\u2654")
  let b5 = newToggleButton("Toggle Button")
  let b0 = newButton("CSS Styled")
  let cssProvider = newCssProvider()
  let data = "button {color: yellow; background: green;}"
  cssProvider.loadFromData(data)
  let styleContext = b0.getStyleContext
  assert styleContext != nil
  addProvider(styleContext, cssProvider, STYLE_PROVIDER_PRIORITY_USER)
  for b in [b0, b1, b2, b3, b4, b5]:
    box.append(b)
    b.connect("clicked", buttonCb)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

For the button b1 we pass a text argument to the constructor function newButton(). That way a label is created which is used to display that text on the button. In the section about GtkLabel widgets we explained that GTK supports mnemonics to activate widgets. To use mnemonics we put an underscore character (_) into a text string to make the following character a keyboard shortcut which is activated by pressing the ALT key and that character simultaneously. We use that for button b2. For button b3 we decided to display an icon instead of a textual message. For that we call the newButtonFromIconName() constructor and pass a valid icon name. To get an overview of the available icons you may launch the gtk4-icon-browser. The button b4 is again a plain GtkButton, but we use unicode glyphs as text. Button b5 is a GtkToggleButton, a subclass of the parent GtkButton. The GtkToggleButton behaves like a mechanical switch and is drawn differently when in pushed down state. The toggle button supports additional to the "clicked" signal of its parent class the "toggled" signal which is emitted whenever the state changes between pressed and released state. Generally we would connect the toggle button to the "toggle" signal, but to kept our program short we use also the available "clicked" signal. Finally button b0 is again a plain GtkButton, but we use CSS styling to change it colors. We said earlier that CSS can be used to customize the GTK GUI. Here we give only a very simple example to show that it is possible. Beginners generally ask about functions for styling widgets and expect at least a simple function to set foreground and background colors and maybe some more widget properties. GTK most of the time does not offer such simple styling functions. One reason for that is that GTK3 and GTK4 uses not plain background and foreground colors, but can use background images, color gradients, transparency effects and much more. The other reason is that customizing widgets is in most cases a bad idea. The themed GTK GUI is designed carefully for a nice appearance, and the user may have customized it further for her own needs. Maybe the user has selected a high contrast color scheme and larger fonts because of visual restrictions. So graphical customized widgets or whole apps generally look ugly and maybe can not used at all by some people.

But with CSS custom styling is generally possible with some efforts. In our example we call the function newCssProvider() to create a CssProvider object and load it from a string with the desired style properties. For simple style changes using a plain string as source is convenient, but for larger customization the function loadFromPath() can be used to load the data from an external CSS text file. The data string starts with the name of the widget that we want to modify followed by the necessary information enclosed in curly braces. As we have direct access to our widget we call getStyleContext() to gets its style context and then call addProvider() to add our new CSS information. This way we can change the size, margins and padding of widgets and many other styling properties. But these form of styling is not always that easy and may sometimes not work as expected at all. For styling widgets which are not directly accessible but contained deeply nested in other widgets, we have to use the function addProviderForScreen(getDefaultScreen(), …​) instead of the pair getStyleContext() and addProvider(). In the second halve of the book we will learn more about CSS styling and we will discover the gtk-inspector tool which allows us to inspect and even modify CSS properties and other useful data interactive from running applications.

To keep our example code short we connect all of our buttons to the same plain callback function which only prints the message "clicked" when one of the buttons is pressed. Earlier in the book we introduced GActions and showed how we can connect actions with widgets, keyboard input and other input sources. For larger applications the use of GActions may be a better solution than connecting signals directly to callbacks. We should also mention that the GtkWidget parent class of the GtkButton provides the function setSensitive() which can be used to make widgets insensitive. For a button widgets that means that mouse clicks are ignored.

For advanced use cases we can use the newButton() constructor without arguments and then call setChild() on the button instance to explicitly set a child widgets. The child widget may be a label styled with pango attributes, icon or image widgets or most other widget classes. When the child of the button is a label widget, then the function getLabel() can be used to get the label text. To set a new label text the function setLabel() can be used.[4] The function setIconName() can be used to set a named icon as child of the button — an existing child is replaced. To set an existing widget as child of a button the function setChild() is available, and getChild() can be used to retrieve a child, maybe to modify it. Finally we can use the function setHasFrame() with a boolean argument to determine if a frame should be drawn around the button.

Direct children of the GtkButton class are the already mentioned GtkToggleButton and the GtkLinkButton and the GtkLockButton. A GtkToggleButton is a GtkButton which will remain “pressed-in” when clicked. Clicking again will cause the toggle button to return to its normal state. A toggle button is created by calling either newToggleButton() or newToggleButtonWithMnemonic(). When you pass a string parameter to the first function, then a label widget is created as child, otherwise you should call setChild() to set the child widget. The state of the toggle button can be set by calling setActive() on the toggle button instance and retrieved by a call of getActive(). Finally the function setGroup() is available to set the group for the button instance, that is a related set of other toggle buttons. In a group of multiple toggle buttons, only one button can be active at a time. The group of toggle buttons behaves like radio buttons in this case, that is you press one and the formerly pressed one releases automatically. Note that the same effect can be achieved via the GtkActionable API, by using the same action with parameter type and state type 's' for all buttons in the group, and giving each button its own target value.

Toggle buttons have the advantage that they use the area of the label text for its function, so no additional space is consumed. But unfortunately it can be difficult to discover the actual state of the toggle button from its visual appearance. So other buttons with retained boolean state like the GtkCheckButton, the GtkSwitch and GtkRadioButtons are additional available. Additional GTK offers many specialized buttons like font-, color or filechooser buttons or menu- and spinbuttons. You may visit the widget gallery linked below to discover more button like classes. We will describe a few of them in the following sections of the book.

Note that you can also use a GtkBox populated with multiple widgets as child of button widgets. That way it is for example possible to have a textual label and an icon as active area of a button.[5]

References:

linklockbutton

The GtkLinkButton, the GtkLockButton anf the GtkToggleButton are direct subclasses of the GtkButton. The GtkToggleButton maintains a boolean state and and changes its visual appearance when in pressed down state. We discussed the toggle button already in the previous section together with the plain GtkButton widget.

The GtkLinkButton is a GtkButton with a hyperlink, similar to the ones used in HTML web pages. It is useful to show references to resources like help or support pages of our app. A link button is created by calling the constructor function newButton() with a string containing an URI and optional with an additional label text argument. The URI bound to the GtkLinkButton can be set specifically by calling the setURI() function, and retrieved by calling getURI().

By default the GtkLinkButton widget calls showURI() when the button is clicked. This function launches the default application for showing a given URI, or shows an error dialog if that fails. This behaviour can be overridden by connecting to the “activate-link” signal of the link button and returning gdk4.EVENT_STOPT from the call back function.

The GtkLockButton is a widget that can be used in control panels or preferences dialogs to allow users to obtain and revoke authorizations needed to operate the controls. The required authorization is represented by a GPermission object. Concrete implementations of GPermission may use PolicyKit or some other authorization framework. As GtkLockButton are used most often only in system tools and we have to learn about GPermission to use it, we will not discuss that button in detail.

The following program creates a GtkLockButton and a GtkLinkButton. The lock button has no function. The link button is connected to a callback function which prints the URI when the button is clicked.

linklockbutton.nim
import gintro/[gtk4, gdk4, gobject, glib, gio]

proc buttonCb(b: LinkButton): bool =
  echo "clicked: ", " ", b.getURI
  return gdk4.EVENT_PROPAGATE

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.horizontal, 50)
  window.setChild( box)
  let p = newSimplePermission(false)
  let lock = newLockButton(p)
  let link = newLinkButtonWithLabel("https://developer.gnome.org", "GTK/Gnome")
  box.append(lock)
  box.append(link)
  link.connect("activate-link", buttonCb)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The newLockButton() constructor function expects as argument a permission object which we create with a call of newSimplePermission(false) with default false state. In a real application we would had to provide code for handling that button. To the newLinkButton() constructor we pass an URI and a label text as arguments. We connect the "activate-link" signal to a callback function that prints the URI when the link is clicked. While most of the callback functions that we used in our previous examples had no return parameter, the callback function of the "activate-link" signal has to return a boolean value. In this case the return value determines if or if not that signal is further processed when our own callback is done. We can return the boolean constants gdk4.EVENT_PROPAGATE to allow processing that signal by other functions, or gdk4.EVENT_STOP to stop the propagation. In our example we return gdk4.EVENT_PROPAGATE at the end of our callback function to allow further processing by GTK. The result is that a web browser with our specified URI is opened automatically when available. If we do not want that behaviour we could return gdk4.EVENT_STOP to avoid this.

We can construct a link button with a call of newLinkButton() passing only a URI string if the button should display the URI or we can call newLinkButton() with an additional string which is used as label text. Later we can extract the URI string with a call of getURI() or set a different URI with setURI(). After the user has clicked on the button for the first time the boolean property "visited" is set to true and the look of the button changes. We can set and query the visited state with the getVisited() and setVisited() functions.

References:

Check-Button

checkbutton

The GtkCheckButton is a widgets with a label and a discrete toggle button. In GTK 4 the GtkCheckButton is a direct child of the GtkWidget class. A group of multiple check buttons can built a set of radio buttons in which only one button can be active at a time. For that variant pressing down one member of the group releases the formerly pressed one automatically, like for station or wavelength-range keys of historic broadcast radio devices.

The following example program creates 3 ordinary check button widgets and 3 radio button variants:

checkbutton.nim
import gintro/[gtk4, gobject, gio]

proc checkCb(ch: CheckButton) =
  echo ch.getLabel, " toggled: ", ch.getActive

proc activate(app: Application) =
  let window = newApplicationWindow(app)
  let bh = newBox(Orientation.horizontal, 10)
  let bv1 = newBox(Orientation.vertical)
  let bv2 = newBox(Orientation.vertical)
  window.setChild( bh)
  bh.append(bv1)
  bh.append(bv2)
  let ch1 = newCheckButton("Red")
  let ch2 = newCheckButton("Yellow")
  let ch3 = newCheckButton("Green")
  for ch in [ch1, ch2, ch3]:
    bv1.append(ch)
    ch.connect("toggled", checkCb)
  let r1 = newCheckButton("Sun")
  let r2 = newCheckButton("Rain")
  let r3 = newCheckButton("Snow")
  for ch in [r1, r2, r3]:
    bv2.append(ch)
    ch.connect("toggled", checkCb)
  r2.setGroup(r1)
  r3.setGroup(r1)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

A radio group is created by calling setGroup() on all but the first member. The first setGroup() call creates the initial group with only two members, and each subsequent call of setGroup() joins one more button to that group. We connect all of our check buttons to the same callback function to keep the example code short. The callback function extract the label text from the check button passed as argument and prints its new state. Note that for the radio buttons one click on a button to activate it automatically releases the formerly pressed button in the group, resulting in one additional call of our callback function. We create a new check or radio button with a call of newCheckButton(). When we give a string argument the button gets a label next to the active area. In most cases we need a label to inform the user about the purpose of that button, but when our check button has a neighborhood relation to another widget a label may not be necessary. We can later set or modify the label text with setLabel() or retrieve the label text with getLabel(). As for most widgets we can also create check buttons with mnemonics by use of the constructor function newCheckButtonWithMnemonic() or by later activation of mnemonics by calling setUseUnderline() on the check button widget. The function setGroup() is used to group multiple check buttons to a radio group. Note that the same effect can be achieved via the GtkActionable API, by using the same action with parameter type and state type 's' for all buttons in the group, and giving each button its own target value. The boolean state of check buttons can be set with a call of setActive() and queried with a call of getActive(). Finally the check button widget provides the function setInconsistent() to set the visual state of a button to an "in between" state. This function affects only the visual appearance of the button, not the functionality. This may be necessary when there is temporary no clear boolean state for a property. Generally we would set the widget to a consistent state as soon as possible by a call of setInconsistent() with false argument, i.e. when the user clicks that button again or when other conditions change.

References:

Switch

switch

The GtkSwitch is one more simple widget with a boolean on/off state. The user can switch the state by clicking in the empty area, or by dragging the handle. The GtkSwitch consists only of the graphical symbol with its active area, there is no label text directly connected to the switch widget. So the GtkSwitch is generally placed beside another widget so that the user can guess on what the switch works. The on/off state of the switch is recognized by the position of the slider and the color of the active area, which should be clear enough at least for the default GUI theme. The GtkSwitch can also handle situations where the underlying state changes with a delay, i.e. when the user turns on blue-tooth or WLAN. The example program below creates a horizontal box with two label widgets and two switches:

switch.nim
import gintro/[gtk4, gdk4, gobject, glib, gio]

proc switchCb(s: Switch; newState: bool; num: int): bool =
  echo "switched ", num, " to ", newState
  return gdk4.EVENT_PROPAGATE

proc activate(app: Application) =
  let window = newApplicationWindow(app)
  let bh = newBox(Orientation.horizontal, 10)
  let l1 = newLabel("Switch 1")
  let l2 = newLabel("Switch 2")
  let s1 = newSwitch()
  let s2 = newSwitch()
  s1.connect("state-set", switchCb, 1)
  s2.connect("state-set", switchCb, 2)
  for w in [l1, s1, l2, s2]:
    bh.append(w)

  window.setChild(bh)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

We use the same callback function for both switches and connect it to the "state-set" signal. To discriminate the two switches we pass an additional parameter to our callback function. The callback function receives the new state of the switch by a boolean parameter and has to return a boolean result indicating if the signal should be further processed by other signal handlers or if the signal emission should stop. When our callback function returns gdk4.EVENT_STOP then the signal is not further processed by GTK, which means that the user action changes only the position of the slider but the color of the switch widget does not change automatically. That can be used to indicate delayed state changes like turning on WLAN or blue-tooth. In that case we call setState() when the state change is complete to bring the switch to a consistent state.

References:

ComboBoxText

comboboxtext

The GtkComboBoxText widget is a simple variant of the GtkComboBox that hides the model-view complexity for simple text-only use cases. That widget behaves similar to a popup menu. When we click on the widget a list with text strings pops up and we can select one string from the list. That widget supports the "changed" signal. The connected callback function is called whenever the user selects a different string from the list. We create a comboboxtext with the constructor function newComboBoxText() and can add entries by use of appendText(), insertText() and prependText(). There exists also functions append(), insert() and prepend() which allows to additional specify a string id for each entry, but these functions are not too useful for the plain GtkComboBoxText variant. We can remove text entries by calling the function remove() with the element index as argument or we can use removeAll() to clear the whole list. We can also create a list with an additional entry field by calling the constructor function newComboBoxTextWithEntry(), which allows the user to type in arbitrary text strings. In the callback function we can access the index of the user selection with the getActive() function and the corresponding text with the function getActiveText().

comboboxtext.nim
import gintro/[gtk4, gobject, gio]

proc changed(cbt: ComboBoxText) =
  echo "changed to: ", cbt.getActive, " ", cbt.getActiveText

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let cb = newComboBoxText()
  for t in ["zero", "one", "two", "three"]:
    cb.appendText(t)
  cb.setActive(0)
  cb.connect("changed", changed)
  window.setChild(cb)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The above program creates four list entries and connects the callback function to the "changed" signal. The function setActive() is called to pre-select an entry.

References:

Text

text

The GtkText widget is a single line text entry field. It provides the basic functionality for the GtkEntry and the GtkSearchEntry widgets. Those widgets provides optical decorations like a frame and can display icons or an progress display, while the GtkText has no decorations itself. For multi-line editable we have also the GtkTextView widget.

The GtkText widget provides a single line text entry field with all the advanced editing functionality like scrolling when the entered text is larger than the widget allocation, cursor movement, delete and backspace support and a popup menu with cut, copy and paste support. It is even possible to expand that popup menu with more menu entries, but that is some effort and not often necessary, so we will not demonstrate it in this section.

The following program creates three variants of the GtkText widget. The first widget shows a message about its purpose as long as the user has not yet entered some actual text. The second text field sets its visibility property to false to hide the entered text as common for password entries. The third text entry field is an ordinary GtkText widget, but we use the function setMaxWidthChars(4) to restrict its size to exactly 4 characters. Note that GtkText is a direct child of GtkWidget and it implements the GtkEditable interface, from which we took the setMaxWidthChars() function. The GtkEntry widget, which uses the functionality of GtkText, is also a direct chield of the GtkWidget class but not a chield of GtkText.

text.nim
import gintro/[gtk4, gobject, gio]

proc activate(t: Text) =
  echo "You entered: ", t.buffer.text

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = newBox(Orientation.horizontal, 20)
  window.setChild( box)
  let t1 = newText()
  let t2 = newText()
  let t3 = newText()
  let f1 = newFrame()
  let f2 = newFrame()
  let f3 = newFrame()
  f1.setChild(t1)
  f2.setChild(t2)
  f3.setChild(t3)
  box.append(f1)
  box.append(f2)
  box.append(f3)
  t1.placeHolderText = "Search ..."
  t2.visibility = false
  t3.setMaxWidthChars(4)
  for t in [t1, t2, t3]:
    t.connect("activate", activate)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

In the example program above we put each text widget into a GTtkFrame widget to better show its extents. The GtkFrame is a decorative container which is used to frame single widgets or to visible group multiple widgets. The GtkText class provides many functions to adapt its appearance or its functionality, which we can not discuss all here. Whenever you should need some special function you should consult the GtkText API as well as the related classes like GtkEditable and GtkWidget. As usual we create the text fields with the newText() constructor. For each text filed we create a frame widget, put all the frame widets into a horizontal box and finally set that box as chield of the main window. We use the "activate" signal to connect all the text fields to the same callback function, which prints the entered text when the user hits the return or enter key. Note that the GtkText widget uses an GtkEntryBuffer object to store the actual text content. In our callback function we had to access the buffer object to get the actual text content. Buffers can be shared between multiple text fields, but that is only useful in rare cases. Also note that we can use the function setAttributes() to style the text of an text field with pango text attributes, maybe to change font size of colors. For details about pango text attributes refer to the GtkLabel section.

In our example program we used the activate signal to call our callback function whenever the user confirms his input by pressing the return or enter key. In a few cases we may desire an immediate call of a callback function whenever the user modifies the input, without waiting for confirmation by return or enter. This is indeed possible when we make use of the fact that the GtkText widget implements the GtkEditable class, which support the "changed" signal. So we can add a changed callback in this way:

proc changed(e: Editable) =
  echo "You entered just now: ", e.getText
# ...
  for t in [t1, t2, t3]:
    connect(cast[Editable](t), "changed", changed)

Currently casting the text fields to editable is necessary to make this work, as current gintro bindings seems not to provide a converter function from GtkText to GtkEditable.

References:

Entry

entry

The GtkEntry is a single line text entry widget. It provides most of the functionality that we already know from the GtkText widget. The GtkEntry uses internally the GtkText widget functions, while it is not a subclass of GtkText but a direct subclass of GtkWidget. Like the GtkText widget the GtkEntry implements the GtkEditable interface. The entry widget supports basic editing functions, and pressing the right mouse button over its active area opens a popup menu which provides cut/copy/past functionality and allows to insert uni-code symbols. If the entered text is longer than the visible size of the input field, then the text scrolls so that the cursor position is always visible. When using an entry for querying passwords or other sensitive information, the widget can be put into “password mode” using the setVisibility() function. In this mode, entered text is displayed by “invisible” placeholder characters. By default GTK uses the best placeholder character that is available in the current font, but the character can be changed with the function setInvisibleChar(). The GtkEntry widget has also the ability to display progress or activity information. We can call the function setProgressFraction() to display a progress state between 0 and 1, or we can call setProgressPulseStep() and progessPulse() to move the progress bar in steps, maybe bouncing back and forth. Additionally, GtkEntry can display icons at either side of the entry. These icons can be activated by clicking, can be set up as drag sources and can have tooltips.[6]

The following program creates an entry widgets with a placeholder text, an icon at the right and a progress indicator:

entry.nim
import gintro/[gtk4, gobject, gio]

proc activate(e: Entry) =
  echo "You entered: ", e.buffer.text
  e.progressPulse
  #entry.setProgressFraction(0.7) # use this if we know the exact progress state in %

proc iconPress(e: Entry; p: EntryIconPosition) =
  echo "You clicked: ", p
  discard e.buffer.deleteText(0, -1) # delete all

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let entry = newEntry()
  entry.marginStart = 10
  entry.marginEnd = 10
  entry.marginTop = 10
  entry.marginBottom = 10
  entry.setProgressPulseStep(0.2)
  entry.setPlaceholderText("Enter some text")
  entry.setIconFromIconName(EntryIconPosition.secondary, "edit-clear")
  entry.setIconActivatable(EntryIconPosition.secondary, true)
  entry.connect("activate", activate)
  entry.connect("icon-press", iconPress)
  window.setChild(entry)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The icon is connected to the iconPress() callback function and clears the text buffer. Whenever we enter some text in the widget the connected activate() callback function prints that text to the terminal window and pulses the progress bar. To increase the visibility of the widget’s border frame and the pulse bar we set the margins to 10 logical pixel for all four borders of the entry widget. The GtkEntry C API provides some more functions to customize entry widgets for concrete use cases. The purpose of that functions should be obvious, for details you can consult the C API documentation.

References:

SpinButton

spinbutton

The GtkSpinButton looks and behaves similar to the GtkEntry widget, but is used for numeric input and has additional to the input field two buttons which allows to increment or decrement the displayed numeric value with the mouse instead of the keyboard. The GtkSpinButton can restrict the input values to a allowed range and round them to a specified precision, i.e. the number of decimal places.

The spin button uses internally a GtkAdjustment object to store the allowed input ranges, the current value and the used increments. Internally both, the adjustment and the spin button stores and processes the numeric value as C double, but some functions like getValueAsInt() allows to to retrieve the rounded integral value.

The entry field of the spin button widget has by default the correct size to display all values of the allowed input range with the desired precision. But this automatic sizing can be overwritten by explicitly setting the “width-chars” property.

The following example program creates two spin button widgets and connects both to a valueChanged() callback function which is called whenever the numeric value is changed by user input:

spinbutton.nim
import gintro/[gtk4, gobject, gio]

proc valueChanged(s: SpinButton) =
  echo "value changed: ", s.getValue, " (", s.getValueAsInt, ")"

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let adj1 = newAdjustment(50.0, 0.0, 100.0, 1.0, 10.0, 0.0) # value, lower, upper, stepIncrement, pageIncrement, pageSize
  let adj2 = newAdjustment(value = 0, lower = 0, upper = 10.0, stepIncrement = 0.01, pageIncrement = 1.0, pageSize = 0.0)

  let b1 = newSpinButton(adj1, 5.0, 0)
  let b2 = newSpinButton(adj2, 0.0, 2)
  b1.connect("value-changed", valueChanged)
  b2.connect("value-changed", valueChanged)
  let box = newBox(Orientation.horizontal, 25)
  box.append(b1)
  box.append(b2)
  window.setChild(box)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

First we create two adjustments objects with a call of the newAdjustment() constructor function. That constructor has 6 arguments of C double (Nim float) — the last one is the pageSize which is zero by default and ignored for spin buttons. The other five numbers are the initial value, the lower and upper bounds of the valid input range, and the step- and page-increments. The step increment is the amount by which the displayed value changes when we click on one of the two buttons with the left mouse key or when we press the up or down array keys of our keyboard. The page increment is used for larger steps — when the middle mouse button is pressed or when the shift key is hold while the arrow keys of the keyboard are used.

To create a spin button we pass the adjustment object and two numeric values to the newSpinButton() constructor function. The numeric values are the the climb rate and the number of decimal digits to display. Understanding the climb rate is not that easy. When we press the up or down buttons with the mouse or hold the up or down array keys of the keyboard pressed for a longer time period, then the numeric values continuously increases or decreases. When we set the clinb rate to zero, than the increase or decrease rate is constant. But when we set the climb rate to a value greater zero, then the steps with which the contents changes increases in size, allowing a faster but less accurate adjustment.

For our first spin button we intent a valid input range from zero to 100 with a start value of 50, and we do want no fractional results, so we set the last parameter digits in the newSpinButton() call to zero. To allow fast modifications of the input value with the mouse or with the arrow keys, we use a climb rate of 5.

Our second spin button shall have an input range from 0.00 up to 10.00. We use a climb rate of zero, which ensures that value changes occur always slowly in 0.01 increments.

When we launch our program, type the value zero into the first spin button and press the "value increase" button of both widgerts for a longer time period we get these values printed in the terminal window:

value changed: 0.0 (0)
value changed: 1.0 (1)
value changed: 2.0 (2)
value changed: 3.0 (3)
value changed: 4.0 (4)
value changed: 5.0 (5)
value changed: 6.0 (6)
value changed: 7.0 (7)
value changed: 8.0 (8)
value changed: 14.0 (14)
value changed: 20.0 (20)
value changed: 26.0 (26)
value changed: 32.0 (32)
value changed: 38.0 (38)
value changed: 44.0 (44)
value changed: 55.0 (55)
value changed: 66.0 (66)
value changed: 77.0 (77)
value changed: 88.0 (88)
value changed: 99.0 (99)
value changed: 100.0 (100)

value changed: 0.01 (0)
value changed: 0.02 (0)
value changed: 0.03 (0)
value changed: 0.04 (0)
value changed: 0.05 (0)
value changed: 0.06 (0)
value changed: 0.07000000000000001 (0)
value changed: 0.08 (0)
value changed: 0.09 (0)
value changed: 0.09999999999999999 (0)
value changed: 0.11 (0)
value changed: 0.12 (0)

So for the first spin button we get increments of one for the first 8 values, then increment increases by 5 to 6, and finally the increment increases again by 5 to 11. The final value is clamped to 100. For the second spin button the increments has the constant value 0.01, but we have to care for the fact that floating point numbers are approximations of real numbers, so we may have to restrict the number of decimals when we print the values. And of course we have to avoid test for exact equality, as they generally fail for floats due to tiny numeric errors.

GTK also provides a newSpinButtonWithRange() constructor function, which needs only the arguments min, max and step and no adjustment object as parameter. The other values like the initial value, increment and precision are then automatically generated. Generally it makes not much sense to use this function, as we have to remember how all the unspecified settings are deviated and because in most cases we do not get exactly what we need. The function setAdjustment() can be used to replace the adjustment object of a spin button, and the function setDigits() is available to set the displayed number of digits after the decimal point. The functions setRange() can be used to modify the input range, and setIncrements() can be used to modify the step and page increments. Retrieving these values is possible with the corresponding getter functions like getRange(). Finally we can set and get the numeric value with getValue() and setValue(), or we can retrieve the value as an integral number with getValueAsInt(). The function setWrap() allows us to determine if a spin button value wraps around to the opposite limit when the upper or lower limit of the range is exceeded, and with setSnapToTicks() we can control whether values are set to the nearest multiple of the step increment when a spin button is activated after providing an invalid value. By calling the configure() function passing an adjustment object, a climb rate and the number of digits, we can also fully reconfigure a spin button. Finally we can call the functions spin() to increment or decrement a spin button’s value in a specified direction by a specified amount, or call the update() function to manually force an update of the spin button.

In the example program above we used the "value-changed" signal to connect our callback function. For that signal our callback function is called when the user presses the up or down buttons, the up or down array keys of the keyboard or when the user has typed in a new value and then confirms the value by pressing enter. If the value is unchanged or clamped our callback is not called. As the GtkSpinButton implements the GtkEditable interface we can also connect to the "changed" signal to get the text typed in by the user immediately. Additional the GtkSpinButton provides the "input" signal, which we may use in special cases to accept numeric text input, maybe allow the user to type "max" or "" to change the actual value. In a similar way the "output" signal could be used to modify the displayed value, maybe we want to display "max" when the value is clamped to the upper bound, always add a "" sign or leading zeros to the displayed numbers, or maybe to suppress decimal places when they are zero. Finally the "wrapped" signal is available for a special action when the value wraps around by user input.

References:

Grid

grid2

The GtkGrid is a container widget which is used to arrange child widgets in a tabular layout with rows and columns. Adjacent cells of the grid can be joined, and the indices of the cells can expand in positive or negative direction, allowing easy extension of existing grids in all direction. A new grid container widget is created with the constructor function newGrid() which needs no arguments. Then we can add child widgets by use of the attach() function which requires a column and a row index and optionally accepts a width and a height parameter for the case that the child should span multiple rows or columns. It is also possible to add a child next to an existing child with the function attachNextTo().

The following example program creates a grid container widget with label and entry child widgets. To keep the example code short we do not connect the entry widgets to callback functions, you may refer to the Entry section for details about the "activate" signal.

grid2.nim
import gintro/[gtk4, gobject, gio]

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  window.title = "Employees"
  let grid = newGrid()
  for i, t in ["First Name", "Surname", "Job", "Retire Date"]:
    let l = newLabel(t & ": ")
    l.xAlign = 1
    let e = newEntry()
    grid.attach(l, 0, i)
    grid.attach(e, 1, i)
  window.setChild(grid)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

We use the attach() function to create the grid cells and to insert the child widgets. We use column and row indices starting at zero and extending to positive values with no gaps. The order in which we add the child widgets is arbitrary, we could use negative indices or skip row or column indices. But we should not try to put more than one child widget into one single grid cell. If multiple widgets should reside in one cell, then we have first to put that child widgets in a box and then can add that box to a cell of the grid. For our label widgets we set the alignment value to 1 to make them right aligned.

The GtkGrid widgets implements the GtkOrientable interface, so we can use the function setOrientation() with enum paramters Orientation.horizontal or Orientation.vertical to rotate the grid by 90 degree.

We can attach child widgets next to already contained children by use of the function attachNextTo(). We have to pass the grid, the new child, the existing child and an enum PositionType with possible values left, right, top bottom. Optionally we can pass a width and height parameter which has the value 1 as defaults. With attachNextTo() we can also insert new child widgets by possible shifting existing widgets. The attachNextTo() function allows to pass nil as third parameter. Then the new widget is inserted at position zero, the concrete behaviour is determined by the PositionType enum parameter.

We can use the function getChildAt() passing the cell indices as parameters to get a child widget and then possible call remove() on the result or another known child to remove a widget. The function queryChild() can be used to get the position and spans of child widgets, and with insertRow() and insertColumn() we can insert whole rows or columns, or remove them with removeRow() and removeColumn(). Cells with children spanning multiple cells expands or shrink in that case. Finally a function called insertNextTo() is available which inserts rows or columns next to an existing child widget, where the concrete behaviour is again determined by the PositionType enum (insert to the left, to the right, upon or below the existing child.) Further function called setRowHomogeneous(), setColumnHomogeneous(), setRowSpacing() and setColumnSpacing() to give all rows or columns the same width or height and a separation are available. Finally the grid class provides a few functions to modify the baseline position of child widgets, which we will not try to explain here. Maybe later we will manage to give a useful example for different baseline positions.

Note that the GtkGrid supports cursor navigation between cells — in our above example you can move with the keyboard up and down keys between the different entry widgets.

References:

Headerbar

headerbar

A very simple and useful widget is the GtkHeaderbar, which is often used as a replacement of the ordinary window title and can contain menu buttons or other widgets. The GtkHeaderbar was introduced in GTK3 but strongly modified for GTK4. The GTK developers provides a C program called testheaderbar.c which demonstrates many different ways in which headerbar widgets can be used. In this section we will present a common use case where the window title is replaced with the headerbar widget and that headerbar widget contains a menu button. As we have already created a program with a menu button and a popup menu with gactions we will reuse that code nearly unchanged:

headerbar.nim
# https://gitlab.gnome.org/GNOME/gtk/-/blob/master/tests/testheaderbar.c
# gcc -Wall testheaderbar.c -o testheaderbar `pkg-config --cflags --libs gtk4`

import gintro/[gtk4, glib, gobject, gio]

const menuData = """
  <interface>
    <menu id="menuModel">
      <section>
        <item>
          <attribute name="label">Normal Menu Item</attribute>
          <attribute name="action">win.normal-menu-item</attribute>
        </item>
        <submenu>
          <attribute name="label">Submenu</attribute>
          <item>
            <attribute name="label">Submenu Item</attribute>
            <attribute name="action">win.submenu-item</attribute>
          </item>
        </submenu>
        <item>
          <attribute name="label">Toggle Menu Item</attribute>
          <attribute name="action">win.toggle-menu-item</attribute>
        </item>
      </section>
      <section>
        <item>
          <attribute name="label">Radio 1</attribute>
          <attribute name="action">win.radio</attribute>
          <attribute name="target">1</attribute>
        </item>
        <item>
          <attribute name="label">Radio 2</attribute>
          <attribute name="action">win.radio</attribute>
          <attribute name="target">2</attribute>
        </item>
        <item>
          <attribute name="label">Radio 3</attribute>
          <attribute name="action">win.radio</attribute>
          <attribute name="target">3</attribute>
        </item>
      </section>
    </menu>
  </interface>"""

proc changeLabelButton(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  label.setLabel("Text set from button")

proc normalMenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  label.setLabel("Text set from normal menu item")

proc toggleMenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  let newState = newVariantBoolean(not action.getState.getBoolean)
  action.changeState(newState)
  label.setLabel("Text set from toggle menu item. Toggle state: " & $newState.getBoolean)

proc submenuItem(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  label.setlabel("Text set from submenu item")

proc radio(action: gio.SimpleAction; parameter: glib.Variant; label: Label) =
  var l: uint64
  let newState: glib.Variant = newVariantString(parameter.getString(l))
  action.changeState(parameter)
  let str: string = "From Radio menu item " & getString(newState, l)
  label.setLabel(str)

proc activate(app: gtk4.Application) =
  let
    window = newApplicationWindow(app)
    box = newBox(gtk4.Orientation.vertical, 12)
    menubutton = newMenuButton()
    button1 = newButton("Change Label Text")
    actionGroup: gio.SimpleActionGroup = newSimpleActionGroup()
    label: Label = newLabel("Initial Text")
    header = newHeaderBar() (1)

  window.setTitle("Headerbar as titlebar") (2)
  window.setTitlebar(header) (3)
  var action: SimpleAction
  action = newSimpleAction("change-label-button")
  discard action.connect("activate", changeLabelButton, label)
  actionGroup.addAction(action)

  action = newSimpleAction("normal-menu-item")
  discard action.connect("activate", normalMenuItem, label)
  actionGroup.addAction(action)

  var v = newVariantBoolean(true)
  action = newSimpleActionStateful("toggle-menu-item", nil, v)
  discard action.connect("activate", toggleMenuItem, label)
  actionGroup.addAction(action)

  action = newSimpleAction("submenu-item")
  discard action.connect("activate", submenuItem, label)
  actionGroup.addAction(action)

  v = newVariantString("1")
  let vt = newVariantType("s")
  action = newSimpleActionStateful("radio", vt, v)
  discard action.connect("activate", radio, label)
  actionGroup.addAction(action)
  window.insertActionGroup("win", actionGroup)

  label.setMarginTop(12)
  label.setMarginBottom(12)
  box.append(label)
  menuButton.setHalign(gtk4.Align.center)

  var builder = newBuilderFromString(menuData)
  var menuModel: gio.MenuModel = builder.getMenuModel("menuModel")
  var menu = newPopoverMenuFromModel(menuModel)
  menuButton.setPopover(menu)
  # box.append(menubutton) (4)
  menuButton.setIconName("open-menu-symbolic") (5)
  header.packEnd(menuButton) (6)
  button1.setHalign(gtk4.Align.center)
  button1.setActionName("win.change-label-button")
  box.append(button1)
  window.setChild(box)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

The differences of the code above to the previous gaction.nim example are indeed tiny. We will use annotations to point you to the modification: Some lines of above code ends with a numeric digit included in a circle to mark that line, and we will refer to that location with the same digit here:

1 ceate a new header bar widget
2 give the window a nice title
3 set our header widget as window titlebar
4 remove or comment out this line as we will add the menu button to our headerbar now
5 set a nice icon image for our menu button
6 add the menu button to the headerbar

Headerbars can contain more widgets, e.g. an "Open" and a "Save" button as used for the gedit editor, or a text entry widget to support text search. We use the functions packEnd() and packStart() to add widgets at the right or left side of our headerbar.

If you should know the headerbar widget already from GTK 3 you may wonder about the gtk_header_bar_set_subtitle() function which was used in GTK 3 to set subtitles, i.e. the file path below the document name in the gedit editor. This function is not directly provided in GTK 4, maybe we can insert a vertical box into the headerbar widget which then displays title and subtitle when desired. But maybe that subtitle is not that useful at all and its use makes the titlebar really thick.

In above example we used the function setIconName() to change the image of our menu button. GTK provides a program called gtk4-icon-browser which can be used to find icons and their names. And you may consult the references mentioned below.

References:

Dialogs

The dialog box is a graphical control element in the form of a small popup window that communicates information to the user and prompts them for a response or selection. Dialog boxes are classified as "modal" or "modeless", depending on whether they block interaction with the software that initiated the dialog. The simplest type of a dialog box is the alert, which displays a message and may require an acknowledgment that the message has been read, usually by clicking "OK", or a decision as to whether or not an action should proceed, by clicking "OK" or "Cancel". An example of a plain dialog is the "About" box found in many software programs, which usually displays the name of the program and the version number, and may also include a list of the author names and copyright information. Another example for the use of dialog boxes is asking for confirmation for actions which may result in loss of important data, e.g. closing a program which has an unsaved data buffer or overwriting an existing file.

Non-modal or modeless dialog boxes are used when the requested information is not essential to continue, and so the window can be left open while work continues elsewhere. An example of some form of modeless dialogs are toolboxes which can be separated or detached from the main application window. In general, good software design calls for dialogs to be of the non-modal type where possible, since they do not interrupt the workflow and do not force the user into a particular mode of operation. An example might be a dialog of settings for the current document, e.g. the background and text colors. The user can continue adding text to the main window whatever color it is, but can change it at any time using the dialog. (This isn’t meant to be an example of the best possible interface for this; often the same functionality may be accomplished by toolbar buttons on the application’s main window.)

Modal dialogs can be classified as system modal, application modal or document modal. System modal dialogs blocks the whole GUI, e.g. the shutdown dialog for the computer or serious, unrecoverable system errors. Application modal dialogs blocks the whole application instance, while document modal dialogs blocks only the editing of a single document.

Additional to the classic dialog box GTK offers a set of highly specialized dialogs, from which the most used and most important one is the well known GtkFileChooserDialog. Other specialized dialogs are the GtkFontChooserDialog, the GtkColorChooserDialog, the GtkAppChooserDialog, the GtkPageSetupUnixDialog and the GtkPrintUnixDialog. A simplified variant of the GtkDialog is the GtkMessageDialog, but that one is not available by gobject-introspection and is currently unavailable in the gintro Nim GTK bindings. All these specialized dialogs are subclasses of the GtkDialog class. And finally there exists the GtkNativeDialog with it child GtkFileChooserNative and the GtkAboutDialog, which are not subclasses of the GtkDialog but direct children of GtkWindow.

You should also note that the existing dialog variants are mostly convenient classes which allow easy creation of dialog boxes with a predefined look which integrates well into the GUI. If you have special needs you can also create your own custom dialog boxes based on the GtkWindow class.

Dialog box

dialog

The GtkDialog is a subclass of the GtkWindow and is used to create popup windows. While Dialog boxes are a convenient way to prompt the user for a small amount of input, to display a message or to ask a question that popup behaviour interrupts the user’s workflow and should be used only with care.

GTK treats a dialog as a window split vertically. The top section is a GtkBox which may contain widgets to display information like a label. The bottom area is known as the “action area” in which buttons or other actionable widgets should be packed, like "OK" and "Cancel" buttons.

We create a new dialog box with a call of the constructor function newDialog() which takes no arguments. The GTK C API offers the convenience function gtk_dialog_new_with_buttons() which allows to set the dialog title, some boolean flags, and to add some buttons. But as that function uses untyped varargs arguments it is not type safe and not supported by gobject-introspection. So with Nim and the gintro bindings we have to use the the newDialog() constructor function and to add the title, buttons, other widgets and flags with separate functions.

A “modal” dialog (that is, one which freezes the rest of the application from user input), can be created by calling the setModal() function with the dialog window as argument.

We can add various buttons to the dialog window with the function addButton(), to which we pass a label text and a response id. For the response id we can use one of the predefined negative enums, or our own positive integer constants. Clicking one of the buttons will emit the “response” signal and our connected callback function will receive the matching response id. These buttons also support Mnemonics (see Mnemonics in Label Text), so we can use "_Yes" as a label text and the "Y" will become underlined if we press the ALT key and ALT + "y" will activate that button.

The predefined response ids of module gtk4 are listed below. The deleteEvent id is passed to the "response" callback when the user clicks not on a button of the dialog box but on the window close symbol or when the ESC key is pressed.

type
  ResponseType* {.size: sizeof(cint), pure.} = enum
    help = -11 # Returned by Help buttons in GTK dialogs
    apply = -10 # Returned by Apply buttons in GTK dialogs
    no = -9 # Returned by No buttons in GTK dialogs
    yes = -8 # Returned by Yes buttons in GTK dialogs
    close = -7 # Returned by Close buttons in GTK dialogs
    cancel = -6 # Returned by Cancel buttons in GTK dialogs
    ok = -5 # Returned by OK buttons in GTK dialogs
    deleteEvent = -4 # Returned if the dialog is deleted
    accept = -3 # Generic response id, not used by GTK dialogs
    reject = -2 # Generic response id, not used by GTK dialogs
    none = -1 # Returned if an action widget has no response id, or if the dialog gets programmatically hidden or destroyed

As the GtkDialog widget class implements the GtkBuildable interface we can also use XML UI files and the GTK builder for building dialogs. We may add an example for that later.

The example program below creates an application window which contains a button widget. We connect that button with the dialogCb() callback function. That callback creates the dialog, set some properties of the dialog and connects the dialog widget to the responseCb() callback function.

dialog.nim
import gintro/[gtk4, gobject, gio]

proc responseCb(d: Dialog; id: int) =
  echo "response: ", ResponseType(id)
  d.destroy

proc dialogCb(b: Button; w: ApplicationWindow) =
  let dialog = newDialog()
  dialog.setMargin(10)
  dialog.title = "Dialog"
  dialog.setTransientFor(w)
  # dialog.setDestroyWithParent(true) # not useful for modal dialogs
  dialog.setModal(true)
  let contentArea = dialog.getContentArea
  let msg = newLabel("Do you like Nim and GTK?")
  contentArea.append(msg)
  discard dialog.addButton("Yes", ResponseType.yes.ord)
  discard dialog.addButton("No", ResponseType.no.ord)
  dialog.connect("response", responseCb)
  dialog.show

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  window.setMargin(50)
  window.title = "Application Main Window"
  let b = newButton("Open Dialog")
  b.connect("clicked", dialogCb, window)
  window.setChild(b)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

In the dialogCb() callback function we call the constructor function newDialog() to create the dialog widget and set the margins and a title for the dialog window. We call setTransientFor() on the dialog widget with our parent window as argument to bind the dialog window to the parent window. And we call the function setModal() on the dialog widget to freeze the rest of the application from user input. Finally we add a label to the content area and two buttons to the action area of our dialog, connect it to the response callback function and call show() on it do display the dialog to the user. The responseCb() callback function prints the response id and calls destroy on the dialog to close its window and return control to the parent application. For non modal dialogs it can be useful to call additional setDestroyWithParent(true) to ensure that the dialog is closed automatically when the user closes the parent window.

The GtkDialog class offers some more functions: The response() function with a dialog widget and an response id as argument can be used to emit the "response" signal which will then call a connected callback function. An example use case where it can be necessary to call the response function() from program code may be when we have an entry widget in our dialog and we want to terminate the dialog when the entry is activated. For that we may connect the entry to its "activate" callback function and call response() on the dialog to close it with a well defined id result from the "activate" callback function. The function addActionWidget() can be used to add other widget types to the action area of the dialog widget, e.g. an entry widget. That function gets an response id as last parameter, that id is passed to the "response" callback function when that widget is activated. The function setDefaultResponse() called with an response id sets the last widget in the dialog’s action area with the given response id as the default widget for the dialog. Pressing “Enter” normally activates the default widget. The function setResponseSensitive() called on the dialog instance with a respose id and a boolean values as parameters calls setSensitive(widget, setting) for each widget in the dialog’s action area with the given response id. This offers a convenient way to sensitize/desensitize dialog buttons. The function getResponseForWidget() queries the response id of a widget in the action area, and getWidgetForResponse() returns the widget for a given response id. Finally the functions getContentArea() and getHeaderBar() allow access to the content area box widget and the header bar of the dialog. Note that the headerbar is only used by the dialog if the “use-header-bar” property is true. When we set the “use-header-bar” property of the dialog to true, then the dialog uses a GtkHeaderBar for action buttons instead of the action-area at the bottom. The GtkFileChooserDialog does this. Currently user defined GtkDialogs with a HeaderBar are not supported by the gintro bindings, because the use “use-header-bar” property has to be set on creation of the dialog, but the only available newDialog() constructor does not have a flags argument.[7]

In our example program we used the “response” signal to connect our response callback function. The response signal is emitted when an action widget is clicked, the dialog receives a delete event, or the application programmer calls the response() function. On a delete event, the response id is ResponseType.deleteEvent. Otherwise, it depends on which action widget was clicked. The dialog widget supports also the "close" signal. The "close" signal is a keybinding signal which gets emitted when the user uses a keybinding to close the dialog. The default binding for this signal is the Escape key.

References:

FileChooserDialog

filechooserdialog

The GtkFileChooserDialog is a child of the GtkDialog. It works by putting a GtkFileChooserWidget inside a GtkDialog and can display the user’s file system directory structure as trees and lists showing the file names and other file properties and is used to interactively open or save files.

Note that the displayed screenshot above is taken from an older display with low pixel density and enabled font antialiasing , which generates a unsharp, washed out look, which some people may prefer.

The GtkFileChooserDialog offers only one single function, which is the constructor for the widget and which is called gtk_file_chooser_dialog_new() in the C API and newFileChooserDialog() for the gintro Nim bindings.

But the file chooser dialog exposes the GtkFileChooser interface, so you can use all of the GtkFileChooser functions on the file chooser dialog as well as those for GtkDialog.

The GtkFileChooser interface provides a set of useful functions for file handling. One of then is called getFile() which returns the selected GFile, which we may use directly to load or save data and which provides the file name and path by the getPath() function.

Using the GtkFileChooserDialog is very similar to the use of the GtkDialog: We call the constructor to create the widget and connect it by use of the "response" signal to our callback function, which checks the response id and then opens or saves the file in case of a positive response.

Note that there is also the GtkFileChooserNative dialog available, which may integrate better in the GUI environment when our program may be run on Windows or macOS. The GtkFileChooserNative will use a platform-specific dialog if available and fall back to GtkFileChooserDialog otherwise. As the GtkFileChooserDialog implements the GtkBuildable interface we also could use XML UI files and the GtkBuilder to create the file chooser dialog.

The following program is a minimal example which lets the user pic a file to open and prints the file system path of the selected file:

filechooserdialog.nim
import gintro/[gtk4, gobject, gio]

proc fileChooserResponseCb(d: FileChooserDialog; id: int) =
  if ResponseType(id) == ResponseType.accept:
    let file = d.file
    echo file.getPath
  d.destroy

proc dialogCb(b: Button; w: ApplicationWindow) =
  let dialog = newFileChooserDialog("Open File", w, FileChooserAction.open)
  discard dialog.addButton("Open", ResponseType.accept.ord)
  discard dialog.addButton("Cancel", ResponseType.cancel.ord)
  dialog.connect("response", fileChooserResponseCb)
  dialog.show

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  window.title = "Application Main Window"
  let b = newButton("Open File Chooser Dialog")
  b.connect("clicked", dialogCb, window)
  window.setChild(b)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

Our main window contains a single button for which we connect the "clicked" signal with our dialocCB() callback function. That function calls the constructor function newFileChooserDialog() to create the file chooser dialog widget. We have to pass a title string, the dialogs’s parent window and the intended action as an enum to that function. The C function gtk_file_chooser_dialog_new() has additional a varargs parameter which allows to pass a set of button label texts and response ids, but that varargs parameter is not available for bindings generated by gobject-introspection. So we use the function addButton() to add an OK and a Cancel button with matching ids to the dialog, which will then appear in the dialogs’s header bar. Finally we connect a response callback function to the dialog widget. In that callback function we check the response id and print the path of the selected file.

Instead of the FileChooserAction.open we can use the enum value save to create a dialog to save a file or the enum value selectFolder when we intent the user to select a whole directory.

There are various cases in which we may need to use a GtkFileChooserDialog:

  • To select a file for opening we use FileChooserAction.open

  • To save a file for the first time we use FileChooserAction.save and may suggest a file name by use of setCurrentName()

  • To save a file under a different name we use FileChooserAction.save and set the existing file as default with setFile()

  • To choose a folder instead of a file we may use FileChooserAction.selectFolder

For most dialog boxes we can use our own custom response codes rather than the ones in GtkResponseType, but GtkFileChooserDialog assumes that its “accept”-type action, e.g. an “Open” or “Save” button, will have one of the following response codes: ResponseType.accept, ok, yes, apply.[8] To summarize, make sure you use a predefined response code when you use GtkFileChooserDialog to ensure proper operation.

References:

FontChooserDialog

fontchooserdialog

The GtkFontChooserDialog is used to ask the user for a custom font. We should need it only in rare cases, as generally most widgets should just use the users default font. But for applications like text editors, text processing programs or CAD tools we should provide a way to change fonts at program runtime dynamically. The font chooser dialog offers only the constructor function newFontChooserDialog(), but it implements the font chooser interface which provides the necessary functions to interact with fonts.

Functions like gtk_widget_override_font() or gtk_widget_modify_font() which allowed to set widget fonts directly from a PangoFontDescription has already been deprecated in GTK 3 and are not available in GTK 4 at all. So we have to use CSS when we really want to modify widget fonts.

The example program below creates a window with a label and a button. When the button is clicked a font chooser dialog window pups up and we can select a different font for the label widget:

fontchooserdialog.nim
import gintro/[gtk4, gobject, gio, pango]

type
  FancyLabel = ref object of Label
    cssPro: CssProvider
    toplevel: ApplicationWindow

proc fontChooserResponseCb(d: FontChooserDialog; id: int; l: FancyLabel) =
  if ResponseType(id) == ResponseType.ok:
    echo "Font: ", d.getFont
    echo "Features: ", d.getFontFeatures
    echo "Size: ", d.getFontSize
    let p: FontDescription = d.getFontDesc
    var data = "label {font-family: " & p.getFamily
    data.add("; font-size: " & $(p.getSize div pango.SCALE) & "pt")
    data.add("; font-style: " & $(p.getStyle))
    data.add("; font-weight: " & $(p.getWeight.ord))
    data.add(";}")
    echo data
    l.cssPro.loadFromData(data)
  d.destroy

proc dialogCb(b: Button; l: FancyLabel) =
  let dialog = newFontChooserDialog("Select a Font", l.topLevel)
  dialog.connect("response", fontChooserResponseCb, l)
  dialog.show

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  window.title = "Application Main Window"
  let box = newBox(Orientation.vertical)
  box.setMargin(10)
  let l = newLabel(FancyLabel, "Some fancy text")
  l.cssPro = newCssProvider()
  l.topLevel = window
  let styleContext = l.getStyleContext
  assert styleContext != nil
  addProvider(styleContext, l.cssPro, STYLE_PROVIDER_PRIORITY_USER)
  let b = newButton("Open Font Chooser Dialog")
  b.connect("clicked", dialogCb, l)
  box.append(l)
  box.append(b)
  window.setChild(box)
  window.show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

We subclass the GtkLabel widget and add two custom fields allowing us to store a CssProvider entity and a reference to our application main window. We need a reference to the main window in our dialog callback function to set the transient window for the font chooser dialog. In GTK 3 we may have used gtk_widget_get_toplevel() to get that transient window, but that function is not available in GTK 4 any more.

In the activate() procedure we create our window with a box widget, which contains a label and a button widget. We use the "clicked" signal to connect the button to our dialogCb() callback function, which then creates the font chooser dialog by a call of the newFontChooserDialog() constructor. The newFontChooserDialog() constructor gets the dialog’s title and the transient window as parameters.

While we had to add buttons to the file chooser dialog our self, the font chooser dialog contains a "Select" and a "Cancel" button with ids ResponseType.ok and ResponseType.cancel already. We use the "response" signal to connect the dialog to our fontChooserResponseCb() callback function, which then calls functions from the font chooser interface to print some information about the selected font and then constructs the CSS string which is used as parameter for the loadFromData() function. It may be surprising that passing the new CSS string to loadFromData() is enougth to update the label widget. Indeed it is not necessary to first remove the CssProvider, update it and add it again to the label’s StyleContext.

We style our label widget by use of CSS similar as we did it earlier for a Button widget. For that we have to create a CssProvider entity and to attach it to our label’s StyleContext. We keep a references to that CssProvider entity in a field of our label widget, so that we can call l.cssPro.loadFromData(data) in the fontChooserResponseCb() callback function to set the desired font attributes. As the font chooser dialog does not give us appropriate CSS strings to set the font directly, we have to first call getFontDesc() on the dialog object to get the PangoFontDescriptor, and then call various functions like getFamily() on the PangoFontDescriptor instance to get the needed data. From that data we then construct a string containing all needed CSS properties and then use loadFromData() to pass the data to the CssProvider entity. Such a constructed CSS string may look like

label {font-family: Source Serif Pro; font-size: 16pt; font-style: italic; font-weight: 700;}

Later in the book we will show how we can style the font of a GtkSourceView widget of a simple text editor program and how we can store the selected font permanently by use of GSettings.

References:


1. You may wonder why the gtk module itself has a numeric suffix, but other companion modules like glib and gobject do not. The reason for this is that the main libraries gtk and gdk are available each in version 2, 3 and 4, and are not backward compatible. But for the companion libs like glib, gio, gobject and some more only one version is available and is used for gtk3 and gtk4 together. For the gintro Nim bindings the gtk3 module was just called gtk for historic reasons.
2. C strings are plain character arrays terminated with \x00 to indicate its end, and in C the length of strings is not directly available but determined by searching for the terminating \x00 character. For long text strings that search has some costs, so it can be useful to provide the actual string length by a function out parameter.
3. Indeed we can avoid the backtics when we just use method call syntx like s.bind().
4. From the API docs it is not fully clear what happens when the button already has a child which is not of GtkLabel class — from the context we may assume that it is replaced.
5. That style was used in GTK2 and other GUI toolkits in early days, but is not recommended for GTK3 and GTK4 any more.
6. We will learn more about "drag and drop" and about tooltips later.
7. Creating an overload constructor with a flags parameter is not difficult, you may create an github issue if you really should need a dialog with buttons in the headerbar.
8. From the C API docs: This is because GtkFileChooserDialog must intercept responses and switch to folders if appropriate, rather than letting the dialog terminate — the implementation uses these known response codes to know which responses can be blocked if appropriate. And from a note of Mr. Bassi in the GTK forum: The file selection widget and dialog is a fairly sizeable finite state machine, and it uses response codes as part of the state selection. GtkFileChooserDialog will only recognize affirmative response identifiers for action widgets if they are in this list: GTK_RESPONSE_ACCEPT, GTK_RESPONSE_OK, GTK_RESPONSE_YES, GTK_RESPONSE_APPLY. As, internally, it has to deal with things like the modal overwrite confirmation dialog, or changing the selected folder during navigation, etc. (https://discourse.gnome.org/t/gtk4-gtkfilechooserdialog/5389)