72
73
const int HEADER_SIZE = 256;
74
75
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;
82
// If != 0, this means we need to load an image at zoom =
83
// 1/mImageDataInvertedZoom
84
int mImageDataInvertedZoom;
88
bool mDownSampledImageLoaded;
92
Exiv2::Image::AutoPtr mExiv2Image;
93
std::auto_ptr<JpegContent> mJpegContent;
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.
102
bool determineKind() {
104
const KUrl& url = mImpl->document()->url();
105
if (KProtocolInfo::determineMimetypeFromExtension(url.protocol())) {
106
mimeType = KMimeType::findByNameAndContent(url.fileName(), mData)->name();
108
mimeType = KMimeType::findByContent(mData)->name();
110
MimeTypeUtils::Kind kind = MimeTypeUtils::mimeTypeKind(mimeType);
111
LOG("mimeType:" << mimeType);
112
LOG("kind:" << kind);
113
mImpl->setDocumentKind(kind);
116
case MimeTypeUtils::KIND_RASTER_IMAGE:
117
case MimeTypeUtils::KIND_SVG_IMAGE:
120
case MimeTypeUtils::KIND_VIDEO:
121
mImpl->switchToImpl(new VideoDocumentLoadedImpl(mImpl->document()));
125
mImpl->setDocumentErrorString(
126
i18nc("@info", "Gwenview cannot display documents of type %1.", mimeType)
128
emit mImpl->loadingFailed();
129
mImpl->switchToImpl(new EmptyDocumentImpl(mImpl->document()));
135
void startLoading() {
136
Q_ASSERT(!mMetaInfoLoaded);
138
switch (mImpl->document()->kind()) {
139
case MimeTypeUtils::KIND_RASTER_IMAGE:
140
mMetaInfoFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadMetaInfo);
141
mMetaInfoFutureWatcher.setFuture(mMetaInfoFuture);
144
case MimeTypeUtils::KIND_SVG_IMAGE:
145
mImpl->switchToImpl(new SvgDocumentLoadedImpl(mImpl->document(), mData));
148
case MimeTypeUtils::KIND_VIDEO:
152
kWarning() << "We should not reach this point!";
157
void startImageDataLoading() {
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);
166
bool loadMetaInfo() {
168
buffer.setBuffer(&mData);
169
buffer.open(QIODevice::ReadOnly);
170
QImageReader reader(&buffer);
171
mFormat = reader.format();
172
if (mFormat.isEmpty()) {
176
Exiv2ImageLoader loader;
177
if (loader.load(mData)) {
178
mExiv2Image = loader.popImage();
181
if (mFormat == "jpeg" && mExiv2Image.get()) {
182
mJpegContent.reset(new JpegContent());
183
if (!mJpegContent->loadFromData(mData, mExiv2Image.get())) {
187
// Use the size from JpegContent, as its correctly transposed if the
188
// image has been rotated
189
mImageSize = mJpegContent->size();
191
mImageSize = reader.size();
193
LOG("mImageSize" << mImageSize);
198
void loadImageData() {
200
buffer.setBuffer(&mData);
201
buffer.open(QIODevice::ReadOnly);
202
QImageReader reader(&buffer);
204
LOG("mImageDataInvertedZoom=" << mImageDataInvertedZoom);
205
if (mImageSize.isValid()
206
&& mImageDataInvertedZoom != 1
207
&& reader.supportsOption(QImageIOHandler::ScaledSize)
210
// Do not use mImageSize here: QImageReader needs a non-transposed
212
QSize size = reader.size() / mImageDataInvertedZoom;
213
if (!size.isEmpty()) {
214
LOG("Setting scaled size to" << size);
215
reader.setScaledSize(size);
217
LOG("Not setting scaled size as it is empty" << size);
221
bool ok = reader.read(&mImage);
223
LOG("QImageReader::read() failed");
227
if (mJpegContent.get()) {
228
Gwenview::Orientation orientation = mJpegContent->orientation();
229
QMatrix matrix = ImageUtils::transformMatrix(orientation);
230
mImage = mImage.transformed(matrix);
233
if (reader.supportsAnimation()
234
&& reader.nextImageDelay() > 0 // Assume delay == 0 <=> only one frame
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)
244
* Decoding the next frame is the only reliable way I found to
245
* detect an animated gif
247
LOG("May be an animated image. delay:" << reader.nextImageDelay());
249
if (reader.read(&nextImage)) {
250
LOG("Really an animated image (more than one frame)");
253
kWarning() << mImpl->document()->url() << "is not really an animated image (only one frame)";
76
LoadingDocumentImpl* q;
77
QPointer<KIO::TransferJob> mTransferJob;
78
QFuture<bool> mMetaInfoFuture;
79
QFutureWatcher<bool> mMetaInfoFutureWatcher;
80
QFuture<void> mImageDataFuture;
81
QFutureWatcher<void> mImageDataFutureWatcher;
83
// If != 0, this means we need to load an image at zoom =
84
// 1/mImageDataInvertedZoom
85
int mImageDataInvertedZoom;
89
bool mDownSampledImageLoaded;
93
Exiv2::Image::AutoPtr mExiv2Image;
94
std::auto_ptr<JpegContent> mJpegContent;
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.
105
const KUrl& url = q->document()->url();
106
if (KProtocolInfo::determineMimetypeFromExtension(url.protocol())) {
107
mimeType = KMimeType::findByNameAndContent(url.fileName(), mData)->name();
109
mimeType = KMimeType::findByContent(mData)->name();
111
MimeTypeUtils::Kind kind = MimeTypeUtils::mimeTypeKind(mimeType);
112
LOG("mimeType:" << mimeType);
113
LOG("kind:" << kind);
114
q->setDocumentKind(kind);
117
case MimeTypeUtils::KIND_RASTER_IMAGE:
118
case MimeTypeUtils::KIND_SVG_IMAGE:
121
case MimeTypeUtils::KIND_VIDEO:
122
q->switchToImpl(new VideoDocumentLoadedImpl(q->document()));
126
q->setDocumentErrorString(
127
i18nc("@info", "Gwenview cannot display documents of type %1.", mimeType)
129
emit q->loadingFailed();
130
q->switchToImpl(new EmptyDocumentImpl(q->document()));
137
Q_ASSERT(!mMetaInfoLoaded);
139
switch (q->document()->kind()) {
140
case MimeTypeUtils::KIND_RASTER_IMAGE:
141
mMetaInfoFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadMetaInfo);
142
mMetaInfoFutureWatcher.setFuture(mMetaInfoFuture);
145
case MimeTypeUtils::KIND_SVG_IMAGE:
146
q->switchToImpl(new SvgDocumentLoadedImpl(q->document(), mData));
149
case MimeTypeUtils::KIND_VIDEO:
153
kWarning() << "We should not reach this point!";
158
void startImageDataLoading()
161
Q_ASSERT(mMetaInfoLoaded);
162
Q_ASSERT(mImageDataInvertedZoom != 0);
163
Q_ASSERT(!mImageDataFuture.isRunning());
164
mImageDataFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadImageData);
165
mImageDataFutureWatcher.setFuture(mImageDataFuture);
171
buffer.setBuffer(&mData);
172
buffer.open(QIODevice::ReadOnly);
173
QImageReader reader(&buffer);
174
mFormat = reader.format();
175
if (mFormat.isEmpty()) {
179
Exiv2ImageLoader loader;
180
if (loader.load(mData)) {
181
mExiv2Image = loader.popImage();
184
if (mFormat == "jpeg" && mExiv2Image.get()) {
185
mJpegContent.reset(new JpegContent());
186
if (!mJpegContent->loadFromData(mData, mExiv2Image.get())) {
190
// Use the size from JpegContent, as its correctly transposed if the
191
// image has been rotated
192
mImageSize = mJpegContent->size();
194
mImageSize = reader.size();
196
LOG("mImageSize" << mImageSize);
204
buffer.setBuffer(&mData);
205
buffer.open(QIODevice::ReadOnly);
206
QImageReader reader(&buffer);
208
LOG("mImageDataInvertedZoom=" << mImageDataInvertedZoom);
209
if (mImageSize.isValid()
210
&& mImageDataInvertedZoom != 1
211
&& reader.supportsOption(QImageIOHandler::ScaledSize)
213
// Do not use mImageSize here: QImageReader needs a non-transposed
215
QSize size = reader.size() / mImageDataInvertedZoom;
216
if (!size.isEmpty()) {
217
LOG("Setting scaled size to" << size);
218
reader.setScaledSize(size);
220
LOG("Not setting scaled size as it is empty" << size);
224
bool ok = reader.read(&mImage);
226
LOG("QImageReader::read() failed");
230
if (mJpegContent.get()) {
231
Gwenview::Orientation orientation = mJpegContent->orientation();
232
QMatrix matrix = ImageUtils::transformMatrix(orientation);
233
mImage = mImage.transformed(matrix);
236
if (reader.supportsAnimation()
237
&& reader.nextImageDelay() > 0 // Assume delay == 0 <=> only one frame
240
* QImageReader is not really helpful to detect animated gif:
241
* - QImageReader::imageCount() returns 0
242
* - QImageReader::nextImageDelay() may return something > 0 if the
243
* image consists of only one frame but includes a "Graphic
244
* Control Extension" (usually only present if we have an
245
* animation) (Bug #185523)
247
* Decoding the next frame is the only reliable way I found to
248
* detect an animated gif
250
LOG("May be an animated image. delay:" << reader.nextImageDelay());
252
if (reader.read(&nextImage)) {
253
LOG("Really an animated image (more than one frame)");
256
kWarning() << q->document()->url() << "is not really an animated image (only one frame)";
260
262
LoadingDocumentImpl::LoadingDocumentImpl(Document* document)
261
263
: AbstractDocumentImpl(document)
262
, d(new LoadingDocumentImplPrivate) {
264
d->mMetaInfoLoaded = false;
265
d->mAnimated = false;
266
d->mDownSampledImageLoaded = false;
267
d->mImageDataInvertedZoom = 0;
269
connect(&d->mMetaInfoFutureWatcher, SIGNAL(finished()),
270
SLOT(slotMetaInfoLoaded()) );
272
connect(&d->mImageDataFutureWatcher, SIGNAL(finished()),
273
SLOT(slotImageLoaded()) );
277
LoadingDocumentImpl::~LoadingDocumentImpl() {
279
// Disconnect watchers to make sure they do not trigger further work
280
d->mMetaInfoFutureWatcher.disconnect();
281
d->mImageDataFutureWatcher.disconnect();
283
d->mMetaInfoFutureWatcher.waitForFinished();
284
d->mImageDataFutureWatcher.waitForFinished();
286
if (d->mTransferJob) {
287
d->mTransferJob->kill();
292
void LoadingDocumentImpl::init() {
293
KUrl url = document()->url();
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()));
304
d->mData = file.read(HEADER_SIZE);
305
if (d->determineKind()) {
308
d->mData += file.readAll();
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();
322
void LoadingDocumentImpl::loadImage(int invertedZoom) {
323
if (d->mImageDataInvertedZoom == invertedZoom) {
324
LOG("Already loading an image at invertedZoom=" << invertedZoom);
327
if (d->mImageDataInvertedZoom == 1) {
328
LOG("Ignoring request: we are loading a full image");
331
d->mImageDataFutureWatcher.waitForFinished();
332
d->mImageDataInvertedZoom = invertedZoom;
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();
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()) {
353
void LoadingDocumentImpl::slotTransferFinished(KJob* job) {
355
setDocumentErrorString(job->errorString());
356
emit loadingFailed();
357
switchToImpl(new EmptyDocumentImpl(document()));
364
bool LoadingDocumentImpl::isEditable() const {
365
return d->mDownSampledImageLoaded;
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;
377
return Document::Loading;
382
void LoadingDocumentImpl::slotMetaInfoLoaded() {
384
Q_ASSERT(!d->mMetaInfoFuture.isRunning());
385
if (!d->mMetaInfoFuture.result()) {
386
setDocumentErrorString(
387
i18nc("@info", "Loading meta information failed.")
389
emit loadingFailed();
390
switchToImpl(new EmptyDocumentImpl(document()));
394
setDocumentFormat(d->mFormat);
395
setDocumentImageSize(d->mImageSize);
396
setDocumentExiv2Image(d->mExiv2Image);
398
d->mMetaInfoLoaded = true;
399
emit metaInfoLoaded();
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();
410
void LoadingDocumentImpl::slotImageLoaded() {
412
if (d->mImage.isNull()) {
413
setDocumentErrorString(
414
i18nc("@info", "Loading image failed.")
416
emit loadingFailed();
417
switchToImpl(new EmptyDocumentImpl(document()));
422
if (d->mImage.size() == d->mImageSize) {
423
// We already decoded the first frame at the right size, let's show
425
setDocumentImage(d->mImage);
428
switchToImpl(new AnimatedDocumentLoadedImpl(
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);
443
LOG("Loaded a full image");
444
setDocumentImage(d->mImage);
445
DocumentLoadedImpl* impl;
446
if (d->mJpegContent.get()) {
447
impl = new JpegDocumentLoadedImpl(
449
d->mJpegContent.release());
451
impl = new DocumentLoadedImpl(
264
, d(new LoadingDocumentImplPrivate)
267
d->mMetaInfoLoaded = false;
268
d->mAnimated = false;
269
d->mDownSampledImageLoaded = false;
270
d->mImageDataInvertedZoom = 0;
272
connect(&d->mMetaInfoFutureWatcher, SIGNAL(finished()),
273
SLOT(slotMetaInfoLoaded()));
275
connect(&d->mImageDataFutureWatcher, SIGNAL(finished()),
276
SLOT(slotImageLoaded()));
279
LoadingDocumentImpl::~LoadingDocumentImpl()
282
// Disconnect watchers to make sure they do not trigger further work
283
d->mMetaInfoFutureWatcher.disconnect();
284
d->mImageDataFutureWatcher.disconnect();
286
d->mMetaInfoFutureWatcher.waitForFinished();
287
d->mImageDataFutureWatcher.waitForFinished();
289
if (d->mTransferJob) {
290
d->mTransferJob->kill();
295
void LoadingDocumentImpl::init()
297
KUrl url = document()->url();
299
if (UrlUtils::urlIsFastLocalFile(url)) {
300
// Load file content directly
301
QFile file(url.toLocalFile());
302
if (!file.open(QIODevice::ReadOnly)) {
303
setDocumentErrorString(i18nc("@info", "Could not open file %1", url.toLocalFile()));
304
emit loadingFailed();
305
switchToImpl(new EmptyDocumentImpl(document()));
308
d->mData = file.read(HEADER_SIZE);
309
if (d->determineKind()) {
312
d->mData += file.readAll();
315
// Transfer file via KIO
316
d->mTransferJob = KIO::get(document()->url());
317
connect(d->mTransferJob, SIGNAL(data(KIO::Job*, QByteArray)),
318
SLOT(slotDataReceived(KIO::Job*, QByteArray)));
319
connect(d->mTransferJob, SIGNAL(result(KJob*)),
320
SLOT(slotTransferFinished(KJob*)));
321
d->mTransferJob->start();
325
void LoadingDocumentImpl::loadImage(int invertedZoom)
327
if (d->mImageDataInvertedZoom == invertedZoom) {
328
LOG("Already loading an image at invertedZoom=" << invertedZoom);
331
if (d->mImageDataInvertedZoom == 1) {
332
LOG("Ignoring request: we are loading a full image");
335
d->mImageDataFutureWatcher.waitForFinished();
336
d->mImageDataInvertedZoom = invertedZoom;
338
if (d->mMetaInfoLoaded) {
339
// Do not test on mMetaInfoFuture.isRunning() here: it might not have
340
// started if we are downloading the image from a remote url
341
d->startImageDataLoading();
345
void LoadingDocumentImpl::slotDataReceived(KIO::Job* job, const QByteArray& chunk)
347
d->mData.append(chunk);
348
if (document()->kind() == MimeTypeUtils::KIND_UNKNOWN && d->mData.length() >= HEADER_SIZE) {
349
if (d->determineKind()) {
356
void LoadingDocumentImpl::slotTransferFinished(KJob* job)
359
setDocumentErrorString(job->errorString());
360
emit loadingFailed();
361
switchToImpl(new EmptyDocumentImpl(document()));
367
bool LoadingDocumentImpl::isEditable() const
369
return d->mDownSampledImageLoaded;
372
Document::LoadingState LoadingDocumentImpl::loadingState() const
374
if (!document()->image().isNull()) {
375
return Document::Loaded;
376
} else if (d->mMetaInfoLoaded) {
377
return Document::MetaInfoLoaded;
378
} else if (document()->kind() != MimeTypeUtils::KIND_UNKNOWN) {
379
return Document::KindDetermined;
381
return Document::Loading;
385
void LoadingDocumentImpl::slotMetaInfoLoaded()
388
Q_ASSERT(!d->mMetaInfoFuture.isRunning());
389
if (!d->mMetaInfoFuture.result()) {
390
setDocumentErrorString(
391
i18nc("@info", "Loading meta information failed.")
393
emit loadingFailed();
394
switchToImpl(new EmptyDocumentImpl(document()));
398
setDocumentFormat(d->mFormat);
399
setDocumentImageSize(d->mImageSize);
400
setDocumentExiv2Image(d->mExiv2Image);
402
d->mMetaInfoLoaded = true;
403
emit metaInfoLoaded();
405
// Start image loading if necessary
406
// We test if mImageDataFuture is not already running because code connected to
407
// metaInfoLoaded() signal could have called loadImage()
408
if (!d->mImageDataFuture.isRunning() && d->mImageDataInvertedZoom != 0) {
409
d->startImageDataLoading();
413
void LoadingDocumentImpl::slotImageLoaded()
416
if (d->mImage.isNull()) {
417
setDocumentErrorString(
418
i18nc("@info", "Loading image failed.")
420
emit loadingFailed();
421
switchToImpl(new EmptyDocumentImpl(document()));
426
if (d->mImage.size() == d->mImageSize) {
427
// We already decoded the first frame at the right size, let's show
429
setDocumentImage(d->mImage);
432
switchToImpl(new AnimatedDocumentLoadedImpl(
439
if (d->mImageDataInvertedZoom != 1 && d->mImage.size() != d->mImageSize) {
440
LOG("Loaded a down sampled image");
441
d->mDownSampledImageLoaded = true;
442
// We loaded a down sampled image
443
setDocumentDownSampledImage(d->mImage, d->mImageDataInvertedZoom);
447
LOG("Loaded a full image");
448
setDocumentImage(d->mImage);
449
DocumentLoadedImpl* impl;
450
if (d->mJpegContent.get()) {
451
impl = new JpegDocumentLoadedImpl(
453
d->mJpegContent.release());
455
impl = new DocumentLoadedImpl(