Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/KDAB/cxx-qt/compare/v0.8.0...HEAD)

### Changed

- Moved subtype aliases for `QEventLoop`, `QTimeZone`, and `QUuid` into the `rust::cxxqtlib1` namespace.
- Many `QString` methods now accept `QChar` parameters.

## [0.8.0](https://github.com/KDAB/cxx-qt/compare/v0.7.2...v0.8.0) - 2025-12-18

Expand Down
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn main() {
let mut rust_bridges = vec![
"core/qbytearray",
"core/qcoreapplication",
"core/qchar",
"core/qdate",
"core/qhash/qhash_i32_qbytearray",
"core/qhash/qhash_qstring_qvariant",
Expand Down Expand Up @@ -144,6 +145,7 @@ fn main() {
"core/qvariant/qvariant_i32",
"core/qvariant/qvariant_i64",
"core/qvariant/qvariant_qbytearray",
"core/qvariant/qvariant_qchar",
"core/qvariant/qvariant_qdate",
"core/qvariant/qvariant_qline",
"core/qvariant/qvariant_qlinef",
Expand Down Expand Up @@ -257,6 +259,7 @@ fn main() {

let mut cpp_files = vec![
"core/qbytearray",
"core/qchar",
"core/qcoreapplication",
"core/qdate",
"core/qhash/qhash",
Expand Down
20 changes: 20 additions & 0 deletions crates/cxx-qt-lib/include/core/qchar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// clang-format off
// SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// clang-format on
// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include <QtCore/QChar>

#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<QChar> : ::std::true_type
{};
}
18 changes: 18 additions & 0 deletions crates/cxx-qt-lib/include/core/qstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,27 @@ qstringInitFromRustString(::rust::Str string);
::rust::Slice<const ::std::uint16_t>
qstringAsSlice(const QString& string);

::rust::Slice<const QChar>
qstringAsChars(const QString& string);

QChar
qstringAt(const QString& string, ::rust::isize position);

QString
qstringArg(const QString& string, const QString& a);
::rust::isize
qstringIndexOf(const QString& string,
QChar ch,
::rust::isize from,
Qt::CaseSensitivity cs);
::rust::isize
qstringIndexOf(const QString& string,
const QString& str,
::rust::isize from,
Qt::CaseSensitivity cs);
QString&
qstringInsert(QString& string, ::rust::isize pos, QChar ch);
QString&
qstringInsert(QString& string, ::rust::isize pos, const QString& str);
QString
qstringLeft(const QString& string, ::rust::isize n);
Expand All @@ -49,6 +62,11 @@ qstringMid(const QString& string, ::rust::isize position, ::rust::isize n);
QString
qstringRight(const QString& string, ::rust::isize n);
QStringList
qstringSplit(const QString& string,
QChar sep,
Qt::SplitBehaviorFlags behavior,
Qt::CaseSensitivity cs);
QStringList
qstringSplit(const QString& string,
const QString& sep,
Qt::SplitBehaviorFlags behavior,
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-lib/include/core/qvariant.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ CXX_QT_QVARIANT_CAN_CONVERT(I16)
CXX_QT_QVARIANT_CAN_CONVERT(I32)
CXX_QT_QVARIANT_CAN_CONVERT(I64)
CXX_QT_QVARIANT_CAN_CONVERT(QByteArray)
CXX_QT_QVARIANT_CAN_CONVERT(QChar)
CXX_QT_QVARIANT_CAN_CONVERT(QDate)
CXX_QT_QVARIANT_CAN_CONVERT(QDateTime)
CXX_QT_QVARIANT_CAN_CONVERT(QLine)
Expand Down
9 changes: 9 additions & 0 deletions crates/cxx-qt-lib/include/qchar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// clang-format off
// SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// clang-format on
// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include "core/qchar.h"
5 changes: 4 additions & 1 deletion crates/cxx-qt-lib/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pub use qbytearray::{
QByteArray, QByteArrayBase64Option, QByteArrayBase64Options, QByteArrayFromBase64Error,
};

mod qchar;
pub use qchar::QChar;

mod qcoreapplication;
pub use qcoreapplication::QCoreApplication;

Expand Down Expand Up @@ -74,7 +77,7 @@ mod qsizef;
pub use qsizef::QSizeF;

mod qstring;
pub use qstring::QString;
pub use qstring::{QString, QStringElement};

#[cfg(cxxqt_qt_version_major = "6")]
mod qanystringview;
Expand Down
17 changes: 17 additions & 0 deletions crates/cxx-qt-lib/src/core/qchar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// clang-format off
// SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// clang-format on
// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#include "cxx-qt-lib/qchar.h"

#include <cxx-qt-lib/assertion_utils.h>

assert_alignment_and_size(QChar, { ::std::uint16_t ucs; });

static_assert(::std::is_trivially_copy_assignable<QChar>::value);
static_assert(::std::is_trivially_copy_constructible<QChar>::value);
static_assert(::std::is_trivially_destructible<QChar>::value);
static_assert(::std::is_move_constructible<QChar>::value);
static_assert(QTypeInfo<QChar>::isRelocatable);
150 changes: 150 additions & 0 deletions crates/cxx-qt-lib/src/core/qchar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::char::{CharTryFromError, TryFromCharError};
use std::fmt;

use cxx::{type_id, ExternType};

#[cxx::bridge]
mod ffi {
extern "C++" {
include!("cxx-qt-lib/qchar.h");
#[allow(unused)]
type QChar = super::QChar;
}
}

#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct QChar {
ucs: u16,
}

impl fmt::Display for QChar {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(c) = self.to_char() {
c.fmt(f)
} else {
write!(f, "\\u{{{:X}}}", self.unicode())
}
}
}

impl fmt::Debug for QChar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(c) = self.to_char() {
c.fmt(f)
} else {
write!(f, "\'\\u{{{:X}}}\'", self.unicode())
}
}
}

impl QChar {
/// Constructs a `QChar` from UTF-16 character `c`.
pub const fn new(c: u16) -> Self {
Self { ucs: c }
}

/// Constructs a `QChar` from a byte if it is valid ASCII (i.e. <= 127).
pub const fn from_ascii(b: u8) -> Option<Self> {
if b.is_ascii() {
Some(Self { ucs: b as u16 })
} else {
None
}
}

/// Converts a Rust `char` to a `QChar` if it is within range (i.e. <= [`u16::MAX`]).
pub const fn from_char(c: char) -> Option<Self> {
if c as u32 > u16::MAX as u32 {
None
} else {
Some(Self::new(c as u16))
}
}

/// Returns the numeric Unicode value of the `QChar`.
pub const fn unicode(self) -> u16 {
self.ucs
}

/// Returns `true` if the `QChar` contains a code point that is in either the high or the low part of the UTF-16 surrogate range (for example if its code point is in range `[0xd800..0xdfff]`); `false` otherwise.
pub const fn is_surrogate(self) -> bool {
self.ucs - 0xd800 < 2048
}

/// Returns `true` if the `QChar` is the high part of a UTF16 surrogate (for example if its code point is in range `[0xd800..0xdbff]`); `false` otherwise.
pub const fn is_high_surrogate(self) -> bool {
(self.ucs & 0xfc00) == 0xd800
}

/// Returns `true` if the `QChar` is the low part of a UTF16 surrogate (for example if its code point is in range `[0xdc00..0xdfff]`); `false` otherwise.
pub const fn is_low_surrogate(self) -> bool {
(self.ucs & 0xfc00) == 0xdc00
}

/// Converts a `QChar` to a Rust `char` if it is a valid character (i.e. not a surrogate).
pub const fn to_char(self) -> Option<char> {
char::from_u32(self.ucs as u32)
}
}

impl From<u16> for QChar {
fn from(value: u16) -> Self {
Self::new(value)
}
}

impl From<QChar> for u16 {
fn from(value: QChar) -> Self {
value.unicode()
}
}

impl TryFrom<char> for QChar {
type Error = TryFromCharError;

fn try_from(value: char) -> Result<Self, Self::Error> {
u16::try_from(value).map(QChar::from)
}
}

impl TryFrom<QChar> for char {
type Error = CharTryFromError;

fn try_from(value: QChar) -> Result<Self, Self::Error> {
u32::from(value.unicode()).try_into()
}
}

// SAFETY: Static checks on the C++ side to ensure the size is the same.
unsafe impl ExternType for QChar {
type Id = type_id!("QChar");
type Kind = cxx::kind::Trivial;
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn qchar_display_char() {
let qchar = QChar::from_char('ᥱ').unwrap();
assert_eq!(qchar.to_string(), "ᥱ");
}

#[test]
fn qchar_display_surrogate() {
let qchar = QChar::new(0xD834);
assert_eq!(qchar.to_string(), "\\u{D834}");
}

#[test]
fn qchar_debug_surrogate() {
let qchar = QChar::new(0xD834);
assert_eq!(format!("{qchar:?}"), "'\\u{D834}'");
}
}
Loading
Loading