gvcore.cpp 11.9 KB
Newer Older
1
// vim: set tabstop=4 shiftwidth=4 expandtab:
Aurélien Gâteau's avatar
Aurélien Gâteau committed
2 3
/*
Gwenview: an image viewer
Aurélien Gâteau's avatar
Aurélien Gâteau committed
4
Copyright 2008 Aurélien Gâteau <agateau@kde.org>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.

*/
// Self
#include "gvcore.h"

// Qt
25
#include <QApplication>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
26
#include <QUrl>
27 28
#include <QImageWriter>
#include <QMimeDatabase>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
29 30

// KDE
31
#include <QFileDialog>
32
#include <KColorScheme>
33 34
#include <KIO/StatJob>
#include <KJobWidgets>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
35
#include <KLocalizedString>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
36
#include <KMessageBox>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
37 38

// Local
39
#include <lib/binder.h>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
40
#include <lib/document/documentfactory.h>
41
#include <lib/document/documentjob.h>
42
#include <lib/document/savejob.h>
43
#include <lib/hud/hudbutton.h>
44
#include <lib/gwenviewconfig.h>
45
#include <lib/historymodel.h>
46
#include <lib/hud/hudmessagebubble.h>
47
#include <lib/mimetypeutils.h>
48
#include <lib/recentfilesmodel.h>
49 50
#include <lib/semanticinfo/semanticinfodirmodel.h>
#include <lib/semanticinfo/sorteddirmodel.h>
51
#include <lib/transformimageoperation.h>
52
#include <mainwindow.h>
53
#include <saveallhelper.h>
54
#include <viewmainpage.h>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
55

56 57
namespace Gwenview
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
58

Aurélien Gâteau's avatar
Aurélien Gâteau committed
59 60
struct GvCorePrivate
{
61 62 63 64
    GvCore* q;
    MainWindow* mMainWindow;
    SortedDirModel* mDirModel;
    HistoryModel* mRecentFoldersModel;
65
    RecentFilesModel* mRecentFilesModel;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
66
    QPalette mPalettes[4];
67
    QString mFullScreenPaletteName;
68

David Edmundson's avatar
David Edmundson committed
69
    bool showSaveAsDialog(const QUrl &url, QUrl* outUrl, QByteArray* format)
70
    {
71 72
        QFileDialog dialog(mMainWindow);
        dialog.setAcceptMode(QFileDialog::AcceptSave);
73
        dialog.setWindowTitle(i18nc("@title:window", "Save Image"));
74
        dialog.selectUrl(url);
75 76 77 78 79 80

        QStringList supportedMimetypes;
        for (const QByteArray &mimeName : QImageWriter::supportedMimeTypes()) {
            supportedMimetypes.append(QString::fromLocal8Bit(mimeName));
        }

81 82
        dialog.setMimeTypeFilters(supportedMimetypes);
        dialog.selectMimeTypeFilter(MimeTypeUtils::urlMimeType(url));
83 84 85 86 87 88 89

        // Show dialog
        do {
            if (!dialog.exec()) {
                return false;
            }

90 91 92
            QList<QUrl> files = dialog.selectedUrls();
            if (files.isEmpty()) {
                return false;
93 94
            }

95 96
            QString filename = files.first().fileName();

97 98 99
            const QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
            if (mimeType.isValid()) {
                *format = mimeType.preferredSuffix().toLocal8Bit();
100 101 102 103 104
                break;
            }
            KMessageBox::sorry(
                mMainWindow,
                i18nc("@info",
105
                      "Gwenview cannot save images as %1.", QFileInfo(filename).suffix())
106 107 108
            );
        } while (true);

109
        *outUrl = dialog.selectedUrls().first();
110 111
        return true;
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
112 113 114 115 116 117 118 119

    void setupPalettes()
    {
        QPalette pal;
        int value = GwenviewConfig::viewBackgroundValue();
        QColor fgColor = value > 128 ? Qt::black : Qt::white;

        // Normal
120 121
        KSharedConfigPtr config = KSharedConfig::openConfig();
        mPalettes[GvCore::NormalPalette] = KColorScheme::createApplicationPalette(config);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
122 123 124 125 126 127 128 129 130 131

        pal = mPalettes[GvCore::NormalPalette];
        pal.setColor(QPalette::Base, QColor::fromHsv(0, 0, value));
        pal.setColor(QPalette::Text, fgColor);
        mPalettes[GvCore::NormalViewPalette] = pal;

        // Fullscreen
        QString name = GwenviewConfig::fullScreenColorScheme();
        if (name.isEmpty()) {
            // Default color scheme
132 133
            mFullScreenPaletteName = QStandardPaths::locate(QStandardPaths::AppDataLocation, "color-schemes/fullscreen.colors");
            config = KSharedConfig::openConfig(mFullScreenPaletteName);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
134 135
        } else if (name.contains('/')) {
            // Full path to a .colors file
136 137
            mFullScreenPaletteName = name;
            config = KSharedConfig::openConfig(mFullScreenPaletteName);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
138 139
        } else {
            // Standard KDE color scheme
140 141
            mFullScreenPaletteName = QString("color-schemes/%1.colors").arg(name);
            config = KSharedConfig::openConfig(mFullScreenPaletteName, KConfig::FullConfig, QStandardPaths::AppDataLocation);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
142
        }
143
        mPalettes[GvCore::FullScreenPalette] = KColorScheme::createApplicationPalette(config);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
144 145

        pal = mPalettes[GvCore::FullScreenPalette];
146
        QString path = QStandardPaths::locate(QStandardPaths::AppDataLocation, "images/background.png");
Aurélien Gâteau's avatar
Aurélien Gâteau committed
147 148 149 150
        QPixmap bgTexture(path);
        pal.setBrush(QPalette::Base, bgTexture);
        mPalettes[GvCore::FullScreenViewPalette] = pal;
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
151 152
};

153 154
GvCore::GvCore(MainWindow* mainWindow, SortedDirModel* dirModel)
: QObject(mainWindow)
155 156 157 158 159 160
, d(new GvCorePrivate)
{
    d->q = this;
    d->mMainWindow = mainWindow;
    d->mDirModel = dirModel;
    d->mRecentFoldersModel = 0;
161
    d->mRecentFilesModel = 0;
162

Aurélien Gâteau's avatar
Aurélien Gâteau committed
163 164
    d->setupPalettes();

165 166
    connect(GwenviewConfig::self(), SIGNAL(configChanged()),
            SLOT(slotConfigChanged()));
Aurélien Gâteau's avatar
Aurélien Gâteau committed
167 168
}

169 170 171
GvCore::~GvCore()
{
    delete d;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
172 173
}

174 175 176
QAbstractItemModel* GvCore::recentFoldersModel() const
{
    if (!d->mRecentFoldersModel) {
177
        d->mRecentFoldersModel = new HistoryModel(const_cast<GvCore*>(this), QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/recentfolders/");
178 179
    }
    return d->mRecentFoldersModel;
180 181
}

182
QAbstractItemModel* GvCore::recentFilesModel() const
183
{
184 185
    if (!d->mRecentFilesModel) {
        d->mRecentFilesModel = new RecentFilesModel(const_cast<GvCore*>(this));
186
    }
187
    return d->mRecentFilesModel;
188 189
}

190 191 192
AbstractSemanticInfoBackEnd* GvCore::semanticInfoBackEnd() const
{
    return d->mDirModel->semanticInfoBackEnd();
193 194
}

195 196 197 198 199
SortedDirModel* GvCore::sortedDirModel() const
{
    return d->mDirModel;
}

David Edmundson's avatar
David Edmundson committed
200
void GvCore::addUrlToRecentFolders(QUrl url)
201 202 203 204
{
    if (!GwenviewConfig::historyEnabled()) {
        return;
    }
Benjamin Löwe's avatar
Benjamin Löwe committed
205 206 207
    if (!url.isValid()) {
        return;
    }
208
    if (url.path() != "") { // This check is a workaround for bug #312060
David Edmundson's avatar
David Edmundson committed
209
        url.setPath(url.path()+'/');
210
    }
211 212
    recentFoldersModel();
    d->mRecentFoldersModel->addUrl(url);
213 214
}

215
void GvCore::addUrlToRecentFiles(const QUrl &url)
216 217 218 219
{
    if (!GwenviewConfig::historyEnabled()) {
        return;
    }
220 221
    recentFilesModel();
    d->mRecentFilesModel->addUrl(url);
222 223
}

224 225 226 227
void GvCore::saveAll()
{
    SaveAllHelper helper(d->mMainWindow);
    helper.save();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
228 229
}

David Edmundson's avatar
David Edmundson committed
230
void GvCore::save(const QUrl &url)
231 232 233
{
    Document::Ptr doc = DocumentFactory::instance()->load(url);
    QByteArray format = doc->format();
234 235
    const QByteArrayList availableTypes = QImageWriter::supportedImageFormats();
    if (availableTypes.contains(format)) {
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
        DocumentJob* job = doc->save(url, format);
        connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*)));
    } else {
        // We don't know how to save in 'format', ask the user for a format we can
        // write to.
        KGuiItem saveUsingAnotherFormat = KStandardGuiItem::saveAs();
        saveUsingAnotherFormat.setText(i18n("Save using another format"));
        int result = KMessageBox::warningContinueCancel(
                         d->mMainWindow,
                         i18n("Gwenview cannot save images in '%1' format.", QString(format)),
                         QString() /* caption */,
                         saveUsingAnotherFormat
                     );
        if (result == KMessageBox::Continue) {
            saveAs(url);
        }
    }
253 254
}

David Edmundson's avatar
David Edmundson committed
255
void GvCore::saveAs(const QUrl &url)
256 257
{
    QByteArray format;
David Edmundson's avatar
David Edmundson committed
258
    QUrl saveAsUrl;
259 260 261 262 263
    if (!d->showSaveAsDialog(url, &saveAsUrl, &format)) {
        return;
    }

    // Check for overwrite
264 265 266
    KIO::StatJob *statJob = KIO::stat(saveAsUrl, KIO::StatJob::DestinationSide, 0);
    KJobWidgets::setWindow(statJob, d->mMainWindow);
    if (statJob->exec()) {
267 268
        int answer = KMessageBox::warningContinueCancel(
                         d->mMainWindow,
269 270 271 272
                         xi18nc("@info",
                                "A file named <filename>%1</filename> already exists.\n"
                                "Are you sure you want to overwrite it?",
                                saveAsUrl.fileName()),
273 274 275 276 277 278 279 280 281 282 283
                         QString(),
                         KStandardGuiItem::overwrite());
        if (answer == KMessageBox::Cancel) {
            return;
        }
    }

    // Start save
    Document::Ptr doc = DocumentFactory::instance()->load(url);
    KJob* job = doc->save(saveAsUrl, format.data());
    if (!job) {
David Edmundson's avatar
David Edmundson committed
284
        const QString name = saveAsUrl.fileName().isEmpty() ? saveAsUrl.toDisplayString() : saveAsUrl.fileName();
285
        const QString msg = xi18nc("@info", "<b>Saving <filename>%1</filename> failed:</b><br />%2",
286
                                   name, doc->errorString());
287 288 289 290
        KMessageBox::sorry(QApplication::activeWindow(), msg);
    } else {
        connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*)));
    }
291 292
}

David Edmundson's avatar
David Edmundson committed
293
static void applyTransform(const QUrl &url, Orientation orientation)
294 295 296 297
{
    TransformImageOperation* op = new TransformImageOperation(orientation);
    Document::Ptr doc = DocumentFactory::instance()->load(url);
    op->applyToDocument(doc);
298 299
}

300 301 302
void GvCore::slotSaveResult(KJob* _job)
{
    SaveJob* job = static_cast<SaveJob*>(_job);
David Edmundson's avatar
David Edmundson committed
303 304
    QUrl oldUrl = job->oldUrl();
    QUrl newUrl = job->newUrl();
305 306

    if (job->error()) {
David Edmundson's avatar
David Edmundson committed
307
        QString name = newUrl.fileName().isEmpty() ? newUrl.toDisplayString() : newUrl.fileName();
308
        QString msg = xi18nc("@info", "<b>Saving <filename>%1</filename> failed:</b><br />%2",
309
                             name, job->errorString());
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

        int result = KMessageBox::warningContinueCancel(
                         d->mMainWindow, msg,
                         QString() /* caption */,
                         KStandardGuiItem::saveAs());

        if (result == KMessageBox::Continue) {
            saveAs(newUrl);
        }
        return;
    }

    if (oldUrl != newUrl) {
        d->mMainWindow->goToUrl(newUrl);

325 326
        ViewMainPage* page = d->mMainWindow->viewMainPage();
        if (page->isVisible()) {
327
            HudMessageBubble* bubble = new HudMessageBubble();
328 329 330
            bubble->setText(i18n("You are now viewing the new document."));
            KGuiItem item = KStandardGuiItem::back();
            item.setText(i18n("Go back to the original"));
331
            HudButton* button = bubble->addButton(item);
332

David Edmundson's avatar
David Edmundson committed
333
            BinderRef<MainWindow, QUrl>::bind(button, SIGNAL(clicked()), d->mMainWindow, &MainWindow::goToUrl, oldUrl);
334 335 336 337
            connect(button, SIGNAL(clicked()),
                bubble, SLOT(deleteLater()));

            page->showMessageWidget(bubble);
338
        }
339
    }
340 341
}

David Edmundson's avatar
David Edmundson committed
342
void GvCore::rotateLeft(const QUrl &url)
343 344
{
    applyTransform(url, ROT_270);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
345 346
}

David Edmundson's avatar
David Edmundson committed
347
void GvCore::rotateRight(const QUrl &url)
348 349
{
    applyTransform(url, ROT_90);
350 351
}

David Edmundson's avatar
David Edmundson committed
352
void GvCore::setRating(const QUrl &url, int rating)
353 354 355
{
    QModelIndex index = d->mDirModel->indexForUrl(url);
    if (!index.isValid()) {
David Edmundson's avatar
David Edmundson committed
356
        qWarning() << "invalid index!";
357 358 359
        return;
    }
    d->mDirModel->setData(index, rating, SemanticInfoDirModel::RatingRole);
360 361
}

362 363 364
static void clearModel(QAbstractItemModel* model)
{
    model->removeRows(0, model->rowCount());
365 366
}

367 368 369 370
void GvCore::slotConfigChanged()
{
    if (!GwenviewConfig::historyEnabled()) {
        clearModel(recentFoldersModel());
371
        clearModel(recentFilesModel());
372
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
373 374 375 376 377 378
    d->setupPalettes();
}

QPalette GvCore::palette(GvCore::PaletteType type) const
{
    return d->mPalettes[type];
379 380
}

381 382 383 384 385
QString GvCore::fullScreenPaletteName() const
{
    return d->mFullScreenPaletteName;
}

Aurélien Gâteau's avatar
Aurélien Gâteau committed
386
} // namespace