842 lines
37 KiB
Text
842 lines
37 KiB
Text
= High level GTK3 bindings for the Nim programming language
|
|
(c) Stefan Salewski
|
|
//Version 0.4 2018
|
|
:experimental:
|
|
:imagesdir: http://ssalewski.de/tmp
|
|
:source-highlighter: pygments
|
|
:pygments-style: monokai
|
|
:icons: font
|
|
|
|
:GIR: GObject-Introspection
|
|
:MAC: MacOSX
|
|
|
|
//(c) Stefan Salewski +
|
|
//2018
|
|
|
|
TIP: A more fancy copy of this document with dark source code background is available at http://ssalewski.de/gintroreadme.html[GIntro README]
|
|
|
|
NOTE: 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.
|
|
|
|
//icon:thumbs-up[]
|
|
This repository contains bindings from the Nim programming language to the GTK3 _GUI_ (_Graphical User Interface_) library and related libraries. (With some fixes
|
|
it should also work for upcoming _GTK4_.)
|
|
|
|
https://nim-lang.org/[Nim] is a modern universal programming language.
|
|
|
|
https://www.gtk.org/[GTK], also known as the _Gimp Tool Kit_ and now sometimes called _Gnome Tool Kit_, is a Graphical User Interface library.
|
|
|
|
NOTE: 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 _{MAC}_.
|
|
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 {MAC} operating systems. But when your primary target platforms
|
|
are Windows and {MAC} 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 {MAC}, then you may find better suited packages
|
|
in the Nim package repository. _Android OS_ is currently not supported by GTK at all.
|
|
|
|
TIP: 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:
|
|
|
|
* https://github.com/ngtk3 (obsolete, will be deleted)
|
|
* https://github.com/StefanSalewski/oldgtk3
|
|
* https://github.com/StefanSalewski/gintro
|
|
|
|
ngtk3 was the first attempt to provide GTK3 support for Nim. It contains single repositories for all the GTK related libraries and
|
|
is 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 _{GIR}_, 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.
|
|
|
|
NOTE: The new package name is *gintro*, short for _{GIR}_. 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: {GIR} 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 {GIR}.
|
|
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 {GIR} 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
|
|
----
|
|
|
|
NOTE: 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.
|
|
|
|
NOTE: 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 {GIR} 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
|
|
|
|
NOTE: 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 http://zetcode.com/gui/gtk2/[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:
|
|
|
|
* https://www.gnome.org/
|
|
* https://www.gtk.org/
|
|
* https://developer.gnome.org/
|
|
* https://developer.gnome.org/gtk3/stable/
|
|
* https://developer.gnome.org/gtk3/stable/ch01s04.html#id-1.2.3.12.5
|
|
* https://developer.gnome.org/gnome-devel-demos/stable/c.html.en
|
|
|
|
TIP: 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]]
|
|
[source,nim]
|
|
.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.
|
|
|
|
NOTE: 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]]
|
|
[source,nim]
|
|
.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.
|
|
|
|
NOTE: 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:
|
|
|
|
//. Setting widget parameters
|
|
[source,nim]
|
|
----
|
|
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
|
|
|
|
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 https://developer.gnome.org/gnome-devel-demos/3.22/c.html.en[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.
|
|
|
|
image::NimGTK3Button.png[]
|
|
|
|
The _C_ code looks like this:
|
|
|
|
[[button.c]]
|
|
[source,c]
|
|
.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]]
|
|
[source,nim]
|
|
.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.
|
|
|
|
TIP: 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]]
|
|
[source,nim]
|
|
.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:
|
|
|
|
[source,nim]
|
|
----
|
|
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.
|
|
|
|
NOTE: 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 kbd:[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]]
|
|
[source,nim]
|
|
.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
|
|
|
|
image::NimGTK3Label.png[]
|
|
|
|
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]]
|
|
[source,c]
|
|
.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]]
|
|
[source,nim]
|
|
.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().
|
|
|
|
== 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.
|
|
|
|
NOTE: 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]]
|
|
[source,nim]
|
|
.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
|
|
|
|
image::NimGTK3ListView.png[]
|
|
|
|
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 http://zetcode.com/gui/gtk2/gtktreeview/[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]]
|
|
[source,nim]
|
|
.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()
|
|
----
|
|
|
|
NOTE: Related work: https://github.com/jdmansour/nim-smartgi
|
|
|