From b02540d1c142300a1d570c4e659dd58b29a12418 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 2 Jan 2015 19:35:37 +0000 Subject: [PATCH 01/25] add NimQmlMacros.nim --- Nim/NimQml/NimQmlMacros.nim | 360 ++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 Nim/NimQml/NimQmlMacros.nim diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim new file mode 100644 index 0000000..8babe18 --- /dev/null +++ b/Nim/NimQml/NimQmlMacros.nim @@ -0,0 +1,360 @@ +## Contains helper macros for NimQml + +import macros +import strutils +import typetraits +import tables + +static: + let nimFromQtVariant = { + "int" : "intVal", + "string" : "stringVal", + "bool" : "boolVal" , + }.toTable + + let nim2QtMeta = { + "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.. 2: # more args than just type + for i in 2.. Date: Fri, 2 Jan 2015 20:07:48 +0000 Subject: [PATCH 02/25] export QtType macro --- Nim/NimQml/NimQml.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Nim/NimQml/NimQml.nim b/Nim/NimQml/NimQml.nim index ecd36e2..1b9807c 100644 --- a/Nim/NimQml/NimQml.nim +++ b/Nim/NimQml/NimQml.nim @@ -1,11 +1,13 @@ import NimQmlTypes import tables +from NimQmlMacros import QtType export QObject export QApplication export QVariant export QQmlApplicationEngine export QQmlContext +export QtType type QMetaType* {.pure.} = enum UnknownType = cint(0), From b4083f1b84ae95c021847f0bb4d6e9ae8c7aecfc Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 2 Jan 2015 21:26:30 +0000 Subject: [PATCH 03/25] work around compiler bug where {.compileTime.} tables are flagged up as not being computable at compile time when a macro using them is imported into another module --- Nim/NimQml/NimQmlMacros.nim | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 8babe18..4cf4177 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -5,14 +5,18 @@ import strutils import typetraits import tables -static: - let nimFromQtVariant = { +# these are defined in a template to work around a compiler bug +# with {.compileTime.}/static variables not being "computable at +# compile time" when a macro using them is imported into another +# module +template defineMappings = + let nimFromQtVariant {.inject.} = { "int" : "intVal", "string" : "stringVal", "bool" : "boolVal" , }.toTable - let nim2QtMeta = { + let nim2QtMeta {.inject.} = { "bool": "Bool", "int " : "Int", "string" : "QString", @@ -198,6 +202,7 @@ proc templateBody*(a: PNimrodNode): PNimrodNode {.compileTime.} = result = a[6] proc genArgTypeArray(params: PNimrodNode): PNimrodNode {.compileTime.} = + defineMappings expectKind params, nnkFormalParams result = newNimNode(nnkBracket) for i in 0 .. Date: Sat, 3 Jan 2015 19:49:59 +0000 Subject: [PATCH 04/25] fixed some warnings, added debug template (compile with -d debug), make QtType macro pass through definitions we do not explicitly handle --- Nim/NimQml/NimQmlMacros.nim | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 4cf4177..c89bf01 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -5,6 +5,14 @@ import strutils import typetraits import tables +template debug(body: stmt): stmt = + {.push warning[user]: off.} + when defined(debug): + {.pop.} + body + else: + {.pop.} + # these are defined in a template to work around a compiler bug # with {.compileTime.}/static variables not being "computable at # compile time" when a macro using them is imported into another @@ -30,7 +38,7 @@ proc getNodeOf*(tree: PNimrodNode, kind: TNimrodNodeKind): PNimrodNode {.compile ## Returnsthe first node that satisfies this condition for i in 0.. Date: Sat, 3 Jan 2015 19:56:22 +0000 Subject: [PATCH 05/25] turns out {.compileTime.} tables work, but ones decalred in static block do not --- Nim/NimQml/NimQmlMacros.nim | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index c89bf01..b1fd6a3 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -13,25 +13,21 @@ template debug(body: stmt): stmt = else: {.pop.} -# these are defined in a template to work around a compiler bug -# with {.compileTime.}/static variables not being "computable at -# compile time" when a macro using them is imported into another -# module -template defineMappings = - let nimFromQtVariant {.inject.} = { - "int" : "intVal", - "string" : "stringVal", - "bool" : "boolVal" , - }.toTable - - let nim2QtMeta {.inject.} = { - "bool": "Bool", - "int " : "Int", - "string" : "QString", - "pointer" : "VoidStar", - "QVariant": "QVariant", - "" : "Void", # no return, which is represented by an nnkEmpty node - }.toTable + +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`` @@ -210,7 +206,6 @@ proc templateBody*(a: PNimrodNode): PNimrodNode {.compileTime.} = result = a[6] proc genArgTypeArray(params: PNimrodNode): PNimrodNode {.compileTime.} = - defineMappings expectKind params, nnkFormalParams result = newNimNode(nnkBracket) for i in 0 .. Date: Sun, 4 Jan 2015 19:22:05 +0000 Subject: [PATCH 06/25] initial support for user defined helpers from within QtType. Implemented by delaying the definition of user deined items (procs, methods, etc.) until after create. This was necessary as create is currently defined as a template and we can't use a forward declaration. --- Nim/NimQml/NimQmlMacros.nim | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index b1fd6a3..8dca486 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -13,7 +13,6 @@ template debug(body: stmt): stmt = else: {.pop.} - let nimFromQtVariant {.compileTime.} = { "int" : "intVal", "string" : "stringVal", @@ -235,6 +234,8 @@ macro QtType*(qtDecl: stmt): stmt {.immediate.} = 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(): @@ -267,6 +268,10 @@ macro QtType*(qtDecl: stmt): stmt {.immediate.} = it.body = addSignalBody(it) result.add it signals.add it + else: + userDefined.add it + elif it.kind == nnkProcDef: + userDefined.add it else: # everything else should pass through unchanged result.add it @@ -368,5 +373,9 @@ macro QtType*(qtDecl: stmt): stmt {.immediate.} = #echo repr createProto result.add createProto + + for fn in userDefined: + result.add fn + debug: echo repr result From e5888a989f98d686ce86190b6a54ee91e298cb73 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Sun, 4 Jan 2015 20:52:43 +0000 Subject: [PATCH 07/25] Changed property syntax to filcuc's suggestion: e.g QtProperty name of string: --- Nim/NimQml/NimQmlMacros.nim | 57 ++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 8dca486..875f9b6 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -246,16 +246,14 @@ macro QtType*(qtDecl: stmt): stmt {.immediate.} = # allow simple types and type aliases result.add it else: - let superName = if superType.kind == nnkIdent: superType else: superType.getNodeOf(nnkIdent) - if $superName != "QtProperty": - if typ != nil: - error("only support one type declaration") + # 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("only support one type declaration") + else: # without this else, it fails to compile typ = typeDecl.getTypeName result.add it result.add genSuperTemplate(typeDecl) - else: - # process later - properties.add(it) elif it.kind == nnkMethodDef: if it.hasPragma("slot"): let pragma = it.pragma() @@ -272,6 +270,12 @@ macro QtType*(qtDecl: stmt): stmt {.immediate.} = userDefined.add it elif it.kind == nnkProcDef: userDefined.add it + elif it.kind == nnkCommand: + let cmdIdent = it[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 @@ -339,36 +343,43 @@ macro QtType*(qtDecl: stmt): stmt {.immediate.} = let call = newCall(regSigDot, newLit name, argTypesArray) createBody.add call for property in properties: - let inherit = property.getNodeOf(nnkOfInherit) - # OfInherit - # BracketExpr - # Ident !"QtProperty" - # Ident !"string" - let nimPropType = inherit[0][1] + #echo treeRepr property + let infix = property[1] + expectKind infix, nnkInfix + # Infix + # Ident !"of" + # Ident !"name" + # Ident !"string" + + let nimPropType = infix[2] let qtPropMeta = nim2QtMeta[$nimPropType] if qtPropMeta == nil: error($nimPropType & " not supported") let metaDot = newDotExpr(ident "QMetaType", ident qtPropMeta) - let typeDef = property.getNodeOf(nnkTypeDef) - let typeName = typeDef.getTypeName() + let propertyName = infix[1] var read, write, notify: PNimrodNode + let stmtList = property[2] # fields - let recList = typeDef.getNodeof(nnkRecList) - for identDef in recList.children: - let name = identDef.getIdentDefName() + # StmtList + # Asgn + # Ident !"read" + # Ident !"getName + for asgn in stmtList.children: + echo treerepr asgn + let name = asgn[0] case $name of "read": - read = identDef[2] + read = asgn[1] of "write": - write = identDef[2] + write = asgn[1] of "notify": - notify = identDef[2] + notify = asgn[1] else: - error("unknown property field: " & $name) + 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($typeName), metaDot, readArg, writeArg, notifyArg) + let call = newCall(regPropDot, newLit($propertyName), metaDot, readArg, writeArg, notifyArg) createBody.add call #echo repr createProto From 62ecd19e608b47d82efa214950997873ae1ac556 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Sun, 4 Jan 2015 20:56:35 +0000 Subject: [PATCH 08/25] renamed QtType macro to QtObject --- Nim/NimQml/NimQml.nim | 2 -- Nim/NimQml/NimQmlMacros.nim | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Nim/NimQml/NimQml.nim b/Nim/NimQml/NimQml.nim index 1b9807c..ecd36e2 100644 --- a/Nim/NimQml/NimQml.nim +++ b/Nim/NimQml/NimQml.nim @@ -1,13 +1,11 @@ import NimQmlTypes import tables -from NimQmlMacros import QtType export QObject export QApplication export QVariant export QQmlApplicationEngine export QQmlContext -export QtType type QMetaType* {.pure.} = enum UnknownType = cint(0), diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 875f9b6..f55ca53 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -227,7 +227,7 @@ proc getIdentDefName*(a: PNimrodNode): PNimrodNode {.compileTime.} = elif a[0].kind == nnkPostFix: return a[0][1] -macro QtType*(qtDecl: stmt): stmt {.immediate.} = +macro QtObject*(qtDecl: stmt): stmt {.immediate.} = expectKind(qtDecl, nnkStmtList) #echo treeRepr qtDecl result = newStmtList() From 010fc23261ac727207f5d80351dd125132528d80 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Sun, 4 Jan 2015 21:05:17 +0000 Subject: [PATCH 09/25] remove debugging print --- Nim/NimQml/NimQmlMacros.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index f55ca53..25107e2 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -364,7 +364,6 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = # Ident !"read" # Ident !"getName for asgn in stmtList.children: - echo treerepr asgn let name = asgn[0] case $name of "read": From cdf02a425180d9ed4f534195d2cccb69f5e9bd9f Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Sun, 4 Jan 2015 21:24:48 +0000 Subject: [PATCH 10/25] documentation improvements --- Nim/NimQml/NimQmlMacros.nim | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 25107e2..33fecd1 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -50,8 +50,9 @@ 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. + ## 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) @@ -64,16 +65,18 @@ proc hookOnNode*[T](context: T, code: PNimrodNode, hook: NodeModifier, 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. +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()]; +proc newTemplate*(name = newEmptyNode(); + params: openArray[PNimrodNode] = [newEmptyNode()]; body: PNimrodNode = newStmtList()): PNimrodNode {.compileTime.} = ## shortcut for creating a new template ## @@ -114,7 +117,8 @@ proc genSuperTemplate*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} = 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 superType = if inheritStmt[0].kind == nnkIdent: inheritStmt[0] + else: inheritStmt[0].getNodeOf(nnkIdent) let superTemplate = getAst declareSuperTemplate(superType, typeName) return superTemplate[0] @@ -228,6 +232,21 @@ proc getIdentDefName*(a: PNimrodNode): PNimrodNode {.compileTime.} = 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() @@ -247,7 +266,8 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = 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) + #let superName = if superType.kind == nnkIdent: superType + # else: superType.getNodeOf(nnkIdent) if typ != nil: error("only support one type declaration") else: # without this else, it fails to compile From 7c4d54a95b1f2776cff28c853279846a1d32aaf5 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Sun, 4 Jan 2015 21:58:32 +0000 Subject: [PATCH 11/25] Added an example of using the QtObject macro --- Nim/Examples/SimpleMacro/CMakeLists.txt | 2 + Nim/Examples/SimpleMacro/main.nim | 57 +++++++++++++++++++++++++ Nim/Examples/SimpleMacro/main.qml | 36 ++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 Nim/Examples/SimpleMacro/CMakeLists.txt create mode 100644 Nim/Examples/SimpleMacro/main.nim create mode 100644 Nim/Examples/SimpleMacro/main.qml diff --git a/Nim/Examples/SimpleMacro/CMakeLists.txt b/Nim/Examples/SimpleMacro/CMakeLists.txt new file mode 100644 index 0000000..79e03af --- /dev/null +++ b/Nim/Examples/SimpleMacro/CMakeLists.txt @@ -0,0 +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) \ No newline at end of file diff --git a/Nim/Examples/SimpleMacro/main.nim b/Nim/Examples/SimpleMacro/main.nim new file mode 100644 index 0000000..f3d668c --- /dev/null +++ b/Nim/Examples/SimpleMacro/main.nim @@ -0,0 +1,57 @@ +## This example replicates the functionality of Examples/Simple but uses +## the helper macro, QtObject, from NimQmlMacros, to remove some of the boiler plate. +## 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 MyQObject = ref object of QObject + m_name: string + + template newMyQObject(initial: string): MyQObject = + var result = MyQObject(m_name: initial) + result.create + result + + method getName(myQObject: MyQObject): string {.slot.} = + echo "nameChanged" + result = myQObject.m_name + + method nameChanged(myQObject: MyQObject) {.signal.} + + method setName(myQObject: MyQObject, name: string) {.slot.} = + echo "setName" + if myQObject.m_name != name: + myQObject.m_name = name + myQObject.nameChanged() + + QtProperty name of string: + read = getName + write = setName + notify = nameChanged + +proc mainProc() = + var app: QApplication + app.create() + defer: app.delete() + + var myQObject = newMyQObject("InitialName") + defer: myQObject.delete() + + 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() + diff --git a/Nim/Examples/SimpleMacro/main.qml b/Nim/Examples/SimpleMacro/main.qml new file mode 100644 index 0000000..a2943c3 --- /dev/null +++ b/Nim/Examples/SimpleMacro/main.qml @@ -0,0 +1,36 @@ +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:" + myQObject.name + } + + TextField + { + id: textField + } + + Button + { + text: "Change Name" + onClicked: { + console.log("QML:", textField.text) + myQObject.name = textField.text + } + } + } +} From c6960df45529bd74351f932e8ab663aff8779a9e Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Sat, 3 Jan 2015 15:46:39 +0100 Subject: [PATCH 12/25] Initial work for writing a nice initial documentation and improving the examples --- Nim/Docs/NimQml.txt | 87 ++++++++++++++++++++++++++ Nim/Examples/CMakeLists.txt | 1 + Nim/Examples/HelloWorld/CMakeLists.txt | 2 + Nim/Examples/HelloWorld/main.nim | 19 ++++++ Nim/Examples/HelloWorld/main.qml | 12 ++++ Nim/Examples/Simple/CMakeLists.txt | 2 +- Nim/NimQml/NimQml.nim | 82 ++++++++++++++++-------- Nim/NimQml/NimQmlTypes.nim | 12 ++-- Nim/cmake/Nim/UseNim.cmake | 2 +- 9 files changed, 186 insertions(+), 33 deletions(-) create mode 100644 Nim/Docs/NimQml.txt create mode 100644 Nim/Examples/HelloWorld/CMakeLists.txt create mode 100644 Nim/Examples/HelloWorld/main.nim create mode 100644 Nim/Examples/HelloWorld/main.qml diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt new file mode 100644 index 0000000..e0ef46b --- /dev/null +++ b/Nim/Docs/NimQml.txt @@ -0,0 +1,87 @@ +:Authors: + Filippo Cucchetto +:Version: 0.1.0 +:Date: 2015/01/02 + +Introduction +----------- +The NimQml module add 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 +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 build 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 + +Given this, 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 + +First example: HelloWorld +---------- +As usual lets start with theHelloWorld 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 contains the presentation layer and expose +the data in your nim files. + +Here's the ``main.nim`` file + +.. code-block:: nim + :file: ../Examples/HelloWorld/main.nim + +and here the ``main.qml`` file + +.. code-block:: qml + :file: ../Examples/HelloWorld/main.qml + +The following examples show 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 + +Second example: exposing data to Qml +------------------------------------ + +Third example: databinding +-------------------------- diff --git a/Nim/Examples/CMakeLists.txt b/Nim/Examples/CMakeLists.txt index 8502d8d..52dcacc 100644 --- a/Nim/Examples/CMakeLists.txt +++ b/Nim/Examples/CMakeLists.txt @@ -1 +1,2 @@ +add_subdirectory(HelloWorld) add_subdirectory(Simple) \ No newline at end of file diff --git a/Nim/Examples/HelloWorld/CMakeLists.txt b/Nim/Examples/HelloWorld/CMakeLists.txt new file mode 100644 index 0000000..5508e23 --- /dev/null +++ b/Nim/Examples/HelloWorld/CMakeLists.txt @@ -0,0 +1,2 @@ +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +add_nim_executable(TARGET HelloWorld SOURCES main.nim PATHS ../../NimQml) \ No newline at end of file diff --git a/Nim/Examples/HelloWorld/main.nim b/Nim/Examples/HelloWorld/main.nim new file mode 100644 index 0000000..897a160 --- /dev/null +++ b/Nim/Examples/HelloWorld/main.nim @@ -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() + diff --git a/Nim/Examples/HelloWorld/main.qml b/Nim/Examples/HelloWorld/main.qml new file mode 100644 index 0000000..612ae5b --- /dev/null +++ b/Nim/Examples/HelloWorld/main.qml @@ -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 +} diff --git a/Nim/Examples/Simple/CMakeLists.txt b/Nim/Examples/Simple/CMakeLists.txt index 79e03af..1c5772b 100644 --- a/Nim/Examples/Simple/CMakeLists.txt +++ b/Nim/Examples/Simple/CMakeLists.txt @@ -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) \ No newline at end of file +add_nim_executable(TARGET Simple SOURCES main.nim PATHS ../../NimQml) \ No newline at end of file diff --git a/Nim/NimQml/NimQml.nim b/Nim/NimQml/NimQml.nim index ecd36e2..cd40e6c 100644 --- a/Nim/NimQml/NimQml.nim +++ b/Nim/NimQml/NimQml.nim @@ -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)) diff --git a/Nim/NimQml/NimQmlTypes.nim b/Nim/NimQml/NimQmlTypes.nim index db30112..7e173c0 100644 --- a/Nim/NimQml/NimQmlTypes.nim +++ b/Nim/NimQml/NimQmlTypes.nim @@ -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 \ No newline at end of file + QQuickView* = distinct pointer ## A QQuickView diff --git a/Nim/cmake/Nim/UseNim.cmake b/Nim/cmake/Nim/UseNim.cmake index be42551..80f7cee 100644 --- a/Nim/cmake/Nim/UseNim.cmake +++ b/Nim/cmake/Nim/UseNim.cmake @@ -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 From 0fb2f9992e6639d9d669f04defc7d0085f913b4b Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Sat, 3 Jan 2015 20:58:52 +0100 Subject: [PATCH 13/25] Added example for simple exchange of data --- Nim/Docs/NimQml.txt | 53 +++++++++++++++++++++++--- Nim/Examples/CMakeLists.txt | 1 + Nim/Examples/SimpleData/CMakeLists.txt | 2 + Nim/Examples/SimpleData/main.nim | 38 ++++++++++++++++++ Nim/Examples/SimpleData/main.qml | 20 ++++++++++ 5 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 Nim/Examples/SimpleData/CMakeLists.txt create mode 100644 Nim/Examples/SimpleData/main.nim create mode 100644 Nim/Examples/SimpleData/main.qml diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index e0ef46b..6734b56 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -1,5 +1,6 @@ :Authors: Filippo Cucchetto + Will Szumski :Version: 0.1.0 :Date: 2015/01/02 @@ -58,23 +59,23 @@ or by manually copying the library in your system lib directory First example: HelloWorld ---------- -As usual lets start with theHelloWorld example. +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 contains the presentation layer and expose the data in your nim files. -Here's the ``main.nim`` file +``Examples/HelloWorld/main.nim`` .. code-block:: nim :file: ../Examples/HelloWorld/main.nim -and here the ``main.qml`` file +``Examples/HelloWorld/main.qml`` .. code-block:: qml :file: ../Examples/HelloWorld/main.qml -The following examples show the basic steps of each NimQml app +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 @@ -82,6 +83,46 @@ the Qt event loop Second example: exposing data to Qml ------------------------------------ +The previous example shown you how to create a simple application +window and how to startup the Qt event loop. + +Is time to explore how to pass to to Qml, but first lets see the +example code: + +``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 to Qml: +1. Create a QVariant and sets the its internal value. +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 +globally in all the Qml script loaded by the current engine (see the Qt doc +for more details about engine and context) + +At the time of writing the QVariant class support the following types: +* int +* string +* bool +* QObject derived classes + +Third example: 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 change in the data layer. +This is achieved by creating object that derives from QObject. + +A QObject is made of : +1. ``Slots``: slots are function callable from the Qml engine +2. ``Signals``: signals allow the sending of events +3. ``Properties``: properties allows the passing of data to + the Qml view and make it aware of changes in the data layer -Third example: databinding --------------------------- diff --git a/Nim/Examples/CMakeLists.txt b/Nim/Examples/CMakeLists.txt index 52dcacc..209534a 100644 --- a/Nim/Examples/CMakeLists.txt +++ b/Nim/Examples/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(HelloWorld) +add_subdirectory(SimpleData) add_subdirectory(Simple) \ No newline at end of file diff --git a/Nim/Examples/SimpleData/CMakeLists.txt b/Nim/Examples/SimpleData/CMakeLists.txt new file mode 100644 index 0000000..e12ae7f --- /dev/null +++ b/Nim/Examples/SimpleData/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/Nim/Examples/SimpleData/main.nim b/Nim/Examples/SimpleData/main.nim new file mode 100644 index 0000000..01f5eb9 --- /dev/null +++ b/Nim/Examples/SimpleData/main.nim @@ -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() + diff --git a/Nim/Examples/SimpleData/main.qml b/Nim/Examples/SimpleData/main.qml new file mode 100644 index 0000000..8f32b75 --- /dev/null +++ b/Nim/Examples/SimpleData/main.qml @@ -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} + } +} From 50eeefa5c4c4d210516098f14007ee2f5189667f Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Mon, 5 Jan 2015 13:55:41 +0100 Subject: [PATCH 14/25] Completed example 3 with slots and properties --- Nim/Docs/NimQml.txt | 79 +++++++++++++++++-- Nim/Examples/CMakeLists.txt | 3 +- .../SlotsAndProperties/CMakeLists.txt | 2 + Nim/Examples/SlotsAndProperties/Contact.nim | 31 ++++++++ Nim/Examples/SlotsAndProperties/main.nim | 27 +++++++ Nim/Examples/SlotsAndProperties/main.qml | 33 ++++++++ 6 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 Nim/Examples/SlotsAndProperties/CMakeLists.txt create mode 100644 Nim/Examples/SlotsAndProperties/Contact.nim create mode 100644 Nim/Examples/SlotsAndProperties/main.nim create mode 100644 Nim/Examples/SlotsAndProperties/main.qml diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index 6734b56..5601483 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -1,5 +1,6 @@ :Authors: Filippo Cucchetto + Will Szumski :Version: 0.1.0 :Date: 2015/01/02 @@ -57,7 +58,7 @@ or by manually copying the library in your system lib directory :: sudo cp build/dir/path/DOtherSide/libDOtherSide.so /usr/lib -First example: HelloWorld +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 @@ -81,7 +82,7 @@ The following example shows the basic steps of each NimQml app 3. Call the ``exec`` proc of the QApplication instance for starting the Qt event loop -Second example: exposing data to Qml +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. @@ -113,16 +114,82 @@ At the time of writing the QVariant class support the following types: * bool * QObject derived classes -Third example: 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 applications need to expose complex data, functions and update the view when something change in the data layer. -This is achieved by creating object that derives from QObject. +This is achieved by creating an object that derives from QObject. A QObject is made of : -1. ``Slots``: slots are function callable from the Qml engine -2. ``Signals``: signals allow the sending of events +1. ``Slots``: slots are function that could both be called from the qml engine and connected to Qt signals +2. ``Signals``: signals allow the sending of events and be connected to slots 3. ``Properties``: properties allows 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 return the current value of the property +* a write slot, a method that set the value of the property +* a notify signal for telling that the current value of the property has been changed + +We'll start by looking at the main.nim file + +``Examples/SlotsAndProperties/main.nim`` + +.. code-block:: nim + :file: ../Examples/SlotsAndProperties/main.nim + +Here's nothing special happen 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 follow: + +``Examples/SlotsAndProperties/main.qml`` + +.. code-block:: qml + :file: ../Examples/SlotsAndProperties/main.qml + +The qml is made by a Label, a TextInput and a button. +The label display the contact name and automatically udpates when +the concat name changes. + +The button update the contact name with the TextInput text when clicked. + +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 + + +What First we declare a QObject subclass and provide a simple +new method where: +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 + +The two slots method implementation are trivial and consist in standard +nim methods. However ``setName`` slot method shows how to emit a signal +by using the ``emit`` method. + +The last thing to condider is the override of the ``onSlotCalled`` method. +This method is called by the NimQml library when an invokation occur from +the Qml side for one of the QObject slot. +The usual implementation for the onSlotCalled method consists in a +switch statement that forward 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 + diff --git a/Nim/Examples/CMakeLists.txt b/Nim/Examples/CMakeLists.txt index 209534a..fec58ad 100644 --- a/Nim/Examples/CMakeLists.txt +++ b/Nim/Examples/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(HelloWorld) add_subdirectory(SimpleData) -add_subdirectory(Simple) \ No newline at end of file +add_subdirectory(Simple) +add_subdirectory(SlotsAndProperties) \ No newline at end of file diff --git a/Nim/Examples/SlotsAndProperties/CMakeLists.txt b/Nim/Examples/SlotsAndProperties/CMakeLists.txt new file mode 100644 index 0000000..5b6ed7b --- /dev/null +++ b/Nim/Examples/SlotsAndProperties/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/Nim/Examples/SlotsAndProperties/Contact.nim b/Nim/Examples/SlotsAndProperties/Contact.nim new file mode 100644 index 0000000..1f04a04 --- /dev/null +++ b/Nim/Examples/SlotsAndProperties/Contact.nim @@ -0,0 +1,31 @@ +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() \ No newline at end of file diff --git a/Nim/Examples/SlotsAndProperties/main.nim b/Nim/Examples/SlotsAndProperties/main.nim new file mode 100644 index 0000000..3e85e9e --- /dev/null +++ b/Nim/Examples/SlotsAndProperties/main.nim @@ -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() + diff --git a/Nim/Examples/SlotsAndProperties/main.qml b/Nim/Examples/SlotsAndProperties/main.qml new file mode 100644 index 0000000..f21c4be --- /dev/null +++ b/Nim/Examples/SlotsAndProperties/main.qml @@ -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 + } + } +} From 328355fff87fe56a22680c3d98aede044e132d64 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Mon, 5 Jan 2015 15:09:13 +0100 Subject: [PATCH 15/25] Finished the initial doc --- Nim/Docs/NimQml.txt | 41 ++++++++++++- Nim/Examples/CMakeLists.txt | 4 +- .../{Simple => QtObjectMacro}/CMakeLists.txt | 2 +- Nim/Examples/QtObjectMacro/Contact.nim | 27 +++++++++ Nim/Examples/QtObjectMacro/main.nim | 27 +++++++++ .../{SimpleMacro => QtObjectMacro}/main.qml | 7 +-- Nim/Examples/Simple/main.nim | 54 ------------------ Nim/Examples/Simple/main.qml | 36 ------------ Nim/Examples/SimpleMacro/CMakeLists.txt | 2 - Nim/Examples/SimpleMacro/main.nim | 57 ------------------- Nim/Examples/SlotsAndProperties/Contact.nim | 2 + Nim/NimQml/NimQmlMacros.nim | 2 +- 12 files changed, 102 insertions(+), 159 deletions(-) rename Nim/Examples/{Simple => QtObjectMacro}/CMakeLists.txt (53%) create mode 100644 Nim/Examples/QtObjectMacro/Contact.nim create mode 100644 Nim/Examples/QtObjectMacro/main.nim rename Nim/Examples/{SimpleMacro => QtObjectMacro}/main.qml (67%) delete mode 100644 Nim/Examples/Simple/main.nim delete mode 100644 Nim/Examples/Simple/main.qml delete mode 100644 Nim/Examples/SimpleMacro/CMakeLists.txt delete mode 100644 Nim/Examples/SimpleMacro/main.nim diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index 5601483..3a89c5f 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -167,7 +167,6 @@ The magic is in the Contact.nim file .. code-block:: nim :file: ../Examples/SlotsAndProperties/Contact.nim - What First we declare a QObject subclass and provide a simple new method where: 1. invoke the ``create()`` procedure. This invoke the C++ bridge and allocate @@ -192,4 +191,44 @@ If the invoked slot has a return value this is always in the index position Example 4: QtObject macro +------------------------- +The previous example shows how to create simple QObject however writing +all those ``register`` procs and writing the ``onSlotCalled`` method +become boring pretty soon. + +Furthermore all this information can be automatically generated. +For this purpose you can import the NimQmlMacros module that provide +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 new in both the ``main.nim`` and ``main.qml`` in 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.}`` macro +3. Each signal is annotated with the ``{.signal.}`` macro +4. Each property is created with the ``QtProperty`` macro + +The ``QtProperty`` macro has the following syntax + +.. code-block:: nim + QtProperty nameOfProperty of typeOfProperty diff --git a/Nim/Examples/CMakeLists.txt b/Nim/Examples/CMakeLists.txt index fec58ad..d9f665a 100644 --- a/Nim/Examples/CMakeLists.txt +++ b/Nim/Examples/CMakeLists.txt @@ -1,4 +1,4 @@ add_subdirectory(HelloWorld) add_subdirectory(SimpleData) -add_subdirectory(Simple) -add_subdirectory(SlotsAndProperties) \ No newline at end of file +add_subdirectory(SlotsAndProperties) +add_subdirectory(QtObjectMacro) \ No newline at end of file diff --git a/Nim/Examples/Simple/CMakeLists.txt b/Nim/Examples/QtObjectMacro/CMakeLists.txt similarity index 53% rename from Nim/Examples/Simple/CMakeLists.txt rename to Nim/Examples/QtObjectMacro/CMakeLists.txt index 1c5772b..c94ccc1 100644 --- a/Nim/Examples/Simple/CMakeLists.txt +++ b/Nim/Examples/QtObjectMacro/CMakeLists.txt @@ -1,2 +1,2 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -add_nim_executable(TARGET Simple SOURCES main.nim PATHS ../../NimQml) \ No newline at end of file +add_nim_executable(TARGET QtObjectMacro SOURCES main.nim PATHS ../../NimQml) \ No newline at end of file diff --git a/Nim/Examples/QtObjectMacro/Contact.nim b/Nim/Examples/QtObjectMacro/Contact.nim new file mode 100644 index 0000000..fef9100 --- /dev/null +++ b/Nim/Examples/QtObjectMacro/Contact.nim @@ -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 name of string: + read = getName + write = setName + notify = nameChanged \ No newline at end of file diff --git a/Nim/Examples/QtObjectMacro/main.nim b/Nim/Examples/QtObjectMacro/main.nim new file mode 100644 index 0000000..9c45926 --- /dev/null +++ b/Nim/Examples/QtObjectMacro/main.nim @@ -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() + diff --git a/Nim/Examples/SimpleMacro/main.qml b/Nim/Examples/QtObjectMacro/main.qml similarity index 67% rename from Nim/Examples/SimpleMacro/main.qml rename to Nim/Examples/QtObjectMacro/main.qml index a2943c3..f21c4be 100644 --- a/Nim/Examples/SimpleMacro/main.qml +++ b/Nim/Examples/QtObjectMacro/main.qml @@ -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 } } } diff --git a/Nim/Examples/Simple/main.nim b/Nim/Examples/Simple/main.nim deleted file mode 100644 index 768f1c9..0000000 --- a/Nim/Examples/Simple/main.nim +++ /dev/null @@ -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() - diff --git a/Nim/Examples/Simple/main.qml b/Nim/Examples/Simple/main.qml deleted file mode 100644 index a2943c3..0000000 --- a/Nim/Examples/Simple/main.qml +++ /dev/null @@ -1,36 +0,0 @@ -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:" + myQObject.name - } - - TextField - { - id: textField - } - - Button - { - text: "Change Name" - onClicked: { - console.log("QML:", textField.text) - myQObject.name = textField.text - } - } - } -} diff --git a/Nim/Examples/SimpleMacro/CMakeLists.txt b/Nim/Examples/SimpleMacro/CMakeLists.txt deleted file mode 100644 index 79e03af..0000000 --- a/Nim/Examples/SimpleMacro/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -add_nim_executable(TARGET Main SOURCES main.nim PATHS ../../NimQml) \ No newline at end of file diff --git a/Nim/Examples/SimpleMacro/main.nim b/Nim/Examples/SimpleMacro/main.nim deleted file mode 100644 index f3d668c..0000000 --- a/Nim/Examples/SimpleMacro/main.nim +++ /dev/null @@ -1,57 +0,0 @@ -## This example replicates the functionality of Examples/Simple but uses -## the helper macro, QtObject, from NimQmlMacros, to remove some of the boiler plate. -## 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 MyQObject = ref object of QObject - m_name: string - - template newMyQObject(initial: string): MyQObject = - var result = MyQObject(m_name: initial) - result.create - result - - method getName(myQObject: MyQObject): string {.slot.} = - echo "nameChanged" - result = myQObject.m_name - - method nameChanged(myQObject: MyQObject) {.signal.} - - method setName(myQObject: MyQObject, name: string) {.slot.} = - echo "setName" - if myQObject.m_name != name: - myQObject.m_name = name - myQObject.nameChanged() - - QtProperty name of string: - read = getName - write = setName - notify = nameChanged - -proc mainProc() = - var app: QApplication - app.create() - defer: app.delete() - - var myQObject = newMyQObject("InitialName") - defer: myQObject.delete() - - 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() - diff --git a/Nim/Examples/SlotsAndProperties/Contact.nim b/Nim/Examples/SlotsAndProperties/Contact.nim index 1f04a04..797d82c 100644 --- a/Nim/Examples/SlotsAndProperties/Contact.nim +++ b/Nim/Examples/SlotsAndProperties/Contact.nim @@ -1,3 +1,5 @@ +## 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 diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 33fecd1..5414c49 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -197,7 +197,7 @@ template declareOnSlotCalled(typ: typedesc): stmt = discard template prototypeCreate(typ: typedesc): stmt = - template create(myQObject: var typ) = + template create*(myQObject: var typ) = var super = (typ.superType())(myQObject) procCall create(super) From 665f6c6b641ea6e0a47e077f5196512a13035ec4 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Mon, 5 Jan 2015 15:24:48 +0100 Subject: [PATCH 16/25] Updated the nimble version and authors --- Nim/NimQml/NimQml.nimble | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nim/NimQml/NimQml.nimble b/Nim/NimQml/NimQml.nimble index 3a334b1..ef79830 100644 --- a/Nim/NimQml/NimQml.nimble +++ b/Nim/NimQml/NimQml.nimble @@ -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" From 476740fffc185f97b903584475f93fe98f05be86 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 5 Jan 2015 18:37:29 +0000 Subject: [PATCH 17/25] various documentation changes --- Nim/Docs/NimQml.txt | 87 +++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index 3a89c5f..dd7dccf 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -7,7 +7,7 @@ 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 and the Nim imperative language. @@ -22,7 +22,7 @@ the libDOtherSide exported symbols in Nim Building -------- 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 :: @@ -36,7 +36,7 @@ than you can proceed with the common CMake build steps cmake .. 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 Installation @@ -63,7 +63,7 @@ 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 contains the presentation layer and expose +layer. The qml files contain the presentation layer and expose the data in your nim files. ``Examples/HelloWorld/main.nim`` @@ -87,8 +87,8 @@ 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. -Is time to explore how to pass to to Qml, but first lets see the -example code: +It's time to explore how to pass data to Qml, but lets see the +example code first: ``Examples/SimpleData/main.nim`` @@ -100,13 +100,13 @@ example code: .. code-block:: qml :file: ../Examples/SimpleData/main.qml -The following example shows how to expose simple data to Qml: -1. Create a QVariant and sets the its internal value. +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 a property is set through the ``setContextProperty`` proc its available -globally in all the Qml script loaded by the current engine (see the Qt doc -for more details about engine and context) +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 @@ -116,21 +116,21 @@ At the time of writing the QVariant class support the following types: 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 -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. 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 -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 A QObject property is made of three things: -* a read slot, a method that return the current value of the property -* a write slot, a method that set the value of the property -* a notify signal for telling that the current value of the property has been changed +* 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 @@ -139,24 +139,25 @@ We'll start by looking at the main.nim file .. code-block:: nim :file: ../Examples/SlotsAndProperties/main.nim -Here's nothing special happen except: +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 follow: +The Qml file is as follows: ``Examples/SlotsAndProperties/main.qml`` .. code-block:: qml :file: ../Examples/SlotsAndProperties/main.qml -The qml is made by a Label, a TextInput and a button. -The label display the contact name and automatically udpates when -the concat name changes. +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. -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? @@ -167,37 +168,37 @@ The magic is in the Contact.nim file .. code-block:: nim :file: ../Examples/SlotsAndProperties/Contact.nim -What First we declare a QObject subclass and provide a simple -new method where: +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 +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 -The two slots method implementation are trivial and consist in standard -nim methods. However ``setName`` slot method shows how to emit a signal -by using the ``emit`` method. +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 condider is the override of the ``onSlotCalled`` method. -This method is called by the NimQml library when an invokation occur from -the Qml side for one of the QObject slot. -The usual implementation for the onSlotCalled method consists in a -switch statement that forward the arguments to the correct slot. -If the invoked slot has a return value this is always in the index position +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 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 -become boring pretty soon. +becomes boring pretty soon. 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. Let's begin as usual with both the main.nim and main.qml files @@ -213,8 +214,8 @@ Let's begin as usual with both the main.nim and main.qml files .. code-block:: qml :file: ../Examples/QtObjectMacro/main.qml -Nothing new in both the ``main.nim`` and ``main.qml`` in respect to -the previous example. What changed is the Contact object +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`` @@ -223,8 +224,8 @@ the previous example. What changed is the Contact object In details: 1. Each QObject is defined inside the QtObject macro -2. Each slot is annotated with the ``{.slot.}`` macro -3. Each signal is annotated with the ``{.signal.}`` 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 From 9b8985c701a3116eca2807830c54a1292369b014 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Mon, 5 Jan 2015 19:51:45 +0100 Subject: [PATCH 18/25] Fixed: typo that prevent the generation of slots/signals/properties with int --- Nim/NimQml/NimQmlMacros.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 5414c49..c373185 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -21,7 +21,7 @@ let nimFromQtVariant {.compileTime.} = { let nim2QtMeta {.compileTime.} = { "bool": "Bool", - "int " : "Int", + "int" : "Int", "string" : "QString", "pointer" : "VoidStar", "QVariant": "QVariant", From d30d8873de34f3137936cbc170bea3b7c7905aa5 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 5 Jan 2015 22:37:14 +0000 Subject: [PATCH 19/25] workaround Araq/Nim#1874 --- Nim/NimQml/NimQmlMacros.nim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index c373185..dae70eb 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -90,8 +90,9 @@ proc newTemplate*(name = newEmptyNode(); newEmptyNode(), ## pragmas newEmptyNode(), 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] = parent @@ -192,11 +193,13 @@ proc addSignalBody(signal: PNimrodNode): PNimrodNode {.compileTime.} = args.add getArgName params[i] 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]) = 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) = var super = (typ.superType())(myQObject) procCall create(super) From 7b25e28d582e2ab9367b5a9f73eccff6225e5b51 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 5 Jan 2015 23:43:15 +0000 Subject: [PATCH 20/25] only export superType and create when the object inheriting from QObject is exported --- Nim/NimQml/NimQmlMacros.nim | 58 +++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index dae70eb..8e746e9 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -109,6 +109,34 @@ proc getTypeName*(a: PNimrodNode): PNimrodNode {.compileTime.} = 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`` @@ -120,8 +148,12 @@ proc genSuperTemplate*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} = # 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) - return superTemplate[0] + 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 @@ -198,7 +230,7 @@ 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 +#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) @@ -272,9 +304,10 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = #let superName = if superType.kind == nnkIdent: superType # else: superType.getNodeOf(nnkIdent) 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 - typ = typeDecl.getTypeName + typ = typeDecl result.add it result.add genSuperTemplate(typeDecl) elif it.kind == nnkMethodDef: @@ -302,9 +335,12 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = 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(typ))[0] + var slotProto = (getAst declareOnSlotCalled(typeName))[0] var caseStmt = newNimNode(nnkCaseStmt) caseStmt.add ident("slotName") for slot in slots: @@ -347,9 +383,13 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = result.add slotProto # 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 - createProto = doRemoveOpenSym(createProto) + createProto = doRemoveOpenSym(createProto) + if typ.isExported: + createProto.exportDef() + else: + createProto.unexportDef() var createBody = createProto.templateBody for slot in slots: let params = slot.params From 9e91b25cdaea276632a6514e1298b3f6f6c2fe37 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 5 Jan 2015 23:48:59 +0000 Subject: [PATCH 21/25] Now require objects to be explicitly exported for create to be exported --- Nim/Examples/QtObjectMacro/Contact.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nim/Examples/QtObjectMacro/Contact.nim b/Nim/Examples/QtObjectMacro/Contact.nim index fef9100..a16d3f2 100644 --- a/Nim/Examples/QtObjectMacro/Contact.nim +++ b/Nim/Examples/QtObjectMacro/Contact.nim @@ -3,7 +3,7 @@ import NimQml, NimQmlMacros QtObject: - type Contact = ref object of QObject + type Contact* = ref object of QObject m_name: string template newContact*(): Contact = @@ -24,4 +24,4 @@ QtObject: QtProperty name of string: read = getName write = setName - notify = nameChanged \ No newline at end of file + notify = nameChanged From 2d05bcf0a8cd559144c4c0fbbc2f9ba91c55abe7 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 6 Jan 2015 00:47:38 +0000 Subject: [PATCH 22/25] changed QtObject macro syntax --- Nim/Docs/NimQml.txt | 2 +- Nim/Examples/QtObjectMacro/Contact.nim | 2 +- Nim/NimQml/NimQmlMacros.nim | 28 ++++++++++++++++---------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index dd7dccf..1655be8 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -231,5 +231,5 @@ In details: The ``QtProperty`` macro has the following syntax .. code-block:: nim - QtProperty nameOfProperty of typeOfProperty + QtProperty[typeOfProperty] nameOfProperty diff --git a/Nim/Examples/QtObjectMacro/Contact.nim b/Nim/Examples/QtObjectMacro/Contact.nim index a16d3f2..7b9c79e 100644 --- a/Nim/Examples/QtObjectMacro/Contact.nim +++ b/Nim/Examples/QtObjectMacro/Contact.nim @@ -21,7 +21,7 @@ QtObject: contact.m_name = name contact.nameChanged() - QtProperty name of string: + QtProperty[string] name: read = getName write = setName notify = nameChanged diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim index 8e746e9..40372d7 100644 --- a/Nim/NimQml/NimQmlMacros.nim +++ b/Nim/NimQml/NimQmlMacros.nim @@ -327,7 +327,13 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = elif it.kind == nnkProcDef: userDefined.add it 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 ($cmdIdent).toLower() != "qtproperty": error("do not know how to handle: \n" & repr(it)) @@ -406,19 +412,19 @@ macro QtObject*(qtDecl: stmt): stmt {.immediate.} = let call = newCall(regSigDot, newLit name, argTypesArray) createBody.add call for property in properties: - #echo treeRepr property - let infix = property[1] - expectKind infix, nnkInfix - # Infix - # Ident !"of" - # Ident !"name" - # Ident !"string" - - let nimPropType = infix[2] + 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 = infix[1] + let propertyName = property[1] var read, write, notify: PNimrodNode let stmtList = property[2] # fields From 9e3151def636b82c15603d9e8d8f28c60b83a241 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Tue, 6 Jan 2015 12:51:12 +0100 Subject: [PATCH 23/25] Updated the documentation version --- Nim/Docs/NimQml.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index 1655be8..e40eff4 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -2,7 +2,7 @@ Filippo Cucchetto Will Szumski -:Version: 0.1.0 +:Version: 0.2.0 :Date: 2015/01/02 Introduction From ea29989be3fa8ed5d49ba6f8c582f7d63fc50b80 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Tue, 6 Jan 2015 13:03:20 +0100 Subject: [PATCH 24/25] Update the Readme for linking at the documentation --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 4370383..b74beba 100644 --- a/README.md +++ b/README.md @@ -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 From 162375c6a70d80c20698693d6ea8d747b1142da1 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Tue, 6 Jan 2015 13:10:54 +0100 Subject: [PATCH 25/25] Updated the doc installation steps --- Nim/Docs/NimQml.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt index e40eff4..ec1e322 100644 --- a/Nim/Docs/NimQml.txt +++ b/Nim/Docs/NimQml.txt @@ -49,7 +49,11 @@ the built Nim example in the following way export LD_LIBRARY_PATH=path/to/libDOtherSide.so ./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 :: cd to/build/dir @@ -58,6 +62,16 @@ 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.