File indexing completed on 2024-05-12 04:58:18

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2017 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 "speeddial.h"
0019 #include "pagethumbnailer.h"
0020 #include "settings.h"
0021 #include "datapaths.h"
0022 #include "qztools.h"
0023 #include "autosaver.h"
0024 
0025 #include <QDir>
0026 #include <QCryptographicHash>
0027 #include <QFileDialog>
0028 #include <QImage>
0029 #include <QJsonDocument>
0030 
0031 #define ENSURE_LOADED if (!m_loaded) loadSettings();
0032 
0033 SpeedDial::SpeedDial(QObject* parent)
0034     : QObject(parent)
0035     , m_maxPagesInRow(4)
0036     , m_sizeOfSpeedDials(231)
0037     , m_sdcentered(false)
0038     , m_loaded(false)
0039     , m_regenerateScript(true)
0040 {
0041     m_autoSaver = new AutoSaver(this);
0042     connect(m_autoSaver, &AutoSaver::save, this, &SpeedDial::saveSettings);
0043     connect(this, &SpeedDial::pagesChanged, m_autoSaver, &AutoSaver::changeOccurred);
0044 }
0045 
0046 SpeedDial::~SpeedDial()
0047 {
0048     m_autoSaver->saveIfNecessary();
0049 }
0050 
0051 void SpeedDial::loadSettings()
0052 {
0053     m_loaded = true;
0054 
0055     Settings settings;
0056     settings.beginGroup(QSL("SpeedDial"));
0057     QString allPages = settings.value(QSL("pages"), QString()).toString();
0058     setBackgroundImage(settings.value(QSL("background"), QString()).toString());
0059     m_backgroundImageSize = settings.value(QSL("backsize"), QSL("auto")).toString();
0060     m_maxPagesInRow = settings.value(QSL("pagesrow"), 4).toInt();
0061     m_sizeOfSpeedDials = settings.value(QSL("sdsize"), 231).toInt();
0062     m_sdcentered = settings.value(QSL("sdcenter"), false).toBool();
0063     m_lockDials = settings.value(QSL("lock-dials"), false).toBool();
0064     settings.endGroup();
0065 
0066     if (allPages.isEmpty()) {
0067         allPages = QL1S("url:\"https://www.falkon.org\"|title:\"Falkon\";"
0068                         "url:\"https://store.falkon.org\"|title:\"Falkon Store\";"
0069                         "url:\"https://www.kde.org\"|title:\"KDE Planet\";"
0070                         "url:\"https://planet.kde.org\"|title:\"KDE Community\";");
0071     }
0072     changed(allPages);
0073 
0074     m_thumbnailsDir = DataPaths::currentProfilePath() + QSL("/thumbnails/");
0075 
0076     // If needed, create thumbnails directory
0077     if (!QDir(m_thumbnailsDir).exists()) {
0078         QDir(DataPaths::currentProfilePath()).mkdir(QSL("thumbnails"));
0079     }
0080 }
0081 
0082 void SpeedDial::saveSettings()
0083 {
0084     ENSURE_LOADED;
0085 
0086     Settings settings;
0087     settings.beginGroup(QSL("SpeedDial"));
0088     settings.setValue(QSL("pages"), generateAllPages());
0089     settings.setValue(QSL("background"), m_backgroundImageUrl);
0090     settings.setValue(QSL("backsize"), m_backgroundImageSize);
0091     settings.setValue(QSL("pagesrow"), m_maxPagesInRow);
0092     settings.setValue(QSL("sdsize"), m_sizeOfSpeedDials);
0093     settings.setValue(QSL("sdcenter"), m_sdcentered);
0094     settings.setValue(QSL("lock-dials"), m_lockDials);
0095     settings.endGroup();
0096 }
0097 
0098 SpeedDial::Page SpeedDial::pageForUrl(const QUrl &url)
0099 {
0100     ENSURE_LOADED;
0101 
0102     QString urlString = url.toString();
0103     if (urlString.endsWith(QL1C('/')))
0104         urlString = urlString.left(urlString.size() - 1);
0105 
0106     for (const Page &page : std::as_const(m_pages)) {
0107         if (page.url == urlString) {
0108             return page;
0109         }
0110     }
0111 
0112     return {};
0113 }
0114 
0115 QUrl SpeedDial::urlForShortcut(int key)
0116 {
0117     ENSURE_LOADED;
0118 
0119     if (key < 0 || m_pages.count() <= key) {
0120         return {};
0121     }
0122 
0123     return QUrl::fromEncoded(m_pages.at(key).url.toUtf8());
0124 }
0125 
0126 void SpeedDial::addPage(const QUrl &url, const QString &title)
0127 {
0128     ENSURE_LOADED;
0129 
0130     if (url.isEmpty()) {
0131         return;
0132     }
0133 
0134     Page page;
0135     page.title = escapeTitle(title);
0136     page.url = escapeUrl(url.toString());
0137 
0138     m_pages.append(page);
0139     m_regenerateScript = true;
0140 
0141     Q_EMIT pagesChanged();
0142 }
0143 
0144 void SpeedDial::removePage(const Page &page)
0145 {
0146     ENSURE_LOADED;
0147 
0148     if (!page.isValid()) {
0149         return;
0150     }
0151 
0152     removeImageForUrl(page.url);
0153     m_pages.removeAll(page);
0154     m_regenerateScript = true;
0155 
0156     Q_EMIT pagesChanged();
0157 }
0158 
0159 int SpeedDial::pagesInRow()
0160 {
0161     ENSURE_LOADED;
0162 
0163     return m_maxPagesInRow;
0164 }
0165 
0166 int SpeedDial::sdSize()
0167 {
0168     ENSURE_LOADED;
0169 
0170     return m_sizeOfSpeedDials;
0171 }
0172 
0173 bool SpeedDial::sdCenter()
0174 {
0175     ENSURE_LOADED;
0176 
0177     return m_sdcentered;
0178 }
0179 
0180 bool SpeedDial::lockDials()
0181 {
0182     ENSURE_LOADED;
0183 
0184     return m_lockDials;
0185 }
0186 
0187 QString SpeedDial::backgroundImage()
0188 {
0189     ENSURE_LOADED;
0190 
0191     return m_backgroundImage;
0192 }
0193 
0194 QString SpeedDial::backgroundImageUrl()
0195 {
0196     return m_backgroundImageUrl;
0197 }
0198 
0199 QString SpeedDial::backgroundImageSize()
0200 {
0201     ENSURE_LOADED;
0202 
0203     return m_backgroundImageSize;
0204 }
0205 
0206 QString SpeedDial::initialScript()
0207 {
0208     ENSURE_LOADED;
0209 
0210     if (!m_regenerateScript) {
0211         return m_initialScript;
0212     }
0213 
0214     m_regenerateScript = false;
0215     m_initialScript.clear();
0216 
0217     QVariantList pages;
0218 
0219     for (const Page &page : std::as_const(m_pages)) {
0220         QString imgSource = m_thumbnailsDir + QString::fromLatin1(QCryptographicHash::hash(page.url.toUtf8(), QCryptographicHash::Md4).toHex()) + QSL(".png");
0221 
0222         if (!QFile(imgSource).exists()) {
0223             imgSource = QSL("qrc:html/loading.gif");
0224 
0225             if (!page.isValid()) {
0226                 imgSource.clear();
0227             }
0228         }
0229         else {
0230             imgSource = QzTools::pixmapToDataUrl(QPixmap(imgSource)).toString();
0231         }
0232 
0233         QVariantMap map;
0234         map[QSL("url")] = page.url;
0235         map[QSL("title")] = page.title;
0236         map[QSL("img")] = imgSource;
0237         pages.append(map);
0238     }
0239 
0240     m_initialScript = QString::fromUtf8(QJsonDocument::fromVariant(pages).toJson(QJsonDocument::Compact));
0241     return m_initialScript;
0242 }
0243 
0244 void SpeedDial::changed(const QString &allPages)
0245 {
0246     const QStringList entries = allPages.split(QLatin1String("\";"), Qt::SkipEmptyParts);
0247     m_pages.clear();
0248 
0249     for (const QString &entry : entries) {
0250         if (entry.isEmpty()) {
0251             continue;
0252         }
0253 
0254         const QStringList tmp = entry.split(QLatin1String("\"|"), Qt::SkipEmptyParts);
0255         if (tmp.count() != 2) {
0256             continue;
0257         }
0258 
0259         Page page;
0260         page.url = tmp.at(0).mid(5);
0261         page.title = tmp.at(1).mid(7);
0262 
0263         if (page.url.endsWith(QL1C('/')))
0264             page.url = page.url.left(page.url.size() - 1);
0265 
0266         m_pages.append(page);
0267     }
0268 
0269     m_regenerateScript = true;
0270     Q_EMIT pagesChanged();
0271 }
0272 
0273 void SpeedDial::loadThumbnail(const QString &url, bool loadTitle)
0274 {
0275     auto* thumbnailer = new PageThumbnailer(this);
0276     thumbnailer->setUrl(QUrl::fromEncoded(url.toUtf8()));
0277     thumbnailer->setLoadTitle(loadTitle);
0278     connect(thumbnailer, &PageThumbnailer::thumbnailCreated, this, &SpeedDial::thumbnailCreated);
0279 
0280     thumbnailer->start();
0281 }
0282 
0283 void SpeedDial::removeImageForUrl(const QString &url)
0284 {
0285     QString fileName = m_thumbnailsDir + QString::fromLatin1(QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md4).toHex()) + QSL(".png");
0286 
0287     if (QFile(fileName).exists()) {
0288         QFile(fileName).remove();
0289     }
0290 }
0291 
0292 QStringList SpeedDial::getOpenFileName()
0293 {
0294     const QString fileTypes = QSL("%3(*.png *.jpg *.jpeg *.bmp *.gif *.svg *.tiff)").arg(tr("Image files"));
0295     const QString image = QzTools::getOpenFileName(QSL("SpeedDial-GetOpenFileName"), nullptr, tr("Click to select image..."), QDir::homePath(), fileTypes);
0296 
0297     if (image.isEmpty())
0298         return {};
0299 
0300     return {QzTools::pixmapToDataUrl(QPixmap(image)).toString(), QString::fromUtf8(QUrl::fromLocalFile(image).toEncoded())};
0301 }
0302 
0303 QString SpeedDial::urlFromUserInput(const QString &url)
0304 {
0305     return QUrl::fromUserInput(url).toString();
0306 }
0307 
0308 void SpeedDial::setBackgroundImage(const QString &image)
0309 {
0310     m_backgroundImage = QzTools::pixmapToDataUrl(QPixmap(QUrl(image).toLocalFile())).toString();
0311     m_backgroundImageUrl = image;
0312 }
0313 
0314 void SpeedDial::setBackgroundImageSize(const QString &size)
0315 {
0316     m_backgroundImageSize = size;
0317 }
0318 
0319 void SpeedDial::setPagesInRow(int count)
0320 {
0321     m_maxPagesInRow = count;
0322 }
0323 
0324 void SpeedDial::setSdSize(int count)
0325 {
0326     m_sizeOfSpeedDials = count;
0327 }
0328 
0329 void SpeedDial::setSdCentered(bool centered)
0330 {
0331     m_sdcentered = centered;
0332 
0333     m_autoSaver->changeOccurred();
0334 }
0335 
0336 void SpeedDial::setLockDials(bool lockDials)
0337 {
0338     m_lockDials = lockDials;
0339 }
0340 
0341 void SpeedDial::thumbnailCreated(const QPixmap &pixmap)
0342 {
0343     auto* thumbnailer = qobject_cast<PageThumbnailer*>(sender());
0344     if (!thumbnailer) {
0345         return;
0346     }
0347 
0348     bool loadTitle = thumbnailer->loadTitle();
0349     QString title = thumbnailer->title();
0350     QString url = thumbnailer->url().toString();
0351     QString fileName = m_thumbnailsDir + QString::fromLatin1(QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md4).toHex()) + QSL(".png");
0352 
0353     if (pixmap.isNull()) {
0354         fileName = QSL(":/html/broken-page.svg");
0355         title = tr("Unable to load");
0356     }
0357     else {
0358         if (!pixmap.save(fileName, "PNG")) {
0359             qWarning() << "SpeedDial::thumbnailCreated Cannot save thumbnail to " << fileName;
0360         }
0361         //fileName = QUrl::fromLocalFile(fileName).toString();
0362     }
0363 
0364     m_regenerateScript = true;
0365     thumbnailer->deleteLater();
0366 
0367     if (loadTitle)
0368         Q_EMIT pageTitleLoaded(url, title);
0369 
0370     Q_EMIT thumbnailLoaded(url, QzTools::pixmapToDataUrl(QPixmap(fileName)).toString());
0371 }
0372 
0373 QString SpeedDial::escapeTitle(QString title) const
0374 {
0375     title.replace(QLatin1Char('"'), QLatin1String("&quot;"));
0376     title.replace(QLatin1Char('\''), QLatin1String("&apos;"));
0377     return title;
0378 }
0379 
0380 QString SpeedDial::escapeUrl(QString url) const
0381 {
0382     url.remove(QLatin1Char('"'));
0383     url.remove(QLatin1Char('\''));
0384     return url;
0385 }
0386 
0387 QString SpeedDial::generateAllPages()
0388 {
0389     QString allPages;
0390 
0391     for (const Page &page : std::as_const(m_pages)) {
0392         const QString string = QSL(R"(url:"%1"|title:"%2";)").arg(page.url, page.title);
0393         allPages.append(string);
0394     }
0395 
0396     return allPages;
0397 }
0398 
0399 QList<SpeedDial::Page> SpeedDial::pages()
0400 {
0401     ENSURE_LOADED;
0402 
0403     return m_pages;
0404 }