diff --git a/Nim/Docs/NimQml.txt b/Nim/Docs/NimQml.txt new file mode 100644 index 0000000..ec1e322 --- /dev/null +++ b/Nim/Docs/NimQml.txt @@ -0,0 +1,249 @@ +:Authors: + Filippo Cucchetto + + Will Szumski +:Version: 0.2.0 +:Date: 2015/01/02 + +Introduction +----------- +The NimQml module adds Qt Qml bindings to the Nim programming language +allowing you to create new modern UI by mixing the Qml declarative syntax +and the Nim imperative language. + +The NimQml is made by two components: +* The DOtherSide C++ shared library +* The NimQml Nim module + +This first component implements the glue code necessary for +communicating with the Qt C++ library, the latter module wraps +the libDOtherSide exported symbols in Nim + +Building +-------- +At the time of writing the DOtherSide C++ library must be compiled +and installed manually from source. + +First clone the DOtherSide git repo +:: + git clone https://github.com/filcuc/DOtherSide + +than you can proceed with the common CMake build steps + +:: + mkdir build + cd build + cmake .. + make + +If everything goes correctly, you'll have built both +the DOtherSide C++ library and the Nim examples + +Installation +---------- +The installation is not mandatory, in fact you could try +the built Nim example in the following way +:: + cd path/to/build/dir + cd Nim/Examples/HelloWorld + export LD_LIBRARY_PATH=path/to/libDOtherSide.so + ./HelloWorld + +The DOtherSide project is made of two components +1. The DOtherSide C++ lib +2. The NimQml module + +You can procede with the installation of the C++ library +in the following way +:: + cd to/build/dir + make install +or by manually copying the library in your system lib directory +:: + sudo cp build/dir/path/DOtherSide/libDOtherSide.so /usr/lib + +For the NimQml module you can use the ``nimble`` package manager +:: + nimble install NimQml + +or +:: + cd to/build/dir/Nim/NimQml + nimble install + + +Example 1: HelloWorld +---------- +As usual lets start with an HelloWorld example. +Most of the NimQml projects are made by one or more nim and qml +files. Usually the .nim files contains your app logic and data +layer. The qml files contain the presentation layer and expose +the data in your nim files. + +``Examples/HelloWorld/main.nim`` + +.. code-block:: nim + :file: ../Examples/HelloWorld/main.nim + +``Examples/HelloWorld/main.qml`` + +.. code-block:: qml + :file: ../Examples/HelloWorld/main.qml + +The following example shows the basic steps of each NimQml app +1. Create the QApplication for initializing the Qt runtime +2. Create the QQmlApplicationEngine and load your main .qml file +3. Call the ``exec`` proc of the QApplication instance for starting +the Qt event loop + +Example 2: exposing data to Qml +------------------------------------ +The previous example shown you how to create a simple application +window and how to startup the Qt event loop. + +It's time to explore how to pass data to Qml, but lets see the +example code first: + +``Examples/SimpleData/main.nim`` + +.. code-block:: nim + :file: ../Examples/SimpleData/main.nim + +``Examples/SimpleData/main.qml`` + +.. code-block:: qml + :file: ../Examples/SimpleData/main.qml + +The following example shows how to expose simple data types to Qml: +1. Create a QVariant and set its internal value. +2. Create a property in the Qml root context with a given name. + +Once a property is set through the ``setContextProperty`` proc, it's available +globally in all the Qml script loaded by the current engine (see the official Qt +documentation for more details about the engine and context objects) + +At the time of writing the QVariant class support the following types: +* int +* string +* bool +* QObject derived classes + +Example 3: exposing complex data and procedures to Qml +---------------------------------------------------------- +As seen by the second example, simple data is fine. However most +applications need to expose complex data, functions and +update the view when something changes in the data layer. +This is achieved by creating an object that derives from QObject. + +A QObject is made of : +1. ``Slots``: slots are functions that could be called from the qml engine and/or connected to Qt signals +2. ``Signals``: signals allow the sending of events and be connected to slots +3. ``Properties``: properties allow the passing of data to + the Qml view and make it aware of changes in the data layer + +A QObject property is made of three things: +* a read slot: a method that returns the current value of the property +* a write slot: a method that sets the value of the property +* a notify signal: emitted when the current value of the property is changed + +We'll start by looking at the main.nim file + +``Examples/SlotsAndProperties/main.nim`` + +.. code-block:: nim + :file: ../Examples/SlotsAndProperties/main.nim + +Here, nothing special happens except: +1. The creation of Contact object +2. The injection of the Contact object to the Qml root context + using the ``setContextProperty`` as seen in the previous + example + +The Qml file is as follows: + +``Examples/SlotsAndProperties/main.qml`` + +.. code-block:: qml + :file: ../Examples/SlotsAndProperties/main.qml + +The qml is made up of: a Label, a TextInput widget, and a button. +The label displays the contact name - this automatically updates when +the contact name changes. + +When clicked, the button updates the contact name with the text from +the TextInput widget. + +So where's the magic? + +The magic is in the Contact.nim file + +``Examples/SlotsAndProperties/Contact.nim`` + +.. code-block:: nim + :file: ../Examples/SlotsAndProperties/Contact.nim + +First we declare a QObject subclass and provide a simple +new method where we: +1. invoke the ``create()`` procedure. This invoke the C++ bridge and allocate +a QObject instance +2. register a slot ``getName`` for reading the Contact name field +3. register a slot ``setName`` for writing the Contact name +4. register a signal ``nameChanged`` for notify the contact name changes +5. register a property called ``name`` of type ``QString`` with the given + read, write slots and notify signal + +Looking at the ``getName`` and ``setName`` methods, you can see that slots, as defined in Nim, +are nothing more than standard methods. The method corresponding to the ``setName`` slot +demonstrates how to use the ``emit`` method to emit a signal. + +The last thing to consider is the override of the ``onSlotCalled`` method. +This method is called by the NimQml library when an invocation occurs from +the Qml side for one of the slots belonging to the QObject. +The usual implementation for the onSlotCalled method consists of a +switch statement that forwards the arguments to the correct slot. +If the invoked slot has a return value, this is always in the index position +0 of the args array. + + +Example 4: QtObject macro +------------------------- +The previous example shows how to create a simple QObject, however writing +all those ``register`` procs and writing the ``onSlotCalled`` method +becomes boring pretty soon. + +Furthermore all this information can be automatically generated. +For this purpose you can import the NimQmlMacros module that provides +the QtObject macro. + +Let's begin as usual with both the main.nim and main.qml files + +``Examples/QtObjectMacro/main.nim`` + +.. code-block:: nim + :file: ../Examples/QtObjectMacro/main.nim + + +``Examples/QtObjectMacro/main.qml`` + +.. code-block:: qml + :file: ../Examples/QtObjectMacro/main.qml + +Nothing is new in both the ``main.nim`` and ``main.qml`` with respect to +the previous example. What changed is the Contact object: + +``Examples/QtObjectMacro/Contact.nim`` + +.. code-block:: nim + :file: ../Examples/QtObjectMacro/Contact.nim + +In details: +1. Each QObject is defined inside the QtObject macro +2. Each slot is annotated with the ``{.slot.}`` pragma +3. Each signal is annotated with the ``{.signal.}`` pragma +4. Each property is created with the ``QtProperty`` macro + +The ``QtProperty`` macro has the following syntax + +.. code-block:: nim + QtProperty[typeOfProperty] nameOfProperty + diff --git a/Nim/Examples/CMakeLists.txt b/Nim/Examples/CMakeLists.txt index 8502d8d..d9f665a 100644 --- a/Nim/Examples/CMakeLists.txt +++ b/Nim/Examples/CMakeLists.txt @@ -1 +1,4 @@ -add_subdirectory(Simple) \ No newline at end of file +add_subdirectory(HelloWorld) +add_subdirectory(SimpleData) +add_subdirectory(SlotsAndProperties) +add_subdirectory(QtObjectMacro) \ No newline at end of file diff --git a/Nim/Examples/Simple/CMakeLists.txt b/Nim/Examples/HelloWorld/CMakeLists.txt similarity index 54% rename from Nim/Examples/Simple/CMakeLists.txt rename to Nim/Examples/HelloWorld/CMakeLists.txt index 79e03af..5508e23 100644 --- a/Nim/Examples/Simple/CMakeLists.txt +++ b/Nim/Examples/HelloWorld/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 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/QtObjectMacro/CMakeLists.txt b/Nim/Examples/QtObjectMacro/CMakeLists.txt new file mode 100644 index 0000000..c94ccc1 --- /dev/null +++ b/Nim/Examples/QtObjectMacro/CMakeLists.txt @@ -0,0 +1,2 @@ +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/main.qml DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +add_nim_executable(TARGET QtObjectMacro SOURCES main.nim PATHS ../../NimQml) \ 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..7b9c79e --- /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[string] name: + read = getName + write = setName + notify = nameChanged 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/Simple/main.qml b/Nim/Examples/QtObjectMacro/main.qml similarity index 67% rename from Nim/Examples/Simple/main.qml rename to Nim/Examples/QtObjectMacro/main.qml index a2943c3..f21c4be 100644 --- a/Nim/Examples/Simple/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/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} + } +} 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..797d82c --- /dev/null +++ b/Nim/Examples/SlotsAndProperties/Contact.nim @@ -0,0 +1,33 @@ +## Please note we are using templates where ordinarily we would like to use procedures +## due to bug: https://github.com/Araq/Nim/issues/1821 +import NimQml + +type Contact = ref object of QObject + m_name: string + +template newContact*(): Contact = + var result = Contact(m_name: "initialName") + result.create() + result.m_name = "InitialName" + result.registerSlot("getName", [QMetaType.QString]) + result.registerSlot("setName", [QMetaType.Void, QMetaType.QString]) + result.registerSignal("nameChanged", [QMetaType.Void]) + result.registerProperty("name", QMetaType.QString, "getName", "setName", "nameChanged") + result + +method getName*(self: Contact): string = + result = self.m_name + +method setName*(self: Contact, name: string) = + if self.m_name != name: + self.m_name = name + self.emit("nameChanged") + +method onSlotCalled(self: Contact, slotName: string, args: openarray[QVariant]) = + case slotName: + of "getName": + args[0].stringVal = self.getName() + of "setName": + self.setName(args[1].stringVal) + else: + discard() \ 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 + } + } +} 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/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" diff --git a/Nim/NimQml/NimQmlMacros.nim b/Nim/NimQml/NimQmlMacros.nim new file mode 100644 index 0000000..40372d7 --- /dev/null +++ b/Nim/NimQml/NimQmlMacros.nim @@ -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.. 2: # more args than just type + for i in 2..