/* Copyright (c) 2003,2004,2005 Clarence Dang 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 keyList = image.textList (); for (TQValueList ::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 ("") : 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 TQUIT_IF_CANCEL(messageBoxCommand) \ { \ if (messageBoxCommand != KMessageBox::Continue) \ { \ return false; \ } \ } const int lossyType = saveOptions.isLossyForSaving (pixmap); if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow | kpDocumentSaveOptions::Quality)) { TQUIT_IF_CANCEL ( KMessageBox::warningContinueCancel (parent, i18n ("

The %1 format may not be able" " to preserve all of the image's color information.

" "

Are you sure you want to save in this format?

") .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) { TQUIT_IF_CANCEL ( KMessageBox::warningContinueCancel (parent, i18n ("

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.

" "

Are you sure you want to save at this color depth?

") .arg (saveOptions.colorDepth ()), i18n ("Low Color Depth"), KStdGuiItem::save (), TQString::fromLatin1 ("SaveAtLowColorDepthDontAskAgain"))); } #undef TQUIT_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 keyList = metaInfo.textList (); for (TQValueList ::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, // , despite the failure. KSaveFile atomicFileWriter (filename); { if (atomicFileWriter.status () != 0) { // We probably don't need this as 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