File indexing completed on 2024-05-05 04:19:16

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2008 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "gvcore.h"
0023 
0024 // Qt
0025 #include <QApplication>
0026 #include <QHBoxLayout>
0027 #include <QImageWriter>
0028 #include <QLabel>
0029 #include <QMimeDatabase>
0030 #include <QSpacerItem>
0031 #include <QSpinBox>
0032 #include <QUrl>
0033 
0034 // KF
0035 #include <KColorScheme>
0036 #include <KColorUtils>
0037 #include <KFileCustomDialog>
0038 #include <KFileWidget>
0039 #include <KLocalizedString>
0040 #include <KMessageBox>
0041 
0042 // Local
0043 #include "gwenview_app_debug.h"
0044 #include <lib/binder.h>
0045 #include <lib/document/documentfactory.h>
0046 #include <lib/document/documentjob.h>
0047 #include <lib/document/savejob.h>
0048 #include <lib/gwenviewconfig.h>
0049 #include <lib/historymodel.h>
0050 #include <lib/hud/hudbutton.h>
0051 #include <lib/hud/hudmessagebubble.h>
0052 #include <lib/mimetypeutils.h>
0053 #include <lib/recentfilesmodel.h>
0054 #include <lib/semanticinfo/semanticinfodirmodel.h>
0055 #include <lib/semanticinfo/sorteddirmodel.h>
0056 #include <lib/transformimageoperation.h>
0057 #include <mainwindow.h>
0058 #include <saveallhelper.h>
0059 #include <viewmainpage.h>
0060 
0061 namespace Gwenview
0062 {
0063 struct GvCorePrivate {
0064     GvCore *q = nullptr;
0065     MainWindow *mMainWindow = nullptr;
0066     SortedDirModel *mDirModel = nullptr;
0067     HistoryModel *mRecentFoldersModel = nullptr;
0068     RecentFilesModel *mRecentFilesModel = nullptr;
0069     QPalette mPalettes[4];
0070     QString mFullScreenPaletteName;
0071     int configFileJPEGQualityValue = GwenviewConfig::jPEGQuality();
0072 
0073     KFileCustomDialog *createSaveAsDialog(const QUrl &url)
0074     {
0075         // Build the JPEG quality chooser custom widget
0076         auto JPEGQualityChooserWidget = new QWidget;
0077         JPEGQualityChooserWidget->setVisible(false); // shown only for JPEGs
0078 
0079         auto JPEGQualityChooserLabel = new QLabel;
0080         JPEGQualityChooserLabel->setText(i18n("Image quality:"));
0081         JPEGQualityChooserLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0082 
0083         auto JPEGQualityChooserSpinBox = new QSpinBox;
0084         JPEGQualityChooserSpinBox->setMinimum(1);
0085         JPEGQualityChooserSpinBox->setMaximum(100);
0086         JPEGQualityChooserSpinBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0087         JPEGQualityChooserSpinBox->setSuffix(i18nc("Spinbox suffix; percentage 1 - 100", "%"));
0088         configFileJPEGQualityValue = GwenviewConfig::jPEGQuality();
0089         JPEGQualityChooserSpinBox->setValue(configFileJPEGQualityValue);
0090 
0091         // Temporarily change JPEG quality value
0092         QObject::connect(JPEGQualityChooserSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), JPEGQualityChooserSpinBox, [=](int value) {
0093             GwenviewConfig::setJPEGQuality(value);
0094         });
0095 
0096         auto horizontalSpacer = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Fixed);
0097 
0098         auto JPEGQualityChooserLayout = new QHBoxLayout(JPEGQualityChooserWidget);
0099         JPEGQualityChooserLayout->setContentsMargins(0, 0, 0, 0);
0100         JPEGQualityChooserLayout->addWidget(JPEGQualityChooserLabel);
0101         JPEGQualityChooserLayout->addWidget(JPEGQualityChooserSpinBox);
0102         JPEGQualityChooserLayout->addItem(horizontalSpacer);
0103 
0104         // Set up the dialog
0105         auto dialog = new KFileCustomDialog(mMainWindow);
0106         dialog->setAttribute(Qt::WA_DeleteOnClose);
0107         dialog->setModal(true);
0108         KFileWidget *fileWidget = dialog->fileWidget();
0109         dialog->setCustomWidget(JPEGQualityChooserWidget);
0110         fileWidget->setConfirmOverwrite(true);
0111         dialog->setOperationMode(KFileWidget::Saving);
0112         dialog->setWindowTitle(i18nc("@title:window", "Save Image"));
0113         // Temporary workaround for selectUrl() not setting the
0114         // initial directory to url (removed in D4193)
0115         dialog->setUrl(url.adjusted(QUrl::RemoveFilename));
0116         fileWidget->setSelectedUrl(url);
0117 
0118         QList<KFileFilter> filters;
0119         for (const QByteArray &mimeName : QImageWriter::supportedMimeTypes()) {
0120             filters << KFileFilter::fromMimeType(mimeName);
0121         }
0122 
0123         fileWidget->setFilters(filters, KFileFilter::fromMimeType(MimeTypeUtils::urlMimeType(url)));
0124 
0125         // Only show the lossy image quality chooser when saving a lossy image
0126         QObject::connect(fileWidget, &KFileWidget::filterChanged, JPEGQualityChooserWidget, [=](const KFileFilter &filter) {
0127             const bool hasQuality = std::any_of(filter.mimePatterns().constBegin(), filter.mimePatterns().constEnd(), [](const QString &mime) {
0128                 return mime.contains(QLatin1String("jpeg")) || mime.contains(QLatin1String("jxl")) || mime.contains(QLatin1String("webp"))
0129                     || mime.contains(QLatin1String("avif")) || mime.contains(QLatin1String("heif")) || mime.contains(QLatin1String("heic"));
0130             });
0131 
0132             JPEGQualityChooserWidget->setVisible(hasQuality);
0133         });
0134 
0135         return dialog;
0136     }
0137 
0138     void setupPalettes()
0139     {
0140         // Normal
0141         KSharedConfigPtr config = KSharedConfig::openConfig();
0142         mPalettes[GvCore::NormalPalette] = KColorScheme::createApplicationPalette(config);
0143         QPalette viewPalette = mPalettes[GvCore::NormalPalette];
0144 
0145         DocumentView::BackgroundColorMode colorMode = GwenviewConfig::backgroundColorMode();
0146         const bool usingLightTheme = qApp->palette().base().color().lightness() > qApp->palette().text().color().lightness();
0147 
0148         if ((usingLightTheme && colorMode == DocumentView::BackgroundColorMode::Dark)
0149             || (!usingLightTheme && colorMode == DocumentView::BackgroundColorMode::Light)) {
0150             viewPalette.setColor(QPalette::Base, mPalettes[GvCore::NormalPalette].color(QPalette::Text));
0151             viewPalette.setColor(QPalette::Text, mPalettes[GvCore::NormalPalette].color(QPalette::Base));
0152             viewPalette.setColor(QPalette::Window, mPalettes[GvCore::NormalPalette].color(QPalette::WindowText));
0153             viewPalette.setColor(QPalette::WindowText, mPalettes[GvCore::NormalPalette].color(QPalette::Window));
0154             viewPalette.setColor(QPalette::Button, mPalettes[GvCore::NormalPalette].color(QPalette::ButtonText));
0155             viewPalette.setColor(QPalette::ButtonText, mPalettes[GvCore::NormalPalette].color(QPalette::Button));
0156             viewPalette.setColor(QPalette::ToolTipBase, mPalettes[GvCore::NormalPalette].color(QPalette::ToolTipText));
0157             viewPalette.setColor(QPalette::ToolTipText, mPalettes[GvCore::NormalPalette].color(QPalette::ToolTipBase));
0158         } else if (colorMode == DocumentView::BackgroundColorMode::Neutral) {
0159             QColor base = KColorUtils::mix(mPalettes[GvCore::NormalPalette].color(QPalette::Base), mPalettes[GvCore::NormalPalette].color(QPalette::Text), 0.5);
0160             QColor window =
0161                 KColorUtils::mix(mPalettes[GvCore::NormalPalette].color(QPalette::Window), mPalettes[GvCore::NormalPalette].color(QPalette::WindowText), 0.5);
0162             QColor button =
0163                 KColorUtils::mix(mPalettes[GvCore::NormalPalette].color(QPalette::Button), mPalettes[GvCore::NormalPalette].color(QPalette::ButtonText), 0.5);
0164             QColor toolTipBase = KColorUtils::mix(mPalettes[GvCore::NormalPalette].color(QPalette::ToolTipBase),
0165                                                   mPalettes[GvCore::NormalPalette].color(QPalette::ToolTipText),
0166                                                   0.5);
0167             viewPalette.setColor(QPalette::Base, base);
0168             viewPalette.setColor(QPalette::Text, base.lightnessF() > 0.5 ? Qt::black : Qt::white);
0169             viewPalette.setColor(QPalette::Window, window);
0170             viewPalette.setColor(QPalette::WindowText, base.lightnessF() > 0.5 ? Qt::black : Qt::white);
0171             viewPalette.setColor(QPalette::Button, button);
0172             viewPalette.setColor(QPalette::ButtonText, base.lightnessF() > 0.5 ? Qt::black : Qt::white);
0173             viewPalette.setColor(QPalette::ToolTipBase, toolTipBase);
0174             viewPalette.setColor(QPalette::ToolTipText, base.lightnessF() > 0.5 ? Qt::black : Qt::white);
0175         }
0176 
0177         mPalettes[GvCore::NormalViewPalette] = viewPalette;
0178 
0179         // Fullscreen
0180         QString name = GwenviewConfig::fullScreenColorScheme();
0181         if (name.isEmpty()) {
0182             // Default color scheme
0183             mFullScreenPaletteName = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("color-schemes/fullscreen.colors"));
0184             config = KSharedConfig::openConfig(mFullScreenPaletteName);
0185         } else if (name.contains('/')) {
0186             // Full path to a .colors file
0187             mFullScreenPaletteName = name;
0188             config = KSharedConfig::openConfig(mFullScreenPaletteName);
0189         } else {
0190             // Standard KDE color scheme
0191             mFullScreenPaletteName = QStringLiteral("color-schemes/%1.colors").arg(name);
0192             config = KSharedConfig::openConfig(mFullScreenPaletteName, KConfig::FullConfig, QStandardPaths::AppDataLocation);
0193         }
0194         mPalettes[GvCore::FullScreenPalette] = KColorScheme::createApplicationPalette(config);
0195 
0196         // If we are using the default palette, adjust it to match the system color scheme
0197         if (name.isEmpty()) {
0198             adjustDefaultFullScreenPalette();
0199         }
0200 
0201         // FullScreenView has either a solid black color or a textured background
0202         viewPalette = mPalettes[GvCore::FullScreenPalette];
0203         QPixmap bgTexture(256, 256);
0204         if (Gwenview::GwenviewConfig::fullScreenBackground() == Gwenview::FullScreenBackground::Black) {
0205             bgTexture.fill(Qt::black);
0206         } else {
0207             QString path = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("images/background.png"));
0208             bgTexture = path;
0209         }
0210         viewPalette.setBrush(QPalette::Base, bgTexture);
0211         mPalettes[GvCore::FullScreenViewPalette] = viewPalette;
0212     }
0213 
0214     void adjustDefaultFullScreenPalette()
0215     {
0216         // The Fullscreen palette by default does not use the system color scheme, and therefore uses an 'accent' color
0217         // of blue. So for every color group/role combination that uses the accent color, we use a muted version of the
0218         // Normal palette. We also use the normal HighlightedText color so it properly contrasts with Highlight.
0219         const QPalette normalPal = mPalettes[GvCore::NormalPalette];
0220         QPalette fullscreenPal = mPalettes[GvCore::FullScreenPalette];
0221 
0222         // Colors from the normal palette (source of the system theme's accent color)
0223         const QColor normalToolTipBase = normalPal.color(QPalette::Normal, QPalette::ToolTipBase);
0224         const QColor normalToolTipText = normalPal.color(QPalette::Normal, QPalette::ToolTipText);
0225         const QColor normalHighlight = normalPal.color(QPalette::Normal, QPalette::Highlight);
0226         const QColor normalHighlightedText = normalPal.color(QPalette::Normal, QPalette::HighlightedText);
0227         const QColor normalLink = normalPal.color(QPalette::Normal, QPalette::Link);
0228         const QColor normalActiveToolTipBase = normalPal.color(QPalette::Active, QPalette::ToolTipBase);
0229         const QColor normalActiveToolTipText = normalPal.color(QPalette::Active, QPalette::ToolTipText);
0230         const QColor normalActiveHighlight = normalPal.color(QPalette::Active, QPalette::Highlight);
0231         const QColor normalActiveHighlightedText = normalPal.color(QPalette::Active, QPalette::HighlightedText);
0232         const QColor normalActiveLink = normalPal.color(QPalette::Active, QPalette::Link);
0233         const QColor normalDisabledToolTipBase = normalPal.color(QPalette::Disabled, QPalette::ToolTipBase);
0234         const QColor normalDisabledToolTipText = normalPal.color(QPalette::Disabled, QPalette::ToolTipText);
0235         // Note: Disabled Highlight missing as they do not use the accent color
0236         const QColor normalDisabledLink = normalPal.color(QPalette::Disabled, QPalette::Link);
0237         const QColor normalInactiveToolTipBase = normalPal.color(QPalette::Inactive, QPalette::ToolTipBase);
0238         const QColor normalInactiveToolTipText = normalPal.color(QPalette::Inactive, QPalette::ToolTipText);
0239         const QColor normalInactiveHighlight = normalPal.color(QPalette::Inactive, QPalette::Highlight);
0240         const QColor normalInactiveHighlightedText = normalPal.color(QPalette::Inactive, QPalette::HighlightedText);
0241         const QColor normalInactiveLink = normalPal.color(QPalette::Inactive, QPalette::Link);
0242 
0243         // Colors of the fullscreen palette which we will be modifying
0244         QColor fullScreenToolTipBase = fullscreenPal.color(QPalette::Normal, QPalette::ToolTipBase);
0245         QColor fullScreenToolTipText = fullscreenPal.color(QPalette::Normal, QPalette::ToolTipText);
0246         QColor fullScreenHighlight = fullscreenPal.color(QPalette::Normal, QPalette::Highlight);
0247         QColor fullScreenLink = fullscreenPal.color(QPalette::Normal, QPalette::Link);
0248         QColor fullScreenActiveToolTipBase = fullscreenPal.color(QPalette::Active, QPalette::ToolTipBase);
0249         QColor fullScreenActiveToolTipText = fullscreenPal.color(QPalette::Active, QPalette::ToolTipText);
0250         QColor fullScreenActiveHighlight = fullscreenPal.color(QPalette::Active, QPalette::Highlight);
0251         QColor fullScreenActiveLink = fullscreenPal.color(QPalette::Active, QPalette::Link);
0252         QColor fullScreenDisabledToolTipBase = fullscreenPal.color(QPalette::Disabled, QPalette::ToolTipBase);
0253         QColor fullScreenDisabledToolTipText = fullscreenPal.color(QPalette::Disabled, QPalette::ToolTipText);
0254         QColor fullScreenDisabledLink = fullscreenPal.color(QPalette::Disabled, QPalette::Link);
0255         QColor fullScreenInactiveToolTipBase = fullscreenPal.color(QPalette::Inactive, QPalette::ToolTipBase);
0256         QColor fullScreenInactiveToolTipText = fullscreenPal.color(QPalette::Inactive, QPalette::ToolTipText);
0257         QColor fullScreenInactiveHighlight = fullscreenPal.color(QPalette::Inactive, QPalette::Highlight);
0258         QColor fullScreenInactiveLink = fullscreenPal.color(QPalette::Inactive, QPalette::Link);
0259 
0260         // Adjust the value of the normal color so it's not too dark/bright, and apply to the respective fullscreen color
0261         fullScreenToolTipBase.setHsv(normalToolTipBase.hue(), normalToolTipBase.saturation(), (127 + 2 * normalToolTipBase.value()) / 3);
0262         fullScreenToolTipText.setHsv(normalToolTipText.hue(), normalToolTipText.saturation(), (127 + 2 * normalToolTipText.value()) / 3);
0263         fullScreenHighlight.setHsv(normalHighlight.hue(), normalHighlight.saturation(), (127 + 2 * normalHighlight.value()) / 3);
0264         fullScreenLink.setHsv(normalLink.hue(), normalLink.saturation(), (127 + 2 * normalLink.value()) / 3);
0265         fullScreenActiveToolTipBase.setHsv(normalActiveToolTipBase.hue(),
0266                                            normalActiveToolTipBase.saturation(),
0267                                            (127 + 2 * normalActiveToolTipBase.value()) / 3);
0268         fullScreenActiveToolTipText.setHsv(normalActiveToolTipText.hue(),
0269                                            normalActiveToolTipText.saturation(),
0270                                            (127 + 2 * normalActiveToolTipText.value()) / 3);
0271         fullScreenActiveHighlight.setHsv(normalActiveHighlight.hue(), normalActiveHighlight.saturation(), (127 + 2 * normalActiveHighlight.value()) / 3);
0272         fullScreenActiveLink.setHsv(normalActiveLink.hue(), normalActiveLink.saturation(), (127 + 2 * normalActiveLink.value()) / 3);
0273         fullScreenDisabledToolTipBase.setHsv(normalDisabledToolTipBase.hue(),
0274                                              normalDisabledToolTipBase.saturation(),
0275                                              (127 + 2 * normalDisabledToolTipBase.value()) / 3);
0276         fullScreenDisabledToolTipText.setHsv(normalDisabledToolTipText.hue(),
0277                                              normalDisabledToolTipText.saturation(),
0278                                              (127 + 2 * normalDisabledToolTipText.value()) / 3);
0279         fullScreenDisabledLink.setHsv(normalDisabledLink.hue(), normalDisabledLink.saturation(), (127 + 2 * normalDisabledLink.value()) / 3);
0280         fullScreenInactiveToolTipBase.setHsv(normalInactiveToolTipBase.hue(),
0281                                              normalInactiveToolTipBase.saturation(),
0282                                              (127 + 2 * normalInactiveToolTipBase.value()) / 3);
0283         fullScreenInactiveToolTipText.setHsv(normalInactiveToolTipText.hue(),
0284                                              normalInactiveToolTipText.saturation(),
0285                                              (127 + 2 * normalInactiveToolTipText.value()) / 3);
0286         fullScreenInactiveHighlight.setHsv(normalInactiveHighlight.hue(),
0287                                            normalInactiveHighlight.saturation(),
0288                                            (127 + 2 * normalInactiveHighlight.value()) / 3);
0289         fullScreenInactiveLink.setHsv(normalInactiveLink.hue(), normalInactiveLink.saturation(), (127 + 2 * normalInactiveLink.value()) / 3);
0290 
0291         // Apply the modified colors to the fullscreen palette
0292         fullscreenPal.setColor(QPalette::Normal, QPalette::ToolTipBase, fullScreenToolTipBase);
0293         fullscreenPal.setColor(QPalette::Normal, QPalette::ToolTipText, fullScreenToolTipText);
0294         fullscreenPal.setColor(QPalette::Normal, QPalette::Highlight, fullScreenHighlight);
0295         fullscreenPal.setColor(QPalette::Normal, QPalette::Link, fullScreenLink);
0296         fullscreenPal.setColor(QPalette::Active, QPalette::ToolTipBase, fullScreenActiveToolTipBase);
0297         fullscreenPal.setColor(QPalette::Active, QPalette::ToolTipText, fullScreenActiveToolTipText);
0298         fullscreenPal.setColor(QPalette::Active, QPalette::Highlight, fullScreenActiveHighlight);
0299         fullscreenPal.setColor(QPalette::Active, QPalette::Link, fullScreenActiveLink);
0300         fullscreenPal.setColor(QPalette::Disabled, QPalette::ToolTipBase, fullScreenDisabledToolTipBase);
0301         fullscreenPal.setColor(QPalette::Disabled, QPalette::ToolTipText, fullScreenDisabledToolTipText);
0302         fullscreenPal.setColor(QPalette::Disabled, QPalette::Link, fullScreenDisabledLink);
0303         fullscreenPal.setColor(QPalette::Inactive, QPalette::ToolTipBase, fullScreenInactiveToolTipBase);
0304         fullscreenPal.setColor(QPalette::Inactive, QPalette::ToolTipText, fullScreenInactiveToolTipText);
0305         fullscreenPal.setColor(QPalette::Inactive, QPalette::Highlight, fullScreenInactiveHighlight);
0306         fullscreenPal.setColor(QPalette::Inactive, QPalette::Link, fullScreenInactiveLink);
0307 
0308         // Since we use an adjusted version of the normal highlight color, we need to use the normal version of the
0309         // text color so it contrasts
0310         fullscreenPal.setColor(QPalette::Normal, QPalette::HighlightedText, normalHighlightedText);
0311         fullscreenPal.setColor(QPalette::Active, QPalette::HighlightedText, normalActiveHighlightedText);
0312         fullscreenPal.setColor(QPalette::Inactive, QPalette::HighlightedText, normalInactiveHighlightedText);
0313 
0314         mPalettes[GvCore::FullScreenPalette] = fullscreenPal;
0315     }
0316 };
0317 
0318 GvCore::GvCore(MainWindow *mainWindow, SortedDirModel *dirModel)
0319     : QObject(mainWindow)
0320     , d(new GvCorePrivate)
0321 {
0322     d->q = this;
0323     d->mMainWindow = mainWindow;
0324     d->mDirModel = dirModel;
0325     d->mRecentFoldersModel = nullptr;
0326     d->mRecentFilesModel = nullptr;
0327 
0328     d->setupPalettes();
0329 
0330     connect(GwenviewConfig::self(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0331     connect(qApp, &QApplication::paletteChanged, this, [this]() {
0332         d->setupPalettes();
0333     });
0334 }
0335 
0336 GvCore::~GvCore()
0337 {
0338     delete d;
0339 }
0340 
0341 QAbstractItemModel *GvCore::recentFoldersModel() const
0342 {
0343     if (!d->mRecentFoldersModel) {
0344         d->mRecentFoldersModel =
0345             new HistoryModel(const_cast<GvCore *>(this), QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/recentfolders/");
0346     }
0347     return d->mRecentFoldersModel;
0348 }
0349 
0350 QAbstractItemModel *GvCore::recentFilesModel() const
0351 {
0352     if (!d->mRecentFilesModel) {
0353         d->mRecentFilesModel = new RecentFilesModel(const_cast<GvCore *>(this));
0354     }
0355     return d->mRecentFilesModel;
0356 }
0357 
0358 AbstractSemanticInfoBackEnd *GvCore::semanticInfoBackEnd() const
0359 {
0360     return d->mDirModel->semanticInfoBackEnd();
0361 }
0362 
0363 SortedDirModel *GvCore::sortedDirModel() const
0364 {
0365     return d->mDirModel;
0366 }
0367 
0368 void GvCore::addUrlToRecentFolders(QUrl url)
0369 {
0370     if (!GwenviewConfig::historyEnabled()) {
0371         return;
0372     }
0373     if (!url.isValid()) {
0374         return;
0375     }
0376 
0377     // For "sftp://localhost", "/" is a different path than "" (bug #312060)
0378     if (!url.path().isEmpty() && !url.path().endsWith('/')) {
0379         url.setPath(url.path() + '/');
0380     }
0381 
0382     recentFoldersModel();
0383     d->mRecentFoldersModel->addUrl(url);
0384 }
0385 
0386 void GvCore::addUrlToRecentFiles(const QUrl &url)
0387 {
0388     if (!GwenviewConfig::historyEnabled()) {
0389         return;
0390     }
0391     recentFilesModel();
0392     d->mRecentFilesModel->addUrl(url);
0393 }
0394 
0395 void GvCore::saveAll()
0396 {
0397     SaveAllHelper helper(d->mMainWindow);
0398     helper.save();
0399 }
0400 
0401 void GvCore::save(const QUrl &url)
0402 {
0403     Document::Ptr doc = DocumentFactory::instance()->load(url);
0404     QByteArray format = doc->format();
0405     const QByteArrayList availableTypes = QImageWriter::supportedImageFormats();
0406     if (availableTypes.contains(format)) {
0407         DocumentJob *job = doc->save(url, format);
0408         connect(job, SIGNAL(result(KJob *)), SLOT(slotSaveResult(KJob *)));
0409     } else {
0410         // We don't know how to save in 'format', ask the user for a format we can
0411         // write to.
0412         KGuiItem saveUsingAnotherFormat = KStandardGuiItem::saveAs();
0413         saveUsingAnotherFormat.setText(i18n("Save using another format"));
0414         int result = KMessageBox::warningContinueCancel(d->mMainWindow,
0415                                                         i18n("Gwenview cannot save images in '%1' format.", QString(format)),
0416                                                         QString() /* caption */,
0417                                                         saveUsingAnotherFormat);
0418         if (result == KMessageBox::Continue) {
0419             saveAs(url);
0420         }
0421     }
0422 }
0423 
0424 void GvCore::saveAs(const QUrl &url)
0425 {
0426     KFileCustomDialog *dialog = d->createSaveAsDialog(url);
0427 
0428     connect(dialog, &QDialog::accepted, this, [=]() {
0429         KFileWidget *fileWidget = dialog->fileWidget();
0430 
0431         const QList<QUrl> files = fileWidget->selectedUrls();
0432         if (files.isEmpty()) {
0433             return;
0434         }
0435 
0436         const QString filename = files.at(0).fileName();
0437 
0438         const QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
0439         QByteArray format;
0440         if (mimeType.isValid()) {
0441             format = mimeType.preferredSuffix().toLocal8Bit();
0442         } else {
0443             KMessageBox::error(d->mMainWindow, i18nc("@info", "Gwenview cannot save images as %1.", QFileInfo(filename).suffix()));
0444         }
0445 
0446         QUrl saveAsUrl = fileWidget->selectedUrls().constFirst();
0447 
0448         if (format == "jpg") {
0449             // Gwenview code assumes JPEG images have "jpeg" format, so if the
0450             // dialog returned the format "jpg", use "jpeg" instead
0451             // This does not affect the actual filename extension
0452             format = "jpeg";
0453         }
0454 
0455         // Start save
0456         Document::Ptr doc = DocumentFactory::instance()->load(url);
0457         KJob *job = doc->save(saveAsUrl, format);
0458         if (!job) {
0459             const QString saveName = saveAsUrl.fileName();
0460             const QString name = !saveName.isEmpty() ? saveName : saveAsUrl.toDisplayString();
0461             const QString msg = xi18nc("@info", "<emphasis strong='true'>Saving <filename>%1</filename> failed:</emphasis><nl />%2", name, doc->errorString());
0462             KMessageBox::error(QApplication::activeWindow(), msg);
0463         } else {
0464             // Regardless of job result, reset JPEG config value if it was changed by
0465             // the Save As dialog
0466             connect(job, &KJob::result, this, [=]() {
0467                 if (GwenviewConfig::jPEGQuality() != d->configFileJPEGQualityValue) {
0468                     GwenviewConfig::setJPEGQuality(d->configFileJPEGQualityValue);
0469                 }
0470             });
0471 
0472             connect(job, &KJob::result, this, &GvCore::slotSaveResult);
0473         }
0474     });
0475 
0476     dialog->show();
0477 }
0478 
0479 static void applyTransform(const QUrl &url, Orientation orientation)
0480 {
0481     auto op = new TransformImageOperation(orientation);
0482     Document::Ptr doc = DocumentFactory::instance()->load(url);
0483     op->applyToDocument(doc);
0484 }
0485 
0486 void GvCore::slotSaveResult(KJob *_job)
0487 {
0488     auto job = static_cast<SaveJob *>(_job);
0489     QUrl oldUrl = job->oldUrl();
0490     QUrl newUrl = job->newUrl();
0491 
0492     if (job->error()) {
0493         QString name = newUrl.fileName().isEmpty() ? newUrl.toDisplayString() : newUrl.fileName();
0494         const QString msg =
0495             xi18nc("@info", "<emphasis strong='true'>Saving <filename>%1</filename> failed:</emphasis><nl />%2", name, kxi18n(qPrintable(job->errorString())));
0496 
0497         int result = KMessageBox::warningContinueCancel(d->mMainWindow, msg, QString() /* caption */, KStandardGuiItem::saveAs());
0498 
0499         if (result == KMessageBox::Continue) {
0500             saveAs(oldUrl);
0501         }
0502         return;
0503     }
0504 
0505     if (oldUrl != newUrl) {
0506         d->mMainWindow->goToUrl(newUrl);
0507 
0508         ViewMainPage *page = d->mMainWindow->viewMainPage();
0509         if (page->isVisible()) {
0510             auto bubble = new HudMessageBubble();
0511             bubble->setText(i18n("You are now viewing the new document."));
0512             KGuiItem item = KStandardGuiItem::back();
0513             item.setText(i18n("Go back to the original"));
0514             HudButton *button = bubble->addButton(item);
0515 
0516             BinderRef<MainWindow, QUrl>::bind(button, SIGNAL(clicked()), d->mMainWindow, &MainWindow::goToUrl, oldUrl);
0517             connect(button, SIGNAL(clicked()), bubble, SLOT(deleteLater()));
0518 
0519             page->showMessageWidget(bubble);
0520         }
0521     }
0522 }
0523 
0524 void GvCore::rotateLeft(const QUrl &url)
0525 {
0526     applyTransform(url, ROT_270);
0527 }
0528 
0529 void GvCore::rotateRight(const QUrl &url)
0530 {
0531     applyTransform(url, ROT_90);
0532 }
0533 
0534 void GvCore::setRating(const QUrl &url, int rating)
0535 {
0536     QModelIndex index = d->mDirModel->indexForUrl(url);
0537     if (!index.isValid()) {
0538         qCWarning(GWENVIEW_APP_LOG) << "invalid index!";
0539         return;
0540     }
0541     d->mDirModel->setData(index, rating, SemanticInfoDirModel::RatingRole);
0542 }
0543 
0544 static void clearModel(QAbstractItemModel *model)
0545 {
0546     model->removeRows(0, model->rowCount());
0547 }
0548 
0549 void GvCore::clearRecentFilesAndFolders()
0550 {
0551     clearModel(recentFilesModel());
0552     clearModel(recentFoldersModel());
0553 }
0554 
0555 void GvCore::slotConfigChanged()
0556 {
0557     if (!GwenviewConfig::historyEnabled()) {
0558         clearRecentFilesAndFolders();
0559     }
0560     d->setupPalettes();
0561 }
0562 
0563 QPalette GvCore::palette(GvCore::PaletteType type) const
0564 {
0565     return d->mPalettes[type];
0566 }
0567 
0568 QString GvCore::fullScreenPaletteName() const
0569 {
0570     return d->mFullScreenPaletteName;
0571 }
0572 
0573 void GvCore::setTrackFileManagerSorting(bool enable)
0574 {
0575     sortingTracksFileManager = enable;
0576 }
0577 
0578 bool GvCore::trackFileManagerSorting()
0579 {
0580     return sortingTracksFileManager;
0581 }
0582 
0583 } // namespace
0584 
0585 #include "moc_gvcore.cpp"