Finish 0.2.0

This commit is contained in:
Filippo Cucchetto 2015-01-06 13:14:25 +01:00
commit fd9b05b904
5 changed files with 146 additions and 75 deletions

View File

@ -2,12 +2,12 @@
Filippo Cucchetto <filippocucchetto@gmail.com> Filippo Cucchetto <filippocucchetto@gmail.com>
Will Szumski <will@cowboycoders.org> Will Szumski <will@cowboycoders.org>
:Version: 0.1.0 :Version: 0.2.0
:Date: 2015/01/02 :Date: 2015/01/02
Introduction Introduction
----------- -----------
The NimQml module add Qt Qml bindings to the Nim programming language The NimQml module adds Qt Qml bindings to the Nim programming language
allowing you to create new modern UI by mixing the Qml declarative syntax allowing you to create new modern UI by mixing the Qml declarative syntax
and the Nim imperative language. and the Nim imperative language.
@ -22,7 +22,7 @@ the libDOtherSide exported symbols in Nim
Building Building
-------- --------
At the time of writing the DOtherSide C++ library must be compiled At the time of writing the DOtherSide C++ library must be compiled
installed manually from source. and installed manually from source.
First clone the DOtherSide git repo First clone the DOtherSide git repo
:: ::
@ -36,7 +36,7 @@ than you can proceed with the common CMake build steps
cmake .. cmake ..
make make
If everything goes correctly you'll have build both If everything goes correctly, you'll have built both
the DOtherSide C++ library and the Nim examples the DOtherSide C++ library and the Nim examples
Installation Installation
@ -49,7 +49,11 @@ the built Nim example in the following way
export LD_LIBRARY_PATH=path/to/libDOtherSide.so export LD_LIBRARY_PATH=path/to/libDOtherSide.so
./HelloWorld ./HelloWorld
Given this, you can procede with the installation of the C++ library The DOtherSide project is made of two components
1. The DOtherSide C++ lib
2. The NimQml module
You can procede with the installation of the C++ library
in the following way in the following way
:: ::
cd to/build/dir cd to/build/dir
@ -58,12 +62,22 @@ or by manually copying the library in your system lib directory
:: ::
sudo cp build/dir/path/DOtherSide/libDOtherSide.so /usr/lib sudo cp build/dir/path/DOtherSide/libDOtherSide.so /usr/lib
For the NimQml module you can use the ``nimble`` package manager
::
nimble install NimQml
or
::
cd to/build/dir/Nim/NimQml
nimble install
Example 1: HelloWorld Example 1: HelloWorld
---------- ----------
As usual lets start with an HelloWorld example. As usual lets start with an HelloWorld example.
Most of the NimQml projects are made by one or more nim and qml Most of the NimQml projects are made by one or more nim and qml
files. Usually the .nim files contains your app logic and data files. Usually the .nim files contains your app logic and data
layer. The qml files contains the presentation layer and expose layer. The qml files contain the presentation layer and expose
the data in your nim files. the data in your nim files.
``Examples/HelloWorld/main.nim`` ``Examples/HelloWorld/main.nim``
@ -87,8 +101,8 @@ Example 2: exposing data to Qml
The previous example shown you how to create a simple application The previous example shown you how to create a simple application
window and how to startup the Qt event loop. window and how to startup the Qt event loop.
Is time to explore how to pass to to Qml, but first lets see the It's time to explore how to pass data to Qml, but lets see the
example code: example code first:
``Examples/SimpleData/main.nim`` ``Examples/SimpleData/main.nim``
@ -100,13 +114,13 @@ example code:
.. code-block:: qml .. code-block:: qml
:file: ../Examples/SimpleData/main.qml :file: ../Examples/SimpleData/main.qml
The following example shows how to expose simple data to Qml: The following example shows how to expose simple data types to Qml:
1. Create a QVariant and sets the its internal value. 1. Create a QVariant and set its internal value.
2. Create a property in the Qml root context with a given name. 2. Create a property in the Qml root context with a given name.
Once a a property is set through the ``setContextProperty`` proc its available Once a property is set through the ``setContextProperty`` proc, it's available
globally in all the Qml script loaded by the current engine (see the Qt doc globally in all the Qml script loaded by the current engine (see the official Qt
for more details about engine and context) documentation for more details about the engine and context objects)
At the time of writing the QVariant class support the following types: At the time of writing the QVariant class support the following types:
* int * int
@ -116,21 +130,21 @@ At the time of writing the QVariant class support the following types:
Example 3: exposing complex data and procedures to Qml Example 3: exposing complex data and procedures to Qml
---------------------------------------------------------- ----------------------------------------------------------
As seen by the second example simple data is fine. However most As seen by the second example, simple data is fine. However most
applications need to expose complex data, functions and applications need to expose complex data, functions and
update the view when something change in the data layer. update the view when something changes in the data layer.
This is achieved by creating an object that derives from QObject. This is achieved by creating an object that derives from QObject.
A QObject is made of : A QObject is made of :
1. ``Slots``: slots are function that could both be called from the qml engine and connected to Qt signals 1. ``Slots``: slots are functions that could be called from the qml engine and/or connected to Qt signals
2. ``Signals``: signals allow the sending of events and be connected to slots 2. ``Signals``: signals allow the sending of events and be connected to slots
3. ``Properties``: properties allows the passing of data to 3. ``Properties``: properties allow the passing of data to
the Qml view and make it aware of changes in the data layer the Qml view and make it aware of changes in the data layer
A QObject property is made of three things: A QObject property is made of three things:
* a read slot, a method that return the current value of the property * a read slot: a method that returns the current value of the property
* a write slot, a method that set the value of the property * a write slot: a method that sets the value of the property
* a notify signal for telling that the current value of the property has been changed * a notify signal: emitted when the current value of the property is changed
We'll start by looking at the main.nim file We'll start by looking at the main.nim file
@ -139,24 +153,25 @@ We'll start by looking at the main.nim file
.. code-block:: nim .. code-block:: nim
:file: ../Examples/SlotsAndProperties/main.nim :file: ../Examples/SlotsAndProperties/main.nim
Here's nothing special happen except: Here, nothing special happens except:
1. The creation of Contact object 1. The creation of Contact object
2. The injection of the Contact object to the Qml root context 2. The injection of the Contact object to the Qml root context
using the ``setContextProperty`` as seen in the previous using the ``setContextProperty`` as seen in the previous
example example
The Qml file is as follow: The Qml file is as follows:
``Examples/SlotsAndProperties/main.qml`` ``Examples/SlotsAndProperties/main.qml``
.. code-block:: qml .. code-block:: qml
:file: ../Examples/SlotsAndProperties/main.qml :file: ../Examples/SlotsAndProperties/main.qml
The qml is made by a Label, a TextInput and a button. The qml is made up of: a Label, a TextInput widget, and a button.
The label display the contact name and automatically udpates when The label displays the contact name - this automatically updates when
the concat name changes. the contact name changes.
The button update the contact name with the TextInput text when clicked. When clicked, the button updates the contact name with the text from
the TextInput widget.
So where's the magic? So where's the magic?
@ -167,37 +182,37 @@ The magic is in the Contact.nim file
.. code-block:: nim .. code-block:: nim
:file: ../Examples/SlotsAndProperties/Contact.nim :file: ../Examples/SlotsAndProperties/Contact.nim
What First we declare a QObject subclass and provide a simple First we declare a QObject subclass and provide a simple
new method where: new method where we:
1. invoke the ``create()`` procedure. This invoke the C++ bridge and allocate 1. invoke the ``create()`` procedure. This invoke the C++ bridge and allocate
a QObject instance a QObject instance
2. register a slot ``getName`` for reading the Contact name field 2. register a slot ``getName`` for reading the Contact name field
3. register a slot `` setName`` for writing the Contact name 3. register a slot ``setName`` for writing the Contact name
4. register a signal ``nameChanged`` for notify the contact name changes 4. register a signal ``nameChanged`` for notify the contact name changes
5. register a property called ``name`` of type ``QString`` with the given 5. register a property called ``name`` of type ``QString`` with the given
read, write slots and notify signal read, write slots and notify signal
The two slots method implementation are trivial and consist in standard Looking at the ``getName`` and ``setName`` methods, you can see that slots, as defined in Nim,
nim methods. However ``setName`` slot method shows how to emit a signal are nothing more than standard methods. The method corresponding to the ``setName`` slot
by using the ``emit`` method. demonstrates how to use the ``emit`` method to emit a signal.
The last thing to condider is the override of the ``onSlotCalled`` method. The last thing to consider is the override of the ``onSlotCalled`` method.
This method is called by the NimQml library when an invokation occur from This method is called by the NimQml library when an invocation occurs from
the Qml side for one of the QObject slot. the Qml side for one of the slots belonging to the QObject.
The usual implementation for the onSlotCalled method consists in a The usual implementation for the onSlotCalled method consists of a
switch statement that forward the arguments to the correct slot. switch statement that forwards the arguments to the correct slot.
If the invoked slot has a return value this is always in the index position If the invoked slot has a return value, this is always in the index position
0 of the args array. 0 of the args array.
Example 4: QtObject macro Example 4: QtObject macro
------------------------- -------------------------
The previous example shows how to create simple QObject however writing The previous example shows how to create a simple QObject, however writing
all those ``register`` procs and writing the ``onSlotCalled`` method all those ``register`` procs and writing the ``onSlotCalled`` method
become boring pretty soon. becomes boring pretty soon.
Furthermore all this information can be automatically generated. Furthermore all this information can be automatically generated.
For this purpose you can import the NimQmlMacros module that provide For this purpose you can import the NimQmlMacros module that provides
the QtObject macro. the QtObject macro.
Let's begin as usual with both the main.nim and main.qml files Let's begin as usual with both the main.nim and main.qml files
@ -213,8 +228,8 @@ Let's begin as usual with both the main.nim and main.qml files
.. code-block:: qml .. code-block:: qml
:file: ../Examples/QtObjectMacro/main.qml :file: ../Examples/QtObjectMacro/main.qml
Nothing new in both the ``main.nim`` and ``main.qml`` in respect to Nothing is new in both the ``main.nim`` and ``main.qml`` with respect to
the previous example. What changed is the Contact object the previous example. What changed is the Contact object:
``Examples/QtObjectMacro/Contact.nim`` ``Examples/QtObjectMacro/Contact.nim``
@ -223,12 +238,12 @@ the previous example. What changed is the Contact object
In details: In details:
1. Each QObject is defined inside the QtObject macro 1. Each QObject is defined inside the QtObject macro
2. Each slot is annotated with the ``{.slot.}`` macro 2. Each slot is annotated with the ``{.slot.}`` pragma
3. Each signal is annotated with the ``{.signal.}`` macro 3. Each signal is annotated with the ``{.signal.}`` pragma
4. Each property is created with the ``QtProperty`` macro 4. Each property is created with the ``QtProperty`` macro
The ``QtProperty`` macro has the following syntax The ``QtProperty`` macro has the following syntax
.. code-block:: nim .. code-block:: nim
QtProperty nameOfProperty of typeOfProperty QtProperty[typeOfProperty] nameOfProperty

View File

@ -3,7 +3,7 @@
import NimQml, NimQmlMacros import NimQml, NimQmlMacros
QtObject: QtObject:
type Contact = ref object of QObject type Contact* = ref object of QObject
m_name: string m_name: string
template newContact*(): Contact = template newContact*(): Contact =
@ -21,7 +21,7 @@ QtObject:
contact.m_name = name contact.m_name = name
contact.nameChanged() contact.nameChanged()
QtProperty name of string: QtProperty[string] name:
read = getName read = getName
write = setName write = setName
notify = nameChanged notify = nameChanged

View File

@ -1,7 +1,7 @@
[Package] [Package]
name = "NimQml" name = "NimQml"
version = "0.01" version = "0.2.0"
author = "Filippo Cucchetto" author = "Filippo Cucchetto, Will Szumski"
description = "QML bindings for Nimrod" description = "QML bindings for Nimrod"
license = "GPLv3" license = "GPLv3"

View File

@ -21,7 +21,7 @@ let nimFromQtVariant {.compileTime.} = {
let nim2QtMeta {.compileTime.} = { let nim2QtMeta {.compileTime.} = {
"bool": "Bool", "bool": "Bool",
"int " : "Int", "int" : "Int",
"string" : "QString", "string" : "QString",
"pointer" : "VoidStar", "pointer" : "VoidStar",
"QVariant": "QVariant", "QVariant": "QVariant",
@ -91,7 +91,8 @@ proc newTemplate*(name = newEmptyNode();
newEmptyNode(), newEmptyNode(),
body) body)
template declareSuperTemplate*(parent: typedesc, typ: typedesc): typedesc = #FIXME: changed parent, typ from typedesc to expr to workaround Nim issue #1874
template declareSuperTemplate*(parent: expr, typ: expr): stmt =
template superType*(ofType: typedesc[typ]): typedesc[parent] = template superType*(ofType: typedesc[typ]): typedesc[parent] =
parent parent
@ -108,6 +109,34 @@ proc getTypeName*(a: PNimrodNode): PNimrodNode {.compileTime.} =
elif testee[0].kind in {nnkPostfix}: elif testee[0].kind in {nnkPostfix}:
return testee[0][1] return testee[0][1]
proc isExported(def: PNimrodNode): bool {.compileTime.} =
## given a type definition, ``typedef``, determines whether or
## not the type is exported with a '*'
assert def.kind in {nnkTypeDef, nnkProcDef, nnkMethodDef, nnkTemplateDef},
"unsupported type: " & $def.kind
if def[0].kind == nnkPostfix:
return true
proc exportDef(def: PNimrodNode) {.compileTime.} =
## Exports exportable definitions. Currently only supports
## templates, methods and procedures and types.
if def.kind in {nnkProcDef, nnkMethodDef, nnkTemplateDef, nnkTypeDef}:
if def.isExported:
return
def[0] = postfix(def[0], "*")
else:
error("node: " & $def.kind & " not supported")
proc unexportDef(def: PNimrodNode) {.compileTime.} =
## unexports exportable definitions. Currently only supports
## templates, methods and procedures and types.
if def.kind in {nnkProcDef, nnkMethodDef, nnkTemplateDef, nnkTypeDef}:
if not def.isExported:
return
def[0] = ident unpackPostfix(def[0])[1]
else:
error("node: " & $def.kind & " not supported")
proc genSuperTemplate*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} = proc genSuperTemplate*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} =
## generates a template, with name: superType, that returns the super type ## generates a template, with name: superType, that returns the super type
## of the object defined in the type defintion, ``typeDecl``. ``typeDecl`` ## of the object defined in the type defintion, ``typeDecl``. ``typeDecl``
@ -120,7 +149,11 @@ proc genSuperTemplate*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} =
let superType = if inheritStmt[0].kind == nnkIdent: inheritStmt[0] let superType = if inheritStmt[0].kind == nnkIdent: inheritStmt[0]
else: inheritStmt[0].getNodeOf(nnkIdent) else: inheritStmt[0].getNodeOf(nnkIdent)
let superTemplate = getAst declareSuperTemplate(superType, typeName) let superTemplate = getAst declareSuperTemplate(superType, typeName)
return superTemplate[0] result = superTemplate[0]
if typeDecl.isExported():
result.exportDef()
else:
result.unexportDef()
proc getSuperType*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} = proc getSuperType*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} =
## returns ast containing superType info, may not be an ident if generic ## returns ast containing superType info, may not be an ident if generic
@ -192,11 +225,13 @@ proc addSignalBody(signal: PNimrodNode): PNimrodNode {.compileTime.} =
args.add getArgName params[i] args.add getArgName params[i]
result.add newCall("emit", args) result.add newCall("emit", args)
template declareOnSlotCalled(typ: typedesc): stmt = #FIXME: changed typ from typedesc to expr to workaround Nim issue #1874
template declareOnSlotCalled(typ: expr): stmt =
method onSlotCalled(myQObject: typ, slotName: string, args: openarray[QVariant]) = method onSlotCalled(myQObject: typ, slotName: string, args: openarray[QVariant]) =
discard discard
template prototypeCreate(typ: typedesc): stmt = #FIXME: changed parent, typ from typedesc to expr to workaround Nim issue #1874
template prototypeCreate(typ: expr): stmt =
template create*(myQObject: var typ) = template create*(myQObject: var typ) =
var super = (typ.superType())(myQObject) var super = (typ.superType())(myQObject)
procCall create(super) procCall create(super)
@ -269,9 +304,10 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} =
#let superName = if superType.kind == nnkIdent: superType #let superName = if superType.kind == nnkIdent: superType
# else: superType.getNodeOf(nnkIdent) # else: superType.getNodeOf(nnkIdent)
if typ != nil: if typ != nil:
error("only support one type declaration") error("you may not define more than one type " &
"within the code block passed to this macro")
else: # without this else, it fails to compile else: # without this else, it fails to compile
typ = typeDecl.getTypeName typ = typeDecl
result.add it result.add it
result.add genSuperTemplate(typeDecl) result.add genSuperTemplate(typeDecl)
elif it.kind == nnkMethodDef: elif it.kind == nnkMethodDef:
@ -291,7 +327,13 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} =
elif it.kind == nnkProcDef: elif it.kind == nnkProcDef:
userDefined.add it userDefined.add it
elif it.kind == nnkCommand: elif it.kind == nnkCommand:
let cmdIdent = it[0] let bracket = it[0]
if bracket.kind != nnkBracketExpr:
error("do not know how to handle: \n" & repr(it))
# BracketExpr
# Ident !"QtProperty"
# Ident !"string"
let cmdIdent = bracket[0]
if cmdIdent == nil or cmdIdent.kind != nnkIdent or if cmdIdent == nil or cmdIdent.kind != nnkIdent or
($cmdIdent).toLower() != "qtproperty": ($cmdIdent).toLower() != "qtproperty":
error("do not know how to handle: \n" & repr(it)) error("do not know how to handle: \n" & repr(it))
@ -299,9 +341,12 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} =
else: else:
# everything else should pass through unchanged # everything else should pass through unchanged
result.add it result.add it
if typ == nil:
error("you must declare an object that inherits from QObject")
let typeName = typ.getTypeName()
## define onSlotCalled ## define onSlotCalled
var slotProto = (getAst declareOnSlotCalled(typ))[0] var slotProto = (getAst declareOnSlotCalled(typeName))[0]
var caseStmt = newNimNode(nnkCaseStmt) var caseStmt = newNimNode(nnkCaseStmt)
caseStmt.add ident("slotName") caseStmt.add ident("slotName")
for slot in slots: for slot in slots:
@ -344,9 +389,13 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} =
result.add slotProto result.add slotProto
# generate create method # generate create method
var createProto = (getAst prototypeCreate(typ))[0] var createProto = (getAst prototypeCreate(typeName))[0]
# the template creates loads of openSyms - replace these with idents # the template creates loads of openSyms - replace these with idents
createProto = doRemoveOpenSym(createProto) createProto = doRemoveOpenSym(createProto)
if typ.isExported:
createProto.exportDef()
else:
createProto.unexportDef()
var createBody = createProto.templateBody var createBody = createProto.templateBody
for slot in slots: for slot in slots:
let params = slot.params let params = slot.params
@ -363,19 +412,19 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} =
let call = newCall(regSigDot, newLit name, argTypesArray) let call = newCall(regSigDot, newLit name, argTypesArray)
createBody.add call createBody.add call
for property in properties: for property in properties:
#echo treeRepr property let bracket = property[0]
let infix = property[1] expectKind bracket, nnkBracketExpr
expectKind infix, nnkInfix #Command
# Infix # BracketExpr
# Ident !"of" # Ident !"QtProperty"
# Ident !"name"
# Ident !"string" # Ident !"string"
# Ident !"name"
let nimPropType = infix[2] # StmtList
let nimPropType = bracket[1]
let qtPropMeta = nim2QtMeta[$nimPropType] let qtPropMeta = nim2QtMeta[$nimPropType]
if qtPropMeta == nil: error($nimPropType & " not supported") if qtPropMeta == nil: error($nimPropType & " not supported")
let metaDot = newDotExpr(ident "QMetaType", ident qtPropMeta) let metaDot = newDotExpr(ident "QMetaType", ident qtPropMeta)
let propertyName = infix[1] let propertyName = property[1]
var read, write, notify: PNimrodNode var read, write, notify: PNimrodNode
let stmtList = property[2] let stmtList = property[2]
# fields # fields

View File

@ -4,6 +4,13 @@ THIS IS UNSTABLE AND ALPHA SOFTWARE
## Description ## Description
Qml bindings for both D and Nim programming languages Qml bindings for both D and Nim programming languages
## Documentation
The documentation for the Nim programming language can be
read [here](http://filcuc.github.io/DOtherSide/ "").
For the D programming language is an on going project
and pull request are accepted.
## Requirements ## Requirements
You need the following software: You need the following software:
* Qt 5.3 * Qt 5.3