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)
127
inline int invertedZoomForZoom(qreal zoom) {
129
for (invertedZoom = 1; zoom < 1./(invertedZoom*2); invertedZoom*=2) {}
134
const QImage& Document::downSampledImageForZoom(qreal zoom) const {
135
static const QImage sNullImage;
137
int invertedZoom = invertedZoomForZoom(zoom);
138
if (invertedZoom == 1) {
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()) {
154
return d->mDownSampledImageMap[invertedZoom];
158
Document::LoadingState Document::loadingState() const {
159
return d->mImpl->loadingState();
163
void Document::switchToImpl(AbstractDocumentImpl* impl) {
166
d->mImpl->deleteLater();
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()) );
184
void Document::setImageInternal(const QImage& image) {
186
d->mDownSampledImageMap.clear();
188
// If we didn't get the image size before decoding the full image, set it
190
setSize(d->mImage.size());
194
KUrl Document::url() const {
199
QByteArray Document::rawData() const {
200
return d->mImpl->rawData();
204
bool Document::keepRawData() const {
205
return d->mKeepRawData;
209
void Document::setKeepRawData(bool value) {
210
d->mKeepRawData = value;
214
void Document::waitUntilLoaded() {
215
startLoadingFullImage();
217
LoadingState state = loadingState();
218
if (state == Loaded || state == LoadingFailed) {
221
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
226
DocumentJob* Document::save(const KUrl& url, const QByteArray& format) {
228
DocumentJob* job = d->mImpl->save(url, format);
230
kWarning() << "Implementation does not support saving!";
231
setErrorString(i18nc("@info", "Gwenview cannot save this kind of documents."));
234
job->setProperty("oldUrl", d->mUrl);
235
job->setProperty("newUrl", url);
236
connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*)));
242
void Document::slotSaveResult(KJob* job) {
244
setErrorString(job->errorString());
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);
255
QByteArray Document::format() const {
260
void Document::setFormat(const QByteArray& format) {
262
emit metaInfoUpdated();
266
MimeTypeUtils::Kind Document::kind() const {
271
void Document::setKind(MimeTypeUtils::Kind kind) {
273
emit kindDetermined(d->mUrl);
277
QSize Document::size() const {
282
bool Document::hasAlphaChannel() const {
283
if (d->mImage.isNull()) {
286
return d->mImage.hasAlphaChannel();
291
int Document::memoryUsage() const {
292
// FIXME: Take undo stack into account
293
int usage = d->mImage.numBytes();
294
usage += rawData().length();
299
void Document::setSize(const QSize& size) {
300
if (size == d->mSize) {
304
d->mImageMetaInfoModel.setImageSize(size);
305
emit metaInfoUpdated();
309
bool Document::isModified() const {
310
return !d->mUndoStack.isClean();
314
AbstractDocumentEditor* Document::editor() {
315
return d->mImpl->editor();
319
void Document::setExiv2Image(Exiv2::Image::AutoPtr image) {
320
d->mExiv2Image = image;
321
d->mImageMetaInfoModel.setExiv2Image(d->mExiv2Image.get());
322
emit metaInfoUpdated();
326
void Document::setDownSampledImage(const QImage& image, int invertedZoom) {
327
Q_ASSERT(!d->mDownSampledImageMap.contains(invertedZoom));
328
d->mDownSampledImageMap[invertedZoom] = image;
329
emit downSampledImageReady();
333
QString Document::errorString() const {
334
return d->mErrorString;
338
void Document::setErrorString(const QString& string) {
339
d->mErrorString = string;
343
ImageMetaInfoModel* Document::metaInfo() const {
344
return &d->mImageMetaInfoModel;
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);
356
d->scheduleImageLoading(1);
357
} else if (state == Loaded) {
359
} else if (state == LoadingFailed) {
360
kWarning() << "Can't load full image: loading has already failed";
365
bool Document::prepareDownSampledImageForZoom(qreal zoom) {
366
if (zoom >= maxDownSampledZoom()) {
367
kWarning() << "No need to call prepareDownSampledImageForZoom if zoom >= " << maxDownSampledZoom();
371
int invertedZoom = invertedZoomForZoom(zoom);
372
if (d->mDownSampledImageMap.contains(invertedZoom)) {
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;
387
// Schedule down sampled image loading
388
d->scheduleImageLoading(invertedZoom);
394
void Document::emitMetaInfoLoaded() {
395
emit metaInfoLoaded(d->mUrl);
399
void Document::emitLoaded() {
400
emit loaded(d->mUrl);
404
void Document::emitLoadingFailed() {
405
emit loadingFailed(d->mUrl);
409
QUndoStack* Document::undoStack() const {
410
return &d->mUndoStack;
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
419
saved(d->mUrl, d->mUrl);
426
bool Document::isEditable() const {
427
return d->mImpl->isEditable();
431
bool Document::isAnimated() const {
432
return d->mImpl->isAnimated();
436
void Document::startAnimation() {
437
return d->mImpl->startAnimation();
441
void Document::stopAnimation() {
442
return d->mImpl->stopAnimation();
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) {
452
busyChanged(d->mUrl, true);
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));
464
d->mJobQueue.dequeue();
465
if (d->mJobQueue.isEmpty()) {
466
busyChanged(d->mUrl, false);
469
d->mJobQueue.head()->start();
473
bool Document::isBusy() const {
474
return !d->mJobQueue.isEmpty();
128
inline int invertedZoomForZoom(qreal zoom)
131
for (invertedZoom = 1; zoom < 1. / (invertedZoom * 2); invertedZoom *= 2) {}
135
const QImage& Document::downSampledImageForZoom(qreal zoom) const
137
static const QImage sNullImage;
139
int invertedZoom = invertedZoomForZoom(zoom);
140
if (invertedZoom == 1) {
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()) {
156
return d->mDownSampledImageMap[invertedZoom];
159
Document::LoadingState Document::loadingState() const
161
return d->mImpl->loadingState();
164
void Document::switchToImpl(AbstractDocumentImpl* impl)
168
d->mImpl->deleteLater();
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()));
185
void Document::setImageInternal(const QImage& image)
188
d->mDownSampledImageMap.clear();
190
// If we didn't get the image size before decoding the full image, set it
192
setSize(d->mImage.size());
195
KUrl Document::url() const
200
QByteArray Document::rawData() const
202
return d->mImpl->rawData();
205
bool Document::keepRawData() const
207
return d->mKeepRawData;
210
void Document::setKeepRawData(bool value)
212
d->mKeepRawData = value;
215
void Document::waitUntilLoaded()
217
startLoadingFullImage();
219
LoadingState state = loadingState();
220
if (state == Loaded || state == LoadingFailed) {
223
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
227
DocumentJob* Document::save(const KUrl& url, const QByteArray& format)
230
DocumentJob* job = d->mImpl->save(url, format);
232
kWarning() << "Implementation does not support saving!";
233
setErrorString(i18nc("@info", "Gwenview cannot save this kind of documents."));
236
job->setProperty("oldUrl", d->mUrl);
237
job->setProperty("newUrl", url);
238
connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*)));
243
void Document::slotSaveResult(KJob* job)
246
setErrorString(job->errorString());
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);
256
QByteArray Document::format() const
261
void Document::setFormat(const QByteArray& format)
264
emit metaInfoUpdated();
267
MimeTypeUtils::Kind Document::kind() const
272
void Document::setKind(MimeTypeUtils::Kind kind)
275
emit kindDetermined(d->mUrl);
278
QSize Document::size() const
283
bool Document::hasAlphaChannel() const
285
if (d->mImage.isNull()) {
288
return d->mImage.hasAlphaChannel();
292
int Document::memoryUsage() const
294
// FIXME: Take undo stack into account
295
int usage = d->mImage.numBytes();
296
usage += rawData().length();
300
void Document::setSize(const QSize& size)
302
if (size == d->mSize) {
306
d->mImageMetaInfoModel.setImageSize(size);
307
emit metaInfoUpdated();
310
bool Document::isModified() const
312
return !d->mUndoStack.isClean();
315
AbstractDocumentEditor* Document::editor()
317
return d->mImpl->editor();
320
void Document::setExiv2Image(Exiv2::Image::AutoPtr image)
322
d->mExiv2Image = image;
323
d->mImageMetaInfoModel.setExiv2Image(d->mExiv2Image.get());
324
emit metaInfoUpdated();
327
void Document::setDownSampledImage(const QImage& image, int invertedZoom)
329
Q_ASSERT(!d->mDownSampledImageMap.contains(invertedZoom));
330
d->mDownSampledImageMap[invertedZoom] = image;
331
emit downSampledImageReady();
334
QString Document::errorString() const
336
return d->mErrorString;
339
void Document::setErrorString(const QString& string)
341
d->mErrorString = string;
344
ImageMetaInfoModel* Document::metaInfo() const
346
return &d->mImageMetaInfoModel;
349
void Document::startLoadingFullImage()
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);
358
d->scheduleImageLoading(1);
359
} else if (state == Loaded) {
361
} else if (state == LoadingFailed) {
362
kWarning() << "Can't load full image: loading has already failed";
366
bool Document::prepareDownSampledImageForZoom(qreal zoom)
368
if (zoom >= maxDownSampledZoom()) {
369
kWarning() << "No need to call prepareDownSampledImageForZoom if zoom >= " << maxDownSampledZoom();
373
int invertedZoom = invertedZoomForZoom(zoom);
374
if (d->mDownSampledImageMap.contains(invertedZoom)) {
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;
389
// Schedule down sampled image loading
390
d->scheduleImageLoading(invertedZoom);
395
void Document::emitMetaInfoLoaded()
397
emit metaInfoLoaded(d->mUrl);
400
void Document::emitLoaded()
402
emit loaded(d->mUrl);
405
void Document::emitLoadingFailed()
407
emit loadingFailed(d->mUrl);
410
QUndoStack* Document::undoStack() const
412
return &d->mUndoStack;
415
void Document::slotUndoIndexChanged()
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
421
saved(d->mUrl, d->mUrl);
427
bool Document::isEditable() const
429
return d->mImpl->isEditable();
432
bool Document::isAnimated() const
434
return d->mImpl->isAnimated();
437
void Document::startAnimation()
439
return d->mImpl->startAnimation();
442
void Document::stopAnimation()
444
return d->mImpl->stopAnimation();
447
void Document::enqueueJob(DocumentJob* job)
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) {
455
busyChanged(d->mUrl, true);
459
void Document::slotJobDestroyed(QObject* job)
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));
468
d->mJobQueue.dequeue();
469
if (d->mJobQueue.isEmpty()) {
470
busyChanged(d->mUrl, false);
473
d->mJobQueue.head()->start();
477
bool Document::isBusy() const
479
return !d->mJobQueue.isEmpty();
482
QSvgRenderer* Document::svgRenderer() const
484
return d->mImpl->svgRenderer();