~ubuntu-branches/ubuntu/oneiric/kolourpaint/oneiric-proposed

« back to all changes in this revision

Viewing changes to document/kpDocument_Save.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Harald Sitter
  • Date: 2011-07-10 12:20:39 UTC
  • Revision ID: james.westby@ubuntu.com-20110710122039-a7bokz6isao1ldt2
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
 
 
2
/*
 
3
   Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
 
4
   All rights reserved.
 
5
 
 
6
   Redistribution and use in source and binary forms, with or without
 
7
   modification, are permitted provided that the following conditions
 
8
   are met:
 
9
 
 
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.
 
15
 
 
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.
 
26
*/
 
27
 
 
28
 
 
29
#define DEBUG_KP_DOCUMENT 0
 
30
 
 
31
 
 
32
#include <kpDocument.h>
 
33
#include <kpDocumentPrivate.h>
 
34
 
 
35
#include <math.h>
 
36
 
 
37
#include <qcolor.h>
 
38
#include <qbitmap.h>
 
39
#include <qbrush.h>
 
40
#include <qfile.h>
 
41
#include <qimage.h>
 
42
#include <qlist.h>
 
43
#include <QImage>
 
44
#include <qpainter.h>
 
45
#include <qrect.h>
 
46
#include <qsize.h>
 
47
#include <qmatrix.h>
 
48
 
 
49
#include <kdebug.h>
 
50
#include <kglobal.h>
 
51
#include <kimageio.h>
 
52
#include <kio/netaccess.h>
 
53
#include <klocale.h>
 
54
#include <kmessagebox.h>
 
55
#include <kmimetype.h>  // TODO: isn't this in KIO?
 
56
#include <KSaveFile>
 
57
#include <ktemporaryfile.h>
 
58
 
 
59
#include <kpColor.h>
 
60
#include <kpColorToolBar.h>
 
61
#include <kpDefs.h>
 
62
#include <kpDocumentEnvironment.h>
 
63
#include <kpDocumentSaveOptions.h>
 
64
#include <kpDocumentMetaInfo.h>
 
65
#include <kpEffectReduceColors.h>
 
66
#include <kpPixmapFX.h>
 
67
#include <kpTool.h>
 
68
#include <kpToolToolBar.h>
 
69
#include <kpUrlFormatter.h>
 
70
#include <kpViewManager.h>
 
71
 
 
72
 
 
73
bool kpDocument::save (bool overwritePrompt, bool lossyPrompt)
 
74
{
 
75
#if DEBUG_KP_DOCUMENT
 
76
    kDebug () << "kpDocument::save("
 
77
               << "overwritePrompt=" << overwritePrompt
 
78
               << ",lossyPrompt=" << lossyPrompt
 
79
               << ") url=" << m_url
 
80
               << " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore ()
 
81
               << endl;
 
82
#endif
 
83
 
 
84
    // TODO: check feels weak
 
85
    if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ())
 
86
    {
 
87
        KMessageBox::detailedError (d->environ->dialogParent (),
 
88
            i18n ("Could not save image - insufficient information."),
 
89
            i18n ("URL: %1\n"
 
90
                  "Mimetype: %2",
 
91
                  prettyUrl (),
 
92
                  m_saveOptions->mimeType ().isEmpty () ?
 
93
                          i18n ("<empty>") :
 
94
                          m_saveOptions->mimeType ()),
 
95
            i18nc ("@title:window", "Internal Error"));
 
96
        return false;
 
97
    }
 
98
 
 
99
    return saveAs (m_url, *m_saveOptions,
 
100
                   overwritePrompt,
 
101
                   lossyPrompt);
 
102
}
 
103
 
 
104
//---------------------------------------------------------------------
 
105
 
 
106
// public static
 
107
bool kpDocument::lossyPromptContinue (const QImage &pixmap,
 
108
                                      const kpDocumentSaveOptions &saveOptions,
 
109
                                      QWidget *parent)
 
110
{
 
111
#if DEBUG_KP_DOCUMENT
 
112
    kDebug () << "kpDocument::lossyPromptContinue()";
 
113
#endif
 
114
 
 
115
#define QUIT_IF_CANCEL(messageBoxCommand)            \
 
116
{                                                    \
 
117
    if (messageBoxCommand != KMessageBox::Continue)  \
 
118
    {                                                \
 
119
        return false;                                \
 
120
    }                                                \
 
121
}
 
122
 
 
123
    const int lossyType = saveOptions.isLossyForSaving (pixmap);
 
124
    if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow |
 
125
                     kpDocumentSaveOptions::Quality))
 
126
    {
 
127
        QUIT_IF_CANCEL (
 
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>"
 
131
 
 
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")));
 
140
    }
 
141
    else if (lossyType & kpDocumentSaveOptions::ColorDepthLow)
 
142
    {
 
143
        QUIT_IF_CANCEL (
 
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."
 
147
 
 
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>"
 
152
 
 
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")));
 
159
    }
 
160
#undef QUIT_IF_CANCEL
 
161
 
 
162
    return true;
 
163
}
 
164
 
 
165
//---------------------------------------------------------------------
 
166
 
 
167
// public static
 
168
bool kpDocument::savePixmapToDevice (const QImage &image,
 
169
                                     QIODevice *device,
 
170
                                     const kpDocumentSaveOptions &saveOptions,
 
171
                                     const kpDocumentMetaInfo &metaInfo,
 
172
                                     bool lossyPrompt,
 
173
                                     QWidget *parent,
 
174
                                     bool *userCancelled)
 
175
{
 
176
    if (userCancelled)
 
177
        *userCancelled = false;
 
178
 
 
179
    QStringList types = KImageIO::typeForMime (saveOptions.mimeType ());
 
180
#if DEBUG_KP_DOCUMENT
 
181
    kDebug () << "\ttypes=" << types;
 
182
#endif
 
183
    if (types.isEmpty ())
 
184
        return false;
 
185
 
 
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];
 
189
 
 
190
#if DEBUG_KP_DOCUMENT
 
191
    kDebug () << "\tmimeType=" << saveOptions.mimeType ()
 
192
               << " type=" << type << endl;
 
193
#endif
 
194
 
 
195
    if (lossyPrompt && !lossyPromptContinue (image, saveOptions, parent))
 
196
    {
 
197
        if (userCancelled)
 
198
            *userCancelled = true;
 
199
 
 
200
    #if DEBUG_KP_DOCUMENT
 
201
        kDebug () << "\treturning false because of lossyPrompt";
 
202
    #endif
 
203
        return false;
 
204
    }
 
205
 
 
206
 
 
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 ());
 
214
 
 
215
 
 
216
    //
 
217
    // Reduce colors if required
 
218
    //
 
219
 
 
220
#if DEBUG_KP_DOCUMENT
 
221
    kDebug () << "\tuseSaveOptionsColorDepth=" << useSaveOptionsColorDepth
 
222
              << "current image depth=" << image.depth ()
 
223
              << "save options depth=" << saveOptions.colorDepth ();
 
224
#endif
 
225
    QImage imageToSave(image);
 
226
 
 
227
    if (useSaveOptionsColorDepth &&
 
228
        imageToSave.depth () != saveOptions.colorDepth ())
 
229
    {
 
230
        // TODO: I think this erases the mask!
 
231
        //
 
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.
 
235
        //
 
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 ());
 
241
    }
 
242
 
 
243
 
 
244
    //
 
245
    // Write Meta Info
 
246
    //
 
247
 
 
248
    imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ());
 
249
    imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ());
 
250
    imageToSave.setOffset (metaInfo.offset ());
 
251
 
 
252
    QList <QString> keyList = metaInfo.textKeys ();
 
253
    for (QList <QString>::const_iterator it = keyList.constBegin ();
 
254
         it != keyList.constEnd ();
 
255
         it++)
 
256
    {
 
257
        imageToSave.setText (*it, metaInfo.text (*it));
 
258
    }
 
259
 
 
260
 
 
261
    //
 
262
    // Save at required quality
 
263
    //
 
264
 
 
265
    int quality = -1;  // default
 
266
 
 
267
    if (useSaveOptionsQuality)
 
268
        quality = saveOptions.quality ();
 
269
 
 
270
#if DEBUG_KP_DOCUMENT
 
271
    kDebug () << "\tsaving";
 
272
#endif
 
273
    if (!imageToSave.save (device, type.toLatin1 (), quality))
 
274
    {
 
275
    #if DEBUG_KP_DOCUMENT
 
276
        kDebug () << "\tQImage::save() returned false";
 
277
    #endif
 
278
        return false;
 
279
    }
 
280
 
 
281
 
 
282
#if DEBUG_KP_DOCUMENT
 
283
    kDebug () << "\tsave OK";
 
284
#endif
 
285
    return true;
 
286
}
 
287
 
 
288
//---------------------------------------------------------------------
 
289
 
 
290
static void CouldNotCreateTemporaryFileDialog (QWidget *parent)
 
291
{
 
292
    KMessageBox::error (parent,
 
293
                        i18n ("Could not save image - unable to create temporary file."));
 
294
}
 
295
 
 
296
//---------------------------------------------------------------------
 
297
 
 
298
static void CouldNotSaveDialog (const KUrl &url, QWidget *parent)
 
299
{
 
300
    // TODO: use file.errorString()
 
301
    KMessageBox::error (parent,
 
302
                        i18n ("Could not save as \"%1\".",
 
303
                              kpUrlFormatter::PrettyFilename (url)));
 
304
}
 
305
 
 
306
//---------------------------------------------------------------------
 
307
 
 
308
// public static
 
309
bool kpDocument::savePixmapToFile (const QImage &pixmap,
 
310
                                   const KUrl &url,
 
311
                                   const kpDocumentSaveOptions &saveOptions,
 
312
                                   const kpDocumentMetaInfo &metaInfo,
 
313
                                   bool overwritePrompt,
 
314
                                   bool lossyPrompt,
 
315
                                   QWidget *parent)
 
316
{
 
317
    // TODO: Use KIO::NetAccess:mostLocalURL() for accessing home:/ (and other
 
318
    //       such local URLs) for efficiency and because only local writes
 
319
    //       are atomic.
 
320
#if DEBUG_KP_DOCUMENT
 
321
    kDebug () << "kpDocument::savePixmapToFile ("
 
322
               << url
 
323
               << ",overwritePrompt=" << overwritePrompt
 
324
               << ",lossyPrompt=" << lossyPrompt
 
325
               << ")" << endl;
 
326
    saveOptions.printDebug (QLatin1String ("\tsaveOptions"));
 
327
    metaInfo.printDebug (QLatin1String ("\tmetaInfo"));
 
328
#endif
 
329
 
 
330
    if (overwritePrompt &&
 
331
        KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent))
 
332
    {
 
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)),
 
337
            QString(),
 
338
            KGuiItem(i18n ("Overwrite")));
 
339
 
 
340
        if (result != KMessageBox::Continue)
 
341
        {
 
342
        #if DEBUG_KP_DOCUMENT
 
343
            kDebug () << "\tuser doesn't want to overwrite";
 
344
        #endif
 
345
 
 
346
            return false;
 
347
        }
 
348
    }
 
349
 
 
350
 
 
351
    if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent))
 
352
    {
 
353
    #if DEBUG_KP_DOCUMENT
 
354
        kDebug () << "\treturning false because of lossyPrompt";
 
355
    #endif
 
356
        return false;
 
357
    }
 
358
 
 
359
 
 
360
    // Local file?
 
361
    if (url.isLocalFile ())
 
362
    {
 
363
        const QString filename = url.toLocalFile ();
 
364
 
 
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);
 
369
        {
 
370
            if (!atomicFileWriter.open ())
 
371
            {
 
372
                // We probably don't need this as <filename> has not been
 
373
                // opened.
 
374
                atomicFileWriter.abort ();
 
375
 
 
376
            #if DEBUG_KP_DOCUMENT
 
377
                kDebug () << "\treturning false because could not open KSaveFile"
 
378
                          << " error=" << atomicFileWriter.error () << endl;
 
379
            #endif
 
380
                ::CouldNotCreateTemporaryFileDialog (parent);
 
381
                return false;
 
382
            }
 
383
 
 
384
            // Write to local temporary file.
 
385
            if (!savePixmapToDevice (pixmap, &atomicFileWriter,
 
386
                                     saveOptions, metaInfo,
 
387
                                     false/*no lossy prompt*/,
 
388
                                     parent))
 
389
            {
 
390
                atomicFileWriter.abort ();
 
391
 
 
392
            #if DEBUG_KP_DOCUMENT
 
393
                kDebug () << "\treturning false because could not save pixmap to device"
 
394
                          << endl;
 
395
            #endif
 
396
                ::CouldNotSaveDialog (url, parent);
 
397
                return false;
 
398
            }
 
399
 
 
400
            // Atomically overwrite local file with the temporary file
 
401
            // we saved to.
 
402
            if (!atomicFileWriter.finalize ())
 
403
            {
 
404
                atomicFileWriter.abort ();
 
405
 
 
406
            #if DEBUG_KP_DOCUMENT
 
407
                kDebug () << "\tcould not close KSaveFile";
 
408
            #endif
 
409
                ::CouldNotSaveDialog (url, parent);
 
410
                return false;
 
411
            }
 
412
        }  // sync KSaveFile.abort()
 
413
    }
 
414
    // Remote file?
 
415
    else
 
416
    {
 
417
        // Create temporary file that is deleted when the variable goes
 
418
        // out of scope.
 
419
        KTemporaryFile tempFile;
 
420
        if (!tempFile.open ())
 
421
        {
 
422
        #if DEBUG_KP_DOCUMENT
 
423
            kDebug () << "\treturning false because could not open tempFile";
 
424
        #endif
 
425
            ::CouldNotCreateTemporaryFileDialog (parent);
 
426
            return false;
 
427
        }
 
428
 
 
429
        // Write to local temporary file.
 
430
        if (!savePixmapToDevice (pixmap, &tempFile,
 
431
                                 saveOptions, metaInfo,
 
432
                                 false/*no lossy prompt*/,
 
433
                                 parent))
 
434
        {
 
435
        #if DEBUG_KP_DOCUMENT
 
436
            kDebug () << "\treturning false because could not save pixmap to device"
 
437
                        << endl;
 
438
        #endif
 
439
            ::CouldNotSaveDialog (url, parent);
 
440
            return false;
 
441
        }
 
442
 
 
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 << "'";
 
448
    #endif
 
449
        Q_ASSERT (!tempFileName.isEmpty ());
 
450
 
 
451
        tempFile.close ();
 
452
        if (tempFile.error () != QFile::NoError)
 
453
        {
 
454
        #if DEBUG_KP_DOCUMENT
 
455
            kDebug () << "\treturning false because could not close";
 
456
        #endif
 
457
            ::CouldNotSaveDialog (url, parent);
 
458
            return false;
 
459
        }
 
460
 
 
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))
 
466
        {
 
467
        #if DEBUG_KP_DOCUMENT
 
468
            kDebug () << "\treturning false because could not upload";
 
469
        #endif
 
470
            KMessageBox::error (parent,
 
471
                                i18n ("Could not save image - failed to upload."));
 
472
            return false;
 
473
        }
 
474
    }
 
475
 
 
476
 
 
477
    return true;
 
478
}
 
479
 
 
480
//---------------------------------------------------------------------
 
481
 
 
482
bool kpDocument::saveAs (const KUrl &url,
 
483
                         const kpDocumentSaveOptions &saveOptions,
 
484
                         bool overwritePrompt,
 
485
                         bool lossyPrompt)
 
486
{
 
487
#if DEBUG_KP_DOCUMENT
 
488
    kDebug () << "kpDocument::saveAs (" << url << ","
 
489
               << saveOptions.mimeType () << ")" << endl;
 
490
#endif
 
491
 
 
492
    if (kpDocument::savePixmapToFile (imageWithSelection (),
 
493
                                      url,
 
494
                                      saveOptions, *metaInfo (),
 
495
                                      overwritePrompt,
 
496
                                      lossyPrompt,
 
497
                                      d->environ->dialogParent ()))
 
498
    {
 
499
        setURL (url, true/*is from url*/);
 
500
        *m_saveOptions = saveOptions;
 
501
        m_modified = false;
 
502
 
 
503
        m_savedAtLeastOnceBefore = true;
 
504
 
 
505
        emit documentSaved ();
 
506
        return true;
 
507
    }
 
508
    else
 
509
    {
 
510
        return false;
 
511
    }
 
512
}
 
513
 
 
514
//---------------------------------------------------------------------