~ubuntu-branches/ubuntu/oneiric/gwenview/oneiric

« back to all changes in this revision

Viewing changes to lib/document/loadingdocumentimpl.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Rohan Garg
  • Date: 2011-07-20 13:46:34 UTC
  • Revision ID: james.westby@ubuntu.com-20110720134634-92930fdjeed4gdc9
Tags: upstream-4.6.90+repack
ImportĀ upstreamĀ versionĀ 4.6.90+repack

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// vim: set tabstop=4 shiftwidth=4 noexpandtab:
 
2
/*
 
3
Gwenview: an image viewer
 
4
Copyright 2007 AurĆ©lien GĆ¢teau <agateau@kde.org>
 
5
 
 
6
This program is free software; you can redistribute it and/or
 
7
modify it under the terms of the GNU General Public License
 
8
as published by the Free Software Foundation; either version 2
 
9
of the License, or (at your option) any later version.
 
10
 
 
11
This program is distributed in the hope that it will be useful,
 
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
GNU General Public License for more details.
 
15
 
 
16
You should have received a copy of the GNU General Public License
 
17
along with this program; if not, write to the Free Software
 
18
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
19
 
 
20
*/
 
21
// Self
 
22
#include "loadingdocumentimpl.moc"
 
23
 
 
24
// STL
 
25
#include <memory>
 
26
 
 
27
// Qt
 
28
#include <QBuffer>
 
29
#include <QByteArray>
 
30
#include <QFile>
 
31
#include <QFuture>
 
32
#include <QFutureWatcher>
 
33
#include <QImage>
 
34
#include <QImageReader>
 
35
#include <QPointer>
 
36
#include <QtConcurrentRun>
 
37
 
 
38
// KDE
 
39
#include <kdebug.h>
 
40
#include <kio/job.h>
 
41
#include <kio/jobclasses.h>
 
42
#include <klocale.h>
 
43
#include <kmimetype.h>
 
44
#include <kprotocolinfo.h>
 
45
#include <kurl.h>
 
46
 
 
47
// Local
 
48
#include "animateddocumentloadedimpl.h"
 
49
#include "document.h"
 
50
#include "documentloadedimpl.h"
 
51
#include "emptydocumentimpl.h"
 
52
#include "exiv2imageloader.h"
 
53
#include "imageutils.h"
 
54
#include "jpegcontent.h"
 
55
#include "jpegdocumentloadedimpl.h"
 
56
#include "orientation.h"
 
57
#include "svgdocumentloadedimpl.h"
 
58
#include "urlutils.h"
 
59
#include "videodocumentloadedimpl.h"
 
60
 
 
61
namespace Gwenview {
 
62
 
 
63
#undef ENABLE_LOG
 
64
#undef LOG
 
65
//#define ENABLE_LOG
 
66
#ifdef ENABLE_LOG
 
67
#define LOG(x) kDebug() << x
 
68
#else
 
69
#define LOG(x) ;
 
70
#endif
 
71
 
 
72
const int HEADER_SIZE = 256;
 
73
 
 
74
struct LoadingDocumentImplPrivate {
 
75
        LoadingDocumentImpl* mImpl;
 
76
        QPointer<KIO::TransferJob> mTransferJob;
 
77
        QFuture<bool> mMetaInfoFuture;
 
78
        QFutureWatcher<bool> mMetaInfoFutureWatcher;
 
79
        QFuture<void> mImageDataFuture;
 
80
        QFutureWatcher<void> mImageDataFutureWatcher;
 
81
 
 
82
        // If != 0, this means we need to load an image at zoom =
 
83
        // 1/mImageDataInvertedZoom
 
84
        int mImageDataInvertedZoom;
 
85
 
 
86
        bool mMetaInfoLoaded;
 
87
        bool mAnimated;
 
88
        bool mDownSampledImageLoaded;
 
89
        QByteArray mData;
 
90
        QByteArray mFormat;
 
91
        QSize mImageSize;
 
92
        Exiv2::Image::AutoPtr mExiv2Image;
 
93
        std::auto_ptr<JpegContent> mJpegContent;
 
94
        QImage mImage;
 
95
 
 
96
 
 
97
        /**
 
98
         * Determine kind of document and switch to an implementation if it is not
 
99
         * necessary to download more data.
 
100
         * @return true if switched to another implementation.
 
101
         */
 
102
        bool determineKind() {
 
103
                QString mimeType;
 
104
                const KUrl& url = mImpl->document()->url();
 
105
                if (KProtocolInfo::determineMimetypeFromExtension(url.protocol())) {
 
106
                        mimeType = KMimeType::findByNameAndContent(url.fileName(), mData)->name();
 
107
                } else {
 
108
                        mimeType = KMimeType::findByContent(mData)->name();
 
109
                }
 
110
                MimeTypeUtils::Kind kind = MimeTypeUtils::mimeTypeKind(mimeType);
 
111
                LOG("mimeType:" << mimeType);
 
112
                LOG("kind:" << kind);
 
113
                mImpl->setDocumentKind(kind);
 
114
 
 
115
                switch (kind) {
 
116
                case MimeTypeUtils::KIND_RASTER_IMAGE:
 
117
                case MimeTypeUtils::KIND_SVG_IMAGE:
 
118
                        return false;
 
119
 
 
120
                case MimeTypeUtils::KIND_VIDEO:
 
121
                        mImpl->switchToImpl(new VideoDocumentLoadedImpl(mImpl->document()));
 
122
                        return true;
 
123
 
 
124
                default:
 
125
                        mImpl->setDocumentErrorString(
 
126
                                i18nc("@info", "Gwenview cannot display documents of type %1.", mimeType)
 
127
                                );
 
128
                        emit mImpl->loadingFailed();
 
129
                        mImpl->switchToImpl(new EmptyDocumentImpl(mImpl->document()));
 
130
                        return true;
 
131
                }
 
132
        }
 
133
 
 
134
 
 
135
        void startLoading() {
 
136
                Q_ASSERT(!mMetaInfoLoaded);
 
137
 
 
138
                switch (mImpl->document()->kind()) {
 
139
                case MimeTypeUtils::KIND_RASTER_IMAGE:
 
140
                        mMetaInfoFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadMetaInfo);
 
141
                        mMetaInfoFutureWatcher.setFuture(mMetaInfoFuture);
 
142
                        break;
 
143
 
 
144
                case MimeTypeUtils::KIND_SVG_IMAGE:
 
145
                        mImpl->switchToImpl(new SvgDocumentLoadedImpl(mImpl->document(), mData));
 
146
                        break;
 
147
 
 
148
                case MimeTypeUtils::KIND_VIDEO:
 
149
                        break;
 
150
 
 
151
                default:
 
152
                        kWarning() << "We should not reach this point!";
 
153
                        break;
 
154
                }
 
155
        }
 
156
 
 
157
        void startImageDataLoading() {
 
158
                LOG("");
 
159
                Q_ASSERT(mMetaInfoLoaded);
 
160
                Q_ASSERT(mImageDataInvertedZoom != 0);
 
161
                Q_ASSERT(!mImageDataFuture.isRunning());
 
162
                mImageDataFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadImageData);
 
163
                mImageDataFutureWatcher.setFuture(mImageDataFuture);
 
164
        }
 
165
 
 
166
        bool loadMetaInfo() {
 
167
                QBuffer buffer;
 
168
                buffer.setBuffer(&mData);
 
169
                buffer.open(QIODevice::ReadOnly);
 
170
                QImageReader reader(&buffer);
 
171
                mFormat = reader.format();
 
172
                if (mFormat.isEmpty()) {
 
173
                        return false;
 
174
                }
 
175
 
 
176
                Exiv2ImageLoader loader;
 
177
                if (loader.load(mData)) {
 
178
                        mExiv2Image = loader.popImage();
 
179
                }
 
180
 
 
181
                if (mFormat == "jpeg" && mExiv2Image.get()) {
 
182
                        mJpegContent.reset(new JpegContent());
 
183
                        if (!mJpegContent->loadFromData(mData, mExiv2Image.get())) {
 
184
                                return false;
 
185
                        }
 
186
 
 
187
                        // Use the size from JpegContent, as its correctly transposed if the
 
188
                        // image has been rotated
 
189
                        mImageSize = mJpegContent->size();
 
190
                } else {
 
191
                        mImageSize = reader.size();
 
192
                }
 
193
                LOG("mImageSize" << mImageSize);
 
194
 
 
195
                return true;
 
196
        }
 
197
 
 
198
        void loadImageData() {
 
199
                QBuffer buffer;
 
200
                buffer.setBuffer(&mData);
 
201
                buffer.open(QIODevice::ReadOnly);
 
202
                QImageReader reader(&buffer);
 
203
 
 
204
                LOG("mImageDataInvertedZoom=" << mImageDataInvertedZoom);
 
205
                if (mImageSize.isValid()
 
206
                        && mImageDataInvertedZoom != 1
 
207
                        && reader.supportsOption(QImageIOHandler::ScaledSize)
 
208
                        )
 
209
                {
 
210
                        // Do not use mImageSize here: QImageReader needs a non-transposed
 
211
                        // image size
 
212
                        QSize size = reader.size() / mImageDataInvertedZoom;
 
213
                        if (!size.isEmpty()) {
 
214
                                LOG("Setting scaled size to" << size);
 
215
                                reader.setScaledSize(size);
 
216
                        } else {
 
217
                                LOG("Not setting scaled size as it is empty" << size);
 
218
                        }
 
219
                }
 
220
 
 
221
                bool ok = reader.read(&mImage);
 
222
                if (!ok) {
 
223
                        LOG("QImageReader::read() failed");
 
224
                        return;
 
225
                }
 
226
 
 
227
                if (mJpegContent.get()) {
 
228
                        Gwenview::Orientation orientation = mJpegContent->orientation();
 
229
                        QMatrix matrix = ImageUtils::transformMatrix(orientation);
 
230
                        mImage = mImage.transformed(matrix);
 
231
                }
 
232
 
 
233
                if (reader.supportsAnimation()
 
234
                        && reader.nextImageDelay() > 0 // Assume delay == 0 <=> only one frame
 
235
                ) {
 
236
                        /*
 
237
                         * QImageReader is not really helpful to detect animated gif:
 
238
                         * - QImageReader::imageCount() returns 0
 
239
                         * - QImageReader::nextImageDelay() may return something > 0 if the
 
240
                         *   image consists of only one frame but includes a "Graphic
 
241
                         *   Control Extension" (usually only present if we have an
 
242
                         *   animation) (Bug #185523)
 
243
                         *
 
244
                         * Decoding the next frame is the only reliable way I found to
 
245
                         * detect an animated gif
 
246
                         */
 
247
                        LOG("May be an animated image. delay:" << reader.nextImageDelay());
 
248
                        QImage nextImage;
 
249
                        if (reader.read(&nextImage)) {
 
250
                                LOG("Really an animated image (more than one frame)");
 
251
                                mAnimated = true;
 
252
                        } else {
 
253
                                kWarning() << mImpl->document()->url() << "is not really an animated image (only one frame)";
 
254
                        }
 
255
                }
 
256
        }
 
257
};
 
258
 
 
259
 
 
260
LoadingDocumentImpl::LoadingDocumentImpl(Document* document)
 
261
: AbstractDocumentImpl(document)
 
262
, d(new LoadingDocumentImplPrivate) {
 
263
        d->mImpl = this;
 
264
        d->mMetaInfoLoaded = false;
 
265
        d->mAnimated = false;
 
266
        d->mDownSampledImageLoaded = false;
 
267
        d->mImageDataInvertedZoom = 0;
 
268
 
 
269
        connect(&d->mMetaInfoFutureWatcher, SIGNAL(finished()),
 
270
                SLOT(slotMetaInfoLoaded()) );
 
271
 
 
272
        connect(&d->mImageDataFutureWatcher, SIGNAL(finished()),
 
273
                SLOT(slotImageLoaded()) );
 
274
}
 
275
 
 
276
 
 
277
LoadingDocumentImpl::~LoadingDocumentImpl() {
 
278
        LOG("");
 
279
        // Disconnect watchers to make sure they do not trigger further work
 
280
        d->mMetaInfoFutureWatcher.disconnect();
 
281
        d->mImageDataFutureWatcher.disconnect();
 
282
 
 
283
        d->mMetaInfoFutureWatcher.waitForFinished();
 
284
        d->mImageDataFutureWatcher.waitForFinished();
 
285
 
 
286
        if (d->mTransferJob) {
 
287
                d->mTransferJob->kill();
 
288
        }
 
289
        delete d;
 
290
}
 
291
 
 
292
void LoadingDocumentImpl::init() {
 
293
        KUrl url = document()->url();
 
294
 
 
295
        if (UrlUtils::urlIsFastLocalFile(url)) {
 
296
                // Load file content directly
 
297
                QFile file(url.toLocalFile());
 
298
                if (!file.open(QIODevice::ReadOnly)) {
 
299
                        setDocumentErrorString(i18nc("@info", "Could not open file %1", url.toLocalFile()));
 
300
                        emit loadingFailed();
 
301
                        switchToImpl(new EmptyDocumentImpl(document()));
 
302
                        return;
 
303
                }
 
304
                d->mData = file.read(HEADER_SIZE);
 
305
                if (d->determineKind()) {
 
306
                        return;
 
307
                }
 
308
                d->mData += file.readAll();
 
309
                d->startLoading();
 
310
        } else {
 
311
                // Transfer file via KIO
 
312
                d->mTransferJob = KIO::get(document()->url());
 
313
                connect(d->mTransferJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
 
314
                        SLOT(slotDataReceived(KIO::Job*, const QByteArray&)) );
 
315
                connect(d->mTransferJob, SIGNAL(result(KJob*)),
 
316
                        SLOT(slotTransferFinished(KJob*)) );
 
317
                d->mTransferJob->start();
 
318
        }
 
319
}
 
320
 
 
321
 
 
322
void LoadingDocumentImpl::loadImage(int invertedZoom) {
 
323
        if (d->mImageDataInvertedZoom == invertedZoom) {
 
324
                LOG("Already loading an image at invertedZoom=" << invertedZoom);
 
325
                return;
 
326
        }
 
327
        if (d->mImageDataInvertedZoom == 1) {
 
328
                LOG("Ignoring request: we are loading a full image");
 
329
                return;
 
330
        }
 
331
        d->mImageDataFutureWatcher.waitForFinished();
 
332
        d->mImageDataInvertedZoom = invertedZoom;
 
333
 
 
334
        if (d->mMetaInfoLoaded) {
 
335
                // Do not test on mMetaInfoFuture.isRunning() here: it might not have
 
336
                // started if we are downloading the image from a remote url
 
337
                d->startImageDataLoading();
 
338
        }
 
339
}
 
340
 
 
341
 
 
342
void LoadingDocumentImpl::slotDataReceived(KIO::Job* job, const QByteArray& chunk) {
 
343
        d->mData.append(chunk);
 
344
        if (document()->kind() == MimeTypeUtils::KIND_UNKNOWN && d->mData.length() >= HEADER_SIZE) {
 
345
                if (d->determineKind()) {
 
346
                        job->kill();
 
347
                        return;
 
348
                }
 
349
        }
 
350
}
 
351
 
 
352
 
 
353
void LoadingDocumentImpl::slotTransferFinished(KJob* job) {
 
354
        if (job->error()) {
 
355
                setDocumentErrorString(job->errorString());
 
356
                emit loadingFailed();
 
357
                switchToImpl(new EmptyDocumentImpl(document()));
 
358
                return;
 
359
        }
 
360
        d->startLoading();
 
361
}
 
362
 
 
363
 
 
364
bool LoadingDocumentImpl::isEditable() const {
 
365
        return d->mDownSampledImageLoaded;
 
366
}
 
367
 
 
368
 
 
369
Document::LoadingState LoadingDocumentImpl::loadingState() const {
 
370
        if (!document()->image().isNull()) {
 
371
                return Document::Loaded;
 
372
        } else if (d->mMetaInfoLoaded) {
 
373
                return Document::MetaInfoLoaded;
 
374
        } else if (document()->kind() != MimeTypeUtils::KIND_UNKNOWN) {
 
375
                return Document::KindDetermined;
 
376
        } else {
 
377
                return Document::Loading;
 
378
        }
 
379
}
 
380
 
 
381
 
 
382
void LoadingDocumentImpl::slotMetaInfoLoaded() {
 
383
        LOG("");
 
384
        Q_ASSERT(!d->mMetaInfoFuture.isRunning());
 
385
        if (!d->mMetaInfoFuture.result()) {
 
386
                setDocumentErrorString(
 
387
                        i18nc("@info", "Loading meta information failed.")
 
388
                        );
 
389
                emit loadingFailed();
 
390
                switchToImpl(new EmptyDocumentImpl(document()));
 
391
                return;
 
392
        }
 
393
 
 
394
        setDocumentFormat(d->mFormat);
 
395
        setDocumentImageSize(d->mImageSize);
 
396
        setDocumentExiv2Image(d->mExiv2Image);
 
397
 
 
398
        d->mMetaInfoLoaded = true;
 
399
        emit metaInfoLoaded();
 
400
 
 
401
        // Start image loading if necessary
 
402
        // We test if mImageDataFuture is not already running because code connected to
 
403
        // metaInfoLoaded() signal could have called loadImage()
 
404
        if (!d->mImageDataFuture.isRunning() && d->mImageDataInvertedZoom != 0) {
 
405
                d->startImageDataLoading();
 
406
        }
 
407
}
 
408
 
 
409
 
 
410
void LoadingDocumentImpl::slotImageLoaded() {
 
411
        LOG("");
 
412
        if (d->mImage.isNull()) {
 
413
                setDocumentErrorString(
 
414
                        i18nc("@info", "Loading image failed.")
 
415
                        );
 
416
                emit loadingFailed();
 
417
                switchToImpl(new EmptyDocumentImpl(document()));
 
418
                return;
 
419
        }
 
420
 
 
421
        if (d->mAnimated) {
 
422
                if (d->mImage.size() == d->mImageSize) {
 
423
                        // We already decoded the first frame at the right size, let's show
 
424
                        // it
 
425
                        setDocumentImage(d->mImage);
 
426
                }
 
427
 
 
428
                switchToImpl(new AnimatedDocumentLoadedImpl(
 
429
                        document(),
 
430
                        d->mData));
 
431
 
 
432
                return;
 
433
        }
 
434
 
 
435
        if (d->mImageDataInvertedZoom != 1 && d->mImage.size() != d->mImageSize) {
 
436
                LOG("Loaded a down sampled image");
 
437
                d->mDownSampledImageLoaded = true;
 
438
                // We loaded a down sampled image
 
439
                setDocumentDownSampledImage(d->mImage, d->mImageDataInvertedZoom);
 
440
                return;
 
441
        }
 
442
 
 
443
        LOG("Loaded a full image");
 
444
        setDocumentImage(d->mImage);
 
445
        DocumentLoadedImpl* impl;
 
446
        if (d->mJpegContent.get()) {
 
447
                impl = new JpegDocumentLoadedImpl(
 
448
                        document(),
 
449
                        d->mJpegContent.release());
 
450
        } else {
 
451
                impl = new DocumentLoadedImpl(
 
452
                        document(),
 
453
                        d->mData);
 
454
        }
 
455
        switchToImpl(impl);
 
456
}
 
457
 
 
458
 
 
459
} // namespace