diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ce096f..41f2fa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,6 @@ ExternalProject_Add( INSTALL_COMMAND "" LOG_BUILD ON) - # Find Qt modules find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core # QCommandLineParser, QStringLiteral @@ -48,8 +47,11 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) + +add_subdirectory(rust_qt_binding_generator) + -set(RMail_SRCS src/main.cpp src/RMailObject.cpp) +set(RMail_SRCS src/main.cpp src/RMailObject.cpp src/tmp.cpp) add_executable(RMail ${RMail_SRCS}) diff --git a/bindings.json b/bindings.json new file mode 100644 index 0000000..3a5032e --- /dev/null +++ b/bindings.json @@ -0,0 +1,22 @@ +{ + "cppfile": "src/tmp.cpp", + "objects": [{ + "name": "Test", + "properties": [{ + "name": "userName", + "type": "QString", + "write": true + }, { + "name": "age", + "type": "int" + }, { + "name": "active", + "type": "bool", + "write": true + }, { + "name": "misc", + "type": "QVariant", + "write": true + }] + }] +} diff --git a/rust_qt_binding_generator/CMakeLists.txt b/rust_qt_binding_generator/CMakeLists.txt new file mode 100644 index 0000000..72b0f43 --- /dev/null +++ b/rust_qt_binding_generator/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_executable(rust_qt_binding_generator rust_qt_binding_generator.cpp) +target_link_libraries(rust_qt_binding_generator + Qt5::Core +) diff --git a/rust_qt_binding_generator/rust_qt_binding_generator.cpp b/rust_qt_binding_generator/rust_qt_binding_generator.cpp new file mode 100644 index 0000000..0298d56 --- /dev/null +++ b/rust_qt_binding_generator/rust_qt_binding_generator.cpp @@ -0,0 +1,387 @@ +#include +#include +#include +#include +#include +#include +#include + +using std::endl; + +QTextStream out(stdout); +QTextStream err(stderr); + +struct Property { + QString name; + QString type; + bool write; +}; + +struct Object { + QString name; + QList properties; +}; + +struct Configuration { + QFileInfo hfile; + QFileInfo cppfile; + QList objects; +}; + +Property +parseProperty(const QJsonObject& json) { + Property p; + p.name = json.value("name").toString(); + p.type = json.value("type").toString(); + p.write = json.value("write").toBool(); + return p; +} + +Object +parseObject(const QJsonObject& json) { + Object o; + o.name = json.value("name").toString(); + for (const QJsonValue& val: json.value("properties").toArray()) { + o.properties.append(parseProperty(val.toObject())); + } + return o; +} + +Configuration +parseConfiguration(const QString& path) { + QFile configurationFile(path); + const QDir base = QFileInfo(configurationFile).dir(); + if (!configurationFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + err << QCoreApplication::translate("main", + "Cannot read %1.\n").arg(configurationFile.fileName()); + err.flush(); + exit(1); + } + const QByteArray data(configurationFile.readAll()); + QJsonParseError error; + const QJsonDocument doc(QJsonDocument::fromJson(data, &error)); + if (error.error != QJsonParseError::NoError) { + err << error.errorString(); + err.flush(); + exit(1); + } + 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"); + for (const QJsonValue& val: o.value("objects").toArray()) { + c.objects.append(parseObject(val.toObject())); + } + return c; + /* + QTextStream in(&configurationFile); + in.setCodec("UTF-8"); + configuration = in.readAll(); + configurationFile.close(); + } else { + return 1; + } +*/ +} + +QString upperInitial(const QString& name) { + return name.left(1).toUpper() + name.mid(1); +} + +QString writeProperty(const QString& name) { + return "WRITE set" + upperInitial(name) + " "; +} + +QString type(const Property& p) { + if (p.type.startsWith("Q")) { + return "const " + p.type + "&"; + } + return p.type; +} + +QString cSetType(const Property& p) { + if (p.type.startsWith("Q")) { + return p.type.toLower() + "_t"; + } + return p.type; +} + +QString cGetType(const Property& p) { + return p.type + "*, " + p.type.toLower() + "_set"; +} + +void writeHeaderObject(QTextStream& h, const Object& o) { + h << QString(R"( +class %1Interface; +class %1 : public QObject +{ + Q_OBJEC%2 + %1Interface * const d; +)").arg(o.name, "T"); + for (auto p: o.properties) { + h << QString(" Q_PROPERTY(%1 %2 READ %2 %3NOTIFY %2Changed FINAL)") + .arg(p.type, p.name, + p.write ? writeProperty(p.name) :"") << endl; + } + h << QString(R"(public: + explicit %1(QObject *parent = nullptr); + ~%1(); +)").arg(o.name); + for (auto p: o.properties) { + h << " " << p.type << " " << p.name << "() const;" << endl; + if (p.write) { + h << " void set" << upperInitial(p.name) << "(" << type(p) << " v);" << endl; + } + } + h << "signals:" << endl; + for (auto p: o.properties) { + h << " void " << p.name << "Changed();" << endl; + } + h << "private:" << endl; + for (auto p: o.properties) { + h << " " << p.type << " m_" << p.name << ";" << endl; + } + h << "};" << endl; +} + +void writeObjectCDecl(QTextStream& cpp, const Object& o) { + const QString lcname(o.name.toLower()); + cpp << QString(" %1Interface* %2_new(%1*").arg(o.name, lcname); + for (const Property& p: o.properties) { + cpp << QString(", void (*)(%1*)").arg(o.name); + } + cpp << ");" << endl; + cpp << QString(" void %2_free(%1Interface*);").arg(o.name, lcname) + << endl; + for (const Property& p: o.properties) { + const QString base = QString("%1_%2").arg(lcname, p.name.toLower()); + if (p.type.startsWith("Q")) { + cpp << QString(" void %2_get(%1Interface*, %3);") + .arg(o.name, base, cGetType(p)) << endl; + } else { + cpp << QString(" %3 %2_get(%1Interface*);") + .arg(o.name, base, p.type) << endl; + } + if (p.write) { + cpp << QString(" void %1_set(void*, %2);") + .arg(base, cSetType(p)) << endl; + } + } +} + +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(" 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); + } + cpp << QString(R"()) {} + +%1::~%1() { + %2_free(d); +} +)").arg(o.name, lcname); + + for (const Property& p: o.properties) { + const QString base = QString("%1_%2").arg(lcname, p.name.toLower()); + cpp << QString("%3 %1::%2() const\n{\n").arg(o.name, p.name, p.type); + if (p.type.startsWith("Q")) { + cpp << " " << p.type << " v;\n"; + cpp << " " << base << "_get(d, &v, set_" << p.type.toLower() + << ");\n"; + cpp << " return v;\n}\n"; + } else { + cpp << QString(" return %1_get(d);\n}\n").arg(base); + } + if (p.write) { + cpp << "void " << o.name << "::set" << upperInitial(p.name) << "(" << type(p) << " v) {" << endl; + if (p.type == "QVariant") { + cpp << QString(" variant(v, d, %1_set);").arg(base) << endl; + } else { + cpp << QString(" %1_set(d, v);").arg(base) << endl; + } + cpp << "}" << endl; + } + } +} + +void writeHeader(const Configuration& conf) { + QFile hFile(conf.hfile.absoluteFilePath()); + if (!hFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + err << QCoreApplication::translate("main", + "Cannot write %1.\n").arg(hFile.fileName()); + err.flush(); + exit(1); + } + QTextStream h(&hFile); + const QString guard(conf.hfile.fileName().replace('.', '_').toUpper()); + h << QString(R"( +#ifndef %1 +#define %1 + +#include +#include +#include +#include +)").arg(guard); + + for (auto object: conf.objects) { + writeHeaderObject(h, object); + } + + h << QString("#endif // %1\n").arg(guard); +} + +void writeCpp(const Configuration& conf) { + QFile cppFile(conf.cppfile.absoluteFilePath()); + if (!cppFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + err << QCoreApplication::translate("main", + "Cannot write %1.\n").arg(cppFile.fileName()); + err.flush(); + exit(1); + } + QTextStream cpp(&cppFile); + cpp << QString(R"( +#include "%1" +namespace { + struct qbytearray_t { + private: + const char* data; + int len; + public: + operator QByteArray() const { + return QByteArray(data, len); + } + }; + struct qstring_t { + private: + const void* data; + int len; + public: + qstring_t(const QString& v): + data(static_cast(v.utf16())), + len(v.size()) { + } + operator QString() const { + return QString::fromUtf8(static_cast(data), len); + } + }; + struct qmodelindex_t { + int row; + int column; + uint64_t id; + qmodelindex_t(const QModelIndex& m): + row(m.row()), column(m.column()), id(m.internalId()) {} + }; + struct qvariant_t { + unsigned int type; + int value; + const char* data; + }; + QVariant variant(const qvariant_t& v) { + switch (v.type) { + case QVariant::Bool: return QVariant((bool)v.value); + case QVariant::String: return QString::fromUtf8(static_cast(v.data), v.value); + default:; + } + return QVariant(); + } + void variant(const QByteArray& v, void* d, void (*set)(void*, qvariant_t)) { + set(d, { + .type = QVariant::ByteArray, + .value = v.length(), + .data = v.data() + }); + } + void variant(const QString& v, void* d, void (*set)(void*, qvariant_t)) { + set(d, { + .type = QVariant::String, + .value = v.size(), + .data = static_cast(static_cast(v.utf16())) + }); + } + void variant(const QVariant& v, void* d, void (*set)(void*, qvariant_t)) { + switch (v.type()) { + case QVariant::Bool: + set(d, { + .type = QVariant::Bool, + .value = v.toBool(), + .data = 0 + }); + break; + case QVariant::Int: + set(d, { + .type = QVariant::Int, + .value = v.toInt(), + .data = 0 + }); + break; + case QVariant::ByteArray: + variant(v.toByteArray(), d, set); + break; + case QVariant::String: + variant(v.toString(), d, set); + break; + default: + set(d, { + .type = QVariant::Invalid, + .value = 0, + .data = 0 + }); + } + } +} +typedef void (*qstring_set)(QString*, qstring_t*); +void set_qstring(QString* v, qstring_t* val) { + *v = *val; +} +typedef void (*qvariant_set)(QVariant*, qvariant_t*); +void set_qvariant(QVariant* v, qvariant_t* val) { + *v = variant(*val); +} + +extern "C" { +)").arg(conf.hfile.fileName()); + + for (auto object: conf.objects) { + writeObjectCDecl(cpp, object); + } + cpp << "};" << endl; + + for (auto object: conf.objects) { + writeCppObject(cpp, object); + } +} + +int main(int argc, char *argv[]) { + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(argv[0]); + QCoreApplication::setApplicationVersion("0.1"); + + QCommandLineParser parser; + parser.setApplicationDescription("Generates bindings between Qt and Rust"); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("configuration", + QCoreApplication::translate("main", "Configuration file")); + + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.isEmpty()) { + err << QCoreApplication::translate("main", + "Configuration file is missing.\n"); + return 1; + } + + const QString configurationFile(args.at(0)); + const Configuration configuration = parseConfiguration(configurationFile); + + writeHeader(configuration); + writeCpp(configuration); + + return 0; +} diff --git a/src/RMailObject.cpp b/src/RMailObject.cpp index e1a2fe5..f40962b 100644 --- a/src/RMailObject.cpp +++ b/src/RMailObject.cpp @@ -64,18 +64,18 @@ namespace { typedef void (*qvariant_set)(void*, qvariant_t*); extern "C" { - RMailObjectInterface* hello_new(void*, void (*)(RMailObject*)); + RMailObjectInterface* hello_new(RMailObject*, void (*)(RMailObject*)); void hello_free(RMailObjectInterface*); void hello_set(RMailObjectInterface*, const uint16_t *, size_t); qstring_t hello_get(RMailObjectInterface*); - RItemModelInterface* ritemmodel_new(void*, void (*)(RItemModel*)); + RItemModelInterface* ritemmodel_new(RItemModel*, void (*)(RItemModel*)); void ritemmodel_free(RItemModelInterface*); int ritemmodel_column_count(RItemModelInterface*, qmodelindex_t parent); int ritemmodel_row_count(RItemModelInterface*, qmodelindex_t parent); qmodelindex_t ritemmodel_index(RItemModelInterface*, int row, int column, qmodelindex_t parent); qmodelindex_t ritemmodel_parent(RItemModelInterface*, qmodelindex_t); - void ritemmodel_data(RItemModelInterface*, qmodelindex_t, int, void*, qvariant_set); + void ritemmodel_data(RItemModelInterface*, qmodelindex_t, int, QVariant*, qvariant_set); } RMailObject::RMailObject(QObject *parent):