Dokit
Internal development documentation
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
abstractpokitservice.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2022-2025 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 * \param uuid
267 * \return
268 */
270{
271 if (!service) {
272 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service assigned.)")
275 }
276
277 if (const QLowEnergyCharacteristic characteristic = service->characteristic(uuid); characteristic.isValid()) {
278 return characteristic;
279 }
280
281 if (service->state() != QLowEnergyService::
282 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
283 ServiceDiscovered
284 #else
285 RemoteServiceDiscovered
286 #endif
287 ) {
288 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service %3 "%4" discovered.)")
290 service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
291 qCInfo(lc).noquote() << tr("Current service state:") << service->state();
293 }
294
295 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" not found in service %3 "%4".)")
297 service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
299}
300
301/*!
302 * Read the \a uuid characteristic.
303 *
304 * If successful, the `QLowEnergyService::characteristicRead` signal will be emitted by the internal
305 * service object. For convenience, derived classes should implement the characteristicRead()
306 * virtual function to handle the read value.
307 *
308 * Returns \c true if the characteristic read request was successfully queued, \c false otherwise.
309 *
310 * \see AbstractPokitService::readCharacteristics()
311 * \see AbstractPokitServicePrivate::characteristicRead()
312 */
314{
315 const QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
316 if (!characteristic.isValid()) {
317 return false;
318 }
319 qCDebug(lc).noquote() << tr(R"(Reading characteristic %1 "%2".)")
321 service->readCharacteristic(characteristic);
322 return true;
323}
324
325/*!
326 * Enables client (Pokit device) side notification for characteristic \a uuid.
327 *
328 * Returns \c true if the notification enable request was successfully queued, \c false otherwise.
329 *
330 * \see AbstractPokitServicePrivate::characteristicChanged
331 * \see AbstractPokitServicePrivate::disableCharacteristicNotificatons
332 */
334{
335 qCDebug(lc).noquote() << tr(R"(Enabling CCCD for characteristic %1 "%2".)")
337 QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
338 if (!characteristic.isValid()) {
339 return false;
340 }
341
342 QLowEnergyDescriptor descriptor = characteristic.descriptor(
343 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); // 0x2902
344 if (!descriptor.isValid()) {
345 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" has no client configuration descriptor.)")
347 return false;
348 }
349
350 service->writeDescriptor(descriptor,
351 #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
352 QLowEnergyCharacteristic::CCCDEnableNotification
353 #else
354 QByteArray::fromHex("0100") // See Qt6's QLowEnergyCharacteristic::CCCDEnableNotification.
355 #endif
356 );
357 return true;
358}
359
360/*!
361 * Disables client (Pokit device) side notification for characteristic \a uuid.
362 *
363 * Returns \c true if the notification disable request was successfully queued, \c false otherwise.
364 *
365 * \see AbstractPokitServicePrivate::characteristicChanged
366 * \see AbstractPokitServicePrivate::enableCharacteristicNotificatons
367 */
369{
370 qCDebug(lc).noquote() << tr(R"(Disabling CCCD for characteristic %1 "%2".)")
372 QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
373 if (!characteristic.isValid()) {
374 return false;
375 }
376
377 QLowEnergyDescriptor descriptor = characteristic.descriptor(
378 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); // 0x2902
379 if (!descriptor.isValid()) {
380 qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" has no client configuration descriptor.)")
382 return false;
383 }
384
385 service->writeDescriptor(descriptor,
386 #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
387 QLowEnergyCharacteristic::CCCDDisable
388 #else
389 QByteArray::fromHex("0000") // See Qt6's QLowEnergyCharacteristic::CCCDDisable.
390 #endif
391 );
392 return true;
393}
394
395/*!
396 * Returns `false` if \a data is smaller than \a minSize, otherwise returns \a failOnMax if \a data
397 * is bigger than \a maxSize, otherwise returns `true`.
398 *
399 * A warning is logged if either \a minSize or \a maxSize is violated, regardless of the returned
400 * value; ie this funcion can be used to simply warn if \a data is too big, or it can be used to
401 * failed (return `false`) in that case.
402 */
404 const int minSize, const int maxSize,
405 const bool failOnMax)
406{
407 if (data.size() < minSize) {
408 qCWarning(lc).noquote() << tr("%1 requires %n byte/s, but only %2 present: %3", nullptr, minSize)
409 .arg(label).arg(data.size()).arg(toHexString(data));
410 return false;
411 }
412 if ((maxSize >= 0) && (data.size() > maxSize)) {
413 qCWarning(lc).noquote() << tr("%1 has %n extraneous byte/s: %2", nullptr, data.size()-maxSize)
414 .arg(label, toHexString(data.mid(maxSize)));
415 return (!failOnMax);
416 }
417 return true;
418}
419
420/*!
421 * Returns up to \a maxSize bytes of \a data as a human readable hexadecimal string. If \a data
422 * exceeds \a maxSize, then \a data is elided in the middle. For example:
423 *
424 * ```
425 * toHex(QBytArray("\x1\x2\x3\x4\x5\x6", 4); // "0x01,02,...,05,06"
426 * ```
427 */
429{
430 return (data.size() <= maxSize)
431 ? QString::fromLatin1("0x%1").arg(QLatin1String(data.toHex(',')))
432 : QString::fromLatin1("0x%1,...,%2").arg(
433 QLatin1String(data.left(maxSize/2-1).toHex(',')),
434 QLatin1String(data.right(maxSize/2-1).toHex(',')));
435}
436
437/*!
438 * Handles `QLowEnergyController::connected` events.
439 *
440 * If `autoDiscover` is enabled, this will begin service discovery on the newly connected controller.
441 *
442 * \see AbstractPokitService::autoDiscover()
443 */
445{
446 if (!controller) {
447 qCWarning(lc).noquote() << tr("Connected with no controller set") << sender();
448 return;
449 }
450
451 qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at %3.)").arg(
452 controller->remoteName(), controller->remoteDeviceUuid().toString(),
453 controller->remoteAddress().toString());
454 if (autoDiscover) {
455 controller->discoverServices();
456 }
457}
458
459/*!
460 * Handles `QLowEnergyController::discoveryFinished` events.
461 *
462 * As this event indicates that the controller has finished discovering services, this function will
463 * invoke createServiceObject() to create the internal service object (if not already created).
464 */
466{
467 if (!controller) {
468 qCWarning(lc).noquote() << tr("Discovery finished with no controller set") << sender();
469 return;
470 }
471
472 qCDebug(lc).noquote() << tr(R"(Discovery finished for "%1" (%2) at %3.)").arg(
473 controller->remoteName(), controller->remoteDeviceUuid().toString(),
474 controller->remoteAddress().toString());
475
476 if (!createServiceObject()) {
477 qCWarning(lc).noquote() << tr("Discovery finished, but service not found.");
479 Q_EMIT q->serviceErrorOccurred(QLowEnergyService::ServiceError::UnknownError);
480 }
481}
482
483/*!
484 * Handles `QLowEnergyController::errorOccurred` events.
485 *
486 * This function simply re-emits \a newError as AbstractPokitService::serviceErrorOccurred.
487 */
489{
491 qCDebug(lc).noquote() << tr("Service error") << newError;
492 Q_EMIT q->serviceErrorOccurred(newError);
493}
494
495/*!
496 * Handles `QLowEnergyController::serviceDiscovered` events.
497 *
498 * If the discovered service is the one this (or rather the derived) class wraps, then
499 * createServiceObject() will be invoked immediately (otherwise it will be invoked after full
500 * service discovery has completed, ie in discoveryFinished()).
501 */
503{
504 if ((!service) && (newService == serviceUuid)) {
505 qCDebug(lc).noquote() << tr("Service discovered") << newService;
507 }
508}
509
510/*!
511 * Handles `QLowEnergyController::stateChanged` events.
512 *
513 * If \a newState indicates that service details have now been discovered, then
514 * AbstractPokitService::serviceDetailsDiscovered will be emitted.
515 *
516 * \see AbstractPokitService::autoDiscover()
517 */
519{
520 qCDebug(lc).noquote() << tr("State changed to") << newState;
521
522 if (lc().isDebugEnabled()) {
523 for (const auto &characteristic: service->characteristics()) {
524 QStringList properties;
525 /// \cond no-doxygen
526 #define QTPOKIT_INTERNAL_TEST_AND_APPEND(property) \
527 if (characteristic.properties().testFlag(QLowEnergyCharacteristic::property)) { \
528 properties.append(QStringLiteral(#property).toLower());\
529 }
530 /// \endcond
531 QTPOKIT_INTERNAL_TEST_AND_APPEND(Broadcasting)
532 QTPOKIT_INTERNAL_TEST_AND_APPEND(Read)
533 QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteNoResponse)
534 QTPOKIT_INTERNAL_TEST_AND_APPEND(Write)
535 QTPOKIT_INTERNAL_TEST_AND_APPEND(Notify)
536 QTPOKIT_INTERNAL_TEST_AND_APPEND(Indicate)
537 QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteSigned)
538 QTPOKIT_INTERNAL_TEST_AND_APPEND(ExtendedProperty)
539 #undef QTPOKIT_INTERNAL_TEST_AND_APPEND
540 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" supports %3.)").arg(characteristic.uuid().toString(),
541 PokitDevice::charcteristicToString(characteristic.uuid()), properties.join(u", "_s));
542 }
543 }
544
545 if (newState == QLowEnergyService::
546 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
547 ServiceDiscovered
548 #else
549 RemoteServiceDiscovered
550 #endif
551 ) {
553 qCDebug(lc).noquote() << tr("Service details discovered.");
554 Q_EMIT q->serviceDetailsDiscovered();
555 }
556}
557
558/*!
559 * Handles `QLowEnergyService::characteristicRead` events. This base implementation simply debug
560 * logs the event.
561 *
562 * Derived classes should implement this function to handle the successful reads of
563 * \a characteristic, typically by parsing \a value, then emitting a specialised signal.
564 */
566 const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
567{
568 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" read %n byte/s: %3)", nullptr, value.size()).arg(
569 characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(value));
570}
571
572/*!
573 * Handles `QLowEnergyService::characteristicWritten` events. This base implementation simply debug
574 * logs the event.
575 *
576 * Derived classes should implement this function to handle the successful writes of
577 * \a characteristic, typically by parsing \a newValue, then emitting a specialised signal.
578 */
580 const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
581{
582 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" written with %Ln byte/s: %3)", nullptr, newValue.size())
583 .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
584}
585
586/*!
587 * Handles `QLowEnergyService::characteristicChanged` events. This base implementation simply debug
588 * logs the event.
589 *
590 * If derived classes support characteristics with client-side notification (ie Notify, as opposed
591 * to Read or Write operations), they should implement this function to handle the successful reads of
592 * \a characteristic, typically by parsing \a value, then emitting a specialised signal.
593 */
595 const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
596{
597 qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" changed to %Ln byte/s: %3)", nullptr, newValue.size())
598 .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
599}
600
601/// \endcond
602
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 ...