Kstars

media.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 Media Channel
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "media.h"
10#include "commands.h"
11#include "skymapcomposite.h"
12#include "fitsviewer/fitsview.h"
13#include "fitsviewer/fitsdata.h"
14#include "indi/indilistener.h"
15#include "hips/hipsfinder.h"
16#include "kstarsdata.h"
17#include "ekos/auxiliary/darklibrary.h"
18#include "ekos/guide/guide.h"
19#include "ekos/align/align.h"
20#include "ekos/capture/capture.h"
21#include "ekos/capture/captureprocess.h"
22#include "kspaths.h"
23#include "Options.h"
24
25#include "ekos_debug.h"
26#include "kstars.h"
27#include "version.h"
28
29#include <QtConcurrent>
30#include <KFormat>
31
32namespace EkosLive
33{
34
35///////////////////////////////////////////////////////////////////////////////////////////
36///
37///////////////////////////////////////////////////////////////////////////////////////////
38Media::Media(Ekos::Manager * manager, QVector<QSharedPointer<NodeManager>> &nodeManagers):
39 m_Manager(manager), m_NodeManagers(nodeManagers)
40{
41 for (auto &nodeManager : m_NodeManagers)
42 {
43 connect(nodeManager->media(), &Node::connected, this, &Media::onConnected);
44 connect(nodeManager->media(), &Node::disconnected, this, &Media::onDisconnected);
45 connect(nodeManager->media(), &Node::onTextReceived, this, &Media::onTextReceived);
46 connect(nodeManager->media(), &Node::onBinaryReceived, this, &Media::onBinaryReceived);
47 }
48
49 connect(this, &Media::newMetadata, this, &Media::uploadMetadata);
50 connect(this, &Media::newImage, this, [this](const QByteArray & image)
51 {
52 uploadImage(image);
53 m_TemporaryView.clear();
54 });
55}
56
57///////////////////////////////////////////////////////////////////////////////////////////
58///
59///////////////////////////////////////////////////////////////////////////////////////////
60bool Media::isConnected() const
61{
62 return std::any_of(m_NodeManagers.begin(), m_NodeManagers.end(), [](auto & nodeManager)
63 {
64 return nodeManager->media()->isConnected();
65 });
66}
67
68///////////////////////////////////////////////////////////////////////////////////////////
69///
70///////////////////////////////////////////////////////////////////////////////////////////
71void Media::onConnected()
72{
73 auto node = qobject_cast<Node*>(sender());
74 if (!node)
75 return;
76
77 qCInfo(KSTARS_EKOS) << "Connected to Media Websocket server at" << node->url().toDisplayString();
78
79 emit connected();
80}
81
82///////////////////////////////////////////////////////////////////////////////////////////
83///
84///////////////////////////////////////////////////////////////////////////////////////////
85void Media::onDisconnected()
86{
87 auto node = qobject_cast<Node*>(sender());
88 if (!node)
89 return;
90
91 qCInfo(KSTARS_EKOS) << "Disconnected from Message Websocket server at" << node->url().toDisplayString();
92
93 if (isConnected() == false)
94 {
95 m_sendBlobs = true;
96
97 for (const QString &oneFile : temporaryFiles)
99 temporaryFiles.clear();
100
101 emit disconnected();
102 }
103}
104
105///////////////////////////////////////////////////////////////////////////////////////////
106///
107///////////////////////////////////////////////////////////////////////////////////////////
108void Media::onTextReceived(const QString &message)
109{
110 qCInfo(KSTARS_EKOS) << "Media Text Websocket Message" << message;
112 auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error);
113 if (error.error != QJsonParseError::NoError)
114 {
115 qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
116 return;
117 }
118
119 const QJsonObject msgObj = serverMessage.object();
120 const QString command = msgObj["type"].toString();
121 const QJsonObject payload = msgObj["payload"].toObject();
122
123 if (command == commands[ALIGN_SET_FILE_EXTENSION])
124 extension = payload["ext"].toString();
125 else if (command == commands[SET_BLOBS])
126 m_sendBlobs = msgObj["payload"].toBool();
127 // Get a list of object based on criteria
128 else if (command == commands[ASTRO_GET_OBJECTS_IMAGE])
129 {
130 int level = payload["level"].toInt(5);
131 double zoom = payload["zoom"].toInt(20000);
132 bool exact = payload["exact"].toBool(false);
133
134 // Object Names
135 QVariantList objectNames = payload["names"].toArray().toVariantList();
136
137 for (auto &oneName : objectNames)
138 {
139 const QString name = oneName.toString();
140 SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, exact);
141 if (oneObject)
142 {
143 QImage centerImage(HIPS_TILE_WIDTH, HIPS_TILE_HEIGHT, QImage::Format_ARGB32_Premultiplied);
144 double fov_w = 0, fov_h = 0;
145
146 if (oneObject->type() == SkyObject::MOON || oneObject->type() == SkyObject::PLANET)
147 {
149 const QString output = KSPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + "xplanet.jpg";
150 xplanetProcess.start(Options::xplanetPath(), QStringList()
151 << "--num_times" << "1"
152 << "--geometry" << QString("%1x%2").arg(HIPS_TILE_WIDTH).arg(HIPS_TILE_HEIGHT)
153 << "--body" << name.toLower()
154 << "--output" << output);
155 xplanetProcess.waitForFinished(5000);
156 centerImage.load(output);
157 }
158 else
159 HIPSFinder::Instance()->render(oneObject, level, zoom, &centerImage, fov_w, fov_h);
160
161 if (!centerImage.isNull())
162 {
163 // Use seed from name, level, and zoom so that it is unique
164 // even if regenerated again.
165 auto seed = QString("%1%2%3").arg(QString::number(level), QString::number(zoom), name);
166 QString uuid = "hips_" + QCryptographicHash::hash(seed.toLatin1(), QCryptographicHash::Md5).toHex();
167 // Send everything as strings
168 QJsonObject metadata =
169 {
170 {"uuid", uuid},
171 {"name", exact ? name : oneObject->name()},
172 {"zoom", zoom},
173 {"resolution", QString("%1x%2").arg(HIPS_TILE_WIDTH).arg(HIPS_TILE_HEIGHT)},
174 {"bin", "1x1"},
175 {"fov_w", QString::number(fov_w)},
176 {"fov_h", QString::number(fov_h)},
177 {"ext", "jpg"}
178 };
179
181 QBuffer buffer(&jpegData);
182 buffer.open(QIODevice::WriteOnly);
183
184 // First METADATA_PACKET bytes of the binary data is always allocated
185 // to the metadata, the rest to the image data.
187 meta = meta.leftJustified(METADATA_PACKET, 0);
188 buffer.write(meta);
189 centerImage.save(&buffer, "jpg", 90);
190 buffer.close();
191
192 emit newImage(jpegData);
193 }
194 }
195 }
196 }
197 else if (command == commands[ASTRO_GET_SKYPOINT_IMAGE])
198 {
199 int level = payload["level"].toInt(5);
200 double zoom = payload["zoom"].toInt(20000);
201 double ra = payload["ra"].toDouble(0);
202 double de = payload["de"].toDouble(0);
203 double width = payload["width"].toDouble(512);
204 double height = payload["height"].toDouble(512);
205
207 SkyPoint coords(ra, de);
208 SkyPoint J2000Coord(coords.ra(), coords.dec());
209 J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd());
210 coords.setRA0(J2000Coord.ra());
211 coords.setDec0(J2000Coord.dec());
212 coords.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
213
214 volatile auto jnowRAString = coords.ra().toHMSString();
215 volatile auto jnowDEString = coords.dec().toDMSString();
216 volatile auto j2000RAString = coords.ra0().toHMSString();
217 volatile auto j2000DEString = coords.dec0().toDMSString();
218
219
220 double fov_w = 0, fov_h = 0;
221 HIPSFinder::Instance()->render(&coords, level, zoom, &centerImage, fov_w, fov_h);
222
223 if (!centerImage.isNull())
224 {
225 // Use seed from name, level, and zoom so that it is unique
226 // even if regenerated again.
227 // Send everything as strings
228 QJsonObject metadata =
229 {
230 {"uuid", "skypoint_hips"},
231 {"name", "skypoint_hips"},
232 {"zoom", zoom},
233 {"resolution", QString("%1x%2").arg(width).arg(height)},
234 {"bin", "1x1"},
235 {"fov_w", QString::number(fov_w)},
236 {"fov_h", QString::number(fov_h)},
237 {"ext", "jpg"}
238 };
239
241 QBuffer buffer(&jpegData);
242 buffer.open(QIODevice::WriteOnly);
243
244 // First METADATA_PACKET bytes of the binary data is always allocated
245 // to the metadata, the rest to the image data.
247 meta = meta.leftJustified(METADATA_PACKET, 0);
248 buffer.write(meta);
249 centerImage.save(&buffer, "jpg", 95);
250 buffer.close();
251
252 emit newImage(jpegData);
253 }
254 }
255}
256
257///////////////////////////////////////////////////////////////////////////////////////////
258///
259///////////////////////////////////////////////////////////////////////////////////////////
260void Media::onBinaryReceived(const QByteArray &message)
261{
262 // Sometimes this is triggered even though it's a text message
263 Ekos::Align * align = m_Manager->alignModule();
264 if (align)
265 {
266 QString metadataString = message.left(METADATA_PACKET);
269 QString extension = metadataJSON.value("ext").toString();
270 align->loadAndSlew(message.mid(METADATA_PACKET), extension);
271 }
272}
273
274///////////////////////////////////////////////////////////////////////////////////////////
275///
276///////////////////////////////////////////////////////////////////////////////////////////
277void Media::sendDarkLibraryData(const QSharedPointer<FITSData> &data)
278{
279 sendData(data, "+D");
280};
281
282///////////////////////////////////////////////////////////////////////////////////////////
283///
284///////////////////////////////////////////////////////////////////////////////////////////
285void Media::sendData(const QSharedPointer<FITSData> &data, const QString &uuid)
286{
287 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false)
288 return;
289
290 m_UUID = uuid;
291
292 m_TemporaryView.reset(new FITSView());
293 m_TemporaryView->loadData(data);
294 QtConcurrent::run(this, &Media::upload, m_TemporaryView);
295}
296
297///////////////////////////////////////////////////////////////////////////////////////////
298///
299///////////////////////////////////////////////////////////////////////////////////////////
300void Media::sendFile(const QString &filename, const QString &uuid)
301{
302 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false)
303 return;
304
305 m_UUID = uuid;
306
307 QSharedPointer<FITSView> previewImage(new FITSView());
308 connect(previewImage.get(), &FITSView::loaded, this, [this, previewImage]()
309 {
310 QtConcurrent::run(this, &Media::upload, previewImage);
311 });
312 previewImage->loadFile(filename);
313}
314
315///////////////////////////////////////////////////////////////////////////////////////////
316///
317///////////////////////////////////////////////////////////////////////////////////////////
318void Media::sendView(const QSharedPointer<FITSView> &view, const QString &uuid)
319{
320 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false)
321 return;
322
323 m_UUID = uuid;
324
325 upload(view);
326}
327
328///////////////////////////////////////////////////////////////////////////////////////////
329///
330///////////////////////////////////////////////////////////////////////////////////////////
331void Media::upload(const QSharedPointer<FITSView> &view)
332{
333 const QString ext = "jpg";
335 QBuffer buffer(&jpegData);
336 buffer.open(QIODevice::WriteOnly);
337
338 const QSharedPointer<FITSData> imageData = view->imageData();
339 QString resolution = QString("%1x%2").arg(imageData->width()).arg(imageData->height());
340 QString sizeBytes = KFormat().formatByteSize(imageData->size());
341 QVariant xbin(1), ybin(1), exposure(0), focal_length(0), gain(0), pixel_size(0), aperture(0);
342 imageData->getRecordValue("XBINNING", xbin);
343 imageData->getRecordValue("YBINNING", ybin);
344 imageData->getRecordValue("EXPTIME", exposure);
345 imageData->getRecordValue("GAIN", gain);
346 imageData->getRecordValue("PIXSIZE1", pixel_size);
347 imageData->getRecordValue("FOCALLEN", focal_length);
348 imageData->getRecordValue("APTDIA", aperture);
349
350 auto stretchParameters = view->getStretchParams();
351
352 // Account for binning
353 const double binned_pixel = pixel_size.toDouble() * xbin.toInt();
354 // Send everything as strings
355 QJsonObject metadata =
356 {
357 {"resolution", resolution},
358 {"size", sizeBytes},
359 {"channels", imageData->channels()},
360 {"mean", imageData->getAverageMean()},
361 {"median", imageData->getAverageMedian()},
362 {"stddev", imageData->getAverageStdDev()},
363 {"min", imageData->getMin()},
364 {"max", imageData->getMax()},
365 {"bin", QString("%1x%2").arg(xbin.toString(), ybin.toString())},
366 {"bpp", QString::number(imageData->bpp())},
367 {"uuid", m_UUID},
368 {"exposure", exposure.toString()},
369 {"focal_length", focal_length.toString()},
370 {"aperture", aperture.toString()},
371 {"gain", gain.toString()},
372 {"pixel_size", QString::number(binned_pixel, 'f', 4)},
373 {"shadows", stretchParameters.grey_red.shadows},
374 {"midtones", stretchParameters.grey_red.midtones},
375 {"highlights", stretchParameters.grey_red.highlights},
376 {"hasWCS", imageData->hasWCS()},
377 {"hfr", imageData->getHFR()},
378 {"view", view->objectName()},
379 {"ext", ext}
380 };
381
382 // First METADATA_PACKET bytes of the binary data is always allocated
383 // to the metadata
384 // the rest to the image data.
386 meta = meta.leftJustified(METADATA_PACKET, 0);
387 buffer.write(meta);
388
389 auto fastImage = (!Options::ekosLiveHighBandwidth() || m_UUID[0] == "+");
390 auto scaleWidth = fastImage ? HB_IMAGE_WIDTH / 2 : HB_IMAGE_WIDTH;
391
392 // For low bandwidth images
393 // Except for dark frames +D
394 QPixmap scaledImage = view->getDisplayPixmap().width() > scaleWidth ?
395 view->getDisplayPixmap().scaledToWidth(scaleWidth, fastImage ? Qt::FastTransformation : Qt::SmoothTransformation) :
396 view->getDisplayPixmap();
397 scaledImage.save(&buffer, ext.toLatin1().constData(), HB_IMAGE_QUALITY);
398
399 buffer.close();
400
401 emit newImage(jpegData);
402}
403
404///////////////////////////////////////////////////////////////////////////////////////////
405///
406///////////////////////////////////////////////////////////////////////////////////////////
407void Media::sendUpdatedFrame(const QSharedPointer<FITSView> &view)
408{
409 QString ext = "jpg";
411 QBuffer buffer(&jpegData);
412 buffer.open(QIODevice::WriteOnly);
413
414 const QSharedPointer<FITSData> imageData = view->imageData();
415
416 if (!imageData)
417 return;
418
419 const int32_t width = imageData->width();
420 const int32_t height = imageData->height();
421 QString resolution = QString("%1x%2").arg(width).arg(height);
422 QString sizeBytes = KFormat().formatByteSize(imageData->size());
423 QVariant xbin(1), ybin(1), exposure(0), focal_length(0), gain(0), pixel_size(0), aperture(0);
424 imageData->getRecordValue("XBINNING", xbin);
425 imageData->getRecordValue("YBINNING", ybin);
426 imageData->getRecordValue("EXPTIME", exposure);
427 imageData->getRecordValue("GAIN", gain);
428 imageData->getRecordValue("PIXSIZE1", pixel_size);
429 imageData->getRecordValue("FOCALLEN", focal_length);
430 imageData->getRecordValue("APTDIA", aperture);
431
432 // Account for binning
433 const double binned_pixel = pixel_size.toDouble() * xbin.toInt();
434 // Send everything as strings
435 QJsonObject metadata =
436 {
437 {"resolution", resolution},
438 {"size", sizeBytes},
439 {"channels", imageData->channels()},
440 {"mean", imageData->getAverageMean()},
441 {"median", imageData->getAverageMedian()},
442 {"stddev", imageData->getAverageStdDev()},
443 {"bin", QString("%1x%2").arg(xbin.toString()).arg(ybin.toString())},
444 {"bpp", QString::number(imageData->bpp())},
445 {"uuid", "+A"},
446 {"exposure", exposure.toString()},
447 {"focal_length", focal_length.toString()},
448 {"aperture", aperture.toString()},
449 {"gain", gain.toString()},
450 {"pixel_size", QString::number(binned_pixel, 'f', 4)},
451 {"ext", ext}
452 };
453
454 // First METADATA_PACKET bytes of the binary data is always allocated
455 // to the metadata
456 // the rest to the image data.
458 meta = meta.leftJustified(METADATA_PACKET, 0);
459 buffer.write(meta);
460
461 // For low bandwidth images
463 // Align images
464 if (correctionVector.isNull() == false)
465 {
466 scaledImage = view->getDisplayPixmap();
467 const double currentZoom = view->getCurrentZoom();
468 const double normalizedZoom = currentZoom / 100;
469 // If zoom level is not 100%, then scale.
470 if (fabs(normalizedZoom - 1) > 0.001)
471 scaledImage = view->getDisplayPixmap().scaledToWidth(view->zoomedWidth());
472 else
473 scaledImage = view->getDisplayPixmap();
474 // as we factor in the zoom level, we adjust center and length accordingly
475 QPointF center = 0.5 * correctionVector.p1() * normalizedZoom + 0.5 * correctionVector.p2() * normalizedZoom;
476 uint32_t length = qMax(correctionVector.length() / normalizedZoom, 100 / normalizedZoom);
477
479 boundingRectable.setSize(QSize(length * 2, length * 2));
480 QPoint topLeft = (center - QPointF(length, length)).toPoint();
481 boundingRectable.moveTo(topLeft);
482 boundingRectable = boundingRectable.intersected(scaledImage.rect());
483
484 emit newBoundingRect(boundingRectable, scaledImage.size(), currentZoom);
485
487 }
488 else
489 {
490 scaledImage = view->getDisplayPixmap().width() > HB_IMAGE_WIDTH / 2 ?
491 view->getDisplayPixmap().scaledToWidth(HB_IMAGE_WIDTH / 2, Qt::FastTransformation) :
492 view->getDisplayPixmap();
493 emit newBoundingRect(QRect(), QSize(), 100);
494 }
495
496 scaledImage.save(&buffer, ext.toLatin1().constData(), HB_IMAGE_QUALITY);
497 buffer.close();
498 emit newImage(jpegData);
499}
500
501///////////////////////////////////////////////////////////////////////////////////////////
502///
503///////////////////////////////////////////////////////////////////////////////////////////
504void Media::sendVideoFrame(const QSharedPointer<QImage> &frame)
505{
506 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false || !frame)
507 return;
508
509 int32_t width = Options::ekosLiveHighBandwidth() ? HB_VIDEO_WIDTH : HB_VIDEO_WIDTH / 2;
510 QByteArray image;
511 QBuffer buffer(&image);
512 buffer.open(QIODevice::WriteOnly);
513
514 QImage videoImage = (frame->width() > width) ? frame->scaledToWidth(width) : *frame;
515
516 QString resolution = QString("%1x%2").arg(videoImage.width()).arg(videoImage.height());
517
518 // First METADATA_PACKET bytes of the binary data is always allocated
519 // to the metadata
520 // the rest to the image data.
521 QJsonObject metadata =
522 {
523 {"resolution", resolution},
524 {"ext", "jpg"}
525 };
527 meta = meta.leftJustified(METADATA_PACKET, 0);
528 buffer.write(meta);
529
530 QImageWriter writer;
531 writer.setDevice(&buffer);
532 writer.setFormat("JPG");
533 writer.setCompression(6);
534 writer.write(videoImage);
535 buffer.close();
536
537 for (auto &nodeManager : m_NodeManagers)
538 {
539 nodeManager->media()->sendBinaryMessage(image);
540 }
541}
542
543///////////////////////////////////////////////////////////////////////////////////////////
544///
545///////////////////////////////////////////////////////////////////////////////////////////
546void Media::registerCameras()
547{
548 static const QRegularExpression re("[-{}]");
549 for(auto &oneDevice : INDIListener::devices())
550 {
551 auto camera = oneDevice->getCamera();
552 if (camera)
553 {
554 camera->disconnect(this);
555 connect(camera, &ISD::Camera::newVideoFrame, this, &Media::sendVideoFrame);
556 }
557 }
558
559 auto captureModule = m_Manager->captureModule();
560 if (!captureModule)
561 return;
562
563 auto process = captureModule->process().get();
564 process->disconnect(this);
565 connect(process, &Ekos::CaptureProcess::newView, this, [this](const QSharedPointer<FITSView> &view)
566 {
567 // Set UUID for each view
569 uuid = uuid.remove(re);
570 sendView(view, uuid);
571 });
572}
573
574void Media::resetPolarView()
575{
576 this->correctionVector = QLineF();
577 m_Manager->alignModule()->zoomAlignView();
578}
579
580void Media::uploadMetadata(const QByteArray &metadata)
581{
582 for (auto &nodeManager : m_NodeManagers)
583 {
584 nodeManager->media()->sendTextMessage(metadata);
585 }
586}
587
588void Media::uploadImage(const QByteArray &image)
589{
590 for (auto &nodeManager : m_NodeManagers)
591 {
592 nodeManager->media()->sendBinaryMessage(image);
593 }
594}
595
596void Media::processNewBLOB(IBLOB * bp)
597{
598 Q_UNUSED(bp)
599}
600
601void Media::sendModuleFrame(const QSharedPointer<FITSView> &view)
602{
603 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false)
604 return;
605
606 if (qobject_cast<Ekos::Align*>(sender()) == m_Manager->alignModule())
607 sendView(view, "+A");
608 else if (qobject_cast<Ekos::Focus*>(sender()) == m_Manager->focusModule())
609 sendView(view, "+F");
610 else if (qobject_cast<Ekos::Guide*>(sender()) == m_Manager->guideModule())
611 sendView(view, "+G");
612 else if (qobject_cast<Ekos::DarkLibrary*>(sender()) == Ekos::DarkLibrary::Instance())
613 sendView(view, "+D");
614}
615}
Align class handles plate-solving and polar alignment measurement and correction using astrometry....
Definition align.h:75
bool loadAndSlew(const QByteArray &image, const QString &extension)
DBUS interface function.
Definition align.cpp:3072
INDIListener is responsible for creating ISD::GDInterface generic devices as new devices arrive from ...
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
static KStars * Instance()
Definition kstars.h:123
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:42
The sky coordinates of a point in the sky.
Definition skypoint.h:45
bool remove(const QString &column, const QVariant &value)
Generic record interfaces and implementations.
Definition cloud.cpp:23
GeoCoordinates geo(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QStringView level(QStringView ifopt)
QString name(StandardAction id)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
QByteArray left(qsizetype len) const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray toHex(char separator) const const
QByteArray hash(QByteArrayView data, Algorithm method)
QChar separator()
Int toInt() const const
Format_ARGB32_Premultiplied
void setCompression(int compression)
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
bool write(const QImage &image)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QByteArray toJson(JsonFormat format) const const
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QByteArray toLatin1() const const
QString toLower() const const
FastTransformation
QTextStream & center(QTextStream &stream)
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUuid createUuid()
QString toString(StringFormat mode) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:48:25 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.