Skip to main content

cxx_qt_lib/core/
qstring.rs

1// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3// SPDX-FileContributor: Gerhard de Clercq <gerhard.declercq@kdab.com>
4//
5// SPDX-License-Identifier: MIT OR Apache-2.0
6use cxx::{type_id, ExternType};
7use std::cmp::Ordering;
8use std::fmt::{self, Write};
9use std::mem::MaybeUninit;
10
11use crate::{CaseSensitivity, QByteArray, QStringList, SplitBehaviorFlags};
12
13#[cxx::bridge]
14mod ffi {
15    #[namespace = "Qt"]
16    unsafe extern "C++" {
17        include!("cxx-qt-lib/qt.h");
18        type CaseSensitivity = crate::CaseSensitivity;
19        type SplitBehaviorFlags = crate::SplitBehaviorFlags;
20    }
21
22    unsafe extern "C++" {
23        include!("cxx-qt-lib/qbytearray.h");
24        type QByteArray = crate::QByteArray;
25        include!("cxx-qt-lib/qstring.h");
26        type QString = super::QString;
27        include!("cxx-qt-lib/qstringlist.h");
28        type QStringList = crate::QStringList;
29
30        /// Appends the string `str` onto the end of this string.
31        fn append<'a>(self: &'a mut QString, str: &QString) -> &'a mut QString;
32
33        /// Clears the contents of the string and makes it null.
34        fn clear(self: &mut QString);
35
36        // We wrap this method to provide an enum so hide it from docs
37        #[doc(hidden)]
38        #[rust_name = "compare_i32"]
39        fn compare(self: &QString, other: &QString, cs: CaseSensitivity) -> i32;
40
41        /// Returns `true` if this string contains an occurrence of the string `str`; otherwise returns `false`.
42        ///
43        /// If `cs` is [`CaseSensitivity::CaseSensitive`], the search is case-sensitive; otherwise the search is case-insensitive.
44        fn contains(self: &QString, str: &QString, cs: CaseSensitivity) -> bool;
45
46        /// Returns `true` if the string ends with `s`; otherwise returns `false`.
47        ///
48        /// If `cs` is [`CaseSensitivity::CaseSensitive`], the search is case-sensitive; otherwise the search is case-insensitive.
49        #[rust_name = "ends_with"]
50        fn endsWith(self: &QString, s: &QString, cs: CaseSensitivity) -> bool;
51
52        /// Returns `true` if the string has no characters; otherwise returns `false`.
53        #[rust_name = "is_empty"]
54        fn isEmpty(self: &QString) -> bool;
55
56        /// Returns `true` if the string is lowercase, that is, it's identical to its [`to_lower`](Self::to_lower) folding.
57        #[rust_name = "is_lower"]
58        fn isLower(self: &QString) -> bool;
59
60        /// Returns `true` if this string is null; otherwise returns `false`.
61        #[rust_name = "is_null"]
62        fn isNull(self: &QString) -> bool;
63
64        /// Returns `true` if the string is read right to left.
65        #[rust_name = "is_right_to_left"]
66        fn isRightToLeft(self: &QString) -> bool;
67
68        /// Returns `true` if the string is uppercase, that is, it's identical to its [`to_upper`](Self::to_upper) folding.
69        #[rust_name = "is_upper"]
70        fn isUpper(self: &QString) -> bool;
71
72        /// Returns `true` if the string contains valid UTF-16 encoded data, or `false` otherwise.
73        #[rust_name = "is_valid_utf16"]
74        fn isValidUtf16(self: &QString) -> bool;
75
76        /// Prepends the string `str` to the beginning of this string and returns a mutable reference to this string.
77        fn prepend<'a>(self: &'a mut QString, str: &QString) -> &'a mut QString;
78
79        /// Removes every occurrence of the given `str` string in this string, and returns a mutable reference to this string.
80        ///
81        /// If `cs` is [`CaseSensitivity::CaseSensitive`], the search is case-sensitive; otherwise the search is case-insensitive.
82        fn remove<'a>(self: &'a mut QString, str: &QString, cs: CaseSensitivity)
83            -> &'a mut QString;
84
85        /// Removes the first character in this string. If the string is empty, this function does nothing.
86        ///
87        /// This function was introduced in Qt 6.5.
88        #[cfg(any(cxxqt_qt_version_at_least_7, cxxqt_qt_version_at_least_6_5))]
89        #[rust_name = "remove_first"]
90        fn removeFirst(self: &mut QString) -> &mut QString;
91
92        /// Removes the last character in this string. If the string is empty, this function does nothing.
93        ///
94        /// This function was introduced in Qt 6.5.
95        #[cfg(any(cxxqt_qt_version_at_least_7, cxxqt_qt_version_at_least_6_5))]
96        #[rust_name = "remove_last"]
97        fn removeLast(self: &mut QString) -> &mut QString;
98
99        /// Replaces every occurrence of the string `before` with the string `after` and returns a mutable reference to this string.
100        ///
101        /// If `cs` is [`CaseSensitivity::CaseSensitive`], the search is case-sensitive; otherwise the search is case-insensitive.
102        fn replace<'a>(
103            self: &'a mut QString,
104            before: &QString,
105            after: &QString,
106            cs: CaseSensitivity,
107        ) -> &'a mut QString;
108
109        /// Returns `true` if the string starts with `s`; otherwise returns `false`.
110        #[rust_name = "starts_with"]
111        fn startsWith(self: &QString, s: &QString, cs: CaseSensitivity) -> bool;
112
113        /// Converts a plain text string to an HTML string with HTML metacharacters `<`, `>`, `&`, and `"` replaced by HTML entities.
114        #[rust_name = "to_html_escaped"]
115        fn toHtmlEscaped(self: &QString) -> QString;
116    }
117
118    #[namespace = "rust::cxxqtlib1"]
119    unsafe extern "C++" {
120        include!("cxx-qt-lib/common.h");
121
122        #[doc(hidden)]
123        #[rust_name = "qstring_drop"]
124        fn drop(string: &mut QString);
125
126        #[doc(hidden)]
127        #[rust_name = "qstring_init_default"]
128        fn construct() -> QString;
129        #[doc(hidden)]
130        #[rust_name = "qstring_init_from_rust_string"]
131        fn qstringInitFromRustString(string: &str) -> QString;
132        #[doc(hidden)]
133        #[rust_name = "qstring_init_from_qstring"]
134        fn construct(string: &QString) -> QString;
135
136        #[doc(hidden)]
137        #[rust_name = "qstring_eq"]
138        fn operatorEq(a: &QString, b: &QString) -> bool;
139        #[doc(hidden)]
140        #[rust_name = "qstring_cmp"]
141        fn operatorCmp(a: &QString, b: &QString) -> i8;
142
143        #[doc(hidden)]
144        #[rust_name = "qstring_as_slice"]
145        fn qstringAsSlice(string: &QString) -> &[u16];
146
147        #[doc(hidden)]
148        #[rust_name = "qstring_arg"]
149        fn qstringArg(string: &QString, a: &QString) -> QString;
150        #[doc(hidden)]
151        #[rust_name = "qstring_index_of"]
152        fn qstringIndexOf(
153            string: &QString,
154            str: &QString,
155            from: isize,
156            cs: CaseSensitivity,
157        ) -> isize;
158        #[doc(hidden)]
159        #[rust_name = "qstring_insert"]
160        fn qstringInsert<'a>(string: &'a mut QString, pos: isize, str: &QString)
161            -> &'a mut QString;
162        #[doc(hidden)]
163        #[rust_name = "qstring_left"]
164        fn qstringLeft(string: &QString, n: isize) -> QString;
165        #[doc(hidden)]
166        #[rust_name = "qstring_len"]
167        fn qstringLen(string: &QString) -> isize;
168        #[doc(hidden)]
169        #[rust_name = "qstring_mid"]
170        fn qstringMid(string: &QString, position: isize, n: isize) -> QString;
171        #[doc(hidden)]
172        #[rust_name = "qstring_right"]
173        fn qstringRight(string: &QString, n: isize) -> QString;
174        #[doc(hidden)]
175        #[rust_name = "qstring_simplified"]
176        fn qstringSimplified(string: &QString) -> QString;
177        #[doc(hidden)]
178        #[rust_name = "qstring_split"]
179        fn qstringSplit(
180            string: &QString,
181            sep: &QString,
182            behavior: SplitBehaviorFlags,
183            cs: CaseSensitivity,
184        ) -> QStringList;
185        #[doc(hidden)]
186        #[rust_name = "qstring_to_latin1"]
187        fn qstringToLatin1(string: &QString) -> QByteArray;
188        #[doc(hidden)]
189        #[rust_name = "qstring_to_local8bit"]
190        fn qstringToLocal8Bit(string: &QString) -> QByteArray;
191        #[doc(hidden)]
192        #[rust_name = "qstring_to_lower"]
193        fn qstringToLower(string: &QString) -> QString;
194        #[doc(hidden)]
195        #[rust_name = "qstring_to_upper"]
196        fn qstringToUpper(string: &QString) -> QString;
197        #[doc(hidden)]
198        #[rust_name = "qstring_to_utf8"]
199        fn qstringToUtf8(string: &QString) -> QByteArray;
200        #[doc(hidden)]
201        #[rust_name = "qstring_trimmed"]
202        fn qstringTrimmed(string: &QString) -> QString;
203    }
204}
205
206/// The `QString` class provides a Unicode character string.
207///
208/// Note that `QString` is encoded in UTF-16, whereas Rust's [`String`] is encoded in UTF-8.
209///
210/// Qt Documentation: [QString](https://doc.qt.io/qt/qstring.html#details)
211#[repr(C)]
212pub struct QString {
213    /// The layout has changed between Qt 5 and Qt 6
214    ///
215    /// Qt5 QString has one pointer as a member
216    /// Qt6 QString has one member, which contains two pointers and a ssize_t
217    _d: MaybeUninit<usize>,
218    #[cfg(cxxqt_qt_version_major = "6")]
219    _ptr: MaybeUninit<usize>,
220    #[cfg(cxxqt_qt_version_major = "6")]
221    _size: MaybeUninit<isize>,
222}
223
224impl Clone for QString {
225    /// Constructs a copy of this string.
226    ///
227    /// This operation takes constant time, because `QString` is implicitly shared.
228    /// This makes returning a `QString` from a function very fast.
229    /// If a shared instance is modified, it will be copied (copy-on-write), and that takes linear time.
230    fn clone(&self) -> Self {
231        ffi::qstring_init_from_qstring(self)
232    }
233}
234
235impl Default for QString {
236    /// Constructs a null string. Null strings are also empty.
237    fn default() -> Self {
238        ffi::qstring_init_default()
239    }
240}
241
242impl PartialEq for QString {
243    fn eq(&self, other: &Self) -> bool {
244        ffi::qstring_eq(self, other)
245    }
246}
247
248impl Eq for QString {}
249
250impl PartialOrd for QString {
251    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
252        Some(self.cmp(other))
253    }
254}
255
256impl Ord for QString {
257    fn cmp(&self, other: &Self) -> Ordering {
258        ffi::qstring_cmp(self, other).cmp(&0)
259    }
260}
261
262impl fmt::Display for QString {
263    /// Format the `QString` as a Rust string.
264    ///
265    /// Note that this converts from UTF-16 to UTF-8.
266    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
267        if f.width().is_some() || f.precision().is_some() {
268            return f.pad(&String::from(self));
269        }
270        for c in char::decode_utf16(self.as_slice().iter().copied()) {
271            f.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER))?;
272        }
273        Ok(())
274    }
275}
276
277impl fmt::Debug for QString {
278    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279        String::from(self).fmt(f)
280    }
281}
282
283impl std::ops::Add for QString {
284    type Output = Self;
285    fn add(self, other: Self) -> Self {
286        let mut res = ffi::qstring_init_from_qstring(&self);
287        res.append(&other);
288        res
289    }
290}
291
292impl Drop for QString {
293    /// Destroys the string.
294    fn drop(&mut self) {
295        ffi::qstring_drop(self)
296    }
297}
298
299impl From<&str> for QString {
300    /// Constructs a `QString` from a string slice.
301    ///
302    /// Note that this converts from UTF-8 to UTF-16.
303    fn from(str: &str) -> Self {
304        ffi::qstring_init_from_rust_string(str)
305    }
306}
307
308impl From<&String> for QString {
309    /// Constructs a `QString` from a Rust `String` reference.
310    ///
311    /// Note that this converts from UTF-8 to UTF-16.
312    fn from(str: &String) -> Self {
313        ffi::qstring_init_from_rust_string(str)
314    }
315}
316
317impl From<String> for QString {
318    /// Constructs a `QString` from a Rust `String`.
319    ///
320    /// Note that this converts from UTF-8 to UTF-16.
321    fn from(str: String) -> Self {
322        ffi::qstring_init_from_rust_string(&str)
323    }
324}
325
326impl From<&QString> for String {
327    /// Constructs a Rust `String` from a `QString` reference.
328    ///
329    /// Note that this converts from UTF-16 to UTF-8.
330    fn from(qstring: &QString) -> Self {
331        String::from_utf16_lossy(qstring.as_slice())
332    }
333}
334
335impl From<QString> for String {
336    /// Constructs a Rust `String` from a `QString`.
337    ///
338    /// Note that this converts from UTF-16 to UTF-8.
339    fn from(qstring: QString) -> Self {
340        String::from_utf16_lossy(qstring.as_slice())
341    }
342}
343
344impl QString {
345    /// Returns a copy of this string with the lowest numbered place marker replaced by string `a`, i.e., %1, %2, ..., %99.
346    ///
347    /// If there is no unreplaced place-marker remaining, a warning message is printed and the result is undefined. Place-marker numbers must be in the range 1 to 99.
348    pub fn arg(&self, a: &QString) -> Self {
349        ffi::qstring_arg(self, a)
350    }
351
352    /// Extracts a slice containing the entire UTF-16 array.
353    pub fn as_slice(&self) -> &[u16] {
354        ffi::qstring_as_slice(self)
355    }
356
357    /// Lexically compares this string with the `other` string.
358    ///
359    /// If `cs` is [`CaseSensitivity::CaseSensitive`], the comparison is case-sensitive; otherwise the comparison is case-insensitive.
360    ///
361    /// Case sensitive comparison is based exclusively on the numeric Unicode values of the characters and is very fast, but is not what a human would expect.
362    pub fn compare(&self, other: &QString, cs: CaseSensitivity) -> Ordering {
363        self.compare_i32(other, cs).cmp(&0)
364    }
365
366    /// Returns the index position of the first occurrence of the string `str` in this string,
367    /// searching forward from index position `from`. Returns -1 if `str` is not found.
368    ///
369    /// If `cs` is [`CaseSensitivity::CaseSensitive`], the search is case-sensitive; otherwise the comparison is case-insensitive.
370    pub fn index_of(&self, str: &QString, from: isize, cs: CaseSensitivity) -> isize {
371        ffi::qstring_index_of(self, str, from, cs)
372    }
373
374    /// Inserts the string `str` at the given index `position` and returns a mutable reference to this string.
375    pub fn insert<'a>(&'a mut self, position: isize, str: &Self) -> &'a mut Self {
376        ffi::qstring_insert(self, position, str)
377    }
378
379    /// Returns a substring that contains the `n` leftmost characters of the string.
380    pub fn left(&self, n: isize) -> Self {
381        ffi::qstring_left(self, n)
382    }
383
384    /// Returns the number of characters in this string.
385    pub fn len(self: &QString) -> isize {
386        ffi::qstring_len(self)
387    }
388
389    /// Returns a string that contains `n` characters of this string, starting at the specified `position` index.
390    pub fn mid(&self, position: isize, n: isize) -> Self {
391        ffi::qstring_mid(self, position, n)
392    }
393
394    /// Returns a substring that contains the `n` rightmost characters of the string.
395    pub fn right(&self, n: isize) -> Self {
396        ffi::qstring_right(self, n)
397    }
398
399    /// Returns a string that has whitespace removed from the start and the end,
400    /// and that has each sequence of internal whitespace replaced with a single space.
401    ///
402    /// Whitespace characters are the ASCII characters tabulation `'\t'`, line feed `'\n'`, carriage return `'\r'`, vertical tabulation `'\x08'` (`'\v'` in C), form feed `'\x0C'` (`'\f'` in C), and space `' '`.
403    pub fn simplified(&self) -> Self {
404        ffi::qstring_simplified(self)
405    }
406
407    /// Splits the string into substrings wherever `sep` occurs, and returns the list of those strings.
408    /// If `sep` does not match anywhere in the string, this function returns a single-element list containing this string.
409    ///
410    /// `cs` specifies whether `sep` should be matched case sensitively or case insensitively.
411    ///
412    /// If `behavior` is [`SplitBehaviorFlags::SkipEmptyParts`], empty entries don't appear in the result.
413    pub fn split(
414        &self,
415        sep: &QString,
416        behavior: SplitBehaviorFlags,
417        cs: CaseSensitivity,
418    ) -> QStringList {
419        ffi::qstring_split(self, sep, behavior, cs)
420    }
421
422    /// Returns a Latin-1 representation of the string as a `QByteArray`.
423    ///
424    /// The returned byte array is undefined if the string contains non-Latin1 characters. Those characters may be suppressed or replaced with a question mark.
425    pub fn to_latin1(&self) -> QByteArray {
426        ffi::qstring_to_latin1(self)
427    }
428
429    /// Returns the local 8-bit representation of the string as a `QByteArray`.
430    ///
431    /// If this string contains any characters that cannot be encoded in the local 8-bit encoding, the returned byte array is undefined. Those characters may be suppressed or replaced by another.
432    pub fn to_local8bit(&self) -> QByteArray {
433        ffi::qstring_to_local8bit(self)
434    }
435
436    /// Returns a lowercase copy of the string.
437    pub fn to_lower(&self) -> Self {
438        ffi::qstring_to_lower(self)
439    }
440
441    /// Returns an uppercase copy of the string.
442    pub fn to_upper(&self) -> Self {
443        ffi::qstring_to_upper(self)
444    }
445
446    /// Returns a UTF-8 representation of the string as a `QByteArray`.
447    pub fn to_utf8(&self) -> QByteArray {
448        ffi::qstring_to_utf8(self)
449    }
450
451    /// Returns a string that has whitespace removed from the start and the end.
452    ///
453    /// Whitespace characters are the ASCII characters tabulation `'\t'`, line feed `'\n'`, carriage return `'\r'`, vertical tabulation `'\x08'` (`'\v'` in C), form feed `'\x0C'` (`'\f'` in C), and space `' '`.
454    pub fn trimmed(&self) -> Self {
455        ffi::qstring_trimmed(self)
456    }
457}
458
459// Safety:
460//
461// Static checks on the C++ side to ensure the size is the same.
462unsafe impl ExternType for QString {
463    type Id = type_id!("QString");
464    type Kind = cxx::kind::Trivial;
465}
466
467#[cfg(feature = "serde")]
468impl serde::Serialize for QString {
469    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
470        serializer.serialize_str(&String::from(self))
471    }
472}
473
474#[cfg(feature = "serde")]
475impl<'de> serde::Deserialize<'de> for QString {
476    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
477        use serde::de::{Error as DeError, Unexpected, Visitor};
478
479        struct StringVisitor;
480
481        impl Visitor<'_> for StringVisitor {
482            type Value = QString;
483
484            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
485                formatter.write_str("a string")
486            }
487
488            fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
489                Ok(Self::Value::from(v))
490            }
491
492            fn visit_bytes<E: DeError>(self, v: &[u8]) -> Result<Self::Value, E> {
493                match std::str::from_utf8(v) {
494                    Ok(s) => Ok(Self::Value::from(s)),
495                    Err(_) => Err(E::invalid_value(Unexpected::Bytes(v), &self)),
496                }
497            }
498        }
499
500        let visitor = StringVisitor;
501        deserializer.deserialize_string(visitor)
502    }
503}
504
505#[cfg(test)]
506mod test {
507    use super::*;
508
509    #[cfg(feature = "serde")]
510    #[test]
511    fn qstring_serde() {
512        let qstring = QString::from("KDAB");
513        assert_eq!(crate::serde_impl::roundtrip(&qstring), qstring);
514    }
515
516    #[test]
517    fn test_ordering() {
518        let qstring_a = QString::from("a");
519        let qstring_b = QString::from("b");
520
521        assert!(qstring_a < qstring_b);
522        assert_eq!(qstring_a.cmp(&qstring_b), Ordering::Less);
523        assert_eq!(qstring_b.cmp(&qstring_a), Ordering::Greater);
524        assert_eq!(qstring_a.cmp(&qstring_a), Ordering::Equal);
525
526        assert_eq!(
527            qstring_a.compare(&qstring_b, crate::CaseSensitivity::CaseInsensitive),
528            Ordering::Less
529        );
530        assert_eq!(
531            qstring_b.compare(&qstring_a, crate::CaseSensitivity::CaseInsensitive),
532            Ordering::Greater
533        );
534        assert_eq!(
535            qstring_a.compare(&qstring_a, crate::CaseSensitivity::CaseInsensitive),
536            Ordering::Equal
537        );
538    }
539}