diff --git a/.gitignore b/.gitignore index 76b6a26..d08c4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -/common-rust/Cargo.lock +/demo/rust/Cargo.lock +/demo/rust/target/ +/tests/rust_*/Cargo.lock +/tests/rust_*/target/ diff --git a/rust_qt_binding_generator/rust_qt_binding_generator.cpp b/rust_qt_binding_generator/rust_qt_binding_generator.cpp index 3ff8089..ed57648 100644 --- a/rust_qt_binding_generator/rust_qt_binding_generator.cpp +++ b/rust_qt_binding_generator/rust_qt_binding_generator.cpp @@ -1310,6 +1310,15 @@ use %1::*; void writeRustImplementationObject(QTextStream& r, const Object& o) { const QString lcname(snakeCase(o.name)); + if (o.type != ObjectType::Object) { + r << "#[derive (Default, Clone)]\n"; + r << QString("struct %1Item {\n").arg(o.name); + for (auto role: o.allRoles) { + const QString lc(snakeCase(role.name)); + r << QString(" %1: %2,\n").arg(lc, role.type.rustType); + } + r << "}\n\n"; + } QString modelStruct = ""; r << QString("pub struct %1 {\n emit: %1Emitter,\n").arg((o.name)); if (o.type == ObjectType::List) { @@ -1323,6 +1332,9 @@ void writeRustImplementationObject(QTextStream& r, const Object& o) { const QString lc(snakeCase(p.name)); r << QString(" %1: %2,\n").arg(lc, rustType(p)); } + if (o.type == ObjectType::List) { + r << QString(" list: Vec<%1Item>,\n").arg(o.name); + } r << "}\n\n"; r << QString(R"(impl %1Trait for %1 { fn create(emit: %1Emitter%2) -> %1 { @@ -1336,6 +1348,10 @@ void writeRustImplementationObject(QTextStream& r, const Object& o) { const QString lc(snakeCase(p.name)); r << QString(" %1: %2,\n").arg(lc, rustTypeInit(p)); } + if (o.type == ObjectType::List) { + r << QString(" list: vec![%1Item::default(); 10],\n") + .arg(o.name); + } r << QString(R"( } } fn emit(&self) -> &%1Emitter { @@ -1364,15 +1380,23 @@ void writeRustImplementationObject(QTextStream& r, const Object& o) { if (o.type == ObjectType::UniformTree) { index = ", row: c_int, parent: usize"; } - r << " fn row_count(&self" << index << ") -> c_int {\n 10\n }\n"; + r << " fn row_count(&self" << index << ") -> c_int {\n self.list.len() as c_int\n }\n"; if (o.type == ObjectType::UniformTree) { index = ", parent: usize"; } for (auto role: o.allRoles) { + const QString lc(snakeCase(role.name)); r << QString(" fn %1(&self, row: c_int%3) -> %2 {\n") - .arg(snakeCase(role.name), rustType(role), index); - r << " " << rustTypeInit(role) << "\n"; + .arg(lc, rustType(role), index); + r << " self.list[row as usize]." << lc << ".clone()\n"; r << " }\n"; + if (role.write) { + r << QString(" fn set_%1(&mut self, row: c_int%3, v: %2) -> bool {\n") + .arg(snakeCase(role.name), rustType(role), index); + r << " self.list[row as usize]." << lc << " = v;\n"; + r << " true\n"; + r << " }\n"; + } } } if (o.type == ObjectType::UniformTree) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 08b357f..adb8bf3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -61,4 +61,4 @@ endfunction(rust_test) rust_test(test_object rust_object) rust_test(test_object_types rust_object_types) - +rust_test(test_list rust_list) diff --git a/tests/rust_list/Cargo.toml b/tests/rust_list/Cargo.toml new file mode 100644 index 0000000..8b321d0 --- /dev/null +++ b/tests/rust_list/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rust_list" +version = "1.0.0" + +[dependencies] +libc = "*" + +[lib] +name = "rust" +crate-type = ["staticlib"] diff --git a/tests/rust_list/src/implementation.rs b/tests/rust_list/src/implementation.rs new file mode 100644 index 0000000..ac4da5d --- /dev/null +++ b/tests/rust_list/src/implementation.rs @@ -0,0 +1,41 @@ +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(dead_code)] +use libc::c_int; +use libc::c_uint; +use types::*; +use interface::*; + +#[derive (Default, Clone)] +struct PersonsItem { + user_name: String, +} + +pub struct Persons { + emit: PersonsEmitter, + model: PersonsList, + list: Vec, +} + +impl PersonsTrait for Persons { + fn create(emit: PersonsEmitter, model: PersonsList) -> Persons { + Persons { + emit: emit, + model: model, + list: vec![PersonsItem::default(); 10], + } + } + fn emit(&self) -> &PersonsEmitter { + &self.emit + } + fn row_count(&self) -> c_int { + self.list.len() as c_int + } + fn user_name(&self, row: c_int) -> String { + self.list[row as usize].user_name.clone() + } + fn set_user_name(&mut self, row: c_int, v: String) -> bool { + self.list[row as usize].user_name = v; + true + } +} diff --git a/tests/rust_list/src/interface.rs b/tests/rust_list/src/interface.rs new file mode 100644 index 0000000..3b2d463 --- /dev/null +++ b/tests/rust_list/src/interface.rs @@ -0,0 +1,141 @@ +/* generated by rust_qt_binding_generator */ +#![allow(unknown_lints)] +#![allow(mutex_atomic, needless_pass_by_value)] +#![allow(unused_imports)] +use libc::{c_int, c_uint, c_void}; +use types::*; +use std::sync::{Arc, Mutex}; +use std::ptr::null; + +use implementation::*; + +pub struct PersonsQObject {} + +#[derive (Clone)] +pub struct PersonsEmitter { + qobject: Arc>, + new_data_ready: fn(*const PersonsQObject), +} + +unsafe impl Send for PersonsEmitter {} + +impl PersonsEmitter { + fn clear(&self) { + *self.qobject.lock().unwrap() = null(); + } + pub fn new_data_ready(&self) { + let ptr = *self.qobject.lock().unwrap(); + if !ptr.is_null() { + (self.new_data_ready)(ptr); + } + } +} + +pub struct PersonsList { + qobject: *const PersonsQObject, + begin_reset_model: fn(*const PersonsQObject), + end_reset_model: fn(*const PersonsQObject), + begin_insert_rows: fn(*const PersonsQObject, c_int, c_int), + end_insert_rows: fn(*const PersonsQObject), + begin_remove_rows: fn(*const PersonsQObject, c_int, c_int), + end_remove_rows: fn(*const PersonsQObject), +} + +impl PersonsList { + pub fn begin_reset_model(&self) { + (self.begin_reset_model)(self.qobject); + } + pub fn end_reset_model(&self) { + (self.end_reset_model)(self.qobject); + } + pub fn begin_insert_rows(&self, first: c_int, last: c_int) { + (self.begin_insert_rows)(self.qobject, first, last); + } + pub fn end_insert_rows(&self) { + (self.end_insert_rows)(self.qobject); + } + pub fn begin_remove_rows(&self, first: c_int, last: c_int) { + (self.begin_remove_rows)(self.qobject, first, last); + } + pub fn end_remove_rows(&self) { + (self.end_remove_rows)(self.qobject); + } +} + +pub trait PersonsTrait { + fn create(emit: PersonsEmitter, model: PersonsList) -> Self; + fn emit(&self) -> &PersonsEmitter; + fn row_count(&self) -> c_int; + fn can_fetch_more(&self) -> bool { false } + fn fetch_more(&mut self) {} + fn sort(&mut self, c_int, SortOrder) {} + fn user_name(&self, row: c_int) -> String; + fn set_user_name(&mut self, row: c_int, String) -> bool; +} + +#[no_mangle] +pub extern "C" fn persons_new(qobject: *const PersonsQObject, + new_data_ready: fn(*const PersonsQObject), + begin_reset_model: fn(*const PersonsQObject), + end_reset_model: fn(*const PersonsQObject), + begin_insert_rows: fn(*const PersonsQObject, + c_int, + c_int), + end_insert_rows: fn(*const PersonsQObject), + begin_remove_rows: fn(*const PersonsQObject, + c_int, + c_int), + end_remove_rows: fn(*const PersonsQObject)) + -> *mut Persons { + let emit = PersonsEmitter { + qobject: Arc::new(Mutex::new(qobject)), + new_data_ready: new_data_ready, + }; + let model = PersonsList { + qobject: qobject, + begin_reset_model: begin_reset_model, + end_reset_model: end_reset_model, + begin_insert_rows: begin_insert_rows, + end_insert_rows: end_insert_rows, + begin_remove_rows: begin_remove_rows, + end_remove_rows: end_remove_rows, + }; + let d = Persons::create(emit, model); + Box::into_raw(Box::new(d)) +} + +#[no_mangle] +pub unsafe extern "C" fn persons_free(ptr: *mut Persons) { + Box::from_raw(ptr).emit().clear(); +} + +#[no_mangle] +pub unsafe extern "C" fn persons_row_count(ptr: *const Persons) -> c_int { + (&*ptr).row_count() +} +#[no_mangle] +pub unsafe extern "C" fn persons_can_fetch_more(ptr: *const Persons) -> bool { + (&*ptr).can_fetch_more() +} +#[no_mangle] +pub unsafe extern "C" fn persons_fetch_more(ptr: *mut Persons) { + (&mut *ptr).fetch_more() +} +#[no_mangle] +pub unsafe extern "C" fn persons_sort(ptr: *mut Persons, column: c_int, order: SortOrder) { + (&mut *ptr).sort(column, order) +} + +#[no_mangle] +pub unsafe extern "C" fn persons_data_user_name(ptr: *const Persons, + row: c_int, + d: *mut c_void, + set: fn(*mut c_void, QString)) { + let data = (&*ptr).user_name(row); + set(d, QString::from(&data)); +} + +#[no_mangle] +pub unsafe extern "C" fn persons_set_data_user_name(ptr: *mut Persons, row: c_int, v: QStringIn) -> bool { + (&mut *ptr).set_user_name(row, v.convert()) +} diff --git a/tests/rust_list/src/lib.rs b/tests/rust_list/src/lib.rs new file mode 100644 index 0000000..94e4632 --- /dev/null +++ b/tests/rust_list/src/lib.rs @@ -0,0 +1,5 @@ +extern crate libc; + +mod types; +pub mod interface; +mod implementation; diff --git a/tests/rust_list/src/types.rs b/tests/rust_list/src/types.rs new file mode 100644 index 0000000..dbe4009 --- /dev/null +++ b/tests/rust_list/src/types.rs @@ -0,0 +1,104 @@ +/* generated by rust_qt_binding_generator */ +#![allow(dead_code)] +use std::slice; +use libc::{c_int, uint8_t, uint16_t}; + +#[repr(C)] +pub struct COption { + data: T, + some: bool, +} + +impl From> for COption where T: Default { + fn from(t: Option) -> COption { + if let Some(v) = t { + COption { + data: v, + some: true + } + } else { + COption { + data: T::default(), + some: false + } + } + } +} + +#[repr(C)] +pub struct QString { + data: *const uint8_t, + len: c_int, +} + +#[repr(C)] +pub struct QStringIn { + data: *const uint16_t, + len: c_int, +} + +impl QStringIn { + pub fn convert(&self) -> String { + let data = unsafe { slice::from_raw_parts(self.data, self.len as usize) }; + String::from_utf16_lossy(data) + } +} + +impl<'a> From<&'a String> for QString { + fn from(string: &'a String) -> QString { + QString { + len: string.len() as c_int, + data: string.as_ptr(), + } + } +} + +#[repr(C)] +pub struct QByteArray { + data: *const uint8_t, + len: c_int, +} + +impl QByteArray { + pub fn convert(&self) -> Vec { + let data = unsafe { slice::from_raw_parts(self.data, self.len as usize) }; + Vec::from(data) + } +} + +impl<'a> From<&'a Vec> for QByteArray { + fn from(value: &'a Vec) -> QByteArray { + QByteArray { + len: value.len() as c_int, + data: value.as_ptr(), + } + } +} + +#[repr(C)] +pub struct QModelIndex { + row: c_int, + internal_id: usize, +} + +impl QModelIndex { + pub fn invalid() -> QModelIndex { + QModelIndex { + row: -1, + internal_id: 0, + } + } + pub fn create(row: c_int, id: usize) -> QModelIndex { + QModelIndex { + row: row, + internal_id: id, + } + } +} + +#[repr(C)] +pub enum SortOrder { + Ascending = 0, + Descending = 1 +} + diff --git a/tests/test_list.cpp b/tests/test_list.cpp new file mode 100644 index 0000000..d7a7cfb --- /dev/null +++ b/tests/test_list.cpp @@ -0,0 +1,49 @@ +#include "test_list_rust.h" +#include +#include + +class TestRustList : public QObject +{ + Q_OBJECT +private slots: + void testConstructor(); + void testStringGetter(); + void testStringSetter(); +}; + +void TestRustList::testConstructor() +{ + Persons persons; +} + +void TestRustList::testStringGetter() +{ + Persons persons; + QCOMPARE(persons.rowCount(), 10); + QVariant value = persons.data(persons.index(0,0)); + // value should be empty string in default implementation + QVERIFY(value.isValid()); + QCOMPARE(value.type(), QVariant::String); + QCOMPARE(value.toString(), QString()); +} + +void TestRustList::testStringSetter() +{ + // GIVEN + Persons persons; + QSignalSpy spy(&persons, &Persons::dataChanged); + + // WHEN + const QModelIndex index(persons.index(0,0)); + const bool set = persons.setData(index, "Konqi"); + + // THEN + QVERIFY(set); + QVERIFY(spy.isValid()); + QCOMPARE(spy.count(), 1); + QVariant value = persons.data(persons.index(0,0)); + QCOMPARE(value.toString(), QString("Konqi")); +} + +QTEST_MAIN(TestRustList) +#include "test_list.moc" diff --git a/tests/test_list.json b/tests/test_list.json new file mode 100644 index 0000000..f21fe54 --- /dev/null +++ b/tests/test_list.json @@ -0,0 +1,23 @@ +{ + "cppFile": "test_list_rust.cpp", + "rust": { + "dir": "rust_list", + "interfaceModule": "interface", + "implementationModule": "implementation", + "typesModule": "types" + }, + "objects": [{ + "name": "Persons", + "type": "List", + "roles": [{ + "name": "userName", + "value": "Qt::DisplayRole", + "type": "QString" + }, { + "name": "userName", + "value": "Qt::EditRole", + "type": "QString", + "write": true + }] + }] +} diff --git a/tests/test_list_rust.cpp b/tests/test_list_rust.cpp new file mode 100644 index 0000000..5f6126f --- /dev/null +++ b/tests/test_list_rust.cpp @@ -0,0 +1,202 @@ +/* generated by rust_qt_binding_generator */ +#include "test_list_rust.h" + +namespace { + template + struct option { + private: + T value; + bool some; + public: + operator QVariant() const { + if (some) { + return QVariant(value); + } + return QVariant(); + } + }; + struct qbytearray_t { + private: + const char* data; + int len; + public: + qbytearray_t(const QByteArray& v): + data(v.data()), + len(v.size()) { + } + 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; + quintptr id; + }; +} +typedef void (*qstring_set)(QString*, qstring_t*); +void set_qstring(QString* v, qstring_t* val) { + *v = *val; +} +typedef void (*qbytearray_set)(QByteArray*, qbytearray_t*); +void set_qbytearray(QByteArray* v, qbytearray_t* val) { + *v = *val; +} + +extern "C" { + Persons::Private* persons_new(Persons*, + void (*)(const Persons*), + void (*)(Persons*), + void (*)(Persons*), + void (*)(Persons*, int, int), + void (*)(Persons*), + void (*)(Persons*, int, int), + void (*)(Persons*)); + void persons_free(Persons::Private*); +}; +Persons::Persons(QObject *parent): + QAbstractItemModel(parent), + d(persons_new(this, + [](const Persons* o) { + emit o->newDataReady(QModelIndex()); + }, + [](Persons* o) { + o->beginResetModel(); + }, + [](Persons* o) { + o->endResetModel(); + }, + [](Persons* o, int first, int last) { + o->beginInsertRows(QModelIndex(), first, last); + }, + [](Persons* o) { + o->endInsertRows(); + }, + [](Persons* o, int first, int last) { + o->beginRemoveRows(QModelIndex(), first, last); + }, + [](Persons* o) { + o->endRemoveRows(); + } + )) { + connect(this, &Persons::newDataReady, this, [this](const QModelIndex& i) { + fetchMore(i); + }, Qt::QueuedConnection); +} + + +Persons::~Persons() { + persons_free(d); +} +extern "C" { + void persons_data_user_name(const Persons::Private*, int, QString*, qstring_set); + bool persons_set_data_user_name(Persons::Private*, int, qstring_t); + void persons_sort(Persons::Private*, int column, Qt::SortOrder order = Qt::AscendingOrder); + + int persons_row_count(const Persons::Private*); + bool persons_can_fetch_more(const Persons::Private*); + void persons_fetch_more(Persons::Private*); +} +int Persons::columnCount(const QModelIndex &parent) const +{ + return (parent.isValid()) ? 0 : 1; +} + +bool Persons::hasChildren(const QModelIndex &parent) const +{ + return rowCount(parent) > 0; +} + +int Persons::rowCount(const QModelIndex &parent) const +{ + return (parent.isValid()) ? 0 : persons_row_count(d); +} + +QModelIndex Persons::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid() && column == 0) { + return createIndex(row, 0, (quintptr)0); + } + return QModelIndex(); +} + +QModelIndex Persons::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + +bool Persons::canFetchMore(const QModelIndex &parent) const +{ + return (parent.isValid()) ? 0 : persons_can_fetch_more(d); +} + +void Persons::fetchMore(const QModelIndex &parent) +{ + if (!parent.isValid()) { + persons_fetch_more(d); + } +} + +void Persons::sort(int column, Qt::SortOrder order) +{ + persons_sort(d, column, order); +} +Qt::ItemFlags Persons::flags(const QModelIndex &i) const +{ + auto flags = QAbstractItemModel::flags(i); + if (i.column() == 0) { + flags |= Qt::ItemIsEditable; + } + return flags; +} +QVariant Persons::data(const QModelIndex &index, int role) const +{ + QVariant v; + QString s; + QByteArray b; + switch (index.column()) { + case 0: + switch (role) { + case Qt::DisplayRole: + persons_data_user_name(d, index.row(), &s, set_qstring); + if (!s.isNull()) v.setValue(s); + break; + case Qt::EditRole: + persons_data_user_name(d, index.row(), &s, set_qstring); + if (!s.isNull()) v.setValue(s); + break; + } + break; + } + return v; +} +QHash Persons::roleNames() const { + QHash names; + names.insert(Qt::DisplayRole, "userName"); + return names; +} +bool Persons::setData(const QModelIndex &index, const QVariant &value, int role) +{ + bool set = false; + if (index.column() == 0) { + if (role == Qt::EditRole) { + set = persons_set_data_user_name(d, index.row(), value.value()); + } + } + if (set) { + emit dataChanged(index, index, QVector() << role); + } + return set; +} diff --git a/tests/test_list_rust.h b/tests/test_list_rust.h new file mode 100644 index 0000000..5e568ce --- /dev/null +++ b/tests/test_list_rust.h @@ -0,0 +1,37 @@ +/* generated by rust_qt_binding_generator */ +#ifndef TEST_LIST_RUST_H +#define TEST_LIST_RUST_H + +#include +#include + +class Persons : public QAbstractItemModel +{ + Q_OBJECT +public: + class Private; +private: + Private * const d; +public: + explicit Persons(QObject *parent = nullptr); + ~Persons(); + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + QHash roleNames() const override; +signals: + // new data is ready to be made available to the model with fetchMore() + void newDataReady(const QModelIndex &parent) const; +signals: +private: +}; +#endif // TEST_LIST_RUST_H