File indexing completed on 2024-05-12 04:58:28
0001 /* ============================================================ 0002 * Falkon - Qt web browser 0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 * ============================================================ */ 0018 #include "qztools.h" 0019 #include "datapaths.h" 0020 #include "settings.h" 0021 #include "mainapplication.h" 0022 0023 #include <QTextDocument> 0024 #include <QDateTime> 0025 #include <QByteArray> 0026 #include <QPixmap> 0027 #include <QPainter> 0028 #include <QPainterPath> 0029 #include <QBuffer> 0030 #include <QFile> 0031 #include <QDir> 0032 #include <QWidget> 0033 #include <QApplication> 0034 #include <QSslCertificate> 0035 #include <QScreen> 0036 #include <QUrl> 0037 #include <QIcon> 0038 #include <QFileIconProvider> 0039 #include <QTemporaryFile> 0040 #include <QHash> 0041 #include <QSysInfo> 0042 #include <QProcess> 0043 #include <QMessageBox> 0044 #include <QUrlQuery> 0045 #include <QtGuiVersion> 0046 0047 #ifdef QZ_WS_X11 0048 #include <xcb/xcb.h> 0049 #endif 0050 0051 #ifdef Q_OS_WIN 0052 #include <windows.h> 0053 #else 0054 #include <unistd.h> 0055 #endif 0056 0057 #ifdef Q_OS_MACOS 0058 #include <CoreServices/CoreServices.h> 0059 #endif 0060 0061 QByteArray QzTools::pixmapToByteArray(const QPixmap &pix) 0062 { 0063 QByteArray bytes; 0064 QBuffer buffer(&bytes); 0065 buffer.open(QIODevice::WriteOnly); 0066 if (pix.save(&buffer, "PNG")) { 0067 return buffer.buffer().toBase64(); 0068 } 0069 0070 return {}; 0071 } 0072 0073 QPixmap QzTools::pixmapFromByteArray(const QByteArray &data) 0074 { 0075 QPixmap image; 0076 QByteArray bArray = QByteArray::fromBase64(data); 0077 image.loadFromData(bArray); 0078 0079 return image; 0080 } 0081 0082 QUrl QzTools::pixmapToDataUrl(const QPixmap &pix) 0083 { 0084 const QString data(QString::fromLatin1(pixmapToByteArray(pix))); 0085 return data.isEmpty() ? QUrl() : QUrl(QSL("data:image/png;base64,") + data); 0086 } 0087 0088 QPixmap QzTools::dpiAwarePixmap(const QString &path) 0089 { 0090 const QIcon icon(path); 0091 if (icon.availableSizes().isEmpty()) { 0092 return QPixmap(path); 0093 } 0094 return icon.pixmap(icon.availableSizes().at(0)); 0095 } 0096 0097 QString QzTools::readAllFileContents(const QString &filename) 0098 { 0099 return QString::fromUtf8(readAllFileByteContents(filename)); 0100 } 0101 0102 QByteArray QzTools::readAllFileByteContents(const QString &filename) 0103 { 0104 QFile file(filename); 0105 0106 if (!filename.isEmpty() && file.open(QFile::ReadOnly)) { 0107 const QByteArray a = file.readAll(); 0108 file.close(); 0109 return a; 0110 } 0111 0112 return {}; 0113 } 0114 0115 void QzTools::centerWidgetOnScreen(QWidget* w) 0116 { 0117 QRect screen = w->screen()->geometry(); 0118 const QRect size = w->geometry(); 0119 w->move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2); 0120 } 0121 0122 // Very, very, very simplified QDialog::adjustPosition from qdialog.cpp 0123 void QzTools::centerWidgetToParent(QWidget* w, QWidget* parent) 0124 { 0125 if (!parent || !w) { 0126 return; 0127 } 0128 0129 QPoint p; 0130 parent = parent->window(); 0131 QPoint pp = parent->mapToGlobal(QPoint(0, 0)); 0132 p = QPoint(pp.x() + parent->width() / 2, pp.y() + parent->height() / 2); 0133 p = QPoint(p.x() - w->width() / 2, p.y() - w->height() / 2 - 20); 0134 0135 w->move(p); 0136 } 0137 0138 bool QzTools::removeRecursively(const QString &filePath) 0139 { 0140 const QFileInfo fileInfo(filePath); 0141 if (!fileInfo.exists() && !fileInfo.isSymLink()) { 0142 return true; 0143 } 0144 if (fileInfo.isDir() && !fileInfo.isSymLink()) { 0145 QDir dir(filePath); 0146 dir.setPath(dir.canonicalPath()); 0147 if (dir.isRoot() || dir.path() == QDir::home().canonicalPath()) { 0148 qCritical() << "Attempt to remove root/home directory" << dir; 0149 return false; 0150 } 0151 const QStringList fileNames = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); 0152 for (const QString &fileName : fileNames) { 0153 if (!removeRecursively(filePath + QLatin1Char('/') + fileName)) { 0154 return false; 0155 } 0156 } 0157 if (!QDir::root().rmdir(dir.path())) { 0158 return false; 0159 } 0160 } else if (!QFile::remove(filePath)) { 0161 return false; 0162 } 0163 return true; 0164 } 0165 0166 bool QzTools::copyRecursively(const QString &sourcePath, const QString &targetPath) 0167 { 0168 const QFileInfo srcFileInfo(sourcePath); 0169 if (srcFileInfo.isDir() && !srcFileInfo.isSymLink()) { 0170 QDir targetDir(targetPath); 0171 targetDir.cdUp(); 0172 if (!targetDir.mkdir(QFileInfo(targetPath).fileName())) { 0173 return false; 0174 } 0175 const QStringList fileNames = QDir(sourcePath).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); 0176 for (const QString &fileName : fileNames) { 0177 const QString newSourcePath = sourcePath + QL1C('/') + fileName; 0178 const QString newTargetPath = targetPath + QL1C('/') + fileName; 0179 if (!copyRecursively(newSourcePath, newTargetPath)) { 0180 return false; 0181 } 0182 } 0183 #ifndef Q_OS_WIN 0184 } else if (srcFileInfo.isSymLink()) { 0185 const QByteArray pathData = sourcePath.toLocal8Bit(); 0186 char buf[1024]; 0187 ssize_t len = readlink(pathData.constData(), buf, sizeof(buf) - 1); 0188 if (len < 0) { 0189 qWarning() << "Error getting symlink path" << pathData; 0190 return false; 0191 } 0192 buf[len] = '\0'; 0193 return QFile::link(QString::fromLocal8Bit(buf), targetPath); 0194 #endif 0195 } else if (!QFile::copy(sourcePath, targetPath)) { 0196 return false; 0197 } 0198 return true; 0199 } 0200 0201 /* Finds same part of @one in @other from the beginning */ 0202 QString QzTools::samePartOfStrings(const QString &one, const QString &other) 0203 { 0204 int maxSize = qMin(one.size(), other.size()); 0205 if (maxSize <= 0) { 0206 return {}; 0207 } 0208 0209 int i = 0; 0210 while (one.at(i) == other.at(i)) { 0211 i++; 0212 if (i == maxSize) { 0213 break; 0214 } 0215 } 0216 return one.left(i); 0217 } 0218 0219 QString QzTools::urlEncodeQueryString(const QUrl &url) 0220 { 0221 QString returnString = url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment); 0222 0223 if (url.hasQuery()) { 0224 returnString += QLatin1Char('?') + url.query(QUrl::FullyEncoded); 0225 } 0226 0227 if (url.hasFragment()) { 0228 returnString += QLatin1Char('#') + url.fragment(QUrl::FullyEncoded); 0229 } 0230 0231 returnString.replace(QLatin1Char(' '), QLatin1String("%20")); 0232 0233 return returnString; 0234 } 0235 0236 QString QzTools::fromPunycode(const QString &str) 0237 { 0238 if (!str.startsWith(QL1S("xn--"))) 0239 return str; 0240 0241 // QUrl::fromAce will only decode domains from idn whitelist 0242 const QString decoded = QUrl::fromAce(str.toUtf8() + QByteArray(".org")); 0243 return decoded.left(decoded.size() - 4); 0244 } 0245 0246 QString QzTools::escapeSqlGlobString(QString urlString) 0247 { 0248 urlString.replace(QL1C('['), QStringLiteral("[[")); 0249 urlString.replace(QL1C(']'), QStringLiteral("[]]")); 0250 urlString.replace(QStringLiteral("[["), QStringLiteral("[[]")); 0251 urlString.replace(QL1C('*'), QStringLiteral("[*]")); 0252 urlString.replace(QL1C('?'), QStringLiteral("[?]")); 0253 return urlString; 0254 } 0255 0256 QString QzTools::ensureUniqueFilename(const QString &name, const QString &appendFormat) 0257 { 0258 Q_ASSERT(appendFormat.contains(QL1S("%1"))); 0259 0260 QFileInfo info(name); 0261 0262 if (!info.exists()) 0263 return name; 0264 0265 const QDir dir = info.absoluteDir(); 0266 const QString fileName = info.fileName(); 0267 0268 int i = 1; 0269 0270 while (info.exists()) { 0271 QString file = fileName; 0272 int index = file.lastIndexOf(QL1C('.')); 0273 const QString appendString = appendFormat.arg(i); 0274 if (index == -1) 0275 file.append(appendString); 0276 else 0277 file = file.left(index) + appendString + file.mid(index); 0278 info.setFile(dir, file); 0279 i++; 0280 } 0281 0282 return info.absoluteFilePath(); 0283 } 0284 0285 QString QzTools::getFileNameFromUrl(const QUrl &url) 0286 { 0287 QString fileName = url.toString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveScheme | QUrl::RemovePort); 0288 0289 if (fileName.endsWith(QLatin1Char('/'))) { 0290 fileName = fileName.mid(0, fileName.length() - 1); 0291 } 0292 0293 if (fileName.indexOf(QLatin1Char('/')) != -1) { 0294 int pos = fileName.lastIndexOf(QLatin1Char('/')); 0295 fileName = fileName.mid(pos); 0296 fileName.remove(QLatin1Char('/')); 0297 } 0298 0299 fileName = filterCharsFromFilename(fileName); 0300 0301 if (fileName.isEmpty()) { 0302 fileName = filterCharsFromFilename(url.host()); 0303 } 0304 0305 return fileName; 0306 } 0307 0308 QString QzTools::filterCharsFromFilename(const QString &name) 0309 { 0310 QString value = name; 0311 0312 value.replace(QLatin1Char('/'), QLatin1Char('-')); 0313 value.remove(QLatin1Char('\\')); 0314 value.remove(QLatin1Char(':')); 0315 value.remove(QLatin1Char('*')); 0316 value.remove(QLatin1Char('?')); 0317 value.remove(QLatin1Char('"')); 0318 value.remove(QLatin1Char('<')); 0319 value.remove(QLatin1Char('>')); 0320 value.remove(QLatin1Char('|')); 0321 0322 return value; 0323 } 0324 0325 QString QzTools::lastPathForFileDialog(const QString &dialogName, const QString &fallbackPath) 0326 { 0327 Settings settings; 0328 settings.beginGroup(QSL("LastFileDialogsPaths")); 0329 QString path = settings.value(QSL("FileDialogs/") + dialogName).toString(); 0330 settings.endGroup(); 0331 0332 return path.isEmpty() ? fallbackPath : path; 0333 } 0334 0335 void QzTools::saveLastPathForFileDialog(const QString &dialogName, const QString &path) 0336 { 0337 if (path.isEmpty()) { 0338 return; 0339 } 0340 0341 Settings settings; 0342 settings.beginGroup(QSL("LastFileDialogsPaths")); 0343 settings.setValue(dialogName, path); 0344 settings.endGroup(); 0345 } 0346 0347 QString QzTools::alignTextToWidth(const QString &string, const QString &text, const QFontMetrics &metrics, int width) 0348 { 0349 int pos = 0; 0350 QString returnString; 0351 0352 while (pos <= string.size()) { 0353 QString part = string.mid(pos); 0354 QString elidedLine = metrics.elidedText(part, Qt::ElideRight, width); 0355 0356 if (elidedLine.isEmpty()) { 0357 break; 0358 } 0359 0360 if (elidedLine.size() != part.size()) { 0361 elidedLine = elidedLine.left(elidedLine.size() - 3); 0362 } 0363 0364 if (!returnString.isEmpty()) { 0365 returnString += text; 0366 } 0367 0368 returnString += elidedLine; 0369 pos += elidedLine.size(); 0370 } 0371 0372 return returnString; 0373 } 0374 0375 QString QzTools::fileSizeToString(qint64 size) 0376 { 0377 if (size < 0) { 0378 return QObject::tr("Unknown size"); 0379 } 0380 0381 double _size = size / 1024.0; // KB 0382 if (_size < 1000) { 0383 return QString::number(_size > 1 ? _size : 1, 'f', 0) + QLatin1Char(' ') + QObject::tr("KB"); 0384 } 0385 0386 _size /= 1024; // MB 0387 if (_size < 1000) { 0388 return QString::number(_size, 'f', 1) + QLatin1Char(' ') + QObject::tr("MB"); 0389 } 0390 0391 _size /= 1024; // GB 0392 return QString::number(_size, 'f', 2) + QLatin1Char(' ') + QObject::tr("GB"); 0393 } 0394 0395 QPixmap QzTools::createPixmapForSite(const QIcon &icon, const QString &title, const QString &url) 0396 { 0397 const QFontMetricsF fontMetrics(QApplication::font()); 0398 const int padding = 4; 0399 const int maxWidth = fontMetrics.horizontalAdvance(title.length() > url.length() ? title : url) + 3 * padding + 16; 0400 const int width = qMin(maxWidth, 150); 0401 const int height = fontMetrics.height() * 2 + fontMetrics.leading() + 2 * padding; 0402 0403 QPixmap pixmap(width * qApp->devicePixelRatio(), height * qApp->devicePixelRatio()); 0404 pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); 0405 0406 QPainter painter(&pixmap); 0407 painter.setRenderHint(QPainter::Antialiasing); 0408 0409 // Draw background 0410 QPen pen(Qt::black); 0411 pen.setWidth(1); 0412 painter.setPen(pen); 0413 0414 QPainterPath path; 0415 path.addRect(QRect(0, 0, width, height)); 0416 0417 painter.fillPath(path, Qt::white); 0418 painter.drawPath(path); 0419 0420 // Draw icon 0421 QRect iconRect(padding, 0, 16, height); 0422 icon.paint(&painter, iconRect); 0423 0424 // Draw title 0425 QRect titleRect(iconRect.right() + padding, padding, width - padding - iconRect.right(), fontMetrics.height()); 0426 painter.drawText(titleRect, fontMetrics.elidedText(title, Qt::ElideRight, titleRect.width())); 0427 0428 // Draw url 0429 QRect urlRect(titleRect.x(), titleRect.bottom() + fontMetrics.leading(), titleRect.width(), titleRect.height()); 0430 painter.setPen(QApplication::palette().color(QPalette::Link)); 0431 painter.drawText(urlRect, fontMetrics.elidedText(url, Qt::ElideRight, urlRect.width())); 0432 0433 return pixmap; 0434 } 0435 0436 QString QzTools::applyDirectionToPage(QString &pageContents) 0437 { 0438 QString direction = QLatin1String("ltr"); 0439 QString right_str = QLatin1String("right"); 0440 QString left_str = QLatin1String("left"); 0441 0442 if (QApplication::isRightToLeft()) { 0443 direction = QLatin1String("rtl"); 0444 right_str = QLatin1String("left"); 0445 left_str = QLatin1String("right"); 0446 } 0447 0448 pageContents.replace(QLatin1String("%DIRECTION%"), direction); 0449 pageContents.replace(QLatin1String("%RIGHT_STR%"), right_str); 0450 pageContents.replace(QLatin1String("%LEFT_STR%"), left_str); 0451 0452 return pageContents; 0453 } 0454 0455 QString QzTools::truncatedText(const QString &text, int size) 0456 { 0457 if (text.length() > size) { 0458 return text.left(size) + QL1S(".."); 0459 } 0460 return text; 0461 } 0462 0463 // Thanks to http://www.qtcentre.org/threads/3205-Toplevel-widget-with-rounded-corners?p=17492#post17492 0464 QRegion QzTools::roundedRect(const QRect &rect, int radius) 0465 { 0466 QRegion region; 0467 0468 // middle and borders 0469 region += rect.adjusted(radius, 0, -radius, 0); 0470 region += rect.adjusted(0, radius, 0, -radius); 0471 0472 // top left 0473 QRect corner(rect.topLeft(), QSize(radius * 2, radius * 2)); 0474 region += QRegion(corner, QRegion::Ellipse); 0475 0476 // top right 0477 corner.moveTopRight(rect.topRight()); 0478 region += QRegion(corner, QRegion::Ellipse); 0479 0480 // bottom left 0481 corner.moveBottomLeft(rect.bottomLeft()); 0482 region += QRegion(corner, QRegion::Ellipse); 0483 0484 // bottom right 0485 corner.moveBottomRight(rect.bottomRight()); 0486 region += QRegion(corner, QRegion::Ellipse); 0487 0488 return region; 0489 } 0490 0491 QIcon QzTools::iconFromFileName(const QString &fileName) 0492 { 0493 static QHash<QString, QIcon> iconCache; 0494 0495 QFileInfo tempInfo(fileName); 0496 if (iconCache.contains(tempInfo.suffix())) { 0497 return iconCache.value(tempInfo.suffix()); 0498 } 0499 0500 QFileIconProvider iconProvider; 0501 QTemporaryFile tempFile(DataPaths::path(DataPaths::Temp) + QSL("/XXXXXX.") + tempInfo.suffix()); 0502 tempFile.open(); 0503 tempInfo.setFile(tempFile.fileName()); 0504 0505 QIcon icon(iconProvider.icon(tempInfo)); 0506 iconCache.insert(tempInfo.suffix(), icon); 0507 0508 return icon; 0509 } 0510 0511 QString QzTools::resolveFromPath(const QString &name) 0512 { 0513 const QString path = QString::fromUtf8(qgetenv("PATH").trimmed()); 0514 0515 if (path.isEmpty()) { 0516 return {}; 0517 } 0518 0519 const QStringList dirs = path.split(QLatin1Char(':'), Qt::SkipEmptyParts); 0520 0521 for (const QString &dir : dirs) { 0522 QDir d(dir); 0523 if (d.exists(name)) { 0524 return d.absoluteFilePath(name); 0525 } 0526 } 0527 0528 return QString(); 0529 } 0530 0531 // http://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c 0532 bool QzTools::isUtf8(const char* string) 0533 { 0534 if (!string) { 0535 return 0; 0536 } 0537 0538 const unsigned char* bytes = (const unsigned char*)string; 0539 while (*bytes) { 0540 if ((// ASCII 0541 bytes[0] == 0x09 || 0542 bytes[0] == 0x0A || 0543 bytes[0] == 0x0D || 0544 (0x20 <= bytes[0] && bytes[0] <= 0x7F) 0545 ) 0546 ) { 0547 bytes += 1; 0548 continue; 0549 } 0550 0551 if ((// non-overlong 2-byte 0552 (0xC2 <= bytes[0] && bytes[0] <= 0xDF) && 0553 (0x80 <= bytes[1] && bytes[1] <= 0xBF) 0554 ) 0555 ) { 0556 bytes += 2; 0557 continue; 0558 } 0559 0560 if ((// excluding overlongs 0561 bytes[0] == 0xE0 && 0562 (0xA0 <= bytes[1] && bytes[1] <= 0xBF) && 0563 (0x80 <= bytes[2] && bytes[2] <= 0xBF) 0564 ) || 0565 (// straight 3-byte 0566 ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) || 0567 bytes[0] == 0xEE || 0568 bytes[0] == 0xEF) && 0569 (0x80 <= bytes[1] && bytes[1] <= 0xBF) && 0570 (0x80 <= bytes[2] && bytes[2] <= 0xBF) 0571 ) || 0572 (// excluding surrogates 0573 bytes[0] == 0xED && 0574 (0x80 <= bytes[1] && bytes[1] <= 0x9F) && 0575 (0x80 <= bytes[2] && bytes[2] <= 0xBF) 0576 ) 0577 ) { 0578 bytes += 3; 0579 continue; 0580 } 0581 0582 if ((// planes 1-3 0583 bytes[0] == 0xF0 && 0584 (0x90 <= bytes[1] && bytes[1] <= 0xBF) && 0585 (0x80 <= bytes[2] && bytes[2] <= 0xBF) && 0586 (0x80 <= bytes[3] && bytes[3] <= 0xBF) 0587 ) || 0588 (// planes 4-15 0589 (0xF1 <= bytes[0] && bytes[0] <= 0xF3) && 0590 (0x80 <= bytes[1] && bytes[1] <= 0xBF) && 0591 (0x80 <= bytes[2] && bytes[2] <= 0xBF) && 0592 (0x80 <= bytes[3] && bytes[3] <= 0xBF) 0593 ) || 0594 (// plane 16 0595 bytes[0] == 0xF4 && 0596 (0x80 <= bytes[1] && bytes[1] <= 0x8F) && 0597 (0x80 <= bytes[2] && bytes[2] <= 0xBF) && 0598 (0x80 <= bytes[3] && bytes[3] <= 0xBF) 0599 ) 0600 ) { 0601 bytes += 4; 0602 continue; 0603 } 0604 0605 return false; 0606 } 0607 0608 return true; 0609 } 0610 0611 bool QzTools::containsSpace(const QString &str) 0612 { 0613 for (const QChar &c : str) { 0614 if (c.isSpace()) 0615 return true; 0616 } 0617 return false; 0618 } 0619 0620 QString QzTools::getExistingDirectory(const QString &name, QWidget* parent, const QString &caption, const QString &dir, QFileDialog::Options options) 0621 { 0622 Settings settings; 0623 settings.beginGroup(QSL("FileDialogPaths")); 0624 0625 QString lastDir = settings.value(name, dir).toString(); 0626 0627 QString path = QFileDialog::getExistingDirectory(parent, caption, lastDir, options); 0628 0629 if (!path.isEmpty()) { 0630 settings.setValue(name, QFileInfo(path).absolutePath()); 0631 } 0632 0633 settings.endGroup(); 0634 return path; 0635 } 0636 0637 static QString getFilename(const QString &path) 0638 { 0639 QFileInfo info(path); 0640 0641 if (info.isFile()) { 0642 return info.fileName(); 0643 } 0644 0645 if (info.isDir()) { 0646 return {}; 0647 } 0648 0649 if (info.dir().exists()) { 0650 return info.fileName(); 0651 } 0652 0653 return {}; 0654 } 0655 0656 QString QzTools::getOpenFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options) 0657 { 0658 Settings settings; 0659 settings.beginGroup(QSL("FileDialogPaths")); 0660 0661 QString lastDir = settings.value(name, QString()).toString(); 0662 QString fileName = getFilename(dir); 0663 0664 if (lastDir.isEmpty()) { 0665 lastDir = dir; 0666 } 0667 else { 0668 lastDir.append(QDir::separator() + fileName); 0669 } 0670 0671 QString path = QFileDialog::getOpenFileName(parent, caption, lastDir, filter, selectedFilter, options); 0672 0673 if (!path.isEmpty()) { 0674 settings.setValue(name, QFileInfo(path).absolutePath()); 0675 } 0676 0677 settings.endGroup(); 0678 return path; 0679 } 0680 0681 QStringList QzTools::getOpenFileNames(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options) 0682 { 0683 Settings settings; 0684 settings.beginGroup(QSL("FileDialogPaths")); 0685 0686 QString lastDir = settings.value(name, QString()).toString(); 0687 QString fileName = getFilename(dir); 0688 0689 if (lastDir.isEmpty()) { 0690 lastDir = dir; 0691 } 0692 else { 0693 lastDir.append(QDir::separator() + fileName); 0694 } 0695 0696 QStringList paths = QFileDialog::getOpenFileNames(parent, caption, lastDir, filter, selectedFilter, options); 0697 0698 if (!paths.isEmpty()) { 0699 settings.setValue(name, QFileInfo(paths.at(0)).absolutePath()); 0700 } 0701 0702 settings.endGroup(); 0703 return paths; 0704 } 0705 0706 QString QzTools::getSaveFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options) 0707 { 0708 Settings settings; 0709 settings.beginGroup(QSL("FileDialogPaths")); 0710 0711 QString lastDir = settings.value(name, QString()).toString(); 0712 QString fileName = getFilename(dir); 0713 0714 if (lastDir.isEmpty()) { 0715 lastDir = dir; 0716 } 0717 else { 0718 lastDir.append(QDir::separator() + fileName); 0719 } 0720 0721 QString path = QFileDialog::getSaveFileName(parent, caption, lastDir, filter, selectedFilter, options); 0722 0723 if (!path.isEmpty()) { 0724 settings.setValue(name, QFileInfo(path).absolutePath()); 0725 } 0726 0727 settings.endGroup(); 0728 return path; 0729 } 0730 0731 // Matches domain (assumes both pattern and domain not starting with dot) 0732 // pattern = domain to be matched 0733 // domain = site domain 0734 bool QzTools::matchDomain(const QString &pattern, const QString &domain) 0735 { 0736 if (pattern == domain) { 0737 return true; 0738 } 0739 0740 if (!domain.endsWith(pattern)) { 0741 return false; 0742 } 0743 0744 int index = domain.indexOf(pattern); 0745 0746 return index > 0 && domain[index - 1] == QLatin1Char('.'); 0747 } 0748 0749 QKeySequence QzTools::actionShortcut(const QKeySequence &shortcut, const QKeySequence &fallBack, const QKeySequence &shortcutRtl, const QKeySequence &fallbackRtl) 0750 { 0751 if (QApplication::isRightToLeft() && (!shortcutRtl.isEmpty() || !fallbackRtl.isEmpty())) 0752 return shortcutRtl.isEmpty() ? fallbackRtl : shortcutRtl; 0753 0754 return shortcut.isEmpty() ? fallBack : shortcut; 0755 } 0756 0757 static inline bool isQuote(const QChar &c) 0758 { 0759 return (c == QLatin1Char('"') || c == QLatin1Char('\'')); 0760 } 0761 0762 // Function splits command line into arguments 0763 // eg. /usr/bin/foo -o test -b "bar bar" -s="sed sed" 0764 // => '/usr/bin/foo' '-o' 'test' '-b' 'bar bar' '-s=sed sed' 0765 QStringList QzTools::splitCommandArguments(const QString &command) 0766 { 0767 QString line = command.trimmed(); 0768 0769 if (line.isEmpty()) { 0770 return {}; 0771 } 0772 0773 QChar SPACE(QL1C(' ')); 0774 QChar EQUAL(QL1C('=')); 0775 QChar BSLASH(QL1C('\\')); 0776 QChar QUOTE(QL1C('"')); 0777 QStringList r; 0778 0779 int equalPos = -1; // Position of = in opt="value" 0780 int startPos = isQuote(line.at(0)) ? 1 : 0; 0781 bool inWord = !isQuote(line.at(0)); 0782 bool inQuote = !inWord; 0783 0784 if (inQuote) { 0785 QUOTE = line.at(0); 0786 } 0787 0788 const int strlen = line.length(); 0789 for (int i = 0; i < strlen; ++i) { 0790 const QChar c = line.at(i); 0791 0792 if (inQuote && c == QUOTE && i > 0 && line.at(i - 1) != BSLASH) { 0793 QString str = line.mid(startPos, i - startPos); 0794 if (equalPos > -1) { 0795 str.remove(equalPos - startPos + 1, 1); 0796 } 0797 0798 inQuote = false; 0799 if (!str.isEmpty()) { 0800 r.append(str); 0801 } 0802 continue; 0803 } 0804 else if (!inQuote && isQuote(c)) { 0805 inQuote = true; 0806 QUOTE = c; 0807 0808 if (!inWord) { 0809 startPos = i + 1; 0810 } 0811 else if (i > 0 && line.at(i - 1) == EQUAL) { 0812 equalPos = i - 1; 0813 } 0814 } 0815 0816 if (inQuote) { 0817 continue; 0818 } 0819 0820 if (inWord && (c == SPACE || i == strlen - 1)) { 0821 int len = (i == strlen - 1) ? -1 : i - startPos; 0822 const QString str = line.mid(startPos, len); 0823 0824 inWord = false; 0825 if (!str.isEmpty()) { 0826 r.append(str); 0827 } 0828 } 0829 else if (!inWord && c != SPACE) { 0830 inWord = true; 0831 startPos = i; 0832 } 0833 } 0834 0835 // Unmatched quote 0836 if (inQuote) { 0837 return {}; 0838 } 0839 0840 return r; 0841 } 0842 0843 bool QzTools::startExternalProcess(const QString &executable, const QString &args) 0844 { 0845 const QStringList arguments = splitCommandArguments(args); 0846 0847 bool success = QProcess::startDetached(executable, arguments); 0848 0849 if (!success) { 0850 QString info = QSL("<ul><li><b>%1</b>%2</li><li><b>%3</b>%4</li></ul>"); 0851 info = info.arg(QObject::tr("Executable: "), executable, 0852 QObject::tr("Arguments: "), arguments.join(QLatin1Char(' '))); 0853 0854 QMessageBox::critical(nullptr, QObject::tr("Cannot start external program"), 0855 QObject::tr("Cannot start external program! %1").arg(info)); 0856 } 0857 0858 return success; 0859 } 0860 0861 #ifdef QZ_WS_X11 0862 static xcb_connection_t *getXcbConnection() 0863 { 0864 const QNativeInterface::QX11Application *x11App = qApp->nativeInterface<QNativeInterface::QX11Application>(); 0865 if (x11App == nullptr) 0866 return nullptr; 0867 return x11App->connection(); 0868 } 0869 #endif 0870 0871 void QzTools::setWmClass(const QString &name, const QWidget* widget) 0872 { 0873 #ifdef QZ_WS_X11 0874 if (QGuiApplication::platformName() != QL1S("xcb")) 0875 return; 0876 0877 xcb_connection_t *connection = getXcbConnection(); 0878 if (connection == nullptr) 0879 return; 0880 0881 const QByteArray nameData = name.toUtf8(); 0882 const QByteArray classData = mApp->wmClass().isEmpty() ? QByteArrayLiteral("Falkon") : mApp->wmClass(); 0883 0884 uint32_t class_len = nameData.length() + 1 + classData.length() + 1; 0885 char *class_hint = (char*) malloc(class_len); 0886 0887 qstrcpy(class_hint, nameData.constData()); 0888 qstrcpy(class_hint + nameData.length() + 1, classData.constData()); 0889 0890 xcb_change_property(connection, XCB_PROP_MODE_REPLACE, widget->winId(), 0891 XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, class_len, class_hint); 0892 0893 free(class_hint); 0894 0895 #else 0896 Q_UNUSED(name) 0897 Q_UNUSED(widget) 0898 #endif 0899 } 0900 0901 QString QzTools::operatingSystem() 0902 { 0903 #ifdef Q_OS_MACOS 0904 QString str = QSL("Mac OS X"); 0905 0906 SInt32 majorVersion; 0907 SInt32 minorVersion; 0908 0909 if (Gestalt(gestaltSystemVersionMajor, &majorVersion) == noErr && Gestalt(gestaltSystemVersionMinor, &minorVersion) == noErr) { 0910 str.append(QSL(" %1.%2").arg(majorVersion).arg(minorVersion)); 0911 } 0912 0913 return str; 0914 #endif 0915 #ifdef Q_OS_LINUX 0916 return QSL("Linux"); 0917 #endif 0918 #ifdef Q_OS_BSD4 0919 return QSL("BSD 4.4"); 0920 #endif 0921 #ifdef Q_OS_BSDI 0922 return QSL("BSD/OS"); 0923 #endif 0924 #ifdef Q_OS_FREEBSD 0925 return QSL("FreeBSD"); 0926 #endif 0927 #ifdef Q_OS_HPUX 0928 return QSL("HP-UX"); 0929 #endif 0930 #ifdef Q_OS_HURD 0931 return QSL("GNU Hurd"); 0932 #endif 0933 #ifdef Q_OS_LYNX 0934 return QSL("LynxOS"); 0935 #endif 0936 #ifdef Q_OS_NETBSD 0937 return QSL("NetBSD"); 0938 #endif 0939 #ifdef Q_OS_OS2 0940 return QSL("OS/2"); 0941 #endif 0942 #ifdef Q_OS_OPENBSD 0943 return QSL("OpenBSD"); 0944 #endif 0945 #ifdef Q_OS_OSF 0946 return QSL("HP Tru64 UNIX"); 0947 #endif 0948 #ifdef Q_OS_SOLARIS 0949 return QSL("Sun Solaris"); 0950 #endif 0951 #ifdef Q_OS_UNIXWARE 0952 return QSL("UnixWare 7 / Open UNIX 8"); 0953 #endif 0954 #ifdef Q_OS_UNIX 0955 return QSL("Unix"); 0956 #endif 0957 #ifdef Q_OS_HAIKU 0958 return QSL("Haiku"); 0959 #endif 0960 #ifdef Q_OS_WIN32 0961 QString str = QSL("Windows"); 0962 0963 switch (QSysInfo::windowsVersion()) { 0964 case QSysInfo::WV_NT: 0965 str.append(QSL(" NT")); 0966 break; 0967 0968 case QSysInfo::WV_2000: 0969 str.append(QSL(" 2000")); 0970 break; 0971 0972 case QSysInfo::WV_XP: 0973 str.append(QSL(" XP")); 0974 break; 0975 case QSysInfo::WV_2003: 0976 str.append(QSL(" XP Pro x64")); 0977 break; 0978 0979 case QSysInfo::WV_VISTA: 0980 str.append(QSL(" Vista")); 0981 break; 0982 0983 case QSysInfo::WV_WINDOWS7: 0984 str.append(QSL(" 7")); 0985 break; 0986 0987 case QSysInfo::WV_WINDOWS8: 0988 str.append(QSL(" 8")); 0989 break; 0990 0991 case QSysInfo::WV_WINDOWS8_1: 0992 str.append(QSL(" 8.1")); 0993 break; 0994 0995 case QSysInfo::WV_WINDOWS10: 0996 str.append(QSL(" 10")); 0997 break; 0998 0999 default: 1000 break; 1001 } 1002 1003 return str; 1004 #endif 1005 } 1006 1007 QString QzTools::cpuArchitecture() 1008 { 1009 return QSysInfo::currentCpuArchitecture(); 1010 } 1011 1012 QString QzTools::operatingSystemLong() 1013 { 1014 const QString arch = cpuArchitecture(); 1015 if (arch.isEmpty()) 1016 return QzTools::operatingSystem(); 1017 return QzTools::operatingSystem() + QSL(" ") + arch; 1018 } 1019 1020 void QzTools::paintDropIndicator(QWidget *widget, const QRect &r) 1021 { 1022 // Modified code from KFilePlacesView 1023 QColor color = widget->palette().brush(QPalette::Normal, QPalette::Highlight).color(); 1024 const int x = (r.left() + r.right()) / 2; 1025 const int thickness = qRound(r.width() / 2.0); 1026 int alpha = 255; 1027 const int alphaDec = alpha / (thickness + 1); 1028 QStylePainter p(widget); 1029 for (int i = 0; i < thickness; i++) { 1030 color.setAlpha(alpha); 1031 alpha -= alphaDec; 1032 p.setPen(color); 1033 p.drawLine(x - i, r.top(), x - i, r.bottom()); 1034 p.drawLine(x + i, r.top(), x + i, r.bottom()); 1035 } 1036 }