Released version 0.2.0

This commit is contained in:
Filippo Cucchetto 2015-01-06 13:46:49 +01:00
parent 07f2c09fb5
commit 5535472495
4 changed files with 525 additions and 33 deletions

View File

@ -1,13 +1,19 @@
import NimQmlTypes import NimQmlTypes
import tables import tables
## NimQml aims to provide binding to the QML for the Nim programming language
export QObject export QObject
export QApplication export QApplication
export QVariant export QVariant
export QQmlApplicationEngine export QQmlApplicationEngine
export QQmlContext 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), UnknownType = cint(0),
Bool = cint(1), Bool = cint(1),
Int = cint(2), 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 dos_chararray_delete(rawCString: cstring) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create*(variant: var QVariant) = proc create*(variant: var QVariant) =
## Create a new QVariant
var data: pointer var data: pointer
dos_qvariant_create(data) dos_qvariant_create(data)
variant = QVariant(data) variant = QVariant(data)
proc create*(variant: var QVariant, value: cint) = proc create*(variant: var QVariant, value: cint) =
## Create a new QVariant given a cint value
var data: pointer var data: pointer
dos_qvariant_create_int(data, value) dos_qvariant_create_int(data, value)
variant = QVariant(data) variant = QVariant(data)
proc create*(variant: var QVariant, value: bool) = proc create*(variant: var QVariant, value: bool) =
## Create a new QVariant given a bool value
var data: pointer var data: pointer
dos_qvariant_create_bool(data, value) dos_qvariant_create_bool(data, value)
variant = QVariant(data) variant = QVariant(data)
proc create*(variant: var QVariant, value: string) = proc create*(variant: var QVariant, value: string) =
## Create a new QVariant given a string value
var data: pointer var data: pointer
dos_qvariant_create_string(data, value) dos_qvariant_create_string(data, value)
variant = QVariant(data) variant = QVariant(data)
proc create*(variant: var QVariant, value: QObject) = proc create*(variant: var QVariant, value: QObject) =
## Create a new QVariant given a QObject
var data: pointer var data: pointer
dos_qvariant_create_qobject(data, value.data) dos_qvariant_create_qobject(data, value.data)
variant = QVariant(data) variant = QVariant(data)
proc delete*(variant: QVariant) = proc delete*(variant: QVariant) =
## Delete a QVariant
debugMsg("QVariant", "delete") debugMsg("QVariant", "delete")
dos_qvariant_delete(pointer(variant)) dos_qvariant_delete(pointer(variant))
proc isNull*(variant: QVariant): bool = proc isNull*(variant: QVariant): bool =
## Return true if the QVariant value is null, false otherwise
dos_qvariant_isnull(pointer(variant), result) dos_qvariant_isnull(pointer(variant), result)
proc intVal*(variant: QVariant): int = proc intVal*(variant: QVariant): int =
## Return the QVariant value as int
var rawValue: cint var rawValue: cint
dos_qvariant_toInt(pointer(variant), rawValue) dos_qvariant_toInt(pointer(variant), rawValue)
result = cast[int](rawValue) result = cast[int](rawValue)
proc `intVal=`*(variant: QVariant, value: int) = proc `intVal=`*(variant: QVariant, value: int) =
## Sets the QVariant value int value
var rawValue = cast[cint](value) var rawValue = cast[cint](value)
dos_qvariant_setInt(pointer(variant), rawValue) dos_qvariant_setInt(pointer(variant), rawValue)
proc boolVal*(variant: QVariant): bool = proc boolVal*(variant: QVariant): bool =
## Return the QVariant value as bool
dos_qvariant_toBool(pointer(variant), result) dos_qvariant_toBool(pointer(variant), result)
proc `boolVal=`*(variant: QVariant, value: bool) = proc `boolVal=`*(variant: QVariant, value: bool) =
## Sets the QVariant bool value
dos_qvariant_setBool(pointer(variant), value) dos_qvariant_setBool(pointer(variant), value)
proc stringVal*(variant: QVariant): string = proc stringVal*(variant: QVariant): string =
## Return the QVariant value as string
var rawCString: cstring var rawCString: cstring
var rawCStringLength: cint var rawCStringLength: cint
dos_qvariant_toString(pointer(variant), rawCString, rawCStringLength) dos_qvariant_toString(pointer(variant), rawCString, rawCStringLength)
@ -104,6 +122,7 @@ proc stringVal*(variant: QVariant): string =
dos_chararray_delete(rawCString) dos_chararray_delete(rawCString)
proc `stringVal=`*(variant: QVariant, value: string) = proc `stringVal=`*(variant: QVariant, value: string) =
## Sets the QVariant string value
dos_qvariant_setString(pointer(variant), 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 dos_qqmlapplicationengine_delete(engine: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create*(engine: var QQmlApplicationEngine) = proc create*(engine: var QQmlApplicationEngine) =
## Create an new QQmlApplicationEngine
var temp: pointer var temp: pointer
dos_qqmlapplicationengine_create(temp) dos_qqmlapplicationengine_create(temp)
engine = QQmlApplicationEngine(temp) engine = QQmlApplicationEngine(temp)
proc load*(engine: QQmlApplicationEngine, filename: cstring) = proc load*(engine: QQmlApplicationEngine, filename: cstring) =
## Load the given Qml file
dos_qqmlapplicationengine_load(pointer(engine), filename) dos_qqmlapplicationengine_load(pointer(engine), filename)
proc rootContext*(engine: QQmlApplicationEngine): QQmlContext = proc rootContext*(engine: QQmlApplicationEngine): QQmlContext =
## Return the engine root context
var context: pointer var context: pointer
dos_qqmlapplicationengine_context(pointer(engine), context) dos_qqmlapplicationengine_context(pointer(engine), context)
result = cast[QQmlContext](context) result = cast[QQmlContext](context)
proc delete*(engine: QQmlApplicationEngine) = proc delete*(engine: QQmlApplicationEngine) =
## Delete the given QQmlApplicationEngine
debugMsg("QQmlApplicationEngine", "delete") debugMsg("QQmlApplicationEngine", "delete")
dos_qqmlapplicationengine_delete(pointer(engine)) 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 dos_qqmlcontext_setcontextproperty(context: pointer, propertyName: cstring, propertyValue: pointer) {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc setContextProperty*(context: QQmlContext, propertyName: string, propertyValue: QVariant) = proc setContextProperty*(context: QQmlContext, propertyName: string, propertyValue: QVariant) =
## Sets a new property with the given value
dos_qqmlcontext_setcontextproperty(pointer(context), propertyName, pointer(propertyValue)) dos_qqmlcontext_setcontextproperty(pointer(context), propertyName, pointer(propertyValue))
# QApplication # QApplication
@ -142,21 +166,31 @@ proc dos_qguiapplication_exec() {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc dos_qguiapplication_delete() {.cdecl, dynlib:"libDOtherSide.so", importc.} proc dos_qguiapplication_delete() {.cdecl, dynlib:"libDOtherSide.so", importc.}
proc create*(application: QApplication) = proc create*(application: QApplication) =
debugMsg("QApplication", "create") ## Create a new QApplication
dos_qguiapplication_create() dos_qguiapplication_create()
proc exec*(application: QApplication) = proc exec*(application: QApplication) =
debugMsg("QApplication", "exec") ## Start the Qt event loop
dos_qguiapplication_exec() dos_qguiapplication_exec()
proc delete*(application: QApplication) = proc delete*(application: QApplication) =
debugMsg("QApplication", "delete") ## Delete the given QApplication
dos_qguiapplication_delete() dos_qguiapplication_delete()
# QObject # QObject
type QVariantArray {.unchecked.} = array[0..0, QVariant] type QVariantArray {.unchecked.} = array[0..0, QVariant]
type QVariantArrayPtr = ptr QVariantArray 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_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_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.} proc dos_qobject_slot_create(qobject: pointer, slotName: cstring, argumentsCount: cint, argumentsMetaTypes: ptr cint, slotIndex: var cint) {.cdecl, dynlib:"libDOtherSide.so", importc.}
@ -165,39 +199,30 @@ proc dos_qobject_signal_emit(qobject: pointer, signalName: cstring, argumentsCou
proc dos_qobject_property_create(qobject: pointer, propertyName: cstring, propertyType: cint, readSlot: cstring, writeSlot: cstring, notifySignal: cstring) {.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]) = method onSlotCalled*(nimobject: QObject, slotName: string, args: openarray[QVariant]) =
debugMsg("QObject", "onSlotCalled", "begin") ## Called from the NimQml bridge when a slot is called from Qml.
debugMsg("QObject", "onSlotCalled", "end") ## 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.} = proc qobjectCallback(nimobject: pointer, slotName: QVariant, numArguments: cint, arguments: QVariantArrayPtr) {.exportc.} =
debugMsg("QObject", "qobjectCallback", "begin")
var nimQObjectCasted = cast[ptr QObject](nimobject) var nimQObjectCasted = cast[ptr QObject](nimobject)
# forward to the QObject subtype instance # forward to the QObject subtype instance
nimQObjectCasted[].onSlotCalled(slotName.stringVal, arguments.toVariantSeq(numArguments)) 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) = proc create*(qobject: var QObject) =
## Create a new QObject
qobject.name = "QObject" qobject.name = "QObject"
qobject.slots = initTable[string,cint]() qobject.slots = initTable[string,cint]()
qobject.signals = initTable[string, cint]() qobject.signals = initTable[string, cint]()
dos_qobject_create(qobject.data, addr(qobject), qobjectCallback) dos_qobject_create(qobject.data, addr(qobject), qobjectCallback)
proc delete*(qobject: QObject) = proc delete*(qobject: QObject) =
debugMsg("QObject", "delete") ## Delete the given QObject
dos_qobject_delete(qobject.data) dos_qobject_delete(qobject.data)
proc registerSlot*(qobject: var QObject, proc registerSlot*(qobject: var QObject,
slotName: string, slotName: string,
metaTypes: openarray[QMetaType]) = metaTypes: openarray[QMetaType]) =
## Register a slot in the QObject with the given name and signature
# Copy the metatypes array # Copy the metatypes array
var copy = toCIntSeq(metatypes) var copy = toCIntSeq(metatypes)
var index: cint var index: cint
@ -207,6 +232,7 @@ proc registerSlot*(qobject: var QObject,
proc registerSignal*(qobject: var QObject, proc registerSignal*(qobject: var QObject,
signalName: string, signalName: string,
metatypes: openarray[QMetaType]) = metatypes: openarray[QMetaType]) =
## Register a signal in the QObject with the given name and signature
var index: cint var index: cint
if metatypes.len > 0: if metatypes.len > 0:
var copy = toCIntSeq(metatypes) var copy = toCIntSeq(metatypes)
@ -221,9 +247,11 @@ proc registerProperty*(qobject: var QObject,
readSlot: string, readSlot: string,
writeSlot: 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) dos_qobject_property_create(qobject.data, propertyName, cast[cint](propertyType), readSlot, writeSlot, notifySignal)
proc emit*(qobject: QObject, signalName: string, args: openarray[QVariant] = []) = proc emit*(qobject: QObject, signalName: string, args: openarray[QVariant] = []) =
## Emit the signal with the given name and values
if args.len > 0: if args.len > 0:
var copy: seq[QVariant] var copy: seq[QVariant]
for i in 0..args.len-1: for i in 0..args.len-1:
@ -240,22 +268,26 @@ proc dos_qquickview_source(view: pointer, filename: var cstring, length: var int
proc dos_qquickview_set_source(view: pointer, filename: cstring) {.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 var temp: pointer
dos_qquickview_create(temp) dos_qquickview_create(temp)
view = QQuickView(temp) view = QQuickView(temp)
proc source(view: QQuickView): cstring = proc source(view: QQuickView): cstring =
## Return the source Qml file loaded by the view
var length: int var length: int
dos_qquickview_source(pointer(view), result, length) dos_qquickview_source(pointer(view), result, length)
proc `source=`(view: QQuickView, filename: cstring) = proc `source=`(view: QQuickView, filename: cstring) =
## Sets the source Qml file laoded by the view
dos_qquickview_set_source(pointer(view), filename) dos_qquickview_set_source(pointer(view), filename)
proc show(view: QQuickView) = proc show(view: QQuickView) =
## Sets the view visible
dos_qquickview_show(pointer(view)) dos_qquickview_show(pointer(view))
proc delete(view: QQuickView) = proc delete(view: QQuickView) =
debugMsg("QQuickView", "delete") ## Delete the given QQuickView
dos_qquickview_delete(pointer(view)) dos_qquickview_delete(pointer(view))

View File

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

460
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 import tables
type type
QVariant* = distinct pointer QVariant* = distinct pointer ## A QVariant
QQmlApplicationEngine* = distinct pointer QQmlApplicationEngine* = distinct pointer ## A QQmlApplicationEngine
QQmlContext* = distinct pointer QQmlContext* = distinct pointer ## A QQmlContext
QApplication* = distinct pointer QApplication* = distinct pointer ## A QApplication
QObject* {.inheritable.} = ref object of RootObj QObject* {.inheritable.} = ref object of RootObj ## A QObject
name*: string name*: string
data*: pointer data*: pointer
slots*: Table[string, cint] slots*: Table[string, cint]
signals*: Table[string, cint] signals*: Table[string, cint]
properties*: Table[string, cint] properties*: Table[string, cint]
QQuickView* = distinct pointer QQuickView* = distinct pointer ## A QQuickView