diff --git a/kpdocument.cpp b/kpdocument.cpp index f9775df4..78a12045 100644 --- a/kpdocument.cpp +++ b/kpdocument.cpp @@ -1,1039 +1,1088 @@ /* Copyright (c) 2003-2004 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 +struct kpDocumentPrivate +{ + int m_constructorWidth, m_constructorHeight; +}; + + kpDocument::kpDocument (int w, int h, int colorDepth, kpMainWindow *mainWindow) : m_selection (0), m_oldWidth (-1), m_oldHeight (-1), m_colorDepth (colorDepth), m_oldColorDepth (-1), m_mainWindow (mainWindow), - m_modified (false) + m_isFromURL (false), + m_modified (false), + d (new kpDocumentPrivate ()) { #if DEBUG_KP_DOCUMENT && 0 kdDebug () << "kpDocument::kpDocument (" << w << "," << h << "," << colorDepth << ")" << endl; #endif + d->m_constructorWidth = w; + d->m_constructorHeight = h; + m_pixmap = new QPixmap (w, h); m_pixmap->fill (Qt::white); } kpDocument::~kpDocument () { + delete d; + delete m_pixmap; delete m_selection; } kpMainWindow *kpDocument::mainWindow () const { return m_mainWindow; } void kpDocument::setMainWindow (kpMainWindow *mainWindow) { m_mainWindow = mainWindow; } /* * File I/O */ // public static QPixmap kpDocument::getPixmapFromFile (const KURL &url, bool suppressDoesntExistDialog, QWidget *parent, - QString &mimeType) + QString *mimeType) { #if DEBUG_KP_DOCUMENT kdDebug () << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")" << endl; #endif - mimeType = QString::null; + if (mimeType) + *mimeType = QString::null; QString 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 QPixmap (); } // sync: remember to "KIO::NetAccess::removeTempFile (tempFile)" in all exit paths - mimeType = KImageIO::mimeType (tempFile); + QString detectedMimeType = KImageIO::mimeType (tempFile); + if (mimeType) + *mimeType = detectedMimeType; #if DEBUG_KP_DOCUMENT kdDebug () << "\ttempFile=" << tempFile << endl; kdDebug () << "\tmimetype=" << mimetype << endl; kdDebug () << "\tsrc=" << url.path () << endl; kdDebug () << "\tmimetype of src=" << KImageIO::mimeType (url.path ()) << endl; #endif - if (mimeType.isEmpty ()) + if (detectedMimeType.isEmpty ()) { KMessageBox::sorry (parent, i18n ("Could not open \"%1\" - unknown mimetype.") .arg (kpDocument::prettyFilenameForURL (url))); KIO::NetAccess::removeTempFile (tempFile); return QPixmap (); } QImage image (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))); KIO::NetAccess::removeTempFile (tempFile); return QPixmap (); } #if DEBUG_KP_DOCUMENT kdDebug () << "\timage: depth=" << image.depth () << " (X display=" << QColor::numBitPlanes () << ")" << " hasAlphaBuffer=" << image.hasAlphaBuffer () << endl; #endif #if DEBUG_KP_DOCUMENT && 0 kdDebug () << "Image dump:" << endl; for (int y = 0; y < image.height (); y++) { for (int x = 0; x < image.width (); x++) { const QRgb rgb = image.pixel (x, y); //fprintf (stderr, " %08X", rgb); } //fprintf (stderr, "\n"); } #endif QPixmap newPixmap; newPixmap = kpPixmapFX::convertToPixmap (image, false/*no dither*/, kpPixmapFX::WarnAboutLossInfo (parent, i18n ("image \"%1\"").arg (prettyFilenameForURL (url)), "docOpen")); if (newPixmap.isNull ()) { KMessageBox::sorry (parent, i18n ("Could not open \"%1\" - out of graphics memory.") .arg (kpDocument::prettyFilenameForURL (url))); KIO::NetAccess::removeTempFile (tempFile); return QPixmap (); } #if DEBUG_KP_DOCUMENT kdDebug () << "\tpixmap: depth=" << newPixmap.depth () << " hasAlphaChannelOrMask=" << newPixmap.hasAlpha () << " hasAlphaChannel=" << newPixmap.hasAlphaChannel () << endl; #endif KIO::NetAccess::removeTempFile (tempFile); return newPixmap; } void kpDocument::openNew (const KURL &url) { #if DEBUG_KP_DOCUMENT kdDebug () << "KpDocument::openNew (" << url << ")" << endl; #endif m_pixmap->fill (Qt::white); - m_url = url; + setURL (url, false/*not from url*/); m_mimetype = QString::null; m_modified = false; emit documentOpened (); } bool kpDocument::open (const KURL &url, bool newDocSameNameIfNotExist) { #if DEBUG_KP_DOCUMENT kdDebug () << "kpDocument::open (" << url << ")" << endl; #endif QString newMimeType; QPixmap newPixmap = kpDocument::getPixmapFromFile (url, newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/, m_mainWindow, - newMimeType/*ref*/); + &newMimeType); if (!newPixmap.isNull ()) { delete m_pixmap; m_pixmap = new QPixmap (newPixmap); - m_url = url; + setURL (url, true/*is from url*/); m_mimetype = newMimeType; 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 () { #if DEBUG_KP_DOCUMENT kdDebug () << "kpDocument::save [" << m_url << "," << m_mimetype << "]" << endl; #endif if (m_url.isEmpty () || m_mimetype.isEmpty ()) { KMessageBox::detailedError (m_mainWindow, i18n ("Could not save image - insufficient information."), i18n ("URL: %1\n" "Mimetype: %2") .arg (prettyURL ()) .arg (m_mimetype.isEmpty () ? i18n ("") : m_mimetype), i18n ("Internal Error")); return false; } return saveAs (m_url, m_mimetype, false); } static QPixmap pixmapWithDefinedTransparentPixels (const QPixmap &pixmap, const QColor &transparentColor) { if (!pixmap.mask ()) return pixmap; QPixmap retPixmap (pixmap.width (), pixmap.height ()); retPixmap.fill (transparentColor); QPainter p (&retPixmap); p.drawPixmap (QPoint (0, 0), pixmap); p.end (); retPixmap.setMask (*pixmap.mask ()); return retPixmap; } // public static bool kpDocument::savePixmapToFile (const QPixmap &pixmap, const KURL &url, const QString &mimeType, bool overwritePrompt, QWidget *parent) { #if DEBUG_KP_DOCUMENT kdDebug () << "kpDocument::savePixmapToFile (" << url << "," << mimetype << ",overwritePrompt=" << overWritePrompt << ")" << endl; #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)), QString::null, i18n ("Overwrite")); if (result != KMessageBox::Continue) { #if DEBUG_KP_DOCUMENT kdDebug () << "\tuser doesn't want to overwrite" << endl; #endif return false; } } KTempFile tempFile; tempFile.setAutoDelete (true); QString filename; if (!url.isLocalFile ()) { filename = tempFile.name (); if (filename.isEmpty ()) { KMessageBox::error (parent, i18n ("Could not save image - unable to create temporary file.")); return false; } } else filename = url.path (); QString type = KImageIO::typeForMime (mimeType); #if DEBUG_KP_DOCUMENT kdDebug () << "\tmimeType=" << mimeType << " type=" << type << endl; #endif QPixmap pixmapToSave = pixmapWithDefinedTransparentPixels (pixmap, Qt::white); // CONFIG if (!pixmapToSave.save (filename, type.latin1 ())) { KMessageBox::error (parent, i18n ("Could not save as \"%1\".") .arg (kpDocument::prettyFilenameForURL (url))); return false; } if (!url.isLocalFile ()) { if (!KIO::NetAccess::upload (filename, url, parent)) { KMessageBox::error (parent, i18n ("Could not save image - failed to upload.")); return false; } } return true; } bool kpDocument::saveAs (const KURL &url, const QString &mimetype, bool overwritePrompt) { #if DEBUG_KP_DOCUMENT kdDebug () << "kpDocument::saveAs (" << url << "," << mimetype << ")" << endl; #endif if (kpDocument::savePixmapToFile (pixmapWithSelection (), url, mimetype, overwritePrompt, m_mainWindow)) { - m_url = url; + setURL (url, true/*is from url*/); m_mimetype = mimetype; m_modified = false; emit documentSaved (); return true; } else { return false; } } +// 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 QString kpDocument::prettyURLForURL (const KURL &url) { if (url.isEmpty ()) return i18n ("Untitled"); else return url.prettyURL (0, KURL::StripFileProtocol); } QString kpDocument::prettyURL () const { return prettyURLForURL (m_url); } // static QString 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 (); } QString kpDocument::prettyFilename () const { return prettyFilenameForURL (m_url); } QString kpDocument::mimetype () const { return m_mimetype; } /* * 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 d->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 d->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); } QRect kpDocument::rect (bool ofSelection) const { if (ofSelection && m_selection) return m_selection->boundingRect (); else return m_pixmap->rect (); } int kpDocument::colorDepth () const { return m_pixmap->depth (); } int kpDocument::oldColorDepth () const { return m_colorDepth; } bool kpDocument::setColorDepth (int) { m_oldColorDepth = colorDepth (); // SYNC: Limitation of X/QPixmap - changing colour depth yet to be implemented // // Not really a major problem though - how could you possibly edit an // image at a higher depth than what your screen is set at // (accurately)? emit colorDepthChanged (colorDepth ()); return true; } /* * Pixmap access */ // public QPixmap kpDocument::getPixmapAt (const QRect &rect) const { return kpPixmapFX::getPixmapAt (*m_pixmap, rect); } // public void kpDocument::setPixmapAt (const QPixmap &pixmap, const QPoint &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 (QRect (at.x (), at.y (), pixmap.width (), pixmap.height ())); } // public void kpDocument::paintPixmapAt (const QPixmap &pixmap, const QPoint &at) { kpPixmapFX::paintPixmapAt (m_pixmap, at, pixmap); slotContentsChanged (QRect (at.x (), at.y (), pixmap.width (), pixmap.height ())); } // public QPixmap *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 QPixmap &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 QPixmap &pixmap) { if (ofSelection) { if (!m_selection) { kdError () << "kpDocument::setPixmap(ofSelection=true) without sel" << endl; return; } m_selection->setPixmap (pixmap); } else setPixmap (pixmap); } // public kpSelection *kpDocument::selection () const { return m_selection; } // public void kpDocument::setSelection (const kpSelection &selection) { #if DEBUG_KP_DOCUMENT 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 different to the tool's because all the Selection // Tools act the same, except for what would be really irritating // if it kept changing whenever you paste an image - drawing the // selection region) 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) { if (m_selection->pixmap ()) slotContentsChanged (m_selection->boundingRect ()); else 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 ()); } } } #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 vm->updateViews (m_selection->boundingRect ()); connect (m_selection, SIGNAL (changed (const QRect &)), this, SLOT (slotContentsChanged (const QRect &))); if (!hadSelection) emit selectionEnabled (true); if (isTextChanged) emit selectionIsTextChanged (selection.type () == kpSelection::Text); if (vm) vm->restoreQueueUpdates (); } // public QPixmap kpDocument::getSelectedPixmap (const QBitmap &maskBitmap_) const { kpSelection *sel = selection (); // must have a selection region if (!sel) { kdError () << "kpDocument::getSelectedPixmap() no sel region" << endl; return QPixmap (); } // easy if we already have it :) if (sel->pixmap ()) return *sel->pixmap (); const QRect boundingRect = sel->boundingRect (); if (!boundingRect.isValid ()) { kdError () << "kpDocument::getSelectedPixmap() boundingRect invalid" << endl; return QPixmap (); } QBitmap maskBitmap = maskBitmap_; if (maskBitmap.isNull () && !sel->isRectangular ()) { maskBitmap = sel->maskForOwnType (); if (maskBitmap.isNull ()) { kdError () << "kpDocument::getSelectedPixmap() could not get mask" << endl; return QPixmap (); } } QPixmap selPixmap = getPixmapAt (boundingRect); if (!maskBitmap.isNull ()) { // Src Dest = Result // ----------------- // 0 0 0 // 0 1 0 // 1 0 0 // 1 1 1 QBitmap selMaskBitmap = kpPixmapFX::getNonNullMask (selPixmap); bitBlt (&selMaskBitmap, QPoint (0, 0), &maskBitmap, QRect (0, 0, maskBitmap.width (), maskBitmap.height ()), Qt::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 QRect boundingRect = sel->boundingRect (); if (!boundingRect.isValid ()) { kdError () << "kpDocument::selectionPullFromDocument() boundingRect invalid" << endl; return false; } // // Figure out mask for non-rectangular selections // QBitmap maskBitmap = sel->maskForOwnType (true/*return null bitmap for rectangular*/); // // Get selection pixmap from document // QPixmap 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 QPixmap selTransparentPixmap = sel->transparentPixmap (); if (backgroundColor.isOpaque ()) { QPixmap erasePixmap (boundingRect.width (), boundingRect.height ()); erasePixmap.fill (backgroundColor.toQColor ()); 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 QRect 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); 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 QRect boundingRect = sel->boundingRect (); if (!boundingRect.isValid ()) return false; paintPixmapAt (useTransparentPixmap ? sel->transparentPixmap () : sel->opaquePixmap (), boundingRect.topLeft ()); return true; } // public bool kpDocument::selectionPushOntoDocument (bool useTransparentPixmap) { return (selectionCopyOntoDocument (useTransparentPixmap) && selectionDelete ()); } // public QPixmap kpDocument::pixmapWithSelection () const { #if DEBUG_KP_DOCUMENT && 1 kdDebug () << "kpDocument::pixmapWithSelection()" << endl; #endif if (m_selection && m_selection->pixmap ()) { #if DEBUG_KP_DOCUMENT && 1 kdDebug () << "\tselection @ " << m_selection->boundingRect () << endl; #endif QPixmap output = *m_pixmap; kpPixmapFX::paintPixmapAt (&output, m_selection->topLeft (), *m_selection->pixmap ()); 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 QRect &rect) { setModified (); emit contentsChanged (rect); } void kpDocument::slotSizeChanged (int newWidth, int newHeight) { setModified (); emit sizeChanged (newWidth, newHeight); emit sizeChanged (QSize (newWidth, newHeight)); } void kpDocument::slotSizeChanged (const QSize &newSize) { slotSizeChanged (newSize.width (), newSize.height ()); } #include diff --git a/kpdocument.h b/kpdocument.h index 74594ba8..388293d6 100644 --- a/kpdocument.h +++ b/kpdocument.h @@ -1,190 +1,214 @@ /* Copyright (c) 2003-2004 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. */ -#ifndef __kpdocument_h__ -#define __kpdocument_h__ +#ifndef KP_DOCUMENT_H +#define KP_DOCUMENT_H #include #include #include #include class QPixmap; class QPoint; class QRect; class QSize; class kpColor; class kpMainWindow; class kpSelection; class kpDocument : public QObject { Q_OBJECT public: kpDocument (int w, int h, int colorDepth, kpMainWindow *mainWindow); ~kpDocument (); kpMainWindow *mainWindow () const; void setMainWindow (kpMainWindow *mainWindow); /* * File I/O */ static QPixmap getPixmapFromFile (const KURL &url, bool suppressDoesntExistDialog, QWidget *parent, - QString &mimeType); + QString *mimeType = 0); + // TODO: fix: open*() should only be called once. + // Create a new kpDocument() if you want to open again. void openNew (const KURL &url); bool open (const KURL &url, bool newDocSameNameIfNotExist = false); static bool savePixmapToFile (const QPixmap &pixmap, const KURL &url, const QString &mimeType, bool overwritePrompt, QWidget *parent); bool save (); bool saveAs (const KURL &url, const QString &mimetype, bool overwritePrompt = true); KURL url () const; + void setURL (const KURL &url, bool isFromURL); + + // Returns whether the document's pixmap was successfully opened from + // or saved to the URL returned by url(). This is not true for a + // new kpDocument and in the case of open() being passed + // "newDocSameNameIfNotExist = true" when the URL doesn't exist. + // + // If this returns true and the kpDocument hasn't been modified, + // this gives a pretty good indication that the pixmap stored at url() + // is equal to pixmap() (unless the something has happened to that url + // outside of KolourPaint). + bool isFromURL (bool checkURLStillExists = true) const; // (will convert: empty URL --> "Untitled") static QString prettyURLForURL (const KURL &url); QString prettyURL () const; // (will convert: empty URL --> "Untitled") static QString prettyFilenameForURL (const KURL &url); QString prettyFilename () const; QString mimetype () const; + int quality () const; /* * Properties (modified, width, height, color depth...) */ void setModified (bool yes = true); bool isModified () const; bool isEmpty () const; + int constructorWidth () const; // as passed to the constructor int width (bool ofSelection = false) const; int oldWidth () const; // only valid in a slot connected to sizeChanged() void setWidth (int w, const kpColor &backgroundColor); + int constructorHeight () const; // as passed to the constructor int height (bool ofSelection = false) const; int oldHeight () const; // only valid in a slot connected to sizeChanged() void setHeight (int h, const kpColor &backgroundColor); QRect rect (bool ofSelection = false) const; int colorDepth () const; int oldColorDepth () const; // only valid in a slot connected to colorDepthChanged() bool setColorDepth (int depth); /* * Pixmap access */ // get a copy of a bit of the doc's pixmap // (not including the selection) QPixmap getPixmapAt (const QRect &rect) const; void setPixmapAt (const QPixmap &pixmap, const QPoint &at); void paintPixmapAt (const QPixmap &pixmap, const QPoint &at); // (not including the selection) QPixmap *pixmap (bool ofSelection = false) const; void setPixmap (const QPixmap &pixmap); void setPixmap (bool ofSelection, const QPixmap &pixmap); kpSelection *selection () const; void setSelection (const kpSelection &selection); QPixmap getSelectedPixmap (const QBitmap &maskBitmap = QBitmap ()) const; bool selectionPullFromDocument (const kpColor &backgroundColor); bool selectionDelete (); bool selectionCopyOntoDocument (bool useTransparentPixmap = true); bool selectionPushOntoDocument (bool useTransparentPixmap = true); // same as pixmap() but returns a _copy_ of the current pixmap // + any selection pasted on top QPixmap pixmapWithSelection () const; /* * Transformations * (convenience only - you could achieve the same effect (and more) with * kpPixmapFX: these functions do not affect the selection) */ void fill (const kpColor &color); void resize (int w, int h, const kpColor &backgroundColor, bool fillNewAreas = true); public slots: // these will emit signals! void slotContentsChanged (const QRect &rect); void slotSizeChanged (int newWidth, int newHeight); void slotSizeChanged (const QSize &newSize); signals: void documentOpened (); void documentSaved (); // Emitted whenever the isModified() flag changes from false to true. // This is the _only_ signal that may be emitted in addition to the others. void documentModified (); void contentsChanged (const QRect &rect); void sizeChanged (int newWidth, int newHeight); // see oldWidth(), oldHeight() void sizeChanged (const QSize &newSize); void colorDepthChanged (int newDepth); // see oldColorDepth() void selectionEnabled (bool on); // HACK: until we support Text Selection -> Rectangular Selection for Image ops void selectionIsTextChanged (bool isText); private: QPixmap *m_pixmap; kpSelection *m_selection; int m_oldWidth, m_oldHeight; int m_colorDepth, m_oldColorDepth; kpMainWindow *m_mainWindow; KURL m_url; + bool m_isFromURL; QString m_mimetype; + int m_quality; bool m_modified; + + // There is no need to maintain binary compatibility at this stage. + // The d-pointer is just so that you can experiment without recompiling + // the kitchen sink. + class kpDocumentPrivate *d; }; -#endif // __kpdocument_h__ +#endif // KP_DOCUMENT_H diff --git a/kpmainwindow_edit.cpp b/kpmainwindow_edit.cpp index 7b692bba..220c37f9 100644 --- a/kpmainwindow_edit.cpp +++ b/kpmainwindow_edit.cpp @@ -1,718 +1,716 @@ /* Copyright (c) 2003-2004 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // private kpPixmapFX::WarnAboutLossInfo kpMainWindow::pasteWarnAboutLossInfo () { return kpPixmapFX::WarnAboutLossInfo (this, i18n ("image to be pasted"), "paste"); } // private void kpMainWindow::setupEditMenuActions () { KActionCollection *ac = actionCollection (); // Undo/Redo m_commandHistory = new kpCommandHistory (this); m_commandHistory->setUndoLimit (10); // CONFIG m_actionCut = KStdAction::cut (this, SLOT (slotCut ()), ac); m_actionCopy = KStdAction::copy (this, SLOT (slotCopy ()), ac); m_actionPaste = KStdAction::paste (this, SLOT (slotPaste ()), ac); d->m_actionPasteInNewWindow = new KAction (i18n ("Paste in &New Window"), 0, this, SLOT (slotPasteInNewWindow ()), ac, "edit_paste_in_new_window"); //m_actionDelete = KStdAction::clear (this, SLOT (slotDelete ()), ac); m_actionDelete = new KAction (i18n ("&Delete Selection"), 0, this, SLOT (slotDelete ()), ac, "edit_clear"); m_actionSelectAll = KStdAction::selectAll (this, SLOT (slotSelectAll ()), ac); m_actionDeselect = KStdAction::deselect (this, SLOT (slotDeselect ()), ac); d->m_actionCopyToFile = new KAction (i18n ("C&opy to File..."), 0, this, SLOT (slotCopyToFile ()), ac, "edit_copy_to_file"); d->m_actionPasteFromFile = new KAction (i18n ("Paste &From File..."), 0, this, SLOT (slotPasteFromFile ()), ac, "edit_paste_from_file"); m_editMenuDocumentActionsEnabled = false; enableEditMenuDocumentActions (false); // Paste should always be enabled, as long as there is something paste // (independent of whether we have a document or not) connect (QApplication::clipboard (), SIGNAL (dataChanged ()), this, SLOT (slotEnablePaste ())); slotEnablePaste (); } // private void kpMainWindow::enableEditMenuDocumentActions (bool enable) { // m_actionCut // m_actionCopy // m_actionPaste // m_actionPasteInNewWindow // m_actionDelete m_actionSelectAll->setEnabled (enable); // m_actionDeselect m_editMenuDocumentActionsEnabled = enable; // m_actionCopyToFile d->m_actionPasteFromFile->setEnabled (enable); } // private slot void kpMainWindow::slotCut () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotCut() CALLED" << endl; #endif if (!m_document || !m_document->selection ()) { kdError () << "kpMainWindow::slotCut () doc=" << m_document << " sel=" << (m_document ? m_document->selection () : 0) << endl; return; } QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); slotCopy (); slotDelete (); QApplication::restoreOverrideCursor (); } // private slot void kpMainWindow::slotCopy () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotCopy() CALLED" << endl; #endif if (!m_document || !m_document->selection ()) { kdError () << "kpMainWindow::slotCopy () doc=" << m_document << " sel=" << (m_document ? m_document->selection () : 0) << endl; return; } QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); kpSelection sel = *m_document->selection (); if (sel.isText ()) { QApplication::clipboard ()->setData (new QTextDrag (sel.text ()), QClipboard::Clipboard); } else { if (!sel.pixmap ()) sel.setPixmap (m_document->getSelectedPixmap ()); QApplication::clipboard ()->setData (new kpSelectionDrag (sel), QClipboard::Clipboard); } QApplication::restoreOverrideCursor (); } // private slot void kpMainWindow::slotEnablePaste () { bool shouldEnable = false; QMimeSource *ms = QApplication::clipboard ()->data (QClipboard::Clipboard); if (ms) { shouldEnable = (kpSelectionDrag::canDecode (ms) || QTextDrag::canDecode (ms)); } d->m_actionPasteInNewWindow->setEnabled (shouldEnable); m_actionPaste->setEnabled (shouldEnable); } // private QRect kpMainWindow::calcUsefulPasteRect (int pixmapWidth, int pixmapHeight) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::calcUsefulPasteRect(" << pixmapWidth << "," << pixmapHeight << ")" << endl; #endif if (!m_document) { kdError () << "kpMainWindow::calcUsefulPasteRect() without doc" << endl; return QRect (); } // TODO: 1st choice is to paste sel near but not overlapping last deselect point if (m_mainView && m_scrollView) { const QPoint viewTopLeft (m_scrollView->contentsX (), m_scrollView->contentsY ()); const QPoint docTopLeft = m_mainView->zoomViewToDoc (viewTopLeft); if ((docTopLeft.x () + pixmapWidth <= m_document->width () && docTopLeft.y () + pixmapHeight <= m_document->height ()) || pixmapWidth <= docTopLeft.x () || pixmapHeight <= docTopLeft.y ()) { return QRect (docTopLeft.x (), docTopLeft.y (), pixmapWidth, pixmapHeight); } } return QRect (0, 0, pixmapWidth, pixmapHeight); } // private void kpMainWindow::paste (const kpSelection &sel) { if (!sel.pixmap ()) { kdError () << "kpMainWindow::paste() with sel without pixmap" << endl; return; } QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); // // Make sure we've got a document (esp. with File/Close) // if (!m_document) { kpDocument *newDoc = new kpDocument ( sel.width (), sel.height (), 32, this); // will also create viewManager setDocument (newDoc); } // // Paste as new selection // kpSelection selInUsefulPos = sel; selInUsefulPos.moveTo (calcUsefulPasteRect (sel.width (), sel.height ()).topLeft ()); addDeselectFirstCommand (new kpToolSelectionCreateCommand ( selInUsefulPos.isText () ? i18n ("Text: Create Box") : i18n ("Selection: Create"), selInUsefulPos, this)); // If the selection is bigger than the document, automatically // resize the document (with the option of Undo'ing) to fit // the selection. // // No annoying dialog necessary. // if (sel.width () > m_document->width () || sel.height () > m_document->height ()) { m_commandHistory->addCommand ( new kpToolResizeScaleCommand ( false/*act on doc, not sel*/, QMAX (sel.width (), m_document->width ()), QMAX (sel.height (), m_document->height ()), kpToolResizeScaleCommand::Resize, this)); } QApplication::restoreOverrideCursor (); } // public void kpMainWindow::pasteText (const QString &text, bool forceNewTextSelection) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::pasteText(" << text << ",forceNewTextSelection=" << forceNewTextSelection << ")" << endl; #endif QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); if (!forceNewTextSelection && m_document && m_document->selection () && m_document->selection ()->type () == kpSelection::Text && m_commandHistory && m_viewManager) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "\treusing existing Text Selection" << endl; #endif m_commandHistory->addCommand (new kpToolTextInsertCommand ( i18n ("Text: Paste"), m_viewManager->textCursorRow (), m_viewManager->textCursorCol (), text, this), false/*no exec*/); } else { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "\tcreating Text Selection" << endl; #endif QValueVector textLines (1, QString::null); for (int i = 0; i < (int) text.length (); i++) { if (text [i] == '\n') textLines.push_back (QString::null); else textLines [textLines.size () - 1].append (text [i]); } const kpTextStyle ts = textStyle (); const QFontMetrics fontMetrics = ts.fontMetrics (); int height = textLines.size () * fontMetrics.height (); if (textLines.size () >= 1) height += (textLines.size () - 1) * fontMetrics.leading (); int width = 0; for (QValueVector ::const_iterator it = textLines.begin (); it != textLines.end (); it++) { const int w = fontMetrics.width (*it); if (w > width) width = w; } kpSelection sel (QRect (0, 0, width + kpSelection::textBorderSize () * 2, height + kpSelection::textBorderSize () * 2), textLines, ts); paste (sel); } QApplication::restoreOverrideCursor (); } // public void kpMainWindow::pasteTextAt (const QString &text, const QPoint &point) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::pasteTextAt(" << text << ",point=" << point << ")" << endl; #endif QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); if (m_document && m_document->selection () && m_document->selection ()->type () == kpSelection::Text && m_document->selection ()->pointIsInTextArea (point)) { kpSelection *sel = m_document->selection (); const int row = sel->textRowForPoint (point); const int col = sel->textColForPoint (point); m_viewManager->setTextCursorPosition (row, col); pasteText (text); } else { pasteText (text, true/*force new text selection*/); } QApplication::restoreOverrideCursor (); } // public slot void kpMainWindow::slotPaste () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotPaste() CALLED" << endl; #endif // sync: restoreOverrideCursor() in all exit paths QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); // // Acquire the pixmap // QMimeSource *ms = QApplication::clipboard ()->data (QClipboard::Clipboard); if (!ms) { kdError () << "kpMainWindow::slotPaste() without mimeSource" << endl; QApplication::restoreOverrideCursor (); return; } kpSelection sel; QString text; if (kpSelectionDrag::decode (ms, sel/*ref*/, pasteWarnAboutLossInfo ())) { sel.setTransparency (selectionTransparency ()); paste (sel); } else if (QTextDrag::decode (ms, text/*ref*/)) { pasteText (text); } else { kdError () << "kpMainWindow::slotPaste() could not decode selection" << endl; QApplication::restoreOverrideCursor (); return; } QApplication::restoreOverrideCursor (); } // private slot void kpMainWindow::slotPasteInNewWindow () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotPasteInNewWindow() CALLED" << endl; #endif QApplication::setOverrideCursor (Qt::waitCursor); if (toolHasBegunShape ()) tool ()->endShapeInternal (); kpMainWindow *win = new kpMainWindow (0/*no document*/); win->show (); win->slotPaste (); QApplication::restoreOverrideCursor (); } // public slot void kpMainWindow::slotDelete () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotDelete() CALLED" << endl; #endif if (!m_actionDelete->isEnabled ()) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "\taction not enabled - was probably called from kpTool::keyPressEvent()" << endl; #endif return; } if (!m_document || !m_document->selection ()) { kdError () << "kpMainWindow::slotDelete () doc=" << m_document << " sel=" << (m_document ? m_document->selection () : 0) << endl; return; } if (toolHasBegunShape ()) tool ()->endShapeInternal (); addImageOrSelectionCommand (new kpToolSelectionDestroyCommand ( m_document->selection ()->isText () ? i18n ("Text: Delete Box") : // not to be confused with i18n ("Text: Delete") i18n ("Selection: Delete"), false/*no push onto doc*/, this)); } // private slot void kpMainWindow::slotSelectAll () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotSelectAll() CALLED" << endl; #endif if (!m_document) { kdError () << "kpMainWindow::slotSelectAll() without doc" << endl; return; } if (toolHasBegunShape ()) tool ()->endShapeInternal (); if (m_document->selection ()) slotDeselect (); // just the border - don't actually pull pixmap from doc yet m_document->setSelection (kpSelection (kpSelection::Rectangle, m_document->rect (), selectionTransparency ())); if (tool ()) tool ()->somethingBelowTheCursorChanged (); } // private void kpMainWindow::addDeselectFirstCommand (KCommand *cmd) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::addDeselectFirstCommand(" << cmd << ")" << endl; #endif kpSelection *sel = m_document->selection (); #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "\tsel=" << sel << endl; #endif if (sel) { // if you just dragged out something with no action then // forget the drag if (!sel->pixmap ()) { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "\tjust a fresh border - was nop - delete" << endl; #endif m_document->selectionDelete (); if (tool ()) tool ()->somethingBelowTheCursorChanged (); if (cmd) m_commandHistory->addCommand (cmd); } else { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "\treal selection with pixmap - push onto doc cmd" << endl; #endif KCommand *deselectCommand = new kpToolSelectionDestroyCommand ( sel->isText () ? i18n ("Text: Finish") : i18n ("Selection: Deselect"), true/*push onto document*/, this); if (cmd) { KMacroCommand *macroCmd = new KMacroCommand (cmd->name ()); macroCmd->addCommand (deselectCommand); macroCmd->addCommand (cmd); m_commandHistory->addCommand (macroCmd); } else m_commandHistory->addCommand (deselectCommand); } } else { if (cmd) m_commandHistory->addCommand (cmd); } } // public slot void kpMainWindow::slotDeselect () { #if DEBUG_KP_MAIN_WINDOW && 1 kdDebug () << "kpMainWindow::slotDeselect() CALLED" << endl; #endif if (!m_document || !m_document->selection ()) { kdError () << "kpMainWindow::slotDeselect() doc=" << m_document << " sel=" << (m_document ? m_document->selection () : 0) << endl; return; } if (toolHasBegunShape ()) tool ()->endShapeInternal (); addDeselectFirstCommand (0); } // private slot void kpMainWindow::slotCopyToFile () { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::slotCopyToFile()" << endl; #endif if (toolHasBegunShape ()) tool ()->endShapeInternal (); QString chosenMimeType; KURL chosenURL = askForSaveURL (i18n ("Copy To File"), d->m_lastCopyToURL.url (), d->m_lastCopyToMimeType, "Edit/Copy To File", false/*allow remote files*/, chosenMimeType/*ref*/); if (chosenURL.isEmpty () || chosenMimeType.isEmpty ()) return; d->m_lastCopyToURL = chosenURL; d->m_lastCopyToMimeType = chosenMimeType; saveLastOutputMimeType (chosenMimeType, "Edit/Copy To File"); if (!kpDocument::savePixmapToFile (m_document->getSelectedPixmap (), chosenURL, chosenMimeType, true/*overwrite prompt*/, this)) { return; } addRecentURL (chosenURL); } // private slot void kpMainWindow::slotPasteFromFile () { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::slotPasteFromFile()" << endl; #endif if (toolHasBegunShape ()) tool ()->endShapeInternal (); KURL::List urls = askForOpenURLs (i18n ("Paste From File"), d->m_lastPasteFromURL.url (), false/*only 1 URL*/); if (urls.count () != 1) return; KURL url = urls.first (); d->m_lastPasteFromURL = url; - QString mimeType_Ignored; QPixmap pixmap = kpDocument::getPixmapFromFile (url, false/*show error message if doesn't exist*/, - this, - mimeType_Ignored); + this); if (pixmap.isNull ()) return; addRecentURL (url); paste (kpSelection (kpSelection::Rectangle, QRect (0, 0, pixmap.width (), pixmap.height ()), pixmap)); } diff --git a/kpmainwindow_file.cpp b/kpmainwindow_file.cpp index a8308d08..0720199f 100644 --- a/kpmainwindow_file.cpp +++ b/kpmainwindow_file.cpp @@ -1,815 +1,871 @@ /* Copyright (c) 2003-2004 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. */ #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 // private void kpMainWindow::setupFileMenuActions () { KActionCollection *ac = actionCollection (); m_actionNew = KStdAction::openNew (this, SLOT (slotNew ()), ac); m_actionOpen = KStdAction::open (this, SLOT (slotOpen ()), ac); m_actionOpenRecent = KStdAction::openRecent (this, SLOT (slotOpenRecent (const KURL &)), ac); m_actionOpenRecent->loadEntries (kapp->config ()); m_actionSave = KStdAction::save (this, SLOT (slotSave ()), ac); m_actionSaveAs = KStdAction::saveAs (this, SLOT (slotSaveAs ()), ac); d->m_actionExport = new KAction (i18n ("E&xport..."), 0, this, SLOT (slotExport ()), ac, "file_export"); //m_actionRevert = KStdAction::revert (this, SLOT (slotRevert ()), ac); m_actionReload = new KAction (i18n ("Reloa&d"), KStdAccel::reload (), this, SLOT (slotReload ()), ac, "file_revert"); slotEnableReload (); m_actionPrint = KStdAction::print (this, SLOT (slotPrint ()), ac); m_actionPrintPreview = KStdAction::printPreview (this, SLOT (slotPrintPreview ()), ac); m_actionMail = KStdAction::mail (this, SLOT (slotMail ()), ac); m_actionSetAsWallpaperCentered = new KAction (i18n ("Set as Wa&llpaper (Centered)"), 0, this, SLOT (slotSetAsWallpaperCentered ()), ac, "file_set_as_wallpaper_centered"); m_actionSetAsWallpaperTiled = new KAction (i18n ("Set as Wallpaper (&Tiled)"), 0, this, SLOT (slotSetAsWallpaperTiled ()), ac, "file_set_as_wallpaper_tiled"); m_actionClose = KStdAction::close (this, SLOT (slotClose ()), ac); m_actionQuit = KStdAction::quit (this, SLOT (slotQuit ()), ac); enableFileMenuDocumentActions (false); } // private void kpMainWindow::enableFileMenuDocumentActions (bool enable) { // m_actionNew // m_actionOpen // m_actionOpenRecent m_actionSave->setEnabled (enable); m_actionSaveAs->setEnabled (enable); d->m_actionExport->setEnabled (enable); // m_actionReload m_actionPrint->setEnabled (enable); m_actionPrintPreview->setEnabled (enable); m_actionMail->setEnabled (enable); m_actionSetAsWallpaperCentered->setEnabled (enable); m_actionSetAsWallpaperTiled->setEnabled (enable); m_actionClose->setEnabled (enable); // m_actionQuit->setEnabled (enable); } // private bool kpMainWindow::shouldOpenInNewWindow () const { return (m_document && !m_document->isEmpty ()); } // private void kpMainWindow::addRecentURL (const KURL &url) { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::addRecentURL(" << url << ")" << endl; #endif if (url.isEmpty ()) return; KConfig *cfg = kapp->config (); // KConfig::readEntry() does not actually reread from disk, hence doesn't // realise what other processes have done e.g. Settings / Show Path cfg->reparseConfiguration (); // HACK: Something might have changed interprocess. // If we could PROPAGATE: interprocess, then this wouldn't be required. m_actionOpenRecent->loadEntries (cfg); m_actionOpenRecent->addURL (url); m_actionOpenRecent->saveEntries (cfg); cfg->sync (); #if DEBUG_KP_MAIN_WINDOW kdDebug () << "\tnew recent URLs=" << m_actionOpenRecent->items () << endl; #endif // TODO: PROPAGATE: interprocess if (KMainWindow::memberList) { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "\thave memberList" << endl; #endif for (QPtrList ::const_iterator it = KMainWindow::memberList->begin (); it != KMainWindow::memberList->end (); it++) { kpMainWindow *mw = dynamic_cast (*it); if (!mw) { kdError () << "kpMainWindow::addRecentURL() given fake kpMainWindow: " << (*it) << endl; continue; } #if DEBUG_KP_MAIN_WINDOW kdDebug () << "\t\tmw=" << mw << endl; #endif if (mw != this) mw->setRecentURLs (m_actionOpenRecent->items ()); } } } // private void kpMainWindow::setRecentURLs (const QStringList &items) { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow(" << name () << ")::setRecentURLs()" << endl; kdDebug () << "\titems=" << items << endl; #endif m_actionOpenRecent->setItems (items); } // private slot void kpMainWindow::slotNew () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); if (m_document) { kpMainWindow *win = new kpMainWindow (); win->show (); } else { open (KURL (), true/*create an empty doc*/); } } static QSize defaultDocSize () { // KConfig::readEntry() does not actually reread from disk, hence doesn't // realise what other processes have done e.g. Settings / Show Path kapp->config ()->reparseConfiguration (); KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); KConfigBase *cfg = cfgGroupSaver.config (); QSize docSize = cfg->readSizeEntry (kpSettingLastDocSize); if (docSize.isEmpty ()) docSize = QSize (400, 300); return docSize; } // private bool kpMainWindow::open (const KURL &url, bool newDocSameNameIfNotExist) { QSize docSize = ::defaultDocSize (); // create doc kpDocument *newDoc = new kpDocument (docSize.width (), docSize.height (), 32, this); if (newDoc->open (url, newDocSameNameIfNotExist)) - addRecentURL (url); + { + if (newDoc->isFromURL (false/*don't bother checking exists*/)) + addRecentURL (url); + } else { delete newDoc; return false; } // need new window? if (shouldOpenInNewWindow ()) { // send doc to new window kpMainWindow *win = new kpMainWindow (newDoc); win->show (); } else { // set up views, doc signals setDocument (newDoc); } return true; } // private KURL::List kpMainWindow::askForOpenURLs (const QString &caption, const QString &startURL, bool allowMultipleURLs) { QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Reading); QString filter = mimeTypes.join (" "); KFileDialog fd (startURL, filter, this, "fd", true/*modal*/); fd.setCaption (caption); fd.setOperationMode (KFileDialog::Opening); if (allowMultipleURLs) fd.setMode (KFile::Files); fd.setPreviewWidget (new KImageFilePreview (&fd)); if (fd.exec ()) return fd.selectedURLs (); else return KURL::List (); } // private slot void kpMainWindow::slotOpen () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); const KURL::List urls = askForOpenURLs (i18n ("Open Image"), m_document ? m_document->url ().url () : QString::null); for (KURL::List::const_iterator it = urls.begin (); it != urls.end (); it++) { open (*it); } } // private slot void kpMainWindow::slotOpenRecent (const KURL &url) { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::slotOpenRecent(" << url << ")" << endl; #endif if (toolHasBegunShape ()) tool ()->endShapeInternal (); open (url); } // private slot bool kpMainWindow::save (bool localOnly) { if (m_document->url ().isEmpty () || KImageIO::mimeTypes (KImageIO::Writing).findIndex (m_document->mimetype ()) < 0 || (localOnly && !m_document->url ().isLocalFile ())) { return saveAs (localOnly); } else { if (m_document->save ()) { addRecentURL (m_document->url ()); return true; } else return false; } } // private slot bool kpMainWindow::slotSave () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); return save (); } // private KURL kpMainWindow::askForSaveURL (const QString &caption, const QString &startURL, const QString &startMimeType, const char *lastOutputMimeTypeSettingsPrefix, bool localOnly, QString &chosenMimeType) { chosenMimeType = QString::null; QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Writing); if (mimeTypes.isEmpty ()) { kdError () << "No KImageIO output mimetypes!" << endl; return KURL (); } KFileDialog fd (startURL, QString::null, this, "fd", true/*modal*/); fd.setCaption (caption); fd.setOperationMode (KFileDialog::Saving); if (localOnly) fd.setMode (KFile::File | KFile::LocalOnly); QString defaultMimeType; // use the current mimetype of the document (if available) // // this is so as to not stuff up users who are just changing the filename // but want to save in the same type if (!startMimeType.isEmpty () && mimeTypes.findIndex (startMimeType) > -1) defaultMimeType = startMimeType; if (defaultMimeType.isEmpty ()) { // KConfig::readEntry() does not actually reread from disk, hence doesn't // realise what other processes have done e.g. Settings / Show Path kapp->config ()->reparseConfiguration (); KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); KConfigBase *cfg = cfgGroupSaver.config (); const QString lastOutputMimeTypeEntry = QString::fromLatin1 (lastOutputMimeTypeSettingsPrefix) + QString::fromLatin1 (" ") + kpSettingLastOutputMimeType; QString lastOutputMimeType = cfg->readEntry (lastOutputMimeTypeEntry, QString::fromLatin1 ("image/png")); if (mimeTypes.findIndex (lastOutputMimeType) > -1) defaultMimeType = lastOutputMimeType; else if (mimeTypes.findIndex ("image/png") > -1) defaultMimeType = "image/png"; else if (mimeTypes.findIndex ("image/x-bmp") > -1) defaultMimeType = "image/x-bmp"; else defaultMimeType = mimeTypes.first (); } #if DEBUG_KP_MAIN_WINDOW kdDebug () << "mimeTypes=" << mimeTypes << endl; #endif fd.setMimeFilter (mimeTypes, defaultMimeType); if (fd.exec ()) { chosenMimeType = fd.currentMimeFilter (); return fd.selectedURL (); } else return KURL (); } // private void kpMainWindow::saveLastOutputMimeType (const QString &mimeType, const char *lastOutputMimeTypeSettingsPrefix) { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "\tCONFIG: saveLastOutputMimeType(" << mimeType << "," << lastOutputMimeType << ")" << endl; #endif KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); KConfigBase *cfg = cfgGroupSaver.config (); const QString lastOutputMimeTypeEntry = QString::fromLatin1 (lastOutputMimeTypeSettingsPrefix) + QString::fromLatin1 (" ") + kpSettingLastOutputMimeType; cfg->writeEntry (lastOutputMimeTypeEntry, mimeType); cfg->sync (); } // private slot bool kpMainWindow::saveAs (bool localOnly) { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::saveAs URL=" << m_document->url () << endl; #endif QString chosenMimeType; KURL chosenURL = askForSaveURL (i18n ("Save Image As"), m_document->url ().url (), m_document->mimetype (), "File/Save As", localOnly, chosenMimeType/*ref*/); if (chosenURL.isEmpty () || chosenMimeType.isEmpty ()) return false; // user forced a mimetype (as opposed to selecting the same type as the current doc) // - probably wants to use it in the future if (chosenMimeType != m_document->mimetype ()) saveLastOutputMimeType (chosenMimeType, "File/Save As"); if (!m_document->saveAs (chosenURL, chosenMimeType)) return false; addRecentURL (chosenURL); return true; } // private slot bool kpMainWindow::slotSaveAs () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); return saveAs (); } // private slot bool kpMainWindow::slotExport () { #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::slotExport()" << endl; #endif if (toolHasBegunShape ()) tool ()->endShapeInternal (); QString chosenMimeType; KURL chosenURL = askForSaveURL (i18n ("Export"), d->m_lastExportURL.url (), d->m_lastExportMimeType, "File/Export", false/*allow remote files*/, chosenMimeType/*ref*/); if (chosenURL.isEmpty () || chosenMimeType.isEmpty ()) return false; d->m_lastExportURL = chosenURL; d->m_lastExportMimeType = chosenMimeType; saveLastOutputMimeType (chosenMimeType, "File/Export"); if (!kpDocument::savePixmapToFile (m_document->pixmapWithSelection (), chosenURL, chosenMimeType, true/*overwrite prompt*/, this)) { return false; } addRecentURL (chosenURL); return true; } // private slot void kpMainWindow::slotEnableReload () { - m_actionReload->setEnabled (m_document && !m_document->url ().isEmpty ()); + m_actionReload->setEnabled (m_document); } // private slot bool kpMainWindow::slotReload () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); - if (m_document && m_document->isModified ()) + if (!m_document) + return false; + + + KURL oldURL = m_document->url (); + + + if (m_document->isModified ()) { - int result = KMessageBox::warningContinueCancel (this, + int result = KMessageBox::Cancel; + + if (m_document->isFromURL () && !oldURL.isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, i18n ("The document \"%1\" has been modified.\n" "Reloading will lose all changes since you last saved it.\n" "Are you sure?") .arg (m_document->prettyFilename ()), - QString::null/*caption*/, - i18n ("&Reload")); + QString::null/*caption*/, + i18n ("&Reload")); + } + else + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?") + .arg (m_document->prettyFilename ()), + QString::null/*caption*/, + i18n ("&Reload")); + } if (result != KMessageBox::Continue) return false; } - KURL oldURL = m_document->url (); -#if DEBUG_KP_MAIN_WINDOW - kdDebug () << "kpMainWindow::slotReload() reloading!" << endl; -#endif - setDocument (0); // make sure we don't open in a new window + kpDocument *doc = 0; - return open (oldURL); + // If it's _supposed to_ come from a URL or it exists + if (m_document->isFromURL (false/*don't bother checking exists*/) || + (!oldURL.isEmpty () && KIO::NetAccess::exists (oldURL, true/*open*/, this))) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotReload() reloading from disk!" << endl; + #endif + + doc = new kpDocument (1, 1, 32, this); + if (!doc->open (oldURL)) + { + delete doc; doc = 0; + return false; + } + + addRecentURL (oldURL); + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotReload() create doc" << endl; + #endif + + doc = new kpDocument (m_document->constructorWidth (), + m_document->constructorHeight (), + 32, + this); + doc->setURL (oldURL, false/*not from URL*/); + } + + + setDocument (doc); + + return true; } // private void kpMainWindow::sendFilenameToPrinter (KPrinter *printer) { KURL url = m_document->url (); if (!url.isEmpty ()) { int dot; QString fileName = url.fileName (); dot = fileName.findRev ('.'); // file.ext but not .hidden-file? if (dot > 0) fileName.truncate (dot); #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::sendFilenameToPrinter() fileName=" << fileName << " dir=" << url.directory () << endl; #endif printer->setDocName (fileName); printer->setDocFileName (fileName); printer->setDocDirectory (url.directory ()); } } // private void kpMainWindow::sendPixmapToPrinter (KPrinter *printer) { QPainter painter; painter.begin (printer); painter.drawPixmap (0, 0, m_document->pixmapWithSelection ()); painter.end (); } // private slot void kpMainWindow::slotPrint () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); KPrinter printer; sendFilenameToPrinter (&printer); if (!printer.setup (this)) return; sendPixmapToPrinter (&printer); } // private slot void kpMainWindow::slotPrintPreview () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); // TODO: get it to reflect default printer's settings KPrinter printer (false/*separate settings from ordinary printer*/); // TODO: pass "this" as parent printer.setPreviewOnly (true); sendFilenameToPrinter (&printer); sendPixmapToPrinter (&printer); } // private slot void kpMainWindow::slotMail () { - if (m_document->url ().isEmpty () || // no name - m_document->isModified ()) // hasn't been saved + if (m_document->url ().isEmpty ()/*no name*/ || + !m_document->isFromURL () || + m_document->isModified ()/*needs to be saved*/) { int result = KMessageBox::questionYesNo (this, i18n ("You must save this image before sending it.\n" "Do you want to save it?"), QString::null, KStdGuiItem::save (), KStdGuiItem::cancel ()); if (result == KMessageBox::Yes) { if (!save ()) { // save failed or aborted - don't email return; } } else { // don't want to save - don't email return; } } kapp->invokeMailer ( QString::null/*to*/, QString::null/*cc*/, QString::null/*bcc*/, m_document->prettyFilename()/*subject*/, QString::null/*body*/, QString::null/*messageFile*/, QStringList (m_document->url ().url ())/*attachments*/); } // private void kpMainWindow::setAsWallpaper (bool centered) { - if (!m_document->url ().isLocalFile () || // remote file - m_document->url ().isEmpty () || // no name - m_document->isModified ()) // hasn't been saved + if (m_document->url ().isEmpty ()/*no name*/ || + !m_document->url ().isLocalFile ()/*remote file*/ || + !m_document->isFromURL () || + m_document->isModified ()/*needs to be saved*/) { QString question; if (!m_document->url ().isLocalFile ()) { question = i18n ("Before this image can be set as the wallpaper, " "you must save it as a local file.\n" "Do you want to save it?"); } else { question = i18n ("Before this image can be set as the wallpaper, " "you must save it.\n" "Do you want to save it?"); } int result = KMessageBox::questionYesNo (this, question, QString::null, KStdGuiItem::save (), KStdGuiItem::cancel ()); if (result == KMessageBox::Yes) { // save() is smart enough to pop up a filedialog if it's a // remote file that should be saved locally if (!save (true/*localOnly*/)) { // save failed or aborted - don't set the wallpaper return; } } else { // don't want to save - don't set wallpaper return; } } QByteArray data; QDataStream dataStream (data, IO_WriteOnly); // write path #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::setAsWallpaper() path=" << m_document->url ().path () << endl; #endif dataStream << QString (m_document->url ().path ()); // write position: // // SYNC: kdebase/kcontrol/background/bgsettings.h: // 1 = Centered // 2 = Tiled // 6 = Scaled // 8 = lastWallpaperMode // // Why restrict the user to Centered & Tiled? // Why don't we let the user choose if it should be common to all desktops? // Why don't we rewrite the Background control page? // // Answer: This is supposed to be a quick & convenient feature. // // If you want more options, go to kcontrol for that kind of // flexiblity. We don't want to slow down average users, who see way too // many dialogs already and probably haven't even heard of "Centered Maxpect"... // dataStream << int (centered ? 1 : 2); // I'm going to all this trouble because the user might not have kdebase // installed so kdebase/kdesktop/KBackgroundIface.h might not be around // to be compiled in (where user == developer :)) if (!KApplication::dcopClient ()->send ("kdesktop", "KBackgroundIface", "setWallpaper(QString,int)", data)) { KMessageBox::sorry (this, i18n ("Could not change wallpaper.")); } } // private slot void kpMainWindow::slotSetAsWallpaperCentered () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); setAsWallpaper (true/*centered*/); } // private slot void kpMainWindow::slotSetAsWallpaperTiled () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); setAsWallpaper (false/*tiled*/); } // private slot void kpMainWindow::slotClose () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::slotClose()" << endl; #endif if (!queryClose ()) return; setDocument (0); } // private slot void kpMainWindow::slotQuit () { if (toolHasBegunShape ()) tool ()->endShapeInternal (); #if DEBUG_KP_MAIN_WINDOW kdDebug () << "kpMainWindow::slotQuit()" << endl; #endif close (); // will call queryClose() }