Falkon Develop
Cross-platform Qt-based web browser
qztools.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
18#include "qztools.h"
19#include "datapaths.h"
20#include "settings.h"
21#include "mainapplication.h"
22
23#include <QTextDocument>
24#include <QDateTime>
25#include <QByteArray>
26#include <QPixmap>
27#include <QPainter>
28#include <QPainterPath>
29#include <QBuffer>
30#include <QFile>
31#include <QDir>
32#include <QWidget>
33#include <QApplication>
34#include <QSslCertificate>
35#include <QLocale>
36#include <QScreen>
37#include <QUrl>
38#include <QIcon>
39#include <QFileIconProvider>
40#include <QTemporaryFile>
41#include <QHash>
42#include <QSysInfo>
43#include <QProcess>
44#include <QMessageBox>
45#include <QUrlQuery>
46#include <QtGuiVersion>
47
48#ifdef QZ_WS_X11
49#include <xcb/xcb.h>
50#endif
51
52#ifdef Q_OS_WIN
53#include <windows.h>
54#else
55#include <unistd.h>
56#endif
57
58#ifdef Q_OS_MACOS
59#include <CoreServices/CoreServices.h>
60#endif
61
62QByteArray QzTools::pixmapToByteArray(const QPixmap &pix)
63{
64 QByteArray bytes;
65 QBuffer buffer(&bytes);
66 buffer.open(QIODevice::WriteOnly);
67 if (pix.save(&buffer, "PNG")) {
68 return buffer.buffer().toBase64();
69 }
70
71 return {};
72}
73
74QPixmap QzTools::pixmapFromByteArray(const QByteArray &data)
75{
76 QPixmap image;
77 QByteArray bArray = QByteArray::fromBase64(data);
78 image.loadFromData(bArray);
79
80 return image;
81}
82
83QUrl QzTools::pixmapToDataUrl(const QPixmap &pix)
84{
85 const QString data(QString::fromLatin1(pixmapToByteArray(pix)));
86 return data.isEmpty() ? QUrl() : QUrl(QSL("data:image/png;base64,") + data);
87}
88
89QPixmap QzTools::dpiAwarePixmap(const QString &path)
90{
91 const QIcon icon(path);
92 if (icon.availableSizes().isEmpty()) {
93 return QPixmap(path);
94 }
95 return icon.pixmap(icon.availableSizes().at(0));
96}
97
98QString QzTools::readAllFileContents(const QString &filename)
99{
100 return QString::fromUtf8(readAllFileByteContents(filename));
101}
102
103QByteArray QzTools::readAllFileByteContents(const QString &filename)
104{
105 QFile file(filename);
106
107 if (!filename.isEmpty() && file.open(QFile::ReadOnly)) {
108 const QByteArray a = file.readAll();
109 file.close();
110 return a;
111 }
112
113 return {};
114}
115
117{
118 QRect screen = w->screen()->geometry();
119 const QRect size = w->geometry();
120 w->move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2);
121}
122
123// Very, very, very simplified QDialog::adjustPosition from qdialog.cpp
124void QzTools::centerWidgetToParent(QWidget* w, QWidget* parent)
125{
126 if (!parent || !w) {
127 return;
128 }
129
130 QPoint p;
131 parent = parent->window();
132 QPoint pp = parent->mapToGlobal(QPoint(0, 0));
133 p = QPoint(pp.x() + parent->width() / 2, pp.y() + parent->height() / 2);
134 p = QPoint(p.x() - w->width() / 2, p.y() - w->height() / 2 - 20);
135
136 w->move(p);
137}
138
139bool QzTools::removeRecursively(const QString &filePath)
140{
141 const QFileInfo fileInfo(filePath);
142 if (!fileInfo.exists() && !fileInfo.isSymLink()) {
143 return true;
144 }
145 if (fileInfo.isDir() && !fileInfo.isSymLink()) {
146 QDir dir(filePath);
147 dir.setPath(dir.canonicalPath());
148 if (dir.isRoot() || dir.path() == QDir::home().canonicalPath()) {
149 qCritical() << "Attempt to remove root/home directory" << dir;
150 return false;
151 }
152 const QStringList fileNames = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
153 for (const QString &fileName : fileNames) {
154 if (!removeRecursively(filePath + QLatin1Char('/') + fileName)) {
155 return false;
156 }
157 }
158 if (!QDir::root().rmdir(dir.path())) {
159 return false;
160 }
161 } else if (!QFile::remove(filePath)) {
162 return false;
163 }
164 return true;
165}
166
167bool QzTools::copyRecursively(const QString &sourcePath, const QString &targetPath)
168{
169 const QFileInfo srcFileInfo(sourcePath);
170 if (srcFileInfo.isDir() && !srcFileInfo.isSymLink()) {
171 QDir targetDir(targetPath);
172 targetDir.cdUp();
173 if (!targetDir.mkdir(QFileInfo(targetPath).fileName())) {
174 return false;
175 }
176 const QStringList fileNames = QDir(sourcePath).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
177 for (const QString &fileName : fileNames) {
178 const QString newSourcePath = sourcePath + QL1C('/') + fileName;
179 const QString newTargetPath = targetPath + QL1C('/') + fileName;
180 if (!copyRecursively(newSourcePath, newTargetPath)) {
181 return false;
182 }
183 }
184#ifndef Q_OS_WIN
185 } else if (srcFileInfo.isSymLink()) {
186 const QByteArray pathData = sourcePath.toLocal8Bit();
187 char buf[1024];
188 ssize_t len = readlink(pathData.constData(), buf, sizeof(buf) - 1);
189 if (len < 0) {
190 qWarning() << "Error getting symlink path" << pathData;
191 return false;
192 }
193 buf[len] = '\0';
194 return QFile::link(QString::fromLocal8Bit(buf), targetPath);
195#endif
196 } else if (!QFile::copy(sourcePath, targetPath)) {
197 return false;
198 }
199 return true;
200}
201
202/* Finds same part of @one in @other from the beginning */
203QString QzTools::samePartOfStrings(const QString &one, const QString &other)
204{
205 int maxSize = qMin(one.size(), other.size());
206 if (maxSize <= 0) {
207 return {};
208 }
209
210 int i = 0;
211 while (one.at(i) == other.at(i)) {
212 i++;
213 if (i == maxSize) {
214 break;
215 }
216 }
217 return one.left(i);
218}
219
220QString QzTools::urlEncodeQueryString(const QUrl &url)
221{
222 QString returnString = url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment);
223
224 if (url.hasQuery()) {
225 returnString += QLatin1Char('?') + url.query(QUrl::FullyEncoded);
226 }
227
228 if (url.hasFragment()) {
229 returnString += QLatin1Char('#') + url.fragment(QUrl::FullyEncoded);
230 }
231
232 returnString.replace(QLatin1Char(' '), QLatin1String("%20"));
233
234 return returnString;
235}
236
237QString QzTools::fromPunycode(const QString &str)
238{
239 if (!str.startsWith(QL1S("xn--")))
240 return str;
241
242 // QUrl::fromAce will only decode domains from idn whitelist
243 const QString decoded = QUrl::fromAce(str.toUtf8() + QByteArray(".org"));
244 return decoded.left(decoded.size() - 4);
245}
246
247QString QzTools::escapeSqlGlobString(QString urlString)
248{
249 urlString.replace(QL1C('['), QStringLiteral("[["));
250 urlString.replace(QL1C(']'), QStringLiteral("[]]"));
251 urlString.replace(QStringLiteral("[["), QStringLiteral("[[]"));
252 urlString.replace(QL1C('*'), QStringLiteral("[*]"));
253 urlString.replace(QL1C('?'), QStringLiteral("[?]"));
254 return urlString;
255}
256
257QString QzTools::ensureUniqueFilename(const QString &name, const QString &appendFormat)
258{
259 Q_ASSERT(appendFormat.contains(QL1S("%1")));
260
261 QFileInfo info(name);
262
263 if (!info.exists())
264 return name;
265
266 const QDir dir = info.absoluteDir();
267 const QString fileName = info.fileName();
268
269 int i = 1;
270
271 while (info.exists()) {
272 QString file = fileName;
273 int index = file.lastIndexOf(QL1C('.'));
274 const QString appendString = appendFormat.arg(i);
275 if (index == -1)
276 file.append(appendString);
277 else
278 file = file.left(index) + appendString + file.mid(index);
279 info.setFile(dir, file);
280 i++;
281 }
282
283 return info.absoluteFilePath();
284}
285
286QString QzTools::getFileNameFromUrl(const QUrl &url)
287{
288 QString fileName = url.toString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveScheme | QUrl::RemovePort);
289
290 if (fileName.endsWith(QLatin1Char('/'))) {
291 fileName = fileName.mid(0, fileName.length() - 1);
292 }
293
294 if (fileName.indexOf(QLatin1Char('/')) != -1) {
295 int pos = fileName.lastIndexOf(QLatin1Char('/'));
296 fileName = fileName.mid(pos);
297 fileName.remove(QLatin1Char('/'));
298 }
299
300 fileName = filterCharsFromFilename(fileName);
301
302 if (fileName.isEmpty()) {
303 fileName = filterCharsFromFilename(url.host());
304 }
305
306 return fileName;
307}
308
309QString QzTools::filterCharsFromFilename(const QString &name)
310{
311 QString value = name;
312
313 value.replace(QLatin1Char('/'), QLatin1Char('-'));
314 value.remove(QLatin1Char('\\'));
315 value.remove(QLatin1Char(':'));
316 value.remove(QLatin1Char('*'));
317 value.remove(QLatin1Char('?'));
318 value.remove(QLatin1Char('"'));
319 value.remove(QLatin1Char('<'));
320 value.remove(QLatin1Char('>'));
321 value.remove(QLatin1Char('|'));
322
323 return value;
324}
325
326QString QzTools::lastPathForFileDialog(const QString &dialogName, const QString &fallbackPath)
327{
328 Settings settings;
329 settings.beginGroup(QSL("LastFileDialogsPaths"));
330 QString path = settings.value(QSL("FileDialogs/") + dialogName).toString();
331 settings.endGroup();
332
333 return path.isEmpty() ? fallbackPath : path;
334}
335
336void QzTools::saveLastPathForFileDialog(const QString &dialogName, const QString &path)
337{
338 if (path.isEmpty()) {
339 return;
340 }
341
342 Settings settings;
343 settings.beginGroup(QSL("LastFileDialogsPaths"));
344 settings.setValue(dialogName, path);
345 settings.endGroup();
346}
347
348QString QzTools::alignTextToWidth(const QString &string, const QString &text, const QFontMetrics &metrics, int width)
349{
350 int pos = 0;
351 QString returnString;
352
353 while (pos <= string.size()) {
354 QString part = string.mid(pos);
355 QString elidedLine = metrics.elidedText(part, Qt::ElideRight, width);
356
357 if (elidedLine.isEmpty()) {
358 break;
359 }
360
361 if (elidedLine.size() != part.size()) {
362 elidedLine = elidedLine.left(elidedLine.size() - 3);
363 }
364
365 if (!returnString.isEmpty()) {
366 returnString += text;
367 }
368
369 returnString += elidedLine;
370 pos += elidedLine.size();
371 }
372
373 return returnString;
374}
375
376QString QzTools::fileSizeToString(qint64 size)
377{
378 if (size < 0) {
379 return QObject::tr("Unknown size");
380 }
381
382 QLocale locale;
383
384 double _size = size / 1024.0; // KB
385 if (_size < 1000) {
386 return QObject::tr("%1 kB").arg(locale.toString(_size > 1 ? _size : 1, 'f', 0));
387 }
388
389 _size /= 1024; // MB
390 if (_size < 1000) {
391 return QObject::tr("%1 MB").arg(locale.toString(_size, 'f', 1));
392 }
393
394 _size /= 1024; // GB
395 return QObject::tr("%1 GB").arg(locale.toString(_size, 'f', 2));
396}
397
398QPixmap QzTools::createPixmapForSite(const QIcon &icon, const QString &title, const QString &url)
399{
400 const QFontMetricsF fontMetrics(QApplication::font());
401 const int padding = 4;
402 const int maxWidth = fontMetrics.horizontalAdvance(title.length() > url.length() ? title : url) + 3 * padding + 16;
403 const int width = qMin(maxWidth, 150);
404 const int height = fontMetrics.height() * 2 + fontMetrics.leading() + 2 * padding;
405
406 QPixmap pixmap(width * qApp->devicePixelRatio(), height * qApp->devicePixelRatio());
407 pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
408
409 QPainter painter(&pixmap);
410 painter.setRenderHint(QPainter::Antialiasing);
411
412 // Draw background
413 QPen pen(Qt::black);
414 pen.setWidth(1);
415 painter.setPen(pen);
416
417 QPainterPath path;
418 path.addRect(QRect(0, 0, width, height));
419
420 painter.fillPath(path, Qt::white);
421 painter.drawPath(path);
422
423 // Draw icon
424 QRect iconRect(padding, 0, 16, height);
425 icon.paint(&painter, iconRect);
426
427 // Draw title
428 QRect titleRect(iconRect.right() + padding, padding, width - padding - iconRect.right(), fontMetrics.height());
429 painter.drawText(titleRect, fontMetrics.elidedText(title, Qt::ElideRight, titleRect.width()));
430
431 // Draw url
432 QRect urlRect(titleRect.x(), titleRect.bottom() + fontMetrics.leading(), titleRect.width(), titleRect.height());
433 painter.setPen(QApplication::palette().color(QPalette::Link));
434 painter.drawText(urlRect, fontMetrics.elidedText(url, Qt::ElideRight, urlRect.width()));
435
436 return pixmap;
437}
438
439QString QzTools::applyDirectionToPage(QString &pageContents)
440{
441 QString direction = QLatin1String("ltr");
442 QString right_str = QLatin1String("right");
443 QString left_str = QLatin1String("left");
444
445 if (QApplication::isRightToLeft()) {
446 direction = QLatin1String("rtl");
447 right_str = QLatin1String("left");
448 left_str = QLatin1String("right");
449 }
450
451 pageContents.replace(QLatin1String("%DIRECTION%"), direction);
452 pageContents.replace(QLatin1String("%RIGHT_STR%"), right_str);
453 pageContents.replace(QLatin1String("%LEFT_STR%"), left_str);
454
455 return pageContents;
456}
457
458QString QzTools::truncatedText(const QString &text, int size)
459{
460 if (text.length() > size) {
461 return text.left(size) + QL1S("..");
462 }
463 return text;
464}
465
466// Thanks to http://www.qtcentre.org/threads/3205-Toplevel-widget-with-rounded-corners?p=17492#post17492
467QRegion QzTools::roundedRect(const QRect &rect, int radius)
468{
469 QRegion region;
470
471 // middle and borders
472 region += rect.adjusted(radius, 0, -radius, 0);
473 region += rect.adjusted(0, radius, 0, -radius);
474
475 // top left
476 QRect corner(rect.topLeft(), QSize(radius * 2, radius * 2));
477 region += QRegion(corner, QRegion::Ellipse);
478
479 // top right
480 corner.moveTopRight(rect.topRight());
481 region += QRegion(corner, QRegion::Ellipse);
482
483 // bottom left
484 corner.moveBottomLeft(rect.bottomLeft());
485 region += QRegion(corner, QRegion::Ellipse);
486
487 // bottom right
488 corner.moveBottomRight(rect.bottomRight());
489 region += QRegion(corner, QRegion::Ellipse);
490
491 return region;
492}
493
494QIcon QzTools::iconFromFileName(const QString &fileName)
495{
496 static QHash<QString, QIcon> iconCache;
497
498 QFileInfo tempInfo(fileName);
499 if (iconCache.contains(tempInfo.suffix())) {
500 return iconCache.value(tempInfo.suffix());
501 }
502
503 QFileIconProvider iconProvider;
504 QTemporaryFile tempFile(DataPaths::path(DataPaths::Temp) + QSL("/XXXXXX.") + tempInfo.suffix());
505 tempFile.open();
506 tempInfo.setFile(tempFile.fileName());
507
508 QIcon icon(iconProvider.icon(tempInfo));
509 iconCache.insert(tempInfo.suffix(), icon);
510
511 return icon;
512}
513
514QString QzTools::resolveFromPath(const QString &name)
515{
516 const QString path = QString::fromUtf8(qgetenv("PATH").trimmed());
517
518 if (path.isEmpty()) {
519 return {};
520 }
521
522 const QStringList dirs = path.split(QLatin1Char(':'), Qt::SkipEmptyParts);
523
524 for (const QString &dir : dirs) {
525 QDir d(dir);
526 if (d.exists(name)) {
527 return d.absoluteFilePath(name);
528 }
529 }
530
531 return QString();
532}
533
534// http://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c
535bool QzTools::isUtf8(const char* string)
536{
537 if (!string) {
538 return 0;
539 }
540
541 const unsigned char* bytes = (const unsigned char*)string;
542 while (*bytes) {
543 if ((// ASCII
544 bytes[0] == 0x09 ||
545 bytes[0] == 0x0A ||
546 bytes[0] == 0x0D ||
547 (0x20 <= bytes[0] && bytes[0] <= 0x7F)
548 )
549 ) {
550 bytes += 1;
551 continue;
552 }
553
554 if ((// non-overlong 2-byte
555 (0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
556 (0x80 <= bytes[1] && bytes[1] <= 0xBF)
557 )
558 ) {
559 bytes += 2;
560 continue;
561 }
562
563 if ((// excluding overlongs
564 bytes[0] == 0xE0 &&
565 (0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
566 (0x80 <= bytes[2] && bytes[2] <= 0xBF)
567 ) ||
568 (// straight 3-byte
569 ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
570 bytes[0] == 0xEE ||
571 bytes[0] == 0xEF) &&
572 (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
573 (0x80 <= bytes[2] && bytes[2] <= 0xBF)
574 ) ||
575 (// excluding surrogates
576 bytes[0] == 0xED &&
577 (0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
578 (0x80 <= bytes[2] && bytes[2] <= 0xBF)
579 )
580 ) {
581 bytes += 3;
582 continue;
583 }
584
585 if ((// planes 1-3
586 bytes[0] == 0xF0 &&
587 (0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
588 (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
589 (0x80 <= bytes[3] && bytes[3] <= 0xBF)
590 ) ||
591 (// planes 4-15
592 (0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
593 (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
594 (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
595 (0x80 <= bytes[3] && bytes[3] <= 0xBF)
596 ) ||
597 (// plane 16
598 bytes[0] == 0xF4 &&
599 (0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
600 (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
601 (0x80 <= bytes[3] && bytes[3] <= 0xBF)
602 )
603 ) {
604 bytes += 4;
605 continue;
606 }
607
608 return false;
609 }
610
611 return true;
612}
613
614bool QzTools::containsSpace(const QString &str)
615{
616 for (const QChar &c : str) {
617 if (c.isSpace())
618 return true;
619 }
620 return false;
621}
622
623QString QzTools::getExistingDirectory(const QString &name, QWidget* parent, const QString &caption, const QString &dir, QFileDialog::Options options)
624{
625 Settings settings;
626 settings.beginGroup(QSL("FileDialogPaths"));
627
628 QString lastDir = settings.value(name, dir).toString();
629
630 QString path = QFileDialog::getExistingDirectory(parent, caption, lastDir, options);
631
632 if (!path.isEmpty()) {
633 settings.setValue(name, QFileInfo(path).absolutePath());
634 }
635
636 settings.endGroup();
637 return path;
638}
639
640static QString getFilename(const QString &path)
641{
642 QFileInfo info(path);
643
644 if (info.isFile()) {
645 return info.fileName();
646 }
647
648 if (info.isDir()) {
649 return {};
650 }
651
652 if (info.dir().exists()) {
653 return info.fileName();
654 }
655
656 return {};
657}
658
659QString QzTools::getOpenFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options)
660{
661 Settings settings;
662 settings.beginGroup(QSL("FileDialogPaths"));
663
664 QString lastDir = settings.value(name, QString()).toString();
665 QString fileName = getFilename(dir);
666
667 if (lastDir.isEmpty()) {
668 lastDir = dir;
669 }
670 else {
671 lastDir.append(QDir::separator() + fileName);
672 }
673
674 QString path = QFileDialog::getOpenFileName(parent, caption, lastDir, filter, selectedFilter, options);
675
676 if (!path.isEmpty()) {
677 settings.setValue(name, QFileInfo(path).absolutePath());
678 }
679
680 settings.endGroup();
681 return path;
682}
683
684QStringList QzTools::getOpenFileNames(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options)
685{
686 Settings settings;
687 settings.beginGroup(QSL("FileDialogPaths"));
688
689 QString lastDir = settings.value(name, QString()).toString();
690 QString fileName = getFilename(dir);
691
692 if (lastDir.isEmpty()) {
693 lastDir = dir;
694 }
695 else {
696 lastDir.append(QDir::separator() + fileName);
697 }
698
699 QStringList paths = QFileDialog::getOpenFileNames(parent, caption, lastDir, filter, selectedFilter, options);
700
701 if (!paths.isEmpty()) {
702 settings.setValue(name, QFileInfo(paths.at(0)).absolutePath());
703 }
704
705 settings.endGroup();
706 return paths;
707}
708
709QString QzTools::getSaveFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options)
710{
711 Settings settings;
712 settings.beginGroup(QSL("FileDialogPaths"));
713
714 QString lastDir = settings.value(name, QString()).toString();
715 QString fileName = getFilename(dir);
716
717 if (lastDir.isEmpty()) {
718 lastDir = dir;
719 }
720 else {
721 lastDir.append(QDir::separator() + fileName);
722 }
723
724 QString path = QFileDialog::getSaveFileName(parent, caption, lastDir, filter, selectedFilter, options);
725
726 if (!path.isEmpty()) {
727 settings.setValue(name, QFileInfo(path).absolutePath());
728 }
729
730 settings.endGroup();
731 return path;
732}
733
734// Matches domain (assumes both pattern and domain not starting with dot)
735// pattern = domain to be matched
736// domain = site domain
737bool QzTools::matchDomain(const QString &pattern, const QString &domain)
738{
739 if (pattern == domain) {
740 return true;
741 }
742
743 if (!domain.endsWith(pattern)) {
744 return false;
745 }
746
747 int index = domain.indexOf(pattern);
748
749 return index > 0 && domain[index - 1] == QLatin1Char('.');
750}
751
752QKeySequence QzTools::actionShortcut(const QKeySequence &shortcut, const QKeySequence &fallBack, const QKeySequence &shortcutRtl, const QKeySequence &fallbackRtl)
753{
754 if (QApplication::isRightToLeft() && (!shortcutRtl.isEmpty() || !fallbackRtl.isEmpty()))
755 return shortcutRtl.isEmpty() ? fallbackRtl : shortcutRtl;
756
757 return shortcut.isEmpty() ? fallBack : shortcut;
758}
759
760static inline bool isQuote(const QChar &c)
761{
762 return (c == QLatin1Char('"') || c == QLatin1Char('\''));
763}
764
765// Function splits command line into arguments
766// eg. /usr/bin/foo -o test -b "bar bar" -s="sed sed"
767// => '/usr/bin/foo' '-o' 'test' '-b' 'bar bar' '-s=sed sed'
768QStringList QzTools::splitCommandArguments(const QString &command)
769{
770 QString line = command.trimmed();
771
772 if (line.isEmpty()) {
773 return {};
774 }
775
776 QChar SPACE(QL1C(' '));
777 QChar EQUAL(QL1C('='));
778 QChar BSLASH(QL1C('\\'));
779 QChar QUOTE(QL1C('"'));
780 QStringList r;
781
782 int equalPos = -1; // Position of = in opt="value"
783 int startPos = isQuote(line.at(0)) ? 1 : 0;
784 bool inWord = !isQuote(line.at(0));
785 bool inQuote = !inWord;
786
787 if (inQuote) {
788 QUOTE = line.at(0);
789 }
790
791 const int strlen = line.length();
792 for (int i = 0; i < strlen; ++i) {
793 const QChar c = line.at(i);
794
795 if (inQuote && c == QUOTE && i > 0 && line.at(i - 1) != BSLASH) {
796 QString str = line.mid(startPos, i - startPos);
797 if (equalPos > -1) {
798 str.remove(equalPos - startPos + 1, 1);
799 }
800
801 inQuote = false;
802 if (!str.isEmpty()) {
803 r.append(str);
804 }
805 continue;
806 }
807 else if (!inQuote && isQuote(c)) {
808 inQuote = true;
809 QUOTE = c;
810
811 if (!inWord) {
812 startPos = i + 1;
813 }
814 else if (i > 0 && line.at(i - 1) == EQUAL) {
815 equalPos = i - 1;
816 }
817 }
818
819 if (inQuote) {
820 continue;
821 }
822
823 if (inWord && (c == SPACE || i == strlen - 1)) {
824 int len = (i == strlen - 1) ? -1 : i - startPos;
825 const QString str = line.mid(startPos, len);
826
827 inWord = false;
828 if (!str.isEmpty()) {
829 r.append(str);
830 }
831 }
832 else if (!inWord && c != SPACE) {
833 inWord = true;
834 startPos = i;
835 }
836 }
837
838 // Unmatched quote
839 if (inQuote) {
840 return {};
841 }
842
843 return r;
844}
845
846bool QzTools::startExternalProcess(const QString &executable, const QString &args)
847{
848 const QStringList arguments = splitCommandArguments(args);
849
850 bool success = QProcess::startDetached(executable, arguments);
851
852 if (!success) {
853 QString info = QSL("<ul><li><b>%1</b>%2</li><li><b>%3</b>%4</li></ul>");
854 info = info.arg(QObject::tr("Executable: "), executable,
855 QObject::tr("Arguments: "), arguments.join(QLatin1Char(' ')));
856
857 QMessageBox::critical(nullptr, QObject::tr("Cannot start external program"),
858 QObject::tr("Cannot start external program! %1").arg(info));
859 }
860
861 return success;
862}
863
864#ifdef QZ_WS_X11
865static xcb_connection_t *getXcbConnection()
866{
867 const QNativeInterface::QX11Application *x11App = qApp->nativeInterface<QNativeInterface::QX11Application>();
868 if (x11App == nullptr)
869 return nullptr;
870 return x11App->connection();
871}
872#endif
873
874void QzTools::setWmClass(const QString &name, const QWidget* widget)
875{
876#ifdef QZ_WS_X11
877 if (QGuiApplication::platformName() != QL1S("xcb"))
878 return;
879
880 xcb_connection_t *connection = getXcbConnection();
881 if (connection == nullptr)
882 return;
883
884 const QByteArray nameData = name.toUtf8();
885 const QByteArray classData = mApp->wmClass().isEmpty() ? QByteArrayLiteral("Falkon") : mApp->wmClass();
886
887 uint32_t class_len = nameData.length() + 1 + classData.length() + 1;
888 char *class_hint = (char*) malloc(class_len);
889
890 qstrcpy(class_hint, nameData.constData());
891 qstrcpy(class_hint + nameData.length() + 1, classData.constData());
892
893 xcb_change_property(connection, XCB_PROP_MODE_REPLACE, widget->winId(),
894 XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, class_len, class_hint);
895
896 free(class_hint);
897
898#else
899 Q_UNUSED(name)
900 Q_UNUSED(widget)
901#endif
902}
903
905{
906#ifdef Q_OS_MACOS
907 QString str = QSL("Mac OS X");
908
909 SInt32 majorVersion;
910 SInt32 minorVersion;
911
912 if (Gestalt(gestaltSystemVersionMajor, &majorVersion) == noErr && Gestalt(gestaltSystemVersionMinor, &minorVersion) == noErr) {
913 str.append(QSL(" %1.%2").arg(majorVersion).arg(minorVersion));
914 }
915
916 return str;
917#endif
918#ifdef Q_OS_LINUX
919 return QSL("Linux");
920#endif
921#ifdef Q_OS_BSD4
922 return QSL("BSD 4.4");
923#endif
924#ifdef Q_OS_BSDI
925 return QSL("BSD/OS");
926#endif
927#ifdef Q_OS_FREEBSD
928 return QSL("FreeBSD");
929#endif
930#ifdef Q_OS_HPUX
931 return QSL("HP-UX");
932#endif
933#ifdef Q_OS_HURD
934 return QSL("GNU Hurd");
935#endif
936#ifdef Q_OS_LYNX
937 return QSL("LynxOS");
938#endif
939#ifdef Q_OS_NETBSD
940 return QSL("NetBSD");
941#endif
942#ifdef Q_OS_OS2
943 return QSL("OS/2");
944#endif
945#ifdef Q_OS_OPENBSD
946 return QSL("OpenBSD");
947#endif
948#ifdef Q_OS_OSF
949 return QSL("HP Tru64 UNIX");
950#endif
951#ifdef Q_OS_SOLARIS
952 return QSL("Sun Solaris");
953#endif
954#ifdef Q_OS_UNIXWARE
955 return QSL("UnixWare 7 / Open UNIX 8");
956#endif
957#ifdef Q_OS_UNIX
958 return QSL("Unix");
959#endif
960#ifdef Q_OS_HAIKU
961 return QSL("Haiku");
962#endif
963#ifdef Q_OS_WIN32
964 QString str = QSL("Windows");
965
966 switch (QSysInfo::windowsVersion()) {
967 case QSysInfo::WV_NT:
968 str.append(QSL(" NT"));
969 break;
970
971 case QSysInfo::WV_2000:
972 str.append(QSL(" 2000"));
973 break;
974
975 case QSysInfo::WV_XP:
976 str.append(QSL(" XP"));
977 break;
978 case QSysInfo::WV_2003:
979 str.append(QSL(" XP Pro x64"));
980 break;
981
982 case QSysInfo::WV_VISTA:
983 str.append(QSL(" Vista"));
984 break;
985
986 case QSysInfo::WV_WINDOWS7:
987 str.append(QSL(" 7"));
988 break;
989
990 case QSysInfo::WV_WINDOWS8:
991 str.append(QSL(" 8"));
992 break;
993
994 case QSysInfo::WV_WINDOWS8_1:
995 str.append(QSL(" 8.1"));
996 break;
997
998 case QSysInfo::WV_WINDOWS10:
999 str.append(QSL(" 10"));
1000 break;
1001
1002 default:
1003 break;
1004 }
1005
1006 return str;
1007#endif
1008}
1009
1011{
1012 return QSysInfo::currentCpuArchitecture();
1013}
1014
1016{
1017 const QString arch = cpuArchitecture();
1018 if (arch.isEmpty())
1019 return QzTools::operatingSystem();
1020 return QzTools::operatingSystem() + QSL(" ") + arch;
1021}
1022
1023void QzTools::paintDropIndicator(QWidget *widget, const QRect &r)
1024{
1025 // Modified code from KFilePlacesView
1026 QColor color = widget->palette().brush(QPalette::Normal, QPalette::Highlight).color();
1027 const int x = (r.left() + r.right()) / 2;
1028 const int thickness = qRound(r.width() / 2.0);
1029 int alpha = 255;
1030 const int alphaDec = alpha / (thickness + 1);
1031 QStylePainter p(widget);
1032 for (int i = 0; i < thickness; i++) {
1033 color.setAlpha(alpha);
1034 alpha -= alphaDec;
1035 p.setPen(color);
1036 p.drawLine(x - i, r.top(), x - i, r.bottom());
1037 p.drawLine(x + i, r.top(), x + i, r.bottom());
1038 }
1039}
static QString path(Path type)
Definition: datapaths.cpp:66
static void setWmClass(const QString &name, const QWidget *widget)
Definition: qztools.cpp:874
static QStringList getOpenFileNames(const QString &name, QWidget *parent=nullptr, const QString &caption=QString(), const QString &dir=QString(), const QString &filter=QString(), QString *selectedFilter=nullptr, QFileDialog::Options options=QFileDialog::Options())
Definition: qztools.cpp:684
static QString ensureUniqueFilename(const QString &name, const QString &appendFormat=QSL("(%1)"))
Definition: qztools.cpp:257
static QKeySequence actionShortcut(const QKeySequence &shortcut, const QKeySequence &fallBack, const QKeySequence &shortcutRtl=QKeySequence(), const QKeySequence &fallbackRtl=QKeySequence())
Definition: qztools.cpp:752
static QString resolveFromPath(const QString &name)
Definition: qztools.cpp:514
static QIcon iconFromFileName(const QString &fileName)
Definition: qztools.cpp:494
static bool startExternalProcess(const QString &executable, const QString &args)
Definition: qztools.cpp:846
static bool containsSpace(const QString &str)
Definition: qztools.cpp:614
static QString fileSizeToString(qint64 size)
Definition: qztools.cpp:376
static QPixmap dpiAwarePixmap(const QString &path)
Definition: qztools.cpp:89
static QString getSaveFileName(const QString &name, QWidget *parent=nullptr, const QString &caption=QString(), const QString &dir=QString(), const QString &filter=QString(), QString *selectedFilter=nullptr, QFileDialog::Options options=QFileDialog::Options())
Definition: qztools.cpp:709
static QString getExistingDirectory(const QString &name, QWidget *parent=nullptr, const QString &caption=QString(), const QString &dir=QString(), QFileDialog::Options options=QFileDialog::ShowDirsOnly)
Definition: qztools.cpp:623
static QUrl pixmapToDataUrl(const QPixmap &pix)
Definition: qztools.cpp:83
static QString urlEncodeQueryString(const QUrl &url)
Definition: qztools.cpp:220
static void centerWidgetToParent(QWidget *w, QWidget *parent)
Definition: qztools.cpp:124
static QString readAllFileContents(const QString &filename)
Definition: qztools.cpp:98
static bool copyRecursively(const QString &sourcePath, const QString &targetPath)
Definition: qztools.cpp:167
static QString fromPunycode(const QString &str)
Definition: qztools.cpp:237
static QString filterCharsFromFilename(const QString &name)
Definition: qztools.cpp:309
static QByteArray readAllFileByteContents(const QString &filename)
Definition: qztools.cpp:103
static void centerWidgetOnScreen(QWidget *w)
Definition: qztools.cpp:116
static QPixmap pixmapFromByteArray(const QByteArray &data)
Definition: qztools.cpp:74
static void paintDropIndicator(QWidget *widget, const QRect &r)
Definition: qztools.cpp:1023
static QString applyDirectionToPage(QString &pageContents)
Definition: qztools.cpp:439
static QString truncatedText(const QString &text, int size)
Definition: qztools.cpp:458
static bool matchDomain(const QString &pattern, const QString &domain)
Definition: qztools.cpp:737
static QString getFileNameFromUrl(const QUrl &url)
Definition: qztools.cpp:286
static QStringList splitCommandArguments(const QString &command)
Definition: qztools.cpp:768
static QString escapeSqlGlobString(QString urlString)
Definition: qztools.cpp:247
static QPixmap createPixmapForSite(const QIcon &icon, const QString &title, const QString &url)
Definition: qztools.cpp:398
static void saveLastPathForFileDialog(const QString &dialogName, const QString &path)
Definition: qztools.cpp:336
static QString getOpenFileName(const QString &name, QWidget *parent=nullptr, const QString &caption=QString(), const QString &dir=QString(), const QString &filter=QString(), QString *selectedFilter=nullptr, QFileDialog::Options options=QFileDialog::Options())
Definition: qztools.cpp:659
static QString samePartOfStrings(const QString &one, const QString &other)
Definition: qztools.cpp:203
static QString operatingSystem()
Definition: qztools.cpp:904
static bool isUtf8(const char *string)
Definition: qztools.cpp:535
static QByteArray pixmapToByteArray(const QPixmap &pix)
Definition: qztools.cpp:62
static bool removeRecursively(const QString &filePath)
Definition: qztools.cpp:139
static QString operatingSystemLong()
Definition: qztools.cpp:1015
static QString cpuArchitecture()
Definition: qztools.cpp:1010
static QString alignTextToWidth(const QString &string, const QString &text, const QFontMetrics &metrics, int width)
Definition: qztools.cpp:348
static QRegion roundedRect(const QRect &rect, int radius)
Definition: qztools.cpp:467
static QString lastPathForFileDialog(const QString &dialogName, const QString &fallbackPath)
Definition: qztools.cpp:326
void beginGroup(const QString &prefix)
Definition: settings.cpp:79
void endGroup()
Definition: settings.cpp:84
QVariant value(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:74
void setValue(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:69
#define mApp
int value(const QColor &c)
Definition: colors.cpp:238
i
Definition: i18n.py:23
locale
Definition: i18n.py:21
#define QL1S(x)
Definition: qzcommon.h:44
#define QL1C(x)
Definition: qzcommon.h:48
#define QSL(x)
Definition: qzcommon.h:40