diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ff8ef1d..ddf3c0dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Support for further types: `QLine`, `QLineF`, `QImage`, `QPainter`, `QFont`, `QPen`, `QPolygon`, `QPolygonF`, `QRegion` +- Support for further types: `QLine`, `QLineF`, `QImage`, `QPainter`, `QFont`, `QPen`, `QPolygon`, `QPolygonF`, `QRegion`, `QAnyStringView` - `internal_pointer_mut()` function on `QModelIndex` - `c_void` in CXX-Qt-lib for easy access to `void *` - `CxxQtThread` is now marked as `Sync` so that it can be used by reference diff --git a/crates/cxx-qt-lib/Cargo.toml b/crates/cxx-qt-lib/Cargo.toml index 97dcb3d9c..43dc4d450 100644 --- a/crates/cxx-qt-lib/Cargo.toml +++ b/crates/cxx-qt-lib/Cargo.toml @@ -32,6 +32,7 @@ serde = { version = "1", features=["derive"], optional = true } [build-dependencies] cxx-qt-build.workspace = true +qt-build-utils.workspace = true [features] full_qt = ["qt_gui", "qt_qml", "qt_quickcontrols"] diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index e3484fd1d..b6006a02c 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -69,6 +69,9 @@ fn write_headers() { } fn main() { + let qtbuild = qt_build_utils::QtBuild::new(vec!["Core".to_owned()]) + .expect("Could not find Qt installation"); + write_headers(); let emscripten_targeted = match std::env::var("CARGO_CFG_TARGET_OS") { @@ -201,6 +204,10 @@ fn main() { "core/qvector/qvector_u64", ]; + if qtbuild.version().major > 5 { + rust_bridges.extend(["core/qanystringview"]); + } + if qt_gui_enabled() { rust_bridges.extend([ "core/qlist/qlist_qcolor", @@ -269,6 +276,10 @@ fn main() { "core/qvector/qvector", ]; + if qtbuild.version().major > 5 { + cpp_files.extend(["core/qanystringview"]); + } + if qt_gui_enabled() { cpp_files.extend([ "gui/qcolor", diff --git a/crates/cxx-qt-lib/include/core/qanystringview.h b/crates/cxx-qt-lib/include/core/qanystringview.h new file mode 100644 index 000000000..ceb908a20 --- /dev/null +++ b/crates/cxx-qt-lib/include/core/qanystringview.h @@ -0,0 +1,39 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include +#include +#include + +#include "rust/cxx.h" + +// Define namespace otherwise we hit a GCC bug +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 +namespace rust { + +template<> +struct IsRelocatable : ::std::true_type +{}; + +} // namespace rust + +namespace rust { +namespace cxxqtlib1 { + +QAnyStringView +qanystringviewInitFromRustString(::rust::Str string); +QAnyStringView +qanystringviewInitFromQString(const QString& string); +::rust::String +qanystringviewToRustString(const QAnyStringView& string); + +::rust::isize +qanystringviewLen(const QAnyStringView& string); + +} +} diff --git a/crates/cxx-qt-lib/src/core/mod.rs b/crates/cxx-qt-lib/src/core/mod.rs index 7f8d82a7e..a2e9bfa33 100644 --- a/crates/cxx-qt-lib/src/core/mod.rs +++ b/crates/cxx-qt-lib/src/core/mod.rs @@ -65,6 +65,11 @@ pub use qsizef::QSizeF; mod qstring; pub use qstring::QString; +#[cfg(cxxqt_qt_version_major = "6")] +mod qanystringview; +#[cfg(cxxqt_qt_version_major = "6")] +pub use qanystringview::QAnyStringView; + mod qstringlist; pub use qstringlist::QStringList; diff --git a/crates/cxx-qt-lib/src/core/qanystringview.cpp b/crates/cxx-qt-lib/src/core/qanystringview.cpp new file mode 100644 index 000000000..1dae2d2fa --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qanystringview.cpp @@ -0,0 +1,48 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#include "cxx-qt-lib/qanystringview.h" + +#include + +// QAnyStringView has two members. +// A union of (void*, char*, char_16*) and a size_t. +// https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/text/qanystringview.h +assert_alignment_and_size(QAnyStringView, { + ::std::size_t a0; + void* a1; +}); + +static_assert(::std::is_trivially_copy_assignable::value); +static_assert(::std::is_trivially_copy_constructible::value); + +static_assert(::std::is_trivially_destructible::value); + +static_assert(QTypeInfo::isRelocatable); + +namespace rust { +namespace cxxqtlib1 { + +QAnyStringView +qanystringviewInitFromRustString(::rust::Str string) +{ + return QAnyStringView(string.data(), string.size()); +} + +QAnyStringView +qanystringviewInitFromQString(const QString& string) +{ + return QAnyStringView(string); +} + +::rust::isize +qanystringviewLen(const QAnyStringView& string) +{ + return static_cast<::rust::isize>(string.size()); +} + +} +} diff --git a/crates/cxx-qt-lib/src/core/qanystringview.rs b/crates/cxx-qt-lib/src/core/qanystringview.rs new file mode 100644 index 000000000..680f9911c --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qanystringview.rs @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::QString; +use core::ffi::c_void; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use cxx::{type_id, ExternType}; + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("cxx-qt-lib/qanystringview.h"); + type QAnyStringView<'a> = super::QAnyStringView<'a>; + + include!("cxx-qt-lib/qstring.h"); + type QString = crate::QString; + + /// Returns true if the string has no characters; otherwise returns false. + #[rust_name = "is_empty"] + fn isEmpty(self: &QAnyStringView) -> bool; + + /// Returns true if this string is null; otherwise returns false. + #[rust_name = "is_null"] + fn isNull(self: &QAnyStringView) -> bool; + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + include!("cxx-qt-lib/common.h"); + + #[doc(hidden)] + #[rust_name = "QAnyStringView_init_default"] + fn construct() -> QAnyStringView<'static>; + #[doc(hidden)] + #[rust_name = "QAnyStringView_init_from_rust_string"] + fn qanystringviewInitFromRustString<'a>(string: &str) -> QAnyStringView<'a>; + #[doc(hidden)] + #[rust_name = "QAnyStringView_init_from_qstring"] + fn qanystringviewInitFromQString<'a>(string: &QString) -> QAnyStringView<'a>; + #[doc(hidden)] + #[rust_name = "QAnyStringView_init_from_QAnyStringView"] + fn construct<'a>(string: &QAnyStringView) -> QAnyStringView<'a>; + + #[doc(hidden)] + #[rust_name = "QAnyStringView_eq"] + fn operatorEq(a: &QAnyStringView, b: &QAnyStringView) -> bool; + + #[doc(hidden)] + #[rust_name = "QAnyStringView_len"] + fn qanystringviewLen(string: &QAnyStringView) -> isize; + } +} + +/// The QAnyStringView class provides a unified view of a Latin-1, UTF-8, or UTF-16 string. +#[repr(C)] +pub struct QAnyStringView<'a> { + /// QAnyStringView has two members, a pointer and a size_t + _space: MaybeUninit<[usize; 1]>, + _space2: MaybeUninit<[c_void; 1]>, + + /// Needed to keep the lifetime in check + _phantom: PhantomData<&'a usize>, +} + +impl<'a> Clone for QAnyStringView<'a> { + /// Constructs a copy of other. + /// + /// This operation takes constant time, because QAnyStringView is a view-only string. + fn clone(&self) -> QAnyStringView<'a> { + ffi::QAnyStringView_init_from_QAnyStringView(self) + } +} + +impl Default for QAnyStringView<'_> { + /// Constructs a null string. Null strings are also empty. + fn default() -> Self { + ffi::QAnyStringView_init_default() + } +} + +impl PartialEq for QAnyStringView<'_> { + fn eq(&self, other: &Self) -> bool { + ffi::QAnyStringView_eq(self, other) + } +} + +impl Eq for QAnyStringView<'_> {} + +impl<'a> From<&'a str> for QAnyStringView<'a> { + /// Constructs a QAnyStringView from a Rust string + fn from(str: &str) -> Self { + ffi::QAnyStringView_init_from_rust_string(str) + } +} + +impl From<&QString> for QAnyStringView<'_> { + /// Constructs a QAnyStringView from a QString + fn from(string: &QString) -> Self { + ffi::QAnyStringView_init_from_qstring(string) + } +} + +impl QAnyStringView<'_> { + /// Returns the number of characters in this string. + pub fn len(&self) -> isize { + ffi::QAnyStringView_len(self) + } +} + +// Safety: +// +// Static checks on the C++ side to ensure the size is the same. +unsafe impl ExternType for QAnyStringView<'_> { + type Id = type_id!("QAnyStringView"); + type Kind = cxx::kind::Trivial; +} diff --git a/tests/qt_types_standalone/CMakeLists.txt b/tests/qt_types_standalone/CMakeLists.txt index 646803038..68e5da39c 100644 --- a/tests/qt_types_standalone/CMakeLists.txt +++ b/tests/qt_types_standalone/CMakeLists.txt @@ -87,6 +87,10 @@ add_executable(${APP_NAME} cpp/qvector3d.h cpp/qvector4d.h ) +if(NOT USE_QT5) + target_sources(${APP_NAME} PRIVATE + cpp/qanystringview.h) +endif() target_include_directories(${APP_NAME} PRIVATE cpp) target_link_libraries(${APP_NAME} PRIVATE diff --git a/tests/qt_types_standalone/cpp/main.cpp b/tests/qt_types_standalone/cpp/main.cpp index 04c42e465..76cf83407 100644 --- a/tests/qt_types_standalone/cpp/main.cpp +++ b/tests/qt_types_standalone/cpp/main.cpp @@ -7,6 +7,10 @@ #include #include +#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) +#include "qanystringview.h" +#endif + #include "qbytearray.h" #include "qcolor.h" #include "qcoreapplication.h" @@ -59,6 +63,9 @@ main(int argc, char* argv[]) } }; +#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) + runTest(QScopedPointer(new QAnyStringViewTest)); +#endif runTest(QScopedPointer(new QByteArrayTest)); runTest(QScopedPointer(new QColorTest)); runTest(QScopedPointer(new QCoreApplicationTest)); diff --git a/tests/qt_types_standalone/cpp/qanystringview.h b/tests/qt_types_standalone/cpp/qanystringview.h new file mode 100644 index 000000000..f8d53318d --- /dev/null +++ b/tests/qt_types_standalone/cpp/qanystringview.h @@ -0,0 +1,38 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include +#include + +#include "qt_types_standalone/src/qanystringview.cxx.h" + +class QAnyStringViewTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void construct() + { + const auto s = construct_qanystringview("String constructed by Rust"); + QCOMPARE(s, QByteArrayLiteral("String constructed by Rust")); + } + + void construct_qstring() + { + const auto s = construct_qanystringview_qstring( + QStringLiteral("String constructed by Rust")); + QCOMPARE(s, QByteArrayLiteral("String constructed by Rust")); + } + + void clone() + { + const auto l = QAnyStringView("Test"); + const auto c = clone_qanystringview(l); + QCOMPARE(c, l); + } +}; diff --git a/tests/qt_types_standalone/rust/Cargo.toml b/tests/qt_types_standalone/rust/Cargo.toml index 5d2bb3191..7acaf9020 100644 --- a/tests/qt_types_standalone/rust/Cargo.toml +++ b/tests/qt_types_standalone/rust/Cargo.toml @@ -20,3 +20,4 @@ cxx-qt-lib = { workspace = true, features = ["full"] } [build-dependencies] cxx-qt-build.workspace = true +qt-build-utils.workspace = true diff --git a/tests/qt_types_standalone/rust/build.rs b/tests/qt_types_standalone/rust/build.rs index 7b748f1d2..3c752a8f2 100644 --- a/tests/qt_types_standalone/rust/build.rs +++ b/tests/qt_types_standalone/rust/build.rs @@ -6,7 +6,10 @@ use cxx_qt_build::CxxQtBuilder; fn main() { - CxxQtBuilder::new() + let qtbuild = qt_build_utils::QtBuild::new(vec!["Core".to_owned()]) + .expect("Could not find Qt installation"); + + let mut builder = CxxQtBuilder::new() .file("src/qbytearray.rs") .file("src/qcolor.rs") .file("src/qcoreapplication.rs") @@ -45,6 +48,11 @@ fn main() { .file("src/qvector.rs") .file("src/qvector2d.rs") .file("src/qvector3d.rs") - .file("src/qvector4d.rs") - .build(); + .file("src/qvector4d.rs"); + + if qtbuild.version().major > 5 { + builder = builder.file("src/qanystringview.rs"); + } + + builder.build(); } diff --git a/tests/qt_types_standalone/rust/src/lib.rs b/tests/qt_types_standalone/rust/src/lib.rs index 89a17d9b3..edee936a9 100644 --- a/tests/qt_types_standalone/rust/src/lib.rs +++ b/tests/qt_types_standalone/rust/src/lib.rs @@ -4,6 +4,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +#[cfg(cxxqt_qt_version_major = "6")] +mod qanystringview; + mod qbytearray; mod qcolor; mod qcoreapplication; diff --git a/tests/qt_types_standalone/rust/src/qanystringview.rs b/tests/qt_types_standalone/rust/src/qanystringview.rs new file mode 100644 index 000000000..f0d806a13 --- /dev/null +++ b/tests/qt_types_standalone/rust/src/qanystringview.rs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// Seems to be a Clippy false positive, we need these lifetime declarations +#![allow(clippy::needless_lifetimes)] + +use cxx_qt_lib::{QAnyStringView, QString}; + +#[cxx::bridge] +mod qanystringview_cxx { + unsafe extern "C++" { + include!("cxx-qt-lib/qanystringview.h"); + type QAnyStringView<'a> = cxx_qt_lib::QAnyStringView<'a>; + + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + } + + extern "Rust" { + fn construct_qanystringview(str: &str) -> QAnyStringView; + unsafe fn construct_qanystringview_qstring<'a>(str: &'a QString) -> QAnyStringView<'a>; + unsafe fn clone_qanystringview<'a>(l: &'a QAnyStringView) -> QAnyStringView<'a>; + } +} + +fn construct_qanystringview(str: &str) -> QAnyStringView { + QAnyStringView::from(str) +} +fn construct_qanystringview_qstring(str: &QString) -> QAnyStringView { + QAnyStringView::from(str) +} +fn clone_qanystringview<'a>(l: &'a QAnyStringView) -> QAnyStringView<'a> { + l.clone() +}