Finish 0.2.0

This commit is contained in:
Filippo Cucchetto 2015-01-06 13:19:29 +01:00
commit b19d3510ff
23 changed files with 1031 additions and 95 deletions

249
Nim/Docs/NimQml.txt Normal file
View File

@ -0,0 +1,249 @@
:Authors:
Filippo Cucchetto <filippocucchetto@gmail.com>
Will Szumski <will@cowboycoders.org>
:Version: 0.2.0
:Date: 2015/01/02
Introduction
-----------
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
and the Nim imperative language.
The NimQml is made by two components:
* The DOtherSide C++ shared library
* The NimQml Nim module
This first component implements the glue code necessary for
communicating with the Qt C++ library, the latter module wraps
the libDOtherSide exported symbols in Nim
Building
--------
At the time of writing the DOtherSide C++ library must be compiled
and installed manually from source.
First clone the DOtherSide git repo
::
git clone https://github.com/filcuc/DOtherSide
than you can proceed with the common CMake build steps
::
mkdir build
cd build
cmake ..
make
If everything goes correctly, you'll have built both
the DOtherSide C++ library and the Nim examples
Installation
----------
The installation is not mandatory, in fact you could try
the built Nim example in the following way
::
cd path/to/build/dir
cd Nim/Examples/HelloWorld
export LD_LIBRARY_PATH=path/to/libDOtherSide.so
./HelloWorld
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
::
cd to/build/dir
make install
or by manually copying the library in your system lib directory
::
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
----------
As usual lets start with an HelloWorld example.
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
layer. The qml files contain the presentation layer and expose
the data in your nim files.
``Examples/HelloWorld/main.nim``
.. code-block:: nim
:file: ../Examples/HelloWorld/main.nim
``Examples/HelloWorld/main.qml``
.. code-block:: qml
:file: ../Examples/HelloWorld/main.qml
The following example shows the basic steps of each NimQml app
1. Create the QApplication for initializing the Qt runtime
2. Create the QQmlApplicationEngine and load your main .qml file
3. Call the ``exec`` proc of the QApplication instance for starting
the Qt event loop
Example 2: exposing data to Qml
------------------------------------
The previous example shown you how to create a simple application
window and how to startup the Qt event loop.
It's time to explore how to pass data to Qml, but lets see the
example code first:
``Examples/SimpleData/main.nim``
.. code-block:: nim
:file: ../Examples/SimpleData/main.nim
``Examples/SimpleData/main.qml``
.. code-block:: qml
:file: ../Examples/SimpleData/main.qml
The following example shows how to expose simple data types to Qml:
1. Create a QVariant and set its internal value.
2. Create a property in the Qml root context with a given name.
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 official Qt
documentation for more details about the engine and context objects)
At the time of writing the QVariant class support the following types:
* int
* string
* bool
* QObject derived classes
Example 3: exposing complex data and procedures to Qml
----------------------------------------------------------
As seen by the second example, simple data is fine. However most
applications need to expose complex data, functions and
update the view when something changes in the data layer.
This is achieved by creating an object that derives from QObject.
A QObject is made of :
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
3. ``Properties``: properties allow the passing of data to
the Qml view and make it aware of changes in the data layer
A QObject property is made of three things:
* a read slot: a method that returns the current value of the property
* a write slot: a method that sets the value of the property
* a notify signal: emitted when the current value of the property is changed
We'll start by looking at the main.nim file
``Examples/SlotsAndProperties/main.nim``
.. code-block:: nim
:file: ../Examples/SlotsAndProperties/main.nim
Here, nothing special happens except:
1. The creation of Contact object
2. The injection of the Contact object to the Qml root context
using the ``setContextProperty`` as seen in the previous
example
The Qml file is as follows:
``Examples/SlotsAndProperties/main.qml``
.. code-block:: qml
:file: ../Examples/SlotsAndProperties/main.qml
The qml is made up of: a Label, a TextInput widget, and a button.
The label displays the contact name - this automatically updates when
the contact name changes.
When clicked, the button updates the contact name with the text from
the TextInput widget.
So where's the magic?
The magic is in the Contact.nim file
``Examples/SlotsAndProperties/Contact.nim``
.. code-block:: nim
:file: ../Examples/SlotsAndProperties/Contact.nim
First we declare a QObject subclass and provide a simple
new method where we:
1. invoke the ``create()`` procedure. This invoke the C++ bridge and allocate
a QObject instance
2. register a slot ``getName`` for reading the Contact name field
3. register a slot ``setName`` for writing the Contact name
4. register a signal ``nameChanged`` for notify the contact name changes
5. register a property called ``name`` of type ``QString`` with the given
read, write slots and notify signal
Looking at the ``getName`` and ``setName`` methods, you can see that slots, as defined in Nim,
are nothing more than standard methods. The method corresponding to the ``setName`` slot
demonstrates how to use the ``emit`` method to emit a signal.
The last thing to consider is the override of the ``onSlotCalled`` method.
This method is called by the NimQml library when an invocation occurs from
the Qml side for one of the slots belonging to the QObject.
The usual implementation for the onSlotCalled method consists of a
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
0 of the args array.
Example 4: QtObject macro
-------------------------
The previous example shows how to create a simple QObject, however writing
all those ``register`` procs and writing the ``onSlotCalled`` method
becomes boring pretty soon.
Furthermore all this information can be automatically generated.
For this purpose you can import the NimQmlMacros module that provides
the QtObject macro.
Let's begin as usual with both the main.nim and main.qml files
``Examples/QtObjectMacro/main.nim``
.. code-block:: nim
:file: ../Examples/QtObjectMacro/main.nim
``Examples/QtObjectMacro/main.qml``
.. code-block:: qml
:file: ../Examples/QtObjectMacro/main.qml
Nothing is new in both the ``main.nim`` and ``main.qml`` with respect to
the previous example. What changed is the Contact object:
``Examples/QtObjectMacro/Contact.nim``
.. code-block:: nim
:file: ../Examples/QtObjectMacro/Contact.nim
In details:
1. Each QObject is defined inside the QtObject macro
2. Each slot is annotated with the ``{.slot.}`` pragma
3. Each signal is annotated with the ``{.signal.}`` pragma
4. Each property is created with the ``QtProperty`` macro
The ``QtProperty`` macro has the following syntax
.. code-block:: nim
QtProperty[typeOfProperty] nameOfProperty

View File

@ -1 +1,4 @@
add_subdirectory(Simple)
add_subdirectory(HelloWorld)
add_subdirectory(SimpleData)
add_subdirectory(SlotsAndProperties)
add_subdirectory(QtObjectMacro)

View File

@ -1,2 +1,2 @@
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_nim_executable(TARGET Main SOURCES main.nim PATHS ../../NimQml)
add_nim_executable(TARGET HelloWorld SOURCES main.nim PATHS ../../NimQml)

View File

@ -0,0 +1,19 @@
import NimQml
import macros
import typeinfo
proc mainProc() =
var app: QApplication
app.create()
defer: app.delete()
var engine: QQmlApplicationEngine
engine.create()
defer: engine.delete()
engine.load("main.qml")
app.exec()
when isMainModule:
mainProc()

View File

@ -0,0 +1,12 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
ApplicationWindow
{
width: 400
height: 300
title: "Hello World"
Component.onCompleted: visible = true
}

View File

@ -0,0 +1,2 @@
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_nim_executable(TARGET QtObjectMacro SOURCES main.nim PATHS ../../NimQml)

View File

@ -0,0 +1,27 @@
## Please note we are using templates where ordinarily we would like to use procedures
## due to bug: https://github.com/Araq/Nim/issues/1821
import NimQml, NimQmlMacros
QtObject:
type Contact* = ref object of QObject
m_name: string
template newContact*(): Contact =
var result = Contact(m_name: "initialName")
result.create
result
method getName*(contact: Contact): string {.slot.} =
result = contact.m_name
method nameChanged*(contact: Contact) {.signal.}
method setName*(contact: Contact, name: string) {.slot.} =
if contact.m_name != name:
contact.m_name = name
contact.nameChanged()
QtProperty[string] name:
read = getName
write = setName
notify = nameChanged

View File

@ -0,0 +1,27 @@
import NimQml
import Contact
proc mainProc() =
var app: QApplication
app.create()
defer: app.delete()
var contact = newContact()
defer: contact.delete()
var engine: QQmlApplicationEngine
engine.create()
defer: engine.delete()
var variant: QVariant
variant.create(contact)
defer: variant.delete()
var rootContext: QQmlContext = engine.rootContext()
rootContext.setContextProperty("contact", variant)
engine.load("main.qml")
app.exec()
when isMainModule:
mainProc()

View File

@ -16,7 +16,7 @@ ApplicationWindow
Label
{
text: "Current name is:" + myQObject.name
text: "Current name is:" + contact.name
}
TextField
@ -27,10 +27,7 @@ ApplicationWindow
Button
{
text: "Change Name"
onClicked: {
console.log("QML:", textField.text)
myQObject.name = textField.text
}
onClicked: contact.name = textField.text
}
}
}

View File

@ -1,54 +0,0 @@
import NimQml
import macros
import typeinfo
type MyQObject = ref object of QObject
m_name: string
method getName(myQObject: MyQObject): string =
result = myQObject.m_name
method setName(myQObject: MyQObject, name: string) =
if myQObject.m_name != name:
myQObject.m_name = name
myQObject.emit("nameChanged")
method onSlotCalled(myQObject: MyQObject, slotName: string, args: openarray[QVariant]) =
case slotName:
of "getName":
args[0].stringVal = myQObject.getName()
of "setName":
myQObject.setName(args[1].stringVal)
else:
discard()
proc mainProc() =
var app: QApplication
app.create()
defer: app.delete()
var myQObject = MyQObject()
myQObject.create()
defer: myQObject.delete()
myQObject.m_name = "InitialName"
myQObject.registerSlot("getName", [QMetaType.QString])
myQObject.registerSlot("setName", [QMetaType.Void, QMetaType.QString])
myQObject.registerSignal("nameChanged", [QMetaType.Void])
myQObject.registerProperty("name", QMetaType.QString, "getName", "setName", "nameChanged")
var engine: QQmlApplicationEngine
engine.create()
defer: engine.delete()
var variant: QVariant
variant.create(myQObject)
defer: variant.delete()
var rootContext: QQmlContext = engine.rootContext()
rootContext.setContextProperty("myQObject", variant)
engine.load("main.qml")
app.exec()
when isMainModule:
mainProc()

View File

@ -0,0 +1,2 @@
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_nim_executable(TARGET SimpleData SOURCES main.nim PATHS ../../NimQml)

View File

@ -0,0 +1,38 @@
import NimQml
import macros
import typeinfo
proc mainProc() =
var app: QApplication
app.create()
defer: app.delete()
var engine: QQmlApplicationEngine
engine.create()
defer: engine.delete()
var qVar1: QVariant
qVar1.create()
defer: qVar1.delete()
qVar1.intVal = 10
var qVar2: QVariant
qVar2.create()
defer: qVar2.delete()
qVar2.stringVal = "Hello World"
var qVar3: QVariant
qVar3.create()
defer: qVar3.delete()
qVar3.boolVal = false
engine.rootContext.setContextProperty("qVar1", qVar1)
engine.rootContext.setContextProperty("qVar2", qVar2)
engine.rootContext.setContextProperty("qVar3", qVar2)
engine.load("main.qml")
app.exec()
when isMainModule:
mainProc()

View File

@ -0,0 +1,20 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
ApplicationWindow
{
width: 400
height: 300
title: "SimpleData"
Component.onCompleted: visible = true
ColumnLayout
{
anchors.fill: parent
SpinBox { value: qVar1}
TextField { text: qVar2}
CheckBox { checked: qVar3}
}
}

View File

@ -0,0 +1,2 @@
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_nim_executable(TARGET SlotsAndProperties SOURCES main.nim PATHS ../../NimQml)

View File

@ -0,0 +1,33 @@
## Please note we are using templates where ordinarily we would like to use procedures
## due to bug: https://github.com/Araq/Nim/issues/1821
import NimQml
type Contact = ref object of QObject
m_name: string
template newContact*(): Contact =
var result = Contact(m_name: "initialName")
result.create()
result.m_name = "InitialName"
result.registerSlot("getName", [QMetaType.QString])
result.registerSlot("setName", [QMetaType.Void, QMetaType.QString])
result.registerSignal("nameChanged", [QMetaType.Void])
result.registerProperty("name", QMetaType.QString, "getName", "setName", "nameChanged")
result
method getName*(self: Contact): string =
result = self.m_name
method setName*(self: Contact, name: string) =
if self.m_name != name:
self.m_name = name
self.emit("nameChanged")
method onSlotCalled(self: Contact, slotName: string, args: openarray[QVariant]) =
case slotName:
of "getName":
args[0].stringVal = self.getName()
of "setName":
self.setName(args[1].stringVal)
else:
discard()

View File

@ -0,0 +1,27 @@
import NimQml
import Contact
proc mainProc() =
var app: QApplication
app.create()
defer: app.delete()
var contact = newContact()
defer: contact.delete()
var engine: QQmlApplicationEngine
engine.create()
defer: engine.delete()
var variant: QVariant
variant.create(contact)
defer: variant.delete()
var rootContext: QQmlContext = engine.rootContext()
rootContext.setContextProperty("contact", variant)
engine.load("main.qml")
app.exec()
when isMainModule:
mainProc()

View File

@ -0,0 +1,33 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
ApplicationWindow
{
width: 400
height: 300
Component.onCompleted: visible = true
ColumnLayout
{
anchors.fill: parent
Label
{
text: "Current name is:" + contact.name
}
TextField
{
id: textField
}
Button
{
text: "Change Name"
onClicked: contact.name = textField.text
}
}
}

View File

@ -1,13 +1,19 @@
import NimQmlTypes
import tables
## NimQml aims to provide binding to the QML for the Nim programming language
export QObject
export QApplication
export QVariant
export QQmlApplicationEngine
export QQmlContext
type QMetaType* {.pure.} = enum
type QMetaType* {.pure.} = enum ## \
## Qt metatypes values used for specifing the
## signals and slots argument and return types.
##
## This enum mimic the QMetaType::Type C++ enum
UnknownType = cint(0),
Bool = cint(1),
Int = cint(2),
@ -50,53 +56,65 @@ proc dos_qvariant_setString(variant: pointer, value: cstring) {.cdecl, dynlib:"l
proc dos_chararray_delete(rawCString: cstring) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create*(variant: var QVariant) =
## Create a new QVariant
var data: pointer
dos_qvariant_create(data)
variant = QVariant(data)
proc create*(variant: var QVariant, value: cint) =
## Create a new QVariant given a cint value
var data: pointer
dos_qvariant_create_int(data, value)
variant = QVariant(data)
proc create*(variant: var QVariant, value: bool) =
## Create a new QVariant given a bool value
var data: pointer
dos_qvariant_create_bool(data, value)
variant = QVariant(data)
proc create*(variant: var QVariant, value: string) =
## Create a new QVariant given a string value
var data: pointer
dos_qvariant_create_string(data, value)
variant = QVariant(data)
proc create*(variant: var QVariant, value: QObject) =
## Create a new QVariant given a QObject
var data: pointer
dos_qvariant_create_qobject(data, value.data)
variant = QVariant(data)
proc delete*(variant: QVariant) =
## Delete a QVariant
debugMsg("QVariant", "delete")
dos_qvariant_delete(pointer(variant))
proc isNull*(variant: QVariant): bool =
## Return true if the QVariant value is null, false otherwise
dos_qvariant_isnull(pointer(variant), result)
proc intVal*(variant: QVariant): int =
## Return the QVariant value as int
var rawValue: cint
dos_qvariant_toInt(pointer(variant), rawValue)
result = cast[int](rawValue)
proc `intVal=`*(variant: QVariant, value: int) =
## Sets the QVariant value int value
var rawValue = cast[cint](value)
dos_qvariant_setInt(pointer(variant), rawValue)
proc boolVal*(variant: QVariant): bool =
## Return the QVariant value as bool
dos_qvariant_toBool(pointer(variant), result)
proc `boolVal=`*(variant: QVariant, value: bool) =
## Sets the QVariant bool value
dos_qvariant_setBool(pointer(variant), value)
proc stringVal*(variant: QVariant): string =
## Return the QVariant value as string
var rawCString: cstring
var rawCStringLength: cint
dos_qvariant_toString(pointer(variant), rawCString, rawCStringLength)
@ -104,6 +122,7 @@ proc stringVal*(variant: QVariant): string =
dos_chararray_delete(rawCString)
proc `stringVal=`*(variant: QVariant, value: string) =
## Sets the QVariant string value
dos_qvariant_setString(pointer(variant), value)
@ -114,19 +133,23 @@ proc dos_qqmlapplicationengine_context(engine: pointer, context: var pointer) {.
proc dos_qqmlapplicationengine_delete(engine: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create*(engine: var QQmlApplicationEngine) =
## Create an new QQmlApplicationEngine
var temp: pointer
dos_qqmlapplicationengine_create(temp)
engine = QQmlApplicationEngine(temp)
proc load*(engine: QQmlApplicationEngine, filename: cstring) =
## Load the given Qml file
dos_qqmlapplicationengine_load(pointer(engine), filename)
proc rootContext*(engine: QQmlApplicationEngine): QQmlContext =
## Return the engine root context
var context: pointer
dos_qqmlapplicationengine_context(pointer(engine), context)
result = cast[QQmlContext](context)
proc delete*(engine: QQmlApplicationEngine) =
## Delete the given QQmlApplicationEngine
debugMsg("QQmlApplicationEngine", "delete")
dos_qqmlapplicationengine_delete(pointer(engine))
@ -134,6 +157,7 @@ proc delete*(engine: QQmlApplicationEngine) =
proc dos_qqmlcontext_setcontextproperty(context: pointer, propertyName: cstring, propertyValue: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc setContextProperty*(context: QQmlContext, propertyName: string, propertyValue: QVariant) =
## Sets a new property with the given value
dos_qqmlcontext_setcontextproperty(pointer(context), propertyName, pointer(propertyValue))
# QApplication
@ -142,21 +166,31 @@ proc dos_qguiapplication_exec() {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc dos_qguiapplication_delete() {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create*(application: QApplication) =
debugMsg("QApplication", "create")
## Create a new QApplication
dos_qguiapplication_create()
proc exec*(application: QApplication) =
debugMsg("QApplication", "exec")
## Start the Qt event loop
dos_qguiapplication_exec()
proc delete*(application: QApplication) =
debugMsg("QApplication", "delete")
## Delete the given QApplication
dos_qguiapplication_delete()
# QObject
type QVariantArray {.unchecked.} = array[0..0, QVariant]
type QVariantArrayPtr = ptr QVariantArray
proc toVariantSeq(args: QVariantArrayPtr, numArgs: cint): seq[QVariant] =
result = @[]
for i in 0..numArgs-1:
result.add(args[i])
proc toCIntSeq(metaTypes: openarray[QMetaType]): seq[cint] =
result = @[]
for metaType in metaTypes:
result.add(cint(metaType))
proc dos_qobject_create(qobject: var pointer, nimobject: pointer, qobjectCallback: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc dos_qobject_delete(qobject: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc dos_qobject_slot_create(qobject: pointer, slotName: cstring, argumentsCount: cint, argumentsMetaTypes: ptr cint, slotIndex: var cint) {.cdecl, dynlib:"libDOtherSide.so", importc.}
@ -164,40 +198,31 @@ proc dos_qobject_signal_create(qobject: pointer, signalName: cstring, argumentsC
proc dos_qobject_signal_emit(qobject: pointer, signalName: cstring, argumentsCount: cint, arguments: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc dos_qobject_property_create(qobject: pointer, propertyName: cstring, propertyType: cint, readSlot: cstring, writeSlot: cstring, notifySignal: cstring) {.cdecl, dynlib:"libDOtherSide.so", importc.}
method onSlotCalled*(nimobject: QObject, slotName: string, args: openarray[QVariant]) =
debugMsg("QObject", "onSlotCalled", "begin")
debugMsg("QObject", "onSlotCalled", "end")
method onSlotCalled*(nimobject: QObject, slotName: string, args: openarray[QVariant]) =
## Called from the NimQml bridge when a slot is called from Qml.
## Subclasses can override the given method for handling the slot call
discard()
proc toVariantSeq(args: QVariantArrayPtr, numArgs: cint): seq[QVariant] =
result = @[]
for i in 0..numArgs-1:
result.add(args[i])
proc qobjectCallback(nimobject: pointer, slotName: QVariant, numArguments: cint, arguments: QVariantArrayPtr) {.exportc.} =
debugMsg("QObject", "qobjectCallback", "begin")
proc qobjectCallback(nimobject: pointer, slotName: QVariant, numArguments: cint, arguments: QVariantArrayPtr) {.exportc.} =
var nimQObjectCasted = cast[ptr QObject](nimobject)
# forward to the QObject subtype instance
nimQObjectCasted[].onSlotCalled(slotName.stringVal, arguments.toVariantSeq(numArguments))
debugMsg("QObject", "qobjectCallback", "end")
proc toCIntSeq(metaTypes: openarray[QMetaType]): seq[cint] =
result = @[]
for metaType in metaTypes:
result.add(cint(metaType))
proc create*(qobject: var QObject) =
## Create a new QObject
qobject.name = "QObject"
qobject.slots = initTable[string,cint]()
qobject.signals = initTable[string, cint]()
dos_qobject_create(qobject.data, addr(qobject), qobjectCallback)
proc delete*(qobject: QObject) =
debugMsg("QObject", "delete")
## Delete the given QObject
dos_qobject_delete(qobject.data)
proc registerSlot*(qobject: var QObject,
slotName: string,
metaTypes: openarray[QMetaType]) =
## Register a slot in the QObject with the given name and signature
# Copy the metatypes array
var copy = toCIntSeq(metatypes)
var index: cint
@ -207,6 +232,7 @@ proc registerSlot*(qobject: var QObject,
proc registerSignal*(qobject: var QObject,
signalName: string,
metatypes: openarray[QMetaType]) =
## Register a signal in the QObject with the given name and signature
var index: cint
if metatypes.len > 0:
var copy = toCIntSeq(metatypes)
@ -220,10 +246,12 @@ proc registerProperty*(qobject: var QObject,
propertyType: QMetaType,
readSlot: string,
writeSlot: string,
notifySignal: string) =
notifySignal: string) =
## Register a property in the QObject with the given name and type.
dos_qobject_property_create(qobject.data, propertyName, cast[cint](propertyType), readSlot, writeSlot, notifySignal)
proc emit*(qobject: QObject, signalName: string, args: openarray[QVariant] = []) =
## Emit the signal with the given name and values
if args.len > 0:
var copy: seq[QVariant]
for i in 0..args.len-1:
@ -239,23 +267,27 @@ proc dos_qquickview_show(view: pointer) {.cdecl, dynlib:"libDOtherSide.so", impo
proc dos_qquickview_source(view: pointer, filename: var cstring, length: var int) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc dos_qquickview_set_source(view: pointer, filename: cstring) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create(view: var QQuickView) =
proc create(view: var QQuickView) =
## Create a new QQuickView
var temp: pointer
dos_qquickview_create(temp)
view = QQuickView(temp)
proc source(view: QQuickView): cstring =
## Return the source Qml file loaded by the view
var length: int
dos_qquickview_source(pointer(view), result, length)
proc `source=`(view: QQuickView, filename: cstring) =
## Sets the source Qml file laoded by the view
dos_qquickview_set_source(pointer(view), filename)
proc show(view: QQuickView) =
## Sets the view visible
dos_qquickview_show(pointer(view))
proc delete(view: QQuickView) =
debugMsg("QQuickView", "delete")
proc delete(view: QQuickView) =
## Delete the given QQuickView
dos_qquickview_delete(pointer(view))

View File

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

460
Nim/NimQml/NimQmlMacros.nim Normal file
View File

@ -0,0 +1,460 @@
## Contains helper macros for NimQml
import macros
import strutils
import typetraits
import tables
template debug(body: stmt): stmt =
{.push warning[user]: off.}
when defined(debug):
{.pop.}
body
else:
{.pop.}
let nimFromQtVariant {.compileTime.} = {
"int" : "intVal",
"string" : "stringVal",
"bool" : "boolVal",
}.toTable
let nim2QtMeta {.compileTime.} = {
"bool": "Bool",
"int" : "Int",
"string" : "QString",
"pointer" : "VoidStar",
"QVariant": "QVariant",
"" : "Void", # no return, which is represented by an nnkEmpty node
}.toTable
proc getNodeOf*(tree: PNimrodNode, kind: TNimrodNodeKind): PNimrodNode {.compileTime.} =
## recursively looks for a node of kind, ``kind``, in the tree provided as ``tree``
## Returnsthe first node that satisfies this condition
for i in 0.. <tree.len:
var child = tree[i]
if child.kind == kind:
return child
var candidate = getNodeOf(child, kind)
if not candidate.isNil:
return candidate
static:
type Context* = ref object of RootObj
type NullContext* = ref object of Context
type NodeModifier*[T] = proc(context: T, a: var PNimrodNode): PNimrodNode
# had to remove type bound on hook due to recent regression with generics
proc hookOnNode*[T](context: T, code: PNimrodNode, hook: NodeModifier,
recursive: bool = false): PNimrodNode {.compileTime.} =
## Iterates over the tree, ``code``, calling ``hook`` on each ``PNimrodNode``
## encountered. If ``recursive`` is true, it will recurse over the tree, otherwise
## it will only visit ``code``'s children. ``hook`` should return a replacement for
## the node that was passed in via it's return value. `hook` may return nil to remove
## the node from the tree.
if code.len == 0:
return code
var newCode = newNimNode(code.kind)
for i in 0.. <code.len:
var child = code[i].copy()
child = hook(context, child)
if recursive:
child = hookOnNode(context,child,hook,true)
if child != nil:
newCode.add child
return newCode
proc removeOpenSym*(context: NullContext,
a: var PNimrodNode): PNimrodNode {.compileTime.} =
## replaces: ``nnkOpenSymChoice`` and ``nnkSym`` nodes with idents
## corresponding to the symbols string representation.
if a.kind == nnkOpenSymChoice:
return ident($a[0].symbol)
elif a.kind == nnkSym:
return ident($a.symbol)
return a
proc newTemplate*(name = newEmptyNode();
params: openArray[PNimrodNode] = [newEmptyNode()];
body: PNimrodNode = newStmtList()): PNimrodNode {.compileTime.} =
## shortcut for creating a new template
##
## The ``params`` array must start with the return type of the template,
## followed by a list of IdentDefs which specify the params.
result = newNimNode(nnkTemplateDef).add(
name,
newEmptyNode(),
newEmptyNode(),
newNimNode(nnkFormalParams).add(params), ##params
newEmptyNode(), ## pragmas
newEmptyNode(),
body)
#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] =
parent
proc getTypeName*(a: PNimrodNode): PNimrodNode {.compileTime.} =
## returns the node containing the name of an object in a
## given type definition block
expectMinLen a, 1
expectKind a, nnkTypeDef
var testee = a
if testee[0].kind == nnkPragmaExpr:
testee = testee[0]
if testee[0].kind in {nnkIdent}:
return testee[0]
elif testee[0].kind in {nnkPostfix}:
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.} =
## generates a template, with name: superType, that returns the super type
## of the object defined in the type defintion, ``typeDecl``. ``typeDecl``
## must contain an object inheriting from a base type.
expectKind typeDecl, nnkTypeDef
let inheritStmt = typeDecl.getNodeOf(nnkOfInherit)
let typeName = getTypeName(typeDecl)
if inheritStmt == nil: error("you must declare a super type for " & $typeName)
# ident of superType (have to deal with generics)
let superType = if inheritStmt[0].kind == nnkIdent: inheritStmt[0]
else: inheritStmt[0].getNodeOf(nnkIdent)
let superTemplate = getAst declareSuperTemplate(superType, typeName)
result = superTemplate[0]
if typeDecl.isExported():
result.exportDef()
else:
result.unexportDef()
proc getSuperType*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} =
## returns ast containing superType info, may not be an ident if generic
let inheritStmt = typeDecl.getNodeOf(nnkOfInherit)
if inheritStmt.isNil: return newEmptyNode()
return inheritStmt[0]
proc getPragmaName*(child: PNimrodNode): PNimrodNode {.compileTime.} =
## name of child in a nnkPragma section
if child.kind == nnkIdent:
return child
# assumes first ident is name of pragma
let ident = child.getNodeOf(nnkIdent)
result = ident
proc removePragma*(pragma: PNimrodNode, toRemove: string): PNimrodNode {.compileTime.} =
## removes a pragma from pragma definition, `pragma`, with name `toRemove`
expectKind pragma, nnkPragma
result = newNimNode(nnkPragma)
for i in 0.. <pragma.len:
let child = pragma[i]
if $child.getPragmaName == toRemove:
continue
result.add child
if result.len == 0:
return newEmptyNode()
proc hasPragma*(node: PNimrodNode, pragmaName: string): bool {.compileTime.} =
## Returns ``true`` iff the method, or proc definition: ``node``, has a pragma
## ``pragmaName``
doAssert node.kind in {nnkMethodDef, nnkProcDef}
result = false
let pragma = node.pragma
if pragma.kind == nnkEmpty:
# denotes no pragma set
return false
for child in pragma.children():
if $child.getPragmaName() == pragmaName:
return true
proc getArgType*(arg: PNimrodNode): PNimrodNode {.compileTime.} =
## returns the ``PNimrodNode`` representing a parameters type
if arg[1].kind == nnkIdent:
arg[1]
else:
arg[1].getNodeOf(nnkIdent)
proc getArgName*(arg: PNimrodNode): PNimrodNode {.compileTime.} =
## returns the ``PNimrodNode`` representing a parameters name
if arg[0].kind == nnkIdent:
arg[0]
else:
arg[0].getNodeOf(nnkIdent)
proc addSignalBody(signal: PNimrodNode): PNimrodNode {.compileTime.} =
# e.g: produces: emit(MyQObject, "nameChanged")
expectKind signal, nnkMethodDef
result = newStmtList()
# if exported, will use postfix
let name = if signal.name.kind == nnkIdent: signal.name else: signal.name[1]
let params = signal.params
# type signal defined on is the 1st arg
let self = getArgName(params[1])
var args = newSeq[PNimrodNode]()
args.add(self)
args.add newLit($name)
if params.len > 2: # more args than just type
for i in 2.. <params.len:
args.add getArgName params[i]
result.add newCall("emit", args)
#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]) =
discard
#FIXME: changed parent, typ from typedesc to expr to workaround Nim issue #1874
template prototypeCreate(typ: expr): stmt =
template create*(myQObject: var typ) =
var super = (typ.superType())(myQObject)
procCall create(super)
proc doRemoveOpenSym(a: var PNimrodNode): PNimrodNode {.compileTime.} =
hookOnNode(NullContext(),a, removeOpenSym, true)
proc templateBody*(a: PNimrodNode): PNimrodNode {.compileTime.} =
expectKind a, nnkTemplateDef
result = a[6]
proc genArgTypeArray(params: PNimrodNode): PNimrodNode {.compileTime.} =
expectKind params, nnkFormalParams
result = newNimNode(nnkBracket)
for i in 0 .. <params.len:
if i == 1:
# skip "self" param eg: myQObject: MyQObject
continue
let pType = if i != 0: getArgType params[i] else: params[i]
let pTypeString = if pType.kind == nnkEmpty: "" else: $pType
# function that maps Qvariant type to nim type
let qtMeta = nim2QtMeta[pTypeString]
if qtMeta == nil: error(pTypeString & " not supported yet")
let metaDot = newDotExpr(ident "QMetaType", ident qtMeta)
result.add metaDot
proc getIdentDefName*(a: PNimrodNode): PNimrodNode {.compileTime.} =
## returns object field name from ident def
expectKind a, nnkIdentDefs
if a[0].kind == nnkIdent:
return a[0]
elif a[0].kind == nnkPostFix:
return a[0][1]
macro QtObject*(qtDecl: stmt): stmt {.immediate.} =
## Generates boiler plate code for registering signals, slots
## and properties.
##
## Currently generates:
## - create: a method to register signal, slots and properties
## - superType: a template that returns the super type of the
## object defined within the macro body
## - onSlotCalled: a method to dispatch an on slot call to the
## appropiate method.
##
## Current limitations:
## - only one type can be defined within the body of code sent to the
## the macro. It is assumed, but not checked, that somewhere in the
## inheritance hierarchy this object derives from ``QObject``.
## - generics are not currently supported
expectKind(qtDecl, nnkStmtList)
#echo treeRepr qtDecl
result = newStmtList()
var slots = newSeq[PNimrodNode]()
var properties = newSeq[PNimrodNode]()
var signals = newSeq[PNimrodNode]()
# holds all user defined procedures so we can add them after create
var userDefined = newSeq[PNimrodNode]()
# assume only one type per section for now
var typ: PNimrodNode
for it in qtDecl.children():
if it.kind == nnkTypeSection:
let typeDecl = it.findChild(it.kind == nnkTypeDef)
let superType = typeDecl.getSuperType()
if superType.kind == nnkEmpty:
# allow simple types and type aliases
result.add it
else:
# may come in useful if we want to check objects inherit from QObject
#let superName = if superType.kind == nnkIdent: superType
# else: superType.getNodeOf(nnkIdent)
if typ != nil:
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
typ = typeDecl
result.add it
result.add genSuperTemplate(typeDecl)
elif it.kind == nnkMethodDef:
if it.hasPragma("slot"):
let pragma = it.pragma()
it.pragma = pragma.removePragma("slot")
slots.add it # we need to gensome code later
result.add it
elif it.hasPragma("signal"):
let pragma = it.pragma()
it.pragma = pragma.removePragma("signal")
it.body = addSignalBody(it)
result.add it
signals.add it
else:
userDefined.add it
elif it.kind == nnkProcDef:
userDefined.add it
elif it.kind == nnkCommand:
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
($cmdIdent).toLower() != "qtproperty":
error("do not know how to handle: \n" & repr(it))
properties.add it
else:
# everything else should pass through unchanged
result.add it
if typ == nil:
error("you must declare an object that inherits from QObject")
let typeName = typ.getTypeName()
## define onSlotCalled
var slotProto = (getAst declareOnSlotCalled(typeName))[0]
var caseStmt = newNimNode(nnkCaseStmt)
caseStmt.add ident("slotName")
for slot in slots:
var ofBranch = newNimNode(nnkOfBranch)
# for exported procedures - strip * marker
let slotName = ($slot.name).replace("*","")
ofBranch.add newLit slotName
let params = slot.params
let hasReturn = not (params[0].kind == nnkEmpty)
var branchStmts = newStmtList()
var args = newSeq[PNimrodNode]()
# first params always the object
args.add ident "myQObject"
for i in 2.. <params.len:
let pType = getArgType params[i]
# function that maps Qvariant type to nim type
let mapper = nimFromQtVariant[$pType]
let argAccess = newNimNode(nnkBracketExpr)
.add (ident "args")
.add newIntLitNode(i-1)
let dot = newDotExpr(argAccess, ident mapper)
args.add dot
var call = newCall(ident slotName, args)
if hasReturn:
# eg: args[0].strVal = getName(myQObject)
let retType = params[0]
let mapper = nimFromQtVariant[$retType]
let argAccess = newNimNode(nnkBracketExpr)
.add (ident "args")
.add newIntLitNode(0)
let dot = newDotExpr(argAccess, ident mapper)
call = newAssignment(dot, call)
branchStmts.add call
ofBranch.add branchStmts
caseStmt.add ofBranch
# add else: discard
caseStmt.add newNimNode(nnkElse)
.add newStmtList().add newNimNode(nnkDiscardStmt).add newNimNode(nnkEmpty)
slotProto.body = newStmtList().add caseStmt
result.add slotProto
# generate create method
var createProto = (getAst prototypeCreate(typeName))[0]
# the template creates loads of openSyms - replace these with idents
createProto = doRemoveOpenSym(createProto)
if typ.isExported:
createProto.exportDef()
else:
createProto.unexportDef()
var createBody = createProto.templateBody
for slot in slots:
let params = slot.params
let regSlotDot = newDotExpr(ident "myQObject", ident "registerSlot")
let name = ($slot.name).replace("*","")
let argTypesArray = genArgTypeArray(params)
let call = newCall(regSlotDot, newLit name, argTypesArray)
createBody.add call
for signal in signals:
let params = signal.params
let regSigDot = newDotExpr(ident "myQObject", ident "registerSignal")
let name = ($signal.name).replace("*","")
let argTypesArray = genArgTypeArray(params)
let call = newCall(regSigDot, newLit name, argTypesArray)
createBody.add call
for property in properties:
let bracket = property[0]
expectKind bracket, nnkBracketExpr
#Command
# BracketExpr
# Ident !"QtProperty"
# Ident !"string"
# Ident !"name"
# StmtList
let nimPropType = bracket[1]
let qtPropMeta = nim2QtMeta[$nimPropType]
if qtPropMeta == nil: error($nimPropType & " not supported")
let metaDot = newDotExpr(ident "QMetaType", ident qtPropMeta)
let propertyName = property[1]
var read, write, notify: PNimrodNode
let stmtList = property[2]
# fields
# StmtList
# Asgn
# Ident !"read"
# Ident !"getName
for asgn in stmtList.children:
let name = asgn[0]
case $name
of "read":
read = asgn[1]
of "write":
write = asgn[1]
of "notify":
notify = asgn[1]
else:
error("unknown property field: " & $name)
let regPropDot = newDotExpr(ident "myQObject", ident "registerProperty")
let readArg = if read == nil: newNilLit() else: newLit($read)
let writeArg = if write == nil: newNilLit() else: newLit($write)
let notifyArg = if notify == nil: newNilLit() else: newLit($notify)
let call = newCall(regPropDot, newLit($propertyName), metaDot, readArg, writeArg, notifyArg)
createBody.add call
#echo repr createProto
result.add createProto
for fn in userDefined:
result.add fn
debug:
echo repr result

View File

@ -1,14 +1,14 @@
import tables
type
QVariant* = distinct pointer
QQmlApplicationEngine* = distinct pointer
QQmlContext* = distinct pointer
QApplication* = distinct pointer
QObject* {.inheritable.} = ref object of RootObj
QVariant* = distinct pointer ## A QVariant
QQmlApplicationEngine* = distinct pointer ## A QQmlApplicationEngine
QQmlContext* = distinct pointer ## A QQmlContext
QApplication* = distinct pointer ## A QApplication
QObject* {.inheritable.} = ref object of RootObj ## A QObject
name*: string
data*: pointer
slots*: Table[string, cint]
signals*: Table[string, cint]
properties*: Table[string, cint]
QQuickView* = distinct pointer
QQuickView* = distinct pointer ## A QQuickView

View File

@ -23,7 +23,7 @@ function(add_nim_executable )
# add target to trigger the nimrod compiler
add_custom_target(
nim ALL
${ARGS_TARGET} ALL
COMMAND
${NIM_EXECUTABLE} "c" ${in_paths} "--nimcache:" ${DIRECTORY} "--out:" ${nim_target} ${in_files}
DEPENDS

View File

@ -4,6 +4,13 @@ THIS IS UNSTABLE AND ALPHA SOFTWARE
## Description
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
You need the following software:
* Qt 5.3