From 447e2c4b24ac10671ee47a442ab4d96a15dfeef7 Mon Sep 17 00:00:00 2001 From: Stefan Salewski Date: Sun, 15 Sep 2019 20:42:08 +0200 Subject: [PATCH] remove .html again --- README.html | 3082 --------------------------------------------------- 1 file changed, 3082 deletions(-) delete mode 100644 README.html diff --git a/README.html b/README.html deleted file mode 100644 index dde01dc..0000000 --- a/README.html +++ /dev/null @@ -1,3082 +0,0 @@ - - - - - - - - -High level GTK3 (and GTK4) bindings for the Nim programming language - - - - - - - -
-
-
-
- - - - - -
- - -A more fancy copy of this document with dark source code background is available at GIntro README -
-
-
- - - - - -
- - -This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf and other Nim and GTK/Gnome developers. -The combinatorics module was kindly provided by R. Behrends. -
-
-
- - - - - -
- - -Starting with release 0.5.3 we do not generate field entries for objects and we do -not generate class structs and private objects. Also we stopped exporting the low level functions -like gtk_button_new(). For a real high-level binding we should not need these. If that is a serious -limitation for you, then use release 0.5.2 for now and create an github issue for your use case, we -will try to fix it, maybe undo these changes. Also starting with v0.5.3 we try to support array -parameters like TargetEntryArray, PageRangeArray and KeymapKeyArray. Use of these array parameters is rare, -if you will use functions with these parameters you may inspect the source code first, as the -code is auto-generated and still untested. -
-
-
- - - - - -
- - -Starting with release 0.5.0 we also support GTK4. GTK4 is still work in progress and not intended for -end users yet, but it is good to have it available for migration testing. For GTK4 we have a new module gsk, and -new versions of modules gtk, gdk and gdkX11, which are not backward compatible with the old once of GTK3. The other -modules can be used by GTK3 and GTK4 in parallel. Due to this fact we use a single nimble package which can be used for -GTK3 and GTK4 development. To archive this, we have named the new modules gtk4, gdk4 and gdkX114 — the -old once are named gtk, gdk and gdkX11 still. So for existing GTK3 software no code changes are necessary. -For GTK4 an example is provided — it imports gtk4 instead of gtk now, and instead of window.showAll() -window.show() is needed. More GTK4 examples may follow eventually, see GTK4 migration page at -https://developer.gnome.org/gtk4/stable/gtk-migrating-3-to-4.html. The gintro package tries to install -the GTK4 modules when GTK4 is available on the local computer and skips it if not available. -For successful detection of GTK4 the typelibs must be found. For example, if you have installed -GTK4 from sources on /opt/gtk as described in https://developer.gnome.org/gtk4/3.96/gtk-building.html, then -you may have to execute "export GI_TYPELIB_PATH=/opt/gtk/lib64/girepository-1.0" in your shell before you -do "nimble install gintro". Currently gtksourceview and vte is not available for GTK4. GTK4 provides a -official test program called gtk4-demo — of course that one should work fine on your box before you -consider testing Nim with GTK4. -
-
-
-

Nim is a modern universal programming language.

-
-
-

GTK, also known as the Gimp Tool Kit and now sometimes called Gnome Tool Kit, is a Graphical User Interface library.

-
-
- - - - - -
- - -Later we will insert at this location a nice picture of a fancy Nim GTK3 GUI. Such a picture is fine to attract users and indeed is a good motivation. -But such pictures are no real evidence for the quality of a GUI toolkit — the concrete example may look nice, while the toolkit -looks much worse in other environments and offers by far not all that what is needed in real life. -
-
-
-

While GTK was initially designed and advertised as cross platform GUI toolkit, it is currently mostly used on Linux and other Unix like operation systems. -Most Linux distributions include it, and some use it for their default desktop environment, often with the Gnome environment or other window managers. -While GTK2 applications like GIMP are still used on Windows, there seems to exist currently only very few GTK3 applications for Windows or MacOSX. -When you develop primary free open source software (FOSS) for Linux or other Unix like operating systems, then GTK3 is a good choice for you. With some -effort you should be even able to port your application to the proprietary Windows or MacOSX operating systems. But when your primary target platforms -are Windows and MacOSX and you desire a real native look and feel there, then you may find better suited ones in the Nim software repository. -Also, when you only need a minimal restricted GUI which is very easy to install on Windows and MacOSX, then you may find better suited packages -in the Nim package repository. Android OS is currently not supported by GTK at all.

-
-
- - - - - -
- - -At least for Windows 10 it seems to be not that hard to install GTK3 libraries, as was recently reported in -https://github.com/StefanSalewski/gintro/issues/24 by user zetashift: -
-
-
-
-
  Sketch of GTK3 install for Windows 10:
-  For the GTK libs I did according these instructions(https://www.gtk.org/download/windows.php):
-  Install MSYS2
-  In the msys2 cmd I entered:
-  pacman -S mingw-w64-x86_64-gtk3
-  Then for some other necessary depencies(girepository.dll) you need to do:
-  pacman -S mingw-w64-x86_64-python3-gobject
-
-  Additional, you have to install the separate GtkSourceView lib in a similar manner from
-  https://github.com/Alexpux/MINGW-packages/blob/master/mingw-w64-gtksourceview3/
-
-
-
-

While low level Nim bindings for GTK3 are already available since a few years, this one is an attempt to -provide real high level bindings with full type safety, full Garbage Collector (GC) support and an idiomatic -Application Programming Interface (API).

-
-
-

Currently there are at least 3 sources of GTK3 bindings for Nim:

-
- -
-

ngtk3 was the first attempt to provide GTK3 support for Nim. It contained single repositories for all the GTK related libraries and -was not supported by nimble package manager. It was created from GTK 3.20 headers and is now deprecated.

-
-
-

oldgtk3 is the port of ngtk3 to GTK 3.22 — joining all libraries and providing nimble support. Some people may still prefer -using oldgtk3. As it is generated with the Nim tool c2nim directly from the C header files without much manual intervention, -it should be complete and contain not that much bugs. Missing Garbage Collector support is generally not really a problem, as -widgets are generally put into containers and were automatically deleted together with its parents due to GTK’s reference counting.

-
-
-

Still there can be some demand for really high level bindings — so this gintro repository tries to provide them.

-
-
-

High level GTK3 bindings, as available for many other programming languages like C++, Python, Ruby or D already, -have these advantages:

-
-
-
    -
  • -

    full Garbage Collector or Destructor support — you should never have to free resources manually

    -
  • -
  • -

    Widgets are Nim objects, so inheritance and sub-classing can be used

    -
  • -
  • -

    full type safety — no needs for casts or other unsafe and dangerous operations

    -
  • -
-
-
-

These high level bindings are based on GObject-Introspection, an XML based database like interface description. Compared to the C header -files this description gives us more and deeper information about data types and function calls, for example ownership transfer of objects and -in or out direction of procedure variables, which makes writing the glue code much easier. -And it should work with minimal -modifications also for the upcoming GTK4.

-
-
-

Unfortunately there are also some drawbacks:

-
-
-
    -
  • -

    The Application Programming Interface (API) will be different from what is known from C API, so using C examples or C tutorials is not really straight forward

    -
  • -
  • -

    The high level source code will differ from available C examples, so there would be a big demand for tutorials

    -
  • -
  • -

    We need a lot of glue code, which has much room for bugs. So much testing is necessary.

    -
  • -
  • -

    There is some overhead due to indirect calls, leading to some code size increase and minimal -performance loss.

    -
  • -
-
-
- - - - - -
- - -The new package name is gintro, short for GObject-Introspection. The previous name was nim-gi, but the hyphen is deprecated for package names, as is the -nim prefix. -
-
-
-
-
-

Current state of these bindings

-
-
-

We are still in an early stage, but it is already more than a proof of concept. GTK and related libraries have many thousand of -callable functions and nearly as many data types. Testing all that is nearly impossible for a small team with limited resources. -The initial approach was to generate low level -bindings, which looked similar to the ones generated by the c2nim tool from the C headers. After that was done, we have associated all -the C structs and GObject data types with Nim proxy objects. A well defined relation between these proxy object and the low level C data types -should ensure fully automatic garbage collection. This is supported by smart type conversion, for example C strings returned by glib library -are assigned to newly created Nim strings, while the memory of the C strings is automatically freed. For most cases this seems to work. But there -exists a few more complicated cases, for example functions may return whole arrays of C strings or other non elementary data types, -or function arguments or results may be so called glists, -list structures of glib library. These cases can not be processed automatically but needs carefully manual investigations. And there may be still functions and data -types missing: GObject-Introspection query gives us many thousand lines of Nim interface code, and it is not really obvious if and what is missing. -Some functions and data types are missing for sure — at least some low level ones, which are considered unneeded for high level bindings by GObject-Introspection. -But maybe more is missing, we have to investigate that. Until now these bindings have been tested only for 64 bit Linux systems with GTK 3.22.

-
-
-

These basic libraries are already partly tested:

-
-
-

Gtk, Gdk, GLib, GObject, Gio, GdkPixbuf, GtkSource, Pango, PangoCairo, PangoFT2, GModule, Rsvg, fontconfig, freetype2, xlib, Atk, Vte, cairo

-
-
-

In best case it should be possible to add more GObject based libraries to this list without larger modifications of the generator source code. -Unfortunately the bindings for the cairo drawing library provided by GObject-Introspection was only a minimal stub — we have extend it manually.

-
-
-
-
-

How to try it out

-
-
-

Of course you will need a working Nim installation with a recent compiler version and you have to ensure that GTK and related libraries are installed on your system. For some Linux -distributions which provide mainly pre-compiled software you may have to also install some GTK related developer files.

-
-
-

With a recent nimble version (>= v0.8.10) you only have to type in a shell window:

-
-
-
-
nimble install gintro
-
-
-
- - - - - -
- - -Latest version of gintro package uses some files from oldgtk3 package for bootstrapping. We assume that -users of gintro generally are not interested in low level oldgtk3 package, so we try to download only 3 single files -from oldgtk3 package. That should work if wget or nimgrab executables are available. If it fails you should -get a longer error message which may help you to solve the issue. -
-
-
- - - - - -
- - -Nimble prepare should run for about 20 seconds, it compiles and executes the generator program gen.nim. -Unfortunately we can not guarantee that the generator command will be able to really build all the -desired modules. The built process highly depends on your OS and installed GTK version. For 64 bit Linux systems -with GTK 3.22 and all required dependencies installed it should work. For never GTK versions it may fail, when that GTK -release introduces for example new unknown data types like array containers. In that case manual fixes may be necessary. -The GObject-Introspection based built process generates bindings customized to the OS where the generator is executed, -so for older GTK releases or a 32 bit system different files are created. Later we may also provide pre-generated -files for various OS and GTK versions, but building locally is preferred when possible. -
-
-
-
-
-

A few basic examples

-
-
- - - - - -
- - -Currently we do not install the example programs. If you want to try them, you have to copy the source code of the -examples from https://github.com/StefanSalewski/gintro/tree/master/examples to your local computer, maybe to /tmp/gintro/examples directory. -
-
-
-

Then you can compile and run them from shell with commands like

-
-
-
-
cd /tmp/gintro/examples/
-nim c app0.nim
-./app0
-
-
-
-

or you may open the source files in your favorite Nim IDE or editor. Taking the source code from this Readme file is not -really recommended, as these source code listings may be not the latest versions.

-
-
-

GTK3 programs can use still the old GTK2 design, where you first initialize the GTK library, create your widgets and finally enter the GTK main loop. -This style is still used in many tutorials as in Zetcode tutorial or in the GTK book of A. Krause. -Or you can use the new GTK3 App style, this is generally recommended by newer original GTK documentation. -Unfortunately the GTK3 original documentation is mostly restricted to the GTK3 API documentation, which is generally very good, but makes -it not really easy for beginners to start with GTK. API docs and some basic introduction is available here:

-
- -
- - - - - -
- - -If you should decide to continue developing software with GTK, then you may consider installing the so called -devhelp tool. It gives you easy and fast access to the GTK API docs. For example, if you want to use a Button Widget in your -GUI and wants to learn more about related functions and signals, you just enter Button in that tool and are guided to -all the relevant information. -
-
-
-

We start with a minimal traditional old style example, which should be familiar to most of us:

-
-
-
t0.nim
-
-
# nim c t0.nim
-import gintro/[gtk, gobject]
-
-proc bye(w: Window) =
-  mainQuit()
-  echo "Bye..."
-
-proc main =
-  gtk.init()
-  let window = newWindow()
-  window.title = "First Test"
-  window.connect("destroy", bye)
-  window.showAll
-  gtk.main()
-
-main()
-
-
-
-

This is the traditional layout of GTK2 programs. When using this style then it is important to initialize the GTK library by calling gtk.init() -at the very beginning. Then we create the desired widgets, connect signals, show all widgets and finally enter the GTK main loop -by calling gtk.main. About connecting signals we will learn more soon, for now it is only important that we have to connect to -the destroy signal here to enable the user to terminate program execution by clicking the window close button.

-
-
-

Now a really minimal but complete App style example, which displays an empty window.

-
-
- - - - - -
- - -The source text of all these examples is contained in the examples directory. Unfortunately github -seems to not allow to include that sources directly into this document, so there may be minimal -differences between the source code displayed here and the sources in examples directory. -
-
-
-
app0.nim
-
-
# app0.nim -- minimal application style example
-# nim c app0.nim
-import gintro/[gtk, glib, gobject, gio]
-
-proc appActivate(app: Application) =
-  let window = newApplicationWindow(app)
-  window.title = "GTK3 & Nim"
-  window.defaultSize = (200, 200)
-  showAll(window)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-

In the main proc we create a new application and connect the activate signal to our activate proc, which then creates and displays -the still empty window.

-
-
- - - - - -
- - -We are importing modules gtk and gio. Initially both modules had a data type called Application (gtk.Application -extends indeed the gio.Application), so we would have to use module name prefixes, or we could import from gio only -what is really needed (from gio import …​) or use the form (import gio exept …​). But as gio.Application is generally -not needed often, we have no renamed gio.Application to GApplication. No more name clashes. -
-
-
-

Various ways to set widget parameters are supported — the number 1 to 6 refer to the comments below:

-
-
-
-
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)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1proc call syntax
2optional qualified with module name prefix
3method call syntax
4named parameters
5tupel assignment
6tupel assignment with named members
-
-
-

Well, that empty window is really not very interesting. The GTK and Gnome team provides some GTK examples -at https://developer.gnome.org/gnome-devel-demos/. -The C demos seems to be most actual and complete, -and are easy to port to Nim. So we start with these, -but if you are familiar with the other listed languages, then you can try to port them to Nim as well. -Let us start with https://developer.gnome.org/gnome-devel-demos/3.22/button.c.html.en as it is -still short and easy to understand, but shows already some interesting topics.

-
-
-
-NimGTK3Button -
-
-
-

The C code looks like this:

-
-
-
button.c
-
-
#include <gtk/gtk.h>
-
-/*This is the callback function. It is a handler function which
-reacts to the signal. In this case, it will cause the button label's
-string to reverse.*/
-static void
-button_clicked (GtkButton *button,
-                gpointer   user_data)
-{
-  const char *old_label;
-  char *new_label;
-
-  old_label = gtk_button_get_label (button);
-  new_label = g_utf8_strreverse (old_label, -1);
-
-  gtk_button_set_label (button, new_label);
-  g_free (new_label);
-}
-
-static void
-activate (GtkApplication *app,
-          gpointer        user_data)
-{
-  GtkWidget *window;
-  GtkWidget *button;
-
-  /*Create a window with a title and a default size*/
-  window = gtk_application_window_new (app);
-  gtk_window_set_title (GTK_WINDOW (window), "GNOME Button");
-  gtk_window_set_default_size (GTK_WINDOW (window), 250, 50);
-
-  /*Create a button with a label, and add it to the window*/
-  button = gtk_button_new_with_label ("Click Me");
-  gtk_container_add (GTK_CONTAINER (window), button);
-
-  /*Connecting the clicked signal to the callback function*/
-  g_signal_connect (GTK_BUTTON (button),
-                    "clicked",
-                    G_CALLBACK (button_clicked),
-                    G_OBJECT (window));
-
-  gtk_widget_show_all (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;
-}
-
-
-
-

Converting it to Nim is straight forward with some basic C and Nim knowledge, and Nim does not force us -to convert its shape into all the classes known from pure Object Orientated (OO) languages. We can either use the -Nim tool c2nim to help us with the conversion, or do it manually. Indeed c2nim can be very helpful by -converting C sources to Nim. Most of the time it works well. Personally I generally pre-process C files, for example -by removing too strange macros and defines, or by replacing strange constructs, like C `for loops, to simpler -ones like while loops. Then I apply c2nim to the C file and finally manually compare the result line by line and -fine tune the Nim code. But for this short source text we may do all that manually and finally get something like -this:

-
-
-
button.nim
-
-
# nim c button.nim
-import gintro/[gtk, glib, gobject, gio]
-
-proc buttonClicked (button: Button) =
-  button.label = utf8Strreverse(button.label, -1)
-
-proc appActivate (app: Application) =
-  let window = newApplicationWindow(app)
-  window.title = "GNOME Button"
-  window.defaultSize = (250, 50)
-  let button = newButton("Click Me")
-  window.add(button)
-  button.connect("clicked",  buttonClicked)
-  window.showAll
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard app.run
-
-main()
-
-
-
-

Again we have the basic shape already known from app0.nim example: Main proc creates the application, connect -to the activate signal and finally runs the application. When GTK launches the application and emits the activate signal, then -our activate proc is called, which creates a main window containing a button widget. That button is again connected with a -signal, in this case named clicked. That signal is emitted by GTK whenever that button is clicked with the mouse and results -in a call of our provided buttonClicked() proc. The procs connected to signals are called callbacks and generally got the widget -on which the signal was emitted as first parameter. They can also get a second optional parameter of arbitrary type — we will -see that in a later example. This callback here gets only the button itself as parameter, and it’s task is to reverse the -text displayed by the button. Not very interesting basically, but we are indeed using the glib function utf8Strreverse() -for this task. While that function internally works with cstrings, and in C we have to free the memory of the returned cstring, -in our Nim example that is done automatically by Nim’s Garbage Collector. When you compare our example carefully with the C code, -then you may notice a difference. The C code passes the window containing the button as an additional parameter to the -callback function, but that parameter is not really used. We simple ignore it here, as it is not used at all. -In one of the following examples you will learn how passing (nearly) arbitrary parameters in a type safe way is done. -Another difference is, that the C code returns an integer status value returned by g_application_run() to the OS. We -could do the same by using the quit() proc of Nim’s OS module, but as that would give us no additional benefit, we simply ignore it.

-
-
- - - - - -
- - -The command nim c sourcetext.nim generates an executable which contains code for runtime checks and debugging, -which increases executable size and decreases performance. -After you have tested your software carefully, you may give the additional parameter -d:release to avoid this. For the gcc backend -you may additional enable Link Time Optimization (LTO), which reduces executable size further. To enable LTO you may put -a nim.cfg file in your sources directory with content like -
-
-
-
-
path:"$projectdir"
-nimcache:"/tmp/$projectdir"
-gcc.options.speed = "-march=native -O3 -flto -fstrict-aliasing"
-
-
-
-

With that optimization, your executable sizes should be in the range of about 50 kB only!

-
-
-
-
-

Optional, type safe parameters for callbacks

-
-
-

The next example shows, how we can pass (nearly) arbitrary parameters to our connect procs. -We pass a string, an object from the stack, a reference to an object allocated on the heap -and finally a widget (in this case the application window itself, you may also try passing -another button). As the main window itself is a so called GTK bin and can contain only one -single child widget, we create a container widget, a vertical box in this case, fill that box with -some buttons, and add that box to the window.

-
-
-

Compile and start this example from the command line and watch what -happens when you click on the buttons.

-
-
-
connect_args.nim
-
-
# nim c connect_args.nim
-import gintro/[gtk, glib, gobject, gio]
-
-type
-  O = object
-    i: int
-
-proc b1Callback(button: Button; str: string) =
-  echo str
-
-proc b2Callback(button: Button; o: O) =
-  echo "Value of field i in object o = ", o.i
-
-proc b3Callback(button: Button; r: ref O) =
-  echo "Value of field i in ref to object O = ", r.i
-
-proc b4Callback(button: Button; w: ApplicationWindow) =
-  if w.title == "Nim with GTK3":
-    w.title = "GTK3 with Nim"
-  else:
-    w.title = "Nim with GTK3"
-
-proc appActivate (app: Application) =
-  var o: O
-  var r: ref O
-  new r
-  o.i = 1234567
-  r.i = 7654321
-  let window = newApplicationWindow(app)
-  let box = newBox(Orientation.vertical, 0)
-  window.title = "Parameters for callbacks"
-  let b1 = newButton("Nim with GTK3")
-  let b2 = newButton("Passing an object from stack")
-  let b3 = newButton("Passing an object from heap")
-  let b4 = newButton("Passing a Widget")
-  b1.connect("clicked",  b1Callback, "is much fun.")
-  b2.connect("clicked",  b2Callback, o)
-  b3.connect("clicked",  b3Callback, r)
-  b4.connect("clicked",  b4Callback, window)
-  box.add(b1)
-  box.add(b2)
-  box.add(b3)
-  box.add(b4)
-  window.add(box)
-  window.showAll
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard app.run
-
-main()
-
-
-
-

To prove type safety, we may modify one of the callback procs and watch the compiler output:

-
-
-
-
proc b1Callback(button: Button; str: int) =
-  discard # echo str
-
-
-
-
-
connect_args.nim(37, 5) template/generic instantiation from here
-gtk.nim(-15021, 10) Error: type mismatch: got (ref Button:ObjectType, string)
-but expected one of:
-proc b1Callback(button: Button; str: int)
-
-
-
-

It may be not always really obvious what the compiler wants to tell us, but at least we -are told that it got a string and expected an int.

-
-
-

Currently the connect function is realized by a Nim type safe macro. Connect accepts two or three -arguments — the widget, the signal name and the optional argument. When the optional argument -is a ref (reference to objects on the heap) then it is passed as a reference, otherwise a deep copy -of the argument is passed. For the above code this means, that r and the window variables are passed -as references, while the string and the stack object are deep copied. Currently it is not possible -to release the memory of passed arguments again. This should be no real problem, as in most -cases no arguments are passed at all, and when arguments are passed, then they are general -small in size like plain numbers or strings, or maybe references to widgets which could not be freed -at all, as they are part of the GUI. Later we may add more variants of that connect macro.

-
-
- - - - - -
- - -Navigation can be hard for beginners. You may have basic knowledge of GTK and want -to build a GUI for your application. But how to find what you need. Well, we offer no separate -automatically generated API documentation currently, as that is not really helpful. In most cases -it is easy to just guess Nim symbol names, proc parameters and all that. Using a smart editor -with good nimsuggest support further supports navigation — for example NEd shows us -all the needed proc parameters when we move the cursor on a proc name, or we press Ctrl+W and jump -to the definition of that symbol. For unknown stuff the original C function name is often a good starting point. -Assume you don’t know much about GTK’s buttons, but you know that you want to have a button in -your GUI application. GTK generally offers generator functions containing the string new in their name. -So it is easy to guess that there exists a C function named gtk_button_new. That name is also -contained in the bindings files, in this case in gtk.nim. So we open that file in a text editor and search for -that term. So it is really easy to find first starting points for related procs and data types. Most data types -are located near by their related functions, so you should be able to find all relevant information fast. -Remember the GTK devhelp tool, and use also grep or the nimgrep variant. -
-
-
-
-
-

Extending or sub-classing Widgets

-
-
-

I may occur that we want to attach additional information to GTK widgets -by extending or subclassing them. Doing this is supported -by providing for each widget class not only a corresponding new() proc which returns -the newly created widget, but also -a init() proc, which gets an uninitialized variable of the (extended) widget type as argument and -initializes that variable with a newly created -GTK widget . Initializing the added fields is -done separately by the user. The following code shows a GTK button, which is -extended with a counter member field. That counter is decreased for -each button click. The amount of decrease (5) is passed to the callback as a int parameter.

-
-
-
count_button.nim
-
-
# nim c count_button.nim
-import gintro/[gtk, glib, gobject, gio]
-
-type
-  CountButton = ref object of Button
-    counter: int
-
-proc buttonClicked (button: CountButton; decrement: int) =
-  dec(button.counter, decrement)
-  button.label = "Counter: " & $button.counter
-  echo "Counter is now: ", button.counter
-
-proc appActivate (app: Application) =
-  var button: CountButton
-  let window = newApplicationWindow(app)
-  window.title = "Count Button"
-  initButton(button, "Counting down from 100 by 5")
-  button.counter = 100
-  window.add(button)
-  button.connect("clicked",  buttonClicked, 5)
-  window.showAll
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard app.run
-
-main()
-
-
-
-

In this example we have to define our new widget type first, then we have to -declare a variable of that type and pass that variable to the init() proc.

-
-
-
-
-

CSS styles, GErrors and Exceptions

-
-
-
-NimGTK3Label -
-
-
-

Often GTK beginners ask how one can apply custom styles to GTK widgets, for example custom colors. -While in most cases the use of custom colors gives just ugly results, as the custom colors generally do -not match well with the default color scheme, it is good to know how we can do it. For GTK3 styles are -applied to widgets by using Cascading Style Sheets (CSS). You may find C example code similar to this:

-
-
-
label.c
-
-
// https://stackoverflow.com/questions/30791670/how-to-style-a-gtklabel-with-css
-// gcc `pkg-config gtk+-3.0 --cflags` test.c -o test `pkg-config --libs gtk+-3.0`
-#include <gtk/gtk.h>
-int main(int argc, char *argv[]) {
-    gtk_init(&argc, &argv);
-    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-    GtkWidget *label = gtk_label_new("Label");
-    GtkCssProvider *cssProvider = gtk_css_provider_new();
-    char *data = "label {color: green;}";
-    gtk_css_provider_load_from_data(cssProvider, data, -1, NULL);
-    gtk_style_context_add_provider(gtk_widget_get_style_context(window),
-                                   GTK_STYLE_PROVIDER(cssProvider),
-                                   GTK_STYLE_PROVIDER_PRIORITY_USER);
-    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
-    gtk_container_add(GTK_CONTAINER(window), label);
-    gtk_widget_show_all(window);
-    gtk_main();
-}
-
-
-
-

Converting that to Nim is again straight forward:

-
-
-
label.nim
-
-
# nim c label.nim
-import gintro/[gtk, glib, gobject, gio]
-
-proc appActivate(app: Application) =
-  let window = newApplicationWindow(app)
-  let label = newLabel("Yellow text on green background")
-  let cssProvider = newCssProvider()
-  let data = "label {color: yellow; background: green;}"
-  #discard cssProvider.loadFromPath("doesnotexist")
-  discard cssProvider.loadFromData(data)
-  let styleContext = label.getStyleContext
-  assert styleContext != nil
-  addProvider(styleContext, cssProvider, STYLE_PROVIDER_PRIORITY_USER)
-  window.add(label)
-  showAll(window)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-

For this example we create a plain label widget with some text. To colorize it, we generate a -CssProvider and load it with a textual description of our desired colors. Then we extract the -style context from the label and add our CssProvider to it.

-
-
-

The last parameter of the C function gtk_css_provider_load_from_data() is of type GError and can -be used in C code to detect runtime errors. The C code above just passes NULL to ignore this error. -For Nim we map that GError argument to exceptions. To test what happens in Nim when an GError would -report an error condition, you may uncomment function loadFromPath() in the code above. As the specified path -does not exist, we should get an exception with a message telling us the problem. Of course in your real -code you may catch such exceptions with Nim’s try: blocks. (You may also modify the data variable above to -an illegal CSS statement — if the statement is seriously wrong, then you should get an exception from -loadFromData().

-
-
-
-
-

GTK Builder — user interfaces created with the glade tool

-
-
-

As C code can be very verbose, some people prefer outsourcing the GUI layout -in XML files which can be created and modified with the glade GUI creator program. -For high level languages like Python or Nim the program source code is generally -short and clean, so that use of XML files may not have much benefit. But of course -we can use GTK builder from Nim. We follow the example from -https://developer.gnome.org/gtk3/stable/ch01s03.html -but we modify it to use the new GTK3 app style: For the XML file we have to change only -class="GtkWindow" into class="GtkApplicationWindow". Our Nim program has -the well known application shape, with one addition: We have to -explicitly set the application for the main window. Of course you can also -use the traditional program structure with Nim and Builder, for that case -you can straight follow the linked page or other examples. Here is the XML file and the Nim code:

-
-
-
builder.ui
-
-
<interface>
-  <object id="window" class="GtkApplicationWindow">
-    <property name="visible">True</property>
-    <property name="title">Grid</property>
-    <property name="border-width">10</property>
-    <child>
-      <object id="grid" class="GtkGrid">
-        <property name="visible">True</property>
-        <child>
-          <object id="button1" class="GtkButton">
-            <property name="visible">True</property>
-            <property name="label">Button 1</property>
-          </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">0</property>
-          </packing>
-        </child>
-        <child>
-          <object id="button2" class="GtkButton">
-            <property name="visible">True</property>
-            <property name="label">Button 2</property>
-          </object>
-          <packing>
-            <property name="left-attach">1</property>
-            <property name="top-attach">0</property>
-          </packing>
-        </child>
-        <child>
-          <object id="quit" class="GtkButton">
-            <property name="visible">True</property>
-            <property name="label">Quit</property>
-          </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">1</property>
-            <property name="width">2</property>
-          </packing>
-        </child>
-      </object>
-      <packing>
-      </packing>
-    </child>
-  </object>
-</interface>
-
-
-
-
builder.nim
-
-
 https://developer.gnome.org/gtk3/stable/ch01s03.html
-# builder.nim -- application style example using builder/glade xml file for user interface
-# nim c builder.nim
-import gintro/[gtk, glib, gobject, gio]
-
-proc hello(b: Button; msg: string) =
-  echo "Hello", msg
-
-proc quitApp(b: Button; app: Application) =
-  echo "Bye"
-  quit(app)
-
-proc appActivate(app: Application) =
-  let builder = newBuilder()
-  discard builder.addFromFile("builder.ui")
-  let window = builder.getApplicationWindow("window")
-  window.setApplication(app)
-  var button = builder.getButton("button1")
-  button.connect("clicked", hello, "")
-  button = builder.getButton("button2")
-  button.connect("clicked", hello, " again...")
-  button = builder.getButton("quit")
-  button.connect("clicked", quitApp, app)
-  #showAll(window)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-

For each builder component gintro provides a typesafe access proc like -getApplicationWindow() and getButton() in this example.

-
-
-

Generally it is possible to use resource files merged with the executable program -instead of an external XML files, we have to investigate how we can do that in Nim. -And it may be possible to connect the signal handlers to handler procs from within -the XML file — this is also work in progress…​

-
-
-
-
-

GAction

-
-
-

GAction represents a single named action and is for GTK3 the prefered way to do -user interactions. GAction works with button, menus and keyboard shortcuts.

-
-
-

The following example is based on

-
- -
-
gaction.nim
-
-
# https://wiki.gnome.org/HowDoI/GAction
-# nim c gaction.nim
-import gintro/[gtk, 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.add(button)
-  button.setActionName("win.save")
-  setAccelsForAction(app, "win.save", "<Control><Shift>S")
-  showAll(window)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-

GtkApplicationWindow provides an interface to GActionMap. As -the interface itself and the interface provider are defined in different modules, -automatic conversion is not possible, so we have to convert the ApplicationWindow -to ActionMap. (We could use a converter to do the conversion for us, but as -these conversions are rare, and because gintro use no converters at all still, we use -an explicit proc.) The use of cstringArray as third parameter for proc setAccelsForAction() -is a bit ugly, we have to fix that later.

-
-
-
-
-

GMenu with GActions

-
-
-

The following example shows how we can define GActions and bind them to Menus, Buttons -and Keyboard shortcuts. Examples for stateless actions (quit), for toggle actions (spellcheck) -and for statefull actions (text justify) are provided.

-
-
-

Note that the following code is not a direct translation of an existing example, but -a collections of informations from various sources, so -it may contain bugs or not fully optimal code.

-
- -
-

We can easily modify the above example to get the more modern look with -a HeaderBar and the "Gears" MenuButtons:

-
-
-
gearsmenu.nim
-
-
# https://developer.gnome.org/glib/stable/glib-GVariant.html
-# https://developer.gnome.org/glib/stable/glib-GVariantType.html
-# https://wiki.gnome.org/HowDoI/GMenu
-# https://wiki.gnome.org/HowDoI/GAction
-# https://developer.gnome.org/gnome-devel-demos/stable/menubutton.c.html.en
-# nim c gearsmenu.nim
-import gintro/[gtk, glib, gobject, gio]
-import strformat
-
-# https://github.com/GNOME/glib/blob/master/gio/tests/gapplication-example-actions.c
-proc activateToggleAction(action: SimpleAction; parameter: Variant; app: Application) =
-  app.hold # hold/release taken over from C example, there may be reasons...
-  block:
-    echo fmt"action {action.name} activated"
-    let state: Variant = action.state
-    let b = state.getBoolean
-    action.state = newVariantBoolean(not b)
-    echo fmt"state change {b} -> {not b}"
-  app.release
-
-proc activateStatefulAction(action: SimpleAction; parameter: Variant; app: Application) =
-  app.hold
-  block:
-    echo fmt"action {action.name} activated"
-    let state: Variant = action.state
-    var l: uint64
-    let oldState = state.getString(l) # yes uint64 parameter is a bit ugly
-    let newState = parameter.getString(l)
-    action.state = newVariantString(newState)
-    echo fmt"state change {oldState} -> {newState}"
-  app.release
-
-proc quitProgram(action: SimpleAction; parameter: Variant; app: Application) =
-  quit(app)
-
-proc appStartup(app: Application) =
-  echo "appStartup"
-  let quit = newSimpleAction("quit") # here we create the actions for whole app
-  connect(quit, "activate", quitProgram, app)
-  app.addAction(quit)
-
-proc appActivate(app: Application) =
-  echo "appActivate"
-  let window = newApplicationWindow(app)
-  # window.title = "GTK3 App with Headerbar and Gears Menu" # unused due to HeaderBar
-  window.defaultSize = (500, 200)
-  window.position = WindowPosition.center
-
-  let menu = gio.newMenu() # root of all menus
-  block: # plain stateless menu
-    let subMenu = gio.newMenu()
-    menu.appendSubMenu("Application", submenu)
-    # let section = gio.newMenu() # no separating section needed here
-    # submenu.appendSection(nil, section)
-    # section.append("Quit", "app.quit")
-    submenu.append("Quit", "app.quit")
-
-  block: #stateful menu with radio items
-    let subMenu = gio.newMenu()
-    menu.appendSubMenu("Layout", submenu)
-    let subMenu2 = gio.newMenu()
-    submenu.appendSubMenu("justify", submenu2)
-    let section = gio.newMenu()
-    submenu2.appendSection(nil, section)
-    section.append("left", "win.justify::left")
-    section.append("center", "win.justify::center")
-    section.append("right", "win.justify::right")
-
-  block: # and finally a toggle menu
-    let subMenu = gio.newMenu()
-    menu.appendSubMenu("Spelling", submenu)
-    let section = gio.newMenu()
-    submenu.appendSection(nil, section)
-    section.append("Check", "win.toggleSpellCheck")
-
-  let headerBar = newHeaderBar()
-  headerBar.setShowCloseButton
-  headerBar.setTitle("Title")
-  headerBar.setSubtitle("Subtitle")
-  window.setTitlebar (headerBar)
-
-  let menubar = newMenuButton()
-  # menubar.setDirection(ArrowType.none) # show the gears Icon
-  # let image = newImageFromIconName("open-menu-symbolic", IconSize.menu.ord)
-  let image = newImageFromIconName("document-save", IconSize.dialog.ord) # dialog is really big!
-  menubar.setImage(image) # this is only an example for a custom image
-  # menubar.setIconName("open-menu-symbolic") # only gtk4
-  headerBar.packEnd(menubar)
-  menubar.setMenuModel(menu)
-
-  block: # creat the window related actions
-    let v = newVariantBoolean(true)
-    let spellCheck = newSimpleActionStateful("toggleSpellCheck", nil, v)
-    connect(spellCheck, "activate", activateToggleAction, app)
-    window.actionMap.addAction(spellCheck)
-  block:
-    let v = newVariantString("left") # default value and
-    let vt = newVariantType("s") # string (value type)
-    let justifyAction = newSimpleActionStateful("justify", vt, v)
-    connect(justifyAction, "activate", activateStatefulAction, app)
-    window.actionMap.addAction(justifyAction)
-  let button = newButton()
-  button.label = "Justify Center"
-  button.setDetailedActionName("win.justify::center")
-  #button.setActionName("app.quit") # for a stateless action
-  setAccelsForAction(app, "win.justify::right", "<Control><Shift>R")
-  window.add(button)
-  showAll(window)
-
-proc main =
-  let app = newApplication("app.example")
-  connect(app, "startup", appStartup)
-  connect(app, "activate", appActivate)
-  echo fmt"GTK Version {majorVersion()}.{minorVersion()}.{microVersion()}"
-  let status = run(app)
-  quit(status)
-
-main()
-
-
-
-

While in the previous example we create only a single menu instance in proc appStartup() -for all of our application windows, here we create a new menu for all of our instances -in proc appActivate(). That seems to work fine, so I assume it is correct.

-
-
-
-
-

GSettings

-
-
-

GSettings provides a convenient way to permanently storing configuration data, -and to bind them to properties of widgets.

-
- -
-

For using GSettings in our own programs, we have first to create a XML file -which defines names and type of each configuration entry, and additional -provides default value 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). For a real application program -we would install the configuration on our computer — unfortunately we -would need root access for this. We could do it this way:

-
-
-
-
# For making gsettings available system wide one method is, as root
-# https://developer.gnome.org/gio/stable/glib-compile-schemas.html
-# echo $XDG_DATA_DIRS
-# /usr/share/gnome:/usr/local/share:/usr/share:/usr/share/gdm
-# cd /usr/local/share/glib-2.0/schemas
-# cp test.gschema.xml .
-# glib-compile-schemas .
-#
-
-
-
-

For testing there is an easier method available:

-
-
-

Create a directory and copy the xml file and the test program below into it.

-
-
-

Then do, as ordinary user:

-
-
-
-
glib-compile-schemas .
-nim c gsettings.nim
-GSETTINGS_SCHEMA_DIR="." ./gsettings
-
-
-
-

This is the xml file and the test program:

-
-
-
test.gschema.xml
-
-
<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>
-
-
-
-
gsettings.nim
-
-
# gsettings.nim -- basic use of gsettings
-# nim c gsettings.nim
-# https://blog.gtk.org/2017/05/01/first-steps-with-gsettings/
-# https://mail.gnome.org/archives/gtk-list/2016-December/msg00003.html
-import gintro/[gtk, glib, gobject, gio]
-
-# unused
-proc toggle(b: CheckButton) =
-  echo b.active
-  let s = newSettings("org.gnome.Recipes")
-  discard s.setBoolean("like-nim", b.active)
-
-proc appActivate(app: Application) =
-  let window = newApplicationWindow(app)
-  window.title = "GTK3, Nim and GSettings"
-  window.defaultSize = (200, 200)
-  let b = newCheckButton()
-  b.halign = Align.center
-  b.label = "I like Nim"
-  #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.get, SettingsBindFlag.set})
-  window.add(b)
-  showAll(window)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-

The command "glib-compile-schemas ." compiles all schemas in the current directory. And -"GSETTINGS_SCHEMA_DIR="." ./gsettings" launches our test program with the environment -variable GSETTINGS_SCHEMA_DIR pointing to the current directory, containing the compiled schema.

-
-
-

Note that a system tool with same name as our test program exists — that one can be used -to get or set configuration data — for example you may query the current state of field -"like-nim" with

-
-
-
-
gsettings --schemadir "." get org.gnome.Recipes like-nim
-
-
-
-

Or test program first creates a window with a check button. Then our settings file is -opened and we print the current value of the boolean variable. After that the -bind procedure binds the active property (checkmark state) of our widget to the -"like-nim" entry of our settings file. The result of this binding is, that -our checkmark state is automatically made persistent, that is when we terminate -and restart our test program, the checkmark will have the last state again.

-
-
-

These bindings works for booleans, integers, floats, strings. The type of the property of the -widget must be identical with the corresponding type of the entry in the settings xml file.

-
-
-

On Linux you may permanently set the gsetting directory by adding the statement

-
-
-
-
export GSETTINGS_SCHEMA_DIR="pathToMyProg"
-
-
-
-

to your .bashrc file — of course after replacing pathToMyProg with the actual path.

-
-
-

For more informations about gsettings see

-
- - -
-
-
-

Drawing with Cairo graphics library

-
-
-

The next example shows how we can use the cairo graphics library for drawing on a DrawingArea widget, -and at the same time uses glib timeoutAdd() function to create a timer which periodically calls the -drawing function to create some animations. The code is based on a recent post to the cairo mailing list -and shows a sine wave which is continuously moving to the left.

-
-
- - - - - -
- - -The gobject-introspection generated cairo module was only a minimal stub, because cairo -library does not really support introspection. Now we are using a cairo module which is generated -directly from the cairo C header files with the tool c2nim and then modified to support a high level -API. -
-
-
-
cairo_anim.nim
-
-
# https://lists.cairographics.org/archives/cairo/2016-October/027791.html
-# Nim version of that plain cairo animation example
-
-import gintro/[gtk, glib, gobject, gio, cairo]
-import math
-
-const
-  NumPoints = 1000
-  Period = 100.0
-
-proc invalidateCb(w: Widget): bool =
-  queueDraw(w)
-  return SOURCE_CONTINUE
-
-proc sineToPoint(x, width, height: int): float =
-  math.sin(x.float * math.TAU / Period) * height.float * 0.5 + height.float * 0.5
-
-proc drawingAreaDrawCb(widget: DrawingArea; context: Context): bool =
-  var redrawNumber {.global.} : int
-  let width = getAllocatedWidth(widget)
-  let height = getAllocatedHeight(widget)
-  for i in 1 ..< NumPoints:
-    context.lineTo(i.float , sineToPoint(i + redrawNumber, width, height))
-  context.stroke
-  inc(redrawNumber)
-  return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
-
-proc appActivate(app: Application) =
-  let window = newApplicationWindow(app)
-  window.title = "Drawing example"
-  window.defaultSize = (400, 400)
-  let drawingArea = newDrawingArea()
-  window.add(drawingArea)
-  showAll(window)
-  discard timeoutAdd(1000 div 60, invalidateCb, drawingArea)
-  connect(drawingArea, "draw", drawingAreaDrawCb)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-
-
-

A simple ListView example

-
-
-
-NimGTK3ListView -
-
-
-

Recently someone reported about some problems porting a GTK2 application to Nim GTK3, so I will give a small example -which may help using ListViews and TreeViews. These two widget types are the most complicated widget types in GTK — I can remember that I had some trouble myself when I used Ruby-GTK some years ago. As I can currently not remember -details about use of ListView widgets, I decided to take an example code from zetcode.com as starting point. Of course -porting is straight forward, but when I tried to compile the result I noticed some bugs and restrictions of current -gintro package. Of course not really surprising, as the package is not really tested yet. I will try to fix these bugs later. -First problem is, that we store a ListStore as model in our TreeView, and we need to extract that ListStore from the TreeView -for some operations. But module gtk.nim offers currently only a function to extract the model itself, which is of type TreeModel. -In the C code an upcast is used to get the ListStore from the retrieved TreeModel. To avoid casting in our Nim code, I have just copied -the getModel() proc and modified it to return a ListStore. Second problem was, that module gio export a ListStore datatype also. -To avoid prefixing all ListStore types with gtk prefix, I excluded gio.ListStore from import list. And finally a real bug: -Proc newListStore() expects currently a plain pointer as last parameter, while we know that it should be the address of a list of GTypes. -So we have to use an ugly cast for now. For populating the ListStore currently GValues are used. That is not very convenient, and -for that we need the correct GType of our string list. In C one would use the macro G_TYPE_STRING, which is not provided by -gobject-introspection. So we use typeFromName() to get the correct GType, which works fine when we know that the string name is "gchararray". -Later we will provide a higher level function for this process.

-
-
-

I will try to give more and better explained ListView and TreeView examples later…​

-
-
-
listview.nim
-
-
# http://zetcode.com/gui/gtk2/gtktreeview/
-# dynamiclistview.c
-
-import gintro/[glib, gobject, gtk]
-import gintro/gio except ListStore
-
-const
-  LIST_ITEM = 0
-  N_COLUMNS = 1
-
-var list: TreeView
-
-# this is copied from gtk.nim
-#proc getModel*(self: TreeView): TreeModel =
-#  new(result)
-#  result.impl = gtk_tree_view_get_model(cast[ptr TreeView00](self.impl))
-
-proc getListStore(self: TreeView): ListStore =
-  new(result)
-  result.impl = gtk_tree_view_get_model(cast[ptr TreeView00](self.impl))
-
-proc appendItem(widget: Button; entry: Entry) =
-  var
-    val: Value
-    iter: TreeIter
-  let store = getListStore(list)
-  let gtype = typeFromName("gchararray")
-  discard gValueInit(val, gtype)
-  gValueSetString(val, entry.text)
-  store.append(iter)
-  store.setValue(iter, LIST_ITEM, val)
-  entry.text = ""
-
-proc removeItem(widget: Button; selection: TreeSelection) =
-  var
-    ls: ListStore
-    iter: TreeIter
-  let store = getListStore(list)
-  if not store.getIterFirst(iter):
-      return
-  if getSelected(selection, ls, iter):
-    discard store.remove(iter)
-
-proc onRemoveAll(widget: Button; selection: TreeSelection) =
-  var
-    iter: TreeIter
-  let store = getListStore(list)
-  if not store.getIterFirst(iter):
-    return
-  clear(store)
-
-proc initList(list: TreeView) =
-  let renderer = newCellRendererText()
-  let column = newTreeViewColumn()
-  column.title = "List Item"
-  column.packStart(renderer, true)
-  column.addAttribute(renderer, "text", LIST_ITEM)
-  discard list.appendColumn(column)
-  let gtype = typeFromName("gchararray")
-  let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim
-  list.setModel(store)
-
-proc appActivate(app: Application) =
-  let
-    window = newApplicationWindow(app)
-    sw = newScrolledWindow()
-    hbox = newBox(Orientation.horizontal, 5)
-    vbox = newBox(Orientation.vertical, 0)
-    add = newButton("Add")
-    remove = newButton("Remove")
-    removeAll = newButton("Remove All")
-    entry = newEntry()
-  window. title = "List view"
-  window.position = WindowPosition.center
-  window.borderWidth = 10
-  window.setSizeRequest(370, 270)
-  list = newTreeView()
-  sw.add(list)
-  sw.setPolicy(PolicyType.automatic, PolicyType.automatic)
-  sw.setShadowType(ShadowType.etchedIn)
-  list.setHeadersVisible(false)
-  vbox.packStart(sw, true, true, 5)
-  entry.setSizeRequest(120, -1)
-  hbox.packStart(add, false, true, 3)
-  hbox.packStart(entry, false, true, 3)
-  hbox.packStart(remove, false, true, 3)
-  hbox.packStart(removeAll, false, true, 3)
-  vbox.packStart(hbox, false, true, 3)
-  window.add(vbox)
-  initList(list)
-  let selection = getSelection(list)
-  connect(add, "clicked", listview.appendItem, entry)
-  connect(remove, "clicked", listview.removeItem, selection)
-  connect(removeAll, "clicked", listview.onRemoveAll, selection)
-  showAll(window)
-
-proc main =
-  let app = newApplication("org.gtk.example")
-  connect(app, "activate", appActivate)
-  discard run(app)
-
-main()
-
-
-
-
-
-

A ListView example with CSS styling

-
-
-

Recently C. Eric Cashon provided this example at https://discourse.gnome.org/t/gtk-treeview-cell-color-change/1750/3

-
-
-

I will show his original code here too, so we can compare it better with the Nim version. -We see that Nim code has currently some disadvantages still, for example we have no -varargs procs implemented, so setting of properties and attributes is done using GValues, -which is typesafe, but not really compact. That is not too bad, but we may consider -creating macros to support a more dense, but still typesafe way similar to C’s varargs functions.

-
-
-
cell_color1.c
-
-
// gcc -Wall cell_color1.c -o cell_color1 `pkg-config --cflags --libs gtk+-3.0`
-// https://discourse.gnome.org/t/gtk-treeview-cell-color-change/1750/4
-// C. Eric Cashon
-
-#include<gtk/gtk.h>
-
-enum
-{
-   ID,
-   PROGRAM,
-   COLOR1,
-   COLOR2,
-   COLUMNS
-};
-
-int main(int argc, char *argv[])
-  {
-    gtk_init(&argc, &argv);
-
-    GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
-    gtk_window_set_title(GTK_WINDOW(window), "Select Cell");
-    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
-    gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
-    gtk_container_set_border_width(GTK_CONTAINER(window), 20);
-    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
-
-    GtkTreeIter iter;
-    GtkListStore *store=gtk_list_store_new(COLUMNS, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
-    gtk_list_store_append(store, &iter);
-    gtk_list_store_set(store, &iter, ID, 0, PROGRAM, "Gedit", COLOR1, "DarkCyan", COLOR2, "cyan", -1);
-    gtk_list_store_append(store, &iter);
-    gtk_list_store_set(store, &iter, ID, 1, PROGRAM, "Gimp", COLOR1,  "LightSlateGray", COLOR2, "cyan", -1);
-    gtk_list_store_append(store, &iter);
-    gtk_list_store_set(store, &iter, ID, 2, PROGRAM, "Inkscape", COLOR1, "DarkCyan", COLOR2, "cyan", -1);
-    gtk_list_store_append(store, &iter);
-    gtk_list_store_set(store, &iter, ID, 3, PROGRAM, "Firefox", COLOR1, "LightSlateGray", COLOR2, "cyan", -1);
-    gtk_list_store_append(store, &iter);
-    gtk_list_store_set(store, &iter, ID, 4, PROGRAM, "Calculator", COLOR1, "DarkCyan", COLOR2, "cyan", -1);
-    gtk_list_store_append(store, &iter);
-    gtk_list_store_set(store, &iter, ID, 5, PROGRAM, "Devhelp", COLOR1, "LightSlateGray", COLOR2, "cyan", -1);
-
-    GtkWidget *tree=gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
-    gtk_widget_set_hexpand(tree, TRUE);
-    gtk_widget_set_vexpand(tree, TRUE);
-    g_object_set(tree, "activate-on-single-click", TRUE, NULL);
-
-    GtkTreeSelection *selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
-    gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
-
-    GtkCellRenderer *renderer1=gtk_cell_renderer_text_new();
-    g_object_set(renderer1, "editable", FALSE, NULL);
-
-    GtkCellRenderer *renderer2=gtk_cell_renderer_text_new();
-    g_object_set(renderer2, "editable", TRUE, NULL);
-
-    //Bind the COLOR column to the "cell-background" property.
-    GtkTreeViewColumn *column1=gtk_tree_view_column_new_with_attributes("ID", renderer1, "text", ID, "cell-background", COLOR1, NULL);
-    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column1);
-    GtkTreeViewColumn *column2 = gtk_tree_view_column_new_with_attributes("Program", renderer2, "text", PROGRAM, "cell-background", COLOR2, NULL);
-    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column2);
-
-    GtkWidget *grid=gtk_grid_new();
-    gtk_grid_attach(GTK_GRID(grid), tree, 0, 0, 1, 1);
-
-    gtk_container_add(GTK_CONTAINER(window), grid);
-
-    gchar *css_string=g_strdup("treeview{background-color: rgba(0,255,255,1.0); font-size:30pt} treeview:selected{background-color: rgba(255,255,0,1.0); color: rgba(0,0,255,1.0);}");
-    GError *css_error=NULL;
-    GtkCssProvider *provider=gtk_css_provider_new();
-    gtk_css_provider_load_from_data(provider, css_string, -1, &css_error);
-    gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
-    if(css_error!=NULL)
-      {
-        g_print("CSS loader error %s\n", css_error->message);
-        g_error_free(css_error);
-      }
-    g_object_unref(provider);
-    g_free(css_string);
-
-    gtk_widget_show_all(window);
-
-    gtk_main();
-    return 0;
-  }
-
-
-
-

And this is the Nim version, created with c2nim and some manual tuning:

-
-
-
css_colored_listview.nim
-
-
# nim c css_colored_listview.nim
-import gintro/[gtk, glib, gobject]
-import gintro/gdk except Window # there is a problem with gdk.Window -- we have to investigate!
-const # maybe we should use Nim's enum here?
-  Id = 0
-  Program = 1
-  Color1 = 2
-  Color2 = 3
-  Columns = 4
-
-proc bye(w: Window) =
-  mainQuit()
-  echo "Bye..."
-
-proc toStringVal(s: string): Value =
-  let gtype = typeFromName("gchararray")
-  discard init(result, gtype)
-  setString(result, s)
-
-proc toUIntVal(i: int): Value =
-  let gtype = typeFromName("guint")
-  discard init(result, gtype)
-  setUint(result, i)
-
-proc toBoolVal(b: bool): Value =
-  let gtype = typeFromName("gboolean")
-  discard init(result, gtype)
-  setBoolean(result, b)
-
-# we need the following two procs for now -- later we will not use that ugly cast...
-proc typeTest(o: gobject.Object; s: string): bool =
-  let gt = g_type_from_name(s)
-  return g_type_check_instance_is_a(cast[ptr TypeInstance00](o.impl), gt).toBool
-
-proc listStore(o: gobject.Object): gtk.ListStore =
-  assert(typeTest(o, "GtkListStore"))
-  cast[gtk.ListStore](o)
-
-proc updateRow(renderer: CellRendererText; path: cstring; newText: cstring; tree: TreeView) =
-  var iter: TreeIter
-  var value: Value
-  let gtype = typeFromName("gchararray")
-  discard init(value, gtype)
-  let store = listStore(tree.getModel())
-  value.setString(newText)
-  let treePath = newTreePathFromString(path)
-  discard store.getIter(iter, treePath)
-  store.setValue(iter, 1, value)
-
-# we use the old gtk style with init() as is used in the C original -- maybe better use modern app sytle
-proc main() =
-  gtk.init()
-  let window = newWindow()
-  window.title = "Select Cell"
-  window.position = WindowPosition.center
-  window.defaultSize = (500, 500)
-  window.borderWidth = 20
-  connect(window, "destroy", bye)
-  var iter: TreeIter
-  var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
-    typeFromName("gchararray")]
-  var store = newListStore(Columns,  cast[pointer]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
-  let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
-  for i, n in progNames:
-    store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original
-    store.setValue(iter, Id, toUIntVal(i))
-    store.setValue(iter, Program, toStringVal(n))
-    store.setValue(iter, Color1, toStringVal(if (i and 1) != 0: "LightSlateGray" else: "DarkCyan"))
-    store.setValue(iter, Color2, toStringVal("cyan"))
-  var tree  = newTreeViewWithModel(store)
-  tree.setHexpand
-  tree.setVexpand
-  setProperty(tree, "activate-on-single-click", toBoolVal(true))
-  var selection = tree.getSelection()
-  selection.setMode(SelectionMode.single)
-  var renderer1 = newCellRendererText()
-  setProperty(renderer1, "editable", toBoolVal(false))
-  var renderer2 = newCellRendererText()
-  setProperty(renderer2, "editable", toBoolVal(true))
-  connect(renderer2, "edited", updateRow, tree)
-  ## Bind the Color column to the "cell-background" property.
-  var column1 = newTreeViewColumn()
-  column1.setTitle("ID")
-  column1.packStart(renderer1, true)
-  column1.addAttribute(renderer1, "text", Id)
-  column1.addAttribute(renderer1, "cell-background", Color1)
-  discard tree.appendColumn(column1)
-  var column2  = newTreeViewColumn()
-  column1.setTitle("Program")
-  column1.packStart(renderer2, true)
-  column1.addAttribute(renderer2, "text", Program)
-  column1.addAttribute(renderer2, "cell-background", Color2)
-  discard tree.appendColumn(column2)
-  var grid = newGrid() # only one occupied cell makes no sense -- but so we can add more widgets later
-  grid.attach(tree, 0, 0, 1, 1)
-  window.add(grid)
-  const cssString = # note: big font selected intentionally
-    """treeview{background-color: rgba(0,255,255,1.0); font-size:30pt} treeview:selected{background-color:
-    rgba(255,255,0,1.0); color: rgba(0,0,255,1.0);}"""
-  var provider  = newCssProvider()
-  discard provider.loadFromData(cssString)
-  addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_APPLICATION)
-  window.showAll
-  gtk.main()
-
-main()
-
-
-
-

When you compile with nim c -d:release -d:danger --passC:-flto css_colored_listview.nim -you will get an executable size of 80k, which is big compared with the 20k of the C version, but -not too bad. You may note that I have added the updateRow() proc, which is necessary to -make editing the program name entries permanent. That proc needs cstring parametes, which -may be surprising, as we generally use Nim strings. Not a big problem, maybe intended, we may have to -check the connect() macro in gimpl.nim.

-
-
-
-
-

A more advanced example for cairo drawing with zooming, panning, scrolling

-
-
-

The following code is a plain Nim version of a drawing demo which I wrote some years ago in Ruby (http://ssalewski.de/PetEd-Demo.html.en). -Cairo surface is currently manually freed, because GC may have a too large delay.

-
-
-

You can resize the window and zoom in with the mouse wheel. When zoomed in scroll bars appear. You -can hold the middle mouse button pressed while moving the mouse for panning, and you can press left mouse button -and move the mouse to first draw a selection rectangle and zoom into it when releasing the mouse button.

-
-
-

In the examples directory there is also a simplified version called simpledrawingarea.nim which does all -the drawings in the draw callback, without using a buffering surface. This is generally preferable for -plain applications.

-
-
-
drawingarea.nim
-
-
# Plain demo for zooming, panning, scrolling with GTK DrawingArea
-# (c) S. Salewski, 21-DEC-2010 (initial Ruby version)
-# Nim version April 2019
-# License MIT
-
-# This version of the demo program uses a separate proc paint()
-# which allocates a custom surface for buffered drawing.
-# That may be not really necessary, for simple drawings doing all
-# the drawing in the "draw" call back is easier and faster. But for
-# more complicated drawing operations, for example when using a
-# background grid, which is a bit larger than the window size and
-# is reused when scrolling, a custom surface may be useful.
-# And finally that custom surface and custom cairo context is an
-# important test for the language bindings.
-
-# https://discourse.gnome.org/t/problem-with-gtkscrollbar-gtk-window-resize-and-gtk-adjustment-set-value/1081
-
-import gintro/[gtk, gdk, glib, gobject, gio, cairo]
-
-const
-  ZoomFactorMouseWheel = 1.1
-  ZoomFactorSelectMax = 10 # ignore zooming in tiny selection
-  ZoomNearMousepointer = true # mouse wheel zooming -- to mouse-pointer or center
-  SelectRectCol = [0.0, 0, 1, 0.5] # blue with transparency
-
-discard """
-Zooming, scrolling, panning...
-
-|-------------------------|
-|<-------- A ------------>|
-|                         |
-|  |---------------|      |
-|  | <---- a ----->|      |
-|  |    visible    |      |
-|  |---------------|      |
-|                         |
-|                         |
-|-------------------------|
-
-a is the visible, zoomed in area == darea.allocatedWidth
-A is the total data range
-A/a == userZoom >= 1
-For horizontal adjustment we use
-hadjustment.setUpper(darea.allocatedWidth * userZoom) == A
-hadjustment.setPageSize(darea.allocatedWidth) == a
-So hadjustment.value == left side of visible area
-
-Initially, we set userZoom = 1, scale our data to fit into darea.allocatedWidth
-and translate the origin of our data to (0, 0)
-
-Zooming: Mouse wheel or selecting a rectangle with left mouse button pressed
-Scrolling: Scrollbars
-Panning: Moving mouse while middle mouse button pressed
-"""
-
-# drawing area and scroll bars in 2x2 grid (PDA == Plain Drawing Area)
-
-type
-  PosAdj = ref object of Adjustment
-    handlerID: uint64
-
-proc newPosAdj: PosAdj =
-  initAdjustment(result, 0, 0, 1, 1, 10, 1)
-
-type
-  PDA_Data* = object
-    draw*: proc (cr: Context)
-    extents*: proc (): tuple[x, y, w, h: float]
-    windowSize*: tuple[w, h: int]
-
-type
-  PDA = ref object of Grid
-    zoomNearMousepointer: bool
-    selecting: bool
-    userZoom: float
-    surf: Surface
-    pattern: Pattern
-    cr: cairo.Context
-    darea: DrawingArea
-    hadjustment: PosAdj
-    vadjustment: PosAdj
-    hscrollbar: Scrollbar
-    vscrollbar: Scrollbar
-    fullScale: float
-    dataX: float
-    dataY: float
-    dataWidth: float
-    dataHeight: float
-    lastButtonDownPosX: float
-    lastButtonDownPosY: float
-    lastMousePosX: float
-    lastMousePosY: float
-    zoomRectX1: float
-    zoomRectY1: float
-    oldSizeX: int
-    oldSizeY: int
-    drawWorld: proc (cr: Context)
-    extents: proc (): tuple[x, y, w, h: float]
-
-proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
-  if this.pattern.isNil: return
-  cr.setSource(this.pattern)
-  cr.paint
-  if this.selecting:
-    cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
-      this.zoomRectX1 - this.lastButtonDownPosX, this.zoomRectY1 - this.lastButtonDownPosY)
-    cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
-    cr.fillPreserve
-    cr.setSource(0, 0, 0)
-    cr.setLineWidth(2)
-    cr.stroke
-  return gdk.EVENT_STOP # EVENT_PROPAGATE
-  #return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
-
-# clamp to correct values, 0 <= value <= (adj.upper - adj.pageSize), block calling onAdjustmentEvent()
-proc updateVal(adj: PosAdj; d: float) =
-  adj.signalHandlerBlock(adj.handlerID)
-  adj.setValue(max(0.0, min(adj.value + d, adj.upper - adj.pageSize)))
-  adj.signalHandlerUnblock(adj.handlerID)
-
-proc updateAdjustments(this: PDA; dx, dy: float) =
-  this.hadjustment.setUpper(this.darea.allocatedWidth.float * this.userZoom)
-  this.vadjustment.setUpper(this.darea.allocatedHeight.float * this.userZoom)
-  this.hadjustment.setPageSize(this.darea.allocatedWidth.float)
-  this.vadjustment.setPageSize(this.darea.allocatedHeight.float)
-  updateVal(this.hadjustment, dx)
-  updateVal(this.vadjustment, dy)
-
-proc paint(this: PDA) =
-  # echo "paint"
-  this.cr.save
-  this.cr.translate(this.hadjustment.upper * 0.5 - this.hadjustment.value, # our origin is the center
-    this.vadjustment.upper * 0.5 - this.vadjustment.value)
-  this.cr.scale(this.fullScale * this.userZoom, this.fullScale * this.userZoom)
-  this.cr.translate(-this.dataX - this.dataWidth * 0.5, -this.dataY - this.dataHeight * 0.5)
-  this.drawWorld(this.cr) # call the user provided drawing function
-  this.cr.restore
-
-proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure; this: PDA): bool =
-  (this.dataX, this.dataY, this.dataWidth,
-    this.dataHeight) = this.extents() # query user defined size
-  this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
-      this.darea.allocatedHeight.float / this.dataHeight)
-  if this.surf != nil:
-    destroy(this.surf) # manually destroy surface -- GC would do it for us, but GC is slow...
-  this.surf = this.darea.window.createSimilarSurface(Content.color,
-      this.darea.allocatedWidth, this.darea.allocatedHeight)
-  if this.pattern != nil:
-    patternDestroy(this.pattern)
-  if this.cr != nil:
-    destroy(this.cr)
-  this.pattern = patternCreateForSurface(this.surf) # pattern now owns the surface!
-  this.cr = newContext(this.surf) # this function references target!
-  this.paint
-  return gdk.EVENT_STOP
-
-proc hscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
-  pda.hadjustment.setUpper(r.width.float * pda.userZoom)
-  pda.hadjustment.setPageSize(r.width.float)
-  if pda.oldSizeX != 0: # this fix is not exact, as fullScale can ...
-    updateVal(pda.hadjustment, (r.width - pda.oldSizeX).float * 0.5)
-  pda.oldSizeX = r.width
-
-proc vscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
-  pda.vadjustment.setUpper(r.height.float * pda.userZoom)
-  pda.vadjustment.setPageSize(r.height.float)
-  if pda.oldSizeY != 0: # ... change when window is rezized. But it's good enough!
-    updateVal(pda.vadjustment, (r.height - pda.oldSizeY).float * 0.5)
-  pda.oldSizeY = r.height
-
-proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
-  this.updateAdjustments(dx, dy)
-  this.paint
-  this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
-
-# event coordinates to user space
-proc getUserCoordinates(this: PDA; eventX, eventY: float): (float, float) =
-  ((eventX - this.hadjustment.upper * 0.5 + this.hadjustment.value) / (
-      this.fullScale * this.userZoom) + this.dataX + this.dataWidth * 0.5,
-   (eventY - this.vadjustment.upper * 0.5 + this.vadjustment.value) / (
-       this.fullScale * this.userZoom) + this.dataY + this.dataHeight * 0.5)
-
-proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
-  let state = getState(event)
-  let (x, y) = event.getCoords
-  if state.contains(button1): # selecting
-    this.selecting = true
-    this.zoomRectX1 = x
-    this.zoomRectY1 = y
-    this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
-  elif button2 in state: # panning
-    this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
-  else:
-    return gdk.EVENT_PROPAGATE
-  this.lastMousePosX = x
-  this.lastMousePosY = y
-  return gdk.EVENT_STOP
-  #event.request # request more motion events ?
-
-# zooming with mouse wheel -- data near mouse pointer should not move if possible!
-# hadjustment.value + event.x is the position in our zoomed_in world, (userZoom / z0 - 1)
-# is the relative movement caused by zooming
-# In other words, this is the delta-move d of a point at position P from zooming:
-# d = newPos - P = P * scale - P = P * (z/z0) - P = P * (z/z0 - 1). We have to compensate for this d.
-proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
-  let z0 = this.userZoom
-  case getScrollDirection(event)
-  of ScrollDirection.up:
-    this.userZoom *= ZoomFactorMouseWheel
-  of ScrollDirection.down:
-    this.userZoom /= ZoomFactorMouseWheel
-    if this.userZoom < 1:
-      this.userZoom = 1
-  else:
-    return gdk.EVENT_PROPAGATE
-  if this.zoomNearMousepointer:
-    let (x, y) = event.getCoords
-    this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (this.userZoom / z0 - 1),
-      (this.vadjustment.value + y) * (this.userZoom / z0 - 1))
-  else: # zoom to center
-    this.updateAdjustmentsAndPaint((this.hadjustment.value +
-        this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1),
-        (this.vadjustment.value + this.darea.allocatedHeight.float * 0.5) * (this.userZoom / z0 - 1))
-  return gdk.EVENT_STOP
-
-proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
-  var (x, y) = event.getCoords
-  this.lastMousePosX = x
-  this.lastMousePosY = y
-  this.lastButtonDownPosX = x
-  this.lastButtonDownPosY = y
-  echo "buttonPressEvent", x, " ", y
-  (x, y) = this.getUserCoordinates(x, y)
-  echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
-  return gdk.EVENT_STOP
-
-# zoom into selected rectangle and center it
-# math: we first center the selection rectangle, and then compensate for translation due to scale
-proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
-  let (x, y) = event.getCoords
-  let b = getButton(event)
-  if b == 1:
-    this.selecting = false
-    let z1 = min(this.darea.allocatedWidth.float / (this.lastButtonDownPosX - x).abs,
-      this.darea.allocatedHeight.float / (this.lastButtonDownPosY - y).abs)
-    if z1 < ZoomFactorSelectMax: # else selection rectangle will persist, we may output a message...
-      this.userZoom *= z1
-      this.updateAdjustmentsAndPaint(
-        ((x + this.lastButtonDownPosX) * z1 - this.darea.allocatedWidth.float) * 0.5 + this.hadjustment.value * (z1 - 1),
-        ((y + this.lastButtonDownPosY) * z1 - this.darea.allocatedHeight.float) * 0.5 + this.vadjustment.value * (z1 - 1))
-    return gdk.EVENT_STOP
-  return gdk.EVENT_PROPAGATE
-
-proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
-  pda.paint
-  pda.darea.queueDrawArea(0, 0, pda.darea.allocatedWidth, pda.darea.allocatedHeight)
-
-proc newPDA: PDA =
-  initGrid(result)
-  let da = newDrawingArea()
-  result.darea = da
-  da.setHExpand
-  da.setVExpand
-  da.connect("draw", drawingAreaDrawCb, result)
-  da.connect("configure-event", dareaConfigureCallback, result)
-  da.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
-      EventFlag.scroll, button1Motion, button2Motion, pointerMotionHint})
-  da.connect("motion-notify-event", onMotion, result)
-  da.connect("scroll_event", scrollEvent, result)
-  da.connect("button_press_event", buttonPressEvent, result)
-  da.connect("button_release_event", buttonReleaseEvent, result)
-  result.zoomNearMousepointer = ZoomNearMousepointer # mouse wheel zooming
-  result.userZoom = 1.0
-  result.hadjustment = newPosAdj()
-  result.hadjustment.handlerID = result.hadjustment.connect("value-changed", onAdjustmentEvent, result)
-  result.vadjustment = newPosAdj()
-  result.vadjustment.handlerID = result.vadjustment.connect("value-changed", onAdjustmentEvent, result)
-  result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
-  result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
-  result.hscrollbar.setHExpand
-  result.vscrollbar.setVExpand
-  result.hscrollbar.connect("size-allocate", hscrollbarSizeAllocateCallback, result)
-  result.vscrollbar.connect("size-allocate", vscrollbarSizeAllocateCallback, result)
-  result.attach(result.darea, 0, 0, 1, 1)
-  result.attach(result.vscrollbar, 1, 0, 1, 1)
-  result.attach(result.hscrollbar, 0, 1, 1, 1)
-
-proc appStartup(app: Application) =
-  echo "appStartup"
-
-proc appActivate(app: Application; initData: PDA_Data) =
-  let window = newApplicationWindow(app)
-  window.title = "Drawing example"
-  # window.defaultSize = initData.windowSize
-  window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
-  let pda = newPDA()
-  pda.drawWorld = initData.draw
-  pda.extents = initData.extents
-  window.add(pda)
-  showAll(window)
-
-proc newDisplay*(initData: PDA_Data) =
-  let app = newApplication("org.gtk.example")
-  connect(app, "startup", appStartup)
-  connect(app, "activate", appActivate, initData)
-  discard run(app)
-
-when isMainModule:
-
-  const # arbitrary locations for our data
-    DataX = 150.0
-    DataY = 250.0
-    DataWidth = 200.0
-    DataHeight = 120.0
-
-  # we need two user defined functions -- one gives the extent of the graphics,
-  # and the other does the cairo drawing using a cairo context.
-
-  # bounding box of user data -- x, y, w, h -- top left corner, width, height
-  proc worldExtents(): (float, float, float, float) =
-    (DataX, DataY, DataWidth, DataHeight) # current extents of our user world
-
-  # draw to cairo context
-  proc drawWorld(cr: cairo.Context) =
-    cr.setSource(1, 1, 1)
-    cr.paint
-    cr.setSource(0, 0, 0)
-    cr.setLineWidth(2)
-    var i = 0.0
-    while min(DataWidth - 2 * i, DataHeight - 2 * i) > 0:
-      cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
-      i += 10
-    cr.stroke
-
-  proc test =
-    let data = PDA_Data(draw: drawWorld, extents: worldExtents, windowSize: (800, 600))
-    newDisplay(data)
-
-  test() # 337 lines
-
-
-
-

We can use this module as a library easily and get this simple drawing tool with full zoom and scroll support:

-
-
-
darea_test.nim
-
-
import gintro/cairo
-import drawingarea
-from math import PI
-
-proc extents(): (float, float, float, float) =
-  (0.0, 0.0, 100.0, 100.0) # ugly float literals
-
-# draw to cairo context
-proc draw(cr: cairo.Context) =
-  cr.setSource(1, 1, 1) # set background color and paint
-  cr.paint
-  cr.setSource(0, 0, 0) # forground color
-  cr.arc(20, 30, 10, 0, 5) # nearly a circle
-  cr.newSubPath # do not join the two arcs
-  cr.arc(70, 60, 20, 0, math.PI)
-  cr.stroke # finally do it
-
-proc main =
-  var data: PDA_Data
-  data.draw = draw
-  data.extents = extents
-  data.windowSize = (800, 600)
-  newDisplay(data)
-
-main()
-
-
-
-
-
-

One more cairo example

-
-
-

Recently Mr. C. Eric Cashon provided an example code for working with a large bitmap image. -His example writes the image to disk, loads it again and displays the image allowing -zooming and translation. As examples are rare in these days, and that example is not to large, -I used c2nim to convert it to Nim. Below is the code with a few manually fixes. Note, the -current shipped cairo.nim module contains an assert statement, which prevents running this example. -If you really intent running this code, you will have to fix that single line in cairo.nim. I -have to do some more fixes in the cairo module and may ship a new version eventually. This -example is really low level, as alloc() is used directly.

-
-
-
cairoImage.nim
-
-
# https://discourse.gnome.org/t/proper-zoom-pan-image-approach-for-large-images/1497/6
-# Nim version of the C example of C. Eric Cashon
-import gintro/[gtk, gobject, glib, cairo]
-from math import TAU
-import strutils
-
-const
-  Width = 5000
-  Height = 5000
-  CFormat = cairo.Format.argb32
-
-var
-  Key: cairo.UserDataKey
-  translateX: float
-  translateY: float
-  scale = 1.0
-  ## Store data from file.
-  bigSurfaceData*: ptr cuchar = nil
-
-proc translateXSpinChanged(spinButton: SpinButton; data: DrawingArea) =
-  translateX = spinButton.value
-  data.queueDraw
-
-proc translateYSpinChanged(spinButton: SpinButton; data: DrawingArea) =
-  translateY = spinButton.getValue
-  data.queueDraw
-
-proc scaleSpinChanged(spinButton: SpinButton; data: DrawingArea) =
-  scale = spinButton.value
-  data.queueDraw
-
-proc saveBigSurface =
-  ## Use gdk_cairo_surface_create_from_pixbuf() to read in a pixbuf. Try a test surface here.
-  let bigSurface = imageSurfaceCreate(CFormat, Width, Height)
-  let cr = newContext(bigSurface)
-  ## Paint the background.
-  cr.setSource(1, 1, 1)
-  cr.paint
-  ## Draw a circle.
-  cr.setSource(0, 0, 1)
-  cr.arc(250, 250, 50, 0, math.TAU)
-  cr.fill
-  ## Draw some test grid lines.
-  cr.setSource(0, 1, 0)
-  for i in countup(0, 4900, 100):
-    cr.moveTo(0, i.float)
-    cr.lineTo(5000, i.float)
-    cr.stroke
-  for i in countup(0, 4900, 100):
-    cr.moveTo(i.float, 0)
-    cr.lineTo(i.float, 5000)
-    cr.stroke
-  cr.setSource(0, 0, 1)
-  cr.setLineWidth(10)
-  for i in 0 ..< 10:
-    cr.moveTo(0, i.float * 500.0)
-    cr.lineTo(5000, i.float * 500.0)
-    cr.stroke
-  for i in 0 ..< 10:
-    cr.moveTo(i.float * 500.0, 0)
-    cr.lineTo(i.float * 500.0, 5000)
-    cr.stroke
-  ## Outside box.
-  cr.setLineWidth(20)
-  cr.setSource(1, 0, 1)
-  cr.rectangle(0, 0, 5000, 5000)
-  cr.stroke
-  ## Save surface data to file.
-  let f: File = open("big_surface.s", fmWrite)
-  let p: ptr cuchar = cairo_image_surface_get_data(bigSurface.impl)
-  let len = writeBuffer(f, p, cairo_format_stride_for_width(CFormat, Width) * Height)
-  echo("write $1\n" % $len)
-  close(f)
-
-proc myDealloc(data: pointer) {.cdecl.} =
-  system.dealloc(data)
-
-proc getBigSurface(): Surface =
-  let f: File = open("big_surface.s", fmRead)
-  # setFilePos(f, 0)
-  # https://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-stride-for-width
-  let stride = cairo_format_stride_for_width(CFormat, Width)
-  bigSurfaceData = cast[ptr cuchar](malloc((stride * Height).uint64))
-  var len = readBuffer(f, bigSurfaceData, stride * Height)
-  echo("read $1" % $len)
-  close(f)
-  let bigSurface: Surface = new Surface # this is a temporary fix, we will support this later in cairo modul
-  bigSurface.impl = cairo_image_surface_create_for_data(bigSurfaceData, CFormat, Width, Height, stride)
-  discard setUserData(bigSurface, addr(Key), bigSurfaceData, myDealloc) # automatic deallocation
-  # flush(bigSurface)
-  echo("open $1" % bigSurface.status.statusToString)
-  return bigSurface
-
-proc daDrawing*(da: DrawingArea; cr: Context; bigSurface: Surface): bool =
-  var
-    width = da.getAllocatedWidth.float
-    height = da.getAllocatedHeight.float
-    originX = translateX
-    originY = translateY
-  ## Some constraints.
-  if translateX > 5000.0 - width:
-    originX = 5000.0 - width / scale
-  if translateY > 5000.0 - height:
-    originY = 5000.0 - height / scale
-  cr.setSource(0, 0, 0)
-  cr.paint
-  ## Partition the big surface.
-  var littleSurface: Surface = cairo.surfaceCreateForRectangle(bigSurface,
-      originX, originY, width / scale, height / scale)
-  cr.scale(scale, scale)
-  cr.setSourceSurface(littleSurface, 0, 0)
-  setFilter(getSource(cr), cairo.Filter.bilinear)
-  cr.paint
-  return true
-
-proc bye(w: Window) =
-  mainQuit()
-  echo "Bye..."
-
-proc main =
-  gtk.init()
-  let window = newWindow()
-  window.setTitle("Big Surface2")
-  window.setDefaultSize(500, 500)
-  window.setPosition(gtk.WindowPosition.center)
-  window.connect("destroy", bye)
-  ## Get a test surface.
-  saveBigSurface()
-  let bigSurface = getBigSurface()
-  let da: DrawingArea = newDrawingArea()
-  da.setHexpand
-  da.setVexpand
-  da.connect("draw", daDrawing, bigSurface)
-  let
-    translateXAdj = newAdjustment(0, 0, 5000, 20, 0, 0)
-    translateYAdj = newAdjustment(0, 0, 5000, 20, 0, 0)
-    scaleAdj = newAdjustment(1, 1, 5, 0.1, 0, 0)
-    translateXLabel = newLabel("translate x")
-    translateXSpin= newSpinButton(translateXAdj, 50, 1)
-  connect(translateXSpin, "value-changed", translateXSpinChanged, da)
-  let translateYLabel = newLabel("translate y")
-  let translateYSpin = newSpinButton(translateYAdj, 50, 1)
-  connect(translateYSpin, "value-changed", translateYSpinChanged, da)
-  let scaleLabel = newLabel("Scale")
-  let scaleSpin = newSpinButton(scaleAdj, 0.2, 1)
-  connect(scaleSpin, "value-changed", scaleSpinChanged, da)
-  let grid = newGrid()
-  grid.attach(da, 0, 0, 3, 1)
-  grid.attach(translateXLabel, 0, 1, 1, 1)
-  grid.attach(translateYLabel, 1, 1, 1, 1)
-  grid.attach(scaleLabel, 2, 1, 1, 1)
-  grid.attach(translateXSpin, 0, 2, 1, 1)
-  grid.attach(translateYSpin, 1, 2, 1, 1)
-  grid.attach(scaleSpin, 2, 2, 1, 1)
-  add(window, grid)
-  showAll(window)
-  gtk.main()
-
-main()
-
-
-
- - - - - -
- - -Related work: https://github.com/jdmansour/nim-smartgi -
-
-
-
-
- - - \ No newline at end of file