I have discovered Nimrod some days ago, when searching for news about Mozilla's Rust and Vala of the GTK project. Currently I mostly use Ruby, which is a nice language, but due to its bytecode interpreter performance is of course not optimal. (It is not that bad, even for two larger projects of mine (http://www.ssalewski.de/PetEd.html.en and http://www.ssalewski.de/Router.html.en) it is still fast enough.) But of course it would be nice to have a language nice like Ruby or Python and fast as C. Vala and Rust are candidates, maybe now Nimrod.
For me GTK bindings are important, preferable for GTK3, because I also use these with Ruby currently. I have already seen c2nim program which can generate bindings from C header files, and of course the existing GTK2 bindings. Unfortunately for GTK2 there seems to exist only the final output, but no hints how it was done. So I started some investigations how we can manage to come to GTK3 bindings for Nimrod. I saw the GTK3 proposal for the non accepted Google Summer Of Code 2014 project, so it must be not a trivial task. I know that some languages like Ruby, Python and Haskell now use GObjects introspection for bindings generation, but I do know nothing about that path currently, and my current opinion is, that c2nim path is OK. One problem of course is, that with new releases of GTK we need refreshed bindings. Start from scratch again, or create new ones? Today I started with some preparation, I wrote a script which tries to determine the best order of all the header files (based of cpp preprocessor order), then merge all together and does some preprocessing, i.e. deleting a few strange macros. I put the script and current output on my page at http://www.ssalewski.de/tmp/prep.sh and http://www.ssalewski.de/tmp/final.h. I think it is not a too bad result after few hours of work. Compared to glib gtk seems to have not too many strange macros. I still have to learn how c2nim works in detail -- I guess I will have to do much more cleanup before I can use that input file for c2nim. And of course then I have to add additional functions, some of which are GTK macros in C. Should be some work...
I have already found c2min's #def, so I have replaced sed replace command (replace at multiple positions) with a sed command which only inserts a #def to define an empty macro like these:
sed -i "1i#def G_GNUC_CONST" $final #sed -i 's/ G_GNUC_CONST;$/;/' $final
Please note, I did not run the cpp preprocessor "really" on the headers, but only on a copy which is later discarded. This is a trick to get the best order of all the small header files. I do not know how important order is, but it should be good if the files which do not include others are at the top, and files which include many are at the bottom. First I tried sorting with a small Ruby script by include list. That works if each header only includes what is needed. But that may not always be true -- for GLib some header files import full glib.h, which then imports all the headers. I think my cpp preprocessor trick is not bad, it is easy and order seems to make sense.
I have already launched c2nim for a test run, and it processed some thousand of lines before it reports an error. That is not bad.
One minor problem are C bit fields, 16 bit entity I patches this way
sed -i 's/^s*guint s*accel_flags : 16;$/ uint16_t accel_flags;/' $final
but there is the struct _GtkWidgetAuxInfo with two 4 bit fields -- I have completely removed that.
Next task are C macros like this one
#define gtk_widget_class_bind_template_child(widget_class, TypeName, member_name) gtk_widget_class_bind_template_child_full (widget_class, #member_name, FALSE, G_STRUCT_OFFSET (TypeName, member_name))
I think c2nim can currently not ignore that type itself, so I have to delete such macros and later make a nimrod equivalent if really needed. I already have a perl regex to delete this pattern, but it is not very fast.
TYPE is a reserved word in nimrod, but with the * marked for export it should be fine? If not, what is the recommended substitue?
I have seen that there is no Pgchar in glib currently, but only PPgchar indicating pointer to cstring. But in GTK plain pointers to gchar are used. What is the benefit of PPgchar?
I think I will convert
/**
* GDK_POINTER_TO_ATOM:
* @ptr: a pointer containing a #GdkAtom.
*
* Extracts a #GdkAtom from a pointer. The #GdkAtom must have been
* stored in the pointer with GDK_ATOM_TO_POINTER().
*/
#define GDK_POINTER_TO_ATOM(ptr) ((GdkAtom)(ptr))
from c2nim's output
to
Is that OK?
For your first question, no, "type" is a reserved word, and cannot be used as the name of a type attribute/member. The convention in these circumstances is to rename "type" to "typ", or some variant thereof.
For your second question, yes, you should change that. I'm surprised c2nim didn't catch that cast and transform it in the first place.
OK, seems to be solved.
I have to remove the multiple TYPE keywords inserted by c2nim...
times.TTime is C's time_t iirc. I don't think we need the valist versions but it surely could be wrapped with enough hacks.
Nice work! So what are the next steps? Porting Aporia to use GTK 3? Wrapping Qt and wxWidgets? ;-)
The compiler user guide mentions the release switch along with others which you may find useful. release is also mentioned in the introduction to the tutorial.
Interestingly while the -d: switch is mentioned in many places, none explain that it introduces constant symbols which can later be checked with the defined compile time procedure, which again doesn't mention this at all. Essentially whatever string you pass to -d:xxx can be checked with when defined(xxx) in the code. An example of arbitrary defines would be the mention of the -d:ssl switch in the sockets module.
import gtk3, glib2
proc destroy(widget: pWidget, data: pgpointer) {.cdecl.} = main_quit()
var window = window_new()
discard g_signal_connect(window, "destroy", G_CALLBACK(t.destroy), nil)
window.title = "Radio Buttons"
echo(window.title) # test get_() method
window.border_width = 10
var
r1 = radio_button_new() # Template call, assign label later
r2 = radio_button_new_with_label(r1, "Radio_Button 2") # or create with label
box = box_new(TOrientation.VERTICAL, 0) # enum
r1.label="Radio_Button 1"
box.pack_start(r1, gfalse, gtrue, 0)
box.pack_start(r2, gfalse, gtrue, 0)
window.add(box)
window.show_all
main()
I have done some more fixes -- the above code works and looks not bad for me, similar to Ruby or Vala. Procs like window_new() return now the true type, not a plain widget. That should be OK, also seen in gtk2. Additional to set and get procedures now code like window.title="Title" and window.title query is now possible. For Radio buttons I have used templates to allow empty parameter list for first button of a group, and I used a default parameter TOPLEVEL for window_new(). Finally I have put init_gtk() call at the end of module gtk3.nim, so we have not to call it manually. Seems to work -- do we expect problems? The enums have now the pure pragma, so we write TOrientation.VERTICAL for example. This is Ruby and Vala style. A few enum fields had name conflicts with keyword, so I used E_END enstead of plain END name, E prexix for enumeration. Of course some more (manually) cleanup is necessary, but I think it may work in its current shape.
@ Stefan_Salewski: impressive job, hope to see GTK-3 in Github soon
Do you plan to re-write you Ruby Schematic and PCB Routing with Nim ?
If this is the case I may have (personally not professionally) a lot of interest in your project. Don't know if I am skilled enough to help but if I can ... (nowadays I am unemployed so have a lot of free time, of course that may change in future)
By the way I have started my professional life as a PCBCad operator long long time ago, and came to dev. after that (at that time Fortran) ;-)
impressive job, hope to see GTK-3 in Github soon
Yes, as I already wrote in the other thread: I think I should be able to finish GTK3 in the next 7 days... Of course there will still be work left: Currently it is a low level wrapper, i.e. no GC support. That is not a big problem for GTK, because internal use of ref counting works well, even plain C programs have not to care much about freeing memory. Another issue is, that GTK objects can be not directly extended in Nim -- also not a big issue, because GTK objects can be components of Nim objects. I have seen that some bindings for other language did very large effort to create high level GTK wrappers, i.e GTKmm did that. I am not sure if that effort is really justified.
For my schematics editor and the PCB routing tool: It is clear that today Nim, Rust or even Go would be the better suited language for these projects. Ruby was really not bad for the early development process, but Nim will give better compiler support and of course more execution speed. I think I will first finish the Schematics editor in Ruby, which may still take about 400 hours, basically simple clean up work. Then I may port it to Nim -- if I have some free time. Unfortunately both programs are close related to the gEDA tools set, which is not very active currently, both for users and developers.
Generally I am not fully fixed to GTK -- if we should get a really fine wrapper for Qt or a GUI written from scratch in Nim I may even take that into account...
First, I have to say thanks for the great job, too. I know it's a ton of work.
Another issue is, that GTK objects can be not directly extended in Nim -- also not a big issue, because GTK objects can be components of Nim objects.
That seems to be the "right thing" to do in Nim anyway (composition over inheritance). But especially in GTK, there are cases where it makes sense, or you even have to create subclasses. For example, to create custom tree models inheriting from GtkTreeModel, instead of just using a GtkTreeStore. To register a class, it's just a matter of calling one of the g_type_register functions. Below is a (slightly verbose) minimal example for gtk2, it should be easy to port it to gtk3. It should be possible to hide the verbosity behind a macro.
(Btw., I found the GObject tutorial helpful in understanding how GObject works underneath, if you ever have to use this.)
import gtk2
import glib2
# additional imports:
when defined(windows):
const gliblib = "libglib-2.0-0.dll"
elif defined(macosx):
const gliblib = "libglib-2.0.dylib"
else:
const gliblib = "libglib-2.0.so(|.0)"
proc g_intern_static_string*(str: cstring): cstring {.cdecl,
dynlib: gliblib, importc: "g_intern_static_string".}
proc g_type_register_static_simple(parent_type: GType, type_name: cstring,
class_size: guint16, class_init: TGClassInitFunc,
instance_size: guint16, instance_init: TGInstanceInitFunc,
flags: TGTypeFlags): GType =
var info = TGTypeInfo(class_size: class_size,
base_init: nil,
base_finalize: nil,
class_init: class_init,
class_finalize: nil,
class_data: nil,
instance_size: instance_size,
n_preallocs: 0,
instance_init: instance_init,
value_table: nil)
result = g_type_register_static(parent_type, type_name, addr(info), flags)
# defining a new type:
type
PMainWindow = ptr TMainWindow
TMainWindow = object of TWindow
TMainWindowClass = object of TWindowClass
proc main_window_init(instance: PGTypeInstance, g_class: gpointer){.cdecl.} =
let self = cast[PMainWindow](instance)
# Do some initialization. This is basically the constructor.
# We can also do construction in main_window_new
self.setTitle("Subclass Window")
# gtk boilerplate to register the type:
proc TYPE_MAIN_WINDOW*(): GType
proc main_window_get_type(): GType =
var g_define_type_id {.global.}: GType = 0
if g_define_type_id == 0:
g_define_type_id = g_type_register_static_simple(gtk2.TYPE_WINDOW(),
g_intern_static_string ("MainWindow"),
cast[guint16](sizeof (TMainWindowClass)),
nil,
cast[guint16](sizeof (TMainWindow)),
(TGInstanceInitFunc) main_window_init,
0)
return g_define_type_id
proc TYPE_MAIN_WINDOW*(): GType =
result = main_window_get_type()
# end gtk boilerplate
proc main_window_new(): PMainWindow =
# helper to create a new instance of MainWindow
result = cast[PMainWindow](g_object_new(TYPE_MAIN_WINDOW(), nil))
gtk2.nimrodInit()
let mainwindow = mainWindowNew()
mainwindow.show_all()
gtk2.main()
import gtk3, glib, gobject
type
PMainWindow = ptr TMainWindow
TMainWindow = object of TWindow
TMainWindowClass = object of TWindowClass
proc main_window_init(instance: PGTypeInstance, g_class: gpointer){.cdecl.} =
let self = cast[PMainWindow](instance)
# Do some initialization. This is basically the constructor.
# We can also do construction in main_window_new
self.setTitle("Subclass Window")
proc main_window_get_type(): GType =
var g_define_type_id {.global.}: GType = 0
if g_define_type_id == 0:
g_define_type_id = g_type_register_static_simple(gtk3.window_get_type(),
g_intern_static_string ("MainWindow"),
cast[guint](sizeof (TMainWindowClass)),
nil,
cast[guint](sizeof (TMainWindow)),
(GInstanceInitFunc) main_window_init,
cast[GTypeFlags](0))
return g_define_type_id
proc main_window_new(): PMainWindow =
result = cast[PMainWindow](g_object_new(main_window_get_type(), nil))
var
i: cint = 0
a: cstringArray = cast[cstringArray](nil)
gtk3.init(i, a) # we should find a smarter init()!
let mainwindow = mainWindowNew()
mainwindow.show_all()
gtk3.main()
Of course we have to press CTRL-C in terminal to terminate it, there is no callback for that defined. But it compiles and do not crash, so that example is really helpful for extending GObject objects. It's seems indeed to be not that difficult. Some days ago I looked at the GtkSourceView C Source-Completion example, seem it works now very different from the early code used in Aporia. It uses so called provider objects, extensions of GObject, and employs macro G_DEFINE_TYPE_WITH_CODE, which is really heavy. But with your example I begin to understand the black magic, so we may build nimsuggest source completion for GTK3 if we want. (Still have to learn what nimsuggest really is and does, have found no docs yet, and first tests from command line just crashes...)
Today someone asked about Nim GTK3 at github, so maybe it is a good time to tell you that the new GTK3 "Application Stile" works well also with Nim.
https://developer.gnome.org/gtk3/stable/ch01s04.html#id-1.2.3.12.5
Here is a picture of the Nim program:
http://ssalewski.de/tmp/nim-app.png
Seems to work well. I had only to add a few missing macros, then I used c2nim to convert the C files to Nim and did a few manually fixes -- and I fixed a few bugs in the wrappers, for example proc parameters from var cstring to cstringArray and such. I have not much experience with the new GTK3 "Application Stile" coding. The C examples generally extent the GTK3 classes, which is done with some not really trivial C macros. And for other languages like Python or C++ there are not many recent examples available, most are restricted or outdated. Of course this style of GUI design is still very C like and the macro calls is some black magic. But it works, and at least for Linux looks not bad. Some people may still prefer the stile which most Python and Ruby tutorials use, of course that can be used with Nim too.
I will try to clean up the macro code and this example and make it available public at some time, maybe end of this year, when stable GTK 3.18 is available. ( I have already updated the main gtk3.nim module for 3.17.6 locally, but the other modules needs still some work.)