diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2a1ab..9947582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -# Version 4.0 + +# Version 0.4.1 +* [DQml] Added support for code generation of slots, signals and properties by using custom UDAs +* [DQml] Updated the examples with the new attributes for code generation +* [NimQml] Little fix for adding fixing compilation with Nim 0.11.0 + +# Version 0.4.0 * [DQml] Inheritance of slots and signals is now supported * [DQml] Added support for QAbstractListModel subclasses * [DQml] Put on par the examples for matching those in NimQml @@ -7,19 +13,19 @@ * [DOtherSide] Initial support for windows build * [DOtherSide] Removed most warnings and code cleanup -# Version 3.0 -* [NimQml] Added support for QAbstractListModel subclasses +# Version 0.3.0 +* [NimQml] Added support for QAbstractListModel subclasses * [NimQml] Fixed QtObject macro wrong reorder of the methods and proc declaration (thanks to Will) -* [NimQml] Added new ContactApp example +* [NimQml] Added new ContactApp example * [NimQml] Added optional support for finalizers * [DOtherSide] Added support for injecting the DynamicQObject as behaviour to QObject subclasses * [DotherSide] Added support for QAbstractListModel subclasses -# Version 2.0 +# Version 0.2.0 * [DQml] Initial support for properties creation * [NimQml] Added new macro syntax for creating QObject derived object (thanks to Will) -# Version 1.0 +# Version 0.1.0 * [DOtherSide] Initial version with support for QObject Slot, Signal and Properties creation * [DQml] Initial support for Slot and Signal creation * [NimQml] Initial support for Slot, Signal and Properties creation diff --git a/D/DQml/CMakeLists.txt b/D/DQml/CMakeLists.txt index 58918db..3a87a42 100644 --- a/D/DQml/CMakeLists.txt +++ b/D/DQml/CMakeLists.txt @@ -12,5 +12,6 @@ add_library(${PROJECT_NAME} STATIC qapplication.d qmodelindex.d qabstractlistmodel.d + qobjectgenerators.d ) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/D/DQml/dqml.d b/D/DQml/dqml.d index 216a1c0..411a39b 100644 --- a/D/DQml/dqml.d +++ b/D/DQml/dqml.d @@ -8,3 +8,7 @@ public import qquickview; public import qmetatype; public import qmodelindex; public import qabstractlistmodel; +public import qobjectgenerators; +public import std.traits; +public import std.algorithm; +public import std.stdio; diff --git a/D/DQml/qabstractlistmodel.d b/D/DQml/qabstractlistmodel.d index 50e6ac6..dfd541c 100644 --- a/D/DQml/qabstractlistmodel.d +++ b/D/DQml/qabstractlistmodel.d @@ -19,6 +19,7 @@ class QAbstractListModel : QObject &roleNamesCallback, &flagsCallback, &headerDataCallback); + qobjectInit(); } ~this() diff --git a/D/DQml/qmetatype.d b/D/DQml/qmetatype.d index 356e828..8eff942 100644 --- a/D/DQml/qmetatype.d +++ b/D/DQml/qmetatype.d @@ -32,36 +32,11 @@ public enum QMetaType Unknown = 0, Bool = 1, Int = 2, + Double = 6, String = 10, VoidStr = 31, + Float = 38, QObject = 39, QVariant = 41, Void = 43 } - -public QMetaType GetMetaType(T)() - if (is (T == int) - || is (T == bool) - || is (T == string) - || is (T == void) - || is (T == QObject) - || is (T == QVariant) - || is (T == void*)) - { - static if (is (T == bool)) - return QMetaType.Bool; - else if (is (T == int)) - return QMetaType.Int; - else if (is( T == void)) - return QMetaType.Void; - else if (is (T == string)) - return QMetaType.String; - else if (is (T == QObject)) - return QMetaType.QObject; - else if (is (T == QVariant)) - return QMetaType.QVariant; - else if (is (T == void*)) - return QMetaType.VoidStar; - else - return QMetaType.Unknown; - } diff --git a/D/DQml/qobject.d b/D/DQml/qobject.d index 8ce1550..6df1c1e 100644 --- a/D/DQml/qobject.d +++ b/D/DQml/qobject.d @@ -20,7 +20,10 @@ public class QObject { this.disableDosCalls = disableDosCalls; if (!this.disableDosCalls) + { dos_qobject_create(this.vptr, cast(void*)this, &staticSlotCallback); + qobjectInit(); + } } ~this() @@ -36,6 +39,9 @@ public class QObject { return this.vptr; } + + protected void qobjectInit() + {} protected void onSlotCalled(QVariant slotName, QVariant[] parameters) { @@ -119,5 +125,5 @@ public class QObject } protected void* vptr; - private bool disableDosCalls; + protected bool disableDosCalls; } diff --git a/D/DQml/qobjectgenerators.d b/D/DQml/qobjectgenerators.d new file mode 100644 index 0000000..275c7e1 --- /dev/null +++ b/D/DQml/qobjectgenerators.d @@ -0,0 +1,265 @@ +import std.traits; +import std.algorithm; +import std.string; +import std.stdio; + +struct QtProperty +{ + public string type; + public string name; + public string read; + public string write; + public string notify; + + this(string type, string name, string read, string write, string notify) + { + this.type = type; + this.name = name; + this.read = read; + this.write = write; + this.notify = notify; + } +} +struct QtSlot {}; +struct QtSignal {}; + + +string GenerateVariantConversionCall(string typeName) +{ + switch (typeName) + { + case "string": + return ".toString()"; + case "int": + return ".toInt()"; + case "bool": + return ".toBool()"; + case "float": + return ".toFloat()"; + case "double": + return ".toDouble()"; + case "QVariant": + return ""; + default: + throw new Exception("Unknown conversion function from Qvariant to " ~ typeName); + } +} + +string GenerateArgumentList(string[] typeNames) +{ + string result = ""; + for (int i = 0; i < typeNames.length; ++i) + { + auto typeName = typeNames[i]; + auto variantCall = GenerateVariantConversionCall(typeName); + result ~= i > 0 ? "," : ""; + result ~= format("arguments[%d]%s", i+1, variantCall); + } + return result; +} + +string GenerateSlotCall(FunctionInfo info) +{ + auto args = GenerateArgumentList(info.parameterTypes); + auto call = format("%s(%s)", info.name, args); + auto formatStr = info.returnType != "void" ? "arguments[0].setValue(%s)" : "%s"; + return format(formatStr, call); +} + +string GenerateCaseBlock(FunctionInfo info) +{ + string result = ""; + result ~= format("case \"%s\":\n", info.name); + result ~= format("%s;\n", GenerateSlotCall(info)); + result ~= "break;\n"; + return result; +} + +string GenerateOnSlotCalled(QtInfo info) +{ + string result = "protected override void onSlotCalled(QVariant slotName, QVariant[] arguments)\n"; + result ~= "{\n"; + result ~= "switch(slotName.toString())\n"; + result ~= "{\n"; + foreach (slot; info.slots) + result ~= GenerateCaseBlock(slot); + result ~= "default: super.onSlotCalled(slotName, arguments);\n"; + result ~= "}\n"; // + result ~= "}"; + return result; +} + +string GenerateSignalCall(FunctionInfo info) +{ + string args = ""; + string vars = ""; + for (int i = 0; i < info.parameterTypes.length; ++i) { + if (i > 0) { + args ~= ","; + vars ~= ","; + } + args ~= format("%s val%d", info.parameterTypes[i], i); + vars ~= format("val%d", i); + } + + auto result = format("public %s %s(%s) { emit(\"%s\", %s); }", info.returnType, info.name, args, info.name, vars); + return result; +} + +string GenerateQtSignals(QtInfo info) +{ + string result = ""; + foreach (signal; info.signals) + result ~= GenerateSignalCall(signal) ~ "\n"; + return result; +} + +string GenerateMetaType(string typeName) +{ + switch(typeName) + { + case "void": + return "QMetaType.Void"; + case "int": + return "QMetaType.Int"; + case "string": + return "QMetaType.String"; + case "QObject": + return "QMetaType.QObject"; + case "QVariant": + return "QMetaType.QVariant"; + case "bool": + return "QMetaType.Bool"; + case "float": + return "QMetaType.Float"; + case "double": + return "QMetaType.Double"; + default: + throw new Exception(format("Unknown conversion from %s to QMetaType", typeName)); + } +} + +string GenerateMetaTypesListForSlot(FunctionInfo info) +{ + string result = GenerateMetaType(info.returnType); + result ~= ", "; + result ~= GenerateMetaTypesListForSignal(info); + return result; +} + +string GenerateMetaTypesListForSignal(FunctionInfo info) +{ + string result = ""; + for (int i = 0; i < info.parameterTypes.length; ++i) + { + if (i > 0) + result ~= ", "; + result ~= GenerateMetaType(info.parameterTypes[i]); + } + return result; +} + +string GenerateQObjectInit(QtInfo info) +{ + string result = ""; + result ~= "protected override void qobjectInit()\n"; + result ~= "{\n"; + foreach (slot; info.slots) + { + auto metaTypes = GenerateMetaTypesListForSlot(slot); + result ~= format("registerSlot(\"%s\", [%s]);\n", slot.name, metaTypes); + } + + foreach (signal; info.signals) + { + auto metaTypes = GenerateMetaTypesListForSignal(signal); + result ~= format("registerSignal(\"%s\", [%s]);\n", signal.name, metaTypes); + } + + foreach (property; info.properties) + { + result ~= format("registerProperty(\"%s\", %s, \"%s\", \"%s\", \"%s\");\n", property.name, GenerateMetaType(property.type), property.read, property.write, property.notify); + } + + result ~= "super.qobjectInit();\n"; + + result ~= "}"; + return result; +} + +struct FunctionInfo +{ + string name; + string returnType; + string[] parameterTypes; +} + +struct QtInfo +{ + FunctionInfo[] slots; + FunctionInfo[] signals; + QtProperty[] properties; +} + +mixin template InjectQObjectMacro() +{ + private static QtInfo GetQtUDA(T)() + { + QtInfo result; + + foreach (attribute; __traits(getAttributes, T)) { + static if (is (typeof(attribute) == QtProperty)) { + result.properties ~= attribute; + } + } + + foreach (member; __traits(allMembers, T)) + { + static if (__traits(compiles, __traits(getMember, T, member)) + && isSomeFunction!(__traits(getMember, T, member))) + { + // Retrieve the UDA + auto attributes = __traits(getAttributes, __traits(getMember, T, member)); + + // Turn the tuple in an array of strings + string[] attributeNames; + foreach (attribute; attributes) + attributeNames ~= typeof(attribute).stringof; + + bool isSlot = attributeNames.canFind("QtSlot"); + bool isSignal = attributeNames.canFind("QtSignal"); + + // Extract the Function Return Type and Arguments + if (isSlot || isSignal) + { + FunctionInfo info; + info.name = member; + info.returnType = ReturnType!(__traits(getMember, T, member)).stringof; + + foreach (param; ParameterTypeTuple!(__traits(getMember, T, member))) + info.parameterTypes ~= param.stringof; + + if (isSlot) + result.slots ~= info; + + if (isSignal) + result.signals ~= info; + } + } + } + + return result; + } + + private static string Q_OBJECT(T)() + { + string result = ""; + auto info = T.GetQtUDA!(T); + result ~= GenerateOnSlotCalled(info) ~ "\n"; + result ~= GenerateQObjectInit(info) ~ "\n"; + result ~= GenerateQtSignals(info) ~ "\n"; + return result; + } +} + + diff --git a/D/Examples/ContactApp/applicationlogic.d b/D/Examples/ContactApp/applicationlogic.d index 2a2c2d3..c667746 100644 --- a/D/Examples/ContactApp/applicationlogic.d +++ b/D/Examples/ContactApp/applicationlogic.d @@ -2,59 +2,43 @@ import dqml; import contactlist; import std.stdio; +@QtProperty(QObject.stringof, "contactList", "contactList", null, null) class ApplicationLogic : QObject { + mixin InjectQObjectMacro; + mixin(Q_OBJECT!(ApplicationLogic)); + this(QApplication app) { this.m_app = app; this.m_contactList = new ContactList(); - this.registerSlot("contactList", [QMetaType.QObject]); - this.registerSlot("onLoadTriggered", [QMetaType.Void]); - this.registerSlot("onSaveTriggered", [QMetaType.Void]); - this.registerSlot("onExitTriggered", [QMetaType.Void]); - this.registerProperty("contactList", QMetaType.QObject, "contactList", null, null); } - public ContactList contactList() + + @QtSlot() + public QObject contactList() { return this.m_contactList; } + @QtSlot() public void onLoadTriggered() { writefln("Load Triggered"); } + @QtSlot() public void onSaveTriggered() { writefln("Save Triggered"); } + @QtSlot() public void onExitTriggered() { this.m_app.quit(); } - protected override void onSlotCalled(QVariant slotName, QVariant[] arguments) - { - switch(slotName.toString()) - { - case "contactList": - return arguments[0].setValue(contactList()); - case "onExitTriggered": - onExitTriggered(); - break; - case "onSaveTriggered": - onSaveTriggered(); - break; - case "onLoadTriggered": - onLoadTriggered(); - break; - default: - break; - } - } - private QApplication m_app; private ContactList m_contactList; } diff --git a/D/Examples/ContactApp/contact.d b/D/Examples/ContactApp/contact.d index 900c7cd..55eb289 100644 --- a/D/Examples/ContactApp/contact.d +++ b/D/Examples/ContactApp/contact.d @@ -1,69 +1,55 @@ import dqml; +@QtProperty(string.stringof, "firstName", "firstName", "setFirstName", "firstNameChanged") +@QtProperty(string.stringof, "lastName", "lastName", "setLastName", "lastNameChanged") class Contact : QObject { + mixin InjectQObjectMacro; + mixin(Q_OBJECT!(Contact)); + this(string firstName = "", string lastName = "") { this.m_firstName = firstName; this.m_lastName = lastName; - this.registerSlot("firstName", [QMetaType.String]); - this.registerSlot("setFirstName", [QMetaType.Void, QMetaType.String]); - this.registerSignal("firstNameChanged", [QMetaType.String]); - this.registerSlot("lastName", [QMetaType.String]); - this.registerSlot("setLastName", [QMetaType.Void, QMetaType.String]); - this.registerSignal("lastNameChanged", [QMetaType.String]); - this.registerProperty("firstName", QMetaType.String, "firstName", "setFirstName", "firstNameChanged"); - this.registerProperty("lastName", QMetaType.String, "lastName", "setLastName", "lastNameChanged"); } + @QtSlot() public string firstName() { return this.m_firstName; } + @QtSlot() public void setFirstName(string firstName) { if (this.m_firstName != firstName) { this.m_firstName = firstName; - emit("firstNameChanged", firstName); + firstNameChanged(firstName); } } + @QtSignal() + public void firstNameChanged(string); + + @QtSlot() public string lastName() { return this.m_lastName; } + @QtSlot() public void setLastName(string lastName) { if (this.m_lastName != lastName) { this.m_lastName = lastName; - emit ("lastNameChanged", lastName); + lastNameChanged(lastName); } } - protected override void onSlotCalled(QVariant slotName, QVariant[] arguments) - { - switch (slotName.toString()) - { - case "firstName": - arguments[0].setValue(firstName()); - break; - case "setFirstName": - setFirstName(arguments[1].toString()); - break; - case "lastName": - arguments[0].setValue(lastName()); - break; - case "setLastName": - setLastName(arguments[1].toString()); - break; - default: - break; - } - } + @QtSignal() + public void lastNameChanged(string); private string m_firstName; private string m_lastName; diff --git a/D/Examples/ContactApp/contactlist.d b/D/Examples/ContactApp/contactlist.d index f4329b7..726a74c 100644 --- a/D/Examples/ContactApp/contactlist.d +++ b/D/Examples/ContactApp/contactlist.d @@ -5,13 +5,14 @@ import std.algorithm; class ContactList : QAbstractListModel { + mixin InjectQObjectMacro; + mixin(Q_OBJECT!(ContactList)); + this() { this.m_contacts = []; this.m_roleNames[Roles.FirstName] = "firstName"; this.m_roleNames[Roles.LastName] = "lastName"; - this.registerSlot("add", [QMetaType.Void, QMetaType.String, QMetaType.String]); - this.registerSlot("del", [QMetaType.Void, QMetaType.Int]); } public override int rowCount(QModelIndex parent = null) @@ -50,6 +51,7 @@ class ContactList : QAbstractListModel return this.m_roleNames; } + @QtSlot() public void add(string firstName, string lastName) { auto index = new QModelIndex(); @@ -60,6 +62,7 @@ class ContactList : QAbstractListModel endInsertRows(); } + @QtSlot() public void del(int pos) { if (pos < 0 || pos >= rowCount()) @@ -71,21 +74,6 @@ class ContactList : QAbstractListModel endRemoveRows(); } - protected override void onSlotCalled(QVariant slotName, QVariant[] arguments) - { - switch (slotName.toString()) - { - case "add": - add(arguments[1].toString(), arguments[2].toString()); - break; - case "del": - del(arguments[1].toInt()); - break; - default: - break; - } - } - private Contact[] m_contacts; private string[int] m_roleNames; private enum Roles : int { FirstName = 0, LastName}; diff --git a/D/Examples/SlotsAndProperties/contact.d b/D/Examples/SlotsAndProperties/contact.d index f92cd2f..b38bf4f 100644 --- a/D/Examples/SlotsAndProperties/contact.d +++ b/D/Examples/SlotsAndProperties/contact.d @@ -1,23 +1,25 @@ import dqml; +@QtProperty(string.stringof, "name", "getName", "setName", "nameChanged") +@QtProperty(string.stringof, "surname", "getSurname", "setSurname", "surnameChanged") class Contact : QObject { - this() - { - registerSlot("getName", [QMetaType.String]); - registerSlot("setName", [QMetaType.Void, QMetaType.String]); - registerSignal("nameChanged", [QMetaType.String]); - registerProperty("name", QMetaType.String, "getName", "setName", "nameChanged"); - } + mixin InjectQObjectMacro; + mixin(Q_OBJECT!(Contact)); - ~this() {} - + this() + {} + + ~this() + {} + @QtSlot() public string getName() { return m_name; } + @QtSlot() public void setName(string name) { if (m_name != name) @@ -27,20 +29,24 @@ class Contact : QObject } } - protected override void onSlotCalled(QVariant slotName, QVariant[] arguments) + @QtSignal() + public void nameChanged(string name); + + @QtSlot() + public string getSurname() { - switch (slotName.toString()) - { - case "getName": - arguments[0].setValue(getName()); - break; - case "setName": - setName(arguments[1].toString()); - break; - default: - break; - } + return m_surname; } + @QtSlot() + public void setSurname(string surname) + { + m_surname = surname; + } + + @QtSignal() + void surnameChanged(string surname); + private string m_name; + private string m_surname; } diff --git a/D/Examples/SlotsAndProperties/main.d b/D/Examples/SlotsAndProperties/main.d index e750c2d..ca52808 100644 --- a/D/Examples/SlotsAndProperties/main.d +++ b/D/Examples/SlotsAndProperties/main.d @@ -6,7 +6,7 @@ void main() { try { - auto app = new QGuiApplication(); + auto app = new QApplication(); scope(exit) destroy(app); auto contact = new Contact(); diff --git a/Nim/NimQml/NimQml.nim b/Nim/NimQml/NimQml.nim index 2b369af..9a07c6d 100644 --- a/Nim/NimQml/NimQml.nim +++ b/Nim/NimQml/NimQml.nim @@ -710,8 +710,8 @@ proc roleNamesCallback(modelObject: ptr QAbstractListModelObj, hash: RawQHashInt debugMsg("QAbstractListModel", "roleNamesCallback") let model = cast[QAbstractListModel](modelObject) let table = model.roleNames() - for pair in table.pairs: - dos_qhash_int_qbytearray_insert(hash, pair.key, pair.val) + for key, val in table.pairs: + dos_qhash_int_qbytearray_insert(hash, key, val) method flags*(model: QAbstractListModel, index: QModelIndex): QtItemFlag = ## Return the item flags and the given index