MauiKit Image Tools

exiv2extractor.cpp
1// SPDX-License-Identifier: LGPL-3.0-or-later
2
3/*
4 * Copyright (C) 2012-15 Vishesh Handa <vhanda@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#include "exiv2extractor.h"
22
23#include <QGeoAddress>
24#include <QFileInfo>
25#include <QDateTime>
26#include <QDebug>
27#include <QFile>
28
29// #include <QTextCodec>
30
31#include "geolocation/cities.h"
32#include "geolocation/city.h"
33
34Exiv2Extractor::Exiv2Extractor(const QUrl &url, QObject *parent) : QObject(parent)
35, m_error(true)
36
37{
38 this->setUrl(url);
39}
40
41Exiv2Extractor::Exiv2Extractor(QObject *parent) : QObject(parent)
42, m_error(true)
43
44{
45}
46
47void Exiv2Extractor::setUrl(const QUrl &url)
48{
49 qDebug() << "Start parsing image file url for metadata";
50 m_url = url;
51 if (!QFileInfo::exists(m_url.toLocalFile()) || m_url.isEmpty() || !m_url.isValid()) {
52 qDebug() << "Image file is not valid or does not exists.";
53 return;
54 }
55
56 try {
57 m_image = Exiv2::ImageFactory::open(m_url.toLocalFile().toStdString());
58 } catch (const std::exception &) {
59 qDebug() << "Failed to open image to extract metadata information.";
60 return;
61 }
62
63 if (!m_image.get()) {
64 qDebug() << "Image can not be accessed.";
65 return;
66 }
67
68 if (!m_image->good()) {
69 qDebug() << "Image is not good.";
70 return;
71 }
72
73 try {
74 m_image->readMetadata();
75 } catch (const std::exception &) {
76 qDebug() << "Can not read metadta from the image.";
77 return;
78 }
79
80 m_error = false;
81}
82
83
84Exiv2::ExifData & Exiv2Extractor::exifData() const
85{
86 Exiv2::ExifData &exifData = m_image->exifData();
87// if (exifData.empty()) {
88// qWarning() << "No EXIF data in : " << m_url.toString();
89// }
90
91 return exifData;
92}
93
94Coordinates Exiv2Extractor::extractGPS() const
95{
96 double latitude = fetchGpsDouble("Exif.GPSInfo.GPSLatitude");
97 double longitude = fetchGpsDouble("Exif.GPSInfo.GPSLongitude");
98
99 QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
100 if (!latRef.isEmpty() && latRef[0] == 'S')
101 latitude *= -1;
102
103 QByteArray longRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
104 if (!longRef.isEmpty() && longRef[0] == 'W')
105 longitude *= -1;
106
107 return {latitude, longitude};
108}
109
110double Exiv2Extractor::fetchGpsDouble(const char *name) const
111{
112 Exiv2::ExifData &data = (exifData());
113 Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name));
114 if (it != data.end() && it->count() == 3) {
115 double n = 0.0;
116 double d = 0.0;
117
118 n = (*it).toRational(0).first;
119 d = (*it).toRational(0).second;
120
121 if (d == 0) {
122 return 0.0;
123 }
124
125 double deg = n / d;
126
127 n = (*it).toRational(1).first;
128 d = (*it).toRational(1).second;
129
130 if (d == 0) {
131 return deg;
132 }
133
134 double min = n / d;
135 if (min != -1.0) {
136 deg += min / 60.0;
137 }
138
139 n = (*it).toRational(2).first;
140 d = (*it).toRational(2).second;
141
142 if (d == 0) {
143 return deg;
144 }
145
146 double sec = n / d;
147 if (sec != -1.0) {
148 deg += sec / 3600.0;
149 }
150
151 return deg;
152 }
153
154 return 0.0;
155}
156
157bool Exiv2Extractor::error() const
158{
159 return m_error;
160}
161
162QString Exiv2Extractor::getExifTagString(const char* exifTagName, bool escapeCR) const
163{
164 try
165 {
166 Exiv2::ExifKey exifKey(exifTagName);
167 Exiv2::ExifData &data = (exifData());
168 Exiv2::ExifData::iterator it = data.findKey(exifKey);
169
170
171 if (it != data.end())
172 {
173 // See B.K.O #184156 comment #13
174 std::string val = it->print(&data);
176
177 if (escapeCR)
179
180 return tagValue;
181 }
182 }
183 catch( Exiv2::Error& e )
184 {
185 qWarning() << QString("Cannot find Exif key '%1' into image using Exiv2 ").arg(QString::fromLatin1(exifTagName)) << e.what();
186 }
187 catch(...)
188 {
189 qWarning() << "Default exception from Exiv2";
190 }
191
192 return QString();
193}
194
195QByteArray Exiv2Extractor::getExifTagData(const char* exifTagName) const
196{
197 try
198 {
199 Exiv2::ExifKey exifKey(exifTagName);
200 Exiv2::ExifData &data = (exifData());
201 Exiv2::ExifData::iterator it = data.findKey(exifKey);
202
203 if (it != data.end())
204 {
205 char* const s = new char[(*it).size()];
206 (*it).copy((Exiv2::byte*)s, Exiv2::bigEndian);
207 QByteArray data(s, (*it).size());
208 delete[] s;
209
210 return data;
211 }
212 }
213 catch( Exiv2::Error& e )
214 {
215 qWarning() << QString("Cannot find Exif key '%1' into image using Exiv2 ").arg(QString::fromLatin1(exifTagName)) << e.what();
216 }
217 catch(...)
218 {
219 qWarning() << "Default exception from Exiv2";
220 }
221
222 return QByteArray();
223}
224
225QVariant Exiv2Extractor::getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts, bool stringEscapeCR, int component) const
226{
227 try
228 {
229 Exiv2::ExifKey exifKey(exifTagName);
230 Exiv2::ExifData &data = (exifData());
231 Exiv2::ExifData::iterator it = data.findKey(exifKey);
232
233 if (it != data.end())
234 {
235 switch (it->typeId())
236 {
237 case Exiv2::unsignedByte:
238 case Exiv2::unsignedShort:
239 case Exiv2::unsignedLong:
240 case Exiv2::signedShort:
241 case Exiv2::signedLong:
242 if (it->count() > component)
243 return QVariant((int)it->toFloat(component));
244 else
245 return QVariant(QVariant::Int);
246 case Exiv2::unsignedRational:
247 case Exiv2::signedRational:
248
249 if (rationalAsListOfInts)
250 {
251 if (it->count() <= component)
252 return QVariant(QVariant::List);
253
255 list << (*it).toRational(component).first;
256 list << (*it).toRational(component).second;
257
258 return QVariant(list);
259 }
260 else
261 {
262 if (it->count() <= component)
264
265 // prefer double precision
266 double num = (*it).toRational(component).first;
267 double den = (*it).toRational(component).second;
268
269 if (den == 0.0)
271
272 return QVariant(num / den);
273 }
274 case Exiv2::date:
275 case Exiv2::time:
276 {
277 QDateTime dateTime = QDateTime::fromString(QString::fromLatin1(it->toString().c_str()), Qt::ISODate);
278 return QVariant(dateTime);
279 }
280 case Exiv2::asciiString:
281 case Exiv2::comment:
282 case Exiv2::string:
283 {
284 std::ostringstream os;
285 os << *it;
286 QString tagValue = QString::fromLocal8Bit(os.str().c_str());
287
288 if (stringEscapeCR)
290
291 return QVariant(tagValue);
292 }
293 default:
294 break;
295 }
296 }
297 }
298 catch( Exiv2::Error& e )
299 {
300 qWarning () << QString("Cannot find Exif key '%1' in the image using Exiv2 ").arg(QString::fromLatin1(exifTagName)) << e.what();
301 }
302 catch(...)
303 {
304 qWarning() << "Default exception from Exiv2";
305 }
306
307 return QVariant();
308}
309
310static bool isUtf8(const char* const buffer)
311{
312 int i, n;
313 unsigned char c;
314 bool gotone = false;
315
316 if (!buffer)
317 return true;
318
319 // character never appears in text
320 #define F 0
321 // character appears in plain ASCII text
322 #define T 1
323 // character appears in ISO-8859 text
324 #define I 2
325 // character appears in non-ISO extended ASCII (Mac, IBM PC)
326 #define X 3
327
328 static const unsigned char text_chars[256] =
329 {
330 // BEL BS HT LF FF CR
331 F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, // 0x0X
332 // ESC
333 F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, // 0x1X
334 T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x2X
335 T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x3X
336 T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x4X
337 T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x5X
338 T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x6X
339 T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, // 0x7X
340 // NEL
341 X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, // 0x8X
342 X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, // 0x9X
343 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xaX
344 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xbX
345 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xcX
346 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xdX
347 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xeX
348 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I // 0xfX
349 };
350
351 for (i = 0; (c = buffer[i]); ++i)
352 {
353 if ((c & 0x80) == 0)
354 {
355 // 0xxxxxxx is plain ASCII
356
357 // Even if the whole file is valid UTF-8 sequences,
358 // still reject it if it uses weird control characters.
359
360 if (text_chars[c] != T)
361 return false;
362
363 }
364 else if ((c & 0x40) == 0)
365 {
366 // 10xxxxxx never 1st byte
367 return false;
368 }
369 else
370 {
371 // 11xxxxxx begins UTF-8
372 int following = 0;
373
374 if ((c & 0x20) == 0)
375 {
376 // 110xxxxx
377 following = 1;
378 }
379 else if ((c & 0x10) == 0)
380 {
381 // 1110xxxx
382 following = 2;
383 }
384 else if ((c & 0x08) == 0)
385 {
386 // 11110xxx
387 following = 3;
388 }
389 else if ((c & 0x04) == 0)
390 {
391 // 111110xx
392 following = 4;
393 }
394 else if ((c & 0x02) == 0)
395 {
396 // 1111110x
397 following = 5;
398 }
399 else
400 {
401 return false;
402 }
403
404 for (n = 0; n < following; ++n)
405 {
406 i++;
407
408 if (!(c = buffer[i]))
409 goto done;
410
411 if ((c & 0x80) == 0 || (c & 0x40))
412 return false;
413 }
414
415 gotone = true;
416 }
417 }
418
419 done:
420
421 return gotone; // don't claim it's UTF-8 if it's all 7-bit.
422}
423
424static QString detectEncodingAndDecode(const std::string& value)
425{
426 // For charset autodetection, we could use sophisticated code
427 // (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent),
428 // but that is probably too much.
429 // We check for UTF8, Local encoding and ASCII.
430 // Look like KEncodingDetector class can provide a full implementation for encoding detection.
431
432 if (value.empty())
433 {
434 return QString();
435 }
436
437 if (isUtf8(value.c_str()))
438 {
439 return QString::fromUtf8(value.c_str());
440 }
441
442 // Utf8 has a pretty unique byte pattern.
443 // Thats not true for ASCII, it is not possible
444 // to reliably autodetect different ISO-8859 charsets.
445 // So we can use either local encoding, or latin1.
446
447 return QString::fromLocal8Bit(value.c_str());
448}
449
450static QString convertCommentValue(const Exiv2::Exifdatum& exifDatum)
451{
452 try
453 {
454 std::string comment;
455 std::string charset;
456
457 comment = exifDatum.toString();
458
459 // libexiv2 will prepend "charset=\"SomeCharset\" " if charset is specified
460 // Before conversion to QString, we must know the charset, so we stay with std::string for a while
461 if (comment.length() > 8 && comment.substr(0, 8) == "charset=")
462 {
463 // the prepended charset specification is followed by a blank
464 std::string::size_type pos = comment.find_first_of(' ');
465
466 if (pos != std::string::npos)
467 {
468 // extract string between the = and the blank
469 charset = comment.substr(8, pos-8);
470 // get the rest of the string after the charset specification
471 comment = comment.substr(pos+1);
472 }
473 }
474
475 if (charset == "\"Unicode\"")
476 {
477 return QString::fromUtf8(comment.data());
478 }
479 else if (charset == "\"Jis\"")
480 {
481 QStringDecoder codec("JIS7");
482 return codec.decode(comment.c_str());
483 }
484 else if (charset == "\"Ascii\"")
485 {
486 return QString::fromLatin1(comment.c_str());
487 }
488 else
489 {
490 return detectEncodingAndDecode(comment);
491 }
492 }
493 catch( Exiv2::Error& e )
494 {
495 qWarning() << (QString::fromLatin1("Cannot convert Comment using Exiv2 "), e.what());
496 }
497 catch(...)
498 {
499 qWarning()<< "Default exception from Exiv2";
500 }
501
502 return QString();
503}
504
505MetaDataMap Exiv2Extractor::getExifTagsDataList(const QStringList& exifKeysFilter, bool invertSelection) const
506{
507 if (exifData().empty())
508 return MetaDataMap();
509
510 try
511 {
512 Exiv2::ExifData &data = exifData();
513 data.sortByKey();
514
515 MetaDataMap metaDataMap;
516
517 for (Exiv2::ExifData::iterator md = data.begin(); md != data.end(); ++md)
518 {
519 QString key = QString::fromLatin1(md->key().c_str());
520
521 // Decode the tag value with a user friendly output.
523
524 if (key == QString::fromLatin1("Exif.Photo.UserComment"))
525 {
526 tagValue = convertCommentValue(*md);
527 }
528 else if (key == QString::fromLatin1("Exif.Image.0x935c"))
529 {
530 tagValue = QString::number(md->value().size());
531 }
532 else
533 {
534 std::ostringstream os;
535 os << *md;
536
537 // Exif tag contents can be an translated strings, no only simple ascii.
538 tagValue = QString::fromLocal8Bit(os.str().c_str());
539 }
540
542
543 // We apply a filter to get only the Exif tags that we need.
544
545 if (!exifKeysFilter.isEmpty())
546 {
547 if (!invertSelection)
548 {
549 if (exifKeysFilter.contains(key.section(QString::fromLatin1("."), 1, 1)))
550 metaDataMap.insert(key, tagValue);
551 }
552 else
553 {
554 if (!exifKeysFilter.contains(key.section(QString::fromLatin1("."), 1, 1)))
555 metaDataMap.insert(key, tagValue);
556 }
557 }
558 else // else no filter at all.
559 {
560 metaDataMap.insert(key, tagValue);
561 }
562 }
563
564 return metaDataMap;
565 }
566 catch (Exiv2::Error& e)
567 {
568 qWarning() << (QString::fromLatin1("Cannot parse EXIF metadata using Exiv2 "), e.what());
569 }
570 catch(...)
571 {
572 qWarning() << "Default exception from Exiv2";
573 }
574
575 return MetaDataMap();
576}
577
578QString Exiv2Extractor::getExifComment() const
579{
580 try
581 {
582 if (!exifData().empty())
583 {
584 Exiv2::ExifData &data(exifData());
585 Exiv2::ExifKey key("Exif.Photo.UserComment");
586 Exiv2::ExifData::iterator it = data.findKey(key);
587
588 if (it != data.end())
589 {
590 QString exifComment = convertCommentValue(*it);
591
592 // some cameras fill the UserComment with whitespace
593 if (!exifComment.isEmpty() && !exifComment.trimmed().isEmpty())
594 return exifComment;
595 }
596
597 Exiv2::ExifKey key2("Exif.Image.ImageDescription");
598 Exiv2::ExifData::iterator it2 = data.findKey(key2);
599
600 if (it2 != data.end())
601 {
602 QString exifComment = convertCommentValue(*it2);
603
604 // Some cameras fill in nonsense default values
605 QStringList blackList;
606 blackList << QString::fromLatin1("SONY DSC"); // + whitespace
607 blackList << QString::fromLatin1("OLYMPUS DIGITAL CAMERA");
608 blackList << QString::fromLatin1("MINOLTA DIGITAL CAMERA");
609
610 QString trimmedComment = exifComment.trimmed();
611
612 // some cameras fill the UserComment with whitespace
613 if (!exifComment.isEmpty() && !trimmedComment.isEmpty() && !blackList.contains(trimmedComment))
614 return exifComment;
615 }
616 }
617 }
618 catch( Exiv2::Error& e )
619 {
620 qWarning() << (QString::fromLatin1("Cannot find Exif User Comment using Exiv2 "), e.what());
621 }
622 catch(...)
623 {
624 qWarning() << "Default exception from Exiv2";
625 }
626
627 return QString();
628}
629
630QString Exiv2Extractor::GPSString() const
631{
632 if(error())
633 {
634 return QString();
635 }
636
637 City m_city(city());
638
639 if(!m_city.isValid())
640 {
641 return QString();
642 }
643
644 return m_city.name();
645}
646
647QString Exiv2Extractor::cityId() const
648{
649 if(error())
650 {
651 return QString();
652 }
653
654 return city().id();
655}
656
657City Exiv2Extractor::city() const
658{
659 if(error())
660 {
661 return City();
662 }
663
664 auto c = extractGPS();
665
666 if(c.first == 0.0 || c.second == 0.0)
667 {
668 return City();
669 }
670
671 return Cities::getInstance()->findCity(c.first, c.second);
672}
673
674bool Exiv2Extractor::writeTag(const char *tagName, const QVariant &value)
675{
676 try
677 {
678 qDebug() << "trying to write tag4";
679
680 Exiv2::ExifKey exifKey(tagName);
681 Exiv2::ExifData &data = (exifData());
682 Exiv2::ExifData::iterator it = data.findKey(exifKey);
683 qDebug() << "trying to write tag5";
684
685 if (it != data.end())
686 {
687 qDebug() << "trying to write tag2";
688
689 switch (it->typeId())
690 {
691 case Exiv2::unsignedByte:
692 case Exiv2::unsignedShort:
693 case Exiv2::unsignedLong:
694 case Exiv2::signedShort:
695 case Exiv2::signedLong:
696 case Exiv2::unsignedLongLong:
697 case Exiv2::signedLongLong:
698 {
699 if(!value.canConvert<QString>())
700 return false;
701
702 qDebug() << "Writting number metadata" << tagName;
703
704#if EXIV2_TEST_VERSION(0,28,0)
705 Exiv2::Value::UniquePtr v = Exiv2::Value::create(Exiv2::signedLongLong);
706
707#else
708 Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::signedLongLong);
709#endif
710 v->read(value.toString().toStdString());
711 it->setValue(v.get());
712 break;
713 }
714
715 case Exiv2::unsignedRational:
716 case Exiv2::signedRational:
717 {
718 if(!value.canConvert<QString>())
719 return false;
720 qDebug() << "Writting rational metadata" << tagName;
721
722#if EXIV2_TEST_VERSION(0,28,0)
723 Exiv2::RationalValue::UniquePtr rv(new Exiv2::RationalValue);
724
725#else
726 Exiv2::RationalValue::AutoPtr rv(new Exiv2::RationalValue);
727#endif
728 rv->read(value.toString().toStdString());
729 it->setValue(rv.get());
730 break;
731
732 }
733 case Exiv2::date:
734 case Exiv2::time:
735 {
736 if(!value.canConvert<QString>())
737 return false;
738
739 auto date = value.toString();
740
741#if EXIV2_TEST_VERSION(0,28,0)
742 Exiv2::Value::UniquePtr v = Exiv2::Value::create(Exiv2::asciiString);
743
744#else
745 Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::asciiString);
746#endif
747 v->read(date.toStdString());
748 it->setValue(v.get());
749 break;
750
751 }
752 case Exiv2::asciiString:
753 case Exiv2::comment:
754 case Exiv2::string:
755 {
756 if(!value.canConvert<QString>())
757 return false;
758 qDebug() << "Writting ascii metadata" << tagName;
759
760 auto string = value.toString();
761
762#if EXIV2_TEST_VERSION(0,28,0)
763 Exiv2::Value::UniquePtr v = Exiv2::Value::create(Exiv2::asciiString);
764
765#else
766 Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::asciiString);
767#endif
768 v->read(string.toStdString());
769 it->setValue(v.get());
770 break;
771
772
773 }
774 default:
775 qDebug() << "Writting unkown metadata" << tagName;
776
777 return false;
778 }
779
780 qDebug() << "Writting metadata EXIF tag to file" << tagName;
781 // m_image->setExifData(data);
782 m_image->writeMetadata();
783 return true;
784 }else
785 {
786 Exiv2::Exifdatum& tag = data[tagName];
787 std::string str = value.toString().toStdString();
788 tag.setValue(str);
789 m_image->writeMetadata();
790 return true;
791 }
792 }
793 catch( Exiv2::Error& e )
794 {
795 qWarning () << QString("Cannot find Exif key '%1' in the image using Exiv2 ").arg(QString::fromLatin1(tagName)) << e.what();
796 return false;
797
798 }
799 catch(...)
800 {
801 qWarning() << "Default exception from Exiv2";
802 return false;
803
804 }
805 return false;
806}
807
808bool Exiv2Extractor::removeTag(const char *tagName)
809{
810
811 try
812 {
813 Exiv2::ExifKey key = Exiv2::ExifKey(tagName);
814 Exiv2::ExifData &data = (exifData());
815
816 Exiv2::ExifData::iterator it = data.findKey(key);
817
818 if (it != data.end())
819 {
820 data.erase(it);
821 m_image->writeMetadata();
822 return true;
823 }
824 }
825 catch( Exiv2::Error& e )
826 {
827 qWarning () << QString("Cannot find Exif key '%1' in the image using Exiv2 ").arg(QString::fromLatin1(tagName)) << e.what();
828 return false;
829
830 }
831 catch(...)
832 {
833 qWarning() << "Default exception from Exiv2";
834 return false;
835
836 }
837
838 return false;
839}
840
A class for representing the GPS coordinates and information of a city.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QByteArray tagValue(const Elem &elem, const char *keyName)
bool isEmpty() const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool exists() const const
T & first()
bool isEmpty() const const
iterator insert(const Key &key, const T &value)
QString arg(Args &&... args) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
std::string toStdString() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isValid() const const
QString toLocalFile() const const
bool canConvert() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:10:19 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.