~ubuntu-branches/ubuntu/precise/gwenview/precise-proposed

« back to all changes in this revision

Viewing changes to lib/document/document.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2011-12-15 14:17:54 UTC
  • mto: This revision was merged to the branch mainline in revision 12.
  • Revision ID: package-import@ubuntu.com-20111215141754-z043hyx69dulbggf
Tags: upstream-4.7.90
ImportĀ upstreamĀ versionĀ 4.7.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
#include "loadingjob.h"
41
41
#include "savejob.h"
42
42
 
43
 
namespace Gwenview {
 
43
namespace Gwenview
 
44
{
44
45
 
45
46
struct DocumentPrivate {
46
 
        AbstractDocumentImpl* mImpl;
47
 
        KUrl mUrl;
48
 
        bool mKeepRawData;
49
 
        QQueue<DocumentJob*> mJobQueue;
50
 
 
51
 
        /**
52
 
         * @defgroup imagedata should be reset in reload()
53
 
         * @{
54
 
         */
55
 
        QSize mSize;
56
 
        QImage mImage;
57
 
        QMap<int, QImage> mDownSampledImageMap;
58
 
        Exiv2::Image::AutoPtr mExiv2Image;
59
 
        MimeTypeUtils::Kind mKind;
60
 
        QByteArray mFormat;
61
 
        ImageMetaInfoModel mImageMetaInfoModel;
62
 
        QUndoStack mUndoStack;
63
 
        QString mErrorString;
64
 
        /** @} */
65
 
 
66
 
        void scheduleImageLoading(int invertedZoom) {
67
 
                LoadingDocumentImpl* impl = qobject_cast<LoadingDocumentImpl*>(mImpl);
68
 
                Q_ASSERT(impl);
69
 
                impl->loadImage(invertedZoom);
70
 
        }
 
47
    AbstractDocumentImpl* mImpl;
 
48
    KUrl mUrl;
 
49
    bool mKeepRawData;
 
50
    QQueue<DocumentJob*> mJobQueue;
 
51
 
 
52
    /**
 
53
     * @defgroup imagedata should be reset in reload()
 
54
     * @{
 
55
     */
 
56
    QSize mSize;
 
57
    QImage mImage;
 
58
    QMap<int, QImage> mDownSampledImageMap;
 
59
    Exiv2::Image::AutoPtr mExiv2Image;
 
60
    MimeTypeUtils::Kind mKind;
 
61
    QByteArray mFormat;
 
62
    ImageMetaInfoModel mImageMetaInfoModel;
 
63
    QUndoStack mUndoStack;
 
64
    QString mErrorString;
 
65
    /** @} */
 
66
 
 
67
    void scheduleImageLoading(int invertedZoom)
 
68
    {
 
69
        LoadingDocumentImpl* impl = qobject_cast<LoadingDocumentImpl*>(mImpl);
 
70
        Q_ASSERT(impl);
 
71
        impl->loadImage(invertedZoom);
 
72
    }
71
73
};
72
74
 
73
 
 
74
 
qreal Document::maxDownSampledZoom() {
75
 
        return 0.5;
 
75
qreal Document::maxDownSampledZoom()
 
76
{
 
77
    return 0.5;
76
78
}
77
79
 
78
 
 
79
80
Document::Document(const KUrl& url)
80
81
: QObject()
81
 
, d(new DocumentPrivate) {
82
 
        d->mImpl = 0;
83
 
        d->mUrl = url;
84
 
        d->mKeepRawData = false;
85
 
        connect(&d->mUndoStack, SIGNAL(indexChanged(int)), SLOT(slotUndoIndexChanged()) );
86
 
 
87
 
        reload();
88
 
}
89
 
 
90
 
 
91
 
Document::~Document() {
92
 
        // We do not want undo stack to emit signals, forcing us to emit signals
93
 
        // ourself while we are being destroyed.
94
 
        disconnect(&d->mUndoStack, 0, this, 0);
95
 
 
96
 
        delete d->mImpl;
97
 
        delete d;
98
 
}
99
 
 
100
 
 
101
 
void Document::reload() {
102
 
        d->mSize = QSize();
103
 
        d->mImage = QImage();
104
 
        d->mDownSampledImageMap.clear();
105
 
        d->mExiv2Image.reset();
106
 
        d->mKind = MimeTypeUtils::KIND_UNKNOWN;
107
 
        d->mFormat = QByteArray();
108
 
        d->mImageMetaInfoModel.setUrl(d->mUrl);
109
 
        d->mUndoStack.clear();
110
 
        d->mErrorString.clear();
111
 
 
112
 
        switchToImpl(new LoadingDocumentImpl(this));
113
 
}
114
 
 
115
 
 
116
 
const QImage& Document::image() const {
117
 
        return d->mImage;
118
 
}
119
 
 
 
82
, d(new DocumentPrivate)
 
83
{
 
84
    d->mImpl = 0;
 
85
    d->mUrl = url;
 
86
    d->mKeepRawData = false;
 
87
    connect(&d->mUndoStack, SIGNAL(indexChanged(int)), SLOT(slotUndoIndexChanged()));
 
88
 
 
89
    reload();
 
90
}
 
91
 
 
92
Document::~Document()
 
93
{
 
94
    // We do not want undo stack to emit signals, forcing us to emit signals
 
95
    // ourself while we are being destroyed.
 
96
    disconnect(&d->mUndoStack, 0, this, 0);
 
97
 
 
98
    delete d->mImpl;
 
99
    delete d;
 
100
}
 
101
 
 
102
void Document::reload()
 
103
{
 
104
    d->mSize = QSize();
 
105
    d->mImage = QImage();
 
106
    d->mDownSampledImageMap.clear();
 
107
    d->mExiv2Image.reset();
 
108
    d->mKind = MimeTypeUtils::KIND_UNKNOWN;
 
109
    d->mFormat = QByteArray();
 
110
    d->mImageMetaInfoModel.setUrl(d->mUrl);
 
111
    d->mUndoStack.clear();
 
112
    d->mErrorString.clear();
 
113
 
 
114
    switchToImpl(new LoadingDocumentImpl(this));
 
115
}
 
116
 
 
117
const QImage& Document::image() const
 
118
{
 
119
    return d->mImage;
 
120
}
120
121
 
121
122
/**
122
123
 * invertedZoom is the biggest power of 2 for which zoom < 1/invertedZoom.
124
125
 * zoom = 0.4 == 1/2.5 => invertedZoom = 2 (1/2.5 < 1/2)
125
126
 * zoom = 0.2 == 1/5   => invertedZoom = 4 (1/5   < 1/4)
126
127
 */
127
 
inline int invertedZoomForZoom(qreal zoom) {
128
 
        int invertedZoom;
129
 
        for (invertedZoom = 1; zoom < 1./(invertedZoom*2); invertedZoom*=2) {}
130
 
        return invertedZoom;
131
 
}
132
 
 
133
 
 
134
 
const QImage& Document::downSampledImageForZoom(qreal zoom) const {
135
 
        static const QImage sNullImage;
136
 
 
137
 
        int invertedZoom = invertedZoomForZoom(zoom);
138
 
        if (invertedZoom == 1) {
139
 
                return d->mImage;
140
 
        }
141
 
 
142
 
        if (!d->mDownSampledImageMap.contains(invertedZoom)) {
143
 
                if (!d->mImage.isNull()) {
144
 
                        // Special case: if we have the full image and the down sampled
145
 
                        // image would be too small, return the original image.
146
 
                        const QSize downSampledSize = d->mImage.size() / invertedZoom;
147
 
                        if (downSampledSize.isEmpty()) {
148
 
                                return d->mImage;
149
 
                        }
150
 
                }
151
 
                return sNullImage;
152
 
        }
153
 
 
154
 
        return d->mDownSampledImageMap[invertedZoom];
155
 
}
156
 
 
157
 
 
158
 
Document::LoadingState Document::loadingState() const {
159
 
        return d->mImpl->loadingState();
160
 
}
161
 
 
162
 
 
163
 
void Document::switchToImpl(AbstractDocumentImpl* impl) {
164
 
        Q_ASSERT(impl);
165
 
        if (d->mImpl) {
166
 
                d->mImpl->deleteLater();
167
 
        }
168
 
        d->mImpl=impl;
169
 
 
170
 
        connect(d->mImpl, SIGNAL(metaInfoLoaded()),
171
 
                this, SLOT(emitMetaInfoLoaded()) );
172
 
        connect(d->mImpl, SIGNAL(loaded()),
173
 
                this, SLOT(emitLoaded()) );
174
 
        connect(d->mImpl, SIGNAL(loadingFailed()),
175
 
                this, SLOT(emitLoadingFailed()) );
176
 
        connect(d->mImpl, SIGNAL(imageRectUpdated(const QRect&)),
177
 
                this, SIGNAL(imageRectUpdated(const QRect&)) );
178
 
        connect(d->mImpl, SIGNAL(isAnimatedUpdated()),
179
 
                this, SIGNAL(isAnimatedUpdated()) );
180
 
        d->mImpl->init();
181
 
}
182
 
 
183
 
 
184
 
void Document::setImageInternal(const QImage& image) {
185
 
        d->mImage = image;
186
 
        d->mDownSampledImageMap.clear();
187
 
 
188
 
        // If we didn't get the image size before decoding the full image, set it
189
 
        // now
190
 
        setSize(d->mImage.size());
191
 
}
192
 
 
193
 
 
194
 
KUrl Document::url() const {
195
 
        return d->mUrl;
196
 
}
197
 
 
198
 
 
199
 
QByteArray Document::rawData() const {
200
 
        return d->mImpl->rawData();
201
 
}
202
 
 
203
 
 
204
 
bool Document::keepRawData() const {
205
 
        return d->mKeepRawData;
206
 
}
207
 
 
208
 
 
209
 
void Document::setKeepRawData(bool value) {
210
 
        d->mKeepRawData = value;
211
 
}
212
 
 
213
 
 
214
 
void Document::waitUntilLoaded() {
215
 
        startLoadingFullImage();
216
 
        while (true) {
217
 
                LoadingState state = loadingState();
218
 
                if (state == Loaded || state == LoadingFailed) {
219
 
                        return;
220
 
                }
221
 
                qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
222
 
        }
223
 
}
224
 
 
225
 
 
226
 
DocumentJob* Document::save(const KUrl& url, const QByteArray& format) {
227
 
        waitUntilLoaded();
228
 
        DocumentJob* job = d->mImpl->save(url, format);
229
 
        if (!job) {
230
 
                kWarning() << "Implementation does not support saving!";
231
 
                setErrorString(i18nc("@info", "Gwenview cannot save this kind of documents."));
232
 
                return false;
233
 
        }
234
 
        job->setProperty("oldUrl", d->mUrl);
235
 
        job->setProperty("newUrl", url);
236
 
        connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*)));
237
 
        enqueueJob(job);
238
 
        return job;
239
 
}
240
 
 
241
 
 
242
 
void Document::slotSaveResult(KJob* job) {
243
 
        if (job->error()) {
244
 
                setErrorString(job->errorString());
245
 
        } else {
246
 
                d->mUndoStack.setClean();
247
 
                SaveJob* saveJob = static_cast<SaveJob*>(job);
248
 
                d->mUrl = saveJob->newUrl();
249
 
                d->mImageMetaInfoModel.setUrl(d->mUrl);
250
 
                saved(saveJob->oldUrl(), d->mUrl);
251
 
        }
252
 
}
253
 
 
254
 
 
255
 
QByteArray Document::format() const {
256
 
        return d->mFormat;
257
 
}
258
 
 
259
 
 
260
 
void Document::setFormat(const QByteArray& format) {
261
 
        d->mFormat = format;
262
 
        emit metaInfoUpdated();
263
 
}
264
 
 
265
 
 
266
 
MimeTypeUtils::Kind Document::kind() const {
267
 
        return d->mKind;
268
 
}
269
 
 
270
 
 
271
 
void Document::setKind(MimeTypeUtils::Kind kind) {
272
 
        d->mKind = kind;
273
 
        emit kindDetermined(d->mUrl);
274
 
}
275
 
 
276
 
 
277
 
QSize Document::size() const {
278
 
        return d->mSize;
279
 
}
280
 
 
281
 
 
282
 
bool Document::hasAlphaChannel() const {
283
 
        if (d->mImage.isNull()) {
284
 
                return false;
285
 
        } else {
286
 
                return d->mImage.hasAlphaChannel();
287
 
        }
288
 
}
289
 
 
290
 
 
291
 
int Document::memoryUsage() const {
292
 
        // FIXME: Take undo stack into account
293
 
        int usage = d->mImage.numBytes();
294
 
        usage += rawData().length();
295
 
        return usage;
296
 
}
297
 
 
298
 
 
299
 
void Document::setSize(const QSize& size) {
300
 
        if (size == d->mSize) {
301
 
                return;
302
 
        }
303
 
        d->mSize = size;
304
 
        d->mImageMetaInfoModel.setImageSize(size);
305
 
        emit metaInfoUpdated();
306
 
}
307
 
 
308
 
 
309
 
bool Document::isModified() const {
310
 
        return !d->mUndoStack.isClean();
311
 
}
312
 
 
313
 
 
314
 
AbstractDocumentEditor* Document::editor() {
315
 
        return d->mImpl->editor();
316
 
}
317
 
 
318
 
 
319
 
void Document::setExiv2Image(Exiv2::Image::AutoPtr image) {
320
 
        d->mExiv2Image = image;
321
 
        d->mImageMetaInfoModel.setExiv2Image(d->mExiv2Image.get());
322
 
        emit metaInfoUpdated();
323
 
}
324
 
 
325
 
 
326
 
void Document::setDownSampledImage(const QImage& image, int invertedZoom) {
327
 
        Q_ASSERT(!d->mDownSampledImageMap.contains(invertedZoom));
328
 
        d->mDownSampledImageMap[invertedZoom] = image;
329
 
        emit downSampledImageReady();
330
 
}
331
 
 
332
 
 
333
 
QString Document::errorString() const {
334
 
        return d->mErrorString;
335
 
}
336
 
 
337
 
 
338
 
void Document::setErrorString(const QString& string) {
339
 
        d->mErrorString = string;
340
 
}
341
 
 
342
 
 
343
 
ImageMetaInfoModel* Document::metaInfo() const {
344
 
        return &d->mImageMetaInfoModel;
345
 
}
346
 
 
347
 
 
348
 
void Document::startLoadingFullImage() {
349
 
        LoadingState state = loadingState();
350
 
        if (state <= MetaInfoLoaded) {
351
 
                // Schedule full image loading
352
 
                LoadingJob* job = new LoadingJob;
353
 
                job->uiDelegate()->setAutoWarningHandlingEnabled(false);
354
 
                job->uiDelegate()->setAutoErrorHandlingEnabled(false);
355
 
                enqueueJob(job);
356
 
                d->scheduleImageLoading(1);
357
 
        } else if (state == Loaded) {
358
 
                return;
359
 
        } else if (state == LoadingFailed) {
360
 
                kWarning() << "Can't load full image: loading has already failed";
361
 
        }
362
 
}
363
 
 
364
 
 
365
 
bool Document::prepareDownSampledImageForZoom(qreal zoom) {
366
 
        if (zoom >= maxDownSampledZoom()) {
367
 
                kWarning() << "No need to call prepareDownSampledImageForZoom if zoom >= " << maxDownSampledZoom();
368
 
                return true;
369
 
        }
370
 
 
371
 
        int invertedZoom = invertedZoomForZoom(zoom);
372
 
        if (d->mDownSampledImageMap.contains(invertedZoom)) {
373
 
                return true;
374
 
        }
375
 
 
376
 
        if (loadingState() == Loaded) {
377
 
                // Resample image from the full one
378
 
                d->mDownSampledImageMap[invertedZoom] = d->mImage.scaled(d->mImage.size() / invertedZoom, Qt::KeepAspectRatio, Qt::FastTransformation);
379
 
                if (d->mDownSampledImageMap[invertedZoom].size().isEmpty()) {
380
 
                        d->mDownSampledImageMap[invertedZoom] = d->mImage;
381
 
                        return true;
382
 
                }
383
 
 
384
 
                return true;
385
 
        }
386
 
 
387
 
        // Schedule down sampled image loading
388
 
        d->scheduleImageLoading(invertedZoom);
389
 
 
390
 
        return false;
391
 
}
392
 
 
393
 
 
394
 
void Document::emitMetaInfoLoaded() {
395
 
        emit metaInfoLoaded(d->mUrl);
396
 
}
397
 
 
398
 
 
399
 
void Document::emitLoaded() {
400
 
        emit loaded(d->mUrl);
401
 
}
402
 
 
403
 
 
404
 
void Document::emitLoadingFailed() {
405
 
        emit loadingFailed(d->mUrl);
406
 
}
407
 
 
408
 
 
409
 
QUndoStack* Document::undoStack() const {
410
 
        return &d->mUndoStack;
411
 
}
412
 
 
413
 
 
414
 
void Document::slotUndoIndexChanged() {
415
 
        if (d->mUndoStack.isClean()) {
416
 
                // If user just undid all his changes this does not really correspond
417
 
                // to a save, but it's similar enough as far as Document users are
418
 
                // concerned
419
 
                saved(d->mUrl, d->mUrl);
420
 
        } else {
421
 
                modified(d->mUrl);
422
 
        }
423
 
}
424
 
 
425
 
 
426
 
bool Document::isEditable() const {
427
 
        return d->mImpl->isEditable();
428
 
}
429
 
 
430
 
 
431
 
bool Document::isAnimated() const {
432
 
        return d->mImpl->isAnimated();
433
 
}
434
 
 
435
 
 
436
 
void Document::startAnimation() {
437
 
        return d->mImpl->startAnimation();
438
 
}
439
 
 
440
 
 
441
 
void Document::stopAnimation() {
442
 
        return d->mImpl->stopAnimation();
443
 
}
444
 
 
445
 
void Document::enqueueJob(DocumentJob* job) {
446
 
        d->mJobQueue.enqueue(job);
447
 
        job->setDocument(Ptr(this));
448
 
        connect(job, SIGNAL(destroyed(QObject*)),
449
 
                SLOT(slotJobDestroyed(QObject*)));
450
 
        if (d->mJobQueue.size() == 1) {
451
 
                job->start();
452
 
                busyChanged(d->mUrl, true);
453
 
        }
454
 
}
455
 
 
456
 
void Document::slotJobDestroyed(QObject* job) {
457
 
        Q_ASSERT(!d->mJobQueue.isEmpty());
458
 
        if (d->mJobQueue.head() != job) {
459
 
                // Job was killed before it even got started, just remove it from the
460
 
                // queue and move along
461
 
                d->mJobQueue.removeAll(static_cast<DocumentJob*>(job));
462
 
                return;
463
 
        }
464
 
        d->mJobQueue.dequeue();
465
 
        if (d->mJobQueue.isEmpty()) {
466
 
                busyChanged(d->mUrl, false);
467
 
                allTasksDone();
468
 
        } else {
469
 
                d->mJobQueue.head()->start();
470
 
        }
471
 
}
472
 
 
473
 
bool Document::isBusy() const {
474
 
        return !d->mJobQueue.isEmpty();
 
128
inline int invertedZoomForZoom(qreal zoom)
 
129
{
 
130
    int invertedZoom;
 
131
    for (invertedZoom = 1; zoom < 1. / (invertedZoom * 2); invertedZoom *= 2) {}
 
132
    return invertedZoom;
 
133
}
 
134
 
 
135
const QImage& Document::downSampledImageForZoom(qreal zoom) const
 
136
{
 
137
    static const QImage sNullImage;
 
138
 
 
139
    int invertedZoom = invertedZoomForZoom(zoom);
 
140
    if (invertedZoom == 1) {
 
141
        return d->mImage;
 
142
    }
 
143
 
 
144
    if (!d->mDownSampledImageMap.contains(invertedZoom)) {
 
145
        if (!d->mImage.isNull()) {
 
146
            // Special case: if we have the full image and the down sampled
 
147
            // image would be too small, return the original image.
 
148
            const QSize downSampledSize = d->mImage.size() / invertedZoom;
 
149
            if (downSampledSize.isEmpty()) {
 
150
                return d->mImage;
 
151
            }
 
152
        }
 
153
        return sNullImage;
 
154
    }
 
155
 
 
156
    return d->mDownSampledImageMap[invertedZoom];
 
157
}
 
158
 
 
159
Document::LoadingState Document::loadingState() const
 
160
{
 
161
    return d->mImpl->loadingState();
 
162
}
 
163
 
 
164
void Document::switchToImpl(AbstractDocumentImpl* impl)
 
165
{
 
166
    Q_ASSERT(impl);
 
167
    if (d->mImpl) {
 
168
        d->mImpl->deleteLater();
 
169
    }
 
170
    d->mImpl = impl;
 
171
 
 
172
    connect(d->mImpl, SIGNAL(metaInfoLoaded()),
 
173
            this, SLOT(emitMetaInfoLoaded()));
 
174
    connect(d->mImpl, SIGNAL(loaded()),
 
175
            this, SLOT(emitLoaded()));
 
176
    connect(d->mImpl, SIGNAL(loadingFailed()),
 
177
            this, SLOT(emitLoadingFailed()));
 
178
    connect(d->mImpl, SIGNAL(imageRectUpdated(QRect)),
 
179
            this, SIGNAL(imageRectUpdated(QRect)));
 
180
    connect(d->mImpl, SIGNAL(isAnimatedUpdated()),
 
181
            this, SIGNAL(isAnimatedUpdated()));
 
182
    d->mImpl->init();
 
183
}
 
184
 
 
185
void Document::setImageInternal(const QImage& image)
 
186
{
 
187
    d->mImage = image;
 
188
    d->mDownSampledImageMap.clear();
 
189
 
 
190
    // If we didn't get the image size before decoding the full image, set it
 
191
    // now
 
192
    setSize(d->mImage.size());
 
193
}
 
194
 
 
195
KUrl Document::url() const
 
196
{
 
197
    return d->mUrl;
 
198
}
 
199
 
 
200
QByteArray Document::rawData() const
 
201
{
 
202
    return d->mImpl->rawData();
 
203
}
 
204
 
 
205
bool Document::keepRawData() const
 
206
{
 
207
    return d->mKeepRawData;
 
208
}
 
209
 
 
210
void Document::setKeepRawData(bool value)
 
211
{
 
212
    d->mKeepRawData = value;
 
213
}
 
214
 
 
215
void Document::waitUntilLoaded()
 
216
{
 
217
    startLoadingFullImage();
 
218
    while (true) {
 
219
        LoadingState state = loadingState();
 
220
        if (state == Loaded || state == LoadingFailed) {
 
221
            return;
 
222
        }
 
223
        qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
 
224
    }
 
225
}
 
226
 
 
227
DocumentJob* Document::save(const KUrl& url, const QByteArray& format)
 
228
{
 
229
    waitUntilLoaded();
 
230
    DocumentJob* job = d->mImpl->save(url, format);
 
231
    if (!job) {
 
232
        kWarning() << "Implementation does not support saving!";
 
233
        setErrorString(i18nc("@info", "Gwenview cannot save this kind of documents."));
 
234
        return false;
 
235
    }
 
236
    job->setProperty("oldUrl", d->mUrl);
 
237
    job->setProperty("newUrl", url);
 
238
    connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*)));
 
239
    enqueueJob(job);
 
240
    return job;
 
241
}
 
242
 
 
243
void Document::slotSaveResult(KJob* job)
 
244
{
 
245
    if (job->error()) {
 
246
        setErrorString(job->errorString());
 
247
    } else {
 
248
        d->mUndoStack.setClean();
 
249
        SaveJob* saveJob = static_cast<SaveJob*>(job);
 
250
        d->mUrl = saveJob->newUrl();
 
251
        d->mImageMetaInfoModel.setUrl(d->mUrl);
 
252
        saved(saveJob->oldUrl(), d->mUrl);
 
253
    }
 
254
}
 
255
 
 
256
QByteArray Document::format() const
 
257
{
 
258
    return d->mFormat;
 
259
}
 
260
 
 
261
void Document::setFormat(const QByteArray& format)
 
262
{
 
263
    d->mFormat = format;
 
264
    emit metaInfoUpdated();
 
265
}
 
266
 
 
267
MimeTypeUtils::Kind Document::kind() const
 
268
{
 
269
    return d->mKind;
 
270
}
 
271
 
 
272
void Document::setKind(MimeTypeUtils::Kind kind)
 
273
{
 
274
    d->mKind = kind;
 
275
    emit kindDetermined(d->mUrl);
 
276
}
 
277
 
 
278
QSize Document::size() const
 
279
{
 
280
    return d->mSize;
 
281
}
 
282
 
 
283
bool Document::hasAlphaChannel() const
 
284
{
 
285
    if (d->mImage.isNull()) {
 
286
        return false;
 
287
    } else {
 
288
        return d->mImage.hasAlphaChannel();
 
289
    }
 
290
}
 
291
 
 
292
int Document::memoryUsage() const
 
293
{
 
294
    // FIXME: Take undo stack into account
 
295
    int usage = d->mImage.numBytes();
 
296
    usage += rawData().length();
 
297
    return usage;
 
298
}
 
299
 
 
300
void Document::setSize(const QSize& size)
 
301
{
 
302
    if (size == d->mSize) {
 
303
        return;
 
304
    }
 
305
    d->mSize = size;
 
306
    d->mImageMetaInfoModel.setImageSize(size);
 
307
    emit metaInfoUpdated();
 
308
}
 
309
 
 
310
bool Document::isModified() const
 
311
{
 
312
    return !d->mUndoStack.isClean();
 
313
}
 
314
 
 
315
AbstractDocumentEditor* Document::editor()
 
316
{
 
317
    return d->mImpl->editor();
 
318
}
 
319
 
 
320
void Document::setExiv2Image(Exiv2::Image::AutoPtr image)
 
321
{
 
322
    d->mExiv2Image = image;
 
323
    d->mImageMetaInfoModel.setExiv2Image(d->mExiv2Image.get());
 
324
    emit metaInfoUpdated();
 
325
}
 
326
 
 
327
void Document::setDownSampledImage(const QImage& image, int invertedZoom)
 
328
{
 
329
    Q_ASSERT(!d->mDownSampledImageMap.contains(invertedZoom));
 
330
    d->mDownSampledImageMap[invertedZoom] = image;
 
331
    emit downSampledImageReady();
 
332
}
 
333
 
 
334
QString Document::errorString() const
 
335
{
 
336
    return d->mErrorString;
 
337
}
 
338
 
 
339
void Document::setErrorString(const QString& string)
 
340
{
 
341
    d->mErrorString = string;
 
342
}
 
343
 
 
344
ImageMetaInfoModel* Document::metaInfo() const
 
345
{
 
346
    return &d->mImageMetaInfoModel;
 
347
}
 
348
 
 
349
void Document::startLoadingFullImage()
 
350
{
 
351
    LoadingState state = loadingState();
 
352
    if (state <= MetaInfoLoaded) {
 
353
        // Schedule full image loading
 
354
        LoadingJob* job = new LoadingJob;
 
355
        job->uiDelegate()->setAutoWarningHandlingEnabled(false);
 
356
        job->uiDelegate()->setAutoErrorHandlingEnabled(false);
 
357
        enqueueJob(job);
 
358
        d->scheduleImageLoading(1);
 
359
    } else if (state == Loaded) {
 
360
        return;
 
361
    } else if (state == LoadingFailed) {
 
362
        kWarning() << "Can't load full image: loading has already failed";
 
363
    }
 
364
}
 
365
 
 
366
bool Document::prepareDownSampledImageForZoom(qreal zoom)
 
367
{
 
368
    if (zoom >= maxDownSampledZoom()) {
 
369
        kWarning() << "No need to call prepareDownSampledImageForZoom if zoom >= " << maxDownSampledZoom();
 
370
        return true;
 
371
    }
 
372
 
 
373
    int invertedZoom = invertedZoomForZoom(zoom);
 
374
    if (d->mDownSampledImageMap.contains(invertedZoom)) {
 
375
        return true;
 
376
    }
 
377
 
 
378
    if (loadingState() == Loaded) {
 
379
        // Resample image from the full one
 
380
        d->mDownSampledImageMap[invertedZoom] = d->mImage.scaled(d->mImage.size() / invertedZoom, Qt::KeepAspectRatio, Qt::FastTransformation);
 
381
        if (d->mDownSampledImageMap[invertedZoom].size().isEmpty()) {
 
382
            d->mDownSampledImageMap[invertedZoom] = d->mImage;
 
383
            return true;
 
384
        }
 
385
 
 
386
        return true;
 
387
    }
 
388
 
 
389
    // Schedule down sampled image loading
 
390
    d->scheduleImageLoading(invertedZoom);
 
391
 
 
392
    return false;
 
393
}
 
394
 
 
395
void Document::emitMetaInfoLoaded()
 
396
{
 
397
    emit metaInfoLoaded(d->mUrl);
 
398
}
 
399
 
 
400
void Document::emitLoaded()
 
401
{
 
402
    emit loaded(d->mUrl);
 
403
}
 
404
 
 
405
void Document::emitLoadingFailed()
 
406
{
 
407
    emit loadingFailed(d->mUrl);
 
408
}
 
409
 
 
410
QUndoStack* Document::undoStack() const
 
411
{
 
412
    return &d->mUndoStack;
 
413
}
 
414
 
 
415
void Document::slotUndoIndexChanged()
 
416
{
 
417
    if (d->mUndoStack.isClean()) {
 
418
        // If user just undid all his changes this does not really correspond
 
419
        // to a save, but it's similar enough as far as Document users are
 
420
        // concerned
 
421
        saved(d->mUrl, d->mUrl);
 
422
    } else {
 
423
        modified(d->mUrl);
 
424
    }
 
425
}
 
426
 
 
427
bool Document::isEditable() const
 
428
{
 
429
    return d->mImpl->isEditable();
 
430
}
 
431
 
 
432
bool Document::isAnimated() const
 
433
{
 
434
    return d->mImpl->isAnimated();
 
435
}
 
436
 
 
437
void Document::startAnimation()
 
438
{
 
439
    return d->mImpl->startAnimation();
 
440
}
 
441
 
 
442
void Document::stopAnimation()
 
443
{
 
444
    return d->mImpl->stopAnimation();
 
445
}
 
446
 
 
447
void Document::enqueueJob(DocumentJob* job)
 
448
{
 
449
    d->mJobQueue.enqueue(job);
 
450
    job->setDocument(Ptr(this));
 
451
    connect(job, SIGNAL(destroyed(QObject*)),
 
452
            SLOT(slotJobDestroyed(QObject*)));
 
453
    if (d->mJobQueue.size() == 1) {
 
454
        job->start();
 
455
        busyChanged(d->mUrl, true);
 
456
    }
 
457
}
 
458
 
 
459
void Document::slotJobDestroyed(QObject* job)
 
460
{
 
461
    Q_ASSERT(!d->mJobQueue.isEmpty());
 
462
    if (d->mJobQueue.head() != job) {
 
463
        // Job was killed before it even got started, just remove it from the
 
464
        // queue and move along
 
465
        d->mJobQueue.removeAll(static_cast<DocumentJob*>(job));
 
466
        return;
 
467
    }
 
468
    d->mJobQueue.dequeue();
 
469
    if (d->mJobQueue.isEmpty()) {
 
470
        busyChanged(d->mUrl, false);
 
471
        allTasksDone();
 
472
    } else {
 
473
        d->mJobQueue.head()->start();
 
474
    }
 
475
}
 
476
 
 
477
bool Document::isBusy() const
 
478
{
 
479
    return !d->mJobQueue.isEmpty();
 
480
}
 
481
 
 
482
QSvgRenderer* Document::svgRenderer() const
 
483
{
 
484
    return d->mImpl->svgRenderer();
475
485
}
476
486
 
477
487
} // namespace