From e51964db1e7a27126d6fd9912aada435595395c5 Mon Sep 17 00:00:00 2001 From: Ilya Somov Date: Tue, 10 Oct 2023 17:13:50 +0300 Subject: [PATCH 01/13] Add a few more QmlEngine methods --- qmetaobject/src/qtdeclarative.rs | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/qmetaobject/src/qtdeclarative.rs b/qmetaobject/src/qtdeclarative.rs index 5c77158..6021ca7 100644 --- a/qmetaobject/src/qtdeclarative.rs +++ b/qmetaobject/src/qtdeclarative.rs @@ -231,6 +231,54 @@ impl QmlEngine { }) } + pub fn invoke_method_noreturn(&mut self, name: QByteArray, args: &[QVariant]) -> QVariant { + let args_size = args.len(); + let args_ptr = args.as_ptr(); + + assert!(args_size <= 9); + + cpp!(unsafe [ + self as "QmlEngineHolder *", + name as "QByteArray", + args_size as "size_t", + args_ptr as "QVariant *" + ] -> QVariant as "QVariant" + { + auto robjs = self->engine->rootObjects(); + if (robjs.isEmpty()) { + return {}; + } + QVariant ret; + #define INVOKE_METHOD(...) QMetaObject::invokeMethod(robjs.first(), name __VA_ARGS__); + switch (args_size) { + case 0: INVOKE_METHOD(); break; + case 1: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0])); break; + case 2: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1])); break; + case 3: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2])); break; + case 4: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3])); break; + case 5: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3]), Q_ARG(QVariant, args_ptr[4])); break; + case 6: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3]), Q_ARG(QVariant, args_ptr[4]), Q_ARG(QVariant, args_ptr[5])); break; + case 7: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3]), Q_ARG(QVariant, args_ptr[4]), Q_ARG(QVariant, args_ptr[5]), Q_ARG(QVariant, args_ptr[6])); break; + case 8: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3]), Q_ARG(QVariant, args_ptr[4]), Q_ARG(QVariant, args_ptr[5]), Q_ARG(QVariant, args_ptr[6]), Q_ARG(QVariant, args_ptr[7])); break; + case 9: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3]), Q_ARG(QVariant, args_ptr[4]), Q_ARG(QVariant, args_ptr[5]), Q_ARG(QVariant, args_ptr[6]), Q_ARG(QVariant, args_ptr[7]), Q_ARG(QVariant, args_ptr[8])); break; + } + #undef INVOKE_METHOD + return ret; + }) + } + + pub fn trim_component_cache(&self) { + cpp!(unsafe [self as "QmlEngineHolder *"] { + self->engine->trimComponentCache(); + }) + } + + pub fn clear_component_cache(&self) { + cpp!(unsafe [self as "QmlEngineHolder *"] { + self->engine->clearComponentCache(); + }) + } + /// Give a QObject to the engine by wrapping it in a QJSValue /// /// This will create the C++ object. From 9d43036f5841aaae424a39dd9acb2628d7b85ab2 Mon Sep 17 00:00:00 2001 From: Ilya Somov Date: Wed, 11 Oct 2023 22:03:26 +0300 Subject: [PATCH 02/13] Update qtdeclarative.rs --- qmetaobject/src/qtdeclarative.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/qmetaobject/src/qtdeclarative.rs b/qmetaobject/src/qtdeclarative.rs index 6021ca7..07dc491 100644 --- a/qmetaobject/src/qtdeclarative.rs +++ b/qmetaobject/src/qtdeclarative.rs @@ -231,7 +231,8 @@ impl QmlEngine { }) } - pub fn invoke_method_noreturn(&mut self, name: QByteArray, args: &[QVariant]) -> QVariant { + /// This method is the same as [invoke_method] but does not capture or return function's return value + pub fn invoke_method_noreturn(&mut self, name: QByteArray, args: &[QVariant]) { let args_size = args.len(); let args_ptr = args.as_ptr(); @@ -242,13 +243,12 @@ impl QmlEngine { name as "QByteArray", args_size as "size_t", args_ptr as "QVariant *" - ] -> QVariant as "QVariant" - { + ] { auto robjs = self->engine->rootObjects(); if (robjs.isEmpty()) { - return {}; + return; } - QVariant ret; + #define INVOKE_METHOD(...) QMetaObject::invokeMethod(robjs.first(), name __VA_ARGS__); switch (args_size) { case 0: INVOKE_METHOD(); break; @@ -263,7 +263,6 @@ impl QmlEngine { case 9: INVOKE_METHOD(, Q_ARG(QVariant, args_ptr[0]), Q_ARG(QVariant, args_ptr[1]), Q_ARG(QVariant, args_ptr[2]), Q_ARG(QVariant, args_ptr[3]), Q_ARG(QVariant, args_ptr[4]), Q_ARG(QVariant, args_ptr[5]), Q_ARG(QVariant, args_ptr[6]), Q_ARG(QVariant, args_ptr[7]), Q_ARG(QVariant, args_ptr[8])); break; } #undef INVOKE_METHOD - return ret; }) } From 74f45bcab01e2791140ba82e7eb0c6144fa72d69 Mon Sep 17 00:00:00 2001 From: Matti Viljanen Date: Sun, 9 Apr 2023 10:56:13 +0300 Subject: [PATCH 03/13] Add basic QSettings wrapper --- qttypes/Cargo.toml | 1 + qttypes/src/lib.rs | 4 +- qttypes/src/qtcore/mod.rs | 2 + qttypes/src/qtcore/qsettings.rs | 171 ++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 qttypes/src/qtcore/qsettings.rs diff --git a/qttypes/Cargo.toml b/qttypes/Cargo.toml index 9df14bd..1c785d2 100644 --- a/qttypes/Cargo.toml +++ b/qttypes/Cargo.toml @@ -45,6 +45,7 @@ semver = "1" [dev-dependencies] cpp_build = "0.5.6" +tempfile = { version = "3.4.0", default-features = false } [package.metadata.docs.rs] dependencies = [ "qtbase5-dev", "qtdeclarative5-dev" ] diff --git a/qttypes/src/lib.rs b/qttypes/src/lib.rs index 00e2bc4..de93164 100644 --- a/qttypes/src/lib.rs +++ b/qttypes/src/lib.rs @@ -168,8 +168,8 @@ use internal_prelude::*; mod qtcore; pub use crate::qtcore::{ - qreal, NormalizationForm, QByteArray, QListIterator, QString, QStringList, QUrl, QVariant, - QVariantList, UnicodeVersion, + qreal, NormalizationForm, QByteArray, QListIterator, QSettings, QString, QStringList, QUrl, + QVariant, QVariantList, UnicodeVersion, }; mod qtgui; diff --git a/qttypes/src/qtcore/mod.rs b/qttypes/src/qtcore/mod.rs index 9d7d0ff..1e5585c 100644 --- a/qttypes/src/qtcore/mod.rs +++ b/qttypes/src/qtcore/mod.rs @@ -2,6 +2,7 @@ mod primitives; mod qbytearray; mod qchar; mod qlist; +mod qsettings; mod qstring; mod qurl; mod qvariant; @@ -10,6 +11,7 @@ pub use self::primitives::qreal; pub use self::qbytearray::QByteArray; pub use self::qchar::UnicodeVersion; pub use self::qlist::{QListIterator, QStringList, QVariantList}; +pub use self::qsettings::QSettings; pub use self::qstring::{NormalizationForm, QString}; pub use self::qurl::QUrl; pub use self::qvariant::QVariant; diff --git a/qttypes/src/qtcore/qsettings.rs b/qttypes/src/qtcore/qsettings.rs new file mode 100644 index 0000000..7c12269 --- /dev/null +++ b/qttypes/src/qtcore/qsettings.rs @@ -0,0 +1,171 @@ +use crate::internal_prelude::*; +use crate::QString; + +cpp! {{ + #include + #include +}} + +cpp_class!( + /// Wrapper around [`QSettings`][class] class. + /// + /// [class]: https://doc.qt.io/qt-5/qsettings.html + #[derive(Default)] + pub unsafe struct QSettings as "QSettings" +); + +impl QSettings { + /// Wrapper around [`QSettings(const QString &organization, const QString &application = QString(), QObject *parent = nullptr)`][ctor] constructor. + /// + /// [ctor]: https://doc.qt.io/qt-5/qsettings.html#QSettings-3 + pub fn new(organization: &str, application: &str) -> *mut Self { + let organization = QString::from(organization); + let application = QString::from(application); + cpp!( + unsafe [organization as "QString", application as "QString"] -> *mut QSettings as "QSettings*" { + QSettings* settings = new QSettings(organization, application); + return settings; + } + ) + } + + /// Wrapper around [`QSettings(const QString &fileName, QSettings::Format format, QObject *parent = nullptr)`][ctor] constructor. + /// + /// [ctor]: https://doc.qt.io/qt-5/qsettings.html#QSettings + pub fn from_path(file_name: &str) -> *mut Self { + let file_name = QString::from(file_name); + cpp!( + unsafe [file_name as "QString"] -> *mut QSettings as "QSettings*" { + QSettings* settings = new QSettings(file_name, QSettings::NativeFormat); + return settings; + } + ) + } + + pub fn filename(&self) -> String { + let filename: QString = cpp!( + unsafe [self as "QSettings *"] -> QString as "QString" { + return self->fileName(); + } + ); + filename.to_string() + } + + pub fn contains(&self, key: &str) -> bool { + let key = QString::from(key); + unsafe { + cpp!([self as "QSettings *", key as "QString"] -> bool as "bool" { + return self->contains(key); + }) + } + } + + pub fn value_bool(&self, key: &str) -> bool { + let key = QString::from(key); + unsafe { + cpp!([self as "QSettings *", key as "QString"] -> bool as "bool" { + return self->value(key).toBool(); + }) + } + } + + pub fn set_bool(&mut self, key: &str, value: bool) { + let key = QString::from(key); + unsafe { + cpp!([self as "QSettings *", key as "QString", value as "bool"] { + self->setValue(key, value); + }) + }; + } + + pub fn value_string(&self, key: &str) -> String { + let key = QString::from(key); + let val = unsafe { + cpp!([self as "QSettings *", key as "QString"] -> QString as "QString" { + return self->value(key).toString(); + }) + }; + val.into() + } + + pub fn set_string(&mut self, key: &str, value: &str) { + let key = QString::from(key); + let value = QString::from(value); + unsafe { + cpp!([self as "QSettings *", key as "QString", value as "QString"] { + self->setValue(key, value); + }) + }; + } + + pub fn sync(&self) { + unsafe { + cpp!([self as "QSettings *"] { + self->sync(); + }) + }; + } +} + +#[test] +fn test_qsettings_filename() { + let inner = QSettings::new("qmetaobject", "qsettings"); + let qsettings = unsafe { inner.as_ref().unwrap() }; + assert!(qsettings.filename().ends_with("qmetaobject/qsettings.conf")); + + drop(qsettings); + drop(inner); +} + +#[test] +fn test_qsettings_new_from_path() { + let inner = QSettings::from_path("/tmp/my_settings.conf"); + let qsettings = unsafe { inner.as_ref().unwrap() }; + + assert_eq!(qsettings.filename(), "/tmp/my_settings.conf"); + + drop(qsettings); + drop(inner); +} + +#[test] +fn test_qsettings_values() { + let temp_dir = tempfile::tempdir().unwrap(); + let config_pathbuf = temp_dir.path().join("qsettings.conf"); + let config_file = config_pathbuf.to_str().unwrap(); + + let inner = QSettings::from_path(config_file); + let qsettings = unsafe { inner.as_mut().unwrap() }; + + qsettings.set_bool("test_true", false); + qsettings.set_bool("test_false", true); + qsettings.set_string("test_empty", ""); + qsettings.set_string("test_string", "Lorem Ipsum"); + qsettings.set_string("test_emoji", "🦀"); + + qsettings.sync(); + + assert_eq!(qsettings.value_bool("test_true"), false); + assert_eq!(qsettings.value_bool("test_false"), true); + assert_eq!(qsettings.value_string("test_empty"), ""); + assert_eq!(qsettings.value_string("test_string"), "Lorem Ipsum"); + assert_eq!(qsettings.value_string("test_emoji"), "🦀"); + + drop(qsettings); + drop(inner); + + let inner = QSettings::from_path(config_file); + let qsettings = unsafe { inner.as_mut().unwrap() }; + + assert_eq!(qsettings.value_bool("test_true"), false); + assert_eq!(qsettings.value_bool("test_false"), true); + assert_eq!(qsettings.value_string("test_empty"), ""); + assert_eq!(qsettings.value_string("test_string"), "Lorem Ipsum"); + assert_eq!(qsettings.value_string("test_emoji"), "🦀"); + + drop(qsettings); + drop(inner); + + drop(temp_dir); + assert!(!config_pathbuf.as_path().exists()); +} From 3007ff3fcfb3c27fb1df9076cfaed826878e07da Mon Sep 17 00:00:00 2001 From: Matti Viljanen Date: Sun, 28 May 2023 19:47:10 +0300 Subject: [PATCH 04/13] Always use `QSettings::IniFormat` --- qttypes/src/qtcore/qsettings.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/qttypes/src/qtcore/qsettings.rs b/qttypes/src/qtcore/qsettings.rs index 7c12269..7abca94 100644 --- a/qttypes/src/qtcore/qsettings.rs +++ b/qttypes/src/qtcore/qsettings.rs @@ -17,13 +17,16 @@ cpp_class!( impl QSettings { /// Wrapper around [`QSettings(const QString &organization, const QString &application = QString(), QObject *parent = nullptr)`][ctor] constructor. /// + /// Note: Under the hood it uses `QSettings(format, scope, org, app)` (like Qt does internally already), + /// with setting `format` set to `IniFormat` and `scope` to (default) `UserScope` + /// /// [ctor]: https://doc.qt.io/qt-5/qsettings.html#QSettings-3 pub fn new(organization: &str, application: &str) -> *mut Self { let organization = QString::from(organization); let application = QString::from(application); cpp!( unsafe [organization as "QString", application as "QString"] -> *mut QSettings as "QSettings*" { - QSettings* settings = new QSettings(organization, application); + QSettings* settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, organization, application); return settings; } ) @@ -36,7 +39,7 @@ impl QSettings { let file_name = QString::from(file_name); cpp!( unsafe [file_name as "QString"] -> *mut QSettings as "QSettings*" { - QSettings* settings = new QSettings(file_name, QSettings::NativeFormat); + QSettings* settings = new QSettings(file_name, QSettings::IniFormat); return settings; } ) @@ -111,7 +114,12 @@ impl QSettings { fn test_qsettings_filename() { let inner = QSettings::new("qmetaobject", "qsettings"); let qsettings = unsafe { inner.as_ref().unwrap() }; - assert!(qsettings.filename().ends_with("qmetaobject/qsettings.conf")); + + assert!( + qsettings.filename().ends_with("/qmetaobject/qsettings.ini"), + "'{}' does not end with '/qmetaobject/qsettings.ini'", + qsettings.filename() + ); drop(qsettings); drop(inner); @@ -122,7 +130,11 @@ fn test_qsettings_new_from_path() { let inner = QSettings::from_path("/tmp/my_settings.conf"); let qsettings = unsafe { inner.as_ref().unwrap() }; - assert_eq!(qsettings.filename(), "/tmp/my_settings.conf"); + assert!( + qsettings.filename().ends_with("/tmp/my_settings.conf"), + "'{}' does not end with '/tmp/my_settings.conf'", + qsettings.filename() + ); drop(qsettings); drop(inner); From 15381012a43ae3106aae205592cd6b713756c58a Mon Sep 17 00:00:00 2001 From: Matti Viljanen Date: Sat, 3 Jun 2023 11:39:38 +0300 Subject: [PATCH 05/13] Wrap QSettings in std::unique_ptr Co-authored-by: Olivier Goffart --- qttypes/src/qtcore/qsettings.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qttypes/src/qtcore/qsettings.rs b/qttypes/src/qtcore/qsettings.rs index 7abca94..9f9a06b 100644 --- a/qttypes/src/qtcore/qsettings.rs +++ b/qttypes/src/qtcore/qsettings.rs @@ -4,6 +4,7 @@ use crate::QString; cpp! {{ #include #include + #include }} cpp_class!( @@ -11,7 +12,7 @@ cpp_class!( /// /// [class]: https://doc.qt.io/qt-5/qsettings.html #[derive(Default)] - pub unsafe struct QSettings as "QSettings" + pub unsafe struct QSettings as "std::unique_ptr" ); impl QSettings { From d41e2c672962be09d8b932b41b03e1fe9b8e8117 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Mon, 30 Oct 2023 12:20:42 +0100 Subject: [PATCH 06/13] Add screenshots from users in Readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 3a53bd8..263e169 100644 --- a/README.md +++ b/README.md @@ -312,3 +312,11 @@ This crates is the best achieving this. implemented in Rust. It has the same goal as providing a new to add a UI to a Rust project with idiomatic Rust API, but instead of using QML for the UI, it uses its own language. + +## Applications built with this crate + + * https://github.com/gyroflow/gyroflow + ![Gyroflow screenshot](https://github.com/gyroflow/gyroflow/blob/master/resources/screenshot.jpg) + +* https://github.com/kalaksi/lightkeeper + ![LightKeeper screenshot](https://github.com/kalaksi/lightkeeper/blob/master/doc/images/LightkeeperRM-overview.png) From 26f3dcbaae8d35c4025bc8497fecda19d0961c9a Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 31 Oct 2023 14:02:33 +0100 Subject: [PATCH 07/13] Fix some warnings --- Cargo.toml | 2 ++ qttypes/build.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0166290..286d408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,5 @@ members = [ 'examples/qenum', 'examples/nested_qobjects', ] + +resolver = "2" diff --git a/qttypes/build.rs b/qttypes/build.rs index a6856ad..86ecde7 100644 --- a/qttypes/build.rs +++ b/qttypes/build.rs @@ -211,9 +211,10 @@ fn main() { "" }; + /* https://github.com/rust-lang/cargo/issues/9562 if std::env::var("CARGO_CFG_TARGET_FAMILY").as_ref().map(|s| s.as_ref()) == Ok("unix") { println!("cargo:rustc-cdylib-link-arg=-Wl,-rpath,{}", &qt_library_path); - } + } */ println!("cargo:rustc-link-search{}={}", macos_lib_search, &qt_library_path); From bfa80db84d3dfe9355e73eec12cc6431b1bdd733 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 31 Oct 2023 14:02:50 +0100 Subject: [PATCH 08/13] Fix QSettings Make it a safe API --- qttypes/src/qtcore/qsettings.rs | 63 +++++++++++++-------------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/qttypes/src/qtcore/qsettings.rs b/qttypes/src/qtcore/qsettings.rs index 9f9a06b..cc4981e 100644 --- a/qttypes/src/qtcore/qsettings.rs +++ b/qttypes/src/qtcore/qsettings.rs @@ -11,7 +11,6 @@ cpp_class!( /// Wrapper around [`QSettings`][class] class. /// /// [class]: https://doc.qt.io/qt-5/qsettings.html - #[derive(Default)] pub unsafe struct QSettings as "std::unique_ptr" ); @@ -22,13 +21,12 @@ impl QSettings { /// with setting `format` set to `IniFormat` and `scope` to (default) `UserScope` /// /// [ctor]: https://doc.qt.io/qt-5/qsettings.html#QSettings-3 - pub fn new(organization: &str, application: &str) -> *mut Self { + pub fn new(organization: &str, application: &str) -> Self { let organization = QString::from(organization); let application = QString::from(application); cpp!( - unsafe [organization as "QString", application as "QString"] -> *mut QSettings as "QSettings*" { - QSettings* settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, organization, application); - return settings; + unsafe [organization as "QString", application as "QString"] -> QSettings as "std::unique_ptr" { + return std::unique_ptr(new QSettings(QSettings::IniFormat, QSettings::UserScope, organization, application)); } ) } @@ -36,20 +34,19 @@ impl QSettings { /// Wrapper around [`QSettings(const QString &fileName, QSettings::Format format, QObject *parent = nullptr)`][ctor] constructor. /// /// [ctor]: https://doc.qt.io/qt-5/qsettings.html#QSettings - pub fn from_path(file_name: &str) -> *mut Self { + pub fn from_path(file_name: &str) -> Self { let file_name = QString::from(file_name); cpp!( - unsafe [file_name as "QString"] -> *mut QSettings as "QSettings*" { - QSettings* settings = new QSettings(file_name, QSettings::IniFormat); - return settings; + unsafe [file_name as "QString"] -> QSettings as "std::unique_ptr" { + return std::unique_ptr(new QSettings(file_name, QSettings::IniFormat)); } ) } pub fn filename(&self) -> String { let filename: QString = cpp!( - unsafe [self as "QSettings *"] -> QString as "QString" { - return self->fileName(); + unsafe [self as "QSettings **"] -> QString as "QString" { + return (*self)->fileName(); } ); filename.to_string() @@ -58,8 +55,8 @@ impl QSettings { pub fn contains(&self, key: &str) -> bool { let key = QString::from(key); unsafe { - cpp!([self as "QSettings *", key as "QString"] -> bool as "bool" { - return self->contains(key); + cpp!([self as "QSettings **", key as "QString"] -> bool as "bool" { + return (*self)->contains(key); }) } } @@ -67,8 +64,8 @@ impl QSettings { pub fn value_bool(&self, key: &str) -> bool { let key = QString::from(key); unsafe { - cpp!([self as "QSettings *", key as "QString"] -> bool as "bool" { - return self->value(key).toBool(); + cpp!([self as "QSettings **", key as "QString"] -> bool as "bool" { + return (*self)->value(key).toBool(); }) } } @@ -76,8 +73,8 @@ impl QSettings { pub fn set_bool(&mut self, key: &str, value: bool) { let key = QString::from(key); unsafe { - cpp!([self as "QSettings *", key as "QString", value as "bool"] { - self->setValue(key, value); + cpp!([self as "QSettings **", key as "QString", value as "bool"] { + (*self)->setValue(key, value); }) }; } @@ -85,8 +82,8 @@ impl QSettings { pub fn value_string(&self, key: &str) -> String { let key = QString::from(key); let val = unsafe { - cpp!([self as "QSettings *", key as "QString"] -> QString as "QString" { - return self->value(key).toString(); + cpp!([self as "QSettings **", key as "QString"] -> QString as "QString" { + return (*self)->value(key).toString(); }) }; val.into() @@ -96,16 +93,16 @@ impl QSettings { let key = QString::from(key); let value = QString::from(value); unsafe { - cpp!([self as "QSettings *", key as "QString", value as "QString"] { - self->setValue(key, value); + cpp!([self as "QSettings **", key as "QString", value as "QString"] { + (*self)->setValue(key, value); }) }; } pub fn sync(&self) { unsafe { - cpp!([self as "QSettings *"] { - self->sync(); + cpp!([self as "QSettings **"] { + (*self)->sync(); }) }; } @@ -113,32 +110,24 @@ impl QSettings { #[test] fn test_qsettings_filename() { - let inner = QSettings::new("qmetaobject", "qsettings"); - let qsettings = unsafe { inner.as_ref().unwrap() }; + let qsettings = QSettings::new("qmetaobject", "qsettings"); assert!( qsettings.filename().ends_with("/qmetaobject/qsettings.ini"), "'{}' does not end with '/qmetaobject/qsettings.ini'", qsettings.filename() ); - - drop(qsettings); - drop(inner); } #[test] fn test_qsettings_new_from_path() { - let inner = QSettings::from_path("/tmp/my_settings.conf"); - let qsettings = unsafe { inner.as_ref().unwrap() }; + let qsettings = QSettings::from_path("/tmp/my_settings.conf"); assert!( qsettings.filename().ends_with("/tmp/my_settings.conf"), "'{}' does not end with '/tmp/my_settings.conf'", qsettings.filename() ); - - drop(qsettings); - drop(inner); } #[test] @@ -147,8 +136,7 @@ fn test_qsettings_values() { let config_pathbuf = temp_dir.path().join("qsettings.conf"); let config_file = config_pathbuf.to_str().unwrap(); - let inner = QSettings::from_path(config_file); - let qsettings = unsafe { inner.as_mut().unwrap() }; + let mut qsettings = QSettings::from_path(config_file); qsettings.set_bool("test_true", false); qsettings.set_bool("test_false", true); @@ -165,10 +153,8 @@ fn test_qsettings_values() { assert_eq!(qsettings.value_string("test_emoji"), "🦀"); drop(qsettings); - drop(inner); - let inner = QSettings::from_path(config_file); - let qsettings = unsafe { inner.as_mut().unwrap() }; + let qsettings = QSettings::from_path(config_file); assert_eq!(qsettings.value_bool("test_true"), false); assert_eq!(qsettings.value_bool("test_false"), true); @@ -177,7 +163,6 @@ fn test_qsettings_values() { assert_eq!(qsettings.value_string("test_emoji"), "🦀"); drop(qsettings); - drop(inner); drop(temp_dir); assert!(!config_pathbuf.as_path().exists()); From 0511506be9a1d85d6265afed6c2e922f74a3f78d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 2 Nov 2023 15:11:38 +0100 Subject: [PATCH 09/13] Error out when we detect incompatibilities between mingw and msvc This has caused lots of frustrations: eg: https://github.com/slint-ui/slint/issues/3802 https://github.com/slint-ui/slint/issues/3539 https://github.com/slint-ui/slint/issues/3739 --- qttypes/build.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qttypes/build.rs b/qttypes/build.rs index 86ecde7..bee8b6a 100644 --- a/qttypes/build.rs +++ b/qttypes/build.rs @@ -211,6 +211,15 @@ fn main() { "" }; + // MinGW and MSVC are not compatible + if cargo_target_os == "windows" { + let spec = qmake_query("QMAKE_SPEC"); + if (spec.contains("msvc") && cargo_target_env == "gnu") + || (spec.contains("g++") && cargo_target_env == "msvc") + { + report_error(&format!("Rust target '{}' is not compatible with Qt mkspec '{spec}'. Mixing MinGW and MSVC is not allowed.", std::env::var_os("TARGET").unwrap_or_default().to_string_lossy())); + } + } /* https://github.com/rust-lang/cargo/issues/9562 if std::env::var("CARGO_CFG_TARGET_FAMILY").as_ref().map(|s| s.as_ref()) == Ok("unix") { println!("cargo:rustc-cdylib-link-arg=-Wl,-rpath,{}", &qt_library_path); From efbca0f100e384797bc678ecb07ef47ef5e375f9 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 3 Nov 2023 08:21:46 +0100 Subject: [PATCH 10/13] Update the comparison section --- README.md | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 263e169..a5303a3 100644 --- a/README.md +++ b/README.md @@ -283,35 +283,33 @@ method. Now send us a Pull Request. 🙂 [`QVariant`]: https://docs.rs/qmetaobject/latest/qmetaobject/struct.QVariant.html [docs.qttypes]: https://docs.rs/qttypes/latest/qttypes/#cargo-features -## Comparison with other projects +## Comparison to Other Projects -This crate objective is to make idiomatic Rust bindings for QML (and only QML, no QWidgets or other -non-graphical Qt API) in a way that doesn't need you to know or use C++ and other build system. -This crates is the best achieving this. +The primary goal of this crate is to provide idiomatic Rust bindings for QML. +It focuses solely on QML, not QWidgets or any other non-graphical Qt API. +The aim is to eliminate the need for users to know or use C++ and other build systems. +This crate excels in achieving this goal. -* **[CXX-Qt](https://github.com/KDAB/cxx-qt/)** still makes you to write a bit of boiler-plate code - in C++ and use extra build step to compile the C++. - CXX-Qt is ideal to bring some Rust in an existing C++ project. But less so when you just want to - make an UI for a Rust-only application. +* **[CXX-Qt](https://github.com/KDAB/cxx-qt/)** is an ideal solution for incorporating some Rust into an existing C++ project. + CXX-Qt is newer than this crate and utilizes Rust features such as attribute macro, + which didn't exist when the qmetaobject crate was designed. + (Only derive procedural macro were available in stable Rust at that time) - The CXX-Qt is also more recent that this crate and make use of Rust features such as attribute - macro, that did not exist when the qmetaobject crate was designed. - (Only derive procedural macro were available in stable rust rust at the time) +* The **[Rust Qt Binding Generator](https://invent.kde.org/sdk/rust-qt-binding-generator)** + is another project that aids in integrating Rust logic into an existing C++/Qt project. + This project was also developed before Rust had procedural macros, so it uses an external .json file to generate C++ and Rust code. -* Similarly, the **[Rust Qt Binding Generator](https://invent.kde.org/sdk/rust-qt-binding-generator)** - is another project that helps to integrate Rust logic in an existing C++/Qt project. This was also - created before rust had procedural macros, so it uses an external .json file to generate C++ and - Rust code. - -* There exist also a bunch of older crates that tries to provide Rust binding around the Qt C++ API. +* There are also several older crates that attempted to provide Rust binding around the Qt C++ API. Often automatically generated, these bindings are not idiomatic Rust, require unsafe code to use, - and are not maintained anymore. + and are no longer maintained. + +* **[Slint](https://slint.rs)** is a project created by the author of this crate. + Slint is not a QML or Qt binding, but a new language inspired from QML, entirely implemented in Rust. + It shares the same objective of providing a means to add a UI to a Rust project with idiomatic Rust API, but instead of using QML for the UI, it uses its own language.\ + [![Slint Logo](https://slint.dev/logo/slint-logo-simple-light.svg)](https://slint.rs) -* **[Slint](https://slint-ui.com)** is a project created by the same author of this crate. - It is not a QML or Qt binding at all, but rather a new language similar to QML, entirely - implemented in Rust. - It has the same goal as providing a new to add a UI to a Rust project with idiomatic Rust API, - but instead of using QML for the UI, it uses its own language. +The qmetaobject crate is currently only being passively maintained as focus has shifted towards developing Slint. +You are encouraged to explore [Slint](https://slint.rs) as an exciting, innovative alternative for creating GUI in Rust projects. ## Applications built with this crate From 5bcb6e5b70b09eaa6b32268e7ea56dc1c5eb755d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 3 Nov 2023 09:03:18 +0100 Subject: [PATCH 11/13] Update my email in Cargo.toml --- examples/graph/Cargo.toml | 2 +- examples/qmlextensionplugins/Cargo.toml | 2 +- examples/todos/Cargo.toml | 2 +- qmetaobject/Cargo.toml | 2 +- qmetaobject_impl/Cargo.toml | 2 +- qttypes/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/graph/Cargo.toml b/examples/graph/Cargo.toml index ff5e811..f861121 100644 --- a/examples/graph/Cargo.toml +++ b/examples/graph/Cargo.toml @@ -2,7 +2,7 @@ name = "graph" version = "0.1.0" edition = "2018" -authors = ["Olivier Goffart "] +authors = ["Olivier Goffart "] build = "build.rs" [dependencies] diff --git a/examples/qmlextensionplugins/Cargo.toml b/examples/qmlextensionplugins/Cargo.toml index 4bdc457..30b6291 100644 --- a/examples/qmlextensionplugins/Cargo.toml +++ b/examples/qmlextensionplugins/Cargo.toml @@ -2,7 +2,7 @@ name = "qmlextensionplugins" version = "0.1.0" edition = "2018" -authors = ["Olivier Goffart "] +authors = ["Olivier Goffart "] [lib] name = "qmlqtimeexampleplugin" diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 7c876b6..702c684 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -2,7 +2,7 @@ name = "todos" version = "0.1.0" edition = "2018" -authors = ["Olivier Goffart "] +authors = ["Olivier Goffart "] [dependencies] qmetaobject = { path = "../../qmetaobject" } diff --git a/qmetaobject/Cargo.toml b/qmetaobject/Cargo.toml index 48b16ca..fec7e29 100644 --- a/qmetaobject/Cargo.toml +++ b/qmetaobject/Cargo.toml @@ -2,7 +2,7 @@ name = "qmetaobject" version = "0.2.9" edition = "2018" -authors = ["Olivier Goffart "] +authors = ["Olivier Goffart "] build = "build.rs" description = "Expose rust object to Qt and QML." readme = "../README.md" diff --git a/qmetaobject_impl/Cargo.toml b/qmetaobject_impl/Cargo.toml index 0826e27..c72f375 100644 --- a/qmetaobject_impl/Cargo.toml +++ b/qmetaobject_impl/Cargo.toml @@ -2,7 +2,7 @@ name = "qmetaobject_impl" version = "0.2.9" edition = "2018" -authors = ["Olivier Goffart "] +authors = ["Olivier Goffart "] description = "Custom derive for the qmetaobject crate." readme = "../README.md" license = "MIT" diff --git a/qttypes/Cargo.toml b/qttypes/Cargo.toml index 1c785d2..27b680b 100644 --- a/qttypes/Cargo.toml +++ b/qttypes/Cargo.toml @@ -2,7 +2,7 @@ name = "qttypes" version = "0.2.9" edition = "2018" -authors = ["Olivier Goffart "] +authors = ["Olivier Goffart "] build = "build.rs" description = "Manually maintained buildings for Qt value types" readme = "README.md" From c5aa4e8d1d28a1e210a5a5772163f28b7acdf5a2 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 3 Nov 2023 09:10:09 +0100 Subject: [PATCH 12/13] Prepare for release: upgrade version number and changelog --- CHANGELOG.md | 7 +++++++ qmetaobject/Cargo.toml | 4 ++-- qmetaobject_impl/Cargo.toml | 2 +- qttypes/Cargo.toml | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 411379d..e1d4c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## 0.2.10 2023-11-03 + + - qttypes: detect MSVC and MinGW incompatibilities + - qttypes: Added wrapper around QSettings + - Added QmlEngine::invoke_method_noreturn + - Added QmlEngine::trim_component_cache and QmlEngine::clear_component_cache + ## 0.2.9 2023-06-15 - Implement QMetaType for QStringList and QVariantMap diff --git a/qmetaobject/Cargo.toml b/qmetaobject/Cargo.toml index fec7e29..af30d9a 100644 --- a/qmetaobject/Cargo.toml +++ b/qmetaobject/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qmetaobject" -version = "0.2.9" +version = "0.2.10" edition = "2018" authors = ["Olivier Goffart "] build = "build.rs" @@ -18,7 +18,7 @@ webengine = ["qttypes/qtwebengine"] [dependencies] qttypes = { path = "../qttypes", version = "0.2.0", features = ["qtquick"] } -qmetaobject_impl = { path = "../qmetaobject_impl", version = "=0.2.9"} +qmetaobject_impl = { path = "../qmetaobject_impl", version = "=0.2.10"} lazy_static = "1.0" cpp = "0.5.6" log = { version = "0.4", optional = true } diff --git a/qmetaobject_impl/Cargo.toml b/qmetaobject_impl/Cargo.toml index c72f375..d945443 100644 --- a/qmetaobject_impl/Cargo.toml +++ b/qmetaobject_impl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qmetaobject_impl" -version = "0.2.9" +version = "0.2.10" edition = "2018" authors = ["Olivier Goffart "] description = "Custom derive for the qmetaobject crate." diff --git a/qttypes/Cargo.toml b/qttypes/Cargo.toml index 27b680b..2c85e2a 100644 --- a/qttypes/Cargo.toml +++ b/qttypes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qttypes" -version = "0.2.9" +version = "0.2.10" edition = "2018" authors = ["Olivier Goffart "] build = "build.rs" From 855840e1313251456ead02dc529b2ddf8860b463 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 3 Nov 2023 14:25:40 +0100 Subject: [PATCH 13/13] Restore the cargo:rustc-cdylib-link-arg Not having it causes link error when building on macOs The CI don't catch that because we define the DYLD_FRAMEWORK_PATH env variable --- CHANGELOG.md | 4 ++++ qttypes/Cargo.toml | 2 +- qttypes/build.rs | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d4c8d..65a2551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## 0.2.11 2023-11-03 (qttype only) + + - reenable `cargo:rustc-cdylib-link-arg=-Wl,-rpath,` command even if it is depracated as it broke people's build + ## 0.2.10 2023-11-03 - qttypes: detect MSVC and MinGW incompatibilities diff --git a/qttypes/Cargo.toml b/qttypes/Cargo.toml index 2c85e2a..c70ef0b 100644 --- a/qttypes/Cargo.toml +++ b/qttypes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qttypes" -version = "0.2.10" +version = "0.2.11" edition = "2018" authors = ["Olivier Goffart "] build = "build.rs" diff --git a/qttypes/build.rs b/qttypes/build.rs index bee8b6a..1a6e0f6 100644 --- a/qttypes/build.rs +++ b/qttypes/build.rs @@ -220,10 +220,9 @@ fn main() { report_error(&format!("Rust target '{}' is not compatible with Qt mkspec '{spec}'. Mixing MinGW and MSVC is not allowed.", std::env::var_os("TARGET").unwrap_or_default().to_string_lossy())); } } - /* https://github.com/rust-lang/cargo/issues/9562 if std::env::var("CARGO_CFG_TARGET_FAMILY").as_ref().map(|s| s.as_ref()) == Ok("unix") { println!("cargo:rustc-cdylib-link-arg=-Wl,-rpath,{}", &qt_library_path); - } */ + } println!("cargo:rustc-link-search{}={}", macos_lib_search, &qt_library_path);