diff --git a/CMakeLists.txt b/CMakeLists.txt index 41f2fa2..fd20dfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ ExternalProject_Add( # Find Qt modules find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core # QCommandLineParser, QStringLiteral + Quick Widgets # QApplication ) @@ -58,6 +59,7 @@ add_executable(RMail ${RMail_SRCS}) add_dependencies(RMail rust) target_link_libraries(RMail + Qt5::Quick Qt5::Widgets KF5::CoreAddons KF5::I18n diff --git a/bindings.json b/bindings.json index a9316c6..99d9719 100644 --- a/bindings.json +++ b/bindings.json @@ -1,12 +1,13 @@ { - "cppfile": "src/tmp.cpp", + "cppFile": "src/tmp.cpp", "rust": { "dir": "common-rust", - "interfacemodule": "testinterface", - "implementationmodule": "testimplementation" + "interfaceModule": "testinterface", + "implementationModule": "testimplementation" }, "objects": [{ "name": "Test", + "type": "Object", "properties": [{ "name": "userName", "type": "QString", @@ -27,5 +28,25 @@ "type": "QByteArray", "write": true }] + }, { + "name": "Directory", + "type": "List", + "properties": [{ + "name": "path", + "type": "QString" + }], + "roles": [{ + "name": "FileIcon", + "value": "Qt::DecorationRole" + }, { + "name": "FilePath", + "value": "Qt::UserRole + 1" + }, { + "name": "FileName", + "value": "Qt::UserRole + 2" + }, { + "name": "FilePermissions", + "value": "Qt::UserRole + 3" + }] }] } diff --git a/common-rust/Cargo.toml b/common-rust/Cargo.toml index b59d13e..8d91ad9 100644 --- a/common-rust/Cargo.toml +++ b/common-rust/Cargo.toml @@ -5,6 +5,15 @@ version = "0.1.0" [dependencies] libc = "*" +[dependencies.serde] +version = "1.0" + +[dependencies.serde_json] +version = "1.0" + +[dependencies.serde_derive] +version = "1.0" + [lib] name = "rust" crate-type = ["staticlib"] diff --git a/common-rust/src/implementation.rs b/common-rust/src/implementation.rs index 3765996..d7cfa85 100644 --- a/common-rust/src/implementation.rs +++ b/common-rust/src/implementation.rs @@ -176,6 +176,10 @@ impl RItemModelTrait for RGeneralItemModel { } fn data(&mut self, index: QModelIndex, role: c_int) -> Variant { let i = self.get(&index); - self.entries[i].data.data(role) + if index.column() == 0 { + self.entries[i].data.data(role) + } else { + Variant::Bool(true) + } } } diff --git a/common-rust/src/lib.rs b/common-rust/src/lib.rs index ba6f27e..0f70e9e 100644 --- a/common-rust/src/lib.rs +++ b/common-rust/src/lib.rs @@ -1,7 +1,7 @@ extern crate libc; -pub mod types; +mod types; pub mod testinterface; -pub mod testimplementation; +mod testimplementation; pub mod interface; -pub mod implementation; +mod implementation; diff --git a/common-rust/src/testimplementation.rs b/common-rust/src/testimplementation.rs index 9f8dbd9..252fa8a 100644 --- a/common-rust/src/testimplementation.rs +++ b/common-rust/src/testimplementation.rs @@ -57,3 +57,33 @@ impl TestTrait for Test { self.emit.icon_changed(); } } +pub struct Directory { + emit: DirectoryEmitter, + model: DirectoryList, + path: String, +} + +impl DirectoryTrait for Directory { + fn create(emit: DirectoryEmitter, model: DirectoryList) -> Directory { + Directory { + emit: emit, + model: model, + path: String::new(), + } + } + fn emit(&self) -> &DirectoryEmitter { + &self.emit + } + fn get_path(&self) -> String { + self.path.clone() + } + fn row_count(&self) -> c_int { 0 } + fn fileicon(&self, row: c_int) -> Variant { + Variant::Bool(row > 0) + } fn filepath(&self, row: c_int) -> Variant { + Variant::Bool(row > 0) + } fn filename(&self, row: c_int) -> Variant { + Variant::Bool(row > 0) + } fn filepermissions(&self, row: c_int) -> Variant { + Variant::Bool(row > 0) + }} diff --git a/rust_qt_binding_generator/rust_qt_binding_generator.cpp b/rust_qt_binding_generator/rust_qt_binding_generator.cpp index 7505e74..0426bcf 100644 --- a/rust_qt_binding_generator/rust_qt_binding_generator.cpp +++ b/rust_qt_binding_generator/rust_qt_binding_generator.cpp @@ -11,23 +11,35 @@ using std::endl; QTextStream out(stdout); QTextStream err(stderr); +enum ObjectType { + ObjectTypeObject, + ObjectTypeList +}; + struct Property { QString name; QString type; bool write; }; +struct Role { + QString name; + QString value; +}; + struct Object { QString name; + ObjectType type; QList properties; + QList roles; }; struct Configuration { - QFileInfo hfile; - QFileInfo cppfile; + QFileInfo hFile; + QFileInfo cppFile; QDir rustdir; - QString interfacemodule; - QString implementationmodule; + QString interfaceModule; + QString implementationModule; QList objects; }; @@ -40,13 +52,30 @@ parseProperty(const QJsonObject& json) { return p; } +Role +parseRole(const QJsonObject& json) { + Role r; + r.name = json.value("name").toString(); + r.value = json.value("value").toString(); + return r; +} + Object parseObject(const QJsonObject& json) { Object o; o.name = json.value("name").toString(); + QString type = json.value("type").toString(); + if (type == "List") { + o.type = ObjectTypeList; + } else { + o.type = ObjectTypeObject; + } for (const QJsonValue& val: json.value("properties").toArray()) { o.properties.append(parseProperty(val.toObject())); } + for (const QJsonValue& val: json.value("roles").toArray()) { + o.roles.append(parseRole(val.toObject())); + } return o; } @@ -70,15 +99,15 @@ parseConfiguration(const QString& path) { } const QJsonObject o = doc.object(); Configuration c; - c.cppfile = QFileInfo(base, o.value("cppfile").toString()); - c.hfile = QFileInfo(c.cppfile.dir(), c.cppfile.completeBaseName() + ".h"); + c.cppFile = QFileInfo(base, o.value("cppFile").toString()); + c.hFile = QFileInfo(c.cppFile.dir(), c.cppFile.completeBaseName() + ".h"); for (const QJsonValue& val: o.value("objects").toArray()) { c.objects.append(parseObject(val.toObject())); } const QJsonObject rust = o.value("rust").toObject(); c.rustdir = QDir(base.filePath(rust.value("dir").toString())); - c.interfacemodule = rust.value("interfacemodule").toString(); - c.implementationmodule = rust.value("implementationmodule").toString(); + c.interfaceModule = rust.value("interfaceModule").toString(); + c.implementationModule = rust.value("implementationModule").toString(); return c; } @@ -108,14 +137,88 @@ QString cGetType(const Property& p) { return p.type + "*, " + p.type.toLower() + "_set"; } +QString baseType(const Object& o) { + if (o.type != ObjectTypeObject) { + return "QAbstractItemModel"; + } + return "QObject"; +} + +void writeHeaderItemModel(QTextStream& h, const Object& o) { + h << QString(R"( + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent) const; +signals: + void newDataReady(); +)"); +} + +void writeCppListModel(QTextStream& cpp, const Object& o) { + const QString lcname(o.name.toLower()); + + cpp << "enum " << o.name << "Role {\n"; + for (auto role: o.roles) { + cpp << " " << o.name << "Role" << role.name << " = " << role.value << ",\n"; + } + cpp << "};\n\n"; + + cpp << "extern \"C\" {\n"; + for (auto role: o.roles) { + cpp << QString(" void %2_data_%3(%1Interface*, int, QVariant*, qvariant_set);\n") + .arg(o.name, lcname, role.name.toLower()); + } + cpp << QString(R"( + int %2_row_count(%1Interface*, qmodelindex_t parent); +} +int %1::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 1; +} + +int %1::rowCount(const QModelIndex &parent) const +{ + return %2_row_count(d, parent); +} + +QModelIndex %1::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid() && column == 0) { + return createIndex(row, 0, (quintptr)0); + } + return QModelIndex(); +} + +QModelIndex %1::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + +QVariant %1::data(const QModelIndex &index, int role) const +{ + QVariant v; + switch ((%1Role)role) { +)").arg(o.name, lcname); + + for (auto role: o.roles) { + cpp << QString(" case %1Role%2:\n").arg(o.name, role.name); + cpp << QString(" %1_data_%2(d, index.row(), &v, set_qvariant);\n") + .arg(lcname, role.name.toLower()); + cpp << " break;\n"; + } + cpp << " }\n return v;\n}\n"; +} + void writeHeaderObject(QTextStream& h, const Object& o) { h << QString(R"( class %1Interface; -class %1 : public QObject +class %1 : public %3 { Q_OBJEC%2 %1Interface * const d; -)").arg(o.name, "T"); +)").arg(o.name, "T", baseType(o)); for (auto p: o.properties) { h << QString(" Q_PROPERTY(%1 %2 READ %2 %3NOTIFY %2Changed FINAL)") .arg(p.type, p.name, @@ -131,6 +234,9 @@ class %1 : public QObject h << " void set" << upperInitial(p.name) << "(" << type(p) << " v);" << endl; } } + if (baseType(o) == "QAbstractItemModel") { + writeHeaderItemModel(h, o); + } h << "signals:" << endl; for (auto p: o.properties) { h << " void " << p.name << "Changed();" << endl; @@ -148,6 +254,13 @@ void writeObjectCDecl(QTextStream& cpp, const Object& o) { for (const Property& p: o.properties) { cpp << QString(", void (*)(%1*)").arg(o.name); } + if (o.type == ObjectTypeList) { + cpp << QString(R"(, + void (*)(%1*, int, int), + void (*)(%1*), + void (*)(%1*, int, int), + void (*)(%1*))").arg(o.name); + } cpp << ");" << endl; cpp << QString(" void %2_free(%1Interface*);").arg(o.name, lcname) << endl; @@ -169,13 +282,29 @@ void writeObjectCDecl(QTextStream& cpp, const Object& o) { void writeCppObject(QTextStream& cpp, const Object& o) { const QString lcname(o.name.toLower()); - cpp << QString("%1::%1(QObject *parent):\n QObject(parent),") - .arg(o.name) << endl; + cpp << QString("%1::%1(QObject *parent):\n %2(parent),") + .arg(o.name, baseType(o)) << endl; cpp << QString(" d(%1_new(this").arg(lcname); for (const Property& p: o.properties) { cpp << QString(",\n [](%1* o) { emit o->%2Changed(); }") .arg(o.name, p.name); } + if (o.type == ObjectTypeList) { + cpp << QString(R"(, + [](%1* o, int first, int last) { + emit o->beginInsertRows(QModelIndex(), first, last); + }, + [](%1* o) { + emit o->endInsertRows(); + }, + [](%1* o, int first, int last) { + emit o->beginRemoveRows(QModelIndex(), first, last); + }, + [](%1* o) { + emit o->endRemoveRows(); + } +)").arg(o.name); + } cpp << QString(R"()) {} %1::~%1() { @@ -204,10 +333,13 @@ void writeCppObject(QTextStream& cpp, const Object& o) { cpp << "}" << endl; } } + if (o.type == ObjectTypeList) { + writeCppListModel(cpp, o); + } } void writeHeader(const Configuration& conf) { - QFile hFile(conf.hfile.absoluteFilePath()); + QFile hFile(conf.hFile.absoluteFilePath()); if (!hFile.open(QIODevice::WriteOnly | QIODevice::Text)) { err << QCoreApplication::translate("main", "Cannot write %1.\n").arg(hFile.fileName()); @@ -215,12 +347,14 @@ void writeHeader(const Configuration& conf) { exit(1); } QTextStream h(&hFile); - const QString guard(conf.hfile.fileName().replace('.', '_').toUpper()); - h << QString(R"(#ifndef %1 + const QString guard(conf.hFile.fileName().replace('.', '_').toUpper()); + h << QString(R"(/* generated by rust_qt_binding_generator */ +#ifndef %1 #define %1 #include #include +#include )").arg(guard); for (auto object: conf.objects) { @@ -231,7 +365,7 @@ void writeHeader(const Configuration& conf) { } void writeCpp(const Configuration& conf) { - QFile cppFile(conf.cppfile.absoluteFilePath()); + QFile cppFile(conf.cppFile.absoluteFilePath()); if (!cppFile.open(QIODevice::WriteOnly | QIODevice::Text)) { err << QCoreApplication::translate("main", "Cannot write %1.\n").arg(cppFile.fileName()); @@ -239,7 +373,8 @@ void writeCpp(const Configuration& conf) { exit(1); } QTextStream cpp(&cppFile); - cpp << QString(R"(#include "%1" + cpp << QString(R"(/* generated by rust_qt_binding_generator */ +#include "%1" #include namespace { @@ -348,7 +483,7 @@ void set_qvariant(QVariant* v, qvariant_t* val) { } extern "C" { -)").arg(conf.hfile.fileName()); +)").arg(conf.hFile.fileName()); for (auto object: conf.objects) { writeObjectCDecl(cpp, object); @@ -391,7 +526,7 @@ QString rustTypeInit(const Property& p) { void writeRustInterfaceObject(QTextStream& r, const Object& o) { const QString lcname(o.name.toLower()); - r << QString(R"( + r << QString(R"(/* generated by rust_qt_binding_generator */ pub struct %1QObject {} #[derive (Clone)] @@ -421,12 +556,41 @@ impl %1Emitter { )").arg(p.name.toLower()); } + QString modelStruct = ""; + if (o.type == ObjectTypeList) { + modelStruct = ", model: " + o.name + "List"; + r << QString(R"(} + +pub struct %1List { + qobject: *const %1QObject, + %2_begin_insert_rows: fn(*const %1QObject, c_int, c_int), + %2_end_insert_rows: fn(*const %1QObject), + %2_begin_remove_rows: fn(*const %1QObject, c_int, c_int), + %2_end_remove_rows: fn(*const %1QObject) +} + +impl %1List { + pub fn %2_begin_insert_rows(&self, first: c_int, last: c_int) { + (self.%2_begin_insert_rows)(self.qobject, first, last); + } + pub fn %2_end_insert_rows(&self) { + (self.%2_end_insert_rows)(self.qobject); + } + pub fn %2_begin_remove_rows(&self, first: c_int, last: c_int) { + (self.%2_begin_remove_rows)(self.qobject, first, last); + } + pub fn %2_end_remove_rows(&self) { + (self.%2_end_remove_rows)(self.qobject); + } +)").arg(o.name, lcname); + } + r << QString(R"(} pub trait %1Trait { - fn create(emit: %1Emitter) -> Self; + fn create(emit: %1Emitter%2) -> Self; fn emit(&self) -> &%1Emitter; -)").arg(o.name); +)").arg(o.name, modelStruct); for (const Property& p: o.properties) { const QString lc(p.name.toLower()); r << QString(" fn get_%1(&self) -> %2;\n").arg(lc, rustType(p)); @@ -434,6 +598,13 @@ pub trait %1Trait { r << QString(" fn set_%1(&mut self, value: %2);\n").arg(lc, rustType(p)); } } + if (o.type == ObjectTypeList) { + r << "fn row_count(&self) -> c_int;\n"; + for (auto role: o.roles) { + r << QString(" fn %1(&self, row: c_int) -> Variant;\n") + .arg(role.name.toLower()); + } + } r << QString(R"(} @@ -443,15 +614,34 @@ pub extern "C" fn %2_new(qobject: *const %1QObject)").arg(o.name, lcname); r << QString(",\n %2_changed: fn(*const %1QObject)") .arg(o.name, p.name.toLower()); } + if (o.type == ObjectTypeList) { + r << QString(R"(, + %2_begin_insert_rows: fn(*const %1QObject, c_int, c_int), + %2_end_insert_rows: fn(*const %1QObject), + %2_begin_remove_rows: fn(*const %1QObject, c_int, c_int), + %2_end_remove_rows: fn(*const %1QObject))").arg(o.name, lcname); + } r << QString(R"() -> *mut %1 { let emit = %1Emitter { qobject: Arc::new(Mutex::new(qobject)))").arg(o.name); for (const Property& p: o.properties) { r << QString(",\n %1_changed: %1_changed").arg(p.name.toLower()); } + QString model = ""; + if (o.type == ObjectTypeList) { + model = ", model"; + r << QString(R"( }; + let model = %1List { + qobject: qobject, + %2_begin_insert_rows: %2_begin_insert_rows, + %2_end_insert_rows: %2_end_insert_rows, + %2_begin_remove_rows: %2_begin_remove_rows, + %2_end_remove_rows: %2_end_remove_rows +)").arg(o.name, lcname); + } r << QString(R"( }; - let d = %1::create(emit); + let d = %1::create(emit%3); Box::into_raw(Box::new(d)) } @@ -459,7 +649,7 @@ pub extern "C" fn %2_new(qobject: *const %1QObject)").arg(o.name, lcname); pub unsafe extern "C" fn %2_free(ptr: *mut %1) { Box::from_raw(ptr).emit().clear(); } -)").arg(o.name, lcname); +)").arg(o.name, lcname, model); for (const Property& p: o.properties) { const QString base = QString("%1_%2").arg(lcname, p.name.toLower()); QString ret = ") -> " + rustType(p); @@ -497,6 +687,25 @@ pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: %4) { } } } + if (o.type == ObjectTypeList) { + r << QString(R"( +#[no_mangle] +pub unsafe extern "C" fn %2_row_count(ptr: *const %1) -> c_int { + (&*ptr).row_count() +} +)").arg(o.name, lcname); + for (auto role: o.roles) { + r << QString(R"( +#[no_mangle] +pub unsafe extern "C" fn %2_data_%3(ptr: *const %1, row: c_int, + d: *mut c_void, + set: fn(*mut c_void, &QVariant)) { + let data = (& *ptr).%3(row); + set(d, &QVariant::from(&data)); +} +)").arg(o.name, lcname, role.name.toLower()); + } + } } QString rustFile(const QDir rustdir, const QString& module) { @@ -506,7 +715,7 @@ QString rustFile(const QDir rustdir, const QString& module) { } void writeRustInterface(const Configuration& conf) { - QFile file(rustFile(conf.rustdir, conf.interfacemodule)); + QFile file(rustFile(conf.rustdir, conf.interfaceModule)); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { err << QCoreApplication::translate("main", "Cannot write %1.\n").arg(file.fileName()); @@ -523,7 +732,7 @@ use std::sync::{Arc, Mutex}; use std::ptr::null; use %1::*; -)").arg(conf.implementationmodule); +)").arg(conf.implementationModule); for (auto object: conf.objects) { writeRustInterfaceObject(r, object); @@ -532,17 +741,25 @@ use %1::*; void writeRustImplementationObject(QTextStream& r, const Object& o) { const QString lcname(o.name.toLower()); + QString modelStruct = ""; r << QString("pub struct %1 {\n emit: %1Emitter,\n").arg((o.name)); + if (o.type == ObjectTypeList) { + modelStruct = ", model: " + o.name + "List"; + r << QString(" model: %1List,\n").arg(o.name); + } for (const Property& p: o.properties) { const QString lc(p.name.toLower()); r << QString(" %1: %2,\n").arg(lc, rustType(p)); } r << "}\n\n"; r << QString(R"(impl %1Trait for %1 { - fn create(emit: %1Emitter) -> %1 { + fn create(emit: %1Emitter%2) -> %1 { %1 { emit: emit, -)").arg(o.name); +)").arg(o.name, modelStruct); + if (o.type == ObjectTypeList) { + r << QString(" model: model,\n"); + } for (const Property& p: o.properties) { const QString lc(p.name.toLower()); r << QString(" %1: %2,\n").arg(lc, rustTypeInit(p)); @@ -570,11 +787,20 @@ void writeRustImplementationObject(QTextStream& r, const Object& o) { )").arg(lc, rustType(p)); } } + if (o.type == ObjectTypeList) { + r << " fn row_count(&self) -> c_int { 0 }\n"; + for (auto role: o.roles) { + r << QString(" fn %1(&self, row: c_int) -> Variant {\n") + .arg(role.name.toLower()); + r << " Variant::Bool(row > 0)\n"; + r << " }"; + } + } r << "}\n"; } void writeRustImplementation(const Configuration& conf) { - QFile file(rustFile(conf.rustdir, conf.implementationmodule)); + QFile file(rustFile(conf.rustdir, conf.implementationModule)); if (file.exists()) { return; } @@ -589,7 +815,7 @@ void writeRustImplementation(const Configuration& conf) { use types::*; use %1::*; -)").arg(conf.interfacemodule); +)").arg(conf.interfaceModule); for (auto object: conf.objects) { writeRustImplementationObject(r, object); diff --git a/src/main.cpp b/src/main.cpp index c5970ef..a97874c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,15 @@ #include "RMailObject.h" +#include "tmp.h" #include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include int main (int argc, char *argv[]) { @@ -45,10 +48,23 @@ int main (int argc, char *argv[]) parser.process(app); aboutData.processCommandLine(&parser); + QQmlApplicationEngine engine; + RItemModel model; QTreeView view; + view.setAnimated(true); + view.setUniformRowHeights(true); view.setModel(&model); view.show(); +/* +*/ + + Directory directory; + + engine.rootContext()->setContextProperty("fsModel", &model); + engine.rootContext()->setContextProperty("directory", &directory); + engine.load(QUrl(QStringLiteral("test.qml"))); + return app.exec(); /* RMailObject rmail;