|
|
|
|
|
|
|
/*
|
|
|
|
Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org>
|
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions
|
|
|
|
are met:
|
|
|
|
|
|
|
|
1. Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in the
|
|
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#define DEBUG_KP_DOCUMENT 0
|
|
|
|
|
|
|
|
|
|
|
|
#include <kpdocument.h>
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include <tqcolor.h>
|
|
|
|
#include <tqbitmap.h>
|
|
|
|
#include <tqbrush.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqimage.h>
|
|
|
|
#include <tqpixmap.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqrect.h>
|
|
|
|
#include <tqsize.h>
|
|
|
|
#include <tqvaluelist.h>
|
|
|
|
#include <tqwmatrix.h>
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <kimageio.h>
|
|
|
|
#include <kio/netaccess.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kmessagebox.h>
|
|
|
|
#include <kmimetype.h>
|
|
|
|
#include <ksavefile.h>
|
|
|
|
#include <ktempfile.h>
|
|
|
|
|
|
|
|
#include <kpcolor.h>
|
|
|
|
#include <kpcolortoolbar.h>
|
|
|
|
#include <kpdefs.h>
|
|
|
|
#include <kpdocumentsaveoptions.h>
|
|
|
|
#include <kpdocumentmetainfo.h>
|
|
|
|
#include <kpeffectreducecolors.h>
|
|
|
|
#include <kpmainwindow.h>
|
|
|
|
#include <kppixmapfx.h>
|
|
|
|
#include <kpselection.h>
|
|
|
|
#include <kptool.h>
|
|
|
|
#include <kptooltoolbar.h>
|
|
|
|
#include <kpviewmanager.h>
|
|
|
|
|
|
|
|
|
|
|
|
struct kpDocumentPrivate
|
|
|
|
{
|
|
|
|
kpDocumentPrivate ()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
kpDocument::kpDocument (int w, int h, kpMainWindow *mainWindow)
|
|
|
|
: m_constructorWidth (w), m_constructorHeight (h),
|
|
|
|
m_mainWindow (mainWindow),
|
|
|
|
m_isFromURL (false),
|
|
|
|
m_savedAtLeastOnceBefore (false),
|
|
|
|
m_saveOptions (new kpDocumentSaveOptions ()),
|
|
|
|
m_metaInfo (new kpDocumentMetaInfo ()),
|
|
|
|
m_modified (false),
|
|
|
|
m_selection (0),
|
|
|
|
m_oldWidth (-1), m_oldHeight (-1),
|
|
|
|
d (new kpDocumentPrivate ())
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT && 0
|
|
|
|
kdDebug () << "kpDocument::kpDocument (" << w << "," << h << ")" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
m_pixmap = new TQPixmap (w, h);
|
|
|
|
m_pixmap->fill (TQt::white);
|
|
|
|
}
|
|
|
|
|
|
|
|
kpDocument::~kpDocument ()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
|
|
|
|
delete m_pixmap;
|
|
|
|
|
|
|
|
delete m_saveOptions;
|
|
|
|
delete m_metaInfo;
|
|
|
|
|
|
|
|
delete m_selection;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
kpMainWindow *kpDocument::mainWindow () const
|
|
|
|
{
|
|
|
|
return m_mainWindow;
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::setMainWindow (kpMainWindow *mainWindow)
|
|
|
|
{
|
|
|
|
m_mainWindow = mainWindow;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* File I/O
|
|
|
|
*/
|
|
|
|
|
|
|
|
// public static
|
|
|
|
TQPixmap kpDocument::convertToPixmapAsLosslessAsPossible (
|
|
|
|
const TQImage &image,
|
|
|
|
const kpPixmapFX::WarnAboutLossInfo &wali,
|
|
|
|
|
|
|
|
kpDocumentSaveOptions *saveOptions,
|
|
|
|
kpDocumentMetaInfo *metaInfo)
|
|
|
|
{
|
|
|
|
if (image.isNull ())
|
|
|
|
return TQPixmap ();
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\timage: depth=" << image.depth ()
|
|
|
|
<< " (X display=" << TQColor::numBitPlanes () << ")"
|
|
|
|
<< " hasAlphaBuffer=" << image.hasAlphaBuffer ()
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (saveOptions)
|
|
|
|
{
|
|
|
|
saveOptions->setColorDepth (image.depth ());
|
|
|
|
saveOptions->setDither (false); // avoid double dithering when saving
|
|
|
|
}
|
|
|
|
|
|
|
|
if (metaInfo)
|
|
|
|
{
|
|
|
|
metaInfo->setDotsPerMeterX (image.dotsPerMeterX ());
|
|
|
|
metaInfo->setDotsPerMeterY (image.dotsPerMeterY ());
|
|
|
|
metaInfo->setOffset (image.offset ());
|
|
|
|
|
|
|
|
TQValueList <TQImageTextKeyLang> keyList = image.textList ();
|
|
|
|
for (TQValueList <TQImageTextKeyLang>::const_iterator it = keyList.begin ();
|
|
|
|
it != keyList.end ();
|
|
|
|
it++)
|
|
|
|
{
|
|
|
|
metaInfo->setText (*it, image.text (*it));
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
metaInfo->printDebug ("\tmetaInfo");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT && 1
|
|
|
|
{
|
|
|
|
if (image.width () <= 16 && image.height () <= 16)
|
|
|
|
{
|
|
|
|
kdDebug () << "Image dump:" << endl;
|
|
|
|
|
|
|
|
for (int y = 0; y < image.height (); y++)
|
|
|
|
{
|
|
|
|
for (int x = 0; x < image.width (); x++)
|
|
|
|
{
|
|
|
|
const TQRgb rgb = image.pixel (x, y);
|
|
|
|
fprintf (stderr, " %08X", rgb);
|
|
|
|
}
|
|
|
|
fprintf (stderr, "\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
TQPixmap newPixmap = kpPixmapFX::convertToPixmapAsLosslessAsPossible (image, wali);
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT && 1
|
|
|
|
{
|
|
|
|
const TQImage image2 = kpPixmapFX::convertToImage (newPixmap);
|
|
|
|
kdDebug () << "(Converted to pixmap) Image dump:" << endl;
|
|
|
|
|
|
|
|
bool differsFromOrgImage = false;
|
|
|
|
unsigned long hash = 0;
|
|
|
|
int numDiff = 0;
|
|
|
|
for (int y = 0; y < image2.height (); y++)
|
|
|
|
{
|
|
|
|
for (int x = 0; x < image2.width (); x++)
|
|
|
|
{
|
|
|
|
const TQRgb rgb = image2.pixel (x, y);
|
|
|
|
hash += ((x % 2) + 1) * rgb;
|
|
|
|
if (rgb != image.pixel (x, y))
|
|
|
|
{
|
|
|
|
differsFromOrgImage = true;
|
|
|
|
numDiff++;
|
|
|
|
}
|
|
|
|
if (image2.width () <= 16 && image2.height () <= 16)
|
|
|
|
fprintf (stderr, " %08X", rgb);
|
|
|
|
}
|
|
|
|
if (image2.width () <= 16 && image2.height () <= 16)
|
|
|
|
fprintf (stderr, "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
kdDebug () << "\tdiffersFromOrgImage="
|
|
|
|
<< differsFromOrgImage
|
|
|
|
<< " numDiff="
|
|
|
|
<< numDiff
|
|
|
|
<< " hash=" << hash << endl;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return newPixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public static
|
|
|
|
TQPixmap kpDocument::getPixmapFromFile (const KURL &url, bool suppressDoesntExistDialog,
|
|
|
|
TQWidget *parent,
|
|
|
|
kpDocumentSaveOptions *saveOptions,
|
|
|
|
kpDocumentMetaInfo *metaInfo)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (saveOptions)
|
|
|
|
*saveOptions = kpDocumentSaveOptions ();
|
|
|
|
|
|
|
|
if (metaInfo)
|
|
|
|
*metaInfo = kpDocumentMetaInfo ();
|
|
|
|
|
|
|
|
|
|
|
|
TQString tempFile;
|
|
|
|
if (url.isEmpty () || !KIO::NetAccess::download (url, tempFile, parent))
|
|
|
|
{
|
|
|
|
if (!suppressDoesntExistDialog)
|
|
|
|
{
|
|
|
|
KMessageBox::sorry (parent,
|
|
|
|
i18n ("Could not open \"%1\".")
|
|
|
|
.arg (kpDocument::prettyFilenameForURL (url)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQImage image;
|
|
|
|
|
|
|
|
// sync: remember to "KIO::NetAccess::removeTempFile (tempFile)" in all exit paths
|
|
|
|
{
|
|
|
|
TQString detectedMimeType = KImageIO::mimeType (tempFile);
|
|
|
|
if (saveOptions)
|
|
|
|
saveOptions->setMimeType (detectedMimeType);
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\ttempFile=" << tempFile << endl;
|
|
|
|
kdDebug () << "\tmimetype=" << detectedMimeType << endl;
|
|
|
|
kdDebug () << "\tsrc=" << url.path () << endl;
|
|
|
|
kdDebug () << "\tmimetype of src=" << KImageIO::mimeType (url.path ()) << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (detectedMimeType.isEmpty ())
|
|
|
|
{
|
|
|
|
KMessageBox::sorry (parent,
|
|
|
|
i18n ("Could not open \"%1\" - unknown mimetype.")
|
|
|
|
.arg (kpDocument::prettyFilenameForURL (url)));
|
|
|
|
KIO::NetAccess::removeTempFile (tempFile);
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
image = TQImage (tempFile);
|
|
|
|
KIO::NetAccess::removeTempFile (tempFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (image.isNull ())
|
|
|
|
{
|
|
|
|
KMessageBox::sorry (parent,
|
|
|
|
i18n ("Could not open \"%1\" - unsupported image format.\n"
|
|
|
|
"The file may be corrupt.")
|
|
|
|
.arg (kpDocument::prettyFilenameForURL (url)));
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
|
|
|
|
const TQPixmap newPixmap = kpDocument::convertToPixmapAsLosslessAsPossible (image,
|
|
|
|
kpPixmapFX::WarnAboutLossInfo (
|
|
|
|
i18n ("The image \"%1\""
|
|
|
|
" may have more colors than the current screen mode."
|
|
|
|
" In order to display it, some colors may be changed."
|
|
|
|
" Try increasing your screen depth to at least %2bpp."
|
|
|
|
|
|
|
|
"\nIt also"
|
|
|
|
|
|
|
|
" contains translucency which is not fully"
|
|
|
|
" supported. The translucency data will be"
|
|
|
|
" approximated with a 1-bit transparency mask.")
|
|
|
|
.arg (prettyFilenameForURL (url)),
|
|
|
|
i18n ("The image \"%1\""
|
|
|
|
" may have more colors than the current screen mode."
|
|
|
|
" In order to display it, some colors may be changed."
|
|
|
|
" Try increasing your screen depth to at least %2bpp.")
|
|
|
|
.arg (prettyFilenameForURL (url)),
|
|
|
|
i18n ("The image \"%1\""
|
|
|
|
" contains translucency which is not fully"
|
|
|
|
" supported. The translucency data will be"
|
|
|
|
" approximated with a 1-bit transparency mask.")
|
|
|
|
.arg (prettyFilenameForURL (url)),
|
|
|
|
"docOpen",
|
|
|
|
parent),
|
|
|
|
saveOptions,
|
|
|
|
metaInfo);
|
|
|
|
|
|
|
|
if (newPixmap.isNull ())
|
|
|
|
{
|
|
|
|
KMessageBox::sorry (parent,
|
|
|
|
i18n ("Could not open \"%1\" - out of graphics memory.")
|
|
|
|
.arg (kpDocument::prettyFilenameForURL (url)));
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\tpixmap: depth=" << newPixmap.depth ()
|
|
|
|
<< " hasAlphaChannelOrMask=" << newPixmap.hasAlpha ()
|
|
|
|
<< " hasAlphaChannel=" << newPixmap.hasAlphaChannel ()
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
return newPixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::openNew (const KURL &url)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "KpDocument::openNew (" << url << ")" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
m_pixmap->fill (TQt::white);
|
|
|
|
|
|
|
|
setURL (url, false/*not from url*/);
|
|
|
|
*m_saveOptions = kpDocumentSaveOptions ();
|
|
|
|
*m_metaInfo = kpDocumentMetaInfo ();
|
|
|
|
m_modified = false;
|
|
|
|
|
|
|
|
emit documentOpened ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool kpDocument::open (const KURL &url, bool newDocSameNameIfNotExist)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::open (" << url << ")" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
kpDocumentSaveOptions newSaveOptions;
|
|
|
|
kpDocumentMetaInfo newMetaInfo;
|
|
|
|
TQPixmap newPixmap = kpDocument::getPixmapFromFile (url,
|
|
|
|
newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/,
|
|
|
|
m_mainWindow,
|
|
|
|
&newSaveOptions,
|
|
|
|
&newMetaInfo);
|
|
|
|
|
|
|
|
if (!newPixmap.isNull ())
|
|
|
|
{
|
|
|
|
delete m_pixmap;
|
|
|
|
m_pixmap = new TQPixmap (newPixmap);
|
|
|
|
|
|
|
|
setURL (url, true/*is from url*/);
|
|
|
|
*m_saveOptions = newSaveOptions;
|
|
|
|
*m_metaInfo = newMetaInfo;
|
|
|
|
m_modified = false;
|
|
|
|
|
|
|
|
emit documentOpened ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newDocSameNameIfNotExist)
|
|
|
|
{
|
|
|
|
if (!url.isEmpty () &&
|
|
|
|
// not just a permission error?
|
|
|
|
!KIO::NetAccess::exists (url, true/*open*/, m_mainWindow))
|
|
|
|
{
|
|
|
|
openNew (url);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
openNew (KURL ());
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool kpDocument::save (bool overwritePrompt, bool lossyPrompt)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::save("
|
|
|
|
<< "overwritePrompt=" << overwritePrompt
|
|
|
|
<< ",lossyPrompt=" << lossyPrompt
|
|
|
|
<< ") url=" << m_url
|
|
|
|
<< " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore ()
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// TODO: check feels weak
|
|
|
|
if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ())
|
|
|
|
{
|
|
|
|
KMessageBox::detailedError (m_mainWindow,
|
|
|
|
i18n ("Could not save image - insufficient information."),
|
|
|
|
i18n ("URL: %1\n"
|
|
|
|
"Mimetype: %2")
|
|
|
|
.arg (prettyURL ())
|
|
|
|
.arg (m_saveOptions->mimeType ().isEmpty () ?
|
|
|
|
i18n ("<empty>") :
|
|
|
|
m_saveOptions->mimeType ()),
|
|
|
|
i18n ("Internal Error"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return saveAs (m_url, *m_saveOptions,
|
|
|
|
overwritePrompt,
|
|
|
|
lossyPrompt);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// public static
|
|
|
|
bool kpDocument::lossyPromptContinue (const TQPixmap &pixmap,
|
|
|
|
const kpDocumentSaveOptions &saveOptions,
|
|
|
|
TQWidget *parent)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::lossyPromptContinue()" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define QUIT_IF_CANCEL(messageBoxCommand) \
|
|
|
|
{ \
|
|
|
|
if (messageBoxCommand != KMessageBox::Continue) \
|
|
|
|
{ \
|
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
|
|
|
const int lossyType = saveOptions.isLossyForSaving (pixmap);
|
|
|
|
if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow |
|
|
|
|
kpDocumentSaveOptions::Quality))
|
|
|
|
{
|
|
|
|
QUIT_IF_CANCEL (
|
|
|
|
KMessageBox::warningContinueCancel (parent,
|
|
|
|
i18n ("<qt><p>The <b>%1</b> format may not be able"
|
|
|
|
" to preserve all of the image's color information.</p>"
|
|
|
|
|
|
|
|
"<p>Are you sure you want to save in this format?</p></qt>")
|
|
|
|
.arg (KMimeType::mimeType (saveOptions.mimeType ())->comment ()),
|
|
|
|
// TODO: caption misleading for lossless formats that have
|
|
|
|
// low maximum colour depth
|
|
|
|
i18n ("Lossy File Format"),
|
|
|
|
KStdGuiItem::save (),
|
|
|
|
TQString::fromLatin1 ("SaveInLossyMimeTypeDontAskAgain")));
|
|
|
|
}
|
|
|
|
else if (lossyType & kpDocumentSaveOptions::ColorDepthLow)
|
|
|
|
{
|
|
|
|
QUIT_IF_CANCEL (
|
|
|
|
KMessageBox::warningContinueCancel (parent,
|
|
|
|
i18n ("<qt><p>Saving the image at the low color depth of %1-bit"
|
|
|
|
" may result in the loss of color information."
|
|
|
|
|
|
|
|
" Any transparency will also be removed.</p>"
|
|
|
|
|
|
|
|
"<p>Are you sure you want to save at this color depth?</p></qt>")
|
|
|
|
.arg (saveOptions.colorDepth ()),
|
|
|
|
i18n ("Low Color Depth"),
|
|
|
|
KStdGuiItem::save (),
|
|
|
|
TQString::fromLatin1 ("SaveAtLowColorDepthDontAskAgain")));
|
|
|
|
}
|
|
|
|
#undef QUIT_IF_CANCEL
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public static
|
|
|
|
bool kpDocument::savePixmapToDevice (const TQPixmap &pixmap,
|
|
|
|
TQIODevice *device,
|
|
|
|
const kpDocumentSaveOptions &saveOptions,
|
|
|
|
const kpDocumentMetaInfo &metaInfo,
|
|
|
|
bool lossyPrompt,
|
|
|
|
TQWidget *parent,
|
|
|
|
bool *userCancelled)
|
|
|
|
{
|
|
|
|
if (userCancelled)
|
|
|
|
*userCancelled = false;
|
|
|
|
|
|
|
|
TQString type = KImageIO::typeForMime (saveOptions.mimeType ());
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\tmimeType=" << saveOptions.mimeType ()
|
|
|
|
<< " type=" << type << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent))
|
|
|
|
{
|
|
|
|
if (userCancelled)
|
|
|
|
*userCancelled = true;
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because of lossyPrompt" << endl;
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQPixmap pixmapToSave =
|
|
|
|
kpPixmapFX::pixmapWithDefinedTransparentPixels (pixmap,
|
|
|
|
TQt::white); // CONFIG
|
|
|
|
TQImage imageToSave = kpPixmapFX::convertToImage (pixmapToSave);
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: fix dup with kpDocumentSaveOptions::isLossyForSaving()
|
|
|
|
const bool useSaveOptionsColorDepth =
|
|
|
|
(saveOptions.mimeTypeHasConfigurableColorDepth () &&
|
|
|
|
!saveOptions.colorDepthIsInvalid ());
|
|
|
|
const bool useSaveOptionsQuality =
|
|
|
|
(saveOptions.mimeTypeHasConfigurableQuality () &&
|
|
|
|
!saveOptions.qualityIsInvalid ());
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Reduce colors if required
|
|
|
|
//
|
|
|
|
|
|
|
|
if (useSaveOptionsColorDepth &&
|
|
|
|
imageToSave.depth () != saveOptions.colorDepth ())
|
|
|
|
{
|
|
|
|
imageToSave = ::convertImageDepth (imageToSave,
|
|
|
|
saveOptions.colorDepth (),
|
|
|
|
saveOptions.dither ());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Write Meta Info
|
|
|
|
//
|
|
|
|
|
|
|
|
imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ());
|
|
|
|
imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ());
|
|
|
|
imageToSave.setOffset (metaInfo.offset ());
|
|
|
|
|
|
|
|
TQValueList <TQImageTextKeyLang> keyList = metaInfo.textList ();
|
|
|
|
for (TQValueList <TQImageTextKeyLang>::const_iterator it = keyList.begin ();
|
|
|
|
it != keyList.end ();
|
|
|
|
it++)
|
|
|
|
{
|
|
|
|
imageToSave.setText ((*it).key, (*it).lang, metaInfo.text (*it));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Save at required quality
|
|
|
|
//
|
|
|
|
|
|
|
|
int quality = -1; // default
|
|
|
|
|
|
|
|
if (useSaveOptionsQuality)
|
|
|
|
quality = saveOptions.quality ();
|
|
|
|
|
|
|
|
if (!imageToSave.save (device, type.latin1 (), quality))
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\tTQImage::save() returned false" << endl;
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\tsave OK" << endl;
|
|
|
|
#endif
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CouldNotCreateTemporaryFileDialog (TQWidget *parent)
|
|
|
|
{
|
|
|
|
KMessageBox::error (parent,
|
|
|
|
i18n ("Could not save image - unable to create temporary file."));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CouldNotSaveDialog (const KURL &url, TQWidget *parent)
|
|
|
|
{
|
|
|
|
// TODO: use file.errorString()
|
|
|
|
KMessageBox::error (parent,
|
|
|
|
i18n ("Could not save as \"%1\".")
|
|
|
|
.arg (kpDocument::prettyFilenameForURL (url)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// public static
|
|
|
|
bool kpDocument::savePixmapToFile (const TQPixmap &pixmap,
|
|
|
|
const KURL &url,
|
|
|
|
const kpDocumentSaveOptions &saveOptions,
|
|
|
|
const kpDocumentMetaInfo &metaInfo,
|
|
|
|
bool overwritePrompt,
|
|
|
|
bool lossyPrompt,
|
|
|
|
TQWidget *parent)
|
|
|
|
{
|
|
|
|
// TODO: Use KIO::NetAccess:mostLocalURL() for accessing home:/ (and other
|
|
|
|
// such local URLs) for efficiency and because only local writes
|
|
|
|
// are atomic.
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::savePixmapToFile ("
|
|
|
|
<< url
|
|
|
|
<< ",overwritePrompt=" << overwritePrompt
|
|
|
|
<< ",lossyPrompt=" << lossyPrompt
|
|
|
|
<< ")" << endl;
|
|
|
|
saveOptions.printDebug (TQString::fromLatin1 ("\tsaveOptions"));
|
|
|
|
metaInfo.printDebug (TQString::fromLatin1 ("\tmetaInfo"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (overwritePrompt && KIO::NetAccess::exists (url, false/*write*/, parent))
|
|
|
|
{
|
|
|
|
int result = KMessageBox::warningContinueCancel (parent,
|
|
|
|
i18n ("A document called \"%1\" already exists.\n"
|
|
|
|
"Do you want to overwrite it?")
|
|
|
|
.arg (prettyFilenameForURL (url)),
|
|
|
|
TQString(),
|
|
|
|
i18n ("Overwrite"));
|
|
|
|
|
|
|
|
if (result != KMessageBox::Continue)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\tuser doesn't want to overwrite" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent))
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because of lossyPrompt" << endl;
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Local file?
|
|
|
|
if (url.isLocalFile ())
|
|
|
|
{
|
|
|
|
const TQString filename = url.path ();
|
|
|
|
|
|
|
|
// sync: All failure exit paths _must_ call KSaveFile::abort() or
|
|
|
|
// else, the KSaveFile destructor will overwrite the file,
|
|
|
|
// <filename>, despite the failure.
|
|
|
|
KSaveFile atomicFileWriter (filename);
|
|
|
|
{
|
|
|
|
if (atomicFileWriter.status () != 0)
|
|
|
|
{
|
|
|
|
// We probably don't need this as <filename> has not been
|
|
|
|
// opened.
|
|
|
|
atomicFileWriter.abort ();
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because could not open KSaveFile"
|
|
|
|
<< " status=" << atomicFileWriter.status () << endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotCreateTemporaryFileDialog (parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write to local temporary file.
|
|
|
|
if (!savePixmapToDevice (pixmap, TQT_TQIODEVICE(atomicFileWriter.file ()),
|
|
|
|
saveOptions, metaInfo,
|
|
|
|
false/*no lossy prompt*/,
|
|
|
|
parent))
|
|
|
|
{
|
|
|
|
atomicFileWriter.abort ();
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because could not save pixmap to device"
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotSaveDialog (url, parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Atomically overwrite local file with the temporary file
|
|
|
|
// we saved to.
|
|
|
|
if (!atomicFileWriter.close ())
|
|
|
|
{
|
|
|
|
atomicFileWriter.abort ();
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\tcould not close KSaveFile" << endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotSaveDialog (url, parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} // sync KSaveFile.abort()
|
|
|
|
}
|
|
|
|
// Remote file?
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Create temporary file that is deleted when the variable goes
|
|
|
|
// out of scope.
|
|
|
|
KTempFile tempFile;
|
|
|
|
tempFile.setAutoDelete (true);
|
|
|
|
|
|
|
|
TQString filename = tempFile.name ();
|
|
|
|
if (filename.isEmpty ())
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because tempFile empty" << endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotCreateTemporaryFileDialog (parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write to local temporary file.
|
|
|
|
TQFile file (filename);
|
|
|
|
{
|
|
|
|
if (!file.open (IO_WriteOnly))
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because can't open file"
|
|
|
|
<< " errorString=" << file.errorString () << endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotCreateTemporaryFileDialog (parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!savePixmapToDevice (pixmap, TQT_TQIODEVICE(&file),
|
|
|
|
saveOptions, metaInfo,
|
|
|
|
false/*no lossy prompt*/,
|
|
|
|
parent))
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because could not save pixmap to device"
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotSaveDialog (url, parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file.close ();
|
|
|
|
if (file.status () != IO_Ok)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because could not close" << endl;
|
|
|
|
#endif
|
|
|
|
::CouldNotSaveDialog (url, parent);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy local temporary file to overwrite remote.
|
|
|
|
// TODO: No one seems to know how to do this atomically
|
|
|
|
// [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2].
|
|
|
|
// At least, fish:// (ssh) is definitely not atomic.
|
|
|
|
if (!KIO::NetAccess::upload (filename, url, parent))
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "\treturning false because could not upload" << endl;
|
|
|
|
#endif
|
|
|
|
KMessageBox::error (parent,
|
|
|
|
i18n ("Could not save image - failed to upload."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool kpDocument::saveAs (const KURL &url,
|
|
|
|
const kpDocumentSaveOptions &saveOptions,
|
|
|
|
bool overwritePrompt,
|
|
|
|
bool lossyPrompt)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::saveAs (" << url << ","
|
|
|
|
<< saveOptions.mimeType () << ")" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (kpDocument::savePixmapToFile (pixmapWithSelection (),
|
|
|
|
url,
|
|
|
|
saveOptions, *metaInfo (),
|
|
|
|
overwritePrompt,
|
|
|
|
lossyPrompt,
|
|
|
|
m_mainWindow))
|
|
|
|
{
|
|
|
|
setURL (url, true/*is from url*/);
|
|
|
|
*m_saveOptions = saveOptions;
|
|
|
|
m_modified = false;
|
|
|
|
|
|
|
|
m_savedAtLeastOnceBefore = true;
|
|
|
|
|
|
|
|
emit documentSaved ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
bool kpDocument::savedAtLeastOnceBefore () const
|
|
|
|
{
|
|
|
|
return m_savedAtLeastOnceBefore;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
KURL kpDocument::url () const
|
|
|
|
{
|
|
|
|
return m_url;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setURL (const KURL &url, bool isFromURL)
|
|
|
|
{
|
|
|
|
m_url = url;
|
|
|
|
m_isFromURL = isFromURL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
bool kpDocument::isFromURL (bool checkURLStillExists) const
|
|
|
|
{
|
|
|
|
if (!m_isFromURL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!checkURLStillExists)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return (!m_url.isEmpty () &&
|
|
|
|
KIO::NetAccess::exists (m_url, true/*open*/, m_mainWindow));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
TQString kpDocument::prettyURLForURL (const KURL &url)
|
|
|
|
{
|
|
|
|
if (url.isEmpty ())
|
|
|
|
return i18n ("Untitled");
|
|
|
|
else
|
|
|
|
return url.prettyURL (0, KURL::StripFileProtocol);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString kpDocument::prettyURL () const
|
|
|
|
{
|
|
|
|
return prettyURLForURL (m_url);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
TQString kpDocument::prettyFilenameForURL (const KURL &url)
|
|
|
|
{
|
|
|
|
if (url.isEmpty ())
|
|
|
|
return i18n ("Untitled");
|
|
|
|
else if (url.fileName ().isEmpty ())
|
|
|
|
return prettyURLForURL (url); // better than the name ""
|
|
|
|
else
|
|
|
|
return url.fileName ();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString kpDocument::prettyFilename () const
|
|
|
|
{
|
|
|
|
return prettyFilenameForURL (m_url);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// public
|
|
|
|
const kpDocumentSaveOptions *kpDocument::saveOptions () const
|
|
|
|
{
|
|
|
|
return m_saveOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setSaveOptions (const kpDocumentSaveOptions &saveOptions)
|
|
|
|
{
|
|
|
|
*m_saveOptions = saveOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// public
|
|
|
|
const kpDocumentMetaInfo *kpDocument::metaInfo () const
|
|
|
|
{
|
|
|
|
return m_metaInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setMetaInfo (const kpDocumentMetaInfo &metaInfo)
|
|
|
|
{
|
|
|
|
*m_metaInfo = metaInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Properties
|
|
|
|
*/
|
|
|
|
|
|
|
|
void kpDocument::setModified (bool yes)
|
|
|
|
{
|
|
|
|
if (yes == m_modified)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_modified = yes;
|
|
|
|
|
|
|
|
if (yes)
|
|
|
|
emit documentModified ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool kpDocument::isModified () const
|
|
|
|
{
|
|
|
|
return m_modified;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool kpDocument::isEmpty () const
|
|
|
|
{
|
|
|
|
return url ().isEmpty () && !isModified ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int kpDocument::constructorWidth () const
|
|
|
|
{
|
|
|
|
return m_constructorWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
int kpDocument::width (bool ofSelection) const
|
|
|
|
{
|
|
|
|
if (ofSelection && m_selection)
|
|
|
|
return m_selection->width ();
|
|
|
|
else
|
|
|
|
return m_pixmap->width ();
|
|
|
|
}
|
|
|
|
|
|
|
|
int kpDocument::oldWidth () const
|
|
|
|
{
|
|
|
|
return m_oldWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::setWidth (int w, const kpColor &backgroundColor)
|
|
|
|
{
|
|
|
|
resize (w, height (), backgroundColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int kpDocument::constructorHeight () const
|
|
|
|
{
|
|
|
|
return m_constructorHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
int kpDocument::height (bool ofSelection) const
|
|
|
|
{
|
|
|
|
if (ofSelection && m_selection)
|
|
|
|
return m_selection->height ();
|
|
|
|
else
|
|
|
|
return m_pixmap->height ();
|
|
|
|
}
|
|
|
|
|
|
|
|
int kpDocument::oldHeight () const
|
|
|
|
{
|
|
|
|
return m_oldHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::setHeight (int h, const kpColor &backgroundColor)
|
|
|
|
{
|
|
|
|
resize (width (), h, backgroundColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQRect kpDocument::rect (bool ofSelection) const
|
|
|
|
{
|
|
|
|
if (ofSelection && m_selection)
|
|
|
|
return m_selection->boundingRect ();
|
|
|
|
else
|
|
|
|
return m_pixmap->rect ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pixmap access
|
|
|
|
*/
|
|
|
|
|
|
|
|
// public
|
|
|
|
TQPixmap kpDocument::getPixmapAt (const TQRect &rect) const
|
|
|
|
{
|
|
|
|
return kpPixmapFX::getPixmapAt (*m_pixmap, rect);
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setPixmapAt (const TQPixmap &pixmap, const TQPoint &at)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT && 0
|
|
|
|
kdDebug () << "kpDocument::setPixmapAt (pixmap (w="
|
|
|
|
<< pixmap.width ()
|
|
|
|
<< ",h=" << pixmap.height ()
|
|
|
|
<< "), x=" << at.x ()
|
|
|
|
<< ",y=" << at.y ()
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
kpPixmapFX::setPixmapAt (m_pixmap, at, pixmap);
|
|
|
|
slotContentsChanged (TQRect (at.x (), at.y (), pixmap.width (), pixmap.height ()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::paintPixmapAt (const TQPixmap &pixmap, const TQPoint &at)
|
|
|
|
{
|
|
|
|
kpPixmapFX::paintPixmapAt (m_pixmap, at, pixmap);
|
|
|
|
slotContentsChanged (TQRect (at.x (), at.y (), pixmap.width (), pixmap.height ()));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// public
|
|
|
|
TQPixmap *kpDocument::pixmap (bool ofSelection) const
|
|
|
|
{
|
|
|
|
if (ofSelection)
|
|
|
|
{
|
|
|
|
if (m_selection && m_selection->pixmap ())
|
|
|
|
return m_selection->pixmap ();
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return m_pixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setPixmap (const TQPixmap &pixmap)
|
|
|
|
{
|
|
|
|
m_oldWidth = width (), m_oldHeight = height ();
|
|
|
|
|
|
|
|
*m_pixmap = pixmap;
|
|
|
|
|
|
|
|
if (m_oldWidth == width () && m_oldHeight == height ())
|
|
|
|
slotContentsChanged (pixmap.rect ());
|
|
|
|
else
|
|
|
|
slotSizeChanged (width (), height ());
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setPixmap (bool ofSelection, const TQPixmap &pixmap)
|
|
|
|
{
|
|
|
|
if (ofSelection)
|
|
|
|
{
|
|
|
|
if (!m_selection)
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::setPixmap(ofSelection=true) without sel" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_selection->setPixmap (pixmap);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
setPixmap (pixmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// private
|
|
|
|
void kpDocument::updateToolsSingleKeyTriggersEnabled ()
|
|
|
|
{
|
|
|
|
if (m_mainWindow)
|
|
|
|
{
|
|
|
|
// Disable single key shortcuts when the user is editing text
|
|
|
|
m_mainWindow->enableActionsSingleKeyTriggers (!m_selection || !m_selection->isText ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// public
|
|
|
|
kpSelection *kpDocument::selection () const
|
|
|
|
{
|
|
|
|
return m_selection;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
void kpDocument::setSelection (const kpSelection &selection)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT && 0
|
|
|
|
kdDebug () << "kpDocument::setSelection() sel boundingRect="
|
|
|
|
<< selection.boundingRect ()
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0;
|
|
|
|
if (vm)
|
|
|
|
vm->setQueueUpdates ();
|
|
|
|
|
|
|
|
bool hadSelection = (bool) m_selection;
|
|
|
|
|
|
|
|
|
|
|
|
const bool isTextChanged = (m_mainWindow->toolIsTextTool () !=
|
|
|
|
(selection.type () == kpSelection::Text));
|
|
|
|
|
|
|
|
// We don't change the Selection Tool if the new selection's
|
|
|
|
// shape is merely different to the current tool's (e.g. rectangular
|
|
|
|
// vs elliptical) because:
|
|
|
|
//
|
|
|
|
// 1. All image selection tools support editing selections of all the
|
|
|
|
// different shapes anyway.
|
|
|
|
// 2. Suppose the user is trying out different drags of selection borders
|
|
|
|
// and then decides to paste a differently shaped selection before continuing
|
|
|
|
// to try out different borders. If the pasting were to switch to
|
|
|
|
// a differently shaped tool, the borders drawn after the paste would
|
|
|
|
// be using a new shape rather than the shape before the paste. This
|
|
|
|
// could get irritating so we don't do the switch.
|
|
|
|
//
|
|
|
|
if (m_mainWindow &&
|
|
|
|
(!m_mainWindow->toolIsASelectionTool () || isTextChanged))
|
|
|
|
{
|
|
|
|
// Switch to the appropriately shaped selection tool
|
|
|
|
// _before_ we change the selection
|
|
|
|
// (all selection tool's ::end() functions nuke the current selection)
|
|
|
|
switch (selection.type ())
|
|
|
|
{
|
|
|
|
case kpSelection::Rectangle:
|
|
|
|
m_mainWindow->slotToolRectSelection ();
|
|
|
|
break;
|
|
|
|
case kpSelection::Ellipse:
|
|
|
|
m_mainWindow->slotToolEllipticalSelection ();
|
|
|
|
break;
|
|
|
|
case kpSelection::Points:
|
|
|
|
m_mainWindow->slotToolFreeFormSelection ();
|
|
|
|
break;
|
|
|
|
case kpSelection::Text:
|
|
|
|
m_mainWindow->slotToolText ();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (m_selection)
|
|
|
|
{
|
|
|
|
// TODO: Emitting this, before setting the new selection, is bogus
|
|
|
|
// since it would redraw the old selection.
|
|
|
|
//
|
|
|
|
// Luckily, this doesn't matter thanks to the
|
|
|
|
// kpViewManager::setQueueUpdates() call above.
|
|
|
|
if (m_selection->pixmap ())
|
|
|
|
slotContentsChanged (m_selection->boundingRect ());
|
|
|
|
else
|
|
|
|
// TODO: Should emit contentsChanged() instead?
|
|
|
|
// I don't think it matters since contentsChanged() is
|
|
|
|
// connected to updateViews() anyway (see
|
|
|
|
// kpMainWindow::setDocument ()).
|
|
|
|
vm->updateViews (m_selection->boundingRect ());
|
|
|
|
|
|
|
|
delete m_selection;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_selection = new kpSelection (selection);
|
|
|
|
|
|
|
|
// TODO: this coupling is bad, careless and lazy
|
|
|
|
if (m_mainWindow)
|
|
|
|
{
|
|
|
|
if (!m_selection->isText ())
|
|
|
|
{
|
|
|
|
if (m_selection->transparency () != m_mainWindow->selectionTransparency ())
|
|
|
|
{
|
|
|
|
kdDebug () << "kpDocument::setSelection() sel's transparency differs "
|
|
|
|
"from mainWindow's transparency - setting mainWindow's transparency "
|
|
|
|
"to sel"
|
|
|
|
<< endl;
|
|
|
|
kdDebug () << "\tisOpaque: sel=" << m_selection->transparency ().isOpaque ()
|
|
|
|
<< " mainWindow=" << m_mainWindow->selectionTransparency ().isOpaque ()
|
|
|
|
<< endl;
|
|
|
|
m_mainWindow->setSelectionTransparency (m_selection->transparency ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (m_selection->textStyle () != m_mainWindow->textStyle ())
|
|
|
|
{
|
|
|
|
kdDebug () << "kpDocument::setSelection() sel's textStyle differs "
|
|
|
|
"from mainWindow's textStyle - setting mainWindow's textStyle "
|
|
|
|
"to sel"
|
|
|
|
<< endl;
|
|
|
|
m_mainWindow->setTextStyle (m_selection->textStyle ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateToolsSingleKeyTriggersEnabled ();
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT && 0
|
|
|
|
kdDebug () << "\tcheck sel " << (int *) m_selection
|
|
|
|
<< " boundingRect=" << m_selection->boundingRect ()
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
if (m_selection->pixmap ())
|
|
|
|
slotContentsChanged (m_selection->boundingRect ());
|
|
|
|
else
|
|
|
|
// TODO: Should emit contentsChanged() instead?
|
|
|
|
// I don't think it matters since contentsChanged() is
|
|
|
|
// connected to updateViews() anyway (see
|
|
|
|
// kpMainWindow::setDocument ()).
|
|
|
|
vm->updateViews (m_selection->boundingRect ());
|
|
|
|
|
|
|
|
// There's no need to disconnect() the old selection since we:
|
|
|
|
//
|
|
|
|
// 1. Connect our _copy_ of the given selection.
|
|
|
|
// 2. We delete our copy when setSelection() is called again.
|
|
|
|
//
|
|
|
|
// See code above for both.
|
|
|
|
connect (m_selection, TQT_SIGNAL (changed (const TQRect &)),
|
|
|
|
this, TQT_SLOT (slotContentsChanged (const TQRect &)));
|
|
|
|
|
|
|
|
|
|
|
|
if (!hadSelection)
|
|
|
|
emit selectionEnabled (true);
|
|
|
|
|
|
|
|
if (isTextChanged)
|
|
|
|
emit selectionIsTextChanged (selection.type () == kpSelection::Text);
|
|
|
|
|
|
|
|
if (vm)
|
|
|
|
vm->restoreQueueUpdates ();
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
TQPixmap kpDocument::getSelectedPixmap (const TQBitmap &maskBitmap_) const
|
|
|
|
{
|
|
|
|
kpSelection *sel = selection ();
|
|
|
|
|
|
|
|
// must have a selection region
|
|
|
|
if (!sel)
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::getSelectedPixmap() no sel region" << endl;
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
|
|
|
|
// easy if we already have it :)
|
|
|
|
if (sel->pixmap ())
|
|
|
|
return *sel->pixmap ();
|
|
|
|
|
|
|
|
|
|
|
|
const TQRect boundingRect = sel->boundingRect ();
|
|
|
|
if (!boundingRect.isValid ())
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::getSelectedPixmap() boundingRect invalid" << endl;
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQBitmap maskBitmap = maskBitmap_;
|
|
|
|
if (maskBitmap.isNull () &&
|
|
|
|
!sel->isRectangular ())
|
|
|
|
{
|
|
|
|
maskBitmap = sel->maskForOwnType ();
|
|
|
|
|
|
|
|
if (maskBitmap.isNull ())
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::getSelectedPixmap() could not get mask" << endl;
|
|
|
|
return TQPixmap ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQPixmap selPixmap = getPixmapAt (boundingRect);
|
|
|
|
|
|
|
|
if (!maskBitmap.isNull ())
|
|
|
|
{
|
|
|
|
// Src Dest = Result
|
|
|
|
// -----------------
|
|
|
|
// 0 0 0
|
|
|
|
// 0 1 0
|
|
|
|
// 1 0 0
|
|
|
|
// 1 1 1
|
|
|
|
TQBitmap selMaskBitmap = kpPixmapFX::getNonNullMask (selPixmap);
|
|
|
|
bitBlt (&selMaskBitmap,
|
|
|
|
TQPoint (0, 0),
|
|
|
|
&maskBitmap,
|
|
|
|
TQRect (0, 0, maskBitmap.width (), maskBitmap.height ()),
|
|
|
|
TQt::AndROP);
|
|
|
|
selPixmap.setMask (selMaskBitmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
return selPixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
bool kpDocument::selectionPullFromDocument (const kpColor &backgroundColor)
|
|
|
|
{
|
|
|
|
kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0;
|
|
|
|
|
|
|
|
kpSelection *sel = selection ();
|
|
|
|
|
|
|
|
// must have a selection region
|
|
|
|
if (!sel)
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::selectionPullFromDocument() no sel region" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// should not already have a pixmap
|
|
|
|
if (sel->pixmap ())
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::selectionPullFromDocument() already has pixmap" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const TQRect boundingRect = sel->boundingRect ();
|
|
|
|
if (!boundingRect.isValid ())
|
|
|
|
{
|
|
|
|
kdError () << "kpDocument::selectionPullFromDocument() boundingRect invalid" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Figure out mask for non-rectangular selections
|
|
|
|
//
|
|
|
|
|
|
|
|
TQBitmap maskBitmap = sel->maskForOwnType (true/*return null bitmap for rectangular*/);
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Get selection pixmap from document
|
|
|
|
//
|
|
|
|
|
|
|
|
TQPixmap selPixmap = getSelectedPixmap (maskBitmap);
|
|
|
|
|
|
|
|
if (vm)
|
|
|
|
vm->setQueueUpdates ();
|
|
|
|
|
|
|
|
sel->setPixmap (selPixmap);
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Fill opaque bits of the hole in the document
|
|
|
|
//
|
|
|
|
|
|
|
|
// TODO: this assumes backgroundColor == sel->transparency ().transparentColor()
|
|
|
|
const TQPixmap selTransparentPixmap = sel->transparentPixmap ();
|
|
|
|
|
|
|
|
if (backgroundColor.isOpaque ())
|
|
|
|
{
|
|
|
|
TQPixmap erasePixmap (boundingRect.width (), boundingRect.height ());
|
|
|
|
erasePixmap.fill (backgroundColor.toTQColor ());
|
|
|
|
|
|
|
|
if (selTransparentPixmap.mask ())
|
|
|
|
erasePixmap.setMask (*selTransparentPixmap.mask ());
|
|
|
|
|
|
|
|
paintPixmapAt (erasePixmap, boundingRect.topLeft ());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kpPixmapFX::paintMaskTransparentWithBrush (m_pixmap,
|
|
|
|
boundingRect.topLeft (),
|
|
|
|
kpPixmapFX::getNonNullMask (selTransparentPixmap));
|
|
|
|
slotContentsChanged (boundingRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vm)
|
|
|
|
vm->restoreQueueUpdates ();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
bool kpDocument::selectionDelete ()
|
|
|
|
{
|
|
|
|
kpSelection *sel = selection ();
|
|
|
|
|
|
|
|
if (!sel)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const TQRect boundingRect = sel->boundingRect ();
|
|
|
|
if (!boundingRect.isValid ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool selectionHadPixmap = m_selection ? (bool) m_selection->pixmap () : false;
|
|
|
|
|
|
|
|
delete m_selection;
|
|
|
|
m_selection = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// HACK to prevent document from being modified when
|
|
|
|
// user cancels dragging out a new selection
|
|
|
|
if (selectionHadPixmap)
|
|
|
|
slotContentsChanged (boundingRect);
|
|
|
|
else
|
|
|
|
emit contentsChanged (boundingRect);
|
|
|
|
|
|
|
|
emit selectionEnabled (false);
|
|
|
|
|
|
|
|
|
|
|
|
updateToolsSingleKeyTriggersEnabled ();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
bool kpDocument::selectionCopyOntoDocument (bool useTransparentPixmap)
|
|
|
|
{
|
|
|
|
kpSelection *sel = selection ();
|
|
|
|
|
|
|
|
// must have a pixmap already
|
|
|
|
if (!sel)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// hasn't actually been lifted yet
|
|
|
|
if (!sel->pixmap ())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
const TQRect boundingRect = sel->boundingRect ();
|
|
|
|
if (!boundingRect.isValid ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!sel->isText ())
|
|
|
|
{
|
|
|
|
// We can't use kpSelection::paint() since that always uses the
|
|
|
|
// transparent pixmap.
|
|
|
|
paintPixmapAt (useTransparentPixmap ? sel->transparentPixmap () : sel->opaquePixmap (),
|
|
|
|
boundingRect.topLeft ());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// (for antialiasing with background)
|
|
|
|
sel->paint (m_pixmap, rect ());
|
|
|
|
}
|
|
|
|
|
|
|
|
slotContentsChanged (boundingRect);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
bool kpDocument::selectionPushOntoDocument (bool useTransparentPixmap)
|
|
|
|
{
|
|
|
|
return (selectionCopyOntoDocument (useTransparentPixmap) && selectionDelete ());
|
|
|
|
}
|
|
|
|
|
|
|
|
// public
|
|
|
|
TQPixmap kpDocument::pixmapWithSelection () const
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT && 1
|
|
|
|
kdDebug () << "kpDocument::pixmapWithSelection()" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Have floating selection?
|
|
|
|
if (m_selection && m_selection->pixmap ())
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT && 1
|
|
|
|
kdDebug () << "\tselection @ " << m_selection->boundingRect () << endl;
|
|
|
|
#endif
|
|
|
|
TQPixmap output = *m_pixmap;
|
|
|
|
|
|
|
|
m_selection->paint (&output, rect ());
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT && 1
|
|
|
|
kdDebug () << "\tno selection" << endl;
|
|
|
|
#endif
|
|
|
|
return *m_pixmap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Transformations
|
|
|
|
*/
|
|
|
|
|
|
|
|
void kpDocument::fill (const kpColor &color)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::fill ()" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
kpPixmapFX::fill (m_pixmap, color);
|
|
|
|
slotContentsChanged (m_pixmap->rect ());
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::resize (int w, int h, const kpColor &backgroundColor, bool fillNewAreas)
|
|
|
|
{
|
|
|
|
#if DEBUG_KP_DOCUMENT
|
|
|
|
kdDebug () << "kpDocument::resize (" << w << "," << h << "," << fillNewAreas << ")" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
m_oldWidth = width (), m_oldHeight = height ();
|
|
|
|
|
|
|
|
#if DEBUG_KP_DOCUMENT && 1
|
|
|
|
kdDebug () << "\toldWidth=" << m_oldWidth
|
|
|
|
<< " oldHeight=" << m_oldHeight
|
|
|
|
<< endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (w == m_oldWidth && h == m_oldHeight)
|
|
|
|
return;
|
|
|
|
|
|
|
|
kpPixmapFX::resize (m_pixmap, w, h, backgroundColor, fillNewAreas);
|
|
|
|
|
|
|
|
slotSizeChanged (width (), height ());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Slots
|
|
|
|
*/
|
|
|
|
|
|
|
|
void kpDocument::slotContentsChanged (const TQRect &rect)
|
|
|
|
{
|
|
|
|
setModified ();
|
|
|
|
emit contentsChanged (rect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::slotSizeChanged (int newWidth, int newHeight)
|
|
|
|
{
|
|
|
|
setModified ();
|
|
|
|
emit sizeChanged (newWidth, newHeight);
|
|
|
|
emit sizeChanged (TQSize (newWidth, newHeight));
|
|
|
|
}
|
|
|
|
|
|
|
|
void kpDocument::slotSizeChanged (const TQSize &newSize)
|
|
|
|
{
|
|
|
|
slotSizeChanged (newSize.width (), newSize.height ());
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <kpdocument.moc>
|