3
Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
6
Redistribution and use in source and binary forms, with or without
7
modification, are permitted provided that the following conditions
10
1. Redistributions of source code must retain the above copyright
11
notice, this list of conditions and the following disclaimer.
12
2. Redistributions in binary form must reproduce the above copyright
13
notice, this list of conditions and the following disclaimer in the
14
documentation and/or other materials provided with the distribution.
16
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
#define DEBUG_KP_DOCUMENT 0
32
#include <kpDocument.h>
33
#include <kpDocumentPrivate.h>
52
#include <kio/netaccess.h>
54
#include <kmessagebox.h>
55
#include <kmimetype.h> // TODO: isn't this in KIO?
57
#include <ktemporaryfile.h>
60
#include <kpColorToolBar.h>
62
#include <kpDocumentEnvironment.h>
63
#include <kpDocumentSaveOptions.h>
64
#include <kpDocumentMetaInfo.h>
65
#include <kpEffectReduceColors.h>
66
#include <kpPixmapFX.h>
68
#include <kpToolToolBar.h>
69
#include <kpUrlFormatter.h>
70
#include <kpViewManager.h>
73
bool kpDocument::save (bool overwritePrompt, bool lossyPrompt)
76
kDebug () << "kpDocument::save("
77
<< "overwritePrompt=" << overwritePrompt
78
<< ",lossyPrompt=" << lossyPrompt
80
<< " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore ()
84
// TODO: check feels weak
85
if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ())
87
KMessageBox::detailedError (d->environ->dialogParent (),
88
i18n ("Could not save image - insufficient information."),
92
m_saveOptions->mimeType ().isEmpty () ?
94
m_saveOptions->mimeType ()),
95
i18nc ("@title:window", "Internal Error"));
99
return saveAs (m_url, *m_saveOptions,
104
//---------------------------------------------------------------------
107
bool kpDocument::lossyPromptContinue (const QImage &pixmap,
108
const kpDocumentSaveOptions &saveOptions,
111
#if DEBUG_KP_DOCUMENT
112
kDebug () << "kpDocument::lossyPromptContinue()";
115
#define QUIT_IF_CANCEL(messageBoxCommand) \
117
if (messageBoxCommand != KMessageBox::Continue) \
123
const int lossyType = saveOptions.isLossyForSaving (pixmap);
124
if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow |
125
kpDocumentSaveOptions::Quality))
128
KMessageBox::warningContinueCancel (parent,
129
i18n ("<qt><p>The <b>%1</b> format may not be able"
130
" to preserve all of the image's color information.</p>"
132
"<p>Are you sure you want to save in this format?</p></qt>",
133
KMimeType::mimeType (saveOptions.mimeType ())->comment ()),
134
// TODO: caption misleading for lossless formats that have
135
// low maximum colour depth
136
i18nc ("@title:window", "Lossy File Format"),
137
KStandardGuiItem::save (),
138
KStandardGuiItem::cancel(),
139
QLatin1String ("SaveInLossyMimeTypeDontAskAgain")));
141
else if (lossyType & kpDocumentSaveOptions::ColorDepthLow)
144
KMessageBox::warningContinueCancel (parent,
145
i18n ("<qt><p>Saving the image at the low color depth of %1-bit"
146
" may result in the loss of color information."
148
// TODO: It looks like 8-bit QImage's now support alpha.
149
// Update kpDocumentSaveOptions::isLossyForSaving()
150
// and change "might" to "will".
151
" Any transparency might also be removed.</p>"
153
"<p>Are you sure you want to save at this color depth?</p></qt>",
154
saveOptions.colorDepth ()),
155
i18nc ("@title:window", "Low Color Depth"),
156
KStandardGuiItem::save (),
157
KStandardGuiItem::cancel(),
158
QLatin1String ("SaveAtLowColorDepthDontAskAgain")));
160
#undef QUIT_IF_CANCEL
165
//---------------------------------------------------------------------
168
bool kpDocument::savePixmapToDevice (const QImage &image,
170
const kpDocumentSaveOptions &saveOptions,
171
const kpDocumentMetaInfo &metaInfo,
177
*userCancelled = false;
179
QStringList types = KImageIO::typeForMime (saveOptions.mimeType ());
180
#if DEBUG_KP_DOCUMENT
181
kDebug () << "\ttypes=" << types;
183
if (types.isEmpty ())
186
// It's safe to arbitrarily choose the 0th type as any type in the list
187
// should invoke the same KImageIO image loader.
188
const QString type = types [0];
190
#if DEBUG_KP_DOCUMENT
191
kDebug () << "\tmimeType=" << saveOptions.mimeType ()
192
<< " type=" << type << endl;
195
if (lossyPrompt && !lossyPromptContinue (image, saveOptions, parent))
198
*userCancelled = true;
200
#if DEBUG_KP_DOCUMENT
201
kDebug () << "\treturning false because of lossyPrompt";
207
// TODO: fix dup with kpDocumentSaveOptions::isLossyForSaving()
208
const bool useSaveOptionsColorDepth =
209
(saveOptions.mimeTypeHasConfigurableColorDepth () &&
210
!saveOptions.colorDepthIsInvalid ());
211
const bool useSaveOptionsQuality =
212
(saveOptions.mimeTypeHasConfigurableQuality () &&
213
!saveOptions.qualityIsInvalid ());
217
// Reduce colors if required
220
#if DEBUG_KP_DOCUMENT
221
kDebug () << "\tuseSaveOptionsColorDepth=" << useSaveOptionsColorDepth
222
<< "current image depth=" << image.depth ()
223
<< "save options depth=" << saveOptions.colorDepth ();
225
QImage imageToSave(image);
227
if (useSaveOptionsColorDepth &&
228
imageToSave.depth () != saveOptions.colorDepth ())
230
// TODO: I think this erases the mask!
232
// I suspect this doesn't matter since this is only called to
233
// reduce color depth and QImage's with depth < 32 don't
234
// support masks anyway.
236
// Later: I think the mask is preserved for 8-bit since Qt4
237
// seems to support it for QImage.
238
imageToSave = kpEffectReduceColors::convertImageDepth (imageToSave,
239
saveOptions.colorDepth (),
240
saveOptions.dither ());
248
imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ());
249
imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ());
250
imageToSave.setOffset (metaInfo.offset ());
252
QList <QString> keyList = metaInfo.textKeys ();
253
for (QList <QString>::const_iterator it = keyList.constBegin ();
254
it != keyList.constEnd ();
257
imageToSave.setText (*it, metaInfo.text (*it));
262
// Save at required quality
265
int quality = -1; // default
267
if (useSaveOptionsQuality)
268
quality = saveOptions.quality ();
270
#if DEBUG_KP_DOCUMENT
271
kDebug () << "\tsaving";
273
if (!imageToSave.save (device, type.toLatin1 (), quality))
275
#if DEBUG_KP_DOCUMENT
276
kDebug () << "\tQImage::save() returned false";
282
#if DEBUG_KP_DOCUMENT
283
kDebug () << "\tsave OK";
288
//---------------------------------------------------------------------
290
static void CouldNotCreateTemporaryFileDialog (QWidget *parent)
292
KMessageBox::error (parent,
293
i18n ("Could not save image - unable to create temporary file."));
296
//---------------------------------------------------------------------
298
static void CouldNotSaveDialog (const KUrl &url, QWidget *parent)
300
// TODO: use file.errorString()
301
KMessageBox::error (parent,
302
i18n ("Could not save as \"%1\".",
303
kpUrlFormatter::PrettyFilename (url)));
306
//---------------------------------------------------------------------
309
bool kpDocument::savePixmapToFile (const QImage &pixmap,
311
const kpDocumentSaveOptions &saveOptions,
312
const kpDocumentMetaInfo &metaInfo,
313
bool overwritePrompt,
317
// TODO: Use KIO::NetAccess:mostLocalURL() for accessing home:/ (and other
318
// such local URLs) for efficiency and because only local writes
320
#if DEBUG_KP_DOCUMENT
321
kDebug () << "kpDocument::savePixmapToFile ("
323
<< ",overwritePrompt=" << overwritePrompt
324
<< ",lossyPrompt=" << lossyPrompt
326
saveOptions.printDebug (QLatin1String ("\tsaveOptions"));
327
metaInfo.printDebug (QLatin1String ("\tmetaInfo"));
330
if (overwritePrompt &&
331
KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent))
333
int result = KMessageBox::warningContinueCancel (parent,
334
i18n ("A document called \"%1\" already exists.\n"
335
"Do you want to overwrite it?",
336
kpUrlFormatter::PrettyFilename (url)),
338
KGuiItem(i18n ("Overwrite")));
340
if (result != KMessageBox::Continue)
342
#if DEBUG_KP_DOCUMENT
343
kDebug () << "\tuser doesn't want to overwrite";
351
if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent))
353
#if DEBUG_KP_DOCUMENT
354
kDebug () << "\treturning false because of lossyPrompt";
361
if (url.isLocalFile ())
363
const QString filename = url.toLocalFile ();
365
// sync: All failure exit paths _must_ call KSaveFile::abort() or
366
// else, the KSaveFile destructor will overwrite the file,
367
// <filename>, despite the failure.
368
KSaveFile atomicFileWriter (filename);
370
if (!atomicFileWriter.open ())
372
// We probably don't need this as <filename> has not been
374
atomicFileWriter.abort ();
376
#if DEBUG_KP_DOCUMENT
377
kDebug () << "\treturning false because could not open KSaveFile"
378
<< " error=" << atomicFileWriter.error () << endl;
380
::CouldNotCreateTemporaryFileDialog (parent);
384
// Write to local temporary file.
385
if (!savePixmapToDevice (pixmap, &atomicFileWriter,
386
saveOptions, metaInfo,
387
false/*no lossy prompt*/,
390
atomicFileWriter.abort ();
392
#if DEBUG_KP_DOCUMENT
393
kDebug () << "\treturning false because could not save pixmap to device"
396
::CouldNotSaveDialog (url, parent);
400
// Atomically overwrite local file with the temporary file
402
if (!atomicFileWriter.finalize ())
404
atomicFileWriter.abort ();
406
#if DEBUG_KP_DOCUMENT
407
kDebug () << "\tcould not close KSaveFile";
409
::CouldNotSaveDialog (url, parent);
412
} // sync KSaveFile.abort()
417
// Create temporary file that is deleted when the variable goes
419
KTemporaryFile tempFile;
420
if (!tempFile.open ())
422
#if DEBUG_KP_DOCUMENT
423
kDebug () << "\treturning false because could not open tempFile";
425
::CouldNotCreateTemporaryFileDialog (parent);
429
// Write to local temporary file.
430
if (!savePixmapToDevice (pixmap, &tempFile,
431
saveOptions, metaInfo,
432
false/*no lossy prompt*/,
435
#if DEBUG_KP_DOCUMENT
436
kDebug () << "\treturning false because could not save pixmap to device"
439
::CouldNotSaveDialog (url, parent);
443
// Collect name of temporary file now, as QTemporaryFile::fileName()
444
// stops working after close() is called.
445
const QString tempFileName = tempFile.fileName ();
446
#if DEBUG_KP_DOCUMENT
447
kDebug () << "\ttempFileName='" << tempFileName << "'";
449
Q_ASSERT (!tempFileName.isEmpty ());
452
if (tempFile.error () != QFile::NoError)
454
#if DEBUG_KP_DOCUMENT
455
kDebug () << "\treturning false because could not close";
457
::CouldNotSaveDialog (url, parent);
461
// Copy local temporary file to overwrite remote.
462
// TODO: No one seems to know how to do this atomically
463
// [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2].
464
// At least, fish:// (ssh) is definitely not atomic.
465
if (!KIO::NetAccess::upload (tempFileName, url, parent))
467
#if DEBUG_KP_DOCUMENT
468
kDebug () << "\treturning false because could not upload";
470
KMessageBox::error (parent,
471
i18n ("Could not save image - failed to upload."));
480
//---------------------------------------------------------------------
482
bool kpDocument::saveAs (const KUrl &url,
483
const kpDocumentSaveOptions &saveOptions,
484
bool overwritePrompt,
487
#if DEBUG_KP_DOCUMENT
488
kDebug () << "kpDocument::saveAs (" << url << ","
489
<< saveOptions.mimeType () << ")" << endl;
492
if (kpDocument::savePixmapToFile (imageWithSelection (),
494
saveOptions, *metaInfo (),
497
d->environ->dialogParent ()))
499
setURL (url, true/*is from url*/);
500
*m_saveOptions = saveOptions;
503
m_savedAtLeastOnceBefore = true;
505
emit documentSaved ();
514
//---------------------------------------------------------------------