Dokit
Internal development documentation
Loading...
Searching...
No Matches
abstractpokitservice.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2022-2026 Paul Colby <git@colby.id.au>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4/*!
5 * \file
6 * Defines the AbstractPokitService and AbstractPokitServicePrivate classes.
7 */
8
11#include "pokitproducts_p.h"
12#include "../stringliterals_p.h"
13
14#include <qtpokit/pokitdevice.h>
15
16#include <QLowEnergyController>
17
20
21/*!
22 * \class AbstractPokitService
23 *
24 * The AbstractPokitService class provides a common base for Pokit services classes.
25 */
26
27/*!
28 * \cond internal
29 * Constructs a new Pokit service with \a parent, and private implementation \a d.
30 */
31AbstractPokitService::AbstractPokitService(
33 : QObject(parent), d_ptr(d)
34{
35
36}
37/// \endcond
38
39/*!
40 * Destroys this AbstractPokitService object.
41 */
46
47/*!
48 * \fn virtual bool AbstractPokitService::readCharacteristics() = 0
49 *
50 * Read all characteristics.
51 *
52 * This convenience function will queue refresh requests of all characteristics supported by this
53 * service.
54 *
55 * Relevant `*Service::*Read` signals will be emitted by derived class objects as each
56 * characteristic is successfully read.
57 */
58
59/*!
60 * Returns `true` if autodiscovery of services and service details is enabled, `false` otherwise.
61 *
62 * \see setAutoDiscover for more information on what autodiscovery provides.
63 */
65{
66 Q_D(const AbstractPokitService);
67 return d->autoDiscover;
68}
69
70/*!
71 * If \a discover is \c true, autodiscovery will be attempted.
72 *
73 * Specifically, this may resulting in automatic invocation of:
74 * * QLowEnergyController::discoverServices if/when the internal controller is connected; and
75 * * QLowEnergyService::discoverDetails if/when an internal service object is created.
76 *
77 * \see autoDiscover
78 */
80{
81 Q_D(AbstractPokitService);
82 d->autoDiscover = discover;
83}
84
85/*!
86 * Returns the Pokit product this service is attached to.
87 *
88 * \see setPokitProduct
89 */
90std::optional<PokitProduct> AbstractPokitService::pokitProduct() const
91{
92 Q_D(const AbstractPokitService);
93 return d->pokitProduct;
94}
95
96/*!
97 * Sets the current Pokit \a product.
98 *
99 * This must be called to set the product before this object's BLE controller's services are discovered. If
100 * autoDiscover() is enabled, then this should be done before the controller's \c connectToDevice() is called.
101 *
102 * For example:
103 * ```
104 * Q_ASSERT(isPokitProduct(deviceInfo));
105 * auto controller = QLowEnergyController::createCentral(deviceInfo);
106 * auto service = new DsoService(controller);
107 * service->setPokitProduct(pokitProduct(deviceInfo));
108 * controller->connectToDevice();
109 * ```
110 *
111 * \see autoDiscover
112 * \see pokitProduct
113 */
115{
116 Q_D(AbstractPokitService);
117 d->pokitProduct = product;
118}
119
120/*!
121 * Returns a non-const pointer to the internal service object, if any.
122 */
124{
125 Q_D(AbstractPokitService);
126 return d->service;
127}
128
129/*!
130 * Returns a const pointer to the internal service object, if any.
131 */
133{
134 Q_D(const AbstractPokitService);
135 return d->service;
136}
137
138/*!
139 * \fn void AbstractPokitService::serviceDetailsDiscovered()
140 *
141 * This signal is emitted when the Pokit service details have been discovered.
142 *
143 * Once this signal has been emitted, cached characteristics values should be immediately available
144 * via derived classes' accessor functions, and refreshes can be queued via readCharacteristics()
145 * and any related read functions provided by derived classes.
146 */
147
148/*!
149 * \fn void AbstractPokitService::serviceErrorOccurred(QLowEnergyService::ServiceError newError)
150 *
151 * This signal is emitted whenever an error occurs on the underlying QLowEnergyService.
152 */
153
154/*!
155 * \cond internal
156 * \class AbstractPokitServicePrivate
157 *
158 * The AbstractPokitServicePrivate class provides private implementation for AbstractPokitService.
159 */
160
161/*!
162 * \internal
163 * Constructs a new AbstractPokitServicePrivate object with public implementation \a q.
164 *
165 * Note, typically the \a serviceUuid should be set validly, however, in the rare case that a
166 * service's UUID can vary (ie the Status Service), \a serviceUuid may be set to a `null`
167 * QBluetoothUuid here, and updated when the correct service UUID is known.
168 *
169 * \see StatusService::ServiceUuids
170 * \see StatusServicePrivate::serviceDiscovered
171 */
189
190/*!
191 * Creates an internal service object from the internal controller.
192 *
193 * Any existing service object will *not* be replaced.
194 *
195 * Returns \c true if a service was created successfully, either now, or sometime previously.
196 */
198{
199 if (!controller) {
200 return false;
201 }
202
203 if (service) {
204 qCDebug(lc).noquote() << tr("Already have service object:") << service;
205 return true;
206 }
207
208 if (serviceUuid.isNull()) {
209 qCDebug(lc).noquote() << tr("Service UUID not assigned yet.");
210 return false;
211 }
212
213 service = controller->createServiceObject(serviceUuid, this);
214 if (!service) {
215 return false;
216 }
217 qCDebug(lc).noquote() << tr("Service object created for %1 device:").arg(toString(*this->pokitProduct)) << service;
218
227
229 [](const QLowEnergyDescriptor &descriptor, const QByteArray &value){
230 qCDebug(lc).noquote() << tr(R"(Descriptor "%1" (%2) read.)")
231 .arg(descriptor.name(), descriptor.uuid().toString());
232 Q_UNUSED(value)
233 });
234
236 [](const QLowEnergyDescriptor &descriptor, const QByteArray &newValue){
237 qCDebug(lc).noquote() << tr(R"(Descriptor "%1" (%2) written.)")
238 .arg(descriptor.name(), descriptor.uuid().toString());
239 Q_UNUSED(newValue)
240 });
241
243 #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
244 QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
245 #else
246 &QLowEnergyService::errorOccurred,
247 #endif
249
250 if (autoDiscover) {
251 service->discoverDetails();
252 }
253 return true;
254}
255
256/*!
257 * Get \a uuid characteristic from the underlying service. This helper function is equivalent to
258 *
259 * ```
260 * return service->characteristic(uuid);
261 * ```
262 *
263 * except that it performs some sanity checks, such as checking the service object pointer has been
264 * assigned first, and also logs failures in a consistent manner.
265 *
266 * \return
267 */
269{
270 if (!service) {
271 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service assigned.)")
274 }
275
276 if (const QLowEnergyCharacteristic characteristic = service->characteristic(uuid); characteristic.isValid()) {
277 return characteristic;
278 }
279
280 if (service->state() != QLowEnergyService::
281 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
282 ServiceDiscovered
283 #else
284 RemoteServiceDiscovered
285 #endif
286 ) {
287 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service %3 "%4" discovered.)")
289 service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
290 qCInfo(lc).noquote() << tr("Current service state:") << service->state();
292 }
293
294 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" not found in service %3 "%4".)")
296 service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
298}
299
300/*!
301 * Read the \a uuid characteristic.
302 *
303 * If successful, the `QLowEnergyService::characteristicRead` signal will be emitted by the internal
304 * service object. For convenience, derived classes should implement the characteristicRead()
305 * virtual function to handle the read value.
306 *
307 * Returns \c true if the characteristic read request was successfully queued, \c false otherwise.
308 *
309 * \see AbstractPokitService::readCharacteristics()
310 * \see AbstractPokitServicePrivate::characteristicRead()
311 */
313{
314 const QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
315 if (!characteristic.isValid()) {
316 return false;
317 }
318 qCDebug(lc).noquote() << tr(R"(Reading characteristic %1 "%2".)")
320 service->readCharacteristic(characteristic);
321 return true;
322}
323
324/*!
325 * Enables client (Pokit device) side notification for characteristic \a uuid.
326 *
327 * Returns \c true if the notification enable request was successfully queued, \c false otherwise.
328 *
329 * \see AbstractPokitServicePrivate::characteristicChanged
330 * \see AbstractPokitServicePrivate::disableCharacteristicNotificatons
331 */
333{
334 qCDebug(lc).noquote() << tr(R"(Enabling CCCD for characteristic %1 "%2".)")
336 QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
337 if (!characteristic.isValid()) {
338 return false;
339 }
340
341 QLowEnergyDescriptor descriptor = characteristic.descriptor(
342 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); // 0x2902
343 if (!descriptor.isValid()) {
344 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" has no client configuration descriptor.)")
346 return false;
347 }
348
349 service->writeDescriptor(descriptor,
350 #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
351 QLowEnergyCharacteristic::CCCDEnableNotification
352 #else
353 QByteArray::fromHex("0100") // See Qt6's QLowEnergyCharacteristic::CCCDEnableNotification.
354 #endif
355 );
356 return true;
357}
358
359/*!
360 * Disables client (Pokit device) side notification for characteristic \a uuid.
361 *
362 * Returns \c true if the notification disable request was successfully queued, \c false otherwise.
363 *
364 * \see AbstractPokitServicePrivate::characteristicChanged
365 * \see AbstractPokitServicePrivate::enableCharacteristicNotificatons
366 */
368{
369 qCDebug(lc).noquote() << tr(R"(Disabling CCCD for characteristic %1 "%2".)")
371 QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
372 if (!characteristic.isValid()) {
373 return false;
374 }
375
376 QLowEnergyDescriptor descriptor = characteristic.descriptor(
377 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); // 0x2902
378 if (!descriptor.isValid()) {
379 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" has no client configuration descriptor.)")
381 return false;
382 }
383
384 service->writeDescriptor(descriptor,
385 #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
386 QLowEnergyCharacteristic::CCCDDisable
387 #else
388 QByteArray::fromHex("0000") // See Qt6's QLowEnergyCharacteristic::CCCDDisable.
389 #endif
390 );
391 return true;
392}
393
394/*!
395 * Returns `false` if \a data is smaller than \a minSize, otherwise returns \a failOnMax if \a data
396 * is bigger than \a maxSize, otherwise returns `true`.
397 *
398 * A warning is logged if either \a minSize or \a maxSize is violated, regardless of the returned
399 * value; ie this funcion can be used to simply warn if \a data is too big, or it can be used to
400 * failed (return `false`) in that case.
401 */
403 const int minSize, const int maxSize,
404 const bool failOnMax)
405{
406 if (data.size() < minSize) {
407 qCWarning(lc).noquote() << tr("%1 requires %n byte/s, but only %2 present: %3", nullptr, minSize)
408 .arg(label).arg(data.size()).arg(toHexString(data));
409 return false;
410 }
411 if ((maxSize >= 0) && (data.size() > maxSize)) {
412 qCWarning(lc).noquote() << tr("%1 has %n extraneous byte/s: %2", nullptr, data.size()-maxSize)
413 .arg(label, toHexString(data.mid(maxSize)));
414 return (!failOnMax);
415 }
416 return true;
417}
418
419/*!
420 * Returns up to \a maxSize bytes of \a data as a human readable hexadecimal string. If \a data
421 * exceeds \a maxSize, then \a data is elided in the middle. For example:
422 *
423 * ```
424 * toHex(QBytArray("\x1\x2\x3\x4\x5\x6", 4); // "0x01,02,...,05,06"
425 * ```
426 */
428{
429 return (data.size() <= maxSize)
430 ? QString::fromLatin1("0x%1").arg(QLatin1String(data.toHex(',')))
431 : QString::fromLatin1("0x%1,...,%2").arg(
432 QLatin1String(data.left(maxSize/2-1).toHex(',')),
433 QLatin1String(data.right(maxSize/2-1).toHex(',')));
434}
435
436/*!
437 * Handles `QLowEnergyController::connected` events.
438 *
439 * If `autoDiscover` is enabled, this will begin service discovery on the newly connected controller.
440 *
441 * \see AbstractPokitService::autoDiscover()
442 */
444{
445 if (!controller) {
446 qCWarning(lc).noquote() << tr("Connected with no controller set") << sender();
447 return;
448 }
449
450 qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at %3.)").arg(
451 controller->remoteName(), controller->remoteDeviceUuid().toString(),
452 controller->remoteAddress().toString());
453 if (autoDiscover) {
454 controller->discoverServices();
455 }
456}
457
458/*!
459 * Handles `QLowEnergyController::discoveryFinished` events.
460 *
461 * As this event indicates that the controller has finished discovering services, this function will
462 * invoke createServiceObject() to create the internal service object (if not already created).
463 */
465{
466 if (!controller) {
467 qCWarning(lc).noquote() << tr("Discovery finished with no controller set") << sender();
468 return;
469 }
470
471 qCDebug(lc).noquote() << tr(R"(Discovery finished for "%1" (%2) at %3.)").arg(
472 controller->remoteName(), controller->remoteDeviceUuid().toString(),
473 controller->remoteAddress().toString());
474
475 if (!createServiceObject()) {
476 qCWarning(lc).noquote() << tr("Discovery finished, but service not found.");
478 Q_EMIT q->serviceErrorOccurred(QLowEnergyService::ServiceError::UnknownError);
479 }
480}
481
482/*!
483 * Handles `QLowEnergyController::errorOccurred` events.
484 *
485 * This function simply re-emits \a newError as AbstractPokitService::serviceErrorOccurred.
486 */
488{
490 qCDebug(lc).noquote() << tr("Service error") << newError;
491 Q_EMIT q->serviceErrorOccurred(newError);
492}
493
494/*!
495 * Handles `QLowEnergyController::serviceDiscovered` events.
496 *
497 * If the discovered service is the one this (or rather the derived) class wraps, then
498 * createServiceObject() will be invoked immediately (otherwise it will be invoked after full
499 * service discovery has completed, ie in discoveryFinished()).
500 */
502{
503 if ((!service) && (newService == serviceUuid)) {
504 qCDebug(lc).noquote() << tr("Service discovered") << newService;
506 }
507}
508
509/*!
510 * Handles `QLowEnergyController::stateChanged` events.
511 *
512 * If \a newState indicates that service details have now been discovered, then
513 * AbstractPokitService::serviceDetailsDiscovered will be emitted.
514 *
515 * \see AbstractPokitService::autoDiscover()
516 */
518{
519 qCDebug(lc).noquote() << tr("State changed to") << newState;
520
521 if (lc().isDebugEnabled()) {
522 for (const auto &characteristic: service->characteristics()) {
523 QStringList properties;
524 /// \cond no-doxygen
525 #define QTPOKIT_INTERNAL_TEST_AND_APPEND(property) \
526 if (characteristic.properties().testFlag(QLowEnergyCharacteristic::property)) { \
527 properties.append(QStringLiteral(#property).toLower());\
528 }
529 /// \endcond
530 QTPOKIT_INTERNAL_TEST_AND_APPEND(Broadcasting)
531 QTPOKIT_INTERNAL_TEST_AND_APPEND(Read)
532 QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteNoResponse)
533 QTPOKIT_INTERNAL_TEST_AND_APPEND(Write)
534 QTPOKIT_INTERNAL_TEST_AND_APPEND(Notify)
535 QTPOKIT_INTERNAL_TEST_AND_APPEND(Indicate)
536 QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteSigned)
537 QTPOKIT_INTERNAL_TEST_AND_APPEND(ExtendedProperty)
538 #undef QTPOKIT_INTERNAL_TEST_AND_APPEND
539 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" supports %3.)").arg(characteristic.uuid().toString(),
540 PokitDevice::charcteristicToString(characteristic.uuid()), properties.join(u", "_s));
541 }
542 }
543
544 if (newState == QLowEnergyService::
545 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
546 ServiceDiscovered
547 #else
548 RemoteServiceDiscovered
549 #endif
550 ) {
552 qCDebug(lc).noquote() << tr("Service details discovered.");
553 Q_EMIT q->serviceDetailsDiscovered();
554 }
555}
556
557/*!
558 * Handles `QLowEnergyService::characteristicRead` events. This base implementation simply debug
559 * logs the event.
560 *
561 * Derived classes should implement this function to handle the successful reads of
562 * \a characteristic, typically by parsing \a value, then emitting a specialised signal.
563 */
565 const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
566{
567 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" read %n byte/s: %3)", nullptr, value.size()).arg(
568 characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(value));
569}
570
571/*!
572 * Handles `QLowEnergyService::characteristicWritten` events. This base implementation simply debug
573 * logs the event.
574 *
575 * Derived classes should implement this function to handle the successful writes of
576 * \a characteristic, typically by parsing \a newValue, then emitting a specialised signal.
577 */
579 const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
580{
581 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" written with %Ln byte/s: %3)", nullptr, newValue.size())
582 .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
583}
584
585/*!
586 * Handles `QLowEnergyService::characteristicChanged` events. This base implementation simply debug
587 * logs the event.
588 *
589 * If derived classes support characteristics with client-side notification (ie Notify, as opposed
590 * to Read or Write operations), they should implement this function to handle the successful reads of
591 * \a characteristic, typically by parsing \a value, then emitting a specialised signal.
592 */
594 const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
595{
596 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" changed to %Ln byte/s: %3)", nullptr, newValue.size())
597 .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
598}
599
600/// \endcond
601
Declares the AbstractPokitService class.
Declares the AbstractPokitServicePrivate class.
The AbstractPokitServicePrivate class provides private implementation for AbstractPokitService.
bool autoDiscover
Whether autodiscovery is enabled or not.
bool disableCharacteristicNotificatons(const QBluetoothUuid &uuid)
Disables client (Pokit device) side notification for characteristic uuid.
bool createServiceObject()
Creates an internal service object from the internal controller.
void discoveryFinished()
Handles QLowEnergyController::discoveryFinished events.
QBluetoothUuid serviceUuid
UUIDs for service.
virtual void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
Handles QLowEnergyService::characteristicChanged events.
virtual void serviceDiscovered(const QBluetoothUuid &newService)
Handles QLowEnergyController::serviceDiscovered events.
AbstractPokitService * q_ptr
Internal q-pointer.
AbstractPokitServicePrivate(const QBluetoothUuid &serviceUuid, QLowEnergyController *controller, AbstractPokitService *const q)
bool enableCharacteristicNotificatons(const QBluetoothUuid &uuid)
Enables client (Pokit device) side notification for characteristic uuid.
virtual void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
Handles QLowEnergyService::characteristicRead events.
void connected()
Handles QLowEnergyController::connected events.
QLowEnergyCharacteristic getCharacteristic(const QBluetoothUuid &uuid) const
Get uuid characteristic from the underlying service.
virtual void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
Handles QLowEnergyService::characteristicWritten events.
void errorOccurred(const QLowEnergyService::ServiceError newError)
Handles QLowEnergyController::errorOccurred events.
void stateChanged(QLowEnergyService::ServiceState newState)
Handles QLowEnergyController::stateChanged events.
bool readCharacteristic(const QBluetoothUuid &uuid)
Read the uuid characteristic.
QLowEnergyService * service
BLE service to read/write characteristics.
QLowEnergyController * controller
BLE controller to fetch the service from.
static QString toHexString(const QByteArray &data, const int maxSize=20)
Returns up to maxSize bytes of data as a human readable hexadecimal string.
static bool checkSize(const QString &label, const QByteArray &data, const int minSize, const int maxSize=-1, const bool failOnMax=false)
Returns false if data is smaller than minSize, otherwise returns failOnMax if data is bigger than max...
std::optional< PokitProduct > pokitProduct
The Pokit product controller is connected to.
The AbstractPokitService class provides a common base for Pokit services classes.
std::optional< PokitProduct > pokitProduct() const
Returns the Pokit product this service is attached to.
bool autoDiscover() const
Returns true if autodiscovery of services and service details is enabled, false otherwise.
QLowEnergyService * service()
Returns a non-const pointer to the internal service object, if any.
void setAutoDiscover(const bool discover=true)
If discover is true, autodiscovery will be attempted.
void setPokitProduct(const PokitProduct product)
Sets the current Pokit product.
AbstractPokitServicePrivate * d_ptr
Internal d-pointer.
virtual ~AbstractPokitService()
Destroys this AbstractPokitService object.
static QString charcteristicToString(const QBluetoothUuid &uuid)
Returns a human-readable name for the uuid characteristic, or a null QString if unknown.
static QString serviceToString(const QBluetoothUuid &uuid)
Returns a human-readable name for the uuid service, or a null QString if unknown.
Declares the PokitDevice class.
PokitProduct
Pokit products known to, and supported by, the QtPokit library.
QTPOKIT_EXPORT QString toString(const PokitProduct product)
Returns product as user-friendly string.
QByteArray fromHex(const QByteArray &hexEncoded)
QByteArray left(int len) const const
QByteArray mid(int pos, int len) const const
QByteArray right(int len) const const
int size() const const
QByteArray toHex() const const
QLowEnergyDescriptor descriptor(const QBluetoothUuid &uuid) const const
bool isValid() const const
QBluetoothUuid uuid() const const
void serviceDiscovered(const QBluetoothUuid &newService)
bool isValid() const const
QString name() const const
QBluetoothUuid uuid() const const
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
void descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &value)
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
QLowEnergyService::ServiceError error() const const
void stateChanged(QLowEnergyService::ServiceState newState)
QObject(QObject *parent)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QObject * sender() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QString join(const QString &separator) const const
#define QTPOKIT_BEGIN_NAMESPACE
Macro for starting the QtPokit library's top-most namespace (if one is defined).
#define QTPOKIT_END_NAMESPACE
Macro for ending the QtPokit library's top-most namespace (if one is defined).
QString toString() const const
Declares the DOKIT_USE_STRINGLITERALS macro, and related functions.
#define DOKIT_USE_STRINGLITERALS
Internal macro for using either official Qt string literals (added in Qt 6.4), or our own equivalent ...