diff --git a/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp b/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp index 4a52f260..cd2dcb52 100644 --- a/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp +++ b/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp @@ -1,482 +1,486 @@ /* Copyright (c) 2003-2007 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_TOOL_RESIZE_SCALE_COMMAND 0 #define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 #include "kpTransformResizeScaleCommand.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/image/kpFreeFormImageSelection.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/selections/image/kpRectangularImageSelection.h" #include "layers/selections/text/kpTextSelection.h" #include #include #include #include #include #include #include "kpLogCategories.h" #include //-------------------------------------------------------------------------------- kpTransformResizeScaleCommand::kpTransformResizeScaleCommand (bool actOnSelection, int newWidth, int newHeight, Type type, kpCommandEnvironment *environ) : kpCommand (environ), m_actOnSelection (actOnSelection), m_type (type), m_backgroundColor (environ->backgroundColor ()), m_oldSelectionPtr (nullptr) { kpDocument *doc = document (); Q_ASSERT (doc); m_oldWidth = doc->width (m_actOnSelection); m_oldHeight = doc->height (m_actOnSelection); m_actOnTextSelection = (m_actOnSelection && doc->textSelection ()); resize (newWidth, newHeight); // If we have a selection _border_ (but not a floating selection), // then scale the selection with the document m_scaleSelectionWithImage = (!m_actOnSelection && (m_type == Scale || m_type == SmoothScale) && document ()->selection () && !document ()->selection ()->hasContent ()); } kpTransformResizeScaleCommand::~kpTransformResizeScaleCommand () { delete m_oldSelectionPtr; } // public virtual [base kpCommand] QString kpTransformResizeScaleCommand::name () const { if (m_actOnSelection) { if (m_actOnTextSelection) { if (m_type == Resize) { return i18n ("Text: Resize Box"); } } else { if (m_type == Scale) { return i18n ("Selection: Scale"); } if (m_type == SmoothScale) { return i18n ("Selection: Smooth Scale"); } } } else { switch (m_type) { case Resize: return i18n ("Resize"); case Scale: return i18n ("Scale"); case SmoothScale: return i18n ("Smooth Scale"); } } return QString (); } // public virtual [base kpCommand] kpCommandSize::SizeType kpTransformResizeScaleCommand::size () const { return ImageSize (m_oldImage) + ImageSize (m_oldRightImage) + ImageSize (m_oldBottomImage) + SelectionSize (m_oldSelectionPtr); } // public int kpTransformResizeScaleCommand::newWidth () const { return m_newWidth; } // public void kpTransformResizeScaleCommand::setNewWidth (int width) { resize (width, newHeight ()); } // public int kpTransformResizeScaleCommand::newHeight () const { return m_newHeight; } // public void kpTransformResizeScaleCommand::setNewHeight (int height) { resize (newWidth (), height); } // public QSize kpTransformResizeScaleCommand::newSize () const { return {newWidth (), newHeight ()}; } // public virtual void kpTransformResizeScaleCommand::resize (int width, int height) { m_newWidth = width; m_newHeight = height; m_isLosslessScale = ((m_type == Scale) && (m_newWidth / m_oldWidth * m_oldWidth == m_newWidth) && (m_newHeight / m_oldHeight * m_oldHeight == m_newHeight)); } // public bool kpTransformResizeScaleCommand::scaleSelectionWithImage () const { return m_scaleSelectionWithImage; } // private void kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument () { #if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument"; #endif Q_ASSERT (m_oldSelectionPtr); Q_ASSERT (!m_oldSelectionPtr->hasContent ()); const double horizScale = double (m_newWidth) / double (m_oldWidth); const double vertScale = double (m_newHeight) / double (m_oldHeight); const int newX = static_cast (m_oldSelectionPtr->x () * horizScale); const int newY = static_cast (m_oldSelectionPtr->y () * vertScale); QPolygon currentPoints = m_oldSelectionPtr->calculatePoints (); currentPoints.translate (-currentPoints.boundingRect ().x (), -currentPoints.boundingRect ().y ()); // TODO: refactor into kpPixmapFX // TODO: Can we get to size 0x0 accidently? QTransform scaleMatrix; scaleMatrix.scale (horizScale, vertScale); currentPoints = scaleMatrix.map (currentPoints); currentPoints.translate ( -currentPoints.boundingRect ().x () + newX, -currentPoints.boundingRect ().y () + newY); auto *imageSel = dynamic_cast (m_oldSelectionPtr); auto *textSel = dynamic_cast (m_oldSelectionPtr); if (imageSel) { document ()->setSelection ( kpFreeFormImageSelection (currentPoints, kpImage (), imageSel->transparency ())); } else if (textSel) { document ()->setSelection ( kpTextSelection (currentPoints.boundingRect (), textSel->textLines (), textSel->textStyle ())); } else { Q_ASSERT (!"Unknown selection type"); } environ ()->somethingBelowTheCursorChanged (); } // public virtual [base kpCommand] void kpTransformResizeScaleCommand::execute () { +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::execute() type=" << (int) m_type << " oldWidth=" << m_oldWidth << " oldHeight=" << m_oldHeight << " newWidth=" << m_newWidth << " newHeight=" << m_newHeight; +#endif if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) return; if (m_type == Resize) { if (m_actOnSelection) { if (!m_actOnTextSelection) { Q_ASSERT (!"kpTransformResizeScaleCommand::execute() resizing sel doesn't make sense"); } QApplication::setOverrideCursor (Qt::WaitCursor); kpTextSelection *textSel = textSelection (); Q_ASSERT (textSel); kpTextSelection *newSel = textSel->resized (m_newWidth, m_newHeight); document ()->setSelection (*newSel); delete newSel; environ ()->somethingBelowTheCursorChanged (); QApplication::restoreOverrideCursor (); } else { QApplication::setOverrideCursor (Qt::WaitCursor); if (m_newWidth < m_oldWidth) { m_oldRightImage = document ()->getImageAt ( QRect (m_newWidth, 0, m_oldWidth - m_newWidth, m_oldHeight)); } if (m_newHeight < m_oldHeight) { m_oldBottomImage = document ()->getImageAt ( QRect (0, m_newHeight, m_newWidth, m_oldHeight - m_newHeight)); } document ()->resize (m_newWidth, m_newHeight, m_backgroundColor); QApplication::restoreOverrideCursor (); } } // Scale else { QApplication::setOverrideCursor (Qt::WaitCursor); kpImage oldImage = document ()->image (m_actOnSelection); if (!m_isLosslessScale) { m_oldImage = oldImage; } kpImage newImage = kpPixmapFX::scale (oldImage, m_newWidth, m_newHeight, m_type == SmoothScale); if (!m_oldSelectionPtr && document ()->selection ()) { // Save sel border m_oldSelectionPtr = document ()->selection ()->clone (); m_oldSelectionPtr->deleteContent (); } if (m_actOnSelection) { if (m_actOnTextSelection) { Q_ASSERT (!"kpTransformResizeScaleCommand::execute() scaling text sel doesn't make sense"); } Q_ASSERT (m_oldSelectionPtr); if ( !m_oldSelectionPtr ) { // make coverity happy return; } QRect newRect = QRect (m_oldSelectionPtr->x (), m_oldSelectionPtr->y (), newImage.width (), newImage.height ()); // Not possible to retain non-rectangular selection borders on scale // (think about e.g. a 45 deg line as part of the border & 2x scale) Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); document ()->setSelection ( kpRectangularImageSelection (newRect, newImage, dynamic_cast (m_oldSelectionPtr) ->transparency ())); environ ()->somethingBelowTheCursorChanged (); } else { document ()->setImage (newImage); if (m_scaleSelectionWithImage) { scaleSelectionRegionWithDocument (); } } QApplication::restoreOverrideCursor (); } } // public virtual [base kpCommand] void kpTransformResizeScaleCommand::unexecute () { +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::unexecute() type=" << m_type; +#endif if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) { return; } kpDocument *doc = document (); Q_ASSERT (doc); if (m_type == Resize) { if (m_actOnSelection) { if (!m_actOnTextSelection) { Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() resizing sel doesn't make sense"); } QApplication::setOverrideCursor (Qt::WaitCursor); kpTextSelection *textSel = textSelection (); Q_ASSERT (textSel); kpTextSelection *newSel = textSel->resized (m_oldWidth, m_oldHeight); document ()->setSelection (*newSel); delete newSel; environ ()->somethingBelowTheCursorChanged (); QApplication::restoreOverrideCursor (); } else { QApplication::setOverrideCursor (Qt::WaitCursor); kpImage newImage (m_oldWidth, m_oldHeight, QImage::Format_ARGB32_Premultiplied); kpPixmapFX::setPixmapAt (&newImage, QPoint (0, 0), doc->image ()); if (m_newWidth < m_oldWidth) { kpPixmapFX::setPixmapAt (&newImage, QPoint (m_newWidth, 0), m_oldRightImage); } if (m_newHeight < m_oldHeight) { kpPixmapFX::setPixmapAt (&newImage, QPoint (0, m_newHeight), m_oldBottomImage); } doc->setImage (newImage); QApplication::restoreOverrideCursor (); } } // Scale else { QApplication::setOverrideCursor (Qt::WaitCursor); kpImage oldImage; if (!m_isLosslessScale) { oldImage = m_oldImage; } else { oldImage = kpPixmapFX::scale (doc->image (m_actOnSelection), m_oldWidth, m_oldHeight); } if (m_actOnSelection) { if (m_actOnTextSelection) { Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() scaling text sel doesn't make sense"); } Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); auto *oldImageSel = dynamic_cast (m_oldSelectionPtr); kpAbstractImageSelection *oldSelection = oldImageSel->clone (); oldSelection->setBaseImage (oldImage); doc->setSelection (*oldSelection); delete oldSelection; environ ()->somethingBelowTheCursorChanged (); } else { doc->setImage (oldImage); if (m_scaleSelectionWithImage) { doc->setSelection (*m_oldSelectionPtr); environ ()->somethingBelowTheCursorChanged (); } } QApplication::restoreOverrideCursor (); } } diff --git a/commands/kpCommandHistory.cpp b/commands/kpCommandHistory.cpp index 4a6d29be..dd44de50 100644 --- a/commands/kpCommandHistory.cpp +++ b/commands/kpCommandHistory.cpp @@ -1,128 +1,131 @@ /* Copyright (c) 2003-2007 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_COMMAND_HISTORY 0 #include "kpCommandHistory.h" #include "layers/selections/kpAbstractSelection.h" #include "mainWindow/kpMainWindow.h" #include "tools/kpTool.h" #include "commands/tools/selection/kpToolSelectionCreateCommand.h" -#include "kpLogCategories.h" kpCommandHistory::kpCommandHistory (bool doReadConfig, kpMainWindow *mainWindow) : kpCommandHistoryBase (doReadConfig, mainWindow->actionCollection ()), m_mainWindow (mainWindow) { } kpCommandHistory::~kpCommandHistory () = default; static bool NextUndoCommandIsCreateBorder (kpCommandHistory *commandHistory) { Q_ASSERT (commandHistory); kpCommand *cmd = commandHistory->nextUndoCommand (); if (!cmd) { return false; } auto *c = dynamic_cast (cmd); if (!c) { return false; } const kpAbstractSelection *sel = c->fromSelection (); Q_ASSERT (sel); return (!sel->hasContent ()); } // public void kpCommandHistory::addCreateSelectionCommand (kpToolSelectionCreateCommand *cmd, bool execute) { if (cmd->fromSelection ()->hasContent ()) { addCommand (cmd, execute); return; } if (::NextUndoCommandIsCreateBorder (this)) { setNextUndoCommand (cmd); if (execute) { cmd->execute (); } } else { addCommand (cmd, execute); } } //--------------------------------------------------------------------- // public slot virtual [base KCommandHistory] void kpCommandHistory::undo () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistory::undo() CALLED!"; +#endif if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) { + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\thas begun shape - cancel draw"; + #endif m_mainWindow->tool ()->cancelShapeInternal (); } else { kpCommandHistoryBase::undo (); } } //--------------------------------------------------------------------- // public slot virtual [base KCommandHistory] void kpCommandHistory::redo () { if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) { // Not completely obvious but what else can we do? // // Ignoring the request would not be intuitive for tools like // Polygon & Polyline (where it's not always apparent to the user // that s/he's still drawing a shape even though the mouse isn't // down). m_mainWindow->tool ()->cancelShapeInternal (); } else { kpCommandHistoryBase::redo (); } } diff --git a/commands/kpCommandHistoryBase.cpp b/commands/kpCommandHistoryBase.cpp index 61e1f219..19dc47f7 100644 --- a/commands/kpCommandHistoryBase.cpp +++ b/commands/kpCommandHistoryBase.cpp @@ -1,654 +1,730 @@ /* Copyright (c) 2003-2007 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_COMMAND_HISTORY 0 #include "kpCommandHistoryBase.h" #include #include #include #include #include #include #include #include #include #include #include #include "kpCommand.h" #include "kpLogCategories.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "mainWindow/kpMainWindow.h" #include "tools/kpTool.h" //--------------------------------------------------------------------- //template static void ClearPointerList (QLinkedList *listPtr) { if (!listPtr) return; qDeleteAll (listPtr->begin (), listPtr->end ()); listPtr->clear (); } struct kpCommandHistoryBasePrivate { }; kpCommandHistoryBase::kpCommandHistoryBase (bool doReadConfig, KActionCollection *ac) : d (new kpCommandHistoryBasePrivate ()) { m_actionUndo = new KToolBarPopupAction(KDE::icon("edit-undo"), undoActionText (), this); ac->addAction (KStandardAction::name (KStandardAction::Undo), m_actionUndo); ac->setDefaultShortcuts (m_actionUndo, KStandardShortcut::shortcut (KStandardShortcut::Undo)); connect (m_actionUndo, &KToolBarPopupAction::triggered, this, &kpCommandHistoryBase::undo); m_actionRedo = new KToolBarPopupAction(KDE::icon("edit-redo"), redoActionText (), this); ac->addAction (KStandardAction::name (KStandardAction::Redo), m_actionRedo); ac->setDefaultShortcuts (m_actionRedo, KStandardShortcut::shortcut (KStandardShortcut::Redo)); connect (m_actionRedo, &KToolBarPopupAction::triggered, this, &kpCommandHistoryBase::redo ); m_actionUndo->setEnabled (false); m_actionRedo->setEnabled (false); connect (m_actionUndo->menu(), &QMenu::triggered, this, &kpCommandHistoryBase::undoUpToNumber); connect (m_actionRedo->menu(), &QMenu::triggered, this, &kpCommandHistoryBase::redoUpToNumber); m_undoMinLimit = 10; m_undoMaxLimit = 500; m_undoMaxLimitSizeLimit = 16 * 1048576; m_documentRestoredPosition = 0; if (doReadConfig) { readConfig (); } } kpCommandHistoryBase::~kpCommandHistoryBase () { ::ClearPointerList (&m_undoCommandList); ::ClearPointerList (&m_redoCommandList); delete d; } // public int kpCommandHistoryBase::undoLimit () const { return undoMinLimit (); } // public void kpCommandHistoryBase::setUndoLimit (int limit) { setUndoMinLimit (limit); } // public int kpCommandHistoryBase::undoMinLimit () const { return m_undoMinLimit; } // public void kpCommandHistoryBase::setUndoMinLimit (int limit) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::setUndoMinLimit(" << limit << ")"; +#endif if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) { qCCritical(kpLogCommands) << "kpCommandHistoryBase::setUndoMinLimit(" << limit << ")"; return; } if (limit == m_undoMinLimit) { return; } m_undoMinLimit = limit; trimCommandListsUpdateActions (); } // public int kpCommandHistoryBase::undoMaxLimit () const { return m_undoMaxLimit; } // public void kpCommandHistoryBase::setUndoMaxLimit (int limit) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimit(" << limit << ")"; +#endif if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) { qCCritical(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimit(" << limit << ")"; return; } if (limit == m_undoMaxLimit) { return; } m_undoMaxLimit = limit; trimCommandListsUpdateActions (); } // public kpCommandSize::SizeType kpCommandHistoryBase::undoMaxLimitSizeLimit () const { return m_undoMaxLimitSizeLimit; } // public void kpCommandHistoryBase::setUndoMaxLimitSizeLimit (kpCommandSize::SizeType sizeLimit) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" << sizeLimit << ")"; +#endif if (sizeLimit < 0 || sizeLimit > (500 * 1048576)/*"ought to be enough for anybody"*/) { qCCritical(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" << sizeLimit << ")"; return; } if (sizeLimit == m_undoMaxLimitSizeLimit) { return; } m_undoMaxLimitSizeLimit = sizeLimit; trimCommandListsUpdateActions (); } // public void kpCommandHistoryBase::readConfig () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::readConfig()"; +#endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupUndoRedo); setUndoMinLimit (cfg.readEntry (kpSettingUndoMinLimit, undoMinLimit ())); setUndoMaxLimit (cfg.readEntry (kpSettingUndoMaxLimit, undoMaxLimit ())); setUndoMaxLimitSizeLimit ( cfg.readEntry (kpSettingUndoMaxLimitSizeLimit, undoMaxLimitSizeLimit ())); trimCommandListsUpdateActions (); } // public void kpCommandHistoryBase::writeConfig () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::writeConfig()"; +#endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupUndoRedo); cfg.writeEntry (kpSettingUndoMinLimit, undoMinLimit ()); cfg.writeEntry (kpSettingUndoMaxLimit, undoMaxLimit ()); cfg.writeEntry ( kpSettingUndoMaxLimitSizeLimit, undoMaxLimitSizeLimit ()); cfg.sync (); } // public void kpCommandHistoryBase::addCommand (kpCommand *command, bool execute) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::addCommand(" << command - << ",execute=" << execute << ")"; + << ",execute=" << execute << ")" +#endif if (execute) { command->execute (); } m_undoCommandList.push_front (command); ::ClearPointerList (&m_redoCommandList); +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#endif if (m_documentRestoredPosition != INT_MAX) { if (m_documentRestoredPosition > 0) { m_documentRestoredPosition = INT_MAX; } else { m_documentRestoredPosition--; } + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition; + #endif } trimCommandListsUpdateActions (); } // public void kpCommandHistoryBase::clear () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::clear()"; +#endif ::ClearPointerList (&m_undoCommandList); ::ClearPointerList (&m_redoCommandList); m_documentRestoredPosition = 0; updateActions (); } //--------------------------------------------------------------------- // protected slot void kpCommandHistoryBase::undoInternal () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::undoInternal()"; +#endif kpCommand *undoCommand = nextUndoCommand (); if (!undoCommand) { return; } undoCommand->unexecute (); m_undoCommandList.erase (m_undoCommandList.begin ()); m_redoCommandList.push_front (undoCommand); - +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#endif if (m_documentRestoredPosition != INT_MAX) { m_documentRestoredPosition++; if (m_documentRestoredPosition == 0) emit documentRestored (); + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition; + #endif } } //--------------------------------------------------------------------- // protected slot void kpCommandHistoryBase::redoInternal () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::redoInternal()"; +#endif kpCommand *redoCommand = nextRedoCommand (); if (!redoCommand) { return; } redoCommand->execute (); m_redoCommandList.erase (m_redoCommandList.begin ()); m_undoCommandList.push_front (redoCommand); +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#endif if (m_documentRestoredPosition != INT_MAX) { m_documentRestoredPosition--; if (m_documentRestoredPosition == 0) { emit documentRestored (); } + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition; + #endif } } //--------------------------------------------------------------------- // public slot virtual void kpCommandHistoryBase::undo () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::undo()"; +#endif undoInternal (); trimCommandListsUpdateActions (); } //--------------------------------------------------------------------- // public slot virtual void kpCommandHistoryBase::redo () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::redo()"; +#endif redoInternal (); trimCommandListsUpdateActions (); } //--------------------------------------------------------------------- // public slot virtual void kpCommandHistoryBase::undoUpToNumber (QAction *which) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::undoUpToNumber(" << which << ")"; +#endif for (int i = 0; i <= which->data().toInt() && !m_undoCommandList.isEmpty (); i++) { undoInternal (); } trimCommandListsUpdateActions (); } // public slot virtual void kpCommandHistoryBase::redoUpToNumber (QAction *which) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::redoUpToNumber(" << which << ")"; +#endif for (int i = 0; i <= which->data().toInt() && !m_redoCommandList.isEmpty (); i++) { redoInternal (); } trimCommandListsUpdateActions (); } // protected QString kpCommandHistoryBase::undoActionText () const { kpCommand *undoCommand = nextUndoCommand (); return (undoCommand) ? i18n ("&Undo: %1", undoCommand->name ()) : i18n ("&Undo"); } // protected QString kpCommandHistoryBase::redoActionText () const { kpCommand *redoCommand = nextRedoCommand (); return (redoCommand) ? i18n ("&Redo: %1", redoCommand->name ()) : i18n ("&Redo"); } // protected QString kpCommandHistoryBase::undoActionToolTip () const { kpCommand *undoCommand = nextUndoCommand (); return (undoCommand) ? i18n ("Undo: %1", undoCommand->name ()) : i18n ("Undo"); } // protected QString kpCommandHistoryBase::redoActionToolTip () const { kpCommand *redoCommand = nextRedoCommand (); return (redoCommand) ? i18n ("Redo: %1", redoCommand->name ()) : i18n ("Redo"); } // protected void kpCommandHistoryBase::trimCommandListsUpdateActions () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::trimCommandListsUpdateActions()"; +#endif trimCommandLists (); updateActions (); } // protected void kpCommandHistoryBase::trimCommandList (QLinkedList *commandList) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::trimCommandList()"; QTime timer; timer.start (); +#endif if (!commandList) { qCCritical(kpLogCommands) << "kpCommandHistoryBase::trimCommandList() passed 0 commandList"; return; } +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tsize=" << commandList->size () << " undoMinLimit=" << m_undoMinLimit << " undoMaxLimit=" << m_undoMaxLimit << " undoMaxLimitSizeLimit=" << m_undoMaxLimitSizeLimit; +#endif if (static_cast (commandList->size ()) <= m_undoMinLimit) { + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\t\tsize under undoMinLimit - done"; + #endif return; } + +#if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "\tsize over undoMinLimit - iterating thru cmds:"; +#endif QLinkedList ::iterator it = commandList->begin (); int upto = 0; kpCommandSize::SizeType sizeSoFar = 0; while (it != commandList->end ()) { bool advanceIt = true; if (sizeSoFar <= m_undoMaxLimitSizeLimit) { sizeSoFar += (*it)->size (); } + #if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "\t\t" << upto << ":" << " name='" << (*it)->name () << "' size=" << (*it)->size () << " sizeSoFar=" << sizeSoFar; + #endif if (upto >= m_undoMinLimit) { if (upto >= m_undoMaxLimit || sizeSoFar > m_undoMaxLimitSizeLimit) { #if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "\t\t\tkill"; #endif delete (*it); it = m_undoCommandList.erase (it); advanceIt = false; } } if (advanceIt) { it++; } upto++; } +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\ttook " << timer.elapsed () << "ms"; +#endif } // protected void kpCommandHistoryBase::trimCommandLists () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::trimCommandLists()"; +#endif trimCommandList (&m_undoCommandList); trimCommandList (&m_redoCommandList); - qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition +#endif if (m_documentRestoredPosition != INT_MAX) { + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\t\tundoCmdList.size=" << m_undoCommandList.size () << " redoCmdList.size=" << m_redoCommandList.size (); + #endif if (m_documentRestoredPosition > static_cast (m_redoCommandList.size ()) || -m_documentRestoredPosition > static_cast (m_undoCommandList.size ())) { + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\t\t\tinvalidate documentRestoredPosition"; + #endif m_documentRestoredPosition = INT_MAX; } } } static void populatePopupMenu (QMenu *popupMenu, const QString &undoOrRedo, const QLinkedList &commandList) { if (!popupMenu) { return; } popupMenu->clear (); QLinkedList ::const_iterator it = commandList.begin (); int i = 0; while (i < 10 && it != commandList.end ()) { QAction *action = new QAction(i18n ("%1: %2", undoOrRedo, (*it)->name ()), popupMenu); action->setData(i); popupMenu->addAction (action); i++; it++; } if (it != commandList.end ()) { // TODO: maybe have a scrollview show all the items instead, like KOffice in KDE 3 // LOCOMPAT: should be centered text. popupMenu->addSection (i18np ("%1 more item", "%1 more items", commandList.size () - i)); } } // protected void kpCommandHistoryBase::updateActions () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::updateActions()"; +#endif m_actionUndo->setEnabled (static_cast (nextUndoCommand ())); // Don't want to keep changing toolbar text. // TODO: As a bad side-effect, the menu doesn't have "Undo: " // anymore. In any case, the KDE4 KToolBarPopupAction // sucks in menus as it forces the clicking of a submenu. IMO, // there should be no submenu in the menu. //m_actionUndo->setText (undoActionText ()); // But in icon mode, a tooltip with context is useful. m_actionUndo->setToolTip (undoActionToolTip ()); +#if DEBUG_KP_COMMAND_HISTORY QTime timer; timer.start (); +#endif populatePopupMenu (m_actionUndo->menu (), i18n ("Undo"), m_undoCommandList); +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tpopuplatePopupMenu undo=" << timer.elapsed () << "ms"; +#endif m_actionRedo->setEnabled (static_cast (nextRedoCommand ())); // Don't want to keep changing toolbar text. // TODO: As a bad side-effect, the menu doesn't have "Undo: " // anymore. In any case, the KDE4 KToolBarPopupAction // sucks in menus as it forces the clicking of a submenu. IMO, // there should be no submenu in the menu. //m_actionRedo->setText (redoActionText ()); // But in icon mode, a tooltip with context is useful. m_actionRedo->setToolTip (redoActionToolTip ()); +#if DEBUG_KP_COMMAND_HISTORY timer.restart (); +#endif populatePopupMenu (m_actionRedo->menu (), i18n ("Redo"), m_redoCommandList); +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tpopuplatePopupMenu redo=" << timer.elapsed () << "ms"; +#endif } // public kpCommand *kpCommandHistoryBase::nextUndoCommand () const { if (m_undoCommandList.isEmpty ()) { return nullptr; } return m_undoCommandList.first (); } // public kpCommand *kpCommandHistoryBase::nextRedoCommand () const { if (m_redoCommandList.isEmpty ()) { return nullptr; } return m_redoCommandList.first (); } // public void kpCommandHistoryBase::setNextUndoCommand (kpCommand *command) { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::setNextUndoCommand("<< command << ")"; +#endif if (m_undoCommandList.isEmpty ()) { return; } delete *m_undoCommandList.begin (); *m_undoCommandList.begin () = command; trimCommandListsUpdateActions (); } // public slot virtual void kpCommandHistoryBase::documentSaved () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpCommandHistoryBase::documentSaved()"; +#endif m_documentRestoredPosition = 0; } + + diff --git a/commands/kpCommandSize.cpp b/commands/kpCommandSize.cpp index 4a1bc542..74d7ca21 100644 --- a/commands/kpCommandSize.cpp +++ b/commands/kpCommandSize.cpp @@ -1,145 +1,152 @@ /* Copyright (c) 2003-2007 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_COMMAND_SIZE 0 #include "commands/kpCommandSize.h" #include "layers/selections/kpAbstractSelection.h" -#include "kpLogCategories.h" #include #include #include -#include + // public static kpCommandSize::SizeType kpCommandSize::PixmapSize (const QImage &image) { return kpCommandSize::PixmapSize (image.width (), image.height (), image.depth ()); } // public static kpCommandSize::SizeType kpCommandSize::PixmapSize (const QImage *image) { return (image ? kpCommandSize::PixmapSize (*image) : 0); } // public static kpCommandSize::SizeType kpCommandSize::PixmapSize (int width, int height, int depth) { // handle 15bpp int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); kpCommandSize::SizeType ret = static_cast (width) * height * roundedDepth / 8; +#if DEBUG_KP_COMMAND_SIZE && 0 qCDebug(kpLogCommands) << "kpCommandSize::PixmapSize() w=" << width << " h=" << height << " d=" << depth << " roundedDepth=" << roundedDepth << " ret=" << ret; +#endif return ret; } // public static kpCommandSize::SizeType kpCommandSize::QImageSize (const QImage &image) { return kpCommandSize::QImageSize (image.width (), image.height (), image.depth ()); } // public static kpCommandSize::SizeType kpCommandSize::QImageSize (const QImage *image) { return (image ? kpCommandSize::QImageSize (*image) : 0); } // public static kpCommandSize::SizeType kpCommandSize::QImageSize (int width, int height, int depth) { // handle 15bpp int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); kpCommandSize::SizeType ret = static_cast (width) * height * roundedDepth / 8; +#if DEBUG_KP_COMMAND_SIZE && 0 qCDebug(kpLogCommands) << "kpCommandSize::QImageSize() w=" << width << " h=" << height << " d=" << depth << " roundedDepth=" << roundedDepth << " ret=" << ret; +#endif return ret; } // public static kpCommandSize::SizeType kpCommandSize::ImageSize (const kpImage &image) { return kpCommandSize::PixmapSize (image); } // public static kpCommandSize::SizeType kpCommandSize::ImageSize (const kpImage *image) { return kpCommandSize::PixmapSize (image); } // public static kpCommandSize::SizeType kpCommandSize::SelectionSize (const kpAbstractSelection &sel) { return sel.size (); } // public static kpCommandSize::SizeType kpCommandSize::SelectionSize (const kpAbstractSelection *sel) { return (sel ? sel->size () : 0); } // public static kpCommandSize::SizeType kpCommandSize::StringSize (const QString &string) { +#if DEBUG_KP_COMMAND_SIZE && 1 qCDebug(kpLogCommands) << "kpCommandSize::StringSize(" << string << ")" << " len=" << string.length () << " sizeof(QChar)=" << sizeof (QChar); +#endif return static_cast (static_cast (string.length ()) * sizeof (QChar)); } // public static kpCommandSize::SizeType kpCommandSize::PolygonSize (const QPolygon &points) { +#if DEBUG_KP_COMMAND_SIZE && 1 qCDebug(kpLogCommands) << "kpCommandSize::PolygonSize() points.size=" << points.size () << " sizeof(QPoint)=" << sizeof (QPoint); +#endif return static_cast (static_cast (points.size ()) * sizeof (QPoint)); } diff --git a/commands/kpMacroCommand.cpp b/commands/kpMacroCommand.cpp index 26294dbc..a1030396 100644 --- a/commands/kpMacroCommand.cpp +++ b/commands/kpMacroCommand.cpp @@ -1,132 +1,147 @@ /* Copyright (c) 2003-2007 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_COMMAND_HISTORY 0 #include "commands/kpMacroCommand.h" #include "views/manager/kpViewManager.h" #include #include -#include "kpLogCategories.h" //--------------------------------------------------------------------- struct kpMacroCommandPrivate { }; kpMacroCommand::kpMacroCommand (const QString &name, kpCommandEnvironment *environ) : kpNamedCommand (name, environ), d (new kpMacroCommandPrivate ()) { } //--------------------------------------------------------------------- kpMacroCommand::~kpMacroCommand () { qDeleteAll (m_commandList.begin (), m_commandList.end ()); delete d; } //--------------------------------------------------------------------- // public virtual [base kpCommand] kpCommandSize::SizeType kpMacroCommand::size () const { +#if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "kpMacroCommand::size()"; +#endif SizeType s = 0; +#if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "\tcalculating:"; - for (auto *cmd : m_commandList) +#endif + foreach (kpCommand *cmd, m_commandList) { + #if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "\t\tcurrentSize=" << s << " + " << cmd->name () << ".size=" << cmd->size (); + #endif s += cmd->size (); } +#if DEBUG_KP_COMMAND_HISTORY && 0 qCDebug(kpLogCommands) << "\treturning " << s; +#endif return s; } //--------------------------------------------------------------------- // public virtual [base kpCommand] void kpMacroCommand::execute () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpMacroCommand::execute()"; +#endif viewManager()->setQueueUpdates(); for (QLinkedList ::const_iterator it = m_commandList.begin (); it != m_commandList.end (); ++it) { + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\texecuting " << (*it)->name (); + #endif (*it)->execute (); } viewManager()->restoreQueueUpdates(); } //--------------------------------------------------------------------- // public virtual [base kpCommand] void kpMacroCommand::unexecute () { +#if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "kpMacroCommand::unexecute()"; +#endif viewManager()->setQueueUpdates(); QLinkedList ::const_iterator it = m_commandList.end (); it--; while (it != m_commandList.end ()) { + #if DEBUG_KP_COMMAND_HISTORY qCDebug(kpLogCommands) << "\tunexecuting " << (*it)->name (); + #endif (*it)->unexecute (); it--; } viewManager()->restoreQueueUpdates(); } //--------------------------------------------------------------------- // public void kpMacroCommand::addCommand (kpCommand *command) { m_commandList.push_back (command); } //--------------------------------------------------------------------- diff --git a/commands/tools/flow/kpToolFlowCommand.cpp b/commands/tools/flow/kpToolFlowCommand.cpp index 9548d159..79827356 100644 --- a/commands/tools/flow/kpToolFlowCommand.cpp +++ b/commands/tools/flow/kpToolFlowCommand.cpp @@ -1,137 +1,140 @@ /* Copyright (c) 2003-2007 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_TOOL_FLOW_COMMAND 0 #include "kpToolFlowCommand.h" #include "document/kpDocument.h" #include "imagelib/kpImage.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "views/manager/kpViewManager.h" -#include "kpLogCategories.h" #include struct kpToolFlowCommandPrivate { kpImage image; QRect boundingRect; }; kpToolFlowCommand::kpToolFlowCommand (const QString &name, kpCommandEnvironment *environ) : kpNamedCommand (name, environ), d (new kpToolFlowCommandPrivate ()) { d->image = document ()->image (); } kpToolFlowCommand::~kpToolFlowCommand () { delete d; } // public virtual [base kpCommand] kpCommandSize::SizeType kpToolFlowCommand::size () const { return ImageSize (d->image); } // public virtual [base kpCommand] void kpToolFlowCommand::execute () { swapOldAndNew (); } // public virtual [base kpCommand] void kpToolFlowCommand::unexecute () { swapOldAndNew (); } // private void kpToolFlowCommand::swapOldAndNew () { if (d->boundingRect.isValid ()) { const kpImage oldImage = document ()->getImageAt (d->boundingRect); document ()->setImageAt (d->image, d->boundingRect.topLeft ()); d->image = oldImage; } } // public void kpToolFlowCommand::updateBoundingRect (const QPoint &point) { updateBoundingRect (QRect (point, point)); } // public void kpToolFlowCommand::updateBoundingRect (const QRect &rect) { +#if DEBUG_KP_TOOL_FLOW_COMMAND & 0 qCDebug(kpLogCommands) << "kpToolFlowCommand::updateBoundingRect() existing=" << d->boundingRect << " plus=" << rect; +#endif d->boundingRect = d->boundingRect.united (rect); +#if DEBUG_KP_TOOL_FLOW_COMMAND & 0 qCDebug(kpLogCommands) << "\tresult=" << d->boundingRect; +#endif } // public void kpToolFlowCommand::finalize () { if (d->boundingRect.isValid ()) { // Store only the needed part of doc image. d->image = kpTool::neededPixmap (d->image, d->boundingRect); } else { d->image = kpImage (); } } // public void kpToolFlowCommand::cancel () { if (d->boundingRect.isValid ()) { viewManager ()->setFastUpdates (); document ()->setImageAt (d->image, d->boundingRect.topLeft ()); viewManager ()->restoreFastUpdates (); } } diff --git a/commands/tools/kpToolFloodFillCommand.cpp b/commands/tools/kpToolFloodFillCommand.cpp index da1ccaf4..bcd54fb4 100644 --- a/commands/tools/kpToolFloodFillCommand.cpp +++ b/commands/tools/kpToolFloodFillCommand.cpp @@ -1,163 +1,169 @@ /* Copyright (c) 2003-2007 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_TOOL_FLOOD_FILL 0 #include "kpToolFloodFillCommand.h" #include "imagelib/kpColor.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "imagelib/kpImage.h" #include "kpLogCategories.h" #include #include //--------------------------------------------------------------------- struct kpToolFloodFillCommandPrivate { kpImage oldImage; bool fillEntireImage{false}; }; //--------------------------------------------------------------------- kpToolFloodFillCommand::kpToolFloodFillCommand (int x, int y, const kpColor &color, int processedColorSimilarity, kpCommandEnvironment *environ) : kpCommand (environ), kpFloodFill (document ()->imagePointer (), x, y, color, processedColorSimilarity), d (new kpToolFloodFillCommandPrivate ()) { d->fillEntireImage = false; } //--------------------------------------------------------------------- kpToolFloodFillCommand::~kpToolFloodFillCommand () { delete d; } //--------------------------------------------------------------------- // public virtual [base kpCommand] QString kpToolFloodFillCommand::name () const { return i18n ("Flood Fill"); } //--------------------------------------------------------------------- // public virtual [base kpCommand] kpCommandSize::SizeType kpToolFloodFillCommand::size () const { return kpFloodFill::size () + ImageSize (d->oldImage); } //--------------------------------------------------------------------- // public void kpToolFloodFillCommand::setFillEntireImage (bool yes) { d->fillEntireImage = yes; } //--------------------------------------------------------------------- // protected virtual [base kpCommand] void kpToolFloodFillCommand::execute () { +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 qCDebug(kpLogCommands) << "kpToolFloodFillCommand::execute() fillEntireImage=" << d->fillEntireImage; +#endif kpDocument *doc = document (); Q_ASSERT (doc); if (d->fillEntireImage) { doc->fill (kpFloodFill::color ()); } else { QRect rect = kpFloodFill::boundingRect (); if (rect.isValid ()) { QApplication::setOverrideCursor (Qt::WaitCursor); { d->oldImage = doc->getImageAt (rect); kpFloodFill::fill (); doc->slotContentsChanged (rect); } QApplication::restoreOverrideCursor (); } else { + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 qCDebug(kpLogCommands) << "\tinvalid boundingRect - must be NOP case"; + #endif } } } //--------------------------------------------------------------------- // protected virtual [base kpCommand] void kpToolFloodFillCommand::unexecute () { +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 qCDebug(kpLogCommands) << "kpToolFloodFillCommand::unexecute() fillEntireImage=" << d->fillEntireImage; +#endif kpDocument *doc = document (); Q_ASSERT (doc); if (d->fillEntireImage) { doc->fill (kpFloodFill::colorToChange ()); } else { QRect rect = kpFloodFill::boundingRect (); if (rect.isValid ()) { doc->setImageAt (d->oldImage, rect.topLeft ()); d->oldImage = kpImage (); doc->slotContentsChanged (rect); } } } //--------------------------------------------------------------------- diff --git a/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp b/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp index 3f6cffa1..2ef45955 100644 --- a/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp +++ b/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp @@ -1,92 +1,96 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "kpToolImageSelectionTransparencyCommand.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "generic/kpSetOverrideCursorSaver.h" #include "kpLogCategories.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include //-------------------------------------------------------------------------------- kpToolImageSelectionTransparencyCommand::kpToolImageSelectionTransparencyCommand ( const QString &name, const kpImageSelectionTransparency &st, const kpImageSelectionTransparency &oldST, kpCommandEnvironment *environ) : kpNamedCommand (name, environ), m_st (st), m_oldST (oldST) { } kpToolImageSelectionTransparencyCommand::~kpToolImageSelectionTransparencyCommand () = default; // public virtual [base kpCommand] kpCommandSize::SizeType kpToolImageSelectionTransparencyCommand::size () const { return 0; } // public virtual [base kpCommand] void kpToolImageSelectionTransparencyCommand::execute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolImageSelectionTransparencyCommand::execute()"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); environ ()->setImageSelectionTransparency (m_st, true/*force colour change*/); if (imageSelection ()) { imageSelection ()->setTransparency (m_st); } } // public virtual [base kpCommand] void kpToolImageSelectionTransparencyCommand::unexecute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolImageSelectionTransparencyCommand::unexecute()"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); environ ()->setImageSelectionTransparency (m_oldST, true/*force colour change*/); if (imageSelection ()) { imageSelection ()->setTransparency (m_oldST); } } diff --git a/commands/tools/selection/kpToolSelectionCreateCommand.cpp b/commands/tools/selection/kpToolSelectionCreateCommand.cpp index 03c8c13c..241fd951 100644 --- a/commands/tools/selection/kpToolSelectionCreateCommand.cpp +++ b/commands/tools/selection/kpToolSelectionCreateCommand.cpp @@ -1,156 +1,161 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "commands/tools/selection/kpToolSelectionCreateCommand.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" #include "kpLogCategories.h" #include #include #include #include #include #include #include #include #include kpToolSelectionCreateCommand::kpToolSelectionCreateCommand (const QString &name, const kpAbstractSelection &fromSelection, kpCommandEnvironment *environ) : kpNamedCommand (name, environ), m_fromSelection (nullptr), m_textRow (0), m_textCol (0) { setFromSelection (fromSelection); } kpToolSelectionCreateCommand::~kpToolSelectionCreateCommand () { delete m_fromSelection; } // public virtual [base kpCommand] kpCommandSize::SizeType kpToolSelectionCreateCommand::size () const { return SelectionSize (m_fromSelection); } // public const kpAbstractSelection *kpToolSelectionCreateCommand::fromSelection () const { return m_fromSelection; } // public void kpToolSelectionCreateCommand::setFromSelection (const kpAbstractSelection &fromSelection) { delete m_fromSelection; m_fromSelection = fromSelection.clone (); } // public virtual [base kpCommand] void kpToolSelectionCreateCommand::execute () { +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionCreateCommand::execute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); if (m_fromSelection) { + #if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "\tusing fromSelection"; - qCDebug(kpLogCommands) << "\t\thave sel=" << doc->selection (); - auto *imageSel = dynamic_cast (m_fromSelection); - auto *textSel = dynamic_cast (m_fromSelection); - + qCDebug(kpLogCommands) << "\t\thave sel=" << doc->selection () << endl; + #endif + kpAbstractImageSelection *imageSel = + dynamic_cast (m_fromSelection); + kpTextSelection *textSel = + dynamic_cast (m_fromSelection); if (imageSel) { if (imageSel->transparency () != environ ()->imageSelectionTransparency ()) { environ ()->setImageSelectionTransparency (imageSel->transparency ()); } } else if (textSel) { if (textSel->textStyle () != environ ()->textStyle ()) { environ ()->setTextStyle (textSel->textStyle ()); } } else { Q_ASSERT (!"Unknown selection type"); } viewManager ()->setTextCursorPosition (m_textRow, m_textCol); doc->setSelection (*m_fromSelection); environ ()->somethingBelowTheCursorChanged (); } } // public virtual [base kpCommand] void kpToolSelectionCreateCommand::unexecute () { kpDocument *doc = document (); Q_ASSERT (doc); if (!doc->selection ()) { // Was just a border that got deselected? if (m_fromSelection && !m_fromSelection->hasContent ()) { return; } Q_ASSERT (!"kpToolSelectionCreateCommand::unexecute() without sel region"); return; } m_textRow = viewManager ()->textCursorRow (); m_textCol = viewManager ()->textCursorCol (); doc->selectionDelete (); environ ()->somethingBelowTheCursorChanged (); } diff --git a/commands/tools/selection/kpToolSelectionDestroyCommand.cpp b/commands/tools/selection/kpToolSelectionDestroyCommand.cpp index ef595ed7..2be17d86 100644 --- a/commands/tools/selection/kpToolSelectionDestroyCommand.cpp +++ b/commands/tools/selection/kpToolSelectionDestroyCommand.cpp @@ -1,165 +1,176 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "kpToolSelectionDestroyCommand.h" #include "kpLogCategories.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "views/manager/kpViewManager.h" //--------------------------------------------------------------------- kpToolSelectionDestroyCommand::kpToolSelectionDestroyCommand (const QString &name, bool pushOntoDocument, kpCommandEnvironment *environ) : kpNamedCommand (name, environ), m_pushOntoDocument (pushOntoDocument), m_oldSelectionPtr (nullptr), m_textRow(0), m_textCol(0) { } //--------------------------------------------------------------------- kpToolSelectionDestroyCommand::~kpToolSelectionDestroyCommand () { delete m_oldSelectionPtr; } //--------------------------------------------------------------------- // public virtual [base kpCommand] kpCommandSize::SizeType kpToolSelectionDestroyCommand::size () const { return ImageSize (m_oldDocImage) + SelectionSize (m_oldSelectionPtr); } //--------------------------------------------------------------------- // public virtual [base kpCommand] void kpToolSelectionDestroyCommand::execute () { +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionDestroyCommand::execute () CALLED"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); Q_ASSERT (doc->selection ()); m_textRow = viewManager ()->textCursorRow (); m_textCol = viewManager ()->textCursorCol (); Q_ASSERT (!m_oldSelectionPtr); m_oldSelectionPtr = doc->selection ()->clone (); if (m_pushOntoDocument) { m_oldDocImage = doc->getImageAt (doc->selection ()->boundingRect ()); doc->selectionPushOntoDocument (); } else { doc->selectionDelete (); } environ ()->somethingBelowTheCursorChanged (); } //--------------------------------------------------------------------- // public virtual [base kpCommand] void kpToolSelectionDestroyCommand::unexecute () { +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionDestroyCommand::unexecute () CALLED"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); if (doc->selection ()) { // not error because it's possible that the user dragged out a new // region (without pulling image), and then CTRL+Z + #if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionDestroyCommand::unexecute() already has sel region"; + #endif if (doc->selection ()->hasContent ()) { Q_ASSERT (!"kpToolSelectionDestroyCommand::unexecute() already has sel content"); return; } } Q_ASSERT (m_oldSelectionPtr); if (m_pushOntoDocument) { + #if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "\tunpush oldDocImage onto doc first"; + #endif doc->setImageAt (m_oldDocImage, m_oldSelectionPtr->topLeft ()); } +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "\tsetting selection to: rect=" << m_oldSelectionPtr->boundingRect () << " hasContent=" << m_oldSelectionPtr->hasContent (); - auto *imageSel = dynamic_cast (m_oldSelectionPtr); - auto *textSel = dynamic_cast (m_oldSelectionPtr); - +#endif + kpAbstractImageSelection *imageSel = + dynamic_cast (m_oldSelectionPtr); + kpTextSelection *textSel = + dynamic_cast (m_oldSelectionPtr); if (imageSel) { if (imageSel->transparency () != environ ()->imageSelectionTransparency ()) { environ ()->setImageSelectionTransparency (imageSel->transparency ()); } if (dynamic_cast (doc->selection())) { doc->selectionPushOntoDocument(); } } else if (textSel) { if (textSel->textStyle () != environ ()->textStyle ()) { environ ()->setTextStyle (textSel->textStyle ()); } if (dynamic_cast (doc->selection())) { doc->selectionPushOntoDocument(); } } else { Q_ASSERT (!"Unknown selection type"); } viewManager ()->setTextCursorPosition (m_textRow, m_textCol); doc->setSelection (*m_oldSelectionPtr); environ ()->somethingBelowTheCursorChanged (); delete m_oldSelectionPtr; m_oldSelectionPtr = nullptr; } diff --git a/commands/tools/selection/kpToolSelectionMoveCommand.cpp b/commands/tools/selection/kpToolSelectionMoveCommand.cpp index 81abb2ab..00904f36 100644 --- a/commands/tools/selection/kpToolSelectionMoveCommand.cpp +++ b/commands/tools/selection/kpToolSelectionMoveCommand.cpp @@ -1,208 +1,219 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "kpToolSelectionMoveCommand.h" #include "layers/selections/kpAbstractSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "tools/kpTool.h" #include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" #include "views/manager/kpViewManager.h" #include "kpLogCategories.h" //-------------------------------------------------------------------------------- kpToolSelectionMoveCommand::kpToolSelectionMoveCommand (const QString &name, kpCommandEnvironment *environ) : kpNamedCommand (name, environ) { kpDocument *doc = document (); Q_ASSERT (doc); Q_ASSERT (doc->selection ()); m_startPoint = m_endPoint = doc->selection ()->topLeft (); } kpToolSelectionMoveCommand::~kpToolSelectionMoveCommand () = default; // public kpAbstractSelection *kpToolSelectionMoveCommand::originalSelectionClone () const { kpDocument *doc = document (); Q_ASSERT (doc); Q_ASSERT (doc->selection ()); kpAbstractSelection *selection = doc->selection ()->clone (); selection->moveTo (m_startPoint); return selection; } // public virtual [base kpComand] kpCommandSize::SizeType kpToolSelectionMoveCommand::size () const { return ImageSize (m_oldDocumentImage) + PolygonSize (m_copyOntoDocumentPoints); } // public virtual [base kpCommand] void kpToolSelectionMoveCommand::execute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::execute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); kpAbstractSelection *sel = doc->selection (); // Must have content before it can be moved. Q_ASSERT (sel && sel->hasContent ()); kpViewManager *vm = viewManager (); Q_ASSERT (vm); vm->setQueueUpdates (); { for (const auto &p : m_copyOntoDocumentPoints) { sel->moveTo (p); doc->selectionCopyOntoDocument (); } sel->moveTo (m_endPoint); environ ()->somethingBelowTheCursorChanged (); } vm->restoreQueueUpdates (); } // public virtual [base kpCommand] void kpToolSelectionMoveCommand::unexecute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::unexecute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); kpAbstractSelection *sel = doc->selection (); // Must have content before it can be un-moved. Q_ASSERT (sel && sel->hasContent ()); kpViewManager *vm = viewManager (); Q_ASSERT (vm); vm->setQueueUpdates (); if (!m_oldDocumentImage.isNull ()) { doc->setImageAt (m_oldDocumentImage, m_documentBoundingRect.topLeft ()); } + +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "\tmove to startPoint=" << m_startPoint; +#endif sel->moveTo (m_startPoint); environ ()->somethingBelowTheCursorChanged (); vm->restoreQueueUpdates (); } // public void kpToolSelectionMoveCommand::moveTo (const QPoint &point, bool moveLater) { +#if DEBUG_KP_TOOL_SELECTION && 0 qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::moveTo" << point << " moveLater=" << moveLater; +#endif if (!moveLater) { kpDocument *doc = document (); Q_ASSERT (doc); kpAbstractSelection *sel = doc->selection (); // Must have content before it can be moved. Q_ASSERT (sel && sel->hasContent ()); if (point == sel->topLeft ()) { return; } sel->moveTo (point); } m_endPoint = point; } // public void kpToolSelectionMoveCommand::moveTo (int x, int y, bool moveLater) { moveTo (QPoint (x, y), moveLater); } // public void kpToolSelectionMoveCommand::copyOntoDocument () { +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::copyOntoDocument()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); kpAbstractSelection *sel = doc->selection (); // Must have content before we allow it be stamped onto the document, // to be consistent with the requirement on other selection operations. Q_ASSERT (sel && sel->hasContent ()); if (m_oldDocumentImage.isNull ()) { m_oldDocumentImage = doc->image (); } QRect selBoundingRect = sel->boundingRect (); m_documentBoundingRect = m_documentBoundingRect.united (selBoundingRect); doc->selectionCopyOntoDocument (); m_copyOntoDocumentPoints.putPoints (m_copyOntoDocumentPoints.count (), 1, selBoundingRect.x (), selBoundingRect.y ()); } // public void kpToolSelectionMoveCommand::finalize () { if (!m_oldDocumentImage.isNull () && !m_documentBoundingRect.isNull ()) { m_oldDocumentImage = kpTool::neededPixmap (m_oldDocumentImage, m_documentBoundingRect); } } diff --git a/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp b/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp index 18cf8fb1..bd486443 100644 --- a/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp +++ b/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp @@ -1,134 +1,140 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "kpToolSelectionPullFromDocumentCommand.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "document/kpDocument.h" #include "views/manager/kpViewManager.h" #include "kpLogCategories.h" kpToolSelectionPullFromDocumentCommand::kpToolSelectionPullFromDocumentCommand ( const kpAbstractImageSelection &originalSelBorder, const kpColor &backgroundColor, const QString &name, kpCommandEnvironment *environ) : kpAbstractSelectionContentCommand (originalSelBorder, name, environ), m_backgroundColor (backgroundColor) { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolSelectionPullFromDocumentCommand::() environ=" << environ; +#endif } kpToolSelectionPullFromDocumentCommand::~kpToolSelectionPullFromDocumentCommand () = default; // public virtual [base kpCommand] void kpToolSelectionPullFromDocumentCommand::execute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolSelectionPullFromDocumentCommand::execute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); kpViewManager *vm = viewManager (); Q_ASSERT (vm); vm->setQueueUpdates (); { // // Recreate border // // The previously executed command is required to have been a // kpToolSelectionCreateCommand, which must have been given an image // selection with no content. // // However, there is a tricky case. Suppose we are called for the first // time, where the above precondition holds. We would add content // to the selection as expected. But the user then undoes (CTRL+Z) the // operation, calling unexecute(). There is now no content again. // Since selection is only a border, the user can freely deselect it // and/or select another region without changing the command history // or document modified state. Therefore, if they now call us again // by redoing (CTRL+Shift+Z), there is potentially no selection at all // or it is at an arbitrary location. // // This assertion covers all 3 possibilities: // // 1. First call: image selection with no content // 2. Later calls: // a) no image selection (due to deselection) // b) image selection with no content, at an arbitrary location Q_ASSERT (!imageSelection () || !imageSelection ()->hasContent ()); const auto *originalImageSel = dynamic_cast (originalSelection ()); if (originalImageSel->transparency () != environ ()->imageSelectionTransparency ()) { environ ()->setImageSelectionTransparency (originalImageSel->transparency ()); } doc->setSelection (*originalSelection ()); // // Add content // doc->imageSelectionPullFromDocument (m_backgroundColor); } vm->restoreQueueUpdates (); } // public virtual [base kpCommand] void kpToolSelectionPullFromDocumentCommand::unexecute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolSelectionPullFromDocumentCommand::unexecute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); // Must have selection image content. Q_ASSERT (doc->imageSelection () && doc->imageSelection ()->hasContent ()); // We can have faith that this is the state of the selection after // execute(), rather than after the user tried to throw us off by // simply selecting another region as to do that, a destroy command // must have been used. doc->selectionCopyOntoDocument (false/*use opaque pixmap*/); doc->imageSelection ()->deleteContent (); } diff --git a/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp b/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp index d0d569eb..da772a1e 100644 --- a/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp +++ b/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp @@ -1,253 +1,257 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "kpToolSelectionResizeScaleCommand.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/image/kpRectangularImageSelection.h" #include "layers/selections/text/kpTextSelection.h" #include "kpLogCategories.h" #include #include #include #include //-------------------------------------------------------------------------------- kpToolSelectionResizeScaleCommand::kpToolSelectionResizeScaleCommand ( kpCommandEnvironment *environ) : kpNamedCommand (environ->textSelection () ? i18n ("Text: Resize Box") : i18n ("Selection: Smooth Scale"), environ), m_smoothScaleTimer (new QTimer (this)) { m_originalSelectionPtr = selection ()->clone (); m_newTopLeft = selection ()->topLeft (); m_newWidth = selection ()->width (); m_newHeight = selection ()->height (); m_smoothScaleTimer->setSingleShot (true); connect (m_smoothScaleTimer, &QTimer::timeout, this, [this]{resizeScaleAndMove(false);}); } kpToolSelectionResizeScaleCommand::~kpToolSelectionResizeScaleCommand () { delete m_originalSelectionPtr; } // public virtual kpCommandSize::SizeType kpToolSelectionResizeScaleCommand::size () const { return SelectionSize (m_originalSelectionPtr); } // public const kpAbstractSelection *kpToolSelectionResizeScaleCommand::originalSelection () const { return m_originalSelectionPtr; } // public QPoint kpToolSelectionResizeScaleCommand::topLeft () const { return m_newTopLeft; } // public void kpToolSelectionResizeScaleCommand::moveTo (const QPoint &point) { if (point == m_newTopLeft) { return; } m_newTopLeft = point; selection ()->moveTo (m_newTopLeft); } // public int kpToolSelectionResizeScaleCommand::width () const { return m_newWidth; } // public int kpToolSelectionResizeScaleCommand::height () const { return m_newHeight; } // public void kpToolSelectionResizeScaleCommand::resize (int width, int height, bool delayed) { if (width == m_newWidth && height == m_newHeight) { return; } m_newWidth = width; m_newHeight = height; resizeScaleAndMove (delayed); } // public void kpToolSelectionResizeScaleCommand::resizeAndMoveTo (int width, int height, const QPoint &point, bool delayed) { if (width == m_newWidth && height == m_newHeight && point == m_newTopLeft) { return; } m_newWidth = width; m_newHeight = height; m_newTopLeft = point; resizeScaleAndMove (delayed); } // protected void kpToolSelectionResizeScaleCommand::killSmoothScaleTimer () { m_smoothScaleTimer->stop (); } // protected void kpToolSelectionResizeScaleCommand::resizeScaleAndMove (bool delayed) { +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionResizeScaleCommand::resizeScaleAndMove(delayed=" << delayed << ")"; +#endif killSmoothScaleTimer (); kpAbstractSelection *newSelPtr = nullptr; if (textSelection ()) { Q_ASSERT (dynamic_cast (m_originalSelectionPtr)); auto *orgTextSel = dynamic_cast (m_originalSelectionPtr); newSelPtr = orgTextSel->resized (m_newWidth, m_newHeight); } else { Q_ASSERT (dynamic_cast (m_originalSelectionPtr)); auto *imageSel = dynamic_cast (m_originalSelectionPtr); newSelPtr = new kpRectangularImageSelection ( QRect (imageSel->x (), imageSel->y (), m_newWidth, m_newHeight), kpPixmapFX::scale (imageSel->baseImage (), m_newWidth, m_newHeight, !delayed/*if not delayed, smooth*/), imageSel->transparency ()); if (delayed) { // Call self (once) with delayed==false in 200ms m_smoothScaleTimer->start (200/*ms*/); } } Q_ASSERT (newSelPtr); newSelPtr->moveTo (m_newTopLeft); document ()->setSelection (*newSelPtr); delete newSelPtr; } // public void kpToolSelectionResizeScaleCommand::finalize () { +#if DEBUG_KP_TOOL_SELECTION qCDebug(kpLogCommands) << "kpToolSelectionResizeScaleCommand::finalize()" << " smoothScaleTimer->isActive=" << m_smoothScaleTimer->isActive (); +#endif // Make sure the selection contains the final image and the timer won't // fire afterwards. if (m_smoothScaleTimer->isActive ()) { resizeScaleAndMove (); Q_ASSERT (!m_smoothScaleTimer->isActive ()); } } // public virtual [base kpToolResizeScaleCommand] void kpToolSelectionResizeScaleCommand::execute () { QApplication::setOverrideCursor (Qt::WaitCursor); killSmoothScaleTimer (); resizeScaleAndMove (); environ ()->somethingBelowTheCursorChanged (); QApplication::restoreOverrideCursor (); } // public virtual [base kpToolResizeScaleCommand] void kpToolSelectionResizeScaleCommand::unexecute () { QApplication::setOverrideCursor (Qt::WaitCursor); killSmoothScaleTimer (); document ()->setSelection (*m_originalSelectionPtr); environ ()->somethingBelowTheCursorChanged (); QApplication::restoreOverrideCursor (); } diff --git a/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp b/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp index 2c593674..1e9a73a1 100644 --- a/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp +++ b/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp @@ -1,92 +1,96 @@ /* Copyright (c) 2003-2007 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_TOOL_TEXT 0 #include "kpToolTextChangeStyleCommand.h" #include "environments/commands/kpCommandEnvironment.h" #include "layers/selections/text/kpTextSelection.h" #include "kpLogCategories.h" kpToolTextChangeStyleCommand::kpToolTextChangeStyleCommand (const QString &name, const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, kpCommandEnvironment *environ) : kpNamedCommand (name, environ), m_newTextStyle (newTextStyle), m_oldTextStyle (oldTextStyle) { } kpToolTextChangeStyleCommand::~kpToolTextChangeStyleCommand () = default; // public virtual [base kpCommand] kpCommandSize::SizeType kpToolTextChangeStyleCommand::size () const { return 0; } // public virtual [base kpCommand] void kpToolTextChangeStyleCommand::execute () { +#if DEBUG_KP_TOOL_TEXT && 1 qCDebug(kpLogCommands) << "kpToolTextChangeStyleCommand::execute()" << " font=" << m_newTextStyle.fontFamily () << " fontSize=" << m_newTextStyle.fontSize () << " isBold=" << m_newTextStyle.isBold () << " isItalic=" << m_newTextStyle.isItalic () << " isUnderline=" << m_newTextStyle.isUnderline () << " isStrikeThru=" << m_newTextStyle.isStrikeThru (); +#endif environ ()->setTextStyle (m_newTextStyle); if (textSelection ()) { textSelection ()->setTextStyle (m_newTextStyle); } } // public virtual [base kpCommand] void kpToolTextChangeStyleCommand::unexecute () { +#if DEBUG_KP_TOOL_TEXT && 1 qCDebug(kpLogCommands) << "kpToolTextChangeStyleCommand::unexecute()" << " font=" << m_newTextStyle.fontFamily () << " fontSize=" << m_newTextStyle.fontSize () << " isBold=" << m_newTextStyle.isBold () << " isItalic=" << m_newTextStyle.isItalic () << " isUnderline=" << m_newTextStyle.isUnderline () << " isStrikeThru=" << m_newTextStyle.isStrikeThru (); +#endif environ ()->setTextStyle (m_oldTextStyle); if (textSelection ()) textSelection ()->setTextStyle (m_oldTextStyle); } diff --git a/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp b/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp index 72cdb07a..7ee0c8fe 100644 --- a/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp +++ b/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp @@ -1,145 +1,151 @@ /* Copyright (c) 2003-2007 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_TOOL_SELECTION 0 #include "kpToolTextGiveContentCommand.h" #include "environments/commands/kpCommandEnvironment.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "views/manager/kpViewManager.h" #include "kpLogCategories.h" kpToolTextGiveContentCommand::kpToolTextGiveContentCommand ( const kpTextSelection &originalSelBorder, const QString &name, kpCommandEnvironment *environ) : kpAbstractSelectionContentCommand (originalSelBorder, name, environ) { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolTextGiveContentCommand::() environ=" - << environ; + << environ +#endif } kpToolTextGiveContentCommand::~kpToolTextGiveContentCommand () = default; // public virtual [base kpCommand] void kpToolTextGiveContentCommand::execute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolTextGiveContentCommand::execute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); kpViewManager *vm = viewManager (); Q_ASSERT (vm); // See API Doc for kpViewManager::textCursorRow() & textCursorCol(). Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); vm->setQueueUpdates (); { // // Recreate border // // The previously executed command is required to have been a // kpToolSelectionCreateCommand, which must have been given a text // selection with no content. // // However, there is a tricky case. Suppose we are called for the first // time, where the above precondition holds. We would add content // to the selection as expected. But the user then undoes (CTRL+Z) the // operation, calling unexecute(). There is now no content again. // Since selection is only a border, the user can freely deselect it // and/or select another region without changing the command history // or document modified state. Therefore, if they now call us again // by redoing (CTRL+Shift+Z), there is potentially no selection at all // or it is at an arbitrary location. // // This assertion covers all 3 possibilities: // // 1. First call: text selection with no content // 2. Later calls: // a) no text selection (due to deselection) // b) text selection with no content, at an arbitrary location Q_ASSERT (!textSelection () || !textSelection ()->hasContent ()); const auto *originalTextSel = dynamic_cast (originalSelection ()); if (originalTextSel->textStyle () != environ ()->textStyle ()) { environ ()->setTextStyle (originalTextSel->textStyle ()); } doc->setSelection (*originalSelection ()); // // Add Content // QList listOfOneEmptyString; listOfOneEmptyString.append (QString ()); textSelection ()->setTextLines (listOfOneEmptyString); } vm->restoreQueueUpdates (); // This should not have changed from the start of the method. Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); } // public virtual [base kpCommand] void kpToolTextGiveContentCommand::unexecute () { +#if DEBUG_KP_TOOL_SELECTION && 1 qCDebug(kpLogCommands) << "kpToolTextGiveContentCommand::unexecute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); // Must have selection text content. Q_ASSERT (doc->textSelection () && doc->textSelection ()->hasContent ()); kpViewManager *vm = viewManager (); Q_ASSERT (vm); // All the commands after us have been unexecuted, so we must be back // to the state we were after our execute(). Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); // We can have faith that this is the state of the selection after // execute(), rather than after the user tried to throw us off by // simply selecting another region as to do that, a destroy command // must have been used. doc->textSelection ()->deleteContent (); // This should not have changed from the start of the method. Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); } diff --git a/cursors/kpCursorLightCross.cpp b/cursors/kpCursorLightCross.cpp index bd954710..915ea0c7 100644 --- a/cursors/kpCursorLightCross.cpp +++ b/cursors/kpCursorLightCross.cpp @@ -1,129 +1,131 @@ /* Copyright (c) 2003-2007 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_CURSOR_LIGHT_CROSS 0 #include "kpCursorLightCross.h" #include "kpLogCategories.h" #include #include enum PixelValue { White, Black, Transparent }; static void setPixel (unsigned char *colorBitmap, unsigned char *maskBitmap, int width, int y, int x, enum PixelValue pv) { const int ColorBlack = 1; const int ColorWhite = 0; const int MaskOpaque = 1; const int MaskTransparent = 0; int colorValue, maskValue; switch (pv) { case White: colorValue = ColorWhite; maskValue = MaskOpaque; break; case Black: colorValue = ColorBlack; maskValue = MaskOpaque; break; case Transparent: colorValue = ColorWhite; maskValue = MaskTransparent; break; } if (colorValue) { colorBitmap [y * (width / 8) + (x / 8)] |= (1 << (x % 8)); } if (maskValue) { maskBitmap [y * (width / 8) + (x / 8)] |= (1 << (x % 8)); } } const QCursor *kpCursorLightCrossCreate () { +#if DEBUG_KP_CURSOR_LIGHT_CROSS qCDebug(kpLogMisc) << "kpCursorLightCrossCreate() "; +#endif const int side = 24; const int byteSize = (side * side) / 8; auto *colorBitmap = new unsigned char [byteSize]; auto *maskBitmap = new unsigned char [byteSize]; memset (colorBitmap, 0, byteSize); memset (maskBitmap, 0, byteSize); const int oddSide = side - 1; const int strokeLen = oddSide * 3 / 8; for (int i = 0; i < strokeLen; i++) { const enum PixelValue pv = (i % 2) ? Black : White; #define X_(val) (val) #define Y_(val) (val) #define DRAW(y,x) setPixel (colorBitmap, maskBitmap, side, (y), (x), pv) // horizontal DRAW (Y_(side / 2), X_(1 + i)); DRAW (Y_(side / 2), X_(side - 1 - i)); // vertical DRAW (Y_(1 + i), X_(side / 2)); DRAW (Y_(side - 1 - i), X_(side / 2)); #undef DRAW #undef Y_ #undef X_ } const QSize size (side, side); QCursor *cursor = new QCursor ( QBitmap::fromData (size, colorBitmap, QImage::Format_MonoLSB), QBitmap::fromData (size, maskBitmap, QImage::Format_MonoLSB)); delete [] maskBitmap; delete [] colorBitmap; return cursor; } diff --git a/dialogs/imagelib/effects/kpEffectsDialog.cpp b/dialogs/imagelib/effects/kpEffectsDialog.cpp index 6ff638bd..99779f10 100644 --- a/dialogs/imagelib/effects/kpEffectsDialog.cpp +++ b/dialogs/imagelib/effects/kpEffectsDialog.cpp @@ -1,339 +1,372 @@ /* Copyright (c) 2003-2007 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_EFFECTS_DIALOG 0 #include "kpEffectsDialog.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "widgets/imagelib/effects/kpEffectBalanceWidget.h" #include "widgets/imagelib/effects/kpEffectBlurSharpenWidget.h" #include "widgets/imagelib/effects/kpEffectEmbossWidget.h" #include "widgets/imagelib/effects/kpEffectFlattenWidget.h" #include "widgets/imagelib/effects/kpEffectHSVWidget.h" #include "widgets/imagelib/effects/kpEffectInvertWidget.h" #include "widgets/imagelib/effects/kpEffectReduceColorsWidget.h" #include "widgets/imagelib/effects/kpEffectToneEnhanceWidget.h" #include "pixmapfx/kpPixmapFX.h" #include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" #include #include "kpLogCategories.h" #include #include #include #include #include #include #include // protected static int kpEffectsDialog::s_lastWidth = 640; int kpEffectsDialog::s_lastHeight = 620; kpEffectsDialog::kpEffectsDialog (bool actOnSelection, kpTransformDialogEnvironment *_env, QWidget *parent, int defaultSelectedEffect) : kpTransformPreviewDialog (kpTransformPreviewDialog::Preview, true/*reserve top row*/, QString()/*caption*/, QString()/*afterActionText (no Dimensions Group Box)*/, actOnSelection, _env, parent), m_delayedUpdateTimer (new QTimer (this)), m_effectsComboBox (nullptr), m_settingsGroupBox (nullptr), m_settingsLayout (nullptr), m_effectWidget (nullptr) { +#if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "kpEffectsDialog::kpEffectsDialog()"; +#endif const bool e = updatesEnabled (); setUpdatesEnabled (false); if (actOnSelection) { setWindowTitle (i18nc ("@title:window", "More Image Effects (Selection)")); } else { setWindowTitle (i18nc ("@title:window", "More Image Effects")); } m_delayedUpdateTimer->setSingleShot (true); connect (m_delayedUpdateTimer, &QTimer::timeout, this, &kpEffectsDialog::slotUpdateWithWaitCursor); QWidget *effectContainer = new QWidget (mainWidget ()); auto *containerLayout = new QHBoxLayout (effectContainer); containerLayout->setContentsMargins(0, 0, 0, 0); QLabel *label = new QLabel (i18n ("&Effect:"), effectContainer); m_effectsComboBox = new QComboBox (effectContainer); // Keep in alphabetical order. // TODO: What about translations? // sync: order in selectEffect(). m_effectsComboBox->addItem (i18n ("Balance")); m_effectsComboBox->addItem (i18n ("Emboss")); m_effectsComboBox->addItem (i18n ("Flatten")); m_effectsComboBox->addItem (i18n ("Histogram Equalizer")); m_effectsComboBox->addItem (i18n ("Hue, Saturation, Value")); m_effectsComboBox->addItem (i18n ("Invert")); m_effectsComboBox->addItem (i18n ("Reduce Colors")); m_effectsComboBox->addItem (i18n ("Soften & Sharpen")); containerLayout->addWidget (label); containerLayout->addWidget (m_effectsComboBox, 1); label->setBuddy (m_effectsComboBox); addCustomWidgetToFront (effectContainer); m_settingsGroupBox = new QGroupBox (mainWidget ()); m_settingsLayout = new QVBoxLayout ( m_settingsGroupBox ); addCustomWidgetToBack (m_settingsGroupBox); connect (m_effectsComboBox, static_cast(&QComboBox::activated), this, &kpEffectsDialog::selectEffect); selectEffect (defaultSelectedEffect); resize (s_lastWidth, s_lastHeight); +#if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "about to setUpdatesEnabled()"; +#endif // OPT: The preview pixmap gets recalculated here and then possibly // again when QResizeEvent fires, when the dialog is shown. setUpdatesEnabled (e); +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << endl + << endl + << endl; +#endif } kpEffectsDialog::~kpEffectsDialog () { s_lastWidth = width (); s_lastHeight = height (); } // public virtual [base kpTransformPreviewDialog] bool kpEffectsDialog::isNoOp () const { if (!m_effectWidget) { return true; } return m_effectWidget->isNoOp (); } // public kpEffectCommandBase *kpEffectsDialog::createCommand () const { if (!m_effectWidget) { return nullptr; } return m_effectWidget->createCommand (m_environ->commandEnvironment ()); } // protected virtual [base kpTransformPreviewDialog] QSize kpEffectsDialog::newDimensions () const { kpDocument *doc = document (); if (!doc) { return {}; } return {doc->width (m_actOnSelection), doc->height (m_actOnSelection)}; } // protected virtual [base kpTransformPreviewDialog] QImage kpEffectsDialog::transformPixmap (const QImage &pixmap, int targetWidth, int targetHeight) const { QImage pixmapWithEffect; if (m_effectWidget && !m_effectWidget->isNoOp ()) { pixmapWithEffect = m_effectWidget->applyEffect (pixmap); } else { pixmapWithEffect = pixmap; } return kpPixmapFX::scale (pixmapWithEffect, targetWidth, targetHeight); } // public int kpEffectsDialog::selectedEffect () const { return m_effectsComboBox->currentIndex (); } // public slot void kpEffectsDialog::selectEffect (int which) { #if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "kpEffectsDialog::selectEffect(" << which << ")"; #endif if (which < 0 || which >= m_effectsComboBox->count ()) { return; } if (which != m_effectsComboBox->currentIndex ()) { m_effectsComboBox->setCurrentIndex (which); } delete m_effectWidget; m_effectWidget = nullptr; m_settingsGroupBox->setWindowTitle(QString()); #define CREATE_EFFECT_WIDGET(name) \ m_effectWidget = new name (m_actOnSelection, m_settingsGroupBox) // sync: order in constructor. switch (which) { case 0: CREATE_EFFECT_WIDGET (kpEffectBalanceWidget); break; case 1: CREATE_EFFECT_WIDGET (kpEffectEmbossWidget); break; case 2: CREATE_EFFECT_WIDGET (kpEffectFlattenWidget); break; case 3: CREATE_EFFECT_WIDGET (kpEffectToneEnhanceWidget); break; case 4: CREATE_EFFECT_WIDGET (kpEffectHSVWidget); break; case 5: CREATE_EFFECT_WIDGET (kpEffectInvertWidget); break; case 6: CREATE_EFFECT_WIDGET (kpEffectReduceColorsWidget); break; case 7: CREATE_EFFECT_WIDGET (kpEffectBlurSharpenWidget); break; } #undef CREATE_EFFECT_WIDGET if (m_effectWidget) { const bool e = updatesEnabled (); setUpdatesEnabled (false); + #if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "widget exists for effect #"; + #endif m_settingsGroupBox->setTitle (m_effectWidget->caption ()); // Show widget. // // Don't resize the whole dialog when doing this. // This seems to work magically without any extra code with Qt4. + #if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "addWidget"; + #endif m_settingsLayout->addWidget (m_effectWidget); + #if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "show widget"; + #endif m_effectWidget->show (); connect (m_effectWidget, &kpEffectWidgetBase::settingsChangedNoWaitCursor, this, &kpEffectsDialog::slotUpdate); connect (m_effectWidget, &kpEffectWidgetBase::settingsChanged, this, &kpEffectsDialog::slotUpdateWithWaitCursor); connect (m_effectWidget, &kpEffectWidgetBase::settingsChangedDelayed, this, &kpEffectsDialog::slotDelayedUpdate); + #if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "about to setUpdatesEnabled()"; + #endif setUpdatesEnabled (e); } - qCDebug(kpLogDialogs) << "done"; + +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "done" + << endl + << endl + << endl; +#endif } // protected slot virtual [base kpTransformPreviewDialog] void kpEffectsDialog::slotUpdate () { +#if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "kpEffectsDialog::slotUpdate()" - << " timerActive=" << m_delayedUpdateTimer->isActive (); + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif m_delayedUpdateTimer->stop (); kpTransformPreviewDialog::slotUpdate (); } // protected slot virtual [base kpTransformPreviewDialog] void kpEffectsDialog::slotUpdateWithWaitCursor () { +#if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "kpEffectsDialog::slotUpdateWithWaitCursor()" - << " timerActive=" << m_delayedUpdateTimer->isActive (); + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif m_delayedUpdateTimer->stop (); kpTransformPreviewDialog::slotUpdateWithWaitCursor (); } // protected slot void kpEffectsDialog::slotDelayedUpdate () { +#if DEBUG_KP_EFFECTS_DIALOG qCDebug(kpLogDialogs) << "kpEffectsDialog::slotDelayedUpdate()" - << " timerActive=" << m_delayedUpdateTimer->isActive (); + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif m_delayedUpdateTimer->stop (); // (single shot) m_delayedUpdateTimer->start (400/*ms*/); } + diff --git a/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp b/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp index 5c8c3f0a..a475ea09 100644 --- a/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp +++ b/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp @@ -1,716 +1,753 @@ /* Copyright (c) 2003-2007 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_META_INFO_DIALOG 0 #include "kpDocumentMetaInfoDialog.h" #include "kpDefs.h" #include "imagelib/kpDocumentMetaInfo.h" #include #include #include "kpLogCategories.h" #include #include #include #include #include #include #include #include #include #include struct kpDocumentMetaInfoDialogPrivate { const kpDocumentMetaInfo *originalMetaInfoPtr; QDoubleSpinBox *horizDpiInput, *vertDpiInput; QSpinBox *horizOffsetInput, *vertOffsetInput; QTableWidget *fieldsTableWidget; QPushButton *fieldsAddRowButton, *fieldsDeleteRowButton, *fieldsResetButton; }; // (shared by all dialogs, across all main windows, in a KolourPaint instance) static int LastWidth = -1, LastHeight = -1; // sync: You must keep DpiMinStep = 10 ^ (-DpiPrecision). // // You can increase the precision to reduce the chance of inadvertently changing // the resolution when converting from kpDocumentMetaInfo's "dots per meter" // to our "dots per inch" and back. It would be bad if simply going into this // dialog and pressing OK changed the resolution (it's unlikely but I still think // it might happen with the current precision). // TODO: On a related note, for many particular resolutions, if the user enters // one of them into the UI, presses OK and then comes back to the dialog, // s/he is presented with a different resolution to the one typed in. // Maybe make DotsPerMeter[XY] be of type "double" instead of "int" to // solve this problem? // // Of course, if you increase the precision too much, the minimum step will // become so small that it will start experiencing floating point inaccuracies // esp. since we use it for the "DpiUnspecified" hack. static const int DpiPrecision = 3; static const double DpiMinStep = 0.001; static const double DpiLegalMin = kpDocumentMetaInfo::MinDotsPerMeter / KP_INCHES_PER_METER; // Out of range represents unspecified DPI. static const double DpiUnspecified = ::DpiLegalMin - ::DpiMinStep; static const double DpiInputMin = ::DpiUnspecified; static const double DpiInputMax = kpDocumentMetaInfo::MaxDotsPerMeter / KP_INCHES_PER_METER; // The increment the DPI spinboxes jump by when they're clicked. // // We make this relatively big since people don't usually just increase their // DPIs by 1 or so -- they are usually changing from say, 72, to 96. // // Obviously, making it equal to DpiMinStep is too slow a UI. Therefore, with // our big setting, the user will still have to manually change the value in // the spinbox, using the keyboard, after all their clicking to ensure it is // exactly the value they want. static const double DpiInputStep = 10; // TODO: Halve groupbox layout margins in every other file since it doesn't // seem to be need in Qt4. kpDocumentMetaInfoDialog::kpDocumentMetaInfoDialog ( const kpDocumentMetaInfo *docMetaInfo, QWidget *parent) : QDialog (parent), d (new kpDocumentMetaInfoDialogPrivate ()) { d->originalMetaInfoPtr = docMetaInfo; setWindowTitle (i18nc ("@title:window", "Document Properties")); auto * buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect (buttons, &QDialogButtonBox::accepted, this, &kpDocumentMetaInfoDialog::accept); connect (buttons, &QDialogButtonBox::rejected, this, &kpDocumentMetaInfoDialog::reject); auto *baseWidget = new QWidget (this); auto *dialogLayout = new QVBoxLayout (this); dialogLayout->addWidget (baseWidget); dialogLayout->addWidget (buttons); // // DPI Group Box // Q_ASSERT (::DpiInputMin < ::DpiInputMax); auto *dpiGroupBox = new QGroupBox(i18n("Dots &Per Inch (DPI)"), baseWidget); d->horizDpiInput = new QDoubleSpinBox(dpiGroupBox); d->horizDpiInput->setRange(::DpiInputMin, ::DpiInputMax); d->horizDpiInput->setValue(0.0); d->horizDpiInput->setSingleStep(::DpiInputStep); d->horizDpiInput->setDecimals(::DpiPrecision); d->horizDpiInput->setSpecialValueText(i18n("Unspecified")); auto *dpiXLabel = new QLabel ( i18nc ("Horizontal DPI 'x' Vertical DPI", " x "), dpiGroupBox); dpiXLabel->setAlignment (Qt::AlignCenter); d->vertDpiInput = new QDoubleSpinBox(dpiGroupBox); d->vertDpiInput->setRange(::DpiInputMin, ::DpiInputMax); d->vertDpiInput->setValue(0.0); d->vertDpiInput->setSingleStep(::DpiInputStep); d->vertDpiInput->setDecimals(::DpiPrecision); d->vertDpiInput->setSpecialValueText(i18n("Unspecified")); auto *dpiLay = new QGridLayout(dpiGroupBox); dpiLay->addWidget(new QLabel(i18n("Horizontal:")), 0, 0, Qt::AlignHCenter); dpiLay->addWidget(d->horizDpiInput, 1, 0); dpiLay->addWidget(dpiXLabel, 0, 1); dpiLay->addWidget(new QLabel(i18n("Vertical:")), 0, 2, Qt::AlignHCenter); dpiLay->addWidget(d->vertDpiInput, 1, 2); dpiLay->setRowStretch(2, 1); dpiGroupBox->setWhatsThis ( i18n ( "" "

Dots Per Inch (DPI) specifies the number of pixels" " of the image that should be printed inside one inch (2.54cm).

" "

The higher the image's DPI, the smaller the printed image." " Note that your printer is unlikely to produce high" " quality prints if you increase this to more than 300 or 600 DPI," " depending on the printer.

" "

If you would like to print the image so that it is the same" " size as it is displayed on the screen, set the image's DPI" " values to be the same as the screen's.

" // TODO: This is currently not true! // See "96dpi" TODO in kpMainWindow::sendPixmapToPrinter(). // This also why we don't try to report the current screen DPI // for the above paragraph. "

If either DPI value is Unspecified, the image will also" " be printed to be the same size as on the screen.

" "

Not all image formats support DPI values. If the format you" " save in does not support them, they will not be saved.

" "
" )); // // Offset Group Box // auto *offsetGroupBox = new QGroupBox(i18n ("O&ffset"), baseWidget); d->horizOffsetInput = new QSpinBox; d->horizOffsetInput->setRange(kpDocumentMetaInfo::MinOffset, kpDocumentMetaInfo::MaxOffset); d->vertOffsetInput = new QSpinBox; d->vertOffsetInput->setRange(kpDocumentMetaInfo::MinOffset, kpDocumentMetaInfo::MaxOffset); auto *offsetLay = new QGridLayout(offsetGroupBox); offsetLay->addWidget(new QLabel(i18n("Horizontal:")), 0, 0, Qt::AlignHCenter); offsetLay->addWidget(d->horizOffsetInput, 1, 0); offsetLay->addWidget(new QLabel(i18n("Vertical:")), 0, 1, Qt::AlignHCenter); offsetLay->addWidget(d->vertOffsetInput, 1, 1); offsetLay->setRowStretch (2, 1); offsetGroupBox->setWhatsThis ( i18n ( "" "

The Offset is the relative position where this image" " should be placed, compared to other images.

" "

Not all image formats support the Offset feature." " If the format you save in does not support it, the values" " specified here will not be saved.

" "
" )); // // Fields Group Box // auto *fieldsGroupBox = new QGroupBox (i18n ("&Text Fields"), baseWidget); d->fieldsTableWidget = new QTableWidget (fieldsGroupBox); d->fieldsTableWidget->setEditTriggers(QAbstractItemView::AllEditTriggers); connect (d->fieldsTableWidget, &QTableWidget::currentCellChanged, this, &kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged); connect (d->fieldsTableWidget, &QTableWidget::itemChanged, this, &kpDocumentMetaInfoDialog::slotFieldsItemChanged); d->fieldsAddRowButton = new QPushButton (i18n ("&Add Row"), fieldsGroupBox); connect (d->fieldsAddRowButton, &QPushButton::clicked, this, &kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked); d->fieldsDeleteRowButton = new QPushButton (i18n ("&Delete Row"), fieldsGroupBox); connect (d->fieldsDeleteRowButton, &QPushButton::clicked, this, &kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked); d->fieldsResetButton = new QPushButton (i18n ("&Reset"), fieldsGroupBox); connect (d->fieldsResetButton, &QPushButton::clicked, this, &kpDocumentMetaInfoDialog::setUIToOriginalMetaInfo); auto *fieldsButtonsLayout = new QHBoxLayout (); fieldsButtonsLayout->addWidget (d->fieldsAddRowButton); fieldsButtonsLayout->addWidget (d->fieldsDeleteRowButton); fieldsButtonsLayout->addStretch (); fieldsButtonsLayout->addWidget (d->fieldsResetButton); auto *fieldsLayout = new QVBoxLayout (fieldsGroupBox); fieldsLayout->addWidget (d->fieldsTableWidget); fieldsLayout->addLayout (fieldsButtonsLayout); fieldsGroupBox->setWhatsThis ( i18n ( "" "

Text Fields provide extra information about the image." " This is probably a comment area that you can freely write any text in.

" "

However, this is format-specific so the fields could theoretically be" " computer-interpreted data - that you should not modify -" " but this is unlikely.

" "

Not all image formats support Text Fields. If the format" " you save in does not support them, they will not be saved.

" "
" )); // // Global Layout // auto *baseLayout = new QGridLayout (baseWidget); baseLayout->setContentsMargins(0, 0, 0, 0); // Col 0 baseLayout->addWidget (dpiGroupBox, 0, 0); baseLayout->addWidget (offsetGroupBox, 1, 0); // Col 1 baseLayout->addWidget (fieldsGroupBox, 0, 1, 2/*row span*/, 1/*col span*/); baseLayout->setColumnStretch (1, 1/*stretch*/); // // Remaining UI Setup // setUIToOriginalMetaInfo (); if (::LastWidth > 0 && ::LastHeight > 0) { resize (::LastWidth, ::LastHeight); } } //--------------------------------------------------------------------- kpDocumentMetaInfoDialog::~kpDocumentMetaInfoDialog () { ::LastWidth = width (); ::LastHeight = height (); delete d; } //--------------------------------------------------------------------- // private void kpDocumentMetaInfoDialog::editCell (int r, int c) { d->fieldsTableWidget->setCurrentCell (r, c); d->fieldsTableWidget->editItem (d->fieldsTableWidget->item (r, c)); } //--------------------------------------------------------------------- // private slot void kpDocumentMetaInfoDialog::setUIToOriginalMetaInfo () { // Set DPI spinboxes. d->horizDpiInput->setValue (d->originalMetaInfoPtr->dotsPerMeterX () / KP_INCHES_PER_METER); d->vertDpiInput->setValue (d->originalMetaInfoPtr->dotsPerMeterY () / KP_INCHES_PER_METER); // Set Offset spinboxes. d->horizOffsetInput->setValue (d->originalMetaInfoPtr->offset ().x ()); d->vertOffsetInput->setValue (d->originalMetaInfoPtr->offset ().y ()); // Set Text Fields. // // Block itemChanged() signal as slotFieldsItemChanged() should not get called // when rows are half-created. const bool b = d->fieldsTableWidget->blockSignals (true); { d->fieldsTableWidget->clear (); d->fieldsTableWidget->setRowCount (d->originalMetaInfoPtr->textKeys ().size ()); d->fieldsTableWidget->setColumnCount (2); QStringList fieldsHeader; fieldsHeader << i18n ("Key") << i18n ("Value"); d->fieldsTableWidget->setHorizontalHeaderLabels (fieldsHeader); int row = 0; for (const auto &key : d->originalMetaInfoPtr->textKeys ()) { d->fieldsTableWidget->setItem (row, 0/*1st col*/, new QTableWidgetItem (key)); d->fieldsTableWidget->setItem (row, 1/*2nd col*/, new QTableWidgetItem (d->originalMetaInfoPtr->text (key))); row++; } fieldsAppendEmptyRow (); } d->fieldsTableWidget->blockSignals (b); editCell (0/*row*/, 0/*col*/); enableFieldsDeleteRowButtonIfShould (); } //--------------------------------------------------------------------- // public bool kpDocumentMetaInfoDialog::isNoOp () const { return (metaInfo () == *d->originalMetaInfoPtr); } //--------------------------------------------------------------------- // public kpDocumentMetaInfo kpDocumentMetaInfoDialog::originalMetaInfo () const { return *d->originalMetaInfoPtr; } // public kpDocumentMetaInfo kpDocumentMetaInfoDialog::metaInfo ( QString *errorMessage) const { if (errorMessage) { // No errors to start with. *errorMessage = QString (); } kpDocumentMetaInfo ret; if (d->horizDpiInput->value () < ::DpiLegalMin) { ret.setDotsPerMeterX (0/*unspecified*/); } else { ret.setDotsPerMeterX (qRound (d->horizDpiInput->value () * KP_INCHES_PER_METER)); } if (d->vertDpiInput->value () < ::DpiLegalMin) { ret.setDotsPerMeterY (0/*unspecified*/); } else { ret.setDotsPerMeterY (qRound (d->vertDpiInput->value () * KP_INCHES_PER_METER)); } ret.setOffset (QPoint (d->horizOffsetInput->value (), d->vertOffsetInput->value ())); for (int r = 0; r < d->fieldsTableWidget->rowCount (); r++) { const QString key = d->fieldsTableWidget->item (r, 0)->text (); const QString value = d->fieldsTableWidget->item (r, 1)->text (); // Empty key? if (key.isEmpty ()) { // Empty value too? if (value.isEmpty ()) { // Ignore empty row. continue; } // Value without a key? if (errorMessage) { *errorMessage = ki18n ("The text value \"%1\" on line %2 requires a key.") .subs (value).subs (r + 1/*count from 1*/).toString (); // Print only 1 error message per method invocation. errorMessage = nullptr; } // Ignore. continue; } // Duplicate key? if (ret.textKeys ().contains (key)) { if (errorMessage) { int q; for (q = 0; q < r; q++) { if (d->fieldsTableWidget->item (q, 0)->text () == key) { break; } } Q_ASSERT (q != r); *errorMessage = ki18n ("All text keys must be unique. The text key \"%1\"" " on lines %2 and %3 are identical.") .subs (key) .subs (q + 1/*count from 1*/) .subs (r + 1/*count from 1*/) .toString (); // Print only 1 error message per method invocation. errorMessage = nullptr; } // Ignore this duplicate - keep the first value of the key. continue; } ret.setText (key, value); } // for (r = 0; r < table widget rows; r++) { return ret; } // private void kpDocumentMetaInfoDialog::fieldsUpdateVerticalHeader () { QStringList vertLabels; for (int r = 1; r <= d->fieldsTableWidget->rowCount (); r++) { vertLabels << QString::number (r); } d->fieldsTableWidget->setVerticalHeaderLabels (vertLabels); } // private void kpDocumentMetaInfoDialog::fieldsAddEmptyRow (int atRow) { // Block itemChanged() signal as slotFieldsItemChanged() should not get called // when rows are half-created. const bool b = d->fieldsTableWidget->blockSignals (true); { d->fieldsTableWidget->insertRow (atRow); d->fieldsTableWidget->setItem (atRow, 0, new QTableWidgetItem (QString ())); d->fieldsTableWidget->setItem (atRow, 1, new QTableWidgetItem (QString ())); } d->fieldsTableWidget->blockSignals (b); // Hack around Qt's failure to redraw these sometimes. fieldsUpdateVerticalHeader (); enableFieldsDeleteRowButtonIfShould (); } // private void kpDocumentMetaInfoDialog::fieldsAppendEmptyRow () { fieldsAddEmptyRow (d->fieldsTableWidget->rowCount ()); } // private bool kpDocumentMetaInfoDialog::isFieldsRowDeleteable (int row) const { // Can't delete no row and can't delete last (always blank) row, which // is used to make it easy for the user to add rows without pressing // the "Add" button explicitly. return (row >= 0 && row < d->fieldsTableWidget->rowCount () - 1); } // private void kpDocumentMetaInfoDialog::fieldsDeleteRow (int r) { +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::fieldsDeleteRow(" << "row=" << r << ")" << " currentRow=" << d->fieldsTableWidget->currentRow (); +#endif Q_ASSERT (isFieldsRowDeleteable (r)); if (r == d->fieldsTableWidget->currentRow ()) { // Assertion follows from previous assertion. const int newRow = r + 1; + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\tnewRow=" << newRow; + #endif Q_ASSERT (newRow < d->fieldsTableWidget->rowCount ()); int newCol = d->fieldsTableWidget->currentColumn (); + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\tnewCol=" << newCol; + #endif if (newCol != 0 && newCol != 1) { newCol = 0; + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\t\tcorrecting to " << newCol; + #endif } // WARNING: You must call this _before_ deleting the row. Else, you'll // trigger a Qt bug where if the editor is active on the row to // be deleted, it might crash. To reproduce, move this line to // after removeRow() (and subtract 1 from newRow) and // press the "Delete Row" button several times in succession // very quickly. // // TODO: This usually results in a redraw error if the scrollbar scrolls // after deleting the 2nd last row. Qt bug. editCell (newRow, newCol); } d->fieldsTableWidget->removeRow (r); fieldsUpdateVerticalHeader (); enableFieldsDeleteRowButtonIfShould (); } // private void kpDocumentMetaInfoDialog::enableFieldsDeleteRowButtonIfShould () { +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::enableFieldsDeleteRowButtonIfShould()"; +#endif const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\tr=" << r; +#endif d->fieldsDeleteRowButton->setEnabled (isFieldsRowDeleteable (r)); } // private slot void kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged (int row, int col, int oldRow, int oldCol) { +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged(" << "row=" << row << ",col=" << col << ",oldRow=" << oldRow << ",oldCol=" << oldCol - << ")"; + << ")" << endl; +#endif + + (void) row; + (void) col; + (void) oldRow; + (void) oldCol; enableFieldsDeleteRowButtonIfShould (); } //--------------------------------------------------------------------- // private slot void kpDocumentMetaInfoDialog::slotFieldsItemChanged (QTableWidgetItem *it) { +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsItemChanged(" << "item=" << it << ") rows=" << d->fieldsTableWidget->rowCount (); +#endif const int r = d->fieldsTableWidget->row (it); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\tr=" << r; +#endif Q_ASSERT (r >= 0 && r < d->fieldsTableWidget->rowCount ()); const QString key = d->fieldsTableWidget->item (r, 0)->text (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << " key='" << key << "'"; +#endif const QString value = d->fieldsTableWidget->item (r, 1)->text (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << " value='" << value << "'"; +#endif // At the last row? if (r == d->fieldsTableWidget->rowCount () - 1) { // Typed some text? if (!key.isEmpty () || !value.isEmpty ()) { // LOTODO: If we're called due to the cell's text being finalized // as a result of the user pressing the "Add Row" button, // should this really append a row since // slotFieldsAddRowButtonClicked() button is going to add // another one? That's two rows when the user only clicked // that button once! fieldsAppendEmptyRow (); } } } //--------------------------------------------------------------------- // private slot void kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked () { +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked()"; +#endif const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\tr=" << r; +#endif // (if no row is selected, r = -1) fieldsAddEmptyRow (r + 1); // Edit the key of this new row (column 0). // No one edits the value first (column 1). editCell ((r + 1)/*row*/, 0/*col*/); } //--------------------------------------------------------------------- // private slot void kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked () { +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked()"; +#endif const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG qCDebug(kpLogDialogs) << "\tr=" << r; +#endif Q_ASSERT (isFieldsRowDeleteable (r)); fieldsDeleteRow (r); } // private slot virtual [base QDialog] void kpDocumentMetaInfoDialog::accept () { // Validate text fields. QString errorMessage; (void) metaInfo (&errorMessage); if (!errorMessage.isEmpty ()) { KMessageBox::sorry (this, errorMessage, i18nc ("@title:window", "Invalid Text Fields")); return; } QDialog::accept (); } + + diff --git a/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp b/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp index 48359fb9..606ac7da 100644 --- a/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp +++ b/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp @@ -1,440 +1,468 @@ /* Copyright (c) 2003-2007 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_TRANSFORM_PREVIEW_DIALOG 0 #include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" #include #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include #include "layers/selections/image/kpAbstractImageSelection.h" #include "imagelib/kpColor.h" #include "document/kpDocument.h" #include "pixmapfx/kpPixmapFX.h" #include "generic/widgets/kpResizeSignallingLabel.h" #include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" kpTransformPreviewDialog::kpTransformPreviewDialog (Features features, bool reserveTopRow, const QString &caption, const QString &afterActionText, bool actOnSelection, kpTransformDialogEnvironment *_env, QWidget *parent) : QDialog (parent), m_afterActionText (afterActionText), m_actOnSelection (actOnSelection), m_dimensionsGroupBox (nullptr), m_afterTransformDimensionsLabel (nullptr), m_previewGroupBox (nullptr), m_previewPixmapLabel (nullptr), m_gridLayout (nullptr), m_environ (_env) { setWindowTitle (caption); QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect (buttons, &QDialogButtonBox::accepted, this, &kpTransformPreviewDialog::accept); connect (buttons, &QDialogButtonBox::rejected, this, &kpTransformPreviewDialog::reject); QWidget *baseWidget = new QWidget (this); m_mainWidget = baseWidget; auto *dialogLayout = new QVBoxLayout (this); dialogLayout->addWidget (baseWidget); dialogLayout->addWidget (buttons); if (document ()) { m_oldWidth = document ()->width (actOnSelection); m_oldHeight = document ()->height (actOnSelection); } else { m_oldWidth = m_oldHeight = 1; } if (features & Dimensions) { createDimensionsGroupBox (); } if (features & Preview) { createPreviewGroupBox (); } m_gridLayout = new QGridLayout (baseWidget ); m_gridLayout->setContentsMargins(0, 0, 0, 0); m_gridNumRows = reserveTopRow ? 1 : 0; if (m_dimensionsGroupBox || m_previewGroupBox) { if (m_dimensionsGroupBox && m_previewGroupBox) { m_gridLayout->addWidget (m_dimensionsGroupBox, m_gridNumRows, 0); m_gridLayout->addWidget (m_previewGroupBox, m_gridNumRows, 1); m_gridLayout->setColumnStretch (1, 1); } else if (m_dimensionsGroupBox) { m_gridLayout->addWidget (m_dimensionsGroupBox, m_gridNumRows, 0, 1, 2); } else if (m_previewGroupBox) { m_gridLayout->addWidget (m_previewGroupBox, m_gridNumRows, 0, 1, 2); } m_gridLayout->setRowStretch (m_gridNumRows, 1); m_gridNumRows++; } } kpTransformPreviewDialog::~kpTransformPreviewDialog () = default; // private void kpTransformPreviewDialog::createDimensionsGroupBox () { m_dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), mainWidget ()); auto *originalLabel = new QLabel (i18n ("Original:"), m_dimensionsGroupBox); QString originalDimensions; if (document ()) { originalDimensions = i18n ("%1 x %2", m_oldWidth, m_oldHeight); // Stop the Dimensions Group Box from resizing so often const QString minimumLengthString ("100000 x 100000"); const int padLength = minimumLengthString.length (); for (int i = originalDimensions.length (); i < padLength; i++) { originalDimensions += ' '; } } auto *originalDimensionsLabel = new QLabel (originalDimensions, m_dimensionsGroupBox); auto *afterTransformLabel = new QLabel (m_afterActionText, m_dimensionsGroupBox); m_afterTransformDimensionsLabel = new QLabel (m_dimensionsGroupBox); auto *dimensionsLayout = new QGridLayout (m_dimensionsGroupBox ); dimensionsLayout->addWidget (originalLabel, 0, 0, Qt::AlignBottom); dimensionsLayout->addWidget (originalDimensionsLabel, 0, 1, Qt::AlignBottom); dimensionsLayout->addWidget (afterTransformLabel, 1, 0, Qt::AlignTop); dimensionsLayout->addWidget (m_afterTransformDimensionsLabel, 1, 1, Qt::AlignTop); } // private void kpTransformPreviewDialog::createPreviewGroupBox () { m_previewGroupBox = new QGroupBox (i18n ("Preview"), mainWidget ()); m_previewPixmapLabel = new kpResizeSignallingLabel (m_previewGroupBox); m_previewPixmapLabel->setMinimumSize (150, 110); connect (m_previewPixmapLabel, &kpResizeSignallingLabel::resized, this, &kpTransformPreviewDialog::updatePreview); QPushButton *updatePushButton = new QPushButton (i18n ("&Update"), m_previewGroupBox); connect (updatePushButton, &QPushButton::clicked, this, &kpTransformPreviewDialog::slotUpdateWithWaitCursor); auto *previewLayout = new QVBoxLayout (m_previewGroupBox); previewLayout->addWidget (m_previewPixmapLabel, 1/*stretch*/); previewLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); } // protected kpDocument *kpTransformPreviewDialog::document () const { return m_environ->document (); } // protected QWidget *kpTransformPreviewDialog::mainWidget () const { return m_mainWidget; } // protected void kpTransformPreviewDialog::addCustomWidgetToFront (QWidget *w) { m_gridLayout->addWidget (w, 0, 0, 1, 2); } // protected void kpTransformPreviewDialog::addCustomWidget (QWidget *w) { m_gridLayout->addWidget (w, m_gridNumRows, 0, 1, 2); m_gridNumRows++; } // public override [base QWidget] void kpTransformPreviewDialog::setUpdatesEnabled (bool enable) { QDialog::setUpdatesEnabled (enable); if (enable) { slotUpdateWithWaitCursor (); } } // private void kpTransformPreviewDialog::updateDimensions () { if (!m_dimensionsGroupBox) { return; } kpDocument *doc = document (); if (!doc) { return; } if (!updatesEnabled ()) { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "updates not enabled - aborting"; + #endif return; } QSize newDim = newDimensions (); +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updateDimensions(): newDim=" << newDim; +#endif QString newDimString = i18n ("%1 x %2", newDim.width (), newDim.height ()); m_afterTransformDimensionsLabel->setText (newDimString); } // public static double kpTransformPreviewDialog::aspectScale (int newWidth, int newHeight, int oldWidth, int oldHeight) { double widthScale = double (newWidth) / double (oldWidth); double heightScale = double (newHeight) / double (oldHeight); // Keeps aspect ratio return qMin (widthScale, heightScale); } // public static int kpTransformPreviewDialog::scaleDimension (int dimension, double scale, int min, int max) { return qMax (min, qMin (max, qRound (dimension * scale))); } // private void kpTransformPreviewDialog::updateShrunkenDocumentPixmap () { +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updateShrunkenDocumentPixmap()" << " shrunkenDocPixmap.size=" << m_shrunkenDocumentPixmap.size () << " previewPixmapLabelSizeWhenUpdatedPixmap=" << m_previewPixmapLabelSizeWhenUpdatedPixmap << " previewPixmapLabel.size=" - << m_previewPixmapLabel->size (); + << m_previewPixmapLabel->size () + << endl; +#endif if (!m_previewGroupBox) { return; } kpDocument *doc = document (); Q_ASSERT (doc && !doc->image ().isNull ()); if (m_shrunkenDocumentPixmap.isNull () || m_previewPixmapLabel->size () != m_previewPixmapLabelSizeWhenUpdatedPixmap) { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "\tupdating shrunkenDocPixmap"; + #endif // TODO: Why the need to keep aspect ratio here? // Isn't scaling the skewed result maintaining aspect enough? double keepsAspectScale = aspectScale (m_previewPixmapLabel->width (), m_previewPixmapLabel->height (), m_oldWidth, m_oldHeight); kpImage image; if (m_actOnSelection) { kpAbstractImageSelection *sel = doc->imageSelection ()->clone (); if (!sel->hasContent ()) { sel->setBaseImage (doc->getSelectedBaseImage ()); } image = sel->transparentImage (); delete sel; } else { image = doc->image (); } m_shrunkenDocumentPixmap = kpPixmapFX::scale ( image, scaleDimension (m_oldWidth, keepsAspectScale, 1, m_previewPixmapLabel->width ()), scaleDimension (m_oldHeight, keepsAspectScale, 1, m_previewPixmapLabel->height ())); m_previewPixmapLabelSizeWhenUpdatedPixmap = m_previewPixmapLabel->size (); } } // private void kpTransformPreviewDialog::updatePreview () { +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updatePreview()"; +#endif if (!m_previewGroupBox) { return; } kpDocument *doc = document (); if (!doc) { return; } if (!updatesEnabled ()) { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "updates not enabled - aborting"; + #endif return; } updateShrunkenDocumentPixmap (); if (!m_shrunkenDocumentPixmap.isNull ()) { QSize newDim = newDimensions (); double keepsAspectScale = aspectScale (m_previewPixmapLabel->width (), m_previewPixmapLabel->height (), newDim.width (), newDim.height ()); int targetWidth = scaleDimension (newDim.width (), keepsAspectScale, 1, // min m_previewPixmapLabel->width ()); // max int targetHeight = scaleDimension (newDim.height (), keepsAspectScale, 1, // min m_previewPixmapLabel->height ()); // max // TODO: Some effects work directly on QImage; so could cache the // QImage so that transformPixmap() is faster QImage transformedShrunkenDocumentPixmap = transformPixmap (m_shrunkenDocumentPixmap, targetWidth, targetHeight); QImage previewPixmap (m_previewPixmapLabel->width (), m_previewPixmapLabel->height (), QImage::Format_ARGB32_Premultiplied); previewPixmap.fill(QColor(Qt::transparent).rgba()); kpPixmapFX::setPixmapAt (&previewPixmap, (previewPixmap.width () - transformedShrunkenDocumentPixmap.width ()) / 2, (previewPixmap.height () - transformedShrunkenDocumentPixmap.height ()) / 2, transformedShrunkenDocumentPixmap); +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updatePreview ():" << " shrunkenDocumentPixmap: w=" << m_shrunkenDocumentPixmap.width () << " h=" << m_shrunkenDocumentPixmap.height () << " previewPixmapLabel: w=" << m_previewPixmapLabel->width () << " h=" << m_previewPixmapLabel->height () << " transformedShrunkenDocumentPixmap: w=" << transformedShrunkenDocumentPixmap.width () << " h=" << transformedShrunkenDocumentPixmap.height () << " previewPixmap: w=" << previewPixmap.width () << " h=" - << previewPixmap.height (); + << previewPixmap.height () + << endl; +#endif m_previewPixmapLabel->setPixmap (QPixmap::fromImage(previewPixmap)); // immediate update esp. for expensive previews m_previewPixmapLabel->repaint (); +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "\tafter QLabel::setPixmap() previewPixmapLabel: w=" << m_previewPixmapLabel->width () << " h=" - << m_previewPixmapLabel->height (); + << m_previewPixmapLabel->height () + << endl; +#endif } } // protected slot virtual void kpTransformPreviewDialog::slotUpdate () { +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::slotUpdate()"; +#endif updateDimensions (); updatePreview (); } // protected slot virtual void kpTransformPreviewDialog::slotUpdateWithWaitCursor () { +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::slotUpdateWithWaitCursor()"; +#endif + QApplication::setOverrideCursor (Qt::WaitCursor); + slotUpdate (); + QApplication::restoreOverrideCursor (); } + + diff --git a/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp index 76cae6cd..44cbe222 100644 --- a/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp +++ b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp @@ -1,824 +1,837 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller 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_TOOL_RESIZE_SCALE_DIALOG 0 #include "kpTransformResizeScaleDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include #include #include #include "layers/selections/kpAbstractSelection.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" //--------------------------------------------------------------------- #define kpSettingResizeScaleLastKeepAspect "Resize Scale - Last Keep Aspect" #define kpSettingResizeScaleScaleType "Resize Scale - ScaleType" //--------------------------------------------------------------------- #define SET_VALUE_WITHOUT_SIGNAL_EMISSION(knuminput_instance,value) \ { \ knuminput_instance->blockSignals (true); \ knuminput_instance->setValue (value); \ knuminput_instance->blockSignals (false); \ } #define IGNORE_KEEP_ASPECT_RATIO(cmd) \ { \ m_ignoreKeepAspectRatio++; \ cmd; \ m_ignoreKeepAspectRatio--; \ } //--------------------------------------------------------------------- kpTransformResizeScaleDialog::kpTransformResizeScaleDialog ( kpTransformDialogEnvironment *_env, QWidget *parent) : QDialog (parent), m_environ (_env), m_ignoreKeepAspectRatio (0), m_lastType(kpTransformResizeScaleCommand::Resize) { setWindowTitle (i18nc ("@title:window", "Resize / Scale")); QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect (buttons, &QDialogButtonBox::accepted, this, &kpTransformResizeScaleDialog::accept); connect (buttons, &QDialogButtonBox::rejected, this, &kpTransformResizeScaleDialog::reject); QWidget *baseWidget = new QWidget (this); auto *dialogLayout = new QVBoxLayout (this); dialogLayout->addWidget (baseWidget); dialogLayout->addWidget (buttons); QWidget *actOnBox = createActOnBox(baseWidget); QGroupBox *operationGroupBox = createOperationGroupBox(baseWidget); QGroupBox *dimensionsGroupBox = createDimensionsGroupBox(baseWidget); auto *baseLayout = new QVBoxLayout (baseWidget); baseLayout->setContentsMargins(0, 0, 0, 0); baseLayout->addWidget(actOnBox); baseLayout->addWidget(operationGroupBox); baseLayout->addWidget(dimensionsGroupBox); KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); setKeepAspectRatio(cfg.readEntry(kpSettingResizeScaleLastKeepAspect, false)); m_lastType = static_cast (cfg.readEntry(kpSettingResizeScaleScaleType, static_cast(kpTransformResizeScaleCommand::Resize))); slotActOnChanged (); m_newWidthInput->setFocus (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // private kpDocument *kpTransformResizeScaleDialog::document () const { return m_environ->document (); } //--------------------------------------------------------------------- // private kpAbstractSelection *kpTransformResizeScaleDialog::selection () const { Q_ASSERT (document ()); return document ()->selection (); } //--------------------------------------------------------------------- // private kpTextSelection *kpTransformResizeScaleDialog::textSelection () const { Q_ASSERT (document ()); return document ()->textSelection (); } //--------------------------------------------------------------------- // private QWidget *kpTransformResizeScaleDialog::createActOnBox(QWidget *baseWidget) { QWidget *actOnBox = new QWidget (baseWidget); auto *actOnLabel = new QLabel (i18n ("Ac&t on:"), actOnBox); m_actOnCombo = new QComboBox (actOnBox); actOnLabel->setBuddy (m_actOnCombo); m_actOnCombo->insertItem (Image, i18n ("Entire Image")); if (selection ()) { QString selName = i18n ("Selection"); if (textSelection ()) { selName = i18n ("Text Box"); } m_actOnCombo->insertItem (Selection, selName); m_actOnCombo->setCurrentIndex (Selection); } else { actOnLabel->setEnabled (false); m_actOnCombo->setEnabled (false); } auto *lay = new QHBoxLayout (actOnBox); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget (actOnLabel); lay->addWidget (m_actOnCombo, 1); connect (m_actOnCombo, static_cast(&QComboBox::activated), this, &kpTransformResizeScaleDialog::slotActOnChanged); return actOnBox; } //--------------------------------------------------------------------- static void toolButtonSetLook (QToolButton *button, const QString &iconName, const QString &name) { QPixmap icon = UserIcon (iconName); button->setIconSize (QSize (icon.width (), icon.height ())); button->setIcon (icon); button->setToolButtonStyle (Qt::ToolButtonTextUnderIcon); button->setText (name); button->setFocusPolicy (Qt::StrongFocus); button->setCheckable (true); } //--------------------------------------------------------------------- // private QGroupBox *kpTransformResizeScaleDialog::createOperationGroupBox (QWidget *baseWidget) { QGroupBox *operationGroupBox = new QGroupBox (i18n ("Operation"), baseWidget); operationGroupBox->setWhatsThis( i18n ("" "
    " "
  • Resize: The size of the picture will be" " increased" " by creating new areas to the right and/or bottom" " (filled in with the background color) or" " decreased by cutting" " it at the right and/or bottom.
  • " "
  • Scale: The picture will be expanded" " by duplicating pixels or squashed by dropping pixels.
  • " "
  • Smooth Scale: This is the same as" " Scale except that it blends neighboring" " pixels to produce a smoother looking picture.
  • " "
" "
")); m_resizeButton = new QToolButton (operationGroupBox); toolButtonSetLook (m_resizeButton, QLatin1String ("resize"), i18n ("&Resize")); m_scaleButton = new QToolButton (operationGroupBox); toolButtonSetLook (m_scaleButton, QLatin1String ("scale"), i18n ("&Scale")); m_smoothScaleButton = new QToolButton (operationGroupBox); toolButtonSetLook (m_smoothScaleButton, QLatin1String ("smooth_scale"), i18n ("S&mooth Scale")); auto *resizeScaleButtonGroup = new QButtonGroup (baseWidget); resizeScaleButtonGroup->addButton (m_resizeButton); resizeScaleButtonGroup->addButton (m_scaleButton); resizeScaleButtonGroup->addButton (m_smoothScaleButton); auto *operationLayout = new QGridLayout (operationGroupBox ); operationLayout->addWidget (m_resizeButton, 0, 0, Qt::AlignCenter); operationLayout->addWidget (m_scaleButton, 0, 1, Qt::AlignCenter); operationLayout->addWidget (m_smoothScaleButton, 0, 2, Qt::AlignCenter); connect (m_resizeButton, &QToolButton::toggled, this, &kpTransformResizeScaleDialog::slotTypeChanged); connect (m_scaleButton, &QToolButton::toggled, this, &kpTransformResizeScaleDialog::slotTypeChanged); connect (m_smoothScaleButton, &QToolButton::toggled, this, &kpTransformResizeScaleDialog::slotTypeChanged); return operationGroupBox; } //--------------------------------------------------------------------- // private QGroupBox *kpTransformResizeScaleDialog::createDimensionsGroupBox(QWidget *baseWidget) { QGroupBox *dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), baseWidget); auto *widthLabel = new QLabel (i18n ("Width:"), dimensionsGroupBox); widthLabel->setAlignment (widthLabel->alignment () | Qt::AlignHCenter); auto *heightLabel = new QLabel (i18n ("Height:"), dimensionsGroupBox); heightLabel->setAlignment (heightLabel->alignment () | Qt::AlignHCenter); auto *originalLabel = new QLabel (i18n ("Original:"), dimensionsGroupBox); m_originalWidthInput = new QSpinBox; m_originalWidthInput->setRange(1, INT_MAX); m_originalWidthInput->setValue(document()->width(static_cast (selection()))); auto *xLabel0 = new QLabel (i18n ("x"), dimensionsGroupBox); m_originalHeightInput = new QSpinBox; m_originalHeightInput->setRange(1, INT_MAX); m_originalHeightInput->setValue(document()->height(static_cast (selection()))); auto *newLabel = new QLabel (i18n ("&New:"), dimensionsGroupBox); m_newWidthInput = new QSpinBox; m_newWidthInput->setRange(1, INT_MAX); auto *xLabel1 = new QLabel (i18n ("x"), dimensionsGroupBox); m_newHeightInput = new QSpinBox; m_newHeightInput->setRange(1, INT_MAX); auto *percentLabel = new QLabel (i18n ("&Percent:"), dimensionsGroupBox); m_percentWidthInput = new QDoubleSpinBox; m_percentWidthInput->setRange(0.01, 1000000); m_percentWidthInput->setValue(100); m_percentWidthInput->setSingleStep(1); m_percentWidthInput->setDecimals(2); m_percentWidthInput->setSuffix(i18n("%")); auto *xLabel2 = new QLabel (i18n ("x"), dimensionsGroupBox); m_percentHeightInput = new QDoubleSpinBox; m_percentHeightInput->setRange(0.01, 1000000); m_percentHeightInput->setValue(100); m_percentHeightInput->setSingleStep(1); m_percentHeightInput->setDecimals(2); m_percentHeightInput->setSuffix(i18n("%")); m_keepAspectRatioCheckBox = new QCheckBox (i18n ("Keep &aspect ratio"), dimensionsGroupBox); m_originalWidthInput->setEnabled (false); m_originalHeightInput->setEnabled (false); originalLabel->setBuddy (m_originalWidthInput); newLabel->setBuddy (m_newWidthInput); m_percentWidthInput->setValue (100); m_percentHeightInput->setValue (100); percentLabel->setBuddy (m_percentWidthInput); auto *dimensionsLayout = new QGridLayout (dimensionsGroupBox); dimensionsLayout->setColumnStretch (1/*column*/, 1); dimensionsLayout->setColumnStretch (3/*column*/, 1); dimensionsLayout->addWidget (widthLabel, 0, 1); dimensionsLayout->addWidget (heightLabel, 0, 3); dimensionsLayout->addWidget (originalLabel, 1, 0); dimensionsLayout->addWidget (m_originalWidthInput, 1, 1); dimensionsLayout->addWidget (xLabel0, 1, 2); dimensionsLayout->addWidget (m_originalHeightInput, 1, 3); dimensionsLayout->addWidget (newLabel, 2, 0); dimensionsLayout->addWidget (m_newWidthInput, 2, 1); dimensionsLayout->addWidget (xLabel1, 2, 2); dimensionsLayout->addWidget (m_newHeightInput, 2, 3); dimensionsLayout->addWidget (percentLabel, 3, 0); dimensionsLayout->addWidget (m_percentWidthInput, 3, 1); dimensionsLayout->addWidget (xLabel2, 3, 2); dimensionsLayout->addWidget (m_percentHeightInput, 3, 3); dimensionsLayout->addWidget (m_keepAspectRatioCheckBox, 4, 0, 1, 4); dimensionsLayout->setRowStretch (4/*row*/, 1); dimensionsLayout->setRowMinimumHeight (4/*row*/, dimensionsLayout->rowMinimumHeight (4) * 2); connect (m_newWidthInput, static_cast(&QSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotWidthChanged); connect (m_newHeightInput, static_cast(&QSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotHeightChanged); // COMPAT: KDoubleNumInput only fires valueChanged(double) once per // edit. It should either fire: // // 1. At the end of the edit (triggered by clicking or tabbing // away), like with KDE 3. // // OR // // 2. Once per keystroke. // // Bug in KDoubleNumInput. connect (m_percentWidthInput, static_cast(&QDoubleSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotPercentWidthChanged); connect (m_percentHeightInput, static_cast(&QDoubleSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotPercentHeightChanged); connect (m_keepAspectRatioCheckBox, &QCheckBox::toggled, this, &kpTransformResizeScaleDialog::setKeepAspectRatio); return dimensionsGroupBox; } //--------------------------------------------------------------------- // private void kpTransformResizeScaleDialog::widthFitHeightToAspectRatio () { if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) { // width / height = oldWidth / oldHeight // height = width * oldHeight / oldWidth const int newHeight = qRound (double (imageWidth ()) * double (originalHeight ()) / double (originalWidth ())); IGNORE_KEEP_ASPECT_RATIO (m_newHeightInput->setValue (newHeight)); } } //--------------------------------------------------------------------- // private void kpTransformResizeScaleDialog::heightFitWidthToAspectRatio () { if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) { // width / height = oldWidth / oldHeight // width = height * oldWidth / oldHeight const int newWidth = qRound (double (imageHeight ()) * double (originalWidth ()) / double (originalHeight ())); IGNORE_KEEP_ASPECT_RATIO (m_newWidthInput->setValue (newWidth)); } } //--------------------------------------------------------------------- // private bool kpTransformResizeScaleDialog::resizeEnabled () const { return (!actOnSelection () || (actOnSelection () && textSelection ())); } //--------------------------------------------------------------------- // private bool kpTransformResizeScaleDialog::scaleEnabled () const { return (!(actOnSelection () && textSelection ())); } //--------------------------------------------------------------------- // private bool kpTransformResizeScaleDialog::smoothScaleEnabled () const { return scaleEnabled (); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotActOnChanged () { +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotActOnChanged()"; +#endif m_resizeButton->setEnabled (resizeEnabled ()); m_scaleButton->setEnabled (scaleEnabled ()); m_smoothScaleButton->setEnabled (smoothScaleEnabled ()); // TODO: somehow share logic with (resize|*scale)Enabled() if (actOnSelection ()) { if (textSelection ()) { m_resizeButton->setChecked (true); } else { if (m_lastType == kpTransformResizeScaleCommand::Scale) { m_scaleButton->setChecked (true); } else { m_smoothScaleButton->setChecked (true); } } } else { if (m_lastType == kpTransformResizeScaleCommand::Resize) { m_resizeButton->setChecked (true); } else if (m_lastType == kpTransformResizeScaleCommand::Scale) { m_scaleButton->setChecked (true); } else { m_smoothScaleButton->setChecked (true); } } m_originalWidthInput->setValue (originalWidth ()); m_originalHeightInput->setValue (originalHeight ()); m_newWidthInput->blockSignals (true); m_newHeightInput->blockSignals (true); m_newWidthInput->setMinimum (actOnSelection () ? selection ()->minimumWidth () : 1); m_newHeightInput->setMinimum (actOnSelection () ? selection ()->minimumHeight () : 1); m_newWidthInput->blockSignals (false); m_newHeightInput->blockSignals (false); IGNORE_KEEP_ASPECT_RATIO (slotPercentWidthChanged (m_percentWidthInput->value ())); IGNORE_KEEP_ASPECT_RATIO (slotPercentHeightChanged (m_percentHeightInput->value ())); setKeepAspectRatio (m_keepAspectRatioCheckBox->isChecked ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotTypeChanged () { m_lastType = type (); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotWidthChanged (int width) { +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotWidthChanged(" - << width << ")"; + << width << ")" << endl; +#endif const double newPercentWidth = double (width) * 100 / double (originalWidth ()); SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentWidthInput,newPercentWidth); widthFitHeightToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotHeightChanged (int height) { +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotHeightChanged(" - << height << ")"; + << height << ")" << endl; +#endif const double newPercentHeight = double (height) * 100 / double (originalHeight ()); SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentHeightInput,newPercentHeight); heightFitWidthToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotPercentWidthChanged (double percentWidth) { +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotPercentWidthChanged(" << percentWidth << ")"; +#endif SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newWidthInput, qRound (percentWidth * originalWidth () / 100.0)); widthFitHeightToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotPercentHeightChanged (double percentHeight) { +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotPercentHeightChanged(" << percentHeight << ")"; +#endif SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newHeightInput, qRound (percentHeight * originalHeight () / 100.0)); heightFitWidthToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::setKeepAspectRatio (bool on) { +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::setKeepAspectRatio(" << on << ")"; +#endif + if (on != m_keepAspectRatioCheckBox->isChecked ()) { m_keepAspectRatioCheckBox->setChecked (on); } if (on) { widthFitHeightToAspectRatio (); } } //--------------------------------------------------------------------- #undef IGNORE_KEEP_ASPECT_RATIO #undef SET_VALUE_WITHOUT_SIGNAL_EMISSION //--------------------------------------------------------------------- // private int kpTransformResizeScaleDialog::originalWidth () const { return document ()->width (actOnSelection ()); } //--------------------------------------------------------------------- // private int kpTransformResizeScaleDialog::originalHeight () const { return document ()->height (actOnSelection ()); } //--------------------------------------------------------------------- // public int kpTransformResizeScaleDialog::imageWidth () const { return m_newWidthInput->value (); } //--------------------------------------------------------------------- // public int kpTransformResizeScaleDialog::imageHeight () const { return m_newHeightInput->value (); } //--------------------------------------------------------------------- // public bool kpTransformResizeScaleDialog::actOnSelection () const { return (m_actOnCombo->currentIndex () == Selection); } //--------------------------------------------------------------------- // public kpTransformResizeScaleCommand::Type kpTransformResizeScaleDialog::type () const { if (m_resizeButton->isChecked ()) { return kpTransformResizeScaleCommand::Resize; } if (m_scaleButton->isChecked ()) { return kpTransformResizeScaleCommand::Scale; } return kpTransformResizeScaleCommand::SmoothScale; } //--------------------------------------------------------------------- // public bool kpTransformResizeScaleDialog::isNoOp () const { return (imageWidth () == originalWidth () && imageHeight () == originalHeight ()); } //--------------------------------------------------------------------- // private slot virtual [base QDialog] void kpTransformResizeScaleDialog::accept () { enum { eText, eSelection, eImage } actionTarget = eText; if (actOnSelection ()) { if (textSelection ()) { actionTarget = eText; } else { actionTarget = eSelection; } } else { actionTarget = eImage; } KLocalizedString message; QString caption, continueButtonText; // Note: If eText, can't Scale nor SmoothScale. // If eSelection, can't Resize. switch (type ()) { default: case kpTransformResizeScaleCommand::Resize: if (actionTarget == eText) { message = ki18n ("

Resizing the text box to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to resize the text box?

"); caption = i18nc ("@title:window", "Resize Text Box?"); continueButtonText = i18n ("R&esize Text Box"); } else if (actionTarget == eImage) { message = ki18n ("

Resizing the image to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to resize the image?

"); caption = i18nc ("@title:window", "Resize Image?"); continueButtonText = i18n ("R&esize Image"); } break; case kpTransformResizeScaleCommand::Scale: if (actionTarget == eImage) { message = ki18n ("

Scaling the image to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to scale the image?

"); caption = i18nc ("@title:window", "Scale Image?"); continueButtonText = i18n ("Scal&e Image"); } else if (actionTarget == eSelection) { message = ki18n ("

Scaling the selection to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to scale the selection?

"); caption = i18nc ("@title:window", "Scale Selection?"); continueButtonText = i18n ("Scal&e Selection"); } break; case kpTransformResizeScaleCommand::SmoothScale: if (actionTarget == eImage) { message = ki18n ("

Smooth Scaling the image to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to smooth scale the image?

"); caption = i18nc ("@title:window", "Smooth Scale Image?"); continueButtonText = i18n ("Smooth Scal&e Image"); } else if (actionTarget == eSelection) { message = ki18n ("

Smooth Scaling the selection to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to smooth scale the selection?

"); caption = i18nc ("@title:window", "Smooth Scale Selection?"); continueButtonText = i18n ("Smooth Scal&e Selection"); } break; } if (kpTool::warnIfBigImageSize (originalWidth (), originalHeight (), imageWidth (), imageHeight (), message.subs (imageWidth ()).subs (imageHeight ()).toString (), caption, continueButtonText, this)) { QDialog::accept (); } // store settings KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); cfg.writeEntry(kpSettingResizeScaleLastKeepAspect, m_keepAspectRatioCheckBox->isChecked()); cfg.writeEntry(kpSettingResizeScaleScaleType, static_cast(m_lastType)); cfg.sync(); } //--------------------------------------------------------------------- diff --git a/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp b/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp index 4736e58f..25f6f738 100644 --- a/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp +++ b/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp @@ -1,214 +1,232 @@ /* Copyright (c) 2003-2007 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_SAVE_OPTIONS_WIDGET 0 #include "kpDocumentSaveOptionsPreviewDialog.h" #include #include #include #include #include #include #include "kpLogCategories.h" #include #include "commands/kpCommandSize.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "pixmapfx/kpPixmapFX.h" #include "generic/widgets/kpResizeSignallingLabel.h" #include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" // protected static const QSize kpDocumentSaveOptionsPreviewDialog::s_pixmapLabelMinimumSize (25, 25); kpDocumentSaveOptionsPreviewDialog::kpDocumentSaveOptionsPreviewDialog ( QWidget *parent ) : kpSubWindow (parent), m_filePixmap (nullptr), m_fileSize (0) { setWindowTitle (i18nc ("@title:window", "Save Preview")); auto *baseWidget = this;//new QWidget (this); //setMainWidget (baseWidget); auto *lay = new QGridLayout ( baseWidget ); m_filePixmapLabel = new kpResizeSignallingLabel (baseWidget); m_fileSizeLabel = new QLabel (baseWidget); m_filePixmapLabel->setMinimumSize (s_pixmapLabelMinimumSize); lay->addWidget (m_filePixmapLabel, 0, 0); lay->addWidget (m_fileSizeLabel, 1, 0, Qt::AlignHCenter); lay->setRowStretch (0, 1); connect (m_filePixmapLabel, &kpResizeSignallingLabel::resized, this, &kpDocumentSaveOptionsPreviewDialog::updatePixmapPreview); } kpDocumentSaveOptionsPreviewDialog::~kpDocumentSaveOptionsPreviewDialog () { delete m_filePixmap; } // public QSize kpDocumentSaveOptionsPreviewDialog::preferredMinimumSize () const { const auto contentsWidth = 180; const auto totalMarginsWidth = fontMetrics ().height (); return {contentsWidth + totalMarginsWidth, contentsWidth * 3 / 4 + totalMarginsWidth}; } // public slot void kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize (const QImage &pixmap, qint64 fileSize) { delete m_filePixmap; m_filePixmap = new QImage (pixmap); updatePixmapPreview (); m_fileSize = fileSize; const kpCommandSize::SizeType pixmapSize = kpCommandSize::PixmapSize (pixmap); // (int cast is safe as long as the file size is not more than 20 million // -- i.e. INT_MAX / 100 -- times the pixmap size) const int percent = pixmapSize ? qMax (1, static_cast (static_cast (fileSize * 100 / pixmapSize))) : 0; +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize()" << " pixmapSize=" << pixmapSize << " fileSize=" << fileSize << " raw fileSize/pixmapSize%=" << (pixmapSize ? (kpCommandSize::SizeType) fileSize * 100 / pixmapSize : 0); +#endif m_fileSizeLabel->setText (i18np ("1 byte (approx. %2%)", "%1 bytes (approx. %2%)", m_fileSize, percent)); } // public slot void kpDocumentSaveOptionsPreviewDialog::updatePixmapPreview () { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::updatePreviewPixmap()" << " filePixmapLabel.size=" << m_filePixmapLabel->size () << " filePixmap.size=" << m_filePixmap->size (); +#endif if (m_filePixmap) { int maxNewWidth = qMin (m_filePixmap->width (), m_filePixmapLabel->width ()), maxNewHeight = qMin (m_filePixmap->height (), m_filePixmapLabel->height ()); double keepsAspect = kpTransformPreviewDialog::aspectScale ( maxNewWidth, maxNewHeight, m_filePixmap->width (), m_filePixmap->height ()); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "\tmaxNewWidth=" << maxNewWidth << " maxNewHeight=" << maxNewHeight << " keepsAspect=" << keepsAspect; - + #endif const int newWidth = kpTransformPreviewDialog::scaleDimension ( m_filePixmap->width (), keepsAspect, 1, maxNewWidth); const int newHeight = kpTransformPreviewDialog::scaleDimension ( m_filePixmap->height (), keepsAspect, 1, maxNewHeight); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "\tnewWidth=" << newWidth << " newHeight=" << newHeight; - + #endif QImage transformedPixmap = kpPixmapFX::scale (*m_filePixmap, newWidth, newHeight); QImage labelPixmap (m_filePixmapLabel->width (), m_filePixmapLabel->height (), QImage::Format_ARGB32_Premultiplied); labelPixmap.fill(QColor(Qt::transparent).rgba()); kpPixmapFX::setPixmapAt (&labelPixmap, (labelPixmap.width () - transformedPixmap.width ()) / 2, (labelPixmap.height () - transformedPixmap.height ()) / 2, transformedPixmap); m_filePixmapLabel->setPixmap (QPixmap::fromImage(labelPixmap)); } else { m_filePixmapLabel->setPixmap (QPixmap ()); } } // protected virtual [base QWidget] void kpDocumentSaveOptionsPreviewDialog::closeEvent (QCloseEvent *e) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::closeEvent()"; +#endif + QWidget::closeEvent (e); + emit finished (); } // protected virtual [base QWidget] void kpDocumentSaveOptionsPreviewDialog::moveEvent (QMoveEvent *e) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::moveEvent()"; +#endif + QWidget::moveEvent (e); + emit moved (); } // protected virtual [base QWidget] void kpDocumentSaveOptionsPreviewDialog::resizeEvent (QResizeEvent *e) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::resizeEvent()"; +#endif + QWidget::resizeEvent (e); + emit resized (); } diff --git a/document/kpDocument.cpp b/document/kpDocument.cpp index 4f712168..b60a38db 100644 --- a/document/kpDocument.cpp +++ b/document/kpDocument.cpp @@ -1,448 +1,456 @@ /* Copyright (c) 2003-2007 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 "kpDocument.h" #include "kpDocumentPrivate.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "document/kpDocumentSaveOptions.h" #include "imagelib/kpDocumentMetaInfo.h" #include "imagelib/effects/kpEffectReduceColors.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "lgpl/generic/kpUrlFormatter.h" #include "kpLogCategories.h" #include // kdelibs4support #include #include #include #include #include #include #include #include #include #include #include #include //--------------------------------------------------------------------- kpDocument::kpDocument (int w, int h, kpDocumentEnvironment *environ) : QObject (), m_constructorWidth (w), m_constructorHeight (h), m_isFromURL (false), m_savedAtLeastOnceBefore (false), m_saveOptions (new kpDocumentSaveOptions ()), m_metaInfo (new kpDocumentMetaInfo ()), m_modified (false), m_selection (nullptr), m_oldWidth (-1), m_oldHeight (-1), d (new kpDocumentPrivate ()) { #if DEBUG_KP_DOCUMENT && 0 qCDebug(kpLogDocument) << "kpDocument::kpDocument (" << w << "," << h << ")"; #endif m_image = new kpImage(w, h, QImage::Format_ARGB32_Premultiplied); m_image->fill(QColor(Qt::white).rgb()); d->environ = environ; } //--------------------------------------------------------------------- kpDocument::~kpDocument () { delete d; delete m_image; delete m_saveOptions; delete m_metaInfo; delete m_selection; } //--------------------------------------------------------------------- // public kpDocumentEnvironment *kpDocument::environ () const { return d->environ; } //--------------------------------------------------------------------- // public void kpDocument::setEnviron (kpDocumentEnvironment *environ) { d->environ = environ; } //--------------------------------------------------------------------- // public bool kpDocument::savedAtLeastOnceBefore () const { return m_savedAtLeastOnceBefore; } //--------------------------------------------------------------------- // public QUrl kpDocument::url () const { return m_url; } //--------------------------------------------------------------------- // public void kpDocument::setURL (const QUrl &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, KIO::NetAccess::SourceSide/*open*/, d->environ->dialogParent ())); } //--------------------------------------------------------------------- // public QString kpDocument::prettyUrl () const { return kpUrlFormatter::PrettyUrl (m_url); } //--------------------------------------------------------------------- // public QString kpDocument::prettyFilename () const { return kpUrlFormatter::PrettyFilename (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 { return (ofSelection && m_selection) ? m_selection->width() : m_image->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 -{ +{ return (ofSelection && m_selection) ? m_selection->height() : m_image->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 -{ +{ return (ofSelection && m_selection) ? m_selection->boundingRect() : m_image->rect(); } //--------------------------------------------------------------------- // public kpImage kpDocument::getImageAt (const QRect &rect) const { return kpPixmapFX::getPixmapAt (*m_image, rect); } //--------------------------------------------------------------------- // public void kpDocument::setImageAt (const kpImage &image, const QPoint &at) { +#if DEBUG_KP_DOCUMENT && 0 qCDebug(kpLogDocument) << "kpDocument::setImageAt (image (w=" << image.width () << ",h=" << image.height () << "), x=" << at.x () << ",y=" << at.y (); +#endif kpPixmapFX::setPixmapAt (m_image, at, image); slotContentsChanged (QRect (at.x (), at.y (), image.width (), image.height ())); } //--------------------------------------------------------------------- // public kpImage kpDocument::image (bool ofSelection) const { kpImage ret; if (ofSelection) { kpAbstractImageSelection *imageSel = imageSelection (); Q_ASSERT (imageSel); ret = imageSel->baseImage (); } else { ret = *m_image; } return ret; } //--------------------------------------------------------------------- // public kpImage *kpDocument::imagePointer () const { return m_image; } //--------------------------------------------------------------------- // public void kpDocument::setImage (const kpImage &image) { m_oldWidth = width (); m_oldHeight = height (); *m_image = image; if (m_oldWidth == width () && m_oldHeight == height ()) { slotContentsChanged (image.rect ()); } else { slotSizeChanged (QSize (width (), height ())); } } //--------------------------------------------------------------------- // public void kpDocument::setImage (bool ofSelection, const kpImage &image) { if (ofSelection) { kpAbstractImageSelection *imageSel = imageSelection (); // Have to have an image selection in order to set its pixmap. Q_ASSERT (imageSel); imageSel->setBaseImage (image); } else { setImage (image); } } //--------------------------------------------------------------------- void kpDocument::fill (const kpColor &color) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::fill ()"; +#endif m_image->fill(color.toQRgb()); slotContentsChanged (m_image->rect ()); } //--------------------------------------------------------------------- void kpDocument::resize (int w, int h, const kpColor &backgroundColor) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::resize (" << w << "," << h << ")"; +#endif m_oldWidth = width (); m_oldHeight = height (); +#if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\toldWidth=" << m_oldWidth << " oldHeight=" << m_oldHeight; +#endif if (w == m_oldWidth && h == m_oldHeight) { return; } kpPixmapFX::resize (m_image, w, h, backgroundColor); slotSizeChanged (QSize (width (), height ())); } //--------------------------------------------------------------------- void kpDocument::slotContentsChanged (const QRect &rect) { setModified (); emit contentsChanged (rect); } //--------------------------------------------------------------------- void kpDocument::slotSizeChanged (const QSize &newSize) { setModified (); emit sizeChanged (newSize.width(), newSize.height()); emit sizeChanged (newSize); } //--------------------------------------------------------------------- diff --git a/document/kpDocumentSaveOptions.cpp b/document/kpDocumentSaveOptions.cpp index 57828e86..da4e3c07 100644 --- a/document/kpDocumentSaveOptions.cpp +++ b/document/kpDocumentSaveOptions.cpp @@ -1,618 +1,622 @@ /* Copyright (c) 2003-2007 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_SAVE_OPTIONS 0 #include "kpDocumentSaveOptions.h" #include "kpDefs.h" #include "pixmapfx/kpPixmapFX.h" #include #include "kpLogCategories.h" #include #include #include #include //--------------------------------------------------------------------- class kpDocumentSaveOptionsPrivate { public: QString m_mimeType; int m_colorDepth{}; bool m_dither{}; int m_quality{}; }; //--------------------------------------------------------------------- kpDocumentSaveOptions::kpDocumentSaveOptions () : d (new kpDocumentSaveOptionsPrivate ()) { d->m_mimeType = invalidMimeType (); d->m_colorDepth = invalidColorDepth (); d->m_dither = initialDither (); d->m_quality = invalidQuality (); } //--------------------------------------------------------------------- kpDocumentSaveOptions::kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs) : d (new kpDocumentSaveOptionsPrivate ()) { d->m_mimeType = rhs.mimeType (); d->m_colorDepth = rhs.colorDepth (); d->m_dither = rhs.dither (); d->m_quality = rhs.quality (); } //--------------------------------------------------------------------- kpDocumentSaveOptions::kpDocumentSaveOptions (QString mimeType, int colorDepth, bool dither, int quality) : d (new kpDocumentSaveOptionsPrivate ()) { d->m_mimeType = mimeType; d->m_colorDepth = colorDepth; d->m_dither = dither; d->m_quality = quality; } //--------------------------------------------------------------------- kpDocumentSaveOptions::~kpDocumentSaveOptions () { delete d; } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::operator== (const kpDocumentSaveOptions &rhs) const { return (mimeType () == rhs.mimeType () && colorDepth () == rhs.colorDepth () && dither () == rhs.dither () && quality () == rhs.quality ()); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::operator!= (const kpDocumentSaveOptions &rhs) const { return !(*this == rhs); } //--------------------------------------------------------------------- // public kpDocumentSaveOptions &kpDocumentSaveOptions::operator= (const kpDocumentSaveOptions &rhs) { setMimeType (rhs.mimeType ()); setColorDepth (rhs.colorDepth ()); setDither (rhs.dither ()); setQuality (rhs.quality ()); return *this; } //--------------------------------------------------------------------- // public void kpDocumentSaveOptions::printDebug (const QString &prefix) const { const QString usedPrefix = !prefix.isEmpty () ? prefix + QLatin1String (": ") : QString(); qCDebug(kpLogDocument) << usedPrefix << "mimeType=" << mimeType () << " colorDepth=" << colorDepth () << " dither=" << dither () << " quality=" << quality (); } //--------------------------------------------------------------------- // public QString kpDocumentSaveOptions::mimeType () const { return d->m_mimeType; } //--------------------------------------------------------------------- // public void kpDocumentSaveOptions::setMimeType (const QString &mimeType) { d->m_mimeType = mimeType; } //--------------------------------------------------------------------- // public static QString kpDocumentSaveOptions::invalidMimeType () { return QString(); } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::mimeTypeIsInvalid (const QString &mimeType) { return (mimeType == invalidMimeType ()); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::mimeTypeIsInvalid () const { return mimeTypeIsInvalid (mimeType ()); } //--------------------------------------------------------------------- // public int kpDocumentSaveOptions::colorDepth () const { return d->m_colorDepth; } // public void kpDocumentSaveOptions::setColorDepth (int depth) { d->m_colorDepth = depth; } // public static int kpDocumentSaveOptions::invalidColorDepth () { return -1; } // public static bool kpDocumentSaveOptions::colorDepthIsInvalid (int colorDepth) { return (colorDepth != 1 && colorDepth != 8 && colorDepth != 32); } // public bool kpDocumentSaveOptions::colorDepthIsInvalid () const { return colorDepthIsInvalid (colorDepth ()); } // public bool kpDocumentSaveOptions::dither () const { return d->m_dither; } // public void kpDocumentSaveOptions::setDither (bool dither) { d->m_dither = dither; } // public static int kpDocumentSaveOptions::initialDither () { return false; // to avoid accidental double dithering } // public int kpDocumentSaveOptions::quality () const { return d->m_quality; } // public void kpDocumentSaveOptions::setQuality (int quality) { d->m_quality = quality; } // public static int kpDocumentSaveOptions::invalidQuality () { return -2; } // public static bool kpDocumentSaveOptions::qualityIsInvalid (int quality) { return (quality < -1 || quality > 100); } // public bool kpDocumentSaveOptions::qualityIsInvalid () const { return qualityIsInvalid (quality ()); } // public static QString kpDocumentSaveOptions::defaultMimeType (const KConfigGroup &config) { return config.readEntry (kpSettingForcedMimeType, QString::fromLatin1 ("image/png")); } // public static void kpDocumentSaveOptions::saveDefaultMimeType (KConfigGroup &config, const QString &mimeType) { config.writeEntry (kpSettingForcedMimeType, mimeType); } // public static int kpDocumentSaveOptions::defaultColorDepth (const KConfigGroup &config) { int colorDepth = config.readEntry (kpSettingForcedColorDepth, -1); if (colorDepthIsInvalid (colorDepth)) { // (not screen depth, in case of transparency) colorDepth = 32; } return colorDepth; } //--------------------------------------------------------------------- // public static void kpDocumentSaveOptions::saveDefaultColorDepth (KConfigGroup &config, int colorDepth) { config.writeEntry (kpSettingForcedColorDepth, colorDepth); } //--------------------------------------------------------------------- // public static int kpDocumentSaveOptions::defaultDither (const KConfigGroup &config) { return config.readEntry (kpSettingForcedDither, initialDither ()); } //--------------------------------------------------------------------- // public static void kpDocumentSaveOptions::saveDefaultDither (KConfigGroup &config, bool dither) { config.writeEntry (kpSettingForcedDither, dither); } //--------------------------------------------------------------------- // public static int kpDocumentSaveOptions::defaultQuality (const KConfigGroup &config) { int val = config.readEntry (kpSettingForcedQuality, -1); return qualityIsInvalid (val) ? -1 : val; } //--------------------------------------------------------------------- // public static void kpDocumentSaveOptions::saveDefaultQuality (KConfigGroup &config, int quality) { config.writeEntry (kpSettingForcedQuality, quality); } //--------------------------------------------------------------------- // public static kpDocumentSaveOptions kpDocumentSaveOptions::defaultDocumentSaveOptions (const KConfigGroup &config) { kpDocumentSaveOptions saveOptions; saveOptions.setMimeType (defaultMimeType (config)); saveOptions.setColorDepth (defaultColorDepth (config)); saveOptions.setDither (defaultDither (config)); saveOptions.setQuality (defaultQuality (config)); +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS saveOptions.printDebug ("kpDocumentSaveOptions::defaultDocumentSaveOptions()"); +#endif return saveOptions; } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::saveDefaultDifferences (KConfigGroup &config, const kpDocumentSaveOptions &oldDocInfo, const kpDocumentSaveOptions &newDocInfo) { bool savedSomething = false; +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS qCDebug(kpLogDocument) << "kpDocumentSaveOptions::saveDefaultDifferences()"; oldDocInfo.printDebug ("\told"); newDocInfo.printDebug ("\tnew"); +#endif if (newDocInfo.mimeType () != oldDocInfo.mimeType ()) { saveDefaultMimeType (config, newDocInfo.mimeType ()); savedSomething = true; } if (newDocInfo.colorDepth () != oldDocInfo.colorDepth ()) { saveDefaultColorDepth (config, newDocInfo.colorDepth ()); savedSomething = true; } if (newDocInfo.dither () != oldDocInfo.dither ()) { saveDefaultDither (config, newDocInfo.dither ()); savedSomething = true; } if (newDocInfo.quality () != oldDocInfo.quality ()) { saveDefaultQuality (config, newDocInfo.quality ()); savedSomething = true; } return savedSomething; } //--------------------------------------------------------------------- static QStringList mimeTypesSupportingProperty (const QString &property, const QStringList &defaultMimeTypesWithPropertyList) { QStringList mimeTypeList; KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupMimeTypeProperties); if (cfg.hasKey (property)) { mimeTypeList = cfg.readEntry (property, QStringList ()); } else { mimeTypeList = defaultMimeTypesWithPropertyList; cfg.writeEntry (property, mimeTypeList); cfg.sync (); } return mimeTypeList; } //--------------------------------------------------------------------- static bool mimeTypeSupportsProperty (const QString &mimeType, const QString &property, const QStringList &defaultMimeTypesWithPropertyList) { const auto mimeTypeList = mimeTypesSupportingProperty ( property, defaultMimeTypesWithPropertyList); return mimeTypeList.contains (mimeType); } //--------------------------------------------------------------------- // SYNC: update mime info // // Only care about writable mimetypes. // // Run: // // branches/kolourpaint/control/scripts/gen_mimetype_line.sh Write | // branches/kolourpaint/control/scripts/split_mimetype_line.pl // // in the version of kdelibs/kimgio/ (e.g. KDE 4.0) KolourPaint is shipped with, // to check for any new mimetypes to add info for. In the methods below, // you can specify this info (maximum color depth, whether it's lossy etc.). // // Update the below list and if you do change any of that info, bump up // "kpSettingsGroupMimeTypeProperties" in kpDefs.h. // // Currently, Depth and Quality settings are mutually exclusive with // Depth overriding Quality. I've currently favoured Quality with the // below mimetypes (i.e. all lossy mimetypes are only given Quality settings, // no Depth settings). // // Mimetypes done: // image/bmp // image/jpeg // image/jp2 // image/png // image/tiff // image/x-eps // image/x-pcx // image/x-portable-bitmap // image/x-portable-graymap // image/x-portable-pixmap // image/x-rgb // image/x-tga // image/x-xbitmap // image/x-xpixmap // video/x-mng [COULD NOT TEST] // // To test whether depth is configurable, write an image in the new // mimetype with all depths and read each one back. See what // kpDocument thinks the depth is when it gets QImage to read it. // public static int kpDocumentSaveOptions::mimeTypeMaximumColorDepth (const QString &mimeType) { QStringList defaultList; // SYNC: update mime info here // Grayscale actually (unenforced since depth not set to configurable) defaultList << QLatin1String ("image/x-eps:32"); defaultList << QLatin1String ("image/x-portable-bitmap:1"); // Grayscale actually (unenforced since depth not set to configurable) defaultList << QLatin1String ("image/x-portable-graymap:8"); defaultList << QLatin1String ("image/x-xbitmap:1"); const auto mimeTypeList = mimeTypesSupportingProperty ( kpSettingMimeTypeMaximumColorDepth, defaultList); const QString mimeTypeColon = mimeType + QLatin1String (":"); for (const auto & it : mimeTypeList) { if (it.startsWith (mimeTypeColon)) { int number = it.mid (mimeTypeColon.length ()).toInt (); if (!colorDepthIsInvalid (number)) { return number; } } } return 32; } //--------------------------------------------------------------------- // public int kpDocumentSaveOptions::mimeTypeMaximumColorDepth () const { return mimeTypeMaximumColorDepth (mimeType ()); } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (const QString &mimeType) { QStringList defaultMimeTypes; // SYNC: update mime info here defaultMimeTypes << QLatin1String ("image/png"); defaultMimeTypes << QLatin1String ("image/bmp"); defaultMimeTypes << QLatin1String ("image/x-pcx"); // TODO: Only 1, 24 not 8; Qt only sees 32 but "file" cmd realizes // it's either 1 or 24. defaultMimeTypes << QLatin1String ("image/x-rgb"); // TODO: Only 8 and 24 - no 1. defaultMimeTypes << QLatin1String ("image/x-xpixmap"); return mimeTypeSupportsProperty (mimeType, kpSettingMimeTypeHasConfigurableColorDepth, defaultMimeTypes); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth () const { return mimeTypeHasConfigurableColorDepth (mimeType ()); } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (const QString &mimeType) { QStringList defaultMimeTypes; // SYNC: update mime info here defaultMimeTypes << QLatin1String ("image/jp2"); defaultMimeTypes << QLatin1String ("image/jpeg"); defaultMimeTypes << QLatin1String ("image/x-webp"); return mimeTypeSupportsProperty (mimeType, kpSettingMimeTypeHasConfigurableQuality, defaultMimeTypes); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality () const { return mimeTypeHasConfigurableQuality (mimeType ()); } //--------------------------------------------------------------------- // public int kpDocumentSaveOptions::isLossyForSaving (const QImage &image) const { auto ret = 0; if (mimeTypeMaximumColorDepth () < image.depth ()) { ret |= MimeTypeMaximumColorDepthLow; } if (mimeTypeHasConfigurableColorDepth () && !colorDepthIsInvalid () /*REFACTOR: guarantee it is valid*/ && ((colorDepth () < image.depth ()) || (colorDepth () < 32 && image.hasAlphaChannel()))) { ret |= ColorDepthLow; } if (mimeTypeHasConfigurableQuality () && !qualityIsInvalid ()) { ret |= Quality; } return ret; } //--------------------------------------------------------------------- diff --git a/document/kpDocument_Open.cpp b/document/kpDocument_Open.cpp index cac002e1..8155a62c 100644 --- a/document/kpDocument_Open.cpp +++ b/document/kpDocument_Open.cpp @@ -1,233 +1,243 @@ /* Copyright (c) 2003-2007 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 "kpDocument.h" #include "kpDocumentPrivate.h" #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "document/kpDocumentSaveOptions.h" #include "imagelib/kpDocumentMetaInfo.h" #include "imagelib/effects/kpEffectReduceColors.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "lgpl/generic/kpUrlFormatter.h" #include "views/manager/kpViewManager.h" #include #include #include #include #include "kpLogCategories.h" #include // kdelibs4support #include #include //--------------------------------------------------------------------- void kpDocument::getDataFromImage(const QImage &image, kpDocumentSaveOptions &saveOptions, kpDocumentMetaInfo &metaInfo) { saveOptions.setColorDepth(image.depth()); saveOptions.setDither(false); // avoid double dithering when saving metaInfo.setDotsPerMeterX(image.dotsPerMeterX()); metaInfo.setDotsPerMeterY(image.dotsPerMeterY()); metaInfo.setOffset(image.offset()); QStringList keys = image.textKeys(); for (int i = 0; i < keys.count(); i++) { metaInfo.setText(keys[i], image.text(keys[i])); } } //--------------------------------------------------------------------- // public static QImage kpDocument::getPixmapFromFile(const QUrl &url, bool suppressDoesntExistDialog, QWidget *parent, kpDocumentSaveOptions *saveOptions, kpDocumentMetaInfo *metaInfo) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")"; +#endif if (saveOptions) { *saveOptions = kpDocumentSaveOptions (); } if (metaInfo) { *metaInfo = kpDocumentMetaInfo (); } QString tempFile; if (url.isEmpty () || !KIO::NetAccess::download (url, tempFile, parent)) { if (!suppressDoesntExistDialog) { // TODO: Use "Cannot" instead of "Could not" in all dialogs in KolourPaint. // Or at least choose one consistently. // // TODO: Have captions for all dialogs in KolourPaint. KMessageBox::sorry (parent, i18n ("Could not open \"%1\".", kpUrlFormatter::PrettyFilename (url))); } return QImage (); } QMimeDatabase db; QMimeType mimeType = db.mimeTypeForFile(tempFile); if (saveOptions) { saveOptions->setMimeType(mimeType.name()); } +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\ttempFile=" << tempFile; qCDebug(kpLogDocument) << "\tmimetype=" << mimeType.name(); qCDebug(kpLogDocument) << "\tsrc=" << url.path (); +#endif QImageReader reader(tempFile); reader.setAutoTransform(true); reader.setDecideFormatFromContent(true); QImage image = reader.read(); KIO::NetAccess::removeTempFile(tempFile); if (image.isNull ()) { KMessageBox::sorry (parent, i18n ("Could not open \"%1\" - unsupported image format.\n" "The file may be corrupt.", kpUrlFormatter::PrettyFilename (url))); return QImage (); } +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tpixmap: depth=" << image.depth () << " hasAlphaChannel=" << image.hasAlphaChannel (); +#endif if ( saveOptions && metaInfo ) { getDataFromImage(image, *saveOptions, *metaInfo); } // make sure we always have Format_ARGB32_Premultiplied as this is the fastest to draw on // and Qt can not draw onto Format_Indexed8 (Qt-4.7) if ( image.format() != QImage::Format_ARGB32_Premultiplied ) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); } return image; } //--------------------------------------------------------------------- void kpDocument::openNew (const QUrl &url) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::openNew (" << url << ")"; +#endif m_image->fill(QColor(Qt::white).rgb()); setURL (url, false/*not from url*/); *m_saveOptions = kpDocumentSaveOptions (); if ( !url.isEmpty() ) { // guess the mimetype from url's filename extension. // // That way "kolourpaint doesnotexist.bmp" automatically // selects the BMP file format when the save dialog comes up for // the first time. QMimeDatabase mimeDb; m_saveOptions->setMimeType(mimeDb.mimeTypeForUrl(url).name()); } *m_metaInfo = kpDocumentMetaInfo (); m_modified = false; emit documentOpened (); } //--------------------------------------------------------------------- bool kpDocument::open (const QUrl &url, bool newDocSameNameIfNotExist) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::open (" << url << ")"; +#endif kpDocumentSaveOptions newSaveOptions; kpDocumentMetaInfo newMetaInfo; QImage newPixmap = kpDocument::getPixmapFromFile (url, newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/, d->environ->dialogParent (), &newSaveOptions, &newMetaInfo); if (!newPixmap.isNull ()) { delete m_image; m_image = new kpImage (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, KIO::NetAccess::SourceSide/*open*/, d->environ->dialogParent ())) { openNew (url); } else { openNew (QUrl ()); } return true; } return false; } //--------------------------------------------------------------------- diff --git a/document/kpDocument_Save.cpp b/document/kpDocument_Save.cpp index c65ffd1f..9e2f2cec 100644 --- a/document/kpDocument_Save.cpp +++ b/document/kpDocument_Save.cpp @@ -1,466 +1,513 @@ /* Copyright (c) 2003-2007 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 "kpDocument.h" #include "kpDocumentPrivate.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include // kdelibs4support #include // kdelibs4support #include #include #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "document/kpDocumentSaveOptions.h" #include "imagelib/kpDocumentMetaInfo.h" #include "imagelib/effects/kpEffectReduceColors.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "lgpl/generic/kpUrlFormatter.h" #include "views/manager/kpViewManager.h" bool kpDocument::save (bool overwritePrompt, bool lossyPrompt) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::save(" << "overwritePrompt=" << overwritePrompt << ",lossyPrompt=" << lossyPrompt << ") url=" << m_url << " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore (); +#endif // TODO: check feels weak if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ()) { KMessageBox::detailedError (d->environ->dialogParent (), i18n ("Could not save image - insufficient information."), i18n ("URL: %1\n" "Mimetype: %2", prettyUrl (), m_saveOptions->mimeType ().isEmpty () ? i18n ("") : m_saveOptions->mimeType ()), i18nc ("@title:window", "Internal Error")); return false; } return saveAs (m_url, *m_saveOptions, overwritePrompt, lossyPrompt); } //--------------------------------------------------------------------- // public static bool kpDocument::lossyPromptContinue (const QImage &pixmap, const kpDocumentSaveOptions &saveOptions, QWidget *parent) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::lossyPromptContinue()"; +#endif #define QUIT_IF_CANCEL(messageBoxCommand) \ { \ if (messageBoxCommand != KMessageBox::Continue) \ { \ return false; \ } \ } const int lossyType = saveOptions.isLossyForSaving (pixmap); if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow | kpDocumentSaveOptions::Quality)) { QMimeDatabase db; QUIT_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?

", db.mimeTypeForName(saveOptions.mimeType()).comment()), // TODO: caption misleading for lossless formats that have // low maximum colour depth i18nc ("@title:window", "Lossy File Format"), KStandardGuiItem::save (), KStandardGuiItem::cancel(), QLatin1String ("SaveInLossyMimeTypeDontAskAgain"))); } else if (lossyType & kpDocumentSaveOptions::ColorDepthLow) { QUIT_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." // TODO: It looks like 8-bit QImage's now support alpha. // Update kpDocumentSaveOptions::isLossyForSaving() // and change "might" to "will". " Any transparency might also be removed.

" "

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

", saveOptions.colorDepth ()), i18nc ("@title:window", "Low Color Depth"), KStandardGuiItem::save (), KStandardGuiItem::cancel(), QLatin1String ("SaveAtLowColorDepthDontAskAgain"))); } #undef QUIT_IF_CANCEL return true; } //--------------------------------------------------------------------- // public static bool kpDocument::savePixmapToDevice (const QImage &image, QIODevice *device, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, bool lossyPrompt, QWidget *parent, bool *userCancelled) { if (userCancelled) { *userCancelled = false; } QStringList types = KImageIO::typeForMime (saveOptions.mimeType ()); +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\ttypes=" << types; +#endif if (types.isEmpty ()) { return false; } // It's safe to arbitrarily choose the 0th type as any type in the list // should invoke the same KImageIO image loader. const QString type = types [0]; +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tmimeType=" << saveOptions.mimeType () << " type=" << type; +#endif if (lossyPrompt && !lossyPromptContinue (image, saveOptions, parent)) { if (userCancelled) { *userCancelled = true; } + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because of lossyPrompt"; + #endif return false; } // 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 DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tuseSaveOptionsColorDepth=" << useSaveOptionsColorDepth << "current image depth=" << image.depth () << "save options depth=" << saveOptions.colorDepth (); +#endif QImage imageToSave(image); if (useSaveOptionsColorDepth && imageToSave.depth () != saveOptions.colorDepth ()) { // TODO: I think this erases the mask! // // I suspect this doesn't matter since this is only called to // reduce color depth and QImage's with depth < 32 don't // support masks anyway. // // Later: I think the mask is preserved for 8-bit since Qt4 // seems to support it for QImage. imageToSave = kpEffectReduceColors::convertImageDepth (imageToSave, saveOptions.colorDepth (), saveOptions.dither ()); } // // Write Meta Info // imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ()); imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ()); imageToSave.setOffset (metaInfo.offset ()); QList keyList = metaInfo.textKeys (); for (QList ::const_iterator it = keyList.constBegin (); it != keyList.constEnd (); ++it) { imageToSave.setText (*it, metaInfo.text (*it)); } // // Save at required quality // int quality = -1; // default if (useSaveOptionsQuality) { quality = saveOptions.quality (); } +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tsaving"; +#endif if (!imageToSave.save (device, type.toLatin1 (), quality)) { + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tQImage::save() returned false"; + #endif return false; } + + +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tsave OK"; +#endif return true; } //--------------------------------------------------------------------- static void CouldNotCreateTemporaryFileDialog (QWidget *parent) { KMessageBox::error (parent, i18n ("Could not save image - unable to create temporary file.")); } //--------------------------------------------------------------------- static void CouldNotSaveDialog (const QUrl &url, QWidget *parent) { // TODO: use file.errorString() KMessageBox::error (parent, i18n ("Could not save as \"%1\".", kpUrlFormatter::PrettyFilename (url))); } //--------------------------------------------------------------------- // public static bool kpDocument::savePixmapToFile (const QImage &pixmap, const QUrl &url, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, bool overwritePrompt, bool lossyPrompt, QWidget *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 qCDebug(kpLogDocument) << "kpDocument::savePixmapToFile (" << url << ",overwritePrompt=" << overwritePrompt << ",lossyPrompt=" << lossyPrompt << ")"; saveOptions.printDebug (QLatin1String ("\tsaveOptions")); metaInfo.printDebug (QLatin1String ("\tmetaInfo")); +#endif if (overwritePrompt && KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent)) { int result = KMessageBox::warningContinueCancel (parent, i18n ("A document called \"%1\" already exists.\n" "Do you want to overwrite it?", kpUrlFormatter::PrettyFilename (url)), QString(), KStandardGuiItem::overwrite ()); if (result != KMessageBox::Continue) { + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tuser doesn't want to overwrite"; + #endif + return false; } } if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent)) { + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because of lossyPrompt"; + #endif return false; } // Local file? if (url.isLocalFile ()) { const QString filename = url.toLocalFile (); // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or // else, the QSaveFile destructor will overwrite the file, // , despite the failure. QSaveFile atomicFileWriter (filename); { if (!atomicFileWriter.open (QIODevice::WriteOnly)) { // We probably don't need this as has not been // opened. atomicFileWriter.cancelWriting (); + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not open QSaveFile" - << " error=" << atomicFileWriter.error (); + << " error=" << atomicFileWriter.error () << endl; + #endif ::CouldNotCreateTemporaryFileDialog (parent); return false; } // Write to local temporary file. if (!savePixmapToDevice (pixmap, &atomicFileWriter, saveOptions, metaInfo, false/*no lossy prompt*/, parent)) { atomicFileWriter.cancelWriting (); - qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device"; + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\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.commit ()) { atomicFileWriter.cancelWriting (); + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tcould not close QSaveFile"; + #endif ::CouldNotSaveDialog (url, parent); return false; } } // sync QSaveFile.cancelWriting() } // Remote file? else { // Create temporary file that is deleted when the variable goes // out of scope. QTemporaryFile tempFile; if (!tempFile.open ()) { + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not open tempFile"; + #endif ::CouldNotCreateTemporaryFileDialog (parent); return false; } // Write to local temporary file. if (!savePixmapToDevice (pixmap, &tempFile, saveOptions, metaInfo, false/*no lossy prompt*/, parent)) { - qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device"; + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device" + << endl; + #endif ::CouldNotSaveDialog (url, parent); return false; } // Collect name of temporary file now, as QTemporaryFile::fileName() // stops working after close() is called. const QString tempFileName = tempFile.fileName (); + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\ttempFileName='" << tempFileName << "'"; + #endif Q_ASSERT (!tempFileName.isEmpty ()); tempFile.close (); if (tempFile.error () != QFile::NoError) { + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not close"; + #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 (tempFileName, url, parent)) { + #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not upload"; + #endif KMessageBox::error (parent, i18n ("Could not save image - failed to upload.")); return false; } } return true; } //--------------------------------------------------------------------- bool kpDocument::saveAs (const QUrl &url, const kpDocumentSaveOptions &saveOptions, bool overwritePrompt, bool lossyPrompt) { +#if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::saveAs (" << url << "," - << saveOptions.mimeType () << ")"; + << saveOptions.mimeType () << ")" << endl; +#endif if (kpDocument::savePixmapToFile (imageWithSelection (), url, saveOptions, *metaInfo (), overwritePrompt, lossyPrompt, d->environ->dialogParent ())) { setURL (url, true/*is from url*/); *m_saveOptions = saveOptions; m_modified = false; m_savedAtLeastOnceBefore = true; emit documentSaved (); return true; } return false; } //--------------------------------------------------------------------- diff --git a/document/kpDocument_Selection.cpp b/document/kpDocument_Selection.cpp index 2a86bc8b..04ae4721 100644 --- a/document/kpDocument_Selection.cpp +++ b/document/kpDocument_Selection.cpp @@ -1,336 +1,350 @@ /* Copyright (c) 2003-2007 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 "kpDocument.h" #include "kpDocumentPrivate.h" #include #include #include #include "kpLogCategories.h" #include #include "imagelib/kpColor.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "layers/selections/text/kpTextSelection.h" // public kpAbstractSelection *kpDocument::selection () const { return m_selection; } //--------------------------------------------------------------------- // public kpAbstractImageSelection *kpDocument::imageSelection () const { return dynamic_cast (m_selection); } //--------------------------------------------------------------------- // public kpTextSelection *kpDocument::textSelection () const { return dynamic_cast (m_selection); } //--------------------------------------------------------------------- // public void kpDocument::setSelection (const kpAbstractSelection &selection) { +#if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "kpDocument::setSelection() sel boundingRect=" << selection.boundingRect (); +#endif d->environ->setQueueViewUpdates (); { const bool hadSelection = static_cast (m_selection); auto *oldSelection = m_selection; // (must be called before giving the document a new selection, to // avoid a potential mess where switchToCompatibleTool() ends // the current selection tool, killing the new selection) bool isTextChanged = false; d->environ->switchToCompatibleTool (selection, &isTextChanged); Q_ASSERT (m_selection == oldSelection); m_selection = selection.clone (); // There's no need to uninitialize the old selection // (e.g. call disconnect()) since we delete it later. connect (m_selection, &kpAbstractSelection::changed, this, &kpDocument::slotContentsChanged); // // Now all kpDocument state has been set. // We can _only_ change the environment after that, as the environment // may access the document. Exception is above with // switchToCompatibleTool(). // d->environ->assertMatchingUIState (selection); // // Now all kpDocument and environment state has been set. // We can _only_ fire signals after that, as the signal receivers (the // "wider environment") may access the document and the environment. // + #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tcheck sel " << (int *) m_selection << " boundingRect=" << m_selection->boundingRect (); + #endif if (oldSelection) { if (oldSelection->hasContent ()) { slotContentsChanged (oldSelection->boundingRect ()); } else { emit contentsChanged (oldSelection->boundingRect ()); } delete oldSelection; oldSelection = nullptr; } if (m_selection->hasContent ()) { slotContentsChanged (m_selection->boundingRect ()); } else { emit contentsChanged (m_selection->boundingRect ()); } if (!hadSelection) { emit selectionEnabled (true); } if (isTextChanged) { emit selectionIsTextChanged (textSelection ()); } } d->environ->restoreQueueViewUpdates (); +#if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tkpDocument::setSelection() ended"; +#endif } //--------------------------------------------------------------------- // public kpImage kpDocument::getSelectedBaseImage () const { auto *imageSel = imageSelection (); Q_ASSERT (imageSel); // Easy if we already have it :) const auto image = imageSel->baseImage (); if (!image.isNull ()) { return image; } const auto boundingRect = imageSel->boundingRect (); Q_ASSERT (boundingRect.isValid ()); // OPT: This is very slow. Image / More Effects ... calls us twice // unnecessarily. return imageSel->givenImageMaskedByShape (getImageAt (boundingRect)); } //--------------------------------------------------------------------- // public void kpDocument::imageSelectionPullFromDocument (const kpColor &backgroundColor) { auto *imageSel = imageSelection (); Q_ASSERT (imageSel); // Should not already have an image or we would not be pulling. Q_ASSERT (!imageSel->hasContent ()); const auto boundingRect = imageSel->boundingRect (); Q_ASSERT (boundingRect.isValid ()); // // Get selection image from document // auto selectedImage = getSelectedBaseImage (); d->environ->setQueueViewUpdates (); imageSel->setBaseImage (selectedImage); // // Fill opaque bits of the hole in the document // #if !defined (QT_NO_DEBUG) && !defined (NDEBUG) if (imageSel->transparency ().isTransparent ()) { Q_ASSERT (backgroundColor == imageSel->transparency ().transparentColor ()); } else { // If this method is begin called by a tool, the assert does not // have to hold since transparentColor() might not be defined in Opaque // Mode. // // If we were called by a tricky sequence of undo/redo commands, the assert // does not have to hold for additional reason, which is that // kpMainWindow::setImageSelectionTransparency() does not have to // set in Opaque Mode. // // In practice, it probably does hold but I wouldn't bet on it. } #endif kpImage eraseImage(boundingRect.size(), QImage::Format_ARGB32_Premultiplied); eraseImage.fill(backgroundColor.toQRgb()); // only paint the region of the shape of the selection QPainter painter(m_image); painter.setClipRegion(imageSel->shapeRegion()); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(boundingRect.topLeft(), eraseImage); slotContentsChanged(boundingRect); d->environ->restoreQueueViewUpdates (); } //--------------------------------------------------------------------- // public void kpDocument::selectionDelete () { if ( !m_selection ) { return; } const auto boundingRect = m_selection->boundingRect (); Q_ASSERT (boundingRect.isValid ()); const auto selectionHadContent = m_selection->hasContent (); delete m_selection; m_selection = nullptr; // HACK to prevent document from being modified when // user cancels dragging out a new selection // REFACTOR: Extract this out into a method. if (selectionHadContent) { slotContentsChanged (boundingRect); } else { emit contentsChanged (boundingRect); } emit selectionEnabled (false); } //--------------------------------------------------------------------- // public void kpDocument::selectionCopyOntoDocument (bool applySelTransparency) { // Empty selection, just doing nothing if ( !m_selection || !m_selection->hasContent() ) { return; } const QRect boundingRect = m_selection->boundingRect (); Q_ASSERT (boundingRect.isValid ()); if (imageSelection ()) { if (applySelTransparency) { imageSelection ()->paint (m_image, rect ()); } else { imageSelection ()->paintWithBaseImage (m_image, rect ()); } } else { // (for antialiasing with background) m_selection->paint (m_image, rect ()); } slotContentsChanged (boundingRect); } //--------------------------------------------------------------------- // public void kpDocument::selectionPushOntoDocument (bool applySelTransparency) { selectionCopyOntoDocument (applySelTransparency); selectionDelete (); } //--------------------------------------------------------------------- // public kpImage kpDocument::imageWithSelection () const { +#if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "kpDocument::imageWithSelection()"; +#endif // Have selection? // // It need not have any content because e.g. a text box with an opaque // background, but no content, is still visually there. if (m_selection) { + #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tselection @ " << m_selection->boundingRect (); - auto output = *m_image; + #endif + kpImage output = *m_image; // (this is a NOP for image selections without content) m_selection->paint (&output, rect ()); return output; } - - qCDebug(kpLogDocument) << "\tno selection"; - return *m_image; + else + { + #if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "\tno selection"; + #endif + return *m_image; + } } //--------------------------------------------------------------------- diff --git a/environments/document/kpDocumentEnvironment.cpp b/environments/document/kpDocumentEnvironment.cpp index ed8d6425..21ac912a 100644 --- a/environments/document/kpDocumentEnvironment.cpp +++ b/environments/document/kpDocumentEnvironment.cpp @@ -1,194 +1,211 @@ /* Copyright (c) 2003-2007 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_ENVIRONMENT 0 + + #include "environments/document/kpDocumentEnvironment.h" #include "kpLogCategories.h" #include "mainWindow/kpMainWindow.h" #include "layers/selections/kpAbstractSelection.h" #include "document/kpDocument.h" #include "layers/selections/image/kpEllipticalImageSelection.h" #include "layers/selections/image/kpFreeFormImageSelection.h" #include "layers/selections/image/kpImageSelectionTransparency.h" #include "layers/selections/image/kpRectangularImageSelection.h" #include "layers/selections/text/kpTextSelection.h" #include "layers/selections/text/kpTextStyle.h" -#include "tools/kpTool.h" +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + #include "tools/kpTool.h" +#endif #include "views/manager/kpViewManager.h" struct kpDocumentEnvironmentPrivate { }; kpDocumentEnvironment::kpDocumentEnvironment (kpMainWindow *mainWindow) : kpEnvironmentBase (mainWindow), d (new kpDocumentEnvironmentPrivate ()) { } kpDocumentEnvironment::~kpDocumentEnvironment () { delete d; } // public QWidget *kpDocumentEnvironment::dialogParent () const { return mainWindow (); } static kpViewManager *ViewManager (kpMainWindow *mw) { return mw->viewManager (); } // public void kpDocumentEnvironment::setQueueViewUpdates () const { ::ViewManager (mainWindow ())->setQueueUpdates (); } // public void kpDocumentEnvironment::restoreQueueViewUpdates () const { ::ViewManager (mainWindow ())->restoreQueueUpdates (); } //--------------------------------------------------------------------- // public void kpDocumentEnvironment::switchToCompatibleTool (const kpAbstractSelection &selection, bool *isTextChanged) const { +#if DEBUG_KP_DOCUMENT_ENVIRONMENT qCDebug(kpLogEnvironments) << "kpDocumentEnvironment::switchToCompatibleTool(" << &selection << ")" << " mainwindow.tool=" << (mainWindow ()->tool () ? mainWindow ()->tool ()->objectName () : nullptr) << " mainWindow.toolIsTextTool=" << mainWindow ()->toolIsTextTool () << " current selection=" << document ()->selection () << " new selection is text=" << dynamic_cast (&selection); - +#endif *isTextChanged = (mainWindow ()->toolIsTextTool () != (dynamic_cast (&selection) != nullptr)); // 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 (!mainWindow ()->toolIsASelectionTool () || *isTextChanged) { // See kpDocument::setSelection() APIDoc for this assumption. Q_ASSERT (!document ()->selection ()); // Switch to the appropriately shaped selection tool // _before_ we change the selection // (all selection tool's ::end() functions nuke the current selection) if (dynamic_cast (&selection)) { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT qCDebug(kpLogEnvironments) << "\tswitch to rect selection tool"; + #endif mainWindow ()->slotToolRectSelection (); } else if (dynamic_cast (&selection)) { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT qCDebug(kpLogEnvironments) << "\tswitch to elliptical selection tool"; + #endif mainWindow ()->slotToolEllipticalSelection (); } else if (dynamic_cast (&selection)) { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT qCDebug(kpLogEnvironments) << "\tswitch to free form selection tool"; + #endif mainWindow ()->slotToolFreeFormSelection (); } else if (dynamic_cast (&selection)) { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT qCDebug(kpLogEnvironments) << "\tswitch to text selection tool"; + #endif mainWindow ()->slotToolText (); } else { Q_ASSERT (!"Unknown selection type"); } } +#if DEBUG_KP_DOCUMENT_ENVIRONMENT qCDebug(kpLogEnvironments) << "kpDocumentEnvironment::switchToCompatibleTool(" << &selection << ") finished"; +#endif } //--------------------------------------------------------------------- // public void kpDocumentEnvironment::assertMatchingUIState (const kpAbstractSelection &selection) const { // Trap and try to recover from bugs. // TODO: See kpDocument::setSelection() API comment and determine best fix. const auto *imageSelection = dynamic_cast (&selection); const auto *textSelection = dynamic_cast (&selection); if (imageSelection) { if (imageSelection->transparency () != mainWindow ()->imageSelectionTransparency ()) { qCCritical(kpLogEnvironments) << "kpDocument::setSelection() sel's transparency differs " "from mainWindow's transparency - setting mainWindow's transparency " "to sel"; qCCritical(kpLogEnvironments) << "\tisOpaque: sel=" << imageSelection->transparency ().isOpaque () << " mainWindow=" << mainWindow ()->imageSelectionTransparency ().isOpaque (); mainWindow ()->setImageSelectionTransparency (imageSelection->transparency ()); } } else if (textSelection) { if (textSelection->textStyle () != mainWindow ()->textStyle ()) { qCCritical(kpLogEnvironments) << "kpDocument::setSelection() sel's textStyle differs " "from mainWindow's textStyle - setting mainWindow's textStyle " "to sel"; mainWindow ()->setTextStyle (textSelection->textStyle ()); } } else { Q_ASSERT (!"Unknown selection type"); } } diff --git a/generic/widgets/kpResizeSignallingLabel.cpp b/generic/widgets/kpResizeSignallingLabel.cpp index 4990e9ee..6793bdea 100644 --- a/generic/widgets/kpResizeSignallingLabel.cpp +++ b/generic/widgets/kpResizeSignallingLabel.cpp @@ -1,60 +1,65 @@ /* Copyright (c) 2003-2007 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_RESIZE_SIGNALLING_LABEL 0 + + #include "generic/widgets/kpResizeSignallingLabel.h" #include -#include #include "kpLogCategories.h" kpResizeSignallingLabel::kpResizeSignallingLabel (const QString &string, QWidget *parent ) : QLabel (string, parent) { } kpResizeSignallingLabel::kpResizeSignallingLabel (QWidget *parent ) : QLabel (parent) { } kpResizeSignallingLabel::~kpResizeSignallingLabel () = default; // protected virtual [base QLabel] void kpResizeSignallingLabel::resizeEvent (QResizeEvent *e) { +#if DEBUG_KP_RESIZE_SIGNALLING_LABEL qCDebug(kpLogMisc) << "kpResizeSignallingLabel::resizeEvent() newSize=" << e->size () << " oldSize=" << e->oldSize (); +#endif QLabel::resizeEvent (e); emit resized (); } diff --git a/imagelib/effects/kpEffectBalance.cpp b/imagelib/effects/kpEffectBalance.cpp index d2712723..c63ae81f 100644 --- a/imagelib/effects/kpEffectBalance.cpp +++ b/imagelib/effects/kpEffectBalance.cpp @@ -1,192 +1,210 @@ /* Copyright (c) 2003-2007 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_EFFECT_BALANCE 0 #include "kpEffectBalance.h" + #include + #include + #include "kpLogCategories.h" + #include "pixmapfx/kpPixmapFX.h" -#include + + +#if DEBUG_KP_EFFECT_BALANCE + #include +#endif + static inline int between0And255 (int val) { if (val < 0) { return 0; } if (val > 255) { return 255; } return val; } static inline int brightness (int base, int strength) { return between0And255 (base + strength * 255 / 50); } static inline int contrast (int base, int strength) { return between0And255 ((base - 127) * (strength + 50) / 50 + 127); } static inline int gamma (int base, int strength) { return between0And255 (qRound (255.0 * std::pow (base / 255.0, 1.0 / std::pow (10., strength / 50.0)))); } static inline int brightnessContrastGamma (int base, int newBrightness, int newContrast, int newGamma) { return gamma (contrast (brightness (base, newBrightness), newContrast), newGamma); } static inline QRgb brightnessContrastGammaForRGB (QRgb rgb, int channels, int brightness, int contrast, int gamma) { int red = qRed (rgb); int green = qGreen (rgb); int blue = qBlue (rgb); if (channels & kpEffectBalance::Red) { red = brightnessContrastGamma (red, brightness, contrast, gamma); } if (channels & kpEffectBalance::Green) { green = brightnessContrastGamma (green, brightness, contrast, gamma); } if (channels & kpEffectBalance::Blue) { blue = brightnessContrastGamma (blue, brightness, contrast, gamma); } + return qRgba (red, green, blue, qAlpha (rgb)); } // public static kpImage kpEffectBalance::applyEffect (const kpImage &image, int channels, int brightness, int contrast, int gamma) { +#if DEBUG_KP_EFFECT_BALANCE qCDebug(kpLogImagelib) << "kpEffectBalance::applyEffect(" << "channels=" << channels << ",brightness=" << brightness << ",contrast=" << contrast << ",gamma=" << gamma << ")"; QTime timer; timer.start (); +#endif QImage qimage = image; +#if DEBUG_KP_EFFECT_BALANCE qCDebug(kpLogImagelib) << "\tconvertToImage=" << timer.restart (); +#endif + quint8 transformRed [256], transformGreen [256], transformBlue [256]; for (int i = 0; i < 256; i++) { auto applied = static_cast (brightnessContrastGamma (i, brightness, contrast, gamma)); if (channels & kpEffectBalance::Red) { transformRed [i] = applied; } else { transformRed [i] = static_cast (i); } if (channels & kpEffectBalance::Green) { transformGreen [i] = applied; } else { transformGreen [i] = static_cast (i); } if (channels & kpEffectBalance::Blue) { transformBlue [i] = applied; } else { transformBlue [i] = static_cast (i); } } +#if DEBUG_KP_EFFECT_BALANCE qCDebug(kpLogImagelib) << "\tbuild lookup=" << timer.restart (); +#endif + if (qimage.depth () > 8) { for (int y = 0; y < qimage.height (); y++) { for (int x = 0; x < qimage.width (); x++) { const QRgb rgb = qimage.pixel (x, y); const auto red = static_cast (qRed (rgb)); const auto green = static_cast (qGreen (rgb)); const auto blue = static_cast (qBlue (rgb)); const auto alpha = static_cast (qAlpha (rgb)); qimage.setPixel (x, y, qRgba (transformRed [red], transformGreen [green], transformBlue [blue], alpha)); } } } else { for (int i = 0; i < qimage.colorCount (); i++) { const QRgb rgb = qimage.color (i); const auto red = static_cast (qRed (rgb)); const auto green = static_cast (qGreen (rgb)); const auto blue = static_cast (qBlue (rgb)); const auto alpha = static_cast (qAlpha (rgb)); qimage.setColor (i, qRgba (transformRed [red], transformGreen [green], transformBlue [blue], alpha)); } } return qimage; } diff --git a/imagelib/effects/kpEffectBlurSharpen.cpp b/imagelib/effects/kpEffectBlurSharpen.cpp index f5c81741..dd46be04 100644 --- a/imagelib/effects/kpEffectBlurSharpen.cpp +++ b/imagelib/effects/kpEffectBlurSharpen.cpp @@ -1,166 +1,184 @@ /* Copyright (c) 2003-2007 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_EFFECT_BLUR_SHARPEN 0 #include "kpEffectBlurSharpen.h" #include "blitz.h" + #include "kpLogCategories.h" + #include "pixmapfx/kpPixmapFX.h" -#include + + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + #include +#endif //--------------------------------------------------------------------- // // For info on "radius" and "sigma", see http://redskiesatnight.com/Articles/IMsharpen/ // // Daniel Duley says: // // // I don't think I can describe it any better than the article: The radius // controls many how pixels are taken into account when determining the value // of the center pixel. This controls the quality [and speed] of the result but not // necessarily the strength. The sigma controls how those neighboring pixels // are weighted depending on how far the are from the center one. This is // closer to strength, but not exactly >:) // // static QImage BlurQImage(const QImage &qimage, int strength) { if (strength == 0) { return qimage; } // The numbers that follow were picked by experimentation to try to get // an effect linearly proportional to and at the same time, // be fast enough. // // I still have no idea what "radius" means. const double RadiusMin = 1; const double RadiusMax = 10; const double radius = RadiusMin + (strength - 1) * (RadiusMax - RadiusMin) / (kpEffectBlurSharpen::MaxStrength - 1); +#if DEBUG_KP_EFFECT_BLUR_SHARPEN qCDebug(kpLogImagelib) << "kpEffectBlurSharpen.cpp:BlurQImage(strength=" << strength << ")" << " radius=" << radius; +#endif QImage img(qimage); return Blitz::blur(img, qRound(radius)); } //--------------------------------------------------------------------- static QImage SharpenQImage (const QImage &qimage_, int strength) { QImage qimage = qimage_; if (strength == 0) { return qimage; } // The numbers that follow were picked by experimentation to try to get // an effect linearly proportional to and at the same time, // be fast enough. // // I still have no idea what "radius" and "sigma" mean. const double RadiusMin = 0.1; const double RadiusMax = 2.5; const double radius = RadiusMin + (strength - 1) * (RadiusMax - RadiusMin) / (kpEffectBlurSharpen::MaxStrength - 1); const double SigmaMin = 0.5; const double SigmaMax = 3.0; const double sigma = SigmaMin + (strength - 1) * (SigmaMax - SigmaMin) / (kpEffectBlurSharpen::MaxStrength - 1); const double RepeatMin = 1; const double RepeatMax = 2; const double repeat = qRound (RepeatMin + (strength - 1) * (RepeatMax - RepeatMin) / (kpEffectBlurSharpen::MaxStrength - 1)); +#if DEBUG_KP_EFFECT_BLUR_SHARPEN qCDebug(kpLogImagelib) << "kpEffectBlurSharpen.cpp:SharpenQImage(strength=" << strength << ")" << " radius=" << radius << " sigma=" << sigma << " repeat=" << repeat; +#endif for (int i = 0; i < repeat; i++) { + #if DEBUG_KP_EFFECT_BLUR_SHARPEN QTime timer; timer.start (); + #endif qimage = Blitz::gaussianSharpen (qimage, static_cast (radius), static_cast (sigma)); + #if DEBUG_KP_EFFECT_BLUR_SHARPEN qCDebug(kpLogImagelib) << "\titeration #" + QString::number (i) << ": " + QString::number (timer.elapsed ()) << "ms"; + #endif } return qimage; } //--------------------------------------------------------------------- // public static kpImage kpEffectBlurSharpen::applyEffect (const kpImage &image, Type type, int strength) { +#if DEBUG_KP_EFFECT_BLUR_SHARPEN qCDebug(kpLogImagelib) << "kpEffectBlurSharpen::applyEffect(image.rect=" << image.rect () << ",type=" << int (type) << ",strength=" << strength << ")"; +#endif Q_ASSERT (strength >= MinStrength && strength <= MaxStrength); if (type == Blur) { return ::BlurQImage (image, strength); } if (type == Sharpen) { return ::SharpenQImage (image, strength); } if (type == MakeConfidential) { QImage img(image); return Blitz::blur(img, qMin(20, img.width() / 2)); } return kpImage(); } + +//--------------------------------------------------------------------- diff --git a/imagelib/effects/kpEffectEmboss.cpp b/imagelib/effects/kpEffectEmboss.cpp index d3a3e499..72b62ecc 100644 --- a/imagelib/effects/kpEffectEmboss.cpp +++ b/imagelib/effects/kpEffectEmboss.cpp @@ -1,78 +1,81 @@ /* Copyright (c) 2003-2007 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_EFFECT_EMBOSS 0 #include "kpEffectEmboss.h" #include "blitz.h" #include "kpLogCategories.h" #include "pixmapfx/kpPixmapFX.h" static QImage EmbossQImage (const QImage &qimage_, int strength) { QImage qimage = qimage_; if (strength == 0) { return qimage; } // The numbers that follow were picked by experimentation to try to get // an effect linearly proportional to and at the same time, // be fast enough. // // I still have no idea what "radius" and "sigma" mean. const auto radius = 0.0; const auto sigma = 1.0; const auto repeat = 1; for (int i = 0; i < repeat; i++) { qimage = Blitz::emboss (qimage, radius, sigma); } return qimage; } // public static kpImage kpEffectEmboss::applyEffect (const kpImage &image, int strength) { - qCDebug(kpLogImagelib) << "kpEffectEmboss::applyEffect(strength=" << strength << ")"; +#if DEBUG_KP_EFFECT_EMBOSS + qCDebug(kpLogImagelib) << "kpEffectEmboss::applyEffect(strength=" << strength << ")" + << endl; +#endif Q_ASSERT (strength >= MinStrength && strength <= MaxStrength); return ::EmbossQImage (image, strength); } diff --git a/imagelib/effects/kpEffectInvert.cpp b/imagelib/effects/kpEffectInvert.cpp index d0b8b96e..b8e6e6b4 100644 --- a/imagelib/effects/kpEffectInvert.cpp +++ b/imagelib/effects/kpEffectInvert.cpp @@ -1,82 +1,84 @@ /* Copyright (c) 2003-2007 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_EFFECT_INVERT 0 #include "kpEffectInvert.h" #include #include #include "kpLogCategories.h" #include "pixmapfx/kpPixmapFX.h" // public static void kpEffectInvert::applyEffect (QImage *destImagePtr, int channels) { QRgb mask = qRgba ((channels & Red) ? 0xFF : 0, (channels & Green) ? 0xFF : 0, (channels & Blue) ? 0xFF : 0, 0/*don't invert alpha*/); +#if DEBUG_KP_EFFECT_INVERT qCDebug(kpLogImagelib) << "kpEffectInvert::applyEffect(channels=" << channels << ") mask=" << (int *) mask; +#endif if (destImagePtr->depth () > 8) { // Above version works for Qt 3.2 at least. // But this version will always work (slower, though) and supports // inverting particular channels. for (int y = 0; y < destImagePtr->height (); y++) { for (int x = 0; x < destImagePtr->width (); x++) { destImagePtr->setPixel (x, y, destImagePtr->pixel (x, y) ^ mask); } } } else { for (int i = 0; i < destImagePtr->colorCount (); i++) { destImagePtr->setColor (i, destImagePtr->color (i) ^ mask); } } } // public static QImage kpEffectInvert::applyEffect (const QImage &img, int channels) { QImage retImage = img; applyEffect (&retImage, channels); return retImage; } diff --git a/imagelib/effects/kpEffectReduceColors.cpp b/imagelib/effects/kpEffectReduceColors.cpp index a703e0c3..40ce0dba 100644 --- a/imagelib/effects/kpEffectReduceColors.cpp +++ b/imagelib/effects/kpEffectReduceColors.cpp @@ -1,221 +1,240 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller 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_EFFECT_REDUCE_COLORS 0 #include "imagelib/effects/kpEffectReduceColors.h" #include "kpLogCategories.h" //--------------------------------------------------------------------- static QImage::Format DepthToFormat (int depth) { // These values are QImage's supported depths. switch (depth) { case 1: // (can be MSB instead, I suppose) return QImage::Format_MonoLSB; case 8: return QImage::Format_Indexed8; case 16: return QImage::Format_ARGB4444_Premultiplied; case 24: return QImage::Format_ARGB6666_Premultiplied; case 32: return QImage::Format_ARGB32_Premultiplied; default: Q_ASSERT (!"unknown depth"); return QImage::Format_Invalid; } } //--------------------------------------------------------------------- // public static QImage kpEffectReduceColors::convertImageDepth (const QImage &image, int depth, bool dither) { +#if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "kpeffectreducecolors.cpp:ConvertImageDepth() changing image (w=" << image.width () << ",h=" << image.height () << ") depth from " << image.depth () << " to " << depth - << " (dither=" << dither << ")"; + << " (dither=" << dither << ")" + << endl; +#endif if (image.isNull ()) { return image; } if (depth == image.depth ()) { return image; } +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 for (int y = 0; y < image.height (); y++) { for (int x = 0; x < image.width (); x++) { fprintf (stderr, " %08X", image.pixel (x, y)); } fprintf (stderr, "\n"); } +#endif // Hack around Qt's braindead QImage::convertToFormat(QImage::Format_MonoLSB, ...) // (with dithering off) which produces pathetic results with an image that // only has 2 colors - sometimes it just gives a completely black // result (try yellow and white as input). Instead, we simply preserve // the 2 colours. // // One use case is resaving a "color monochrome" image (<= 2 colors but // not necessarily black & white). if (depth == 1 && !dither) { + #if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "\tinvoking convert-to-depth 1 hack"; + #endif QRgb color0 = 0, color1 = 0; bool color0Valid = false, color1Valid = false; bool moreThan2Colors = false; QImage monoImage (image.width (), image.height (), QImage::Format_MonoLSB); monoImage.setColorCount (2); + #if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "\t\tinitialising output image w=" << monoImage.width () << ",h=" << monoImage.height () << ",d=" << monoImage.depth (); + #endif for (int y = 0; y < image.height (); y++) { for (int x = 0; x < image.width (); x++) { // (this can be transparent) QRgb imagePixel = image.pixel (x, y); if (color0Valid && imagePixel == color0) { monoImage.setPixel (x, y, 0); } else if (color1Valid && imagePixel == color1) { monoImage.setPixel (x, y, 1); } else if (!color0Valid) { color0 = imagePixel; color0Valid = true; monoImage.setPixel (x, y, 0); + #if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "\t\t\tcolor0=" << (int *) color0 << " at x=" << x << ",y=" << y; + #endif } else if (!color1Valid) { color1 = imagePixel; color1Valid = true; monoImage.setPixel (x, y, 1); + #if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "\t\t\tcolor1=" << (int *) color1 << " at x=" << x << ",y=" << y; + #endif } else { + #if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "\t\t\timagePixel=" << (int *) imagePixel << " at x=" << x << ",y=" << y << " moreThan2Colors - abort hack"; + #endif moreThan2Colors = true; // Dijkstra, this is clearer than double break'ing or // a check in both loops goto exit_loop; } } } exit_loop: if (!moreThan2Colors) { monoImage.setColor (0, color0Valid ? color0 : 0xFFFFFF); monoImage.setColor (1, color1Valid ? color1 : 0x000000); return monoImage; } } QImage retImage = image.convertToFormat (::DepthToFormat (depth), Qt::AutoColor | (dither ? Qt::DiffuseDither : Qt::ThresholdDither) | Qt::ThresholdAlphaDither | (dither ? Qt::PreferDither : Qt::AvoidDither)); +#if DEBUG_KP_EFFECT_REDUCE_COLORS qCDebug(kpLogImagelib) << "\tformat: before=" << image.format () << "after=" << retImage.format (); +#endif +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 qCDebug(kpLogImagelib) << "After colour reduction:"; for (int y = 0; y < image.height (); y++) { for (int x = 0; x < image.width (); x++) { fprintf (stderr, " %08X", image.pixel (x, y)); } fprintf (stderr, "\n"); } +#endif return retImage; } //--------------------------------------------------------------------- // public static void kpEffectReduceColors::applyEffect (QImage *destPtr, int depth, bool dither) { if (!destPtr) { return; } // You can't "reduce" to 32-bit since it's the highest depth. if (depth != 1 && depth != 8) { return; } *destPtr = convertImageDepth(*destPtr, depth, dither); // internally we always use QImage::Format_ARGB32_Premultiplied and // this effect is just an "effect" in that it changes the image (the look) somehow // When one wants a different depth on the file, then he needs to save the image // in that depth *destPtr = destPtr->convertToFormat(QImage::Format_ARGB32_Premultiplied); } //--------------------------------------------------------------------- QImage kpEffectReduceColors::applyEffect (const QImage &pm, int depth, bool dither) { QImage ret = pm; applyEffect (&ret, depth, dither); return ret; } //--------------------------------------------------------------------- diff --git a/imagelib/kpColor.cpp b/imagelib/kpColor.cpp index aaa04c09..12b2f3b0 100644 --- a/imagelib/kpColor.cpp +++ b/imagelib/kpColor.cpp @@ -1,311 +1,317 @@ /* Copyright (c) 2003-2007 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_COLOR 0 #include "kpColor.h" #include #include "kpLogCategories.h" //--------------------------------------------------------------------- kpColor::kpColor() : m_rgbaIsValid(false), m_rgba(0), m_colorCacheIsValid(false) { } //--------------------------------------------------------------------- kpColor::kpColor (int red, int green, int blue, bool isTransparent) : m_rgba(0), m_colorCacheIsValid(false) { +#if DEBUG_KP_COLOR qCDebug(kpLogImagelib) << "kpColor::(r=" << red << ",g=" << green << ",b=" << blue << ",isTrans=" << isTransparent << ")"; +#endif if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { qCCritical(kpLogImagelib) << "kpColor::(r=" << red << ",g=" << green << ",b=" << blue << ",t=" << isTransparent << ") passed out of range values"; m_rgbaIsValid = false; return; } m_rgba = qRgba (red, green, blue, isTransparent ? 0 : 255/*opaque*/); m_rgbaIsValid = true; } //--------------------------------------------------------------------- kpColor::kpColor (const QRgb &rgba) : m_colorCacheIsValid (false) { +#if DEBUG_KP_COLOR qCDebug(kpLogImagelib) << "kpColor::(rgba=" << (int *) rgba << ")"; +#endif m_rgba = rgba; m_rgbaIsValid = true; } //--------------------------------------------------------------------- kpColor::kpColor (const kpColor &rhs) : m_rgbaIsValid (rhs.m_rgbaIsValid), m_rgba (rhs.m_rgba), m_colorCacheIsValid (rhs.m_colorCacheIsValid), m_colorCache (rhs.m_colorCache) { +#if DEBUG_KP_COLOR qCDebug(kpLogImagelib) << "kpColor::()"; +#endif } //--------------------------------------------------------------------- // friend QDataStream &operator<< (QDataStream &stream, const kpColor &color) { stream << int (color.m_rgbaIsValid) << int (color.m_rgba); return stream; } //--------------------------------------------------------------------- // friend QDataStream &operator>> (QDataStream &stream, kpColor &color) { int a, b; stream >> a >> b; color.m_rgbaIsValid = a; color.m_rgba = static_cast (b); color.m_colorCacheIsValid = false; return stream; } //--------------------------------------------------------------------- kpColor &kpColor::operator= (const kpColor &rhs) { // (as soon as you add a ptr, you won't be complaining to me that this // method was unnecessary :)) if (this == &rhs) { return *this; } m_rgbaIsValid = rhs.m_rgbaIsValid; m_rgba = rhs.m_rgba; m_colorCacheIsValid = rhs.m_colorCacheIsValid; m_colorCache = rhs.m_colorCache; return *this; } bool kpColor::operator== (const kpColor &rhs) const { return isSimilarTo (rhs, kpColor::Exact); } bool kpColor::operator!= (const kpColor &rhs) const { return !(*this == rhs); } //--------------------------------------------------------------------- template inline dtype square (dtype val) { return val * val; } //--------------------------------------------------------------------- // public static int kpColor::processSimilarity (double colorSimilarity) { // sqrt (dr ^ 2 + dg ^ 2 + db ^ 2) <= colorSimilarity * sqrt (255 ^ 2 * 3) // dr ^ 2 + dg ^ 2 + db ^ 2 <= (colorSimilarity ^ 2) * (255 ^ 2 * 3) return int (square (colorSimilarity) * (square (255) * 3)); } //--------------------------------------------------------------------- bool kpColor::isSimilarTo (const kpColor &rhs, int processedSimilarity) const { // Are we the same? if (this == &rhs) { return true; } // Do we dither in terms of validity? if (isValid () != rhs.isValid ()) { return false; } // Are both of us invalid? if (!isValid ()) { return true; } // --- both are now valid --- if (m_rgba == rhs.m_rgba) { return true; } if (processedSimilarity == kpColor::Exact) { return false; } return (square (qRed (m_rgba) - qRed (rhs.m_rgba)) + square (qGreen (m_rgba) - qGreen (rhs.m_rgba)) + square (qBlue (m_rgba) - qBlue (rhs.m_rgba)) <= processedSimilarity); } //--------------------------------------------------------------------- // public bool kpColor::isValid () const { return m_rgbaIsValid; } //--------------------------------------------------------------------- // public int kpColor::red () const { if (!m_rgbaIsValid) { qCCritical(kpLogImagelib) << "kpColor::red() called with invalid kpColor"; return 0; } return qRed (m_rgba); } //--------------------------------------------------------------------- // public int kpColor::green () const { if (!m_rgbaIsValid) { qCCritical(kpLogImagelib) << "kpColor::green() called with invalid kpColor"; return 0; } return qGreen (m_rgba); } //--------------------------------------------------------------------- // public int kpColor::blue () const { if (!m_rgbaIsValid) { qCCritical(kpLogImagelib) << "kpColor::blue() called with invalid kpColor"; return 0; } return qBlue (m_rgba); } //--------------------------------------------------------------------- // public int kpColor::alpha () const { if (!m_rgbaIsValid) { qCCritical(kpLogImagelib) << "kpColor::alpha() called with invalid kpColor"; return 0; } return qAlpha (m_rgba); } //--------------------------------------------------------------------- // public bool kpColor::isTransparent () const { return (alpha () == 0); } //--------------------------------------------------------------------- // public QRgb kpColor::toQRgb () const { if (!m_rgbaIsValid) { qCCritical(kpLogImagelib) << "kpColor::toQRgb() called with invalid kpColor"; return 0; } return m_rgba; } //--------------------------------------------------------------------- // public QColor kpColor::toQColor () const { if (!m_rgbaIsValid) { qCCritical(kpLogImagelib) << "kpColor::toQColor() called with invalid kpColor"; return Qt::black; } if (m_colorCacheIsValid) { return m_colorCache; } m_colorCache = QColor(qRed(m_rgba), qGreen(m_rgba), qBlue(m_rgba), qAlpha(m_rgba)); m_colorCacheIsValid = true; return m_colorCache; } //--------------------------------------------------------------------- diff --git a/imagelib/kpFloodFill.cpp b/imagelib/kpFloodFill.cpp index a28e9652..566dadee 100644 --- a/imagelib/kpFloodFill.cpp +++ b/imagelib/kpFloodFill.cpp @@ -1,412 +1,430 @@ /* Copyright (c) 2003-2007 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_FLOOD_FILL 0 #include "kpFloodFill.h" #include #include #include #include #include #include "kpLogCategories.h" #include "kpColor.h" #include "kpImage.h" #include "kpDefs.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" //--------------------------------------------------------------------- class kpFillLine { public: kpFillLine (int y = -1, int x1 = -1, int x2 = -1) : m_y (y), m_x1 (x1), m_x2 (x2) { } static kpCommandSize::SizeType size () { return sizeof (kpFillLine); } int m_y, m_x1, m_x2; }; //--------------------------------------------------------------------- static kpCommandSize::SizeType FillLinesListSize (const QLinkedList &fillLines) { return (fillLines.size () * kpFillLine::size ()); } //--------------------------------------------------------------------- struct kpFloodFillPrivate { // // Copy of whatever was passed to the constructor. // kpImage *imagePtr{}; int x{}, y{}; kpColor color; int processedColorSimilarity{}; // // Set by Step 1. // kpColor colorToChange; // // Set by Step 2. // QLinkedList fillLines; QList < QLinkedList > fillLinesCache; QRect boundingRect; bool prepared{}; }; //--------------------------------------------------------------------- kpFloodFill::kpFloodFill (kpImage *image, int x, int y, const kpColor &color, int processedColorSimilarity) : d (new kpFloodFillPrivate ()) { d->imagePtr = image; d->x = x; d->y = y; d->color = color; d->processedColorSimilarity = processedColorSimilarity; d->prepared = false; } //--------------------------------------------------------------------- kpFloodFill::~kpFloodFill () { delete d; } //--------------------------------------------------------------------- // public kpColor kpFloodFill::color () const { return d->color; } //--------------------------------------------------------------------- // public int kpFloodFill::processedColorSimilarity () const { return d->processedColorSimilarity; } //--------------------------------------------------------------------- // public kpCommandSize::SizeType kpFloodFill::size () const { kpCommandSize::SizeType fillLinesCacheSize = 0; for (const auto &linesList : d->fillLinesCache) { fillLinesCacheSize += ::FillLinesListSize (linesList); } return ::FillLinesListSize(d->fillLines) + kpCommandSize::QImageSize(d->imagePtr) + fillLinesCacheSize; } //--------------------------------------------------------------------- // public void kpFloodFill::prepareColorToChange () { if (d->colorToChange.isValid ()) { return; } +#if DEBUG_KP_FLOOD_FILL && 1 qCDebug(kpLogImagelib) << "kpFloodFill::prepareColorToChange()"; +#endif d->colorToChange = kpPixmapFX::getColorAtPixel (*d->imagePtr, QPoint (d->x, d->y)); } //--------------------------------------------------------------------- // public kpColor kpFloodFill::colorToChange () { prepareColorToChange (); return d->colorToChange; } //--------------------------------------------------------------------- // Derived from the zSprite2 Graphics Engine // private kpColor kpFloodFill::pixelColor (int x, int y, bool *beenHere) const { if (beenHere) { *beenHere = false; } Q_ASSERT (y >= 0 && y < static_cast (d->fillLinesCache.count ())); for (const auto &line : d->fillLinesCache [y]) { if (x >= line.m_x1 && x <= line.m_x2) { if (beenHere) { *beenHere = true; } return d->color; } } return kpPixmapFX::getColorAtPixel (*(d->imagePtr), QPoint (x, y)); } //--------------------------------------------------------------------- // private bool kpFloodFill::shouldGoTo (int x, int y) const { bool beenThere; const kpColor col = pixelColor (x, y, &beenThere); return (!beenThere && col.isSimilarTo (d->colorToChange, d->processedColorSimilarity)); } //--------------------------------------------------------------------- // private int kpFloodFill::findMinX (int y, int x) const { for (;;) { if (x < 0) { return 0; } if (shouldGoTo (x, y)) { x--; } else { return x + 1; } } } //--------------------------------------------------------------------- // private int kpFloodFill::findMaxX (int y, int x) const { for (;;) { if (x > d->imagePtr->width () - 1) { return d->imagePtr->width () - 1; } if (shouldGoTo (x, y)) { x++; } else { return x - 1; } } } //--------------------------------------------------------------------- // private void kpFloodFill::addLine (int y, int x1, int x2) { +#if DEBUG_KP_FLOOD_FILL && 0 qCDebug(kpLogImagelib) << "kpFillCommand::fillAddLine (" - << y << "," << x1 << "," << x2 << ")"; + << y << "," << x1 << "," << x2 << ")" << endl; +#endif d->fillLines.append (kpFillLine (y, x1, x2)); d->fillLinesCache [y].append ( kpFillLine (y/*OPT: can determine from array index*/, x1, x2)); d->boundingRect = d->boundingRect.united (QRect (QPoint (x1, y), QPoint (x2, y))); } //--------------------------------------------------------------------- // private void kpFloodFill::findAndAddLines (const kpFillLine &fillLine, int dy) { // out of bounds? if (fillLine.m_y + dy < 0 || fillLine.m_y + dy >= d->imagePtr->height ()) { return; } for (int xnow = fillLine.m_x1; xnow <= fillLine.m_x2; xnow++) { // At current position, right colour? if (shouldGoTo (xnow, fillLine.m_y + dy)) { // Find minimum and maximum x values int minxnow = findMinX (fillLine.m_y + dy, xnow); int maxxnow = findMaxX (fillLine.m_y + dy, xnow); // Draw line addLine (fillLine.m_y + dy, minxnow, maxxnow); // Move x pointer xnow = maxxnow; } } } //--------------------------------------------------------------------- // public void kpFloodFill::prepare () { if (d->prepared) { return; } +#if DEBUG_KP_FLOOD_FILL && 1 qCDebug(kpLogImagelib) << "kpFloodFill::prepare()"; +#endif prepareColorToChange (); d->boundingRect = QRect (); + +#if DEBUG_KP_FLOOD_FILL && 1 qCDebug(kpLogImagelib) << "\tperforming NOP check"; +#endif // get the color we need to replace if (d->processedColorSimilarity == 0 && d->color == d->colorToChange) { // need to do absolutely nothing (this is a significant optimization // for people who randomly click a lot over already-filled areas) d->prepared = true; // sync with all "return true"'s return; } +#if DEBUG_KP_FLOOD_FILL && 1 qCDebug(kpLogImagelib) << "\tcreating fillLinesCache"; +#endif // ready cache for (int i = 0; i < d->imagePtr->height (); i++) { d->fillLinesCache.append (QLinkedList ()); } +#if DEBUG_KP_FLOOD_FILL && 1 qCDebug(kpLogImagelib) << "\tcreating fill lines"; +#endif // draw initial line addLine (d->y, findMinX (d->y, d->x), findMaxX (d->y, d->x)); for (QLinkedList ::ConstIterator it = d->fillLines.begin (); it != d->fillLines.end (); ++it) { + #if DEBUG_KP_FLOOD_FILL && 0 qCDebug(kpLogImagelib) << "Expanding from y=" << (*it).m_y << " x1=" << (*it).m_x1 - << " x2=" << (*it).m_x2; + << " x2=" << (*it).m_x2 + << endl; + #endif // // Make more lines above and below current line. // // WARNING: Adds to end of "fillLines" (the linked list we are iterating // through). Therefore, "fillLines" must remain a linked list // - you cannot change it into a vector. Also, do not use // "foreach" for this loop as that makes a copy of the linked // list at the start and won't see new lines. // findAndAddLines (*it, -1); findAndAddLines (*it, +1); } +#if DEBUG_KP_FLOOD_FILL && 1 qCDebug(kpLogImagelib) << "\tfinalising memory usage"; +#endif // finalize memory usage d->fillLinesCache.clear (); d->prepared = true; // sync with all "return true"'s } //--------------------------------------------------------------------- // public QRect kpFloodFill::boundingRect () { prepare (); return d->boundingRect; } //--------------------------------------------------------------------- // public void kpFloodFill::fill() { prepare(); QApplication::setOverrideCursor(Qt::WaitCursor); QPainter painter(d->imagePtr); // by definition, flood fill with a fully transparent color erases the pixels // and sets them to be fully transparent if ( d->color.isTransparent() ) { painter.setCompositionMode(QPainter::CompositionMode_Clear); } painter.setPen(d->color.toQColor()); for (const auto &l : d->fillLines) { if ( l.m_x1 == l.m_x2 ) { painter.drawPoint(l.m_x1, l.m_y); } else { painter.drawLine(l.m_x1, l.m_y, l.m_x2, l.m_y); } } QApplication::restoreOverrideCursor(); } //--------------------------------------------------------------------- diff --git a/imagelib/kpPainter.cpp b/imagelib/kpPainter.cpp index 5d9de308..d20e7ccd 100644 --- a/imagelib/kpPainter.cpp +++ b/imagelib/kpPainter.cpp @@ -1,469 +1,523 @@ /* Copyright (c) 2003-2007 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_PAINTER 0 + + #include "kpPainter.h" #include "kpImage.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "tools/flow/kpToolFlowBase.h" #include #include #include -#include -#include #include "kpLogCategories.h" #include //--------------------------------------------------------------------- // public static bool kpPainter::pointsAreCardinallyAdjacent (const QPoint &p, const QPoint &q) { int dx = qAbs (p.x () - q.x ()); int dy = qAbs (p.y () - q.y ()); return (dx + dy == 1); } //--------------------------------------------------------------------- // Returns a random integer from 0 to 999 inclusive. static int RandomNumberFrom0to999 () { return (KRandom::random () % 1000); } //--------------------------------------------------------------------- // public static QList kpPainter::interpolatePoints (const QPoint &startPoint, const QPoint &endPoint, bool cardinalAdjacency, double probability) { +#if DEBUG_KP_PAINTER qCDebug(kpLogImagelib) << "CALL(startPoint=" << startPoint << ",endPoint=" << endPoint << ")"; +#endif QList ret; Q_ASSERT (probability >= 0.0 && probability <= 1.0); const int probabilityTimes1000 = qRound (probability * 1000); #define SHOULD_DRAW() (probabilityTimes1000 == 1000/*avoid ::RandomNumberFrom0to999() call*/ || \ ::RandomNumberFrom0to999 () < probabilityTimes1000) // Derived from the zSprite2 Graphics Engine. // "MODIFIED" comment shows deviation from zSprite2 and Bresenham's line // algorithm. const int x1 = startPoint.x (), y1 = startPoint.y (), x2 = endPoint.x (), y2 = endPoint.y (); // Difference of x and y values const int dx = x2 - x1; const int dy = y2 - y1; // Absolute values of differences const int ix = qAbs (dx); const int iy = qAbs (dy); // Larger of the x and y differences const int inc = ix > iy ? ix : iy; // Plot location int plotx = x1; int ploty = y1; int x = 0; int y = 0; if (SHOULD_DRAW ()) { ret.append (QPoint (plotx, ploty)); } for (int i = 0; i <= inc; i++) { // oldplotx is equally as valid but would look different // (but nobody will notice which one it is) const int oldploty = ploty; int plot = 0; x += ix; y += iy; if (x > inc) { plot++; x -= inc; if (dx < 0) { plotx--; } else { plotx++; } } if (y > inc) { plot++; y -= inc; if (dy < 0) { ploty--; } else { ploty++; } } if (plot) { if (cardinalAdjacency && plot == 2) { // MODIFIED: Every point is // horizontally or vertically adjacent to another point (if there // is more than 1 point, of course). This is in contrast to the // ordinary line algorithm which can create diagonal adjacencies. if (SHOULD_DRAW ()) { ret.append (QPoint (plotx, oldploty)); } } if (SHOULD_DRAW ()) { ret.append (QPoint (plotx, ploty)); } } } #undef SHOULD_DRAW return ret; } //--------------------------------------------------------------------- // public static void kpPainter::fillRect (kpImage *image, int x, int y, int width, int height, const kpColor &color) { kpPixmapFX::fillRect (image, x, y, width, height, color); } //--------------------------------------------------------------------- // are operating on the original image // (the original image is not passed to this function). // // = subset of the original image containing all the pixels in // // = the rectangle, relative to the painters, whose pixels we // want to change static bool ReadableImageWashRect (QPainter *rgbPainter, const QImage &image, const kpColor &colorToReplace, const QRect &imageRect, const QRect &drawRect, int processedColorSimilarity) { bool didSomething = false; +#if DEBUG_KP_PAINTER && 0 qCDebug(kpLogImagelib) << "kppixmapfx.cpp:WashRect(imageRect=" << imageRect << ",drawRect=" << drawRect - << ")"; + << ")" << endl; +#endif // If you're going to pass painter pointers, those painters had better be // active (i.e. QPainter::begin() has been called). Q_ASSERT (!rgbPainter || rgbPainter->isActive ()); // make use of scanline coherence #define FLUSH_LINE() \ { \ if (rgbPainter) { \ if (startDrawX == x - 1) \ rgbPainter->drawPoint (startDrawX + imageRect.x (), \ y + imageRect.y ()); \ else \ rgbPainter->drawLine (startDrawX + imageRect.x (), \ y + imageRect.y (), \ x - 1 + imageRect.x (), \ y + imageRect.y ()); \ } \ didSomething = true; \ startDrawX = -1; \ } const int maxY = drawRect.bottom () - imageRect.top (); const int minX = drawRect.left () - imageRect.left (); const int maxX = drawRect.right () - imageRect.left (); for (int y = drawRect.top () - imageRect.top (); y <= maxY; y++) { int startDrawX = -1; int x; // for FLUSH_LINE() for (x = minX; x <= maxX; x++) { + #if DEBUG_KP_PAINTER && 0 fprintf (stderr, "y=%i x=%i colorAtPixel=%08X colorToReplace=%08X ... ", y, x, kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).toQRgb (), colorToReplace.toQRgb ()); + #endif if (kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).isSimilarTo (colorToReplace, processedColorSimilarity)) { + #if DEBUG_KP_PAINTER && 0 fprintf (stderr, "similar\n"); + #endif if (startDrawX < 0) { startDrawX = x; } } else { + #if DEBUG_KP_PAINTER && 0 fprintf (stderr, "different\n"); + #endif if (startDrawX >= 0) { FLUSH_LINE (); } } } if (startDrawX >= 0) { FLUSH_LINE (); } } #undef FLUSH_LINE return didSomething; } //--------------------------------------------------------------------- struct WashPack { QPoint startPoint, endPoint; kpColor color; int penWidth{}, penHeight{}; kpColor colorToReplace; int processedColorSimilarity{}; QRect readableImageRect; QImage readableImage; }; //--------------------------------------------------------------------- static QRect Wash (kpImage *image, const QPoint &startPoint, const QPoint &endPoint, const kpColor &color, int penWidth, int penHeight, const kpColor &colorToReplace, int processedColorSimilarity, QRect (*drawFunc) (QPainter * /*rgbPainter*/, void * /*data*/)) { WashPack pack; pack.startPoint = startPoint; pack.endPoint = endPoint; pack.color = color; pack.penWidth = penWidth; pack.penHeight = penHeight; pack.colorToReplace = colorToReplace; pack.processedColorSimilarity = processedColorSimilarity; // Get the rectangle that bounds the changes and the pixmap for that // rectangle. const QRect normalizedRect = kpPainter::normalizedRect(pack.startPoint, pack.endPoint); pack.readableImageRect = kpTool::neededRect (normalizedRect, qMax (pack.penWidth, pack.penHeight)); +#if DEBUG_KP_PAINTER qCDebug(kpLogImagelib) << "kppainter.cpp:Wash() startPoint=" << startPoint << " endPoint=" << endPoint << " --> normalizedRect=" << normalizedRect - << " readableImageRect=" << pack.readableImageRect; + << " readableImageRect=" << pack.readableImageRect + << endl; +#endif pack.readableImage = kpPixmapFX::getPixmapAt (*image, pack.readableImageRect); QPainter painter(image); return (*drawFunc)(&painter, &pack); } //--------------------------------------------------------------------- void WashHelperSetup (QPainter *rgbPainter, const WashPack *pack) { // Set the drawing colors for the painters. if (rgbPainter) { rgbPainter->setPen (pack->color.toQColor()); } } //--------------------------------------------------------------------- static QRect WashLineHelper (QPainter *rgbPainter, void *data) { +#if DEBUG_KP_PAINTER && 0 + qCDebug(kpLogImagelib) << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; +#endif + auto *pack = static_cast (data); // Setup painters. ::WashHelperSetup (rgbPainter, pack); + bool didSomething = false; QList points = kpPainter::interpolatePoints (pack->startPoint, pack->endPoint); for (QList ::const_iterator pit = points.constBegin (); pit != points.constEnd (); ++pit) { // OPT: This may be reading and possibly writing pixels that were // visited on a previous iteration, since the pen is usually // bigger than 1 pixel. Maybe we could use QRegion to determine // all the non-intersecting regions and only wash each region once. // // Profiling needs to be done as QRegion is known to be a CPU hog. if (::ReadableImageWashRect (rgbPainter, pack->readableImage, pack->colorToReplace, pack->readableImageRect, kpToolFlowBase::hotRectForMousePointAndBrushWidthHeight ( *pit, pack->penWidth, pack->penHeight), pack->processedColorSimilarity)) { didSomething = true; } } + +#if DEBUG_KP_PAINTER && 0 + int ms = timer.restart (); + qCDebug(kpLogImagelib) << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; +#endif + + // TODO: Rectangle may be too big. Use QRect::united() incrementally? // Efficiency? return didSomething ? pack->readableImageRect : QRect (); } //--------------------------------------------------------------------- // public static QRect kpPainter::washLine (kpImage *image, int x1, int y1, int x2, int y2, const kpColor &color, int penWidth, int penHeight, const kpColor &colorToReplace, int processedColorSimilarity) { return ::Wash (image, QPoint (x1, y1), QPoint (x2, y2), color, penWidth, penHeight, colorToReplace, processedColorSimilarity, &::WashLineHelper); } //--------------------------------------------------------------------- static QRect WashRectHelper (QPainter *rgbPainter, void *data) { auto *pack = static_cast (data); +#if DEBUG_KP_PAINTER && 0 + qCDebug(kpLogImagelib) << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; +#endif // Setup painters. ::WashHelperSetup (rgbPainter, pack); const QRect drawRect (pack->startPoint, pack->endPoint); bool didSomething = false; if (::ReadableImageWashRect (rgbPainter, pack->readableImage, pack->colorToReplace, pack->readableImageRect, drawRect, pack->processedColorSimilarity)) { didSomething = true; } + +#if DEBUG_KP_PAINTER && 0 + int ms = timer.restart (); + qCDebug(kpLogImagelib) << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; +#endif + + return didSomething ? drawRect : QRect (); } //--------------------------------------------------------------------- // public static QRect kpPainter::washRect (kpImage *image, int x, int y, int width, int height, const kpColor &color, const kpColor &colorToReplace, int processedColorSimilarity) { return ::Wash (image, QPoint (x, y), QPoint (x + width - 1, y + height - 1), color, 1/*pen width*/, 1/*pen height*/, colorToReplace, processedColorSimilarity, &::WashRectHelper); } //--------------------------------------------------------------------- // public static void kpPainter::sprayPoints (kpImage *image, const QList &points, const kpColor &color, int spraycanSize) { +#if DEBUG_KP_PAINTER qCDebug(kpLogImagelib) << "kpPainter::sprayPoints()"; +#endif + Q_ASSERT (spraycanSize > 0); QPainter painter(image); const int radius = spraycanSize / 2; // Set the drawing colors for the painters. painter.setPen(color.toQColor()); for (const auto &p : points) { for (int i = 0; i < 10; i++) { const int dx = (KRandom::random () % spraycanSize) - radius; const int dy = (KRandom::random () % spraycanSize) - radius; // Make it look circular. // TODO: Can be done better by doing a random vector angle & length // but would sin and cos be too slow? if ((dx * dx) + (dy * dy) > (radius * radius)) { continue; } const QPoint p2 (p.x () + dx, p.y () + dy); painter.drawPoint(p2); } } } //--------------------------------------------------------------------- diff --git a/imagelib/transforms/kpTransformAutoCrop.cpp b/imagelib/transforms/kpTransformAutoCrop.cpp index 7b075da0..c0315043 100644 --- a/imagelib/transforms/kpTransformAutoCrop.cpp +++ b/imagelib/transforms/kpTransformAutoCrop.cpp @@ -1,736 +1,756 @@ /* Copyright (c) 2003-2007 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. */ // TODO: Color Similarity is obviously useful in Autocrop but it isn't // obvious as to how to implement it. The current heuristic, // for each side, chooses an arbitrary reference color for which // all other candidate pixels in that side are tested against // for similarity. But if the reference color happens to be at // one extreme of the range of colors in that side, then pixels // at the other extreme would not be deemed similar enough. The // key is to find the median color as the reference but how do // you do this if you don't know which pixels to sample in the first // place (that's what you're trying to find)? Chicken and egg situation. // // The other heuristic that is in doubt is the use of the average // color in determining the similarity of sides (it is possible // to get vastly differently colors in both sides yet they will be // considered similar). +#define DEBUG_KP_TOOL_AUTO_CROP 0 + + #include "kpTransformAutoCrop.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "widgets/toolbars/kpColorToolBar.h" #include "environments/commands/kpCommandEnvironment.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "mainWindow/kpMainWindow.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/selections/image/kpRectangularImageSelection.h" #include "generic/kpSetOverrideCursorSaver.h" #include "tools/kpTool.h" #include "views/manager/kpViewManager.h" #include "kpLogCategories.h" #include #include #include //--------------------------------------------------------------------- class kpTransformAutoCropBorder { public: // WARNING: Only call the with imagePtr = 0 if you are going to use // operator= to fill it in with a valid imagePtr immediately // afterwards. kpTransformAutoCropBorder (const kpImage *imagePtr = nullptr, int processedColorSimilarity = 0); kpCommandSize::SizeType size () const; const kpImage *image () const; int processedColorSimilarity () const; QRect rect () const; int left () const; int right () const; int top () const; int bottom () const; kpColor referenceColor () const; kpColor averageColor () const; bool isSingleColor () const; // (returns true on success (even if no rect) or false on error) bool calculate (int isX, int dir); bool fillsEntireImage () const; bool exists () const; void invalidate (); private: const kpImage *m_imagePtr; int m_processedColorSimilarity; QRect m_rect; kpColor m_referenceColor; int m_redSum, m_greenSum, m_blueSum; bool m_isSingleColor; }; kpTransformAutoCropBorder::kpTransformAutoCropBorder (const kpImage *imagePtr, int processedColorSimilarity) : m_imagePtr (imagePtr), m_processedColorSimilarity (processedColorSimilarity) { invalidate (); } // public kpCommandSize::SizeType kpTransformAutoCropBorder::size () const { return sizeof (kpTransformAutoCropBorder); } // public const kpImage *kpTransformAutoCropBorder::image () const { return m_imagePtr; } // public int kpTransformAutoCropBorder::processedColorSimilarity () const { return m_processedColorSimilarity; } // public QRect kpTransformAutoCropBorder::rect () const { return m_rect; } // public int kpTransformAutoCropBorder::left () const { return m_rect.left (); } // public int kpTransformAutoCropBorder::right () const { return m_rect.right (); } // public int kpTransformAutoCropBorder::top () const { return m_rect.top (); } // public int kpTransformAutoCropBorder::bottom () const { return m_rect.bottom (); } // public kpColor kpTransformAutoCropBorder::referenceColor () const { return m_referenceColor; } // public kpColor kpTransformAutoCropBorder::averageColor () const { if (!m_rect.isValid ()) return kpColor::Invalid; if (m_referenceColor.isTransparent ()) return kpColor::Transparent; if (m_processedColorSimilarity == 0) return m_referenceColor; int numPixels = (m_rect.width () * m_rect.height ()); Q_ASSERT (numPixels > 0); return kpColor (m_redSum / numPixels, m_greenSum / numPixels, m_blueSum / numPixels); } //--------------------------------------------------------------------- bool kpTransformAutoCropBorder::isSingleColor () const { return m_isSingleColor; } //--------------------------------------------------------------------- // public bool kpTransformAutoCropBorder::calculate (int isX, int dir) { +#if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "kpTransformAutoCropBorder::calculate() CALLED!"; +#endif int maxX = m_imagePtr->width () - 1; int maxY = m_imagePtr->height () - 1; QImage qimage = *m_imagePtr; Q_ASSERT (!qimage.isNull ()); // (sync both branches) if (isX) { int numCols = 0; int startX = (dir > 0) ? 0 : maxX; kpColor col = kpPixmapFX::getColorAtPixel (qimage, startX, 0); for (int x = startX; x >= 0 && x <= maxX; x += dir) { int y; for (y = 0; y <= maxY; y++) { if (!kpPixmapFX::getColorAtPixel (qimage, x, y).isSimilarTo (col, m_processedColorSimilarity)) break; } if (y <= maxY) break; else numCols++; } if (numCols) { m_rect = kpPainter::normalizedRect(QPoint(startX, 0), QPoint(startX + (numCols - 1) * dir, maxY)); m_referenceColor = col; } } else { int numRows = 0; int startY = (dir > 0) ? 0 : maxY; kpColor col = kpPixmapFX::getColorAtPixel (qimage, 0, startY); for (int y = startY; y >= 0 && y <= maxY; y += dir) { int x; for (x = 0; x <= maxX; x++) { if (!kpPixmapFX::getColorAtPixel (qimage, x, y).isSimilarTo (col, m_processedColorSimilarity)) break; } if (x <= maxX) break; else numRows++; } if (numRows) { m_rect = kpPainter::normalizedRect(QPoint(0, startY), QPoint(maxX, startY + (numRows - 1) * dir)); m_referenceColor = col; } } if (m_rect.isValid ()) { m_isSingleColor = true; if (m_processedColorSimilarity != 0) { for (int y = m_rect.top (); y <= m_rect.bottom (); y++) { for (int x = m_rect.left (); x <= m_rect.right (); x++) { kpColor colAtPixel = kpPixmapFX::getColorAtPixel (qimage, x, y); if (m_isSingleColor && colAtPixel != m_referenceColor) m_isSingleColor = false; m_redSum += colAtPixel.red (); m_greenSum += colAtPixel.green (); m_blueSum += colAtPixel.blue (); } } } } return true; } // public bool kpTransformAutoCropBorder::fillsEntireImage () const { return (m_rect == m_imagePtr->rect ()); } // public bool kpTransformAutoCropBorder::exists () const { // (will use in an addition so make sure returns 1 or 0) return (m_rect.isValid () ? 1 : 0); } // public void kpTransformAutoCropBorder::invalidate () { m_rect = QRect (); m_referenceColor = kpColor::Invalid; m_redSum = m_greenSum = m_blueSum = 0; m_isSingleColor = false; } struct kpTransformAutoCropCommandPrivate { bool actOnSelection{}; kpTransformAutoCropBorder leftBorder, rightBorder, topBorder, botBorder; kpImage *leftImage{}, *rightImage{}, *topImage{}, *botImage{}; QRect contentsRect; int oldWidth{}, oldHeight{}; kpAbstractImageSelection *oldSelectionPtr{}; }; // REFACTOR: Move to /commands/ kpTransformAutoCropCommand::kpTransformAutoCropCommand (bool actOnSelection, const kpTransformAutoCropBorder &leftBorder, const kpTransformAutoCropBorder &rightBorder, const kpTransformAutoCropBorder &topBorder, const kpTransformAutoCropBorder &botBorder, kpCommandEnvironment *environ) : kpNamedCommand(text(actOnSelection, DontShowAccel), environ), d (new kpTransformAutoCropCommandPrivate ()) { d->actOnSelection = actOnSelection; d->leftBorder = leftBorder; d->rightBorder = rightBorder; d->topBorder = topBorder; d->botBorder = botBorder; d->leftImage = nullptr; d->rightImage = nullptr; d->topImage = nullptr; d->botImage = nullptr; kpDocument *doc = document (); Q_ASSERT (doc); d->oldWidth = doc->width (d->actOnSelection); d->oldHeight = doc->height (d->actOnSelection); d->oldSelectionPtr = nullptr; } //--------------------------------------------------------------------- kpTransformAutoCropCommand::~kpTransformAutoCropCommand () { deleteUndoImages (); delete d->oldSelectionPtr; delete d; } //--------------------------------------------------------------------- // public static QString kpTransformAutoCropCommand::text(bool actOnSelection, int options) { if (actOnSelection) { if (options & kpTransformAutoCropCommand::ShowAccel) { return i18n ("Remove Internal B&order"); } return i18n ("Remove Internal Border"); } if (options & kpTransformAutoCropCommand::ShowAccel) return i18n ("Autocr&op"); return i18n ("Autocrop"); } //--------------------------------------------------------------------- // public virtual [base kpCommand] kpCommandSize::SizeType kpTransformAutoCropCommand::size () const { return d->leftBorder.size () + d->rightBorder.size () + d->topBorder.size () + d->botBorder.size () + ImageSize (d->leftImage) + ImageSize (d->rightImage) + ImageSize (d->topImage) + ImageSize (d->botImage) + SelectionSize (d->oldSelectionPtr); } //--------------------------------------------------------------------- // private void kpTransformAutoCropCommand::getUndoImage (const kpTransformAutoCropBorder &border, kpImage **image) { kpDocument *doc = document (); Q_ASSERT (doc); +#if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "kpTransformAutoCropCommand::getUndoImage()"; qCDebug(kpLogImagelib) << "\timage=" << image << " border: rect=" << border.rect () << " isSingleColor=" << border.isSingleColor (); +#endif if (image && border.exists () && !border.isSingleColor ()) { if (*image) { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "\talready have *image - delete it"; + #endif delete *image; } *image = new kpImage ( kpPixmapFX::getPixmapAt (doc->image (d->actOnSelection), border.rect ())); } } // private void kpTransformAutoCropCommand::getUndoImages () { getUndoImage (d->leftBorder, &d->leftImage); getUndoImage (d->rightBorder, &d->rightImage); getUndoImage (d->topBorder, &d->topImage); getUndoImage (d->botBorder, &d->botImage); } // private void kpTransformAutoCropCommand::deleteUndoImages () { +#if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "kpTransformAutoCropCommand::deleteUndoImages()"; +#endif delete d->leftImage; d->leftImage = nullptr; delete d->rightImage; d->rightImage = nullptr; delete d->topImage; d->topImage = nullptr; delete d->botImage; d->botImage = nullptr; } // public virtual [base kpCommand] void kpTransformAutoCropCommand::execute () { if (!d->contentsRect.isValid ()) { d->contentsRect = contentsRect (); } getUndoImages (); kpDocument *doc = document (); Q_ASSERT (doc); kpImage imageWithoutBorder = kpTool::neededPixmap (doc->image (d->actOnSelection), d->contentsRect); if (!d->actOnSelection) { doc->setImage (imageWithoutBorder); } else { d->oldSelectionPtr = doc->imageSelection ()->clone (); d->oldSelectionPtr->setBaseImage (kpImage ()); // d->contentsRect is relative to the top of the sel // while sel is relative to the top of the doc QRect rect = d->contentsRect; rect.translate (d->oldSelectionPtr->x (), d->oldSelectionPtr->y ()); kpRectangularImageSelection sel ( rect, imageWithoutBorder, d->oldSelectionPtr->transparency ()); doc->setSelection (sel); environ ()->somethingBelowTheCursorChanged (); } } // public virtual [base kpCommand] void kpTransformAutoCropCommand::unexecute () { +#if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "kpTransformAutoCropCommand::unexecute()"; +#endif kpDocument *doc = document (); Q_ASSERT (doc); kpImage image (d->oldWidth, d->oldHeight, QImage::Format_ARGB32_Premultiplied); // restore the position of the center image kpPixmapFX::setPixmapAt (&image, d->contentsRect, doc->image (d->actOnSelection)); // draw the borders const kpTransformAutoCropBorder *borders [] = { &d->leftBorder, &d->rightBorder, &d->topBorder, &d->botBorder, nullptr }; const kpImage *images [] = { d->leftImage, d->rightImage, d->topImage, d->botImage, nullptr }; const kpImage **p = images; for (const kpTransformAutoCropBorder **b = borders; *b; b++, p++) { if (!(*b)->exists ()) { continue; } if ((*b)->isSingleColor ()) { kpColor col = (*b)->referenceColor (); #if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "\tdrawing border " << (*b)->rect () << " rgb=" << (int *) col.toQRgb () /* %X hack */; #endif const QRect r = (*b)->rect (); kpPainter::fillRect (&image, r.x (), r.y (), r.width (), r.height (), col); } else { #if DEBUG_KP_TOOL_AUTO_CROP && 1 qCDebug(kpLogImagelib) << "\trestoring border image " << (*b)->rect (); #endif if (*p) { // REFACTOR: Add equivalent method to kpPainter and use. kpPixmapFX::setPixmapAt (&image, (*b)->rect (), **p); } } } if (!d->actOnSelection) { doc->setImage (image); } else { d->oldSelectionPtr->setBaseImage (image); doc->setSelection (*d->oldSelectionPtr); delete d->oldSelectionPtr; d->oldSelectionPtr = nullptr; environ ()->somethingBelowTheCursorChanged (); } deleteUndoImages (); } // private QRect kpTransformAutoCropCommand::contentsRect () const { const kpImage image = document ()->image (d->actOnSelection); QPoint topLeft (d->leftBorder.exists () ? d->leftBorder.rect ().right () + 1 : 0, d->topBorder.exists () ? d->topBorder.rect ().bottom () + 1 : 0); QPoint botRight (d->rightBorder.exists () ? d->rightBorder.rect ().left () - 1 : image.width () - 1, d->botBorder.exists () ? d->botBorder.rect ().top () - 1 : image.height () - 1); return {topLeft, botRight}; } static void ShowNothingToAutocropMessage (kpMainWindow *mainWindow, bool actOnSelection) { kpSetOverrideCursorSaver cursorSaver (Qt::ArrowCursor); if (actOnSelection) { KMessageBox::information (mainWindow, i18n ("KolourPaint cannot remove the selection's internal border as it" " could not be located."), i18nc ("@title:window", "Cannot Remove Internal Border"), "NothingToAutoCrop"); } else { KMessageBox::information (mainWindow, i18n ("KolourPaint cannot automatically crop the image as its" " border could not be located."), i18nc ("@title:window", "Cannot Autocrop"), "NothingToAutoCrop"); } } bool kpTransformAutoCrop (kpMainWindow *mainWindow) { +#if DEBUG_KP_TOOL_AUTO_CROP qCDebug(kpLogImagelib) << "kpTransformAutoCrop() CALLED!"; +#endif Q_ASSERT (mainWindow); kpDocument *doc = mainWindow->document (); Q_ASSERT (doc); // OPT: if already pulled selection image, no need to do it again here kpImage image = doc->selection () ? doc->getSelectedBaseImage () : doc->image (); Q_ASSERT (!image.isNull ()); kpViewManager *vm = mainWindow->viewManager (); Q_ASSERT (vm); int processedColorSimilarity = mainWindow->colorToolBar ()->processedColorSimilarity (); kpTransformAutoCropBorder leftBorder (&image, processedColorSimilarity), rightBorder (&image, processedColorSimilarity), topBorder (&image, processedColorSimilarity), botBorder (&image, processedColorSimilarity); kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); mainWindow->colorToolBar ()->flashColorSimilarityToolBarItem (); // TODO: With Colour Similarity, a lot of weird (and wonderful) things can // happen resulting in a huge number of code paths. Needs refactoring // and regression testing. // // TODO: e.g. When the top fills entire rect but bot doesn't we could // invalidate top and continue autocrop. int numRegions = 0; if (!leftBorder.calculate (true/*x*/, +1/*going right*/) || leftBorder.fillsEntireImage () || !rightBorder.calculate (true/*x*/, -1/*going left*/) || rightBorder.fillsEntireImage () || !topBorder.calculate (false/*y*/, +1/*going down*/) || topBorder.fillsEntireImage () || !botBorder.calculate (false/*y*/, -1/*going up*/) || botBorder.fillsEntireImage () || ((numRegions = leftBorder.exists () + rightBorder.exists () + topBorder.exists () + botBorder.exists ()) == 0)) { #if DEBUG_KP_TOOL_AUTO_CROP qCDebug(kpLogImagelib) << "\tcan't find border; leftBorder.rect=" << leftBorder.rect () << " rightBorder.rect=" << rightBorder.rect () << " topBorder.rect=" << topBorder.rect () << " botBorder.rect=" << botBorder.rect (); #endif ::ShowNothingToAutocropMessage (mainWindow, static_cast (doc->selection ())); return false; } +#if DEBUG_KP_TOOL_AUTO_CROP qCDebug(kpLogImagelib) << "\tnumRegions=" << numRegions; qCDebug(kpLogImagelib) << "\t\tleft=" << leftBorder.rect () << " refCol=" << (leftBorder.exists () ? (int *) leftBorder.referenceColor ().toQRgb () : nullptr) << " avgCol=" << (leftBorder.exists () ? (int *) leftBorder.averageColor ().toQRgb () : nullptr); qCDebug(kpLogImagelib) << "\t\tright=" << rightBorder.rect () << " refCol=" << (rightBorder.exists () ? (int *) rightBorder.referenceColor ().toQRgb () : nullptr) << " avgCol=" << (rightBorder.exists () ? (int *) rightBorder.averageColor ().toQRgb () : nullptr); qCDebug(kpLogImagelib) << "\t\ttop=" << topBorder.rect () << " refCol=" << (topBorder.exists () ? (int *) topBorder.referenceColor ().toQRgb () : nullptr) << " avgCol=" << (topBorder.exists () ? (int *) topBorder.averageColor ().toQRgb () : nullptr); qCDebug(kpLogImagelib) << "\t\tbot=" << botBorder.rect () << " refCol=" << (botBorder.exists () ? (int *) botBorder.referenceColor ().toQRgb () : nullptr) << " avgCol=" << (botBorder.exists () ? (int *) botBorder.averageColor ().toQRgb () : nullptr); - +#endif // In case e.g. the user pastes a solid, coloured-in rectangle, // we favor killing the bottom and right regions // (these regions probably contain the unwanted whitespace due // to the doc being bigger than the pasted selection to start with). // // We also kill if they kiss or even overlap. if (leftBorder.exists () && rightBorder.exists ()) { const kpColor leftCol = leftBorder.averageColor (); const kpColor rightCol = rightBorder.averageColor (); if ((numRegions == 2 && !leftCol.isSimilarTo (rightCol, processedColorSimilarity)) || leftBorder.right () >= rightBorder.left () - 1) // kissing or overlapping { + #if DEBUG_KP_TOOL_AUTO_CROP qCDebug(kpLogImagelib) << "\tignoring left border"; + #endif leftBorder.invalidate (); } } if (topBorder.exists () && botBorder.exists ()) { const kpColor topCol = topBorder.averageColor (); const kpColor botCol = botBorder.averageColor (); if ((numRegions == 2 && !topCol.isSimilarTo (botCol, processedColorSimilarity)) || topBorder.bottom () >= botBorder.top () - 1) // kissing or overlapping { + #if DEBUG_KP_TOOL_AUTO_CROP qCDebug(kpLogImagelib) << "\tignoring top border"; + #endif topBorder.invalidate (); } } mainWindow->addImageOrSelectionCommand ( new kpTransformAutoCropCommand (static_cast (doc->selection ()), leftBorder, rightBorder, topBorder, botBorder, mainWindow->commandEnvironment ())); return true; } diff --git a/imagelib/transforms/kpTransformCrop_ImageSelection.cpp b/imagelib/transforms/kpTransformCrop_ImageSelection.cpp index e6cb0dda..795e6885 100644 --- a/imagelib/transforms/kpTransformCrop_ImageSelection.cpp +++ b/imagelib/transforms/kpTransformCrop_ImageSelection.cpp @@ -1,238 +1,257 @@ /* Copyright (c) 2003-2007 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_TOOL_CROP 0 + + #include "kpTransformCrop.h" #include "kpTransformCropPrivate.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "imagelib/kpImage.h" #include "commands/kpMacroCommand.h" #include "mainWindow/kpMainWindow.h" #include "pixmapfx/kpPixmapFX.h" #include "commands/tools/selection/kpToolSelectionCreateCommand.h" #include "views/manager/kpViewManager.h" -#include "kpLogCategories.h" // See the "image selection" part of the kpTransformCrop() API Doc. // // REFACTOR: Move into commands/ class SetDocumentToSelectionImageCommand : public kpCommand { public: SetDocumentToSelectionImageCommand (kpCommandEnvironment *environ); ~SetDocumentToSelectionImageCommand () override; /* (uninteresting child of macro cmd) */ QString name () const override { return QString(); } kpCommandSize::SizeType size () const override { return ImageSize (m_oldImage) + SelectionSize (m_fromSelectionPtr) + ImageSize (m_imageIfFromSelectionDoesntHaveOne); } // ASSUMPTION: Document has been resized to be the same size as the // selection. void execute () override; void unexecute () override; protected: kpColor m_backgroundColor; kpImage m_oldImage; kpAbstractImageSelection *m_fromSelectionPtr; kpImage m_imageIfFromSelectionDoesntHaveOne; }; SetDocumentToSelectionImageCommand::SetDocumentToSelectionImageCommand (kpCommandEnvironment *environ) : kpCommand (environ), m_backgroundColor (environ->backgroundColor ()), m_fromSelectionPtr ( dynamic_cast ( environ->document ()->selection ()->clone ())) { Q_ASSERT (m_fromSelectionPtr); if ( m_fromSelectionPtr ) // make coverity happy { m_imageIfFromSelectionDoesntHaveOne = m_fromSelectionPtr->hasContent () ? kpImage () : document ()->getSelectedBaseImage (); } } //--------------------------------------------------------------------- SetDocumentToSelectionImageCommand::~SetDocumentToSelectionImageCommand () { delete m_fromSelectionPtr; } //--------------------------------------------------------------------- // public virtual [base kpCommand] void SetDocumentToSelectionImageCommand::execute () { +#if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "SetDocumentToSelectionImageCommand::execute()"; +#endif viewManager ()->setQueueUpdates (); { // kpTransformCrop_ImageSelection's has // executed, resizing the document to be the size of the selection // bounding rectangle. Q_ASSERT (document ()->width () == m_fromSelectionPtr->width ()); Q_ASSERT (document ()->height () == m_fromSelectionPtr->height ()); m_oldImage = document ()->image (); // // e.g. original elliptical selection: // // t/---\ T = original transparent selection pixel // | TT | t = outside the selection region // t\__/t [every other character] = original opaque selection pixel // // Afterwards, the _document_ image becomes: // // b/---\ T = [unchanged] // | TT | b = background color // b\__/b [every other character] = [unchanged] // // The selection is deleted. // // TODO: Do not introduce a mask if the result will not contain // any transparent pixels. // QImage newDocImage(document()->width(), document()->height(), QImage::Format_ARGB32_Premultiplied); newDocImage.fill(m_backgroundColor.toQRgb()); + #if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\tsel: rect=" << m_fromSelectionPtr->boundingRect () << " pm=" << m_fromSelectionPtr->hasContent (); + #endif QImage setTransparentImage; if (m_fromSelectionPtr->hasContent ()) { setTransparentImage = m_fromSelectionPtr->transparentImage (); + #if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\thave pixmap; rect=" << setTransparentImage.rect (); + #endif } else { setTransparentImage = m_imageIfFromSelectionDoesntHaveOne; + #if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\tno pixmap in sel - get it; rect=" << setTransparentImage.rect (); + #endif } kpPixmapFX::paintPixmapAt (&newDocImage, QPoint (0, 0), setTransparentImage); document ()->setImageAt (newDocImage, QPoint (0, 0)); document ()->selectionDelete (); environ ()->somethingBelowTheCursorChanged (); } viewManager ()->restoreQueueUpdates (); } //--------------------------------------------------------------------- // public virtual [base kpCommand] void SetDocumentToSelectionImageCommand::unexecute () { +#if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "SetDocumentToSelectionImageCommand::unexecute()"; +#endif viewManager ()->setQueueUpdates (); { document ()->setImageAt (m_oldImage, QPoint (0, 0)); m_oldImage = kpImage (); + #if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\tsel: rect=" << m_fromSelectionPtr->boundingRect () << " pm=" << m_fromSelectionPtr->hasContent (); + #endif document ()->setSelection (*m_fromSelectionPtr); environ ()->somethingBelowTheCursorChanged (); } viewManager ()->restoreQueueUpdates (); } //--------------------------------------------------------------------- void kpTransformCrop_ImageSelection (kpMainWindow *mainWindow, const QString &commandName, kpCommand *resizeDocCommand) { // Save starting selection, minus the border. auto *borderImageSel = dynamic_cast ( mainWindow->document ()->selection ()->clone ()); Q_ASSERT (borderImageSel); if ( !borderImageSel ) { // make coverity happy return; } // (only interested in border) borderImageSel->deleteContent (); borderImageSel->moveTo (QPoint (0, 0)); auto *environ = mainWindow->commandEnvironment (); auto *macroCmd = new kpMacroCommand (commandName, environ); // (must resize doc _before_ SetDocumentToSelectionImageCommand in case // doc needs to gets bigger - else selection image may not fit) macroCmd->addCommand (resizeDocCommand); +#if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\tis pixmap sel"; qCDebug(kpLogImagelib) << "\tcreating SetImage cmd"; +#endif macroCmd->addCommand (new SetDocumentToSelectionImageCommand (environ)); + mainWindow->addImageOrSelectionCommand ( macroCmd, true/*add create cmd*/, false/*don't add pull cmd*/); // Add selection border back for convenience. mainWindow->commandHistory ()->addCommand ( new kpToolSelectionCreateCommand ( i18n ("Selection: Create"), *borderImageSel, mainWindow->commandEnvironment ())); + delete borderImageSel; } diff --git a/imagelib/transforms/kpTransformCrop_TextSelection.cpp b/imagelib/transforms/kpTransformCrop_TextSelection.cpp index f3f88931..f629b31e 100644 --- a/imagelib/transforms/kpTransformCrop_TextSelection.cpp +++ b/imagelib/transforms/kpTransformCrop_TextSelection.cpp @@ -1,73 +1,76 @@ /* Copyright (c) 2003-2007 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_TOOL_CROP 0 #include "kpTransformCrop.h" #include "kpTransformCropPrivate.h" #include "commands/imagelib/effects/kpEffectClearCommand.h" #include "commands/kpMacroCommand.h" #include "mainWindow/kpMainWindow.h" #include "commands/tools/selection/kpToolSelectionMoveCommand.h" -#include "kpLogCategories.h" void kpTransformCrop_TextSelection (kpMainWindow *mainWindow, const QString &commandName, kpCommand *resizeDocCommand) { kpCommandEnvironment *environ = mainWindow->commandEnvironment (); auto *macroCmd = new kpMacroCommand (commandName, environ); macroCmd->addCommand (resizeDocCommand); +#if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\tisText"; qCDebug(kpLogImagelib) << "\tclearing doc with trans cmd"; +#endif macroCmd->addCommand ( new kpEffectClearCommand ( false/*act on doc*/, kpColor::Transparent, environ)); +#if DEBUG_KP_TOOL_CROP qCDebug(kpLogImagelib) << "\tmoving sel to (0,0) cmd"; +#endif kpToolSelectionMoveCommand *moveCmd = new kpToolSelectionMoveCommand ( QString()/*uninteresting child of macro cmd*/, environ); moveCmd->moveTo (QPoint (0, 0), true/*move on exec, not now*/); moveCmd->finalize (); macroCmd->addCommand (moveCmd); mainWindow->addImageOrSelectionCommand ( macroCmd, true/*add create cmd*/, true/*add create content cmd*/); } diff --git a/layers/selections/image/kpAbstractImageSelection.cpp b/layers/selections/image/kpAbstractImageSelection.cpp index 842875ac..288dc92c 100644 --- a/layers/selections/image/kpAbstractImageSelection.cpp +++ b/layers/selections/image/kpAbstractImageSelection.cpp @@ -1,557 +1,589 @@ /* Copyright (c) 2003-2007 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_SELECTION 0 #include "layers/selections/image/kpAbstractImageSelection.h" #include #include #include "kpLogCategories.h" //--------------------------------------------------------------------- // Returns whether can be set to have . // In other words, this is the precondition for .setBaseImage(width () && baseImage.height () == sel->height ()); } //--------------------------------------------------------------------- struct kpAbstractImageSelectionPrivate { kpImage baseImage; kpImageSelectionTransparency transparency; // The mask for the image, after selection transparency (a.k.a. background // subtraction) is applied. QBitmap transparencyMaskCache; // OPT: calculate lazily i.e. on-demand only }; //--------------------------------------------------------------------- // protected kpAbstractImageSelection::kpAbstractImageSelection ( const kpImageSelectionTransparency &transparency) : kpAbstractSelection (), d (new kpAbstractImageSelectionPrivate ()) { setTransparency (transparency); } //--------------------------------------------------------------------- // protected kpAbstractImageSelection::kpAbstractImageSelection (const QRect &rect, const kpImage &baseImage, const kpImageSelectionTransparency &transparency) : kpAbstractSelection (rect), d (new kpAbstractImageSelectionPrivate ()) { // This also checks that and have compatible // relative dimensions. setBaseImage (baseImage); setTransparency (transparency); } //--------------------------------------------------------------------- // protected kpAbstractImageSelection::kpAbstractImageSelection (const QRect &rect, const kpImageSelectionTransparency &transparency) : kpAbstractSelection (rect), d (new kpAbstractImageSelectionPrivate ()) { setTransparency (transparency); } //--------------------------------------------------------------------- // protected kpAbstractImageSelection &kpAbstractImageSelection::operator= ( const kpAbstractImageSelection &rhs) { kpAbstractSelection::operator= (rhs); d->baseImage = rhs.d->baseImage; d->transparency = rhs.d->transparency; d->transparencyMaskCache = rhs.d->transparencyMaskCache; return *this; } //--------------------------------------------------------------------- // protected kpAbstractImageSelection::~kpAbstractImageSelection () { delete d; } //--------------------------------------------------------------------- // public virtual [base kpAbstractSelection] bool kpAbstractImageSelection::readFromStream (QDataStream &stream) { if (!kpAbstractSelection::readFromStream (stream )) { return false; } QImage qimage; stream >> qimage; +#if DEBUG_KP_SELECTION && 1 qCDebug(kpLogLayers) << "\timage: w=" << qimage.width () << " h=" << qimage.height () << " depth=" << qimage.depth (); +#endif if (!qimage.isNull ()) { // Image size does not match the selection's dimensions? // This call only accesses our superclass' fields, which have already // been read in. if (!::CanSetBaseImageTo (this, qimage)) { return false; } d->baseImage = qimage; } // (was just a selection border in the clipboard, even though KolourPaint's // GUI doesn't allow you to copy such a thing into the clipboard) else { d->baseImage = kpImage (); } // TODO: Reset transparency mask? // TODO: Concrete subclass need to emit changed()? // [we can't since changed() must be called after all reading // is complete and subclasses always call this method // _before_ their reading logic] return true; } //--------------------------------------------------------------------- // public virtual [base kpAbstractSelection] void kpAbstractImageSelection::writeToStream (QDataStream &stream) const { kpAbstractSelection::writeToStream (stream); if (!d->baseImage.isNull ()) { const QImage image = d->baseImage; + #if DEBUG_KP_SELECTION && 1 qCDebug(kpLogLayers) << "\twrote image rect=" << image.rect (); + #endif stream << image; } else { + #if DEBUG_KP_SELECTION && 1 qCDebug(kpLogLayers) << "\twrote no image because no pixmap"; + #endif stream << QImage (); } } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] QString kpAbstractImageSelection::name () const { return i18n ("Selection"); } //--------------------------------------------------------------------- // public virtual [base kpAbstractSelection] kpCommandSize::SizeType kpAbstractImageSelection::size () const { return kpAbstractSelection::size () + kpCommandSize::ImageSize (d->baseImage) + (d->transparencyMaskCache.width() * d->transparencyMaskCache.height()) / 8; } //--------------------------------------------------------------------- // public kpCommandSize::SizeType kpAbstractImageSelection::sizeWithoutImage () const { return (size () - kpCommandSize::ImageSize (d->baseImage)); } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] int kpAbstractImageSelection::minimumWidth () const { return 1; } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] int kpAbstractImageSelection::minimumHeight () const { return 1; } //--------------------------------------------------------------------- // public virtual QBitmap kpAbstractImageSelection::shapeBitmap (bool nullForRectangular) const { (void) nullForRectangular; Q_ASSERT (boundingRect ().isValid ()); QBitmap maskBitmap (width (), height ()); maskBitmap.fill (Qt::color0/*transparent*/); { QPainter painter(&maskBitmap); painter.setPen (Qt::color1/*opaque*/); painter.setBrush (Qt::color1/*opaque*/); QPolygon points = calculatePoints (); points.translate (-x (), -y ()); // Unlike QPainter::drawRect(), this draws the points literally // without being 1 pixel wider and higher. This requires a QPen // or it will draw 1 pixel narrower and shorter. painter.drawPolygon (points, Qt::OddEvenFill); } return maskBitmap; } //--------------------------------------------------------------------- // public kpImage kpAbstractImageSelection::givenImageMaskedByShape (const kpImage &image) const { +#if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "kpAbstractImageSelection::givenImageMaskedByShape() boundingRect=" - << boundingRect (); + << boundingRect () << endl; +#endif Q_ASSERT (image.width () == width () && image.height () == height ()); if (isRectangular ()) { return image; } const QRegion mRegion = shapeRegion ().translated (-topLeft ()); +#if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\tshapeRegion=" << shapeRegion () << " [rect=" << shapeRegion ().boundingRect () << "]" << " calculatePoints=" << calculatePoints () - << " [rect=" << calculatePoints ().boundingRect () << "]"; + << " [rect=" << calculatePoints ().boundingRect () << "]" + << endl; +#endif kpImage retImage(width (), height (), QImage::Format_ARGB32_Premultiplied); retImage.fill(0); // transparent QPainter painter(&retImage); painter.setClipRegion(mRegion); painter.drawImage(0, 0, image); painter.end(); return retImage; } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] bool kpAbstractImageSelection::hasContent () const { return !d->baseImage.isNull (); } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] void kpAbstractImageSelection::deleteContent () { if (!hasContent ()) { return; } setBaseImage (kpImage ()); } //--------------------------------------------------------------------- // public kpImage kpAbstractImageSelection::baseImage () const { return d->baseImage; } //--------------------------------------------------------------------- // public void kpAbstractImageSelection::setBaseImage (const kpImage &baseImage) { Q_ASSERT (::CanSetBaseImageTo (this, baseImage)); // qt doc: the image format must be set to Format_ARGB32Premultiplied or Format_ARGB32 // for the composition modes to have any effect d->baseImage = baseImage.convertToFormat(QImage::Format_ARGB32_Premultiplied); recalculateTransparencyMaskCache (); emit changed (boundingRect ()); } //--------------------------------------------------------------------- // public kpImageSelectionTransparency kpAbstractImageSelection::transparency () const { return d->transparency; } //--------------------------------------------------------------------- // public bool kpAbstractImageSelection::setTransparency ( const kpImageSelectionTransparency &transparency, bool checkTransparentPixmapChanged) { if (d->transparency == transparency) { return false; } d->transparency = transparency; bool haveChanged = true; QBitmap oldTransparencyMaskCache = d->transparencyMaskCache; recalculateTransparencyMaskCache (); if ( oldTransparencyMaskCache.size() == d->transparencyMaskCache.size() ) { if (d->transparencyMaskCache.isNull ()) { + #if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\tboth old and new pixmaps are null - nothing changed"; + #endif haveChanged = false; } else if (checkTransparentPixmapChanged) { QImage oldTransparencyMaskImage = oldTransparencyMaskCache.toImage(); QImage newTransparencyMaskImage = d->transparencyMaskCache.toImage(); bool changed = false; for (int y = 0; y < oldTransparencyMaskImage.height () && !changed; y++) { for (int x = 0; x < oldTransparencyMaskImage.width () && !changed; x++) { if (kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y) != kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y)) { + #if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\tdiffer at " << QPoint (x, y) << " old=" << kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y).toQRgb () - << " new=" << kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y).toQRgb (); + << " new=" << kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y).toQRgb () + << endl; + #endif changed = true; break; } } } if (!changed) { haveChanged = false; } } } if (haveChanged) { emit changed (boundingRect ()); } return haveChanged; } //--------------------------------------------------------------------- // private void kpAbstractImageSelection::recalculateTransparencyMaskCache () { +#if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "kpAbstractImageSelection::recalculateTransparencyMaskCache()"; +#endif if (d->baseImage.isNull ()) { + #if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\tno image - no need for transparency mask"; + #endif d->transparencyMaskCache = QBitmap (); return; } if (d->transparency.isOpaque ()) { + #if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\topaque - no need for transparency mask"; + #endif d->transparencyMaskCache = QBitmap (); return; } d->transparencyMaskCache = QBitmap(d->baseImage.size()); QPainter transparencyMaskPainter (&d->transparencyMaskCache); bool hasTransparent = false; for (int y = 0; y < d->baseImage.height (); y++) { for (int x = 0; x < d->baseImage.width (); x++) { const kpColor pixelCol = kpPixmapFX::getColorAtPixel (d->baseImage, x, y); if (pixelCol == kpColor::Transparent || pixelCol.isSimilarTo (d->transparency.transparentColor (), d->transparency.processedColorSimilarity ())) { transparencyMaskPainter.setPen (Qt::color1/*transparent*/); hasTransparent = true; } else { transparencyMaskPainter.setPen (Qt::color0/*opaque*/); } transparencyMaskPainter.drawPoint (x, y); } } transparencyMaskPainter.end (); if (!hasTransparent) { + #if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\tcolour useless - completely opaque"; + #endif d->transparencyMaskCache = QBitmap (); return; } } //--------------------------------------------------------------------- // public kpImage kpAbstractImageSelection::transparentImage () const { kpImage image = baseImage (); if (!d->transparencyMaskCache.isNull ()) { QPainter painter(&image); painter.setCompositionMode(QPainter::CompositionMode_Clear); painter.drawPixmap(0, 0, d->transparencyMaskCache); } return image; } //--------------------------------------------------------------------- // public void kpAbstractImageSelection::fill (const kpColor &color) { QImage newImage(width(), height(), QImage::Format_ARGB32_Premultiplied); newImage.fill(color.toQRgb()); // LOTODO: Maybe disable Image/Clear menu item if transparent color if ( !color.isTransparent() ) { QPainter painter(&newImage); painter.setCompositionMode(QPainter::CompositionMode_Clear); painter.drawPixmap(0, 0, shapeBitmap()); } setBaseImage (newImage); } //--------------------------------------------------------------------- // public virtual void kpAbstractImageSelection::flip (bool horiz, bool vert) { +#if DEBUG_KP_SELECTION && 1 qCDebug(kpLogLayers) << "kpAbstractImageSelection::flip(horiz=" << horiz << ",vert=" << vert << ")"; +#endif if (!d->baseImage.isNull ()) { + #if DEBUG_KP_SELECTION && 1 qCDebug(kpLogLayers) << "\thave pixmap - flipping that"; + #endif d->baseImage = d->baseImage.mirrored(horiz, vert); } if (!d->transparencyMaskCache.isNull ()) { + #if DEBUG_KP_SELECTION && 1 qCDebug(kpLogLayers) << "\thave transparency mask - flipping that"; + #endif QImage image = d->transparencyMaskCache.toImage().mirrored(horiz, vert); d->transparencyMaskCache = QBitmap::fromImage(image); } emit changed (boundingRect ()); } //--------------------------------------------------------------------- static void Paint (const kpAbstractImageSelection *sel, const kpImage &srcImage, QImage *destImage, const QRect &docRect) { if (!srcImage.isNull ()) { kpPixmapFX::paintPixmapAt (destImage, sel->topLeft () - docRect.topLeft (), srcImage); } } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] void kpAbstractImageSelection::paint (QImage *destImage, const QRect &docRect) const { ::Paint (this, transparentImage (), destImage, docRect); } //--------------------------------------------------------------------- // public void kpAbstractImageSelection::paintWithBaseImage (QImage *destImage, const QRect &docRect) const { ::Paint (this, baseImage (), destImage, docRect); } //--------------------------------------------------------------------- + + diff --git a/layers/selections/image/kpFreeFormImageSelection.cpp b/layers/selections/image/kpFreeFormImageSelection.cpp index c000f95e..1268fe2a 100644 --- a/layers/selections/image/kpFreeFormImageSelection.cpp +++ b/layers/selections/image/kpFreeFormImageSelection.cpp @@ -1,388 +1,394 @@ /* Copyright (c) 2003-2007 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_SELECTION 0 #include "layers/selections/image/kpFreeFormImageSelection.h" #include "kpLogCategories.h" #include "imagelib/kpPainter.h" struct kpFreeFormImageSelectionPrivate { QPolygon orgPoints; // Various Qt methods that take a QPolygon interpolate points differently // (e.g. QPainter::drawPolygon() vs QRegion(QPolygon)) when given consecutive // points that are not cardinally adjacent e.g. these 2 points: // // # // # // // are diagonally, but not cardinally, adjacent. They are rendered // inconsistently. Also, points which are not adjacent at all definitely // require interpolation and are inconsistently rendered: // // # // # // // So, we only pass cardinally interpolated points to those methods to // avoid this issue: // // ## // # // // These interpolated points are stored in . Regarding // , see the APIDoc for cardinallyAdjacentPointsLoop(). QPolygon cardPointsCache, cardPointsLoopCache; }; kpFreeFormImageSelection::kpFreeFormImageSelection ( const kpImageSelectionTransparency &transparency) : kpAbstractImageSelection (transparency), d (new kpFreeFormImageSelectionPrivate ()) { } kpFreeFormImageSelection::kpFreeFormImageSelection (const QPolygon &points, const kpImage &baseImage, const kpImageSelectionTransparency &transparency) : kpAbstractImageSelection (points.boundingRect (), baseImage, transparency), d (new kpFreeFormImageSelectionPrivate ()) { d->orgPoints = points; recalculateCardinallyAdjacentPoints (); } kpFreeFormImageSelection::kpFreeFormImageSelection (const QPolygon &points, const kpImageSelectionTransparency &transparency) : kpAbstractImageSelection (points.boundingRect (), transparency), d (new kpFreeFormImageSelectionPrivate ()) { d->orgPoints = points; recalculateCardinallyAdjacentPoints (); } kpFreeFormImageSelection::kpFreeFormImageSelection (const kpFreeFormImageSelection &rhs) : kpAbstractImageSelection (), d (new kpFreeFormImageSelectionPrivate ()) { *this = rhs; } kpFreeFormImageSelection &kpFreeFormImageSelection::operator= (const kpFreeFormImageSelection &rhs) { kpAbstractImageSelection::operator= (rhs); d->orgPoints = rhs.d->orgPoints; d->cardPointsCache = rhs.d->cardPointsCache; d->cardPointsLoopCache = rhs.d->cardPointsLoopCache; return *this; } // public virtual [kpAbstractSelection] kpFreeFormImageSelection *kpFreeFormImageSelection::clone () const { kpFreeFormImageSelection *sel = new kpFreeFormImageSelection (); *sel = *this; return sel; } kpFreeFormImageSelection::~kpFreeFormImageSelection () { delete d; } // public virtual [kpAbstractSelection] int kpFreeFormImageSelection::serialID () const { return SerialID; } // public virtual [base kpAbstractImageSelection] bool kpFreeFormImageSelection::readFromStream (QDataStream &stream) { if (!kpAbstractImageSelection::readFromStream (stream)) { return false; } stream >> d->orgPoints; recalculateCardinallyAdjacentPoints (); return true; } // public virtual [base kpAbstractImageSelection] void kpFreeFormImageSelection::writeToStream (QDataStream &stream) const { kpAbstractImageSelection::writeToStream (stream); stream << d->orgPoints; } // public virtual [base kpAbstractImageSelection] kpCommandSize::SizeType kpFreeFormImageSelection::size () const { return kpAbstractImageSelection::size () + (kpCommandSize::PolygonSize (d->orgPoints) + kpCommandSize::PolygonSize (d->cardPointsCache) + kpCommandSize::PolygonSize (d->cardPointsLoopCache)); } // public virtual [kpAbstractSelection] bool kpFreeFormImageSelection::isRectangular () const { return false; } // public QPolygon kpFreeFormImageSelection::originalPoints () const { return d->orgPoints; } static QPolygon RecalculateCardinallyAdjacentPoints (const QPolygon &points) { +#if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "kpFreeFormImageSelection.cpp:RecalculateCardinallyAdjacentPoints()"; qCDebug(kpLogLayers) << "\tpoints=" << points; +#endif // Filter out duplicates. QPolygon noDups; for (const auto &p : points) { if (!noDups.isEmpty () && p == noDups.last ()) { continue; } noDups.append (p); } +#if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\twithout dups=" << noDups; +#endif // Interpolate to ensure cardinal adjacency. QPolygon cardPoints; for (const auto &p : noDups) { if (!cardPoints.isEmpty () && !kpPainter::pointsAreCardinallyAdjacent (p, cardPoints.last ())) { const QPoint lastPoint = cardPoints.last (); QList interpPoints = kpPainter::interpolatePoints ( lastPoint, p, true/*cardinal adjacency*/); Q_ASSERT (interpPoints.size () >= 2); Q_ASSERT (interpPoints [0] == lastPoint); Q_ASSERT (interpPoints.last () == p); for (int i = 1/*skip already existing point*/; i < interpPoints.size (); i++) { cardPoints.append (interpPoints [i]); } } else { cardPoints.append (p); } } +#if DEBUG_KP_SELECTION qCDebug(kpLogLayers) << "\tcardinally adjacent=" << cardPoints; +#endif return cardPoints; } // protected void kpFreeFormImageSelection::recalculateCardinallyAdjacentPoints () { d->cardPointsCache = ::RecalculateCardinallyAdjacentPoints (d->orgPoints); QPolygon pointsLoop = d->cardPointsCache; if (!pointsLoop.isEmpty ()) { pointsLoop.append (pointsLoop.first ()); } // OPT: We know this method only needs to act on the last 2 points of // "pointLoop", since the previous points are definitely cardinally // adjacent. d->cardPointsLoopCache = ::RecalculateCardinallyAdjacentPoints (pointsLoop); } // public QPolygon kpFreeFormImageSelection::cardinallyAdjacentPoints () const { return d->cardPointsCache; } // public QPolygon kpFreeFormImageSelection::cardinallyAdjacentPointsLoop () const { return d->cardPointsLoopCache; } // public virtual [kpAbstractSelection] QPolygon kpFreeFormImageSelection::calculatePoints () const { return d->cardPointsLoopCache; } // protected virtual [kpAbstractSelection] QRegion kpFreeFormImageSelection::shapeRegion () const { const QRegion region = QRegion (d->cardPointsLoopCache, Qt::OddEvenFill); // In Qt4, while QPainter::drawRect() gives you rectangles 1 pixel // wider and higher, QRegion(QPolygon) gives you regions 1 pixel // narrower and shorter! Compensate for this by merging shifted // versions of the region. This seems to be consistent with shapeBitmap() // but I am a bit worried. // // Regarding alternative solutions: // 1. Instead of doing this region shifting and merging, if we were to // construct a QRegion simply from a point array with 4 points for // every point in "d->cardPointsLoopCache" (4 points = original point + 3 // translations below), it probably wouldn't work because the order of // the points in any point array matter for the odd-even fill // algorithm. This would probably manifest as problems with // self-intersecting borders. // 2. Constructing a QRegion from QBitmap (from shapeBitmap()) is probably // very slow since it would have to read each pixel of the QBitmap. // Having said that, this is probably the safest option as region shifting // is dodgy. Also, this would guarantee that shapeBitmap() and shapeRegion() // are consistent and we wouldn't need cardinally adjacent points either // (d->cardPointsCache and d->cardPointsLoopCache). const QRegion regionX = region.translated (1, 0); const QRegion regionY = region.translated (0, 1); const QRegion regionXY = region.translated (1, 1); return region.united (regionX).united (regionY).united (regionXY); } // public virtual [kpAbstractSelection] bool kpFreeFormImageSelection::contains (const QPoint &point) const { if (!boundingRect ().contains (point)) { return false; } // We can't use the baseImage() (when non-null) and get the transparency of // the pixel at , instead of this region test, as the pixel may be // transparent but still within the border. return shapeRegion ().contains (point); } // public virtual [base kpAbstractSelection] void kpFreeFormImageSelection::moveBy (int dx, int dy) { d->orgPoints.translate (dx, dy); d->cardPointsCache.translate (dx, dy); d->cardPointsLoopCache.translate (dx, dy); // Call base last since it fires the changed() signal and we only // want that to fire at the very end of this method, after all // the selection state has been changed. kpAbstractImageSelection::moveBy (dx, dy); } //--------------------------------------------------------------------- static void FlipPoints (QPolygon *points, bool horiz, bool vert, const QRect &oldRect) { points->translate (-oldRect.x (), -oldRect.y ()); const QTransform matrix (horiz ? -1 : +1, // m11 0, // m12 0, // m21 vert ? -1 : +1, // m22 horiz ? (oldRect.width() - 1) : 0, // dx vert ? (oldRect.height() - 1) : 0); // dy #if !defined (QT_NO_DEBUG) && !defined (NDEBUG) QPolygon oldPoints = *points; #endif *points = matrix.map (*points); #if !defined (QT_NO_DEBUG) && !defined (NDEBUG) // Sanity check: flipping the points twice gives us the original points. Q_ASSERT (oldPoints == matrix.map (*points)); #endif points->translate (oldRect.x (), oldRect.y ()); } //--------------------------------------------------------------------- // public virtual [base kpAbstractImageSelection] void kpFreeFormImageSelection::flip (bool horiz, bool vert) { ::FlipPoints (&d->orgPoints, horiz, vert, boundingRect ()); ::FlipPoints (&d->cardPointsCache, horiz, vert, boundingRect ()); ::FlipPoints (&d->cardPointsLoopCache, horiz, vert, boundingRect ()); // Call base last since it fires the changed() signal and we only // want that to fire at the very end of this method, after all // the selection state has been changed. kpAbstractImageSelection::flip (horiz, vert); } //--------------------------------------------------------------------- // public virtual [kpAbstractSelection] void kpFreeFormImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, bool selectionFinished) const { if (selectionFinished) { paintPolygonalBorder (cardinallyAdjacentPointsLoop (), destPixmap, docRect, selectionFinished); } else { paintPolygonalBorder (cardinallyAdjacentPoints (), destPixmap, docRect, selectionFinished); } } diff --git a/layers/selections/image/kpImageSelectionTransparency.cpp b/layers/selections/image/kpImageSelectionTransparency.cpp index e138b26c..37b79ef5 100644 --- a/layers/selections/image/kpImageSelectionTransparency.cpp +++ b/layers/selections/image/kpImageSelectionTransparency.cpp @@ -1,195 +1,206 @@ /* Copyright (c) 2003-2007 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_SELECTION_TRANSPARENCY 0 #include "layers/selections/image/kpImageSelectionTransparency.h" #include "kpLogCategories.h" #include "imagelib/kpColor.h" #include "widgets/colorSimilarity/kpColorSimilarityHolder.h" //--------------------------------------------------------------------- kpImageSelectionTransparency::kpImageSelectionTransparency () : m_isOpaque (true) { setColorSimilarity (0); } //--------------------------------------------------------------------- kpImageSelectionTransparency::kpImageSelectionTransparency (const kpColor &transparentColor, double colorSimilarity) : m_isOpaque (false), m_transparentColor (transparentColor) { setColorSimilarity (colorSimilarity); } //--------------------------------------------------------------------- kpImageSelectionTransparency::kpImageSelectionTransparency (bool isOpaque, const kpColor &transparentColor, double colorSimilarity) : m_isOpaque (isOpaque), m_transparentColor (transparentColor) { setColorSimilarity (colorSimilarity); } //--------------------------------------------------------------------- bool kpImageSelectionTransparency::operator== (const kpImageSelectionTransparency &rhs) const { +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 qCDebug(kpLogLayers) << "kpImageSelectionTransparency::operator==()"; +#endif if (m_isOpaque != rhs.m_isOpaque) { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 qCDebug(kpLogLayers) << "\tdifferent opacity: lhs=" << m_isOpaque - << " rhs=" << rhs.m_isOpaque; + << " rhs=" << rhs.m_isOpaque + << endl; + #endif return false; } if (m_isOpaque) { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 qCDebug(kpLogLayers) << "\tboth opaque - eq"; + #endif return true; } +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 qCDebug(kpLogLayers) << "\tcolours: lhs=" << (int *) m_transparentColor.toQRgb () - << " rhs=" << (int *) rhs.m_transparentColor.toQRgb (); + << " rhs=" << (int *) rhs.m_transparentColor.toQRgb () + << endl; qCDebug(kpLogLayers) << "\tcolour similarity: lhs=" << m_colorSimilarity - << " rhs=" << rhs.m_colorSimilarity; + << " rhs=" << rhs.m_colorSimilarity + << endl; +#endif return (m_transparentColor == rhs.m_transparentColor && m_colorSimilarity == rhs.m_colorSimilarity); } //--------------------------------------------------------------------- bool kpImageSelectionTransparency::operator!= (const kpImageSelectionTransparency &rhs) const { return !(*this == rhs); } //--------------------------------------------------------------------- kpImageSelectionTransparency::~kpImageSelectionTransparency () = default; //--------------------------------------------------------------------- // public bool kpImageSelectionTransparency::isOpaque () const { return m_isOpaque; } //--------------------------------------------------------------------- // public bool kpImageSelectionTransparency::isTransparent () const { return !isOpaque (); } //--------------------------------------------------------------------- // public void kpImageSelectionTransparency::setOpaque (bool yes) { m_isOpaque = yes; } //--------------------------------------------------------------------- // public void kpImageSelectionTransparency::setTransparent (bool yes) { setOpaque (!yes); } //--------------------------------------------------------------------- // public kpColor kpImageSelectionTransparency::transparentColor () const { if (m_isOpaque) { // There are legitimate uses for this so no qCCritical(kpLogLayers) qCDebug(kpLogLayers) << "kpImageSelectionTransparency::transparentColor() " "getting transparent color even though opaque"; } return m_transparentColor; } //--------------------------------------------------------------------- // public void kpImageSelectionTransparency::setTransparentColor (const kpColor &transparentColor) { m_transparentColor = transparentColor; } //--------------------------------------------------------------------- // public double kpImageSelectionTransparency::colorSimilarity () const { if (m_colorSimilarity < 0 || m_colorSimilarity > kpColorSimilarityHolder::MaxColorSimilarity) { qCCritical(kpLogLayers) << "kpImageSelectionTransparency::colorSimilarity() invalid colorSimilarity"; return 0; } return m_colorSimilarity; } //--------------------------------------------------------------------- // pubulic void kpImageSelectionTransparency::setColorSimilarity (double colorSimilarity) { m_colorSimilarity = colorSimilarity; m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity); } //--------------------------------------------------------------------- // public int kpImageSelectionTransparency::processedColorSimilarity () const { return m_processedColorSimilarity; } //--------------------------------------------------------------------- diff --git a/lgpl/CMakeLists.txt b/lgpl/CMakeLists.txt index 9e701545..9268419d 100644 --- a/lgpl/CMakeLists.txt +++ b/lgpl/CMakeLists.txt @@ -1,37 +1,36 @@ # # LGPL Library # # This MUST be a dynamic link library to avoid LGPL license infection. # include(GenerateExportHeader) set(kolourpaint_lgpl_SRCS generic/kpUrlFormatter.cpp generic/kpColorCollection.cpp generic/widgets/kpColorCellsBase.cpp - ../kpLogCategories.cpp ) add_library(kolourpaint_lgpl SHARED ${kolourpaint_lgpl_SRCS}) set(kolourpaint_lgpl_version 5) target_link_libraries(kolourpaint_lgpl KF5::KDELibs4Support KF5::I18n KF5::GuiAddons KF5::WidgetsAddons KF5::KIOCore Qt5::Widgets ) set_target_properties(kolourpaint_lgpl PROPERTIES VERSION ${kolourpaint_lgpl_version} DEFINE_SYMBOL MAKE_KOLOURPAINT4_LGPL_LIB ) generate_export_header(kolourpaint_lgpl BASE_NAME kolourpaint_lgpl) install(TARGETS kolourpaint_lgpl ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/lgpl/generic/kpColorCollection.cpp b/lgpl/generic/kpColorCollection.cpp index ab751105..9107f385 100644 --- a/lgpl/generic/kpColorCollection.cpp +++ b/lgpl/generic/kpColorCollection.cpp @@ -1,509 +1,535 @@ // REFACT0R: Remote open/save file logic is duplicated in kpDocument. // HITODO: Test when remote file support in KDE 4 stabilizes /* This file is part of the KDE libraries Copyright (C) 1999 Waldo Bastian (bastian@kde.org) Copyright (C) 2007 Clarence Dang (dang@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //----------------------------------------------------------------------------- // KDE color collection #define DEBUG_KP_COLOR_COLLECTION 0 #include "kpColorCollection.h" #include "kpUrlFormatter.h" #include // kdelibs4support #include #include #include "kpLogCategories.h" #include #include #include #include #include #include #include #include struct ColorNode { ColorNode(const QColor &c, const QString &n) : color(c), name(n) {} QColor color; QString name; }; //--------------------------------------------------------------------- //BEGIN kpColorCollectionPrivate class kpColorCollectionPrivate { public: kpColorCollectionPrivate(); kpColorCollectionPrivate(const kpColorCollectionPrivate&); QList colorList; QString name; QString desc; kpColorCollection::Editable editable; }; kpColorCollectionPrivate::kpColorCollectionPrivate() : editable(kpColorCollection::Yes) { } kpColorCollectionPrivate::kpColorCollectionPrivate(const kpColorCollectionPrivate& p) : colorList(p.colorList), name(p.name), desc(p.desc), editable(p.editable) { } //END kpColorCollectionPrivate //--------------------------------------------------------------------- QStringList kpColorCollection::installedCollections() { QStringList paletteList; QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, "colors", QStandardPaths::LocateDirectory); for (const auto &path : paths) { paletteList.append(QDir(path).entryList(QStringList(), QDir::Files)); } return paletteList; } kpColorCollection::kpColorCollection() { d = new kpColorCollectionPrivate(); } kpColorCollection::kpColorCollection(const kpColorCollection &p) { d = new kpColorCollectionPrivate(*p.d); } kpColorCollection::~kpColorCollection() { // Need auto-save? delete d; } static void CouldNotOpenDialog (const QUrl &url, QWidget *parent) { KMessageBox::sorry (parent, i18n ("Could not open color palette \"%1\".", kpUrlFormatter::PrettyFilename (url))); } // TODO: Set d->editable? bool kpColorCollection::open(const QUrl &url, QWidget *parent) { QString tempPaletteFilePath; if (url.isEmpty () || !KIO::NetAccess::download (url, tempPaletteFilePath, parent)) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\tcould not download"; + #endif ::CouldNotOpenDialog (url, parent); return false; } // sync: remember to "KIO::NetAccess::removeTempFile (tempPaletteFilePath)" in all exit paths QFile paletteFile(tempPaletteFilePath); if (!paletteFile.exists() || !paletteFile.open(QIODevice::ReadOnly)) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\tcould not open qfile"; + #endif KIO::NetAccess::removeTempFile (tempPaletteFilePath); ::CouldNotOpenDialog (url, parent); return false; } // Read first line // Expected "GIMP Palette" QString line = QString::fromLocal8Bit(paletteFile.readLine()); if (line.indexOf(" Palette") == -1) { KIO::NetAccess::removeTempFile (tempPaletteFilePath); KMessageBox::sorry (parent, i18n ("Could not open color palette \"%1\" - unsupported format.\n" "The file may be corrupt.", kpUrlFormatter::PrettyFilename (url))); return false; } QList newColorList; QString newDesc; while( !paletteFile.atEnd() ) { line = QString::fromLocal8Bit(paletteFile.readLine()); if (line[0] == '#') { // This is a comment line line = line.mid(1); // Strip '#' line = line.trimmed(); // Strip remaining white space.. if (!line.isEmpty()) { newDesc += line+'\n'; // Add comment to description } } else { // This is a color line, hopefully line = line.trimmed(); if (line.isEmpty()) continue; int r, g, b; int pos = 0; if (sscanf(line.toLatin1(), "%d %d %d%n", &r, &g, &b, &pos) >= 3) { r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); QString name = line.mid(pos).trimmed(); newColorList.append(ColorNode(QColor(r, g, b), name)); } } } d->colorList = newColorList; d->name.clear (); d->desc = newDesc; KIO::NetAccess::removeTempFile (tempPaletteFilePath); return true; } static void CouldNotOpenKDEDialog (const QString &name, QWidget *parent) { KMessageBox::sorry (parent, i18n ("Could not open KDE color palette \"%1\".", name)); } bool kpColorCollection::openKDE(const QString &name, QWidget *parent) { +#if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "name=" << name; +#endif if (name.isEmpty()) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "name.isEmpty"; + #endif ::CouldNotOpenKDEDialog (name, parent); return false; } QString filename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "colors/" + name); if (filename.isEmpty()) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "could not find file"; + #endif ::CouldNotOpenKDEDialog (name, parent); return false; } // (this will pop up an error dialog on failure) if (!open (QUrl::fromLocalFile (filename), parent)) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "could not open"; + #endif return false; } d->name = name; +#if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "opened"; +#endif return true; } static void CouldNotSaveDialog (const QUrl &url, QWidget *parent) { // TODO: use file.errorString() KMessageBox::error (parent, i18n ("Could not save color palette as \"%1\".", kpUrlFormatter::PrettyFilename (url))); } static void SaveToFile (kpColorCollectionPrivate *d, QIODevice *device) { // HITODO: QTextStream can fail but does not report errors. // Bug in KColorCollection too. QTextStream str (device); QString description = d->desc.trimmed(); description = '#'+description.split( '\n', QString::KeepEmptyParts).join("\n#"); str << "KDE RGB Palette\n"; str << description << "\n"; for (const auto &node : d->colorList) { // Added for KolourPaint. if(!node.color.isValid ()) continue; int r,g,b; node.color.getRgb(&r, &g, &b); str << r << " " << g << " " << b << " " << node.name << "\n"; } str.flush(); } bool kpColorCollection::saveAs(const QUrl &url, bool showOverwritePrompt, QWidget *parent) const { if (showOverwritePrompt && KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent)) { int result = KMessageBox::warningContinueCancel (parent, i18n ("A color palette called \"%1\" already exists.\n" "Do you want to overwrite it?", kpUrlFormatter::PrettyFilename (url)), QString (), KStandardGuiItem::overwrite ()); if (result != KMessageBox::Continue) return false; } if (url.isLocalFile ()) { const QString filename = url.toLocalFile (); // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or // else, the QSaveFile destructor will overwrite the file, // , despite the failure. QSaveFile atomicFileWriter (filename); { if (!atomicFileWriter.open (QIODevice::WriteOnly)) { // We probably don't need this as has not been // opened. atomicFileWriter.cancelWriting (); + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\treturning false because could not open QSaveFile" << " error=" << atomicFileWriter.error (); + #endif ::CouldNotSaveDialog (url, parent); return false; } // Write to local temporary file. ::SaveToFile (d, &atomicFileWriter); // Atomically overwrite local file with the temporary file // we saved to. if (!atomicFileWriter.commit ()) { atomicFileWriter.cancelWriting (); + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\tcould not close QSaveFile"; + #endif ::CouldNotSaveDialog (url, parent); return false; } } // sync QSaveFile.cancelWriting() } // Remote file? else { // Create temporary file that is deleted when the variable goes // out of scope. QTemporaryFile tempFile; if (!tempFile.open ()) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\treturning false because could not open tempFile"; + #endif ::CouldNotSaveDialog (url, parent); return false; } // Write to local temporary file. ::SaveToFile (d, &tempFile); // Collect name of temporary file now, as QTemporaryFile::fileName() // stops working after close() is called. const QString tempFileName = tempFile.fileName (); + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\ttempFileName='" << tempFileName << "'"; + #endif Q_ASSERT (!tempFileName.isEmpty ()); tempFile.close (); if (tempFile.error () != QFile::NoError) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\treturning false because could not close"; + #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 (tempFileName, url, parent)) { + #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogMisc) << "\treturning false because could not upload"; + #endif ::CouldNotSaveDialog (url, parent); return false; } } d->name.clear (); return true; } bool kpColorCollection::saveKDE(QWidget *parent) const { const QString name = d->name; QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "colors/" + name; const bool ret = saveAs (QUrl::fromLocalFile (filename), false/*no overwite prompt*/, parent); // (d->name is wiped by saveAs()). d->name = name; return ret; } QString kpColorCollection::description() const { return d->desc; } void kpColorCollection::setDescription(const QString &desc) { d->desc = desc; } QString kpColorCollection::name() const { return d->name; } void kpColorCollection::setName(const QString &name) { d->name = name; } kpColorCollection::Editable kpColorCollection::editable() const { return d->editable; } void kpColorCollection::setEditable(Editable editable) { d->editable = editable; } int kpColorCollection::count() const { return (int) d->colorList.count(); } void kpColorCollection::resize(int newCount) { if (newCount == count()) return; else if (newCount < count()) { d->colorList.erase(d->colorList.begin() + newCount, d->colorList.end()); } else if (newCount > count()) { while(newCount > count()) { const int ret = addColor(QColor(), QString()/*color name*/); Q_ASSERT(ret == count() - 1); } } } kpColorCollection& kpColorCollection::operator=( const kpColorCollection &p) { if (&p == this) return *this; d->colorList = p.d->colorList; d->name = p.d->name; d->desc = p.d->desc; d->editable = p.d->editable; return *this; } QColor kpColorCollection::color(int index) const { if ((index < 0) || (index >= count())) return QColor(); return d->colorList[index].color; } int kpColorCollection::findColor(const QColor &color) const { for (int i = 0; i < d->colorList.size(); ++i) { if (d->colorList[i].color == color) return i; } return -1; } QString kpColorCollection::name(int index) const { if ((index < 0) || (index >= count())) return QString(); return d->colorList[index].name; } QString kpColorCollection::name(const QColor &color) const { return name(findColor(color)); } int kpColorCollection::addColor(const QColor &newColor, const QString &newColorName) { d->colorList.append(ColorNode(newColor, newColorName)); return count() - 1; } int kpColorCollection::changeColor(int index, const QColor &newColor, const QString &newColorName) { if ((index < 0) || (index >= count())) return -1; ColorNode& node = d->colorList[index]; node.color = newColor; node.name = newColorName; return index; } int kpColorCollection::changeColor(const QColor &oldColor, const QColor &newColor, const QString &newColorName) { return changeColor( findColor(oldColor), newColor, newColorName); } diff --git a/lgpl/generic/widgets/kpColorCellsBase.cpp b/lgpl/generic/widgets/kpColorCellsBase.cpp index b66907c6..0755c79a 100644 --- a/lgpl/generic/widgets/kpColorCellsBase.cpp +++ b/lgpl/generic/widgets/kpColorCellsBase.cpp @@ -1,543 +1,563 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Martin Jones (mjones@kde.org) Copyright (C) 2007 Roberto Raggi (roberto@kdevelop.org) Copyright (C) 2007 Clarence Dang (dang@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //----------------------------------------------------------------------------- #define DEBUG_KP_COLOR_CELLS_BASE 0 #include "kpColorCellsBase.h" #include #include #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include class kpColorCellsBase::kpColorCellsBasePrivate { public: kpColorCellsBasePrivate(kpColorCellsBase *q): q(q) { colors = nullptr; inMouse = false; selected = -1; shade = false; acceptDrags = false; cellsResizable = true; } kpColorCellsBase *q; // Note: This is a good thing and is _not_ data duplication with the // colors of QTableWidget cells, for the following reasons: // // 1. QColor in Qt4 is full-quality RGB. However, QTableWidget // cells are lossy as their colors may be dithered on the screen. // // Eventually, this field will be changed to a kpColor. // // 2. We change the QTableWidget cells' colors when the widget is // disabled (see changeEvent()). // // Therefore, do not remove this field without better reasons. QColor *colors; QPoint mousePos; int selected; bool shade; bool acceptDrags; bool cellsResizable; bool inMouse; }; kpColorCellsBase::kpColorCellsBase( QWidget *parent, int rows, int cols ) : QTableWidget( parent ), d(new kpColorCellsBasePrivate(this)) { setItemDelegate(new QItemDelegate(this)); setFrameShape(QFrame::NoFrame); d->shade = true; setRowCount( rows ); setColumnCount( cols ); verticalHeader()->setMinimumSectionSize(16); verticalHeader()->hide(); horizontalHeader()->setMinimumSectionSize(16); horizontalHeader()->hide(); d->colors = new QColor [ rows * cols ]; d->selected = 0; d->inMouse = false; // Drag'n'Drop setAcceptDrops( true); setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); viewport()->setBackgroundRole( QPalette::Window ); setBackgroundRole( QPalette::Window ); } kpColorCellsBase::~kpColorCellsBase() { delete [] d->colors; delete d; } void kpColorCellsBase::invalidateAllColors () { for (int r = 0; r < rowCount (); r++) for (int c = 0; c < columnCount (); c++) d->colors [r * columnCount () + c] = QColor (); } void kpColorCellsBase::clear() { invalidateAllColors (); QTableWidget::clear (); } void kpColorCellsBase::clearContents() { invalidateAllColors (); QTableWidget::clearContents (); } void kpColorCellsBase::setRowColumnCounts (int rows, int columns) { const int oldRows = rowCount (), oldCols = columnCount (); const int newRows = rows, newCols = columns; +#if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "oldRows=" << oldRows << "oldCols=" << oldCols << "newRows=" << newRows << "newCols=" << newCols; +#endif if (oldRows == newRows && oldCols == newCols) return; QTableWidget::setColumnCount (newCols); QTableWidget::setRowCount (newRows); QColor *oldColors = d->colors; d->colors = new QColor [newRows * newCols]; for (int r = 0; r < qMin (oldRows, newRows); r++) for (int c = 0; c < qMin (oldCols, newCols); c++) d->colors [r * newCols + c] = oldColors [r * oldCols + c]; delete [] oldColors; } void kpColorCellsBase::setColumnCount (int newColumns) { setRowColumnCounts (rowCount (), newColumns); } void kpColorCellsBase::setRowCount (int newRows) { setRowColumnCounts (newRows, columnCount ()); } QColor kpColorCellsBase::color(int index) const { return d->colors[index]; } int kpColorCellsBase::count() const { return rowCount() * columnCount(); } void kpColorCellsBase::setShading(bool _shade) { d->shade = _shade; } void kpColorCellsBase::setAcceptDrags(bool _acceptDrags) { d->acceptDrags = _acceptDrags; } void kpColorCellsBase::setCellsResizable(bool yes) { d->cellsResizable = yes; } void kpColorCellsBase::setSelected(int index) { Q_ASSERT( index >= 0 && index < count() ); d->selected = index; } int kpColorCellsBase::selectedIndex() const { return d->selected; } //--------------------------------------------------------------------- static void TableWidgetItemSetColor (QTableWidgetItem *tableItem, const QColor &color) { Q_ASSERT (tableItem); QImage image(16, 16, QImage::Format_ARGB32_Premultiplied); QPainter painter(&image); const int StippleSize = 4; QColor useColor; for (int dy = 0; dy < 16; dy += StippleSize) { for (int dx = 0; dx < 16; dx += StippleSize) { const bool parity = ((dy + dx) / StippleSize) % 2; if (!parity) useColor = Qt::white; else useColor = Qt::lightGray; - + painter.fillRect(dx, dy, StippleSize, StippleSize, useColor); } } painter.fillRect(image.rect(), color); painter.end(); tableItem->setData(Qt::BackgroundRole , QBrush(image)); } //--------------------------------------------------------------------- void kpColorCellsBase::setColor( int column, const QColor &colorIn ) { const int tableRow = column / columnCount(); const int tableColumn = column % columnCount(); Q_ASSERT( tableRow >= 0 && tableRow < rowCount() ); Q_ASSERT( tableColumn >= 0 && tableColumn < columnCount() ); QColor color = colorIn; d->colors[column] = color; QTableWidgetItem* tableItem = item(tableRow,tableColumn); if (color.isValid ()) { if ( tableItem == nullptr ) { tableItem = new QTableWidgetItem(); setItem(tableRow,tableColumn,tableItem); } if (isEnabled ()) ::TableWidgetItemSetColor (tableItem, color); } else { delete tableItem; } emit colorChanged (column, color); } void kpColorCellsBase::changeEvent( QEvent* event ) { QTableWidget::changeEvent (event); if (event->type () != QEvent::EnabledChange) return; for (int r = 0; r < rowCount (); r++) { for (int c = 0; c < columnCount (); c++) { const int index = r * columnCount () + c; QTableWidgetItem* tableItem = item(r, c); // See API Doc for this invariant. Q_ASSERT (!!tableItem == d->colors [index].isValid ()); if (!tableItem) continue; QColor color; if (isEnabled ()) color = d->colors [index]; else color = palette ().color (backgroundRole ()); ::TableWidgetItemSetColor (tableItem, color); } } } /*void kpColorCellsBase::paintCell( QPainter *painter, int row, int col ) { painter->setRenderHint( QPainter::Antialiasing , true ); QBrush brush; int w = 1; if (shade) { qDrawShadePanel( painter, 1, 1, cellWidth()-2, cellHeight()-2, palette(), true, 1, &brush ); w = 2; } QColor color = colors[ row * numCols() + col ]; if (!color.isValid()) { if (!shade) return; color = palette().color(backgroundRole()); } const QRect colorRect( w, w, cellWidth()-w*2, cellHeight()-w*2 ); painter->fillRect( colorRect, color ); if ( row * numCols() + col == selected ) { painter->setPen( qGray(color.rgb())>=127 ? Qt::black : Qt::white ); painter->drawLine( colorRect.topLeft(), colorRect.bottomRight() ); painter->drawLine( colorRect.topRight(), colorRect.bottomLeft() ); } }*/ void kpColorCellsBase::resizeEvent( QResizeEvent* e ) { if (d->cellsResizable) { // According to the Qt doc: // If you need to set the width of a given column to a fixed value, call // QHeaderView::resizeSection() on the table's {horizontal,vertical} // header. // Therefore we iterate over each row and column and set the header section // size, as the sizeHint does indeed appear to be ignored in favor of a // minimum size that is larger than what we want. for ( int index = 0 ; index < columnCount() ; index++ ) horizontalHeader()->resizeSection( index, sizeHintForColumn(index) ); for ( int index = 0 ; index < rowCount() ; index++ ) verticalHeader()->resizeSection( index, sizeHintForRow(index) ); } else { // Update scrollbars if they're forced on by a subclass. // TODO: Should the d->cellsResizable path (from kdelibs) do this as well? QTableWidget::resizeEvent (e); } } int kpColorCellsBase::sizeHintForColumn(int /*column*/) const { // TODO: Should it be "(width() - frameWidth() * 2) / columnCount()"? return width() / columnCount() ; } int kpColorCellsBase::sizeHintForRow(int /*row*/) const { // TODO: Should be "(height() - frameWidth() * 2) / rowCount()"? return height() / rowCount() ; } void kpColorCellsBase::mousePressEvent( QMouseEvent *e ) { d->inMouse = true; d->mousePos = e->pos(); } int kpColorCellsBase::positionToCell(const QPoint &pos, bool ignoreBorders, bool allowEmptyCell) const { //TODO ignoreBorders not yet handled Q_UNUSED( ignoreBorders ) const int r = indexAt (pos).row (), c = indexAt (pos).column (); +#if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "r=" << r << "c=" << c; +#endif if (r == -1 || c == -1) return -1; if (!allowEmptyCell && !itemAt(pos)) return -1; const int cell = r * columnCount() + c; /*if (!ignoreBorders) { int border = 2; int x = pos.x() - col * cellWidth(); int y = pos.y() - row * cellHeight(); if ( (x < border) || (x > cellWidth()-border) || (y < border) || (y > cellHeight()-border)) return -1; }*/ return cell; } void kpColorCellsBase::mouseMoveEvent( QMouseEvent *e ) { if( !(e->buttons() & Qt::LeftButton)) return; if(d->inMouse) { int delay = QApplication::startDragDistance(); if(e->x() > d->mousePos.x()+delay || e->x() < d->mousePos.x()-delay || e->y() > d->mousePos.y()+delay || e->y() < d->mousePos.y()-delay){ // Drag color object int cell = positionToCell(d->mousePos); if (cell != -1) { + #if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "beginning drag from cell=" << cell << "color: isValid=" << d->colors [cell].isValid () << " rgba=" << (int *) d->colors [cell].rgba(); + #endif Q_ASSERT (d->colors[cell].isValid()); KColorMimeData::createDrag(d->colors[cell], this)->start(Qt::CopyAction | Qt::MoveAction); + #if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "finished drag"; + #endif } } } } // LOTODO: I'm not quite clear on how the drop actions logic is supposed // to be done e.g.: // // 1. Who is supposed to call setDropAction(). // 2. Which variant of accept(), setAccepted(), acceptProposedAction() etc. // is supposed to be called to accept a move -- rather than copy -- // action. // // Nevertheless, it appears to work -- probably because we restrict // the non-Qt-default move/swap action to be intrawidget. static void SetDropAction (QWidget *self, QDropEvent *event) { // TODO: Would be nice to default to CopyAction if the destination cell // is null. if (event->source () == self && (event->keyboardModifiers () & Qt::ControlModifier) == 0) event->setDropAction(Qt::MoveAction); else event->setDropAction(Qt::CopyAction); } void kpColorCellsBase::dragEnterEvent( QDragEnterEvent *event) { +#if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "kpColorCellsBase::dragEnterEvent() acceptDrags=" << d->acceptDrags << " canDecode=" << KColorMimeData::canDecode(event->mimeData()); +#endif event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData())); if (event->isAccepted ()) ::SetDropAction (this, event); } // Reimplemented to override QTableWidget's override. Else dropping doesn't work. void kpColorCellsBase::dragMoveEvent (QDragMoveEvent *event) { +#if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "kpColorCellsBase::dragMoveEvent() acceptDrags=" << d->acceptDrags << " canDecode=" << KColorMimeData::canDecode(event->mimeData()); +#endif // TODO: Disallow drag that isn't onto a cell. event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData())); if (event->isAccepted ()) ::SetDropAction (this, event); } void kpColorCellsBase::dropEvent( QDropEvent *event) { QColor c=KColorMimeData::fromMimeData(event->mimeData()); const int dragSourceCell = event->source () == this ? positionToCell (d->mousePos, true) : -1; +#if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "kpColorCellsBase::dropEvent()" << "color: rgba=" << (const int *) c.rgba () << "isValid=" << c.isValid() << "source=" << event->source () << "dragSourceCell=" << dragSourceCell; +#endif if( c.isValid()) { ::SetDropAction (this, event); int cell = positionToCell(event->pos(), true, true/*allow empty cell*/); + #if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "\tcell=" << cell; + #endif // TODO: I believe kdelibs forgets to do this. if (cell == -1) return; // Avoid NOP. if (cell == dragSourceCell) return; QColor destOldColor = d->colors [cell]; setColor(cell,c); + #if DEBUG_KP_COLOR_CELLS_BASE qCDebug(kpLogMisc) << "\tdropAction=" << event->dropAction () << "destOldColor.rgba=" << (const int *) destOldColor.rgba (); + #endif if (event->dropAction () == Qt::MoveAction && dragSourceCell != -1) { setColor(dragSourceCell, destOldColor); } } } void kpColorCellsBase::mouseReleaseEvent( QMouseEvent *e ) { int cell = positionToCell(d->mousePos); int currentCell = positionToCell(e->pos()); // If we release the mouse in another cell and we don't have // a drag we should ignore this event. if (currentCell != cell) cell = -1; if ( (cell != -1) && (d->selected != cell) ) { d->selected = cell; const int newRow = cell/columnCount(); const int newColumn = cell%columnCount(); clearSelection(); // we do not want old violet selected cells item(newRow,newColumn)->setSelected(true); } d->inMouse = false; if (cell != -1) { emit colorSelected( cell , color(cell) ); emit colorSelectedWhitButton( cell , color(cell), e->button() ); } } void kpColorCellsBase::mouseDoubleClickEvent( QMouseEvent * /*e*/ ) { int cell = positionToCell(d->mousePos, false, true/*allow empty cell*/); if (cell != -1) emit colorDoubleClicked( cell , color(cell) ); } + + diff --git a/mainWindow/kpMainWindow.cpp b/mainWindow/kpMainWindow.cpp index 136797e8..8d47e62e 100644 --- a/mainWindow/kpMainWindow.cpp +++ b/mainWindow/kpMainWindow.cpp @@ -1,872 +1,924 @@ /* Copyright (c) 2003-2007 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 "kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "environments/tools/kpToolEnvironment.h" #include "widgets/kpColorCells.h" #include "widgets/toolbars/kpColorToolBar.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "environments/document/kpDocumentEnvironment.h" #include "layers/selections/kpSelectionDrag.h" #include "kpThumbnail.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include "generic/kpWidgetMapper.h" #include "views/kpZoomedThumbnailView.h" #include "views/kpZoomedView.h" #include #include #include #include #include #include #include "kpLogCategories.h" //--------------------------------------------------------------------- kpMainWindow::kpMainWindow () : KXmlGuiWindow (nullptr/*parent*/) { init (); open (QUrl (), true/*create an empty doc*/); d->isFullyConstructed = true; } //--------------------------------------------------------------------- kpMainWindow::kpMainWindow (const QUrl &url) : KXmlGuiWindow (nullptr/*parent*/) { init (); open (url, true/*create an empty doc with the same url if url !exist*/); d->isFullyConstructed = true; } //--------------------------------------------------------------------- kpMainWindow::kpMainWindow (kpDocument *newDoc) : KXmlGuiWindow (nullptr/*parent*/) { init (); setDocument (newDoc); d->isFullyConstructed = true; } //--------------------------------------------------------------------- // TODO: Move into appropriate kpMainWindow_*.cpp or another class // private void kpMainWindow::readGeneralSettings () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName () << ")::readGeneralSettings()"; +#endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); d->configFirstTime = cfg.readEntry (kpSettingFirstTime, true); d->configShowGrid = cfg.readEntry (kpSettingShowGrid, false); d->configShowPath = cfg.readEntry (kpSettingShowPath, false); d->moreEffectsDialogLastEffect = cfg.readEntry (kpSettingMoreEffectsLastEffect, 0); kpToolEnvironment::drawAntiAliased = cfg.readEntry(kpSettingDrawAntiAliased, true); if (cfg.hasKey (kpSettingOpenImagesInSameWindow)) { d->configOpenImagesInSameWindow = cfg.readEntry (kpSettingOpenImagesInSameWindow, false); } else { d->configOpenImagesInSameWindow = false; +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tconfigOpenImagesInSameWindow: first time" << " - writing default: " << d->configOpenImagesInSameWindow; +#endif // TODO: More hidden options have to write themselves out on startup, // not on use, to be discoverable (e.g. printing centered on page). cfg.writeEntry (kpSettingOpenImagesInSameWindow, d->configOpenImagesInSameWindow); cfg.sync (); } d->configPrintImageCenteredOnPage = cfg.readEntry (kpSettingPrintImageCenteredOnPage, true); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tGeneral Settings: firstTime=" << d->configFirstTime << " showGrid=" << d->configShowGrid << " showPath=" << d->configShowPath << " moreEffectsDialogLastEffect=" << d->moreEffectsDialogLastEffect << " openImagesInSameWindow=" << d->configOpenImagesInSameWindow << " printImageCenteredOnPage=" << d->configPrintImageCenteredOnPage; +#endif } //--------------------------------------------------------------------- // private void kpMainWindow::readThumbnailSettings () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName () << ")::readThumbnailSettings()"; +#endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); d->configThumbnailShown = cfg.readEntry (kpSettingThumbnailShown, false); d->configThumbnailGeometry = cfg.readEntry (kpSettingThumbnailGeometry, QRect ()); d->configZoomedThumbnail = cfg.readEntry (kpSettingThumbnailZoomed, true); d->configThumbnailShowRectangle = cfg.readEntry (kpSettingThumbnailShowRectangle, true); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tThumbnail Settings: shown=" << d->configThumbnailShown << " geometry=" << d->configThumbnailGeometry << " zoomed=" << d->configZoomedThumbnail << " showRectangle=" << d->configThumbnailShowRectangle; +#endif } //--------------------------------------------------------------------- void kpMainWindow::finalizeGUI(KXMLGUIClient *client) { if ( client == this ) { const QList menuToHide = findChildren("toolToolBarHiddenMenu"); // should only contain one but... for (auto *menu : menuToHide) { menu->menuAction()->setVisible(false); } } } //--------------------------------------------------------------------- // private void kpMainWindow::init () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow(" << objectName () << ")::init()"; QTime totalTime; totalTime.start (); +#endif d = new kpMainWindowPrivate; // // set mainwindow properties // setMinimumSize (320, 260); setAcceptDrops (true); // // read config // // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path KSharedConfig::openConfig ()->reparseConfiguration (); readGeneralSettings (); readThumbnailSettings (); // // create GUI // setupActions (); createStatusBar (); createGUI (); createColorBox (); createToolBox (); // Let the Tool Box take all the vertical space, since it can be quite // tall with all its tool option widgets. This also avoids occasional // bugs like the Tool Box overlapping the Color Tool Bar. setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); // no tabbed docks; does not make sense with only 2 dock widgets setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks); addDockWidget(Qt::BottomDockWidgetArea, d->colorToolBar, Qt::Horizontal); d->scrollView = new kpViewScrollableContainer (this); d->scrollView->setObjectName ( QLatin1String("scrollView" )); connect (d->scrollView, &kpViewScrollableContainer::beganDocResize, this, &kpMainWindow::slotBeganDocResize); connect (d->scrollView, &kpViewScrollableContainer::continuedDocResize, this, &kpMainWindow::slotContinuedDocResize); connect (d->scrollView, &kpViewScrollableContainer::cancelledDocResize, this, &kpMainWindow::slotCancelledDocResize); connect (d->scrollView, &kpViewScrollableContainer::endedDocResize, this, &kpMainWindow::slotEndedDocResize); connect (d->scrollView, &kpViewScrollableContainer::statusMessageChanged, this, &kpMainWindow::slotDocResizeMessageChanged); connect (d->scrollView, &kpViewScrollableContainer::contentsMoved, this, &kpMainWindow::slotScrollViewAfterScroll); setCentralWidget (d->scrollView); // // set initial pos/size of GUI // setAutoSaveSettings (); // our non-XMLGUI tools-toolbar will get initially the toolButtonStyle as // all other toolbars, but we want to show only icons for the tools by default // (have to do this _after_ setAutoSaveSettings as that applies the default settings) if (d->configFirstTime) { d->toolToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); cfg.writeEntry(kpSettingFirstTime, d->configFirstTime = false); cfg.sync(); } +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tall done in " << totalTime.elapsed () << "msec"; +#endif } //--------------------------------------------------------------------- // private virtual [base KMainWindow] void kpMainWindow::readProperties (const KConfigGroup &configGroup) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::readProperties()"; +#endif // No document at all? if (!configGroup.hasKey (kpSessionSettingDocumentUrl)) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tno url - no document"; + #endif setDocument (nullptr); } // Have a document. else { const QUrl url = QUrl (configGroup.readEntry (kpSessionSettingDocumentUrl, QString ())); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\turl=" << url; + #endif const QSize notFromURLDocSize = configGroup.readEntry (kpSessionSettingNotFromUrlDocumentSize, QSize ()); // Is from URL? if (notFromURLDocSize.isEmpty ()) { // If this fails, the empty document that kpMainWindow::kpMainWindow() // created is left untouched. openInternal (url, defaultDocSize (), false/*show error message if url !exist*/); } // Not from URL? else { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << notFromURLDocSize; + #endif // Either we have an empty URL or we have a "kolourpaint doesnotexist.png" // URL. Regarding the latter case, if a file now actually exists at that // URL, we do open it - ignoring notFromURLDocSize - to avoid putting // the user in a situation where he might accidentally overwrite an // existing file. openInternal (url, notFromURLDocSize, true/*create an empty doc with the same url if url !exist*/); } } } //--------------------------------------------------------------------- // private virtual [base KMainWindow] // WARNING: KMainWindow API Doc says "No user interaction is allowed // in this function!" void kpMainWindow::saveProperties (KConfigGroup &configGroup) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::saveProperties()"; +#endif // No document at all? if (!d->document) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tno url - no document"; + #endif } // Have a document. else { // Save URL in all cases: // // a) d->document->isFromURL() // b) !d->document->isFromURL() [save size in this case] // i) No URL // ii) URL (from "kolourpaint doesnotexist.png") const QUrl url = d->document->url (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\turl=" << url; + #endif configGroup.writeEntry (kpSessionSettingDocumentUrl, url.url ()); // Not from URL e.g. "kolourpaint doesnotexist.png"? // // Note that "kolourpaint doesexist.png" is considered to be from // a URL even if it was deleted in the background (hence the // "false" arg to isFromURL()). This is because the user expects // it to be from a URL, so when we session restore, we pop up a // "cannot find file" dialog, instead of silently creating a new, // blank document. if (!d->document->isFromURL (false/*don't bother checking exists*/)) { // If we don't have a URL either: // // a) it was not modified - so we can use either width() or // constructorWidth() (they'll be equal). // b) the changes were discarded so we use the initial width, // constructorWidth(). // // Similarly for height() and constructorHeight(). const QSize docSize (d->document->constructorWidth (), d->document->constructorHeight ()); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << docSize; + #endif configGroup.writeEntry (kpSessionSettingNotFromUrlDocumentSize, docSize); } } } //--------------------------------------------------------------------- kpMainWindow::~kpMainWindow () { d->isFullyConstructed = false; // Get the kpTool to finish up. This makes sure that the kpTool destructor // will not need to access any other class (that might be deleted before // the destructor is called by the QObject child-deletion mechanism). if (tool ()) { tool ()->endInternal (); } // Delete document & views. // Note: This will disconnects signals from the current kpTool, so kpTool // must not be destructed yet. setDocument (nullptr); delete d->commandHistory; d->commandHistory = nullptr; delete d->scrollView; d->scrollView = nullptr; delete d; d = nullptr; } //--------------------------------------------------------------------- // public kpDocument *kpMainWindow::document () const { return d->document; } //--------------------------------------------------------------------- // public kpDocumentEnvironment *kpMainWindow::documentEnvironment () { if (!d->documentEnvironment) { d->documentEnvironment = new kpDocumentEnvironment (this); } return d->documentEnvironment; } //--------------------------------------------------------------------- // public kpViewManager *kpMainWindow::viewManager () const { return d->viewManager; } //--------------------------------------------------------------------- // public kpColorToolBar *kpMainWindow::colorToolBar () const { return d->colorToolBar; } //--------------------------------------------------------------------- // public kpColorCells *kpMainWindow::colorCells () const { return d->colorToolBar ? d->colorToolBar->colorCells () : nullptr; } //--------------------------------------------------------------------- // public kpToolToolBar *kpMainWindow::toolToolBar () const { return d->toolToolBar; } //--------------------------------------------------------------------- // public kpCommandHistory *kpMainWindow::commandHistory () const { return d->commandHistory; } //--------------------------------------------------------------------- kpCommandEnvironment *kpMainWindow::commandEnvironment () { if (!d->commandEnvironment) { d->commandEnvironment = new kpCommandEnvironment (this); } return d->commandEnvironment; } //--------------------------------------------------------------------- // private void kpMainWindow::setupActions () { setupFileMenuActions (); setupEditMenuActions (); setupViewMenuActions (); setupImageMenuActions (); setupColorsMenuActions (); setupSettingsMenuActions (); setupTextToolBarActions (); setupToolActions (); } //--------------------------------------------------------------------- // private void kpMainWindow::enableDocumentActions (bool enable) { enableFileMenuDocumentActions (enable); enableEditMenuDocumentActions (enable); enableViewMenuDocumentActions (enable); enableImageMenuDocumentActions (enable); enableColorsMenuDocumentActions (enable); enableSettingsMenuDocumentActions (enable); } //--------------------------------------------------------------------- // private void kpMainWindow::setDocument (kpDocument *newDoc) { //qCDebug(kpLogMainWindow) << newDoc; // is it a close operation? if (!newDoc) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdisabling actions"; + #endif // sync with the bit marked "sync" below // TODO: Never disable the Color Box because the user should be // able to manipulate the colors, even without a currently // open document. // // We just have to make sure that signals from the Color // Box aren't fired and received unexpectedly when there's // no document. Q_ASSERT (d->colorToolBar); d->colorToolBar->setEnabled (false); enableTextToolBarActions (false); } // Always disable the tools. // If we decide to open a new document/mainView we want // kpTool::begin() to be called again e.g. in case it sets the cursor. // kpViewManager won't do this because we nuke it to avoid stale state. enableToolsDocumentActions (false); if (!newDoc) { enableDocumentActions (false); } delete d->mainView; d->mainView = nullptr; slotDestroyThumbnail (); // viewManager will die and so will the selection d->actionCopy->setEnabled (false); d->actionCut->setEnabled (false); d->actionDelete->setEnabled (false); d->actionDeselect->setEnabled (false); d->actionCopyToFile->setEnabled (false); delete d->viewManager; d->viewManager = nullptr; +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdestroying document"; qCDebug(kpLogMainWindow) << "\t\td->document=" << d->document; +#endif // destroy current document delete d->document; d->document = newDoc; if (!d->lastCopyToURL.isEmpty ()) { // remove file name from path QString path = d->lastCopyToURL.path (); path = path.left (path.lastIndexOf (QLatin1Char ('/')) + 1); d->lastCopyToURL.setPath (path); } d->copyToFirstTime = true; if (!d->lastExportURL.isEmpty ()) { QString path = d->lastExportURL.path (); path = path.left (path.lastIndexOf (QLatin1Char ('/')) + 1); d->lastExportURL.setPath (path); } d->exportFirstTime = true; // not a close operation? if (d->document) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\treparenting doc that may have been created into a" << " different mainWindiow"; + #endif d->document->setEnviron (documentEnvironment ()); d->viewManager = new kpViewManager (this); d->mainView = new kpZoomedView (d->document, d->toolToolBar, d->viewManager, nullptr/*buddyView*/, d->scrollView, d->scrollView->viewport ()); d->mainView->setObjectName ( QLatin1String("mainView" )); d->viewManager->registerView (d->mainView); d->scrollView->setView (d->mainView); d->mainView->show (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\thooking up document signals"; + #endif // Copy/Cut/Deselect/Delete connect (d->document, &kpDocument::selectionEnabled, d->actionCut, &QAction::setEnabled); connect (d->document, &kpDocument::selectionEnabled, d->actionCopy, &QAction::setEnabled); connect (d->document, &kpDocument::selectionEnabled, d->actionDelete, &QAction::setEnabled); connect (d->document, &kpDocument::selectionEnabled, d->actionDeselect, &QAction::setEnabled); connect (d->document, &kpDocument::selectionEnabled, d->actionCopyToFile, &QAction::setEnabled); // this code won't actually enable any actions at this stage // (fresh document) but better safe than sorry d->actionCopy->setEnabled (d->document->selection ()); d->actionCut->setEnabled (d->document->selection ()); d->actionDeselect->setEnabled (d->document->selection ()); d->actionDelete->setEnabled (d->document->selection ()); d->actionCopyToFile->setEnabled (d->document->selection ()); connect (d->document, &kpDocument::selectionEnabled, this, &kpMainWindow::slotImageMenuUpdateDueToSelection); connect (d->document, &kpDocument::selectionIsTextChanged, this, &kpMainWindow::slotImageMenuUpdateDueToSelection); // Status bar connect (d->document, &kpDocument::documentOpened, this, &kpMainWindow::recalculateStatusBar); connect (d->document, SIGNAL (sizeChanged(QSize)), this, SLOT (setStatusBarDocSize(QSize))); // Caption (url, modified) connect (d->document, &kpDocument::documentModified, this, &kpMainWindow::slotUpdateCaption); connect (d->document, &kpDocument::documentOpened, this, &kpMainWindow::slotUpdateCaption); connect (d->document, &kpDocument::documentSaved, this, &kpMainWindow::slotUpdateCaption); // File/Reload action only available with non-empty URL connect (d->document, &kpDocument::documentSaved, this, &kpMainWindow::slotEnableReload); connect (d->document, &kpDocument::documentSaved, this, &kpMainWindow::slotEnableSettingsShowPath); // Command history Q_ASSERT (d->commandHistory); connect (d->commandHistory, &kpCommandHistory::documentRestored, this, &kpMainWindow::slotDocumentRestored); // caption "!modified" connect (d->document, &kpDocument::documentSaved, d->commandHistory, &kpCommandHistory::documentSaved); // Sync document -> views connect (d->document, &kpDocument::contentsChanged, d->viewManager, &kpViewManager::updateViews); connect (d->document, static_cast(&kpDocument::sizeChanged), d->viewManager, &kpViewManager::adjustViewsToEnvironment); connect (d->document, static_cast(&kpDocument::sizeChanged), d->viewManager, &kpViewManager::adjustViewsToEnvironment); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tenabling actions"; + #endif // sync with the bit marked "sync" above Q_ASSERT (d->colorToolBar); d->colorToolBar->setEnabled (true); // Hide the text toolbar - it will be shown by kpToolText::begin() enableTextToolBarActions (false); enableToolsDocumentActions (true); enableDocumentActions (true); } +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tupdating mainWindow elements"; +#endif slotImageMenuUpdateDueToSelection (); recalculateStatusBar (); slotUpdateCaption (); // Untitled to start with slotEnableReload (); slotEnableSettingsShowPath (); if (d->commandHistory) { d->commandHistory->clear (); } +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdocument and views ready to go!"; +#endif } //--------------------------------------------------------------------- // private virtual [base QWidget] void kpMainWindow::dragEnterEvent (QDragEnterEvent *e) { // It's faster to test for QMimeData::hasText() first due to the // lazy evaluation of the '||' operator. e->setAccepted (e->mimeData ()->hasText () || e->mimeData ()->hasUrls () || kpSelectionDrag::canDecode (e->mimeData ())); } //--------------------------------------------------------------------- // private virtual [base QWidget] void kpMainWindow::dropEvent (QDropEvent *e) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::dropEvent" << e->pos (); +#endif QList urls; kpAbstractImageSelection *sel = kpSelectionDrag::decode (e->mimeData ()); if (sel) { // TODO: How do you actually drop a selection or ordinary images on // the clipboard)? Will this code path _ever_ execute? sel->setTransparency (imageSelectionTransparency ()); // TODO: drop at point like with QTextDrag below? paste (*sel); delete sel; } else if (!(urls = e->mimeData ()->urls ()).isEmpty ()) { // LOTODO: kpSetOverrideCursorSaver cursorSaver (Qt::waitCursor); // // However, you would need to prefix all possible error/warning // dialogs that might be called, with Qt::arrowCursor e.g. in // kpDocument and probably a lot more places. for (const auto &u : urls) open (u); } else if (e->mimeData ()->hasText ()) { const QString text = e->mimeData ()->text (); QPoint selTopLeft = KP_INVALID_POINT; const QPoint globalPos = QWidget::mapToGlobal (e->pos ()); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tpos toGlobal=" << globalPos; + #endif kpView *view = nullptr; if (d->viewManager) { view = d->viewManager->viewUnderCursor (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tviewUnderCursor=" << view; + #endif if (!view) { // HACK: see kpViewManager::setViewUnderCursor() to see why // it's not reliable + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tattempting to discover view"; if (d->mainView && d->scrollView) { qCDebug(kpLogMainWindow) << "\t\t\tmainView->globalRect=" << kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ()) << " scrollView->globalRect=" << kpWidgetMapper::toGlobal (d->scrollView, QRect (0, 0, d->scrollView->viewport()->width (), d->scrollView->viewport()->height ())); } + #endif if (d->thumbnailView && kpWidgetMapper::toGlobal (d->thumbnailView, d->thumbnailView->rect ()) .contains (globalPos)) { // TODO: Code will never get executed. // Thumbnail doesn't accept drops. view = d->thumbnailView; } else if (d->mainView && kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ()) .contains (globalPos) && d->scrollView && kpWidgetMapper::toGlobal (d->scrollView, QRect (0, 0, d->scrollView->viewport()->width (), d->scrollView->viewport()->height ())) .contains (globalPos)) { view = d->mainView; } } } if (view) { const QPoint viewPos = view->mapFromGlobal (globalPos); const QPoint docPoint = view->transformViewToDoc (viewPos); // viewUnderCursor() is hacky and can return a view when we aren't // over one thanks to drags. if (d->document && d->document->rect ().contains (docPoint)) { selTopLeft = docPoint; // TODO: In terms of doc pixels, would be inconsistent behaviour // based on zoomLevel of view. // selTopLeft -= QPoint (-view->selectionResizeHandleAtomicSize (), // -view->selectionResizeHandleAtomicSize ()); } } pasteText (text, true/*force new text selection*/, selTopLeft); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotScrollViewAfterScroll () { // OPT: Why can't this be moved into kpViewScrollableContainer::slotDragScroll(), // grouping all drag-scroll-related repaints, which would potentially avoid // double repainting? if (tool ()) { tool ()->somethingBelowTheCursorChanged (); } } //--------------------------------------------------------------------- // private virtual [base QWidget] void kpMainWindow::moveEvent (QMoveEvent * /*e*/) { if (d->thumbnail) { // Disabled because it lags too far behind the mainWindow // d->thumbnail->move (d->thumbnail->pos () + (e->pos () - e->oldPos ())); notifyThumbnailGeometryChanged (); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotUpdateCaption () { if (d->document) { setCaption (d->configShowPath ? d->document->prettyUrl () : d->document->prettyFilename (), d->document->isModified ()); } else { setCaption (QString(), false); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotDocumentRestored () { if (d->document) { d->document->setModified (false); } slotUpdateCaption (); } //--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Colors.cpp b/mainWindow/kpMainWindow_Colors.cpp index fa6b7b7e..56be6016 100644 --- a/mainWindow/kpMainWindow_Colors.cpp +++ b/mainWindow/kpMainWindow_Colors.cpp @@ -1,482 +1,494 @@ /* Copyright (c) 2003-2007 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 "kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "widgets/kpColorCells.h" #include "lgpl/generic/kpColorCollection.h" #include "lgpl/generic/kpUrlFormatter.h" #include "widgets/toolbars/kpColorToolBar.h" #include #include #include #include #include "kpLogCategories.h" #include #include //--------------------------------------------------------------------- static QStringList KDEColorCollectionNames () { return kpColorCollection::installedCollections (); } //--------------------------------------------------------------------- // private void kpMainWindow::setupColorsMenuActions () { KActionCollection *ac = actionCollection (); d->actionColorsDefault = ac->addAction ("colors_default"); d->actionColorsDefault->setText (i18n ("Use KolourPaint Defaults")); connect (d->actionColorsDefault, &QAction::triggered, this, &kpMainWindow::slotColorsDefault); d->actionColorsKDE = ac->add ("colors_kde"); d->actionColorsKDE->setText (i18nc ("@item:inmenu colors", "Use KDE's")); // TODO: Will this slot be called spuriously if there are no colors // installed? connect (d->actionColorsKDE, static_cast(&KSelectAction::triggered), this, &kpMainWindow::slotColorsKDE); for (const auto &colName : ::KDEColorCollectionNames ()) { d->actionColorsKDE->addAction (colName); } d->actionColorsOpen = ac->addAction ("colors_open"); d->actionColorsOpen->setText (i18nc ("@item:inmenu colors", "&Open...")); connect (d->actionColorsOpen, &QAction::triggered, this, &kpMainWindow::slotColorsOpen); d->actionColorsReload = ac->addAction ("colors_reload"); d->actionColorsReload->setText (i18nc ("@item:inmenu colors", "Reloa&d")); connect (d->actionColorsReload, &QAction::triggered, this, &kpMainWindow::slotColorsReload); d->actionColorsSave = ac->addAction ("colors_save"); d->actionColorsSave->setText (i18nc ("@item:inmenu colors", "&Save")); connect (d->actionColorsSave, &QAction::triggered, this, &kpMainWindow::slotColorsSave); d->actionColorsSaveAs = ac->addAction ("colors_save_as"); d->actionColorsSaveAs->setText (i18nc ("@item:inmenu colors", "Save &As...")); connect (d->actionColorsSaveAs, &QAction::triggered, this, &kpMainWindow::slotColorsSaveAs); d->actionColorsAppendRow = ac->addAction ("colors_append_row"); d->actionColorsAppendRow->setText (i18nc ("@item:inmenu colors", "Add Row")); connect (d->actionColorsAppendRow, &QAction::triggered, this, &kpMainWindow::slotColorsAppendRow); d->actionColorsDeleteRow = ac->addAction ("colors_delete_row"); d->actionColorsDeleteRow->setText (i18nc ("@item:inmenu colors", "Delete Last Row")); connect (d->actionColorsDeleteRow, &QAction::triggered, this, &kpMainWindow::slotColorsDeleteRow); enableColorsMenuDocumentActions (false); } //--------------------------------------------------------------------- // private void kpMainWindow::createColorBox () { d->colorToolBar = new kpColorToolBar (i18n ("Color Box"), this); // (needed for QMainWindow::saveState()) d->colorToolBar->setObjectName ( QLatin1String("Color Box" )); connect (colorCells (), &kpColorCells::rowCountChanged, this, &kpMainWindow::slotUpdateColorsDeleteRowActionEnabled); } //--------------------------------------------------------------------- // private void kpMainWindow::enableColorsMenuDocumentActions (bool enable) { d->actionColorsDefault->setEnabled (enable); d->actionColorsKDE->setEnabled (enable); d->actionColorsOpen->setEnabled (enable); d->actionColorsReload->setEnabled (enable); d->actionColorsSave->setEnabled (enable); d->actionColorsSaveAs->setEnabled (enable); d->actionColorsAppendRow->setEnabled (enable); d->colorMenuDocumentActionsEnabled = enable; slotUpdateColorsDeleteRowActionEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotUpdateColorsDeleteRowActionEnabled () { // Currently, this is always enabled since kpColorCells guarantees that // there will be at least one row of cells (which might all be of the // invalid color). // // But this method is left here for future extensibility. d->actionColorsDeleteRow->setEnabled ( d->colorMenuDocumentActionsEnabled && (colorCells ()->rowCount () > 0)); } //--------------------------------------------------------------------- // Used in 2 situations: // // 1. User opens a color without using the "Use KDE's" submenu. // 2. User attempts to open a color using the "Use KDE's" submenu but the // opening fails. // // TODO: Maybe we could put the 3 actions (for different ways of opening // colors) in an exclusive group -- this might elminate the need for // this hack. // // private void kpMainWindow::deselectActionColorsKDE () { d->actionColorsKDE->setCurrentItem (-1); } //--------------------------------------------------------------------- // private bool kpMainWindow::queryCloseColors () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::queryCloseColors() colorCells.modified=" << colorCells ()->isModified (); +#endif toolEndShape (); if (!colorCells ()->isModified ()) { return true; // ok to close } int result = KMessageBox::Cancel; if (!colorCells ()->url ().isEmpty ()) { result = KMessageBox::warningYesNoCancel (this, i18n ("The color palette \"%1\" has been modified.\n" "Do you want to save it?", kpUrlFormatter::PrettyFilename (colorCells ()->url ())), QString ()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); } else { const QString name = colorCells ()->colorCollection ()->name (); if (!name.isEmpty ()) { result = KMessageBox::warningYesNoCancel (this, i18n ("The KDE color palette \"%1\" has been modified.\n" "Do you want to save it to a file?", name), QString ()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); } else { result = KMessageBox::warningYesNoCancel (this, i18n ("The default color palette has been modified.\n" "Do you want to save it to a file?"), QString ()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); } } switch (result) { case KMessageBox::Yes: return slotColorsSave (); // close only if save succeeds case KMessageBox::No: return true; // close without saving default: return false; // don't close current doc } } //--------------------------------------------------------------------- // private void kpMainWindow::openDefaultColors () { colorCells ()->setColorCollection ( kpColorCells::DefaultColorCollection ()); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsDefault () { // Call just in case. toolEndShape (); if (!queryCloseColors ()) { return; } openDefaultColors (); deselectActionColorsKDE (); } //--------------------------------------------------------------------- // private bool kpMainWindow::openKDEColors (const QString &name) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::openKDEColors(" << name << ")"; +#endif kpColorCollection colorCol; if (colorCol.openKDE (name, this)) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "opened"; + #endif colorCells ()->setColorCollection (colorCol); return true; } - - qCDebug(kpLogMainWindow) << "failed to open"; - return false; + else + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "failed to open"; + #endif + return false; + } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsKDE () { // Call in case an error dialog appears. toolEndShape (); const int curItem = d->actionColorsKDE->currentItem (); if (!queryCloseColors ()) { deselectActionColorsKDE (); return; } // queryCloseColors() calls slotColorSave(), which can call // slotColorSaveAs(), which can call deselectActionColorsKDE(). d->actionColorsKDE->setCurrentItem (curItem); const QStringList colNames = ::KDEColorCollectionNames (); const int selected = d->actionColorsKDE->currentItem (); Q_ASSERT (selected >= 0 && selected < colNames.size ()); if (!openKDEColors (colNames [selected])) { deselectActionColorsKDE (); } } //--------------------------------------------------------------------- // private bool kpMainWindow::openColors (const QUrl &url) { return colorCells ()->openColorCollection (url); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsOpen () { // Call due to dialog. toolEndShape (); QFileDialog fd(this); fd.setDirectoryUrl(colorCells ()->url()); fd.setWindowTitle(i18nc ("@title:window", "Open Color Palette")); if (fd.exec ()) { if (!queryCloseColors ()) { return; } QList selected = fd.selectedUrls(); if ( selected.count() && openColors(selected[0]) ) { deselectActionColorsKDE(); } } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsReload () { toolEndShape (); if (colorCells ()->isModified ()) { int result = KMessageBox::Cancel; if (!colorCells ()->url ().isEmpty ()) { result = KMessageBox::warningContinueCancel (this, i18n ("The color palette \"%1\" has been modified.\n" "Reloading will lose all changes since you last saved it.\n" "Are you sure?", kpUrlFormatter::PrettyFilename (colorCells ()->url ())), QString ()/*caption*/, KGuiItem(i18n ("&Reload"))); } else { const QString name = colorCells ()->colorCollection ()->name (); if (!name.isEmpty ()) { result = KMessageBox::warningContinueCancel (this, i18n ("The KDE color palette \"%1\" has been modified.\n" "Reloading will lose all changes.\n" "Are you sure?", colorCells ()->colorCollection ()->name ()), QString ()/*caption*/, KGuiItem (i18n ("&Reload"))); } else { result = KMessageBox::warningContinueCancel (this, i18n ("The default color palette has been modified.\n" "Reloading will lose all changes.\n" "Are you sure?"), QString ()/*caption*/, KGuiItem (i18n ("&Reload"))); } } + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "result=" << result << "vs KMessageBox::Continue" << KMessageBox::Continue; + #endif if (result != KMessageBox::Continue) { return; } } if (!colorCells ()->url ().isEmpty ()) { openColors (colorCells ()->url ()); } else { const QString name = colorCells ()->colorCollection ()->name (); if (!name.isEmpty ()) { openKDEColors (name); } else { openDefaultColors (); } } } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotColorsSave () { // Call due to dialog. toolEndShape (); if (colorCells ()->url ().isEmpty ()) { return slotColorsSaveAs (); } return colorCells ()->saveColorCollection (); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotColorsSaveAs () { // Call due to dialog. toolEndShape (); QFileDialog fd(this); fd.setDirectoryUrl(colorCells ()->url()); fd.setWindowTitle(i18n("Save Color Palette As")); fd.setAcceptMode(QFileDialog::AcceptSave); if (fd.exec ()) { QList selected = fd.selectedUrls(); if ( !selected.count() || !colorCells ()->saveColorCollectionAs(selected[0]) ) { return false; } // We're definitely using our own color collection now. deselectActionColorsKDE (); return true; } return false; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsAppendRow () { // Call just in case. toolEndShape (); kpColorCells *colorCells = d->colorToolBar->colorCells (); colorCells->appendRow (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsDeleteRow () { // Call just in case. toolEndShape (); kpColorCells *colorCells = d->colorToolBar->colorCells (); colorCells->deleteLastRow (); } diff --git a/mainWindow/kpMainWindow_Edit.cpp b/mainWindow/kpMainWindow_Edit.cpp index b5bf31a2..6bda1a25 100644 --- a/mainWindow/kpMainWindow_Edit.cpp +++ b/mainWindow/kpMainWindow_Edit.cpp @@ -1,888 +1,932 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller 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 "kpMainWindow.h" #include "kpMainWindowPrivate.h" #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include #include #include #include #include #include "layers/selections/image/kpAbstractImageSelection.h" #include "widgets/toolbars/kpColorToolBar.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "imagelib/kpDocumentMetaInfo.h" #include "document/kpDocumentSaveOptions.h" #include "layers/selections/image/kpImageSelectionTransparency.h" #include "commands/kpMacroCommand.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/selections/image/kpRectangularImageSelection.h" #include "layers/selections/kpSelectionDrag.h" #include "generic/kpSetOverrideCursorSaver.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "commands/tools/selection/text/kpToolTextGiveContentCommand.h" #include "commands/tools/selection/kpToolSelectionCreateCommand.h" #include "commands/tools/selection/kpToolSelectionDestroyCommand.h" #include "commands/tools/selection/text/kpToolTextEnterCommand.h" #include "commands/tools/selection/text/kpToolTextInsertCommand.h" #include "imagelib/transforms/kpTransformCrop.h" #include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include "views/kpZoomedView.h" //--------------------------------------------------------------------- // private void kpMainWindow::setupEditMenuActions () { KActionCollection *ac = actionCollection (); // Undo/Redo // CONFIG: Need GUI for config history size. d->commandHistory = new kpCommandHistory (true/*read config*/, this); if (d->configFirstTime) { // (so that cfg-file-editing user can modify in the meantime) d->commandHistory->writeConfig (); } d->actionCut = KStandardAction::cut (this, SLOT (slotCut()), ac); d->actionCopy = KStandardAction::copy (this, SLOT (slotCopy()), ac); d->actionPaste = KStandardAction::paste (this, SLOT (slotPaste()), ac); d->actionPasteInNewWindow = ac->addAction ("edit_paste_in_new_window"); d->actionPasteInNewWindow->setText (i18n ("Paste in &New Window")); connect (d->actionPasteInNewWindow, &QAction::triggered, this, &kpMainWindow::slotPasteInNewWindow); ac->setDefaultShortcut (d->actionPasteInNewWindow, Qt::CTRL + Qt::SHIFT + Qt::Key_V); //d->actionDelete = KStandardAction::clear (this, SLOT (slotDelete()), ac); d->actionDelete = ac->addAction ("edit_clear"); d->actionDelete->setText (i18n ("&Delete Selection")); connect (d->actionDelete, &QAction::triggered, this, &kpMainWindow::slotDelete); d->actionSelectAll = KStandardAction::selectAll (this, SLOT (slotSelectAll()), ac); d->actionDeselect = KStandardAction::deselect (this, SLOT (slotDeselect()), ac); d->actionCopyToFile = ac->addAction ("edit_copy_to_file"); d->actionCopyToFile->setText (i18n ("C&opy to File...")); connect (d->actionCopyToFile, &QAction::triggered, this, &kpMainWindow::slotCopyToFile); d->actionPasteFromFile = ac->addAction ("edit_paste_from_file"); d->actionPasteFromFile->setText (i18n ("Paste &From File...")); connect (d->actionPasteFromFile, &QAction::triggered, this, &kpMainWindow::slotPasteFromFile); d->editMenuDocumentActionsEnabled = false; enableEditMenuDocumentActions (false); // Paste should always be enabled, as long as there is something to paste // (independent of whether we have a document or not) connect (QApplication::clipboard(), &QClipboard::dataChanged, this, &kpMainWindow::slotEnablePaste); slotEnablePaste (); } //--------------------------------------------------------------------- // private void kpMainWindow::enableEditMenuDocumentActions (bool enable) { // d->actionCut // d->actionCopy // d->actionPaste // d->actionPasteInNewWindow // d->actionDelete d->actionSelectAll->setEnabled (enable); // d->actionDeselect d->editMenuDocumentActionsEnabled = enable; // d->actionCopyToFile // Unlike d->actionPaste, we disable this if there is no document. // This is because "File / Open" would do the same thing, if there is // no document. d->actionPasteFromFile->setEnabled (enable); } //--------------------------------------------------------------------- // public QMenu *kpMainWindow::selectionToolRMBMenu () { return qobject_cast (guiFactory ()->container ("selectionToolRMBMenu", this)); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotCut () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::slotCut() CALLED"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); Q_ASSERT (d->document && d->document->selection ()); toolEndShape (); slotCopy (); slotDelete (); } //--------------------------------------------------------------------- static QMimeData *NewTextMimeData (const QString &text) { auto *md = new QMimeData (); md->setText (text); return md; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotCopy () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::slotCopy() CALLED"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); Q_ASSERT (d->document && d->document->selection ()); toolEndShape (); kpAbstractSelection *sel = d->document->selection ()->clone (); if (dynamic_cast (sel)) { auto *textSel = dynamic_cast (sel); if (!textSel->text ().isEmpty ()) { QApplication::clipboard ()->setMimeData ( ::NewTextMimeData (textSel->text ()), QClipboard::Clipboard); // SYNC: Normally, users highlight text and press CTRL+C. // Highlighting text copies it to the X11 "middle // mouse button" clipboard. CTRL+C copies it to the // separate, Windows-like "CTRL+V" clipboard. // // However, KolourPaint doesn't support highlighting. // So when they press CTRL+C to copy all text, simulate // the highlighting by copying the text to the "middle // mouse button" clipboard. We don't do this for images // as no one ever middle-mouse-pastes images. // // Note that we don't share the QMimeData pointer with // the above in case Qt doesn't expect it. // // Once we change KolourPaint to support highlighted text // and CTRL+C to copy only the highlighted text, delete // this code. QApplication::clipboard ()->setMimeData ( ::NewTextMimeData (textSel->text ()), QClipboard::Selection); } } else if (dynamic_cast (sel)) { auto *imageSel = dynamic_cast (sel); // Transparency doesn't get sent across the aether so nuke it now // so that transparency mask doesn't get needlessly recalculated // if we ever call sel.setBaseImage(). imageSel->setTransparency (kpImageSelectionTransparency ()); kpImage rawImage; if (imageSel->hasContent ()) { rawImage = imageSel->baseImage (); } else { rawImage = d->document->getSelectedBaseImage (); } imageSel->setBaseImage ( rawImage ); QApplication::clipboard ()->setMimeData ( new kpSelectionDrag (*imageSel), QClipboard::Clipboard); } else { Q_ASSERT (!"Unknown selection type"); } delete sel; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotEnablePaste () { const QMimeData *md = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // It's faster to test for QMimeData::hasText() first due to the // lazy evaluation of the '||' operator. const bool shouldEnable = md && (md->hasText() || kpSelectionDrag::canDecode(md)); d->actionPasteInNewWindow->setEnabled(shouldEnable); d->actionPaste->setEnabled(shouldEnable); } //--------------------------------------------------------------------- // private QRect kpMainWindow::calcUsefulPasteRect (int imageWidth, int imageHeight) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::calcUsefulPasteRect(" << imageWidth << "," << imageHeight << ")"; +#endif Q_ASSERT (d->document); // TODO: 1st choice is to paste sel near but not overlapping last deselect point if (d->mainView && d->scrollView) { const QPoint viewTopLeft (d->scrollView->horizontalScrollBar()->value (), d->scrollView->verticalScrollBar()->value ()); const QPoint docTopLeft = d->mainView->transformViewToDoc (viewTopLeft); if ((docTopLeft.x () + imageWidth <= d->document->width () && docTopLeft.y () + imageHeight <= d->document->height ()) || imageWidth <= docTopLeft.x () || imageHeight <= docTopLeft.y ()) { return {docTopLeft.x (), docTopLeft.y (), imageWidth, imageHeight}; } } return {0, 0, imageWidth, imageHeight}; } //--------------------------------------------------------------------- // private void kpMainWindow::paste(const kpAbstractSelection &sel, bool forceTopLeft) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::paste(forceTopLeft=" << forceTopLeft << ")"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); toolEndShape (); // // Make sure we've got a document (esp. with File/Close) // if (!d->document) { auto *newDoc = new kpDocument ( sel.width (), sel.height (), documentEnvironment ()); // will also create viewManager setDocument (newDoc); } // // Paste as new selection // const auto *imageSel = dynamic_cast (&sel); if (imageSel && imageSel->hasContent () && imageSel->transparency ().isTransparent ()) { d->colorToolBar->flashColorSimilarityToolBarItem (); } kpAbstractSelection *selInUsefulPos = sel.clone (); if (!forceTopLeft) { selInUsefulPos->moveTo (calcUsefulPasteRect (sel.width (), sel.height ()).topLeft ()); } // TODO: Should use kpCommandHistory::addCreateSelectionCommand(), // as well, to really support pasting selection borders. addDeselectFirstCommand (new kpToolSelectionCreateCommand ( dynamic_cast (selInUsefulPos) ? i18n ("Text: Create Box") : i18n ("Selection: Create"), *selInUsefulPos, commandEnvironment ())); delete selInUsefulPos; +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "sel.size=" << QSize (sel.width (), sel.height ()) << " document.size=" << QSize (d->document->width (), d->document->height ()); +#endif // 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 () > d->document->width () || sel.height () > d->document->height ()) { d->commandHistory->addCommand ( new kpTransformResizeScaleCommand ( false/*act on doc, not sel*/, qMax (sel.width (), d->document->width ()), qMax (sel.height (), d->document->height ()), kpTransformResizeScaleCommand::Resize, commandEnvironment ())); } } //--------------------------------------------------------------------- // public void kpMainWindow::pasteText (const QString &text, bool forceNewTextSelection, const QPoint &newTextSelectionTopLeft) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::pasteText(" << text << ",forceNewTextSelection=" << forceNewTextSelection << ",newTextSelectionTopLeft=" << newTextSelectionTopLeft << ")"; +#endif if ( text.isEmpty() ) { return; } kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); toolEndShape (); QStringList textLines = text.split('\n'); if (!forceNewTextSelection && d->document && d->document->textSelection () && d->commandHistory && d->viewManager) { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\treusing existing Text Selection"; + #endif d->viewManager->setQueueUpdates(); kpTextSelection *textSel = d->document->textSelection (); if (!textSel->hasContent ()) { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\t\tneeds content"; + #endif commandHistory ()->addCreateSelectionCommand ( new kpToolSelectionCreateCommand ( i18n ("Text: Create Box"), *textSel, commandEnvironment ()), false/*no exec*/); } kpMacroCommand *macroCmd = new kpMacroCommand (i18n ("Text: Paste"), commandEnvironment ()); // (yes, this is the same check as the previous "if") if (!textSel->hasContent ()) { kpCommand *giveContentCmd = new kpToolTextGiveContentCommand ( *textSel, QString ()/*uninteresting child of macro cmd*/, commandEnvironment ()); giveContentCmd->execute (); macroCmd->addCommand (giveContentCmd); } for (int i = 0; i < static_cast (textLines.size ()); i++) { if (i > 0) { macroCmd->addCommand ( new kpToolTextEnterCommand ( QString()/*uninteresting child of macroCmd*/, d->viewManager->textCursorRow (), d->viewManager->textCursorCol (), kpToolTextEnterCommand::AddEnterNow, commandEnvironment ())); } macroCmd->addCommand ( new kpToolTextInsertCommand ( QString()/*uninteresting child of macroCmd*/, d->viewManager->textCursorRow (), d->viewManager->textCursorCol (), textLines [i], commandEnvironment ())); } d->commandHistory->addCommand (macroCmd, false/*no exec*/); d->viewManager->restoreQueueUpdates(); } else { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\tcreating Text Selection"; + #endif 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 (QList ::const_iterator it = textLines.constBegin (); it != textLines.constEnd (); ++it) { const int w = fontMetrics.width (*it); if (w > width) { width = w; } } // limit the size to avoid memory overflow width = qMin(qMax(QApplication::desktop()->width(), d->document ? d->document->width() : 0), width); height = qMin(qMax(QApplication::desktop()->height(), d->document ? d->document->height() : 0), height); const int selWidth = qMax (kpTextSelection::MinimumWidthForTextStyle (ts), width + kpTextSelection::TextBorderSize () * 2); const int selHeight = qMax (kpTextSelection::MinimumHeightForTextStyle (ts), height + kpTextSelection::TextBorderSize () * 2); kpTextSelection newTextSel (QRect (0, 0, selWidth, selHeight), textLines, ts); if (newTextSelectionTopLeft != KP_INVALID_POINT) { newTextSel.moveTo (newTextSelectionTopLeft); paste (newTextSel, true/*force topLeft*/); } else { paste (newTextSel); } } } //--------------------------------------------------------------------- // public void kpMainWindow::pasteTextAt (const QString &text, const QPoint &point, bool allowNewTextSelectionPointShift) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::pasteTextAt(" << text << ",point=" << point << ",allowNewTextSelectionPointShift=" << allowNewTextSelectionPointShift << ")"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); toolEndShape (); if (d->document && d->document->textSelection () && d->document->textSelection ()->pointIsInTextArea (point)) { kpTextSelection *textSel = d->document->textSelection (); int row, col; if (textSel->hasContent ()) { row = textSel->closestTextRowForPoint (point); col = textSel->closestTextColForPoint (point); } else { row = col = 0; } d->viewManager->setTextCursorPosition (row, col); pasteText (text); } else { QPoint pointToUse = point; if (allowNewTextSelectionPointShift) { // TODO: In terms of doc pixels, would be inconsistent behaviour // based on zoomLevel of view. // pointToUse -= QPoint (-view->selectionResizeHandleAtomicSize (), // -view->selectionResizeHandleAtomicSize ()); } pasteText (text, true/*force new text selection*/, pointToUse); } } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotPaste() { kpSetOverrideCursorSaver cursorSaver(Qt::WaitCursor); toolEndShape(); const QMimeData *mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard); kpAbstractImageSelection *sel = kpSelectionDrag::decode(mimeData); if ( sel ) { sel->setTransparency(imageSelectionTransparency()); paste(*sel); delete sel; } else if ( mimeData->hasText() ) { pasteText(mimeData->text()); } else { kpSetOverrideCursorSaver cursorSaver(Qt::ArrowCursor); KMessageBox::sorry(this, i18n("KolourPaint cannot paste the contents of" " the clipboard as it has an unknown format."), i18n("Cannot Paste")); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotPasteInNewWindow () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::slotPasteInNewWindow() CALLED"; +#endif kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); toolEndShape (); // // Pasting must ensure that: // // Requirement 1. the document is the same size as the image to be pasted. // Requirement 2. transparent pixels in the image must remain as transparent. // auto *win = new kpMainWindow (nullptr/*no document*/); win->show (); // Make "Edit / Paste in New Window" always paste white pixels as white. // Don't let selection transparency get in the way and paste them as // transparent. kpImageSelectionTransparency transparency = win->imageSelectionTransparency (); if (transparency.isTransparent ()) { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\tchanging image selection transparency to opaque"; + #endif transparency.setOpaque (); // Since we are setting selection transparency programmatically // -- as opposed to in response to user input -- this will not // affect the selection transparency tool option widget's "last used" // config setting. win->setImageSelectionTransparency (transparency); } // (this handles Requirement 1. above) win->slotPaste (); // if slotPaste could not decode clipboard data, no document was created if ( win->document() ) { // (this handles Requirement 2. above; // slotDeselect() is not enough unless the document is filled with the // transparent color in advance) win->slotCrop(); } } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotDelete () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::slotDelete() CALLED"; +#endif if (!d->actionDelete->isEnabled ()) { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\taction not enabled - was probably called from kpTool::keyPressEvent()"; + #endif return; } Q_ASSERT (d->document && d->document->selection ()); toolEndShape (); addImageOrSelectionCommand (new kpToolSelectionDestroyCommand ( d->document->textSelection () ? i18n ("Text: Delete Box") : // not to be confused with i18n ("Text: Delete") i18n ("Selection: Delete"), false/*no push onto doc*/, commandEnvironment ())); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotSelectAll () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::slotSelectAll() CALLED"; +#endif Q_ASSERT (d->document); toolEndShape (); if (d->document->selection ()) { slotDeselect (); } // just the border - don't actually pull image from doc yet d->document->setSelection ( kpRectangularImageSelection (d->document->rect (), imageSelectionTransparency ())); if (tool ()) { tool ()->somethingBelowTheCursorChanged (); } } //--------------------------------------------------------------------- // private void kpMainWindow::addDeselectFirstCommand (kpCommand *cmd) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::addDeselectFirstCommand(" << cmd << ")"; +#endif kpAbstractSelection *sel = d->document->selection (); +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\tsel=" << sel; +#endif if (sel) { // if you just dragged out something with no action then // forget the drag if (!sel->hasContent ()) { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\tjust a fresh border - was nop - delete"; + #endif d->document->selectionDelete (); if (tool ()) { tool ()->somethingBelowTheCursorChanged (); } if (cmd) { d->commandHistory->addCommand (cmd); } } else { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\treal selection with image - push onto doc cmd"; + #endif kpCommand *deselectCommand = new kpToolSelectionDestroyCommand ( dynamic_cast (sel) ? i18n ("Text: Finish") : i18n ("Selection: Deselect"), true/*push onto document*/, commandEnvironment ()); if (cmd) { kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), commandEnvironment ()); macroCmd->addCommand (deselectCommand); macroCmd->addCommand (cmd); d->commandHistory->addCommand (macroCmd); } else { d->commandHistory->addCommand (deselectCommand); } } } else { if (cmd) { d->commandHistory->addCommand (cmd); } } } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotDeselect () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::slotDeselect() CALLED"; +#endif Q_ASSERT (d->document && d->document->selection ()); toolEndShape (); addDeselectFirstCommand (nullptr); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotCopyToFile () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotCopyToFile()"; +#endif toolEndShape (); if (!d->document->selection ()) { return; } kpImage imageToSave; if (d->document->imageSelection ()) { kpAbstractImageSelection *imageSel = d->document->imageSelection (); if (!imageSel->hasContent ()) { // Not a floating selection - user has just selected a region; // haven't pulled it off yet so probably don't expect and can't // visualize selection transparency so give opaque, not transparent // image. imageToSave = d->document->getSelectedBaseImage (); } else { imageToSave = imageSel->transparentImage (); } } else if (d->document->textSelection ()) { imageToSave = d->document->textSelection ()->approximateImage (); } else { Q_ASSERT (!"Unknown selection type"); } kpDocumentSaveOptions chosenSaveOptions; bool allowOverwritePrompt, allowLossyPrompt; QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Copy to File"), d->lastCopyToURL.url (), imageToSave, d->lastCopyToSaveOptions, kpDocumentMetaInfo (), kpSettingsGroupEditCopyTo, false/*allow remote files*/, &chosenSaveOptions, d->copyToFirstTime, &allowOverwritePrompt, &allowLossyPrompt); if (chosenURL.isEmpty ()) { return; } if (!kpDocument::savePixmapToFile (imageToSave, chosenURL, chosenSaveOptions, kpDocumentMetaInfo (), allowOverwritePrompt, allowLossyPrompt, this)) { return; } addRecentURL (chosenURL); d->lastCopyToURL = chosenURL; d->lastCopyToSaveOptions = chosenSaveOptions; d->copyToFirstTime = false; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotPasteFromFile () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotPasteFromFile()"; +#endif toolEndShape (); QList urls = askForOpenURLs(i18nc ("@title:window", "Paste From File"), false/*only 1 URL*/); if (urls.count () != 1) { return; } QUrl url = urls.first (); kpImage image = kpDocument::getPixmapFromFile (url, false/*show error message if doesn't exist*/, this); if (image.isNull ()) { return; } addRecentURL (url); paste (kpRectangularImageSelection ( QRect (0, 0, image.width (), image.height ()), image, imageSelectionTransparency ())); } //--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_File.cpp b/mainWindow/kpMainWindow_File.cpp index 6b617adb..f9179909 100644 --- a/mainWindow/kpMainWindow_File.cpp +++ b/mainWindow/kpMainWindow_File.cpp @@ -1,1443 +1,1524 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2007 John Layt Copyright (c) 2007,2011,2015 Martin Koller 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 "kpMainWindow.h" #include "kpMainWindowPrivate.h" #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 // kdelibs4support #include #include // kdelibs4support #include #include #include #include #include #include #include // kdelibs4support #include "kpLogCategories.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "commands/imagelib/kpDocumentMetaInfoCommand.h" #include "dialogs/imagelib/kpDocumentMetaInfoDialog.h" #include "widgets/kpDocumentSaveOptionsWidget.h" #include "pixmapfx/kpPixmapFX.h" #include "widgets/kpPrintDialogPage.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" #if HAVE_KSANE #include "../scan/sanedialog.h" #endif // HAVE_KSANE // private void kpMainWindow::setupFileMenuActions () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::setupFileMenuActions()"; +#endif KActionCollection *ac = actionCollection (); d->actionNew = KStandardAction::openNew (this, SLOT (slotNew()), ac); d->actionOpen = KStandardAction::open (this, SLOT (slotOpen()), ac); d->actionOpenRecent = KStandardAction::openRecent(this, &kpMainWindow::slotOpenRecent, ac); connect(d->actionOpenRecent, &KRecentFilesAction::recentListCleared, this, &kpMainWindow::slotRecentListCleared); d->actionOpenRecent->loadEntries (KSharedConfig::openConfig ()->group (kpSettingsGroupRecentFiles)); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items (); +#endif d->actionSave = KStandardAction::save (this, SLOT (slotSave()), ac); d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs()), ac); d->actionExport = ac->addAction("file_export"); d->actionExport->setText (i18n ("E&xport...")); d->actionExport->setIcon(KDE::icon("document-export")); connect (d->actionExport, &QAction::triggered, this, &kpMainWindow::slotExport); d->actionScan = ac->addAction("file_scan"); d->actionScan->setText(i18n ("Scan...")); d->actionScan->setIcon(SmallIcon("scanner")); #if HAVE_KSANE connect (d->actionScan, &QAction::triggered, this, &kpMainWindow::slotScan); #else d->actionScan->setEnabled(false); #endif // HAVE_KSANE d->actionScreenshot = ac->addAction("file_screenshot"); d->actionScreenshot->setText(i18n("Acquire Screenshot")); connect (d->actionScreenshot, &QAction::triggered, this, &kpMainWindow::slotScreenshot); d->actionProperties = ac->addAction ("file_properties"); d->actionProperties->setText (i18n ("Properties")); d->actionProperties->setIcon(KDE::icon("document-properties")); connect (d->actionProperties, &QAction::triggered, this, &kpMainWindow::slotProperties); //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert()), ac); d->actionReload = ac->addAction ("file_revert"); d->actionReload->setText (i18n ("Reloa&d")); d->actionReload->setIcon(KDE::icon("view-refresh")); connect (d->actionReload, &QAction::triggered, this, &kpMainWindow::slotReload); ac->setDefaultShortcuts (d->actionReload, KStandardShortcut::reload ()); slotEnableReload (); d->actionPrint = KStandardAction::print (this, SLOT (slotPrint()), ac); d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview()), ac); d->actionMail = KStandardAction::mail (this, SLOT (slotMail()), ac); d->actionClose = KStandardAction::close (this, SLOT (slotClose()), ac); d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit()), ac); d->scanDialog = nullptr; enableFileMenuDocumentActions (false); } //--------------------------------------------------------------------- // private void kpMainWindow::enableFileMenuDocumentActions (bool enable) { // d->actionNew // d->actionOpen // d->actionOpenRecent d->actionSave->setEnabled (enable); d->actionSaveAs->setEnabled (enable); d->actionExport->setEnabled (enable); // d->actionScan d->actionProperties->setEnabled (enable); // d->actionReload d->actionPrint->setEnabled (enable); d->actionPrintPreview->setEnabled (enable); d->actionMail->setEnabled (enable); d->actionClose->setEnabled (enable); // d->actionQuit->setEnabled (enable); } //--------------------------------------------------------------------- // private void kpMainWindow::addRecentURL (const QUrl &url_) { // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls // map. // // So afterwards, the URL ref, our method is given, points to an // element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)). // Accessing it would result in a crash. // // To avoid the crash, make a copy of it before calling // loadEntries() and use this copy, instead of the to-be-dangling // ref. const QUrl& url = url_; +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")"; +#endif if (url.isEmpty ()) return; KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path cfg->reparseConfiguration (); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items (); +#endif // HACK: Something might have changed interprocess. // If we could PROPAGATE: interprocess, then this wouldn't be required. d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items (); +#endif d->actionOpenRecent->addUrl (url); d->actionOpenRecent->saveEntries (cfg->group (kpSettingsGroupRecentFiles)); cfg->sync (); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tnew recent URLs=" << d->actionOpenRecent->items (); +#endif // TODO: PROPAGATE: interprocess // TODO: Is this loop safe since a KMainWindow later along in the list, // could be closed as the code in the body almost certainly re-enters // the event loop? Problem for KDE 3 as well, I think. for (auto *kmw : KMainWindow::memberList ()) { Q_ASSERT (dynamic_cast (kmw)); auto *mw = dynamic_cast (kmw); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tmw=" << mw; + #endif if (mw != this) { // WARNING: Do not use KRecentFilesAction::setItems() // - it does not work since only its superclass, // KSelectAction, implements setItems() and can't // update KRecentFilesAction's URL list. // Avoid URL memory leak in KRecentFilesAction::loadEntries(). mw->d->actionOpenRecent->clear (); mw->d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\t\tcheck recent URLs=" << mw->d->actionOpenRecent->items (); + #endif } } } //--------------------------------------------------------------------- // private slot // TODO: Disable action if // (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty()) // as it does nothing if this is true. void kpMainWindow::slotNew () { toolEndShape (); if (d->document && !d->configOpenImagesInSameWindow) { // A document -- empty or otherwise -- is open. // Force open a new window. In contrast, open() might not open // a new window in this case. auto *win = new kpMainWindow (); win->show (); } else { open (QUrl (), true/*create an empty doc*/); } } //--------------------------------------------------------------------- // private QSize kpMainWindow::defaultDocSize () const { // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path KSharedConfig::openConfig ()->reparseConfiguration (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ()); if (docSize.isEmpty ()) { docSize = QSize (400, 300); } else { // Don't get too big or you'll thrash (or even lock up) the computer // just by opening a window docSize = QSize (qMin (2048, docSize.width ()), qMin (2048, docSize.height ())); } return docSize; } //--------------------------------------------------------------------- // private void kpMainWindow::saveDefaultDocSize (const QSize &size) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tCONFIG: saving Last Doc Size = " << size; +#endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingLastDocSize, size); cfg.sync (); } //--------------------------------------------------------------------- // private bool kpMainWindow::shouldOpen () { if (d->configOpenImagesInSameWindow) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\topenImagesInSameWindow"; + #endif // (this brings up a dialog and might save the current doc) if (!queryCloseDocument ()) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tqueryCloseDocument() aborts open"; + #endif return false; } } return true; } //--------------------------------------------------------------------- // private void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc) { // Want new window? if (d->document && !d->document->isEmpty () && !d->configOpenImagesInSameWindow) { // Send doc to new window. auto *win = new kpMainWindow (doc); win->show (); } else { // (sets up views, doc signals) setDocument (doc); } } //--------------------------------------------------------------------- // private kpDocument *kpMainWindow::openInternal (const QUrl &url, const QSize &fallbackDocSize, bool newDocSameNameIfNotExist) { // If using OpenImagesInSameWindow mode, ask whether to close the // current document. if (!shouldOpen ()) return nullptr; // Create/open doc. auto *newDoc = new kpDocument (fallbackDocSize.width (), fallbackDocSize.height (), documentEnvironment ()); if (!newDoc->open (url, newDocSameNameIfNotExist)) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\topen failed"; + #endif delete newDoc; return nullptr; } +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\topen OK"; +#endif // Send document to current or new window. setDocumentChoosingWindow (newDoc); return newDoc; } //--------------------------------------------------------------------- // private bool kpMainWindow::open (const QUrl &url, bool newDocSameNameIfNotExist) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::open(" << url << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist << ")"; +#endif kpDocument *newDoc = openInternal (url, defaultDocSize (), newDocSameNameIfNotExist); if (newDoc) { if (newDoc->isFromURL (false/*don't bother checking exists*/)) addRecentURL (url); return true; } return false; } //--------------------------------------------------------------------- // private QList kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs) { QMimeDatabase db; QStringList filterList; QString filter; for (const auto &type : QImageReader::supportedMimeTypes()) { if ( !filter.isEmpty() ) { filter += QLatin1Char(' '); } QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type))); if ( mime.isValid() ) { QString glob = mime.globPatterns().join(QLatin1Char(' ')); filter += glob; // I want to show the mime comment AND the file glob pattern, // but to avoid that the "All Supported Files" entry shows ALL glob patterns, // I must add the pattern here a second time so that QFileDialog::HideNameFilterDetails // can hide the first pattern and I still see the second one filterList << mime.comment() + QString(" (%1)(%2)").arg(glob).arg(glob); } } filterList.prepend(i18n("All Supported Files (%1)", filter)); QFileDialog fd(this); fd.setNameFilters(filterList); fd.setOption(QFileDialog::HideNameFilterDetails); fd.setWindowTitle(caption); if ( allowMultipleURLs ) { fd.setFileMode(QFileDialog::ExistingFiles); } if ( fd.exec() ) { return fd.selectedUrls(); } return QList(); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotOpen () { toolEndShape (); const QList urls = askForOpenURLs(i18nc("@title:window", "Open Image")); for (const auto & url : urls) { open (url); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotOpenRecent (const QUrl &url) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotOpenRecent(" << url << ")"; qCDebug(kpLogMainWindow) << "\titems=" << d->actionOpenRecent->items (); +#endif toolEndShape (); open (url); // If the open is successful, addRecentURL() would have bubbled up the // URL in the File / Open Recent action. As a side effect, the URL is // deselected. // // If the open fails, we should deselect the URL: // // 1. for consistency // // 2. because it has not been opened. // d->actionOpenRecent->setCurrentItem (-1); } //--------------------------------------------------------------------- void kpMainWindow::slotRecentListCleared() { d->actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group(kpSettingsGroupRecentFiles)); } //--------------------------------------------------------------------- #if HAVE_KSANE // private slot void kpMainWindow::slotScan () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog; +#endif toolEndShape (); if (!d->scanDialog) { // Create scan dialog d->scanDialog = new SaneDialog(this); // No scanning support (kdegraphics/libkscan) installed? if (!d->scanDialog) { KMessageBox::sorry (this, i18n("Failed to open scanning dialog."), i18nc("@title:window", "Scanning Failed")); return; } + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcreated scanDialog=" << d->scanDialog; + #endif connect (d->scanDialog, &SaneDialog::finalImage, this, &kpMainWindow::slotScanned); } // If using OpenImagesInSameWindow mode, ask whether to close the // current document. // // Do this after scan support is detected. Because if it's not, what // would be the point of closing the document? // // Ideally, we would do this after the user presses "Final Scan" in // the scan dialog and before the scan begins (if the user wants to // cancel the scan operation, it would be annoying to offer this choice // only after the slow scan is completed) but the KScanDialog API does // not allow this. So we settle for doing this before any // scan dialogs are shown. We don't do this between KScanDialog::setup() // and KScanDialog::exec() as it could be confusing alternating between // scanning and KolourPaint dialogs. if (!shouldOpen ()) { return; } +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcalling setup"; +#endif // Bring up dialog to select scan device. // If there is no scanner, we find that this does not bring up a dialog // but still returns true. if (d->scanDialog->setup ()) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tOK - showing dialog"; + #endif // Called only if scanner configured/available. // // In reality, this seems to be called even if you press "Cancel" in // the KScanDialog::setup() dialog! // // We use exec() to make sure it's modal. show() seems to work too // but better safe than sorry. d->scanDialog->exec (); } else { // Have never seen this code path execute even if "Cancel" is pressed. + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tFAIL"; + #endif } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotScanned (const QImage &image, int) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotScanned() image.rect=" << image.rect (); +#endif +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\thiding dialog"; +#endif // (KScanDialog does not close itself after a scan is made) // // Close the dialog, first thing: // // 1. This means that any dialogs we bring up won't be nested on top. // // 2. We don't want to return from this method but forget to close // the dialog. So do it before anything else. d->scanDialog->hide (); // (just in case there's some drawing between slotScan() exiting and // us being called) toolEndShape (); // TODO: Maybe this code should be moved into kpdocument.cpp - // since it resembles the responsibilities of kpDocument::open(). kpDocumentSaveOptions saveOptions; kpDocumentMetaInfo metaInfo; kpDocument::getDataFromImage(image, saveOptions, metaInfo); // Create document from image and meta info. auto *doc = new kpDocument (image.width (), image.height (), documentEnvironment ()); doc->setImage (image); doc->setSaveOptions (saveOptions); doc->setMetaInfo (metaInfo); // Send document to current or new window. setDocumentChoosingWindow (doc); } #endif // HAVE_KSANE //--------------------------------------------------------------------- void kpMainWindow::slotScreenshot() { toolEndShape(); auto *dialog = new QDialog(this); auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog); connect (buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect (buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject); auto *label = new QLabel(i18n("Snapshot Delay")); auto *seconds = new KPluralHandlingSpinBox; seconds->setRange(0, 99); seconds->setSuffix(ki18np(" second", " seconds")); seconds->setSpecialValueText(i18n("No delay")); auto *hideWindow = new QCheckBox(i18n("Hide Main Window")); hideWindow->setChecked(true); auto *vbox = new QVBoxLayout(dialog); vbox->addWidget(label); vbox->addWidget(seconds); vbox->addWidget(hideWindow); vbox->addWidget(buttons); if ( dialog->exec() == QDialog::Rejected ) { delete dialog; return; } if ( hideWindow->isChecked() ) { hide(); } // at least 1 seconds to make sure the window is hidden and the hide effect already stopped QTimer::singleShot((seconds->value() + 1) * 1000, this, SLOT(slotMakeScreenshot())); delete dialog; } //--------------------------------------------------------------------- void kpMainWindow::slotMakeScreenshot() { QCoreApplication::processEvents(); QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId()); auto *doc = new kpDocument(pixmap.width(), pixmap.height(), documentEnvironment()); doc->setImage(pixmap.toImage()); // Send document to current or new window. setDocumentChoosingWindow(doc); show(); // in case we hid the mainwindow, show it again } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotProperties () { toolEndShape (); kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this); if (dialog.exec () && !dialog.isNoOp ()) { commandHistory ()->addCommand ( new kpDocumentMetaInfoCommand ( i18n ("Document Properties"), dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/, commandEnvironment ())); } } //--------------------------------------------------------------------- // private slot bool kpMainWindow::save (bool localOnly) { if (d->document->url ().isEmpty () || !QImageWriter::supportedMimeTypes() .contains(d->document->saveOptions ()->mimeType().toLatin1()) || // SYNC: kpDocument::getPixmapFromFile() can't determine quality // from file so it has been set initially to an invalid value. (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () && d->document->saveOptions ()->qualityIsInvalid ()) || (localOnly && !d->document->url ().isLocalFile ())) { return saveAs (localOnly); } if (d->document->save (false/*no overwrite prompt*/, !d->document->savedAtLeastOnceBefore ()/*lossy prompt*/)) { addRecentURL (d->document->url ()); return true; } return false; } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotSave () { toolEndShape (); return save (); } //--------------------------------------------------------------------- // private QUrl kpMainWindow::askForSaveURL (const QString &caption, const QString &startURL, const kpImage &imageToBeSaved, const kpDocumentSaveOptions &startSaveOptions, const kpDocumentMetaInfo &docMetaInfo, const QString &forcedSaveOptionsGroup, bool localOnly, kpDocumentSaveOptions *chosenSaveOptions, bool isSavingForFirstTime, bool *allowOverwritePrompt, bool *allowLossyPrompt) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::askForURL() startURL=" << startURL; startSaveOptions.printDebug ("\tstartSaveOptions"); #endif bool reparsedConfiguration = false; // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path // so reparseConfiguration() must be called #define SETUP_READ_CFG() \ if (!reparsedConfiguration) \ { \ KSharedConfig::openConfig ()->reparseConfiguration (); \ reparsedConfiguration = true; \ } \ \ KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup); if (chosenSaveOptions) { *chosenSaveOptions = kpDocumentSaveOptions (); } if (allowOverwritePrompt) { *allowOverwritePrompt = true; // play it safe for now } if (allowLossyPrompt) { *allowLossyPrompt = true; // play it safe for now } kpDocumentSaveOptions fdSaveOptions = startSaveOptions; QStringList mimeTypes; for (const auto &type : QImageWriter::supportedMimeTypes()) { mimeTypes << QString::fromLatin1(type); } - +#if DEBUG_KP_MAIN_WINDOW + QStringList sortedMimeTypes = mimeTypes; + sortedMimeTypes.sort (); qCDebug(kpLogMainWindow) << "\tmimeTypes=" << mimeTypes - << "\tsortedMimeTypes="; - + << "\tsortedMimeTypes=" << sortedMimeTypes; +#endif if (mimeTypes.isEmpty ()) { qCCritical(kpLogMainWindow) << "No output mimetypes!"; return QUrl (); } #define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () && \ mimeTypes.contains (fdSaveOptions.mimeType ())) if (!MIME_TYPE_IS_VALID ()) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType () << " not valid, get default"; + #endif SETUP_READ_CFG (); fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg)); if (!MIME_TYPE_IS_VALID ()) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType () << " not valid, get hardcoded"; + #endif if (mimeTypes.contains ("image/png")) { fdSaveOptions.setMimeType ("image/png"); } else if (mimeTypes.contains ("image/bmp")) { fdSaveOptions.setMimeType ("image/bmp"); } else { fdSaveOptions.setMimeType (mimeTypes.first ()); } } } #undef MIME_TYPE_IS_VALID if (fdSaveOptions.colorDepthIsInvalid ()) { SETUP_READ_CFG (); fdSaveOptions.setColorDepth (kpDocumentSaveOptions::defaultColorDepth (cfg)); fdSaveOptions.setDither (kpDocumentSaveOptions::defaultDither (cfg)); } if (fdSaveOptions.qualityIsInvalid ()) { SETUP_READ_CFG (); fdSaveOptions.setQuality (kpDocumentSaveOptions::defaultQuality (cfg)); } +#if DEBUG_KP_MAIN_WINDOW fdSaveOptions.printDebug ("\tcorrected saveOptions passed to fileDialog"); +#endif auto *saveOptionsWidget = new kpDocumentSaveOptionsWidget (imageToBeSaved, fdSaveOptions, docMetaInfo, this); KFileDialog fd (QUrl (startURL), QString(), this, saveOptionsWidget); saveOptionsWidget->setVisualParent (&fd); fd.setWindowTitle (caption); fd.setOperationMode (KFileDialog::Saving); fd.setMimeFilter (mimeTypes, fdSaveOptions.mimeType ()); if (localOnly) { fd.setMode (KFile::File | KFile::LocalOnly); } connect (&fd, &KFileDialog::filterChanged, saveOptionsWidget, &kpDocumentSaveOptionsWidget::setMimeType); if ( fd.exec() == QDialog::Accepted ) { kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions (); + #if DEBUG_KP_MAIN_WINDOW newSaveOptions.printDebug ("\tnewSaveOptions"); + #endif KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup); // Save options user forced - probably want to use them in future kpDocumentSaveOptions::saveDefaultDifferences (cfg, fdSaveOptions, newSaveOptions); cfg.sync (); if (chosenSaveOptions) { *chosenSaveOptions = newSaveOptions; } bool shouldAllowOverwritePrompt = (fd.selectedUrl () != QUrl (startURL) || newSaveOptions.mimeType () != startSaveOptions.mimeType ()); if (allowOverwritePrompt) { *allowOverwritePrompt = shouldAllowOverwritePrompt; + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tallowOverwritePrompt=" << *allowOverwritePrompt; + #endif } if (allowLossyPrompt) { // SYNC: kpDocumentSaveOptions elements - everything except quality // (one quality setting is "just as lossy" as another so no // need to continually warn due to quality change) *allowLossyPrompt = (isSavingForFirstTime || shouldAllowOverwritePrompt || newSaveOptions.mimeType () != startSaveOptions.mimeType () || newSaveOptions.colorDepth () != startSaveOptions.colorDepth () || newSaveOptions.dither () != startSaveOptions.dither ()); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tallowLossyPrompt=" << *allowLossyPrompt; + #endif } + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tselectedUrl=" << fd.selectedUrl (); + #endif return fd.selectedUrl (); } return QUrl (); #undef SETUP_READ_CFG } //--------------------------------------------------------------------- // private slot bool kpMainWindow::saveAs (bool localOnly) { kpDocumentSaveOptions chosenSaveOptions; bool allowOverwritePrompt, allowLossyPrompt; QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Save Image As"), d->document->url ().url (), d->document->imageWithSelection (), *d->document->saveOptions (), *d->document->metaInfo (), kpSettingsGroupFileSaveAs, localOnly, &chosenSaveOptions, !d->document->savedAtLeastOnceBefore (), &allowOverwritePrompt, &allowLossyPrompt); if (chosenURL.isEmpty ()) { return false; } if (!d->document->saveAs (chosenURL, chosenSaveOptions, allowOverwritePrompt, allowLossyPrompt)) { return false; } addRecentURL (chosenURL); return true; } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotSaveAs () { toolEndShape (); return saveAs (); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotExport () { toolEndShape (); kpDocumentSaveOptions chosenSaveOptions; bool allowOverwritePrompt, allowLossyPrompt; QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Export"), d->lastExportURL.url (), d->document->imageWithSelection (), d->lastExportSaveOptions, *d->document->metaInfo (), kpSettingsGroupFileExport, false/*allow remote files*/, &chosenSaveOptions, d->exportFirstTime, &allowOverwritePrompt, &allowLossyPrompt); if (chosenURL.isEmpty ()) { return false; } if (!kpDocument::savePixmapToFile (d->document->imageWithSelection (), chosenURL, chosenSaveOptions, *d->document->metaInfo (), allowOverwritePrompt, allowLossyPrompt, this)) { return false; } addRecentURL (chosenURL); d->lastExportURL = chosenURL; d->lastExportSaveOptions = chosenSaveOptions; d->exportFirstTime = false; return true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotEnableReload () { d->actionReload->setEnabled (d->document); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotReload () { toolEndShape (); Q_ASSERT (d->document); QUrl oldURL = d->document->url (); if (d->document->isModified ()) { int result = KMessageBox::Cancel; if (d->document->isFromURL (false/*don't bother checking exists*/) && !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?", d->document->prettyFilename ()), QString()/*caption*/, KGuiItem(i18n ("&Reload"))); } else { result = KMessageBox::warningContinueCancel (this, i18n ("The document \"%1\" has been modified.\n" "Reloading will lose all changes.\n" "Are you sure?", d->document->prettyFilename ()), QString()/*caption*/, KGuiItem(i18n ("&Reload"))); } if (result != KMessageBox::Continue) { return false; } } kpDocument *doc = nullptr; // If it's _supposed to_ come from a URL or it exists if (d->document->isFromURL (false/*don't bother checking exists*/) || (!oldURL.isEmpty () && KIO::NetAccess::exists (oldURL, KIO::NetAccess::SourceSide/*open*/, this))) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() reloading from disk!"; + #endif doc = new kpDocument (1, 1, documentEnvironment ()); if (!doc->open (oldURL)) { delete doc; doc = nullptr; return false; } addRecentURL (oldURL); } else { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() create doc"; + #endif doc = new kpDocument (d->document->constructorWidth (), d->document->constructorHeight (), documentEnvironment ()); doc->setURL (oldURL, false/*not from URL*/); } setDocument (doc); return true; } // private void kpMainWindow::sendDocumentNameToPrinter (QPrinter *printer) { QUrl url = d->document->url (); if (!url.isEmpty ()) { int dot; QString fileName = url.fileName (); dot = fileName.lastIndexOf ('.'); // file.ext but not .hidden-file? if (dot > 0) { fileName.truncate (dot); } + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::sendDocumentNameToPrinter() fileName=" << fileName << " dir=" << url.path(); + #endif printer->setDocName (fileName); } } //-------------------------------------------------------------------------------- void kpMainWindow::sendPreviewToPrinter(QPrinter *printer) { sendImageToPrinter(printer, false); } //-------------------------------------------------------------------------------- // private void kpMainWindow::sendImageToPrinter (QPrinter *printer, bool showPrinterSetupDialog) { // Get image to be printed. kpImage image = d->document->imageWithSelection (); // Get image DPI. auto imageDotsPerMeterX = double (d->document->metaInfo ()->dotsPerMeterX ()); auto imageDotsPerMeterY = double (d->document->metaInfo ()->dotsPerMeterY ()); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::sendImageToPrinter() image:" << " width=" << image.width () << " height=" << image.height () << " dotsPerMeterX=" << imageDotsPerMeterX << " dotsPerMeterY=" << imageDotsPerMeterY; +#endif // Image DPI invalid (e.g. new image, could not read from file // or Qt3 doesn't implement DPI for JPEG)? if (imageDotsPerMeterX <= 0 || imageDotsPerMeterY <= 0) { // Even if just one DPI dimension is invalid, mutate both DPI // dimensions as we have no information about the intended // aspect ratio anyway (and other dimension likely to be invalid). // When rendering text onto a document, the fonts are rasterised // according to the screen's DPI. // TODO: I think we should use the image's DPI. Technically // possible? // // So no matter what computer you draw text on, you get // the same pixels. // // So we must print at the screen's DPI to get the right text size. // // Unfortunately, this means that moving to a different screen DPI // affects printing. If you edited the image at a different screen // DPI than when you print, you get incorrect results. Furthermore, // this is bogus if you don't have text in your image. Worse still, // what if you have multiple screens connected to the same computer // with different DPIs? // TODO: mysteriously, someone else is setting this to 96dpi always. QPixmap arbitraryScreenElement(1, 1); const QPaintDevice *screenDevice = &arbitraryScreenElement; const auto dpiX = screenDevice->logicalDpiX (); const auto dpiY = screenDevice->logicalDpiY (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY; + #endif imageDotsPerMeterX = dpiX * KP_INCHES_PER_METER; imageDotsPerMeterY = dpiY * KP_INCHES_PER_METER; } // Get page size (excluding margins). // Coordinate (0,0) is the X here: // mmmmm // mX m // m m m = margin // m m // mmmmm const auto printerWidthMM = printer->widthMM (); const auto printerHeightMM = printer->heightMM (); - qCDebug(kpLogMainWindow) << "\tprinter: widthMM=" << printerWidthMM - << " heightMM=" << printerHeightMM; - auto dpiX = imageDotsPerMeterX / KP_INCHES_PER_METER; auto dpiY = imageDotsPerMeterY / KP_INCHES_PER_METER; + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tprinter: widthMM=" << printerWidthMM + << " heightMM=" << printerHeightMM; + qCDebug(kpLogMainWindow) << "\timage: dpiX=" << dpiX << " dpiY=" << dpiY; -# +#endif // // If image doesn't fit on page at intended DPI, change the DPI. // const auto scaleDpiX = (image.width () / (printerWidthMM / KP_MILLIMETERS_PER_INCH)) / dpiX; const auto scaleDpiY = (image.height () / (printerHeightMM / KP_MILLIMETERS_PER_INCH)) / dpiY; const auto scaleDpi = qMax (scaleDpiX, scaleDpiY); + +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY << " --> scale at " << scaleDpi << " to fit?"; +#endif // Need to increase resolution to fit page? if (scaleDpi > 1.0) { dpiX *= scaleDpi; dpiY *= scaleDpi; + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\t\tto fit page, scaled to:" << " dpiX=" << dpiX << " dpiY=" << dpiY; + #endif } // Make sure DPIs are equal as that's all QPrinter::setResolution() // supports. We do this in such a way that we only ever stretch an // image, to avoid losing information. Don't antialias as the printer // will do that to translate our DPI to its physical resolution and // double-antialiasing looks bad. if (dpiX > dpiY) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdpiX > dpiY; stretching image height to equalise DPIs to dpiX=" << dpiX; + #endif kpPixmapFX::scale (&image, image.width (), qMax (1, qRound (image.height () * dpiX / dpiY)), false/*don't antialias*/); dpiY = dpiX; } else if (dpiY > dpiX) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdpiY > dpiX; stretching image width to equalise DPIs to dpiY=" << dpiY; + #endif kpPixmapFX::scale (&image, qMax (1, qRound (image.width () * dpiY / dpiX)), image.height (), false/*don't antialias*/); dpiX = dpiY; } Q_ASSERT (dpiX == dpiY); // QPrinter::setResolution() has to be called before QPrinter::setup(). printer->setResolution (qMax (1, qRound (dpiX))); sendDocumentNameToPrinter (printer); if (showPrinterSetupDialog) { auto *optionsPage = new kpPrintDialogPage (this); optionsPage->setPrintImageCenteredOnPage (d->configPrintImageCenteredOnPage); QPrintDialog *printDialog = KdePrint::createPrintDialog ( printer, QList () << optionsPage, this); printDialog->setWindowTitle (i18nc ("@title:window", "Print Image")); // Display dialog. const bool wantToPrint = printDialog->exec (); if (optionsPage->printImageCenteredOnPage () != d->configPrintImageCenteredOnPage) { // Save config option even if the dialog was cancelled. d->configPrintImageCenteredOnPage = optionsPage->printImageCenteredOnPage (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingPrintImageCenteredOnPage, d->configPrintImageCenteredOnPage); cfg.sync (); } delete printDialog; if (!wantToPrint) { return; } } // Send image to printer. QPainter painter; painter.begin(printer); double originX = 0, originY = 0; // Center image on page? if (d->configPrintImageCenteredOnPage) { originX = (printer->width() - image.width ()) / 2; originY = (printer->height() - image.height ()) / 2; } painter.drawImage(qRound(originX), qRound(originY), image); painter.end(); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotPrint () { toolEndShape (); QPrinter printer; sendImageToPrinter (&printer, true/*showPrinterSetupDialog*/); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotPrintPreview () { toolEndShape (); QPrintPreviewDialog printPreview(this); connect(&printPreview, &QPrintPreviewDialog::paintRequested, this, &kpMainWindow::sendPreviewToPrinter); printPreview.exec (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotMail () { toolEndShape (); if (d->document->url ().isEmpty ()/*no name*/ || !d->document->isFromURL () || d->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(), KStandardGuiItem::save (), KStandardGuiItem::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; } } KToolInvocation::invokeMailer ( QString()/*to*/, QString()/*cc*/, QString()/*bcc*/, d->document->prettyFilename()/*subject*/, QString()/*body*/, QString()/*messageFile*/, QStringList(d->document->url().url())/*attachments*/); } //--------------------------------------------------------------------- // private bool kpMainWindow::queryCloseDocument () { toolEndShape (); if (!d->document || !d->document->isModified ()) { return true; // ok to close current doc } int result = KMessageBox::warningYesNoCancel (this, i18n ("The document \"%1\" has been modified.\n" "Do you want to save it?", d->document->prettyFilename ()), QString()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); switch (result) { case KMessageBox::Yes: return slotSave (); // close only if save succeeds case KMessageBox::No: return true; // close without saving default: return false; // don't close current doc } } //--------------------------------------------------------------------- // private virtual [base KMainWindow] bool kpMainWindow::queryClose () { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::queryClose()"; #endif toolEndShape (); if (!queryCloseDocument ()) { return false; } if (!queryCloseColors ()) { return false; } return true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotClose () { toolEndShape (); if (!queryCloseDocument ()) { return; } setDocument (nullptr); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotQuit () { toolEndShape (); close (); // will call queryClose() } //--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Image.cpp b/mainWindow/kpMainWindow_Image.cpp index e5d43c78..c10c9c96 100644 --- a/mainWindow/kpMainWindow_Image.cpp +++ b/mainWindow/kpMainWindow_Image.cpp @@ -1,621 +1,625 @@ /* Copyright (c) 2003-2007 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "imagelib/kpColor.h" #include "kpDefs.h" #include "widgets/toolbars/kpColorToolBar.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "commands/imagelib/effects/kpEffectInvertCommand.h" #include "commands/imagelib/effects/kpEffectReduceColorsCommand.h" #include "dialogs/imagelib/effects/kpEffectsDialog.h" #include "commands/imagelib/effects/kpEffectClearCommand.h" #include "commands/imagelib/effects/kpEffectGrayscaleCommand.h" #include "commands/kpMacroCommand.h" #include "layers/selections/text/kpTextSelection.h" #include "commands/tools/selection/kpToolSelectionCreateCommand.h" #include "commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h" #include "commands/tools/selection/text/kpToolTextGiveContentCommand.h" #include "imagelib/transforms/kpTransformAutoCrop.h" #include "imagelib/transforms/kpTransformCrop.h" #include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" #include "commands/imagelib/transforms/kpTransformFlipCommand.h" #include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" #include "dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h" #include "commands/imagelib/transforms/kpTransformRotateCommand.h" #include "dialogs/imagelib/transforms/kpTransformRotateDialog.h" #include "commands/imagelib/transforms/kpTransformSkewCommand.h" #include "dialogs/imagelib/transforms/kpTransformSkewDialog.h" #include "views/manager/kpViewManager.h" #include "commands/imagelib/effects/kpEffectBlurSharpenCommand.h" #include "imagelib/effects/kpEffectBlurSharpen.h" #include "kpLogCategories.h" #include #include #include #include #include #include #include #include //--------------------------------------------------------------------- // private kpTransformDialogEnvironment *kpMainWindow::transformDialogEnvironment () { if (!d->transformDialogEnvironment) d->transformDialogEnvironment = new kpTransformDialogEnvironment (this); return d->transformDialogEnvironment; } //--------------------------------------------------------------------- // private bool kpMainWindow::isSelectionActive () const { return (d->document ? bool (d->document->selection ()) : false); } //--------------------------------------------------------------------- // private bool kpMainWindow::isTextSelection () const { return (d->document && d->document->textSelection ()); } //--------------------------------------------------------------------- // private QString kpMainWindow::autoCropText () const { return kpTransformAutoCropCommand::text(isSelectionActive(), kpTransformAutoCropCommand::ShowAccel); } //--------------------------------------------------------------------- // private void kpMainWindow::setupImageMenuActions () { KActionCollection *ac = actionCollection (); d->actionResizeScale = ac->addAction ("image_resize_scale"); d->actionResizeScale->setText (i18n ("R&esize / Scale...")); connect (d->actionResizeScale, &QAction::triggered, this, &kpMainWindow::slotResizeScale); ac->setDefaultShortcut (d->actionResizeScale, Qt::CTRL + Qt::Key_E); d->actionCrop = ac->addAction ("image_crop"); d->actionCrop->setText (i18n ("Se&t as Image (Crop)")); connect (d->actionCrop, &QAction::triggered, this, &kpMainWindow::slotCrop); ac->setDefaultShortcut (d->actionCrop, Qt::CTRL + Qt::Key_T); d->actionAutoCrop = ac->addAction ("image_auto_crop"); d->actionAutoCrop->setText (autoCropText ()); connect (d->actionAutoCrop, &QAction::triggered, this, &kpMainWindow::slotAutoCrop); ac->setDefaultShortcut (d->actionAutoCrop, Qt::CTRL + Qt::Key_U); d->actionFlip = ac->addAction ("image_flip"); d->actionFlip->setText (i18n ("&Flip (upside down)")); connect (d->actionFlip, &QAction::triggered, this, &kpMainWindow::slotFlip); ac->setDefaultShortcut (d->actionFlip, Qt::CTRL + Qt::Key_F); d->actionMirror = ac->addAction ("image_mirror"); d->actionMirror->setText (i18n ("Mirror (horizontally)")); connect (d->actionMirror, &QAction::triggered, this, &kpMainWindow::slotMirror); //ac->setDefaultShortcut (d->actionMirror, Qt::CTRL + Qt::Key_M); d->actionRotate = ac->addAction ("image_rotate"); d->actionRotate->setText (i18n ("&Rotate...")); d->actionRotate->setIcon(KDE::icon("transform-rotate")); connect (d->actionRotate, &QAction::triggered, this, &kpMainWindow::slotRotate); ac->setDefaultShortcut (d->actionRotate, Qt::CTRL + Qt::Key_R); d->actionRotateLeft = ac->addAction ("image_rotate_270deg"); d->actionRotateLeft->setText (i18n ("Rotate &Left")); d->actionRotateLeft->setIcon(KDE::icon("object-rotate-left")); connect (d->actionRotateLeft, &QAction::triggered, this, &kpMainWindow::slotRotate270); ac->setDefaultShortcut (d->actionRotateLeft, Qt::CTRL + Qt::SHIFT + Qt::Key_Left); d->actionRotateRight = ac->addAction ("image_rotate_90deg"); d->actionRotateRight->setText (i18n ("Rotate Righ&t")); d->actionRotateRight->setIcon(KDE::icon("object-rotate-right")); connect (d->actionRotateRight, &QAction::triggered, this, &kpMainWindow::slotRotate90); ac->setDefaultShortcut (d->actionRotateRight, Qt::CTRL + Qt::SHIFT + Qt::Key_Right); d->actionSkew = ac->addAction ("image_skew"); d->actionSkew->setText (i18n ("S&kew...")); connect (d->actionSkew, &QAction::triggered, this, &kpMainWindow::slotSkew); ac->setDefaultShortcut (d->actionSkew, Qt::CTRL + Qt::Key_K); d->actionConvertToBlackAndWhite = ac->addAction ("image_convert_to_black_and_white"); d->actionConvertToBlackAndWhite->setText (i18n ("Reduce to Mo&nochrome (Dithered)")); connect (d->actionConvertToBlackAndWhite, &QAction::triggered, this, &kpMainWindow::slotConvertToBlackAndWhite); d->actionConvertToGrayscale = ac->addAction ("image_convert_to_grayscale"); d->actionConvertToGrayscale->setText (i18n ("Reduce to &Grayscale")); connect (d->actionConvertToGrayscale, &QAction::triggered, this, &kpMainWindow::slotConvertToGrayscale); d->actionInvertColors = ac->addAction ("image_invert_colors"); d->actionInvertColors->setText (i18n ("&Invert Colors")); connect (d->actionInvertColors, &QAction::triggered, this, &kpMainWindow::slotInvertColors); ac->setDefaultShortcut (d->actionInvertColors, Qt::CTRL + Qt::Key_I); d->actionClear = ac->addAction ("image_clear"); d->actionClear->setText (i18n ("C&lear")); connect (d->actionClear, &QAction::triggered, this, &kpMainWindow::slotClear); ac->setDefaultShortcut (d->actionClear, Qt::CTRL + Qt::SHIFT + Qt::Key_N); d->actionBlur = ac->addAction("image_make_confidential"); d->actionBlur->setText(i18n("Make Confidential")); connect(d->actionBlur, &QAction::triggered, this, &kpMainWindow::slotMakeConfidential); d->actionMoreEffects = ac->addAction ("image_more_effects"); d->actionMoreEffects->setText (i18n ("&More Effects...")); connect (d->actionMoreEffects, &QAction::triggered, this, &kpMainWindow::slotMoreEffects); ac->setDefaultShortcut (d->actionMoreEffects, Qt::CTRL + Qt::Key_M); enableImageMenuDocumentActions (false); } //--------------------------------------------------------------------- // private void kpMainWindow::enableImageMenuDocumentActions (bool enable) { d->actionResizeScale->setEnabled (enable); d->actionCrop->setEnabled (enable); d->actionAutoCrop->setEnabled (enable); d->actionFlip->setEnabled (enable); d->actionMirror->setEnabled (enable); d->actionRotate->setEnabled (enable); d->actionRotateLeft->setEnabled (enable); d->actionRotateRight->setEnabled (enable); d->actionSkew->setEnabled (enable); d->actionConvertToBlackAndWhite->setEnabled (enable); d->actionConvertToGrayscale->setEnabled (enable); d->actionInvertColors->setEnabled (enable); d->actionClear->setEnabled (enable); d->actionBlur->setEnabled (enable); d->actionMoreEffects->setEnabled (enable); d->imageMenuDocumentActionsEnabled = enable; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotImageMenuUpdateDueToSelection () { // SYNC: kolourpaintui.rc const QString MenuBarItemTextImage = i18nc ( "Image/Selection Menu caption - make sure the translation has" " the same accel as the Select&ion translation", "&Image"); const QString MenuBarItemTextSelection = i18nc ( "Image/Selection Menu caption - make sure that translation has" " the same accel as the &Image translation", "Select&ion"); Q_ASSERT (menuBar ()); for (auto *action : menuBar ()->actions ()) { if (action->text () == MenuBarItemTextImage || action->text () == MenuBarItemTextSelection) { if (isSelectionActive ()) { action->setText (MenuBarItemTextSelection); } else { action->setText (MenuBarItemTextImage); } break; } } d->actionResizeScale->setEnabled (d->imageMenuDocumentActionsEnabled); d->actionCrop->setEnabled (d->imageMenuDocumentActionsEnabled && isSelectionActive ()); const bool enable = (d->imageMenuDocumentActionsEnabled && !isTextSelection ()); d->actionAutoCrop->setText (autoCropText ()); d->actionAutoCrop->setEnabled (enable); d->actionFlip->setEnabled (enable); d->actionMirror->setEnabled (enable); d->actionRotate->setEnabled (enable); d->actionRotateLeft->setEnabled (enable); d->actionRotateRight->setEnabled (enable); d->actionSkew->setEnabled (enable); d->actionConvertToBlackAndWhite->setEnabled (enable); d->actionConvertToGrayscale->setEnabled (enable); d->actionInvertColors->setEnabled (enable); d->actionClear->setEnabled (enable); d->actionBlur->setEnabled (enable); d->actionMoreEffects->setEnabled (enable); } //--------------------------------------------------------------------- // public kpColor kpMainWindow::backgroundColor (bool ofSelection) const { if (ofSelection) { return kpColor::Transparent; } Q_ASSERT (d->colorToolBar); return d->colorToolBar->backgroundColor (); } //--------------------------------------------------------------------- // public // REFACTOR: sync: Code dup with kpAbstractSelectionTool::addNeedingContentCommand(). void kpMainWindow::addImageOrSelectionCommand (kpCommand *cmd, bool addSelCreateCmdIfSelAvail, bool addSelContentCmdIfSelAvail) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::addImageOrSelectionCommand()" << " addSelCreateCmdIfSelAvail=" << addSelCreateCmdIfSelAvail << " addSelContentCmdIfSelAvail=" << addSelContentCmdIfSelAvail; +#endif Q_ASSERT (d->document); if (d->viewManager) { d->viewManager->setQueueUpdates (); } kpAbstractSelection *sel = d->document->selection (); +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\timage sel=" << sel << " sel->hasContent=" << (sel ? sel->hasContent () : 0); +#endif if (addSelCreateCmdIfSelAvail && sel && !sel->hasContent ()) { QString createCmdName; if (dynamic_cast (sel)) { createCmdName = i18n ("Selection: Create"); } else if (dynamic_cast (sel)) { createCmdName = i18n ("Text: Create Box"); } else { Q_ASSERT (!"Unknown selection type"); } // create selection region commandHistory ()->addCreateSelectionCommand ( new kpToolSelectionCreateCommand ( createCmdName, *sel, commandEnvironment ()), false/*no exec - user already dragged out sel*/); } if (addSelContentCmdIfSelAvail && sel && !sel->hasContent ()) { auto *imageSel = dynamic_cast (sel); auto *textSel = dynamic_cast (sel); if (imageSel && imageSel->transparency ().isTransparent ()) { d->colorToolBar->flashColorSimilarityToolBarItem (); } kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), commandEnvironment ()); if (imageSel) { macroCmd->addCommand ( new kpToolSelectionPullFromDocumentCommand ( *imageSel, backgroundColor (), QString()/*uninteresting child of macro cmd*/, commandEnvironment ())); } else if (textSel) { macroCmd->addCommand ( new kpToolTextGiveContentCommand ( *textSel, QString()/*uninteresting child of macro cmd*/, commandEnvironment ())); } else { Q_ASSERT (!"Unknown selection type"); } macroCmd->addCommand (cmd); d->commandHistory->addCommand (macroCmd); } else { d->commandHistory->addCommand (cmd); } if (d->viewManager) { d->viewManager->restoreQueueUpdates (); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotResizeScale () { toolEndShape (); kpTransformResizeScaleDialog dialog(transformDialogEnvironment(), this); if (dialog.exec () && !dialog.isNoOp ()) { auto *cmd = new kpTransformResizeScaleCommand ( dialog.actOnSelection (), dialog.imageWidth (), dialog.imageHeight (), dialog.type (), commandEnvironment ()); bool addSelCreateCommand = (dialog.actOnSelection () || cmd->scaleSelectionWithImage ()); bool addSelContentCommand = dialog.actOnSelection (); addImageOrSelectionCommand ( cmd, addSelCreateCommand, addSelContentCommand); // Resized document? if (!dialog.actOnSelection () && dialog.type () == kpTransformResizeScaleCommand::Resize) { // TODO: this should be the responsibility of kpDocument saveDefaultDocSize (QSize (dialog.imageWidth (), dialog.imageHeight ())); } } } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotCrop () { toolEndShape (); Q_ASSERT (d->document && d->document->selection ()); ::kpTransformCrop (this); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotAutoCrop () { toolEndShape (); ::kpTransformAutoCrop (this); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotFlip() { toolEndShape(); addImageOrSelectionCommand( new kpTransformFlipCommand(d->document->selection(), false, true, commandEnvironment())); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotMirror() { toolEndShape(); addImageOrSelectionCommand( new kpTransformFlipCommand(d->document->selection(), true, false, commandEnvironment())); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotRotate () { toolEndShape (); kpTransformRotateDialog dialog (static_cast (d->document->selection ()), transformDialogEnvironment (), this); if (dialog.exec () && !dialog.isNoOp ()) { addImageOrSelectionCommand ( new kpTransformRotateCommand (d->document->selection (), dialog.angle (), commandEnvironment ())); } } // private slot void kpMainWindow::slotRotate270 () { toolEndShape (); // TODO: Special command name instead of just "Rotate"? addImageOrSelectionCommand ( new kpTransformRotateCommand ( d->document->selection (), 270, commandEnvironment ())); } // private slot void kpMainWindow::slotRotate90 () { toolEndShape (); // TODO: Special command name instead of just "Rotate"? addImageOrSelectionCommand ( new kpTransformRotateCommand ( d->document->selection (), 90, commandEnvironment ())); } // private slot void kpMainWindow::slotSkew () { toolEndShape (); kpTransformSkewDialog dialog (static_cast (d->document->selection ()), transformDialogEnvironment (), this); if (dialog.exec () && !dialog.isNoOp ()) { addImageOrSelectionCommand ( new kpTransformSkewCommand (d->document->selection (), dialog.horizontalAngle (), dialog.verticalAngle (), commandEnvironment ())); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotConvertToBlackAndWhite () { toolEndShape (); addImageOrSelectionCommand ( new kpEffectReduceColorsCommand (1/*depth*/, true/*dither*/, d->document->selection (), commandEnvironment ())); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotConvertToGrayscale () { toolEndShape (); addImageOrSelectionCommand ( new kpEffectGrayscaleCommand (d->document->selection (), commandEnvironment ())); } //-------------------------------------------------------------------------------- // private slot void kpMainWindow::slotInvertColors () { toolEndShape (); addImageOrSelectionCommand ( new kpEffectInvertCommand (d->document->selection (), commandEnvironment ())); } //-------------------------------------------------------------------------------- // private slot void kpMainWindow::slotClear () { toolEndShape (); addImageOrSelectionCommand ( new kpEffectClearCommand ( d->document->selection (), backgroundColor (), commandEnvironment ())); } //-------------------------------------------------------------------------------- void kpMainWindow::slotMakeConfidential() { toolEndShape(); addImageOrSelectionCommand( new kpEffectBlurSharpenCommand(kpEffectBlurSharpen::MakeConfidential, kpEffectBlurSharpen::MaxStrength, d->document->selection(), commandEnvironment())); } //-------------------------------------------------------------------------------- // private slot void kpMainWindow::slotMoreEffects () { toolEndShape (); kpEffectsDialog dialog (static_cast (d->document->selection ()), transformDialogEnvironment (), this, d->moreEffectsDialogLastEffect); if (dialog.exec () && !dialog.isNoOp ()) { addImageOrSelectionCommand (dialog.createCommand ()); } if (d->moreEffectsDialogLastEffect != dialog.selectedEffect ()) { d->moreEffectsDialogLastEffect = dialog.selectedEffect (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingMoreEffectsLastEffect, d->moreEffectsDialogLastEffect); cfg.sync (); } } //-------------------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Settings.cpp b/mainWindow/kpMainWindow_Settings.cpp index b3cd86f6..8556334b 100644 --- a/mainWindow/kpMainWindow_Settings.cpp +++ b/mainWindow/kpMainWindow_Settings.cpp @@ -1,156 +1,160 @@ /* Copyright (c) 2003-2007 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "kpLogCategories.h" #include #include #include #include #include #include #include #include "kpDefs.h" #include "document/kpDocument.h" #include "tools/kpToolAction.h" #include "widgets/toolbars/kpToolToolBar.h" #include "environments/tools/kpToolEnvironment.h" //--------------------------------------------------------------------- // private void kpMainWindow::setupSettingsMenuActions () { KActionCollection *ac = actionCollection (); // Settings/Toolbars |> %s setStandardToolBarMenuEnabled (true); // Settings/Show Statusbar createStandardStatusBarAction (); d->actionFullScreen = KStandardAction::fullScreen (this, SLOT (slotFullScreen()), this/*window*/, ac); d->actionShowPath = ac->add ("settings_show_path"); d->actionShowPath->setText (i18n ("Show &Path")); connect (d->actionShowPath, &QAction::triggered, this, &kpMainWindow::slotShowPathToggled); slotEnableSettingsShowPath (); auto *action = ac->add("settings_draw_antialiased"); action->setText(i18n("Draw Anti-Aliased")); action->setChecked(kpToolEnvironment::drawAntiAliased); connect (action, &KToggleAction::triggered, this, &kpMainWindow::slotDrawAntiAliasedToggled); d->actionKeyBindings = KStandardAction::keyBindings (this, SLOT (slotKeyBindings()), ac); KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); enableSettingsMenuDocumentActions (false); } //--------------------------------------------------------------------- // private void kpMainWindow::enableSettingsMenuDocumentActions (bool /*enable*/) { } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotFullScreen () { KToggleFullScreenAction::setFullScreen( this, d->actionFullScreen->isChecked ()); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotEnableSettingsShowPath () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotEnableSettingsShowPath()"; +#endif const bool enable = (d->document && !d->document->url ().isEmpty ()); d->actionShowPath->setEnabled (enable); d->actionShowPath->setChecked (enable && d->configShowPath); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotShowPathToggled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotShowPathToggled()"; +#endif d->configShowPath = d->actionShowPath->isChecked (); slotUpdateCaption (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingShowPath, d->configShowPath); cfg.sync (); } //--------------------------------------------------------------------- void kpMainWindow::slotDrawAntiAliasedToggled(bool on) { kpToolEnvironment::drawAntiAliased = on; KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); cfg.writeEntry(kpSettingDrawAntiAliased, kpToolEnvironment::drawAntiAliased); cfg.sync(); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotKeyBindings () { toolEndShape (); if (KShortcutsDialog::configure (actionCollection (), KShortcutsEditor::LetterShortcutsAllowed, this)) { // TODO: PROPAGATE: thru mainWindow's and interprocess } } //--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_StatusBar.cpp b/mainWindow/kpMainWindow_StatusBar.cpp index 34472d51..0332abc0 100644 --- a/mainWindow/kpMainWindow_StatusBar.cpp +++ b/mainWindow/kpMainWindow_StatusBar.cpp @@ -1,414 +1,440 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller 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_STATUS_BAR (DEBUG_KP_MAIN_WINDOW && 0) #include "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "tools/kpTool.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include "views/kpZoomedView.h" #include #include //--------------------------------------------------------------------- // private void kpMainWindow::addPermanentStatusBarItem (int id, int maxTextLen) { QStatusBar *sb = statusBar (); QLabel *label = new QLabel (sb); label->setAlignment (Qt::AlignCenter); label->setFixedHeight (label->fontMetrics ().height () + 2); int maxWidth = label->fontMetrics ().width (QLatin1Char ('8')) * maxTextLen; // add some margins maxWidth += label->fontMetrics ().height (); label->setFixedWidth (maxWidth); // Permanent --> place on the right sb->addPermanentWidget (label); d->statusBarLabels.append (label); Q_ASSERT (d->statusBarLabels.at(id) == label); } //--------------------------------------------------------------------- // private void kpMainWindow::createStatusBar () { QStatusBar *sb = statusBar(); // 9999 pixels "ought to be enough for anybody" const int maxDimenLength = 4; d->statusBarMessageLabel = new KSqueezedTextLabel(sb); // this is done to have the same height as the other labels in status bar; done like in kstatusbar.cpp d->statusBarMessageLabel->setFixedHeight(d->statusBarMessageLabel->fontMetrics().height() + 2); d->statusBarMessageLabel->setTextElideMode(Qt::ElideRight); // this is the reason why we explicitly set a widget sb->addWidget(d->statusBarMessageLabel, 1/*stretch*/); addPermanentStatusBarItem (StatusBarItemShapePoints, (maxDimenLength + 1/*,*/ + maxDimenLength) * 2 + 3/* - */); addPermanentStatusBarItem (StatusBarItemShapeSize, (1/*+/-*/ + maxDimenLength) * 2 + 1/*x*/); QString numSample = i18n("%1 x %2", 5000, 5000); // localized string; can e.g. be "5 000" addPermanentStatusBarItem(StatusBarItemDocSize, numSample.length()); addPermanentStatusBarItem(StatusBarItemDocDepth, 5/*XXbpp*/); addPermanentStatusBarItem (StatusBarItemZoom, 5/*1600%*/); d->statusBarShapeLastPointsInitialised = false; d->statusBarShapeLastSizeInitialised = false; d->statusBarCreated = true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::setStatusBarMessage (const QString &message) { +#if DEBUG_STATUS_BAR && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarMessage(" << message << ") ok=" << d->statusBarCreated; +#endif if (!d->statusBarCreated) { return; } d->statusBarMessageLabel->setText (message); } //--------------------------------------------------------------------- // private slot void kpMainWindow::setStatusBarShapePoints (const QPoint &startPoint, const QPoint &endPoint) { +#if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarShapePoints(" << startPoint << "," << endPoint << ") ok=" << d->statusBarCreated; +#endif if (!d->statusBarCreated) { return; } if (d->statusBarShapeLastPointsInitialised && startPoint == d->statusBarShapeLastStartPoint && endPoint == d->statusBarShapeLastEndPoint) { + #if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "\tNOP"; + #endif return; } QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemShapePoints); if (startPoint == KP_INVALID_POINT) { statusBarLabel->setText (QString()); } else if (endPoint == KP_INVALID_POINT) { statusBarLabel->setText (i18n ("%1,%2", startPoint.x (), startPoint.y ())); } else { statusBarLabel->setText (i18n ("%1,%2 - %3,%4", startPoint.x (), startPoint.y (), endPoint.x (), endPoint.y ())); } d->statusBarShapeLastStartPoint = startPoint; d->statusBarShapeLastEndPoint = endPoint; d->statusBarShapeLastPointsInitialised = true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::setStatusBarShapeSize (const QSize &size) { #if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarShapeSize(" << size << ") ok=" << d->statusBarCreated; #endif if (!d->statusBarCreated) { return; } if (d->statusBarShapeLastSizeInitialised && size == d->statusBarShapeLastSize) { #if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "\tNOP"; #endif return; } QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemShapeSize); if (size == KP_INVALID_SIZE) { statusBarLabel->setText (QString()); } else { statusBarLabel->setText (i18n ("%1x%2", size.width (), size.height ())); } d->statusBarShapeLastSize = size; d->statusBarShapeLastSizeInitialised = true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::setStatusBarDocSize (const QSize &size) { +#if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarDocSize(" << size << ") ok=" << d->statusBarCreated; +#endif if (!d->statusBarCreated) { return; } QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemDocSize); if (size == KP_INVALID_SIZE) { statusBarLabel->setText (QString()); } else { statusBarLabel->setText (i18n ("%1 x %2", size.width (), size.height ())); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::setStatusBarDocDepth (int depth) { +#if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarDocDepth(" << depth << ") ok=" << d->statusBarCreated; +#endif if (!d->statusBarCreated) { return; } QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemDocDepth); if (depth <= 0) { statusBarLabel->setText (QString()); } else { statusBarLabel->setText (i18n ("%1bpp", depth)); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::setStatusBarZoom (int zoom) { +#if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarZoom(" << zoom << ") ok=" << d->statusBarCreated; +#endif if (!d->statusBarCreated) { return; } QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemZoom); if (zoom <= 0) { statusBarLabel->setText (QString()); } else { statusBarLabel->setText (i18n ("%1%", zoom)); } } //--------------------------------------------------------------------- void kpMainWindow::recalculateStatusBarMessage () { +#if DEBUG_STATUS_BAR && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::recalculateStatusBarMessage()"; +#endif QString scrollViewMessage = d->scrollView->statusMessage (); +#if DEBUG_STATUS_BAR && 1 qCDebug(kpLogMainWindow) << "\tscrollViewMessage=" << scrollViewMessage; qCDebug(kpLogMainWindow) << "\tresizing doc? " << !d->scrollView->newDocSize ().isEmpty (); qCDebug(kpLogMainWindow) << "\tviewUnderCursor? " << (d->viewManager && d->viewManager->viewUnderCursor ()); +#endif // HACK: To work around kpViewScrollableContainer's unreliable // status messages (which in turn is due to Qt not updating // QWidget::underMouse() on drags and we needing to hack around it) if (!scrollViewMessage.isEmpty () && d->scrollView->newDocSize ().isEmpty () && d->viewManager && d->viewManager->viewUnderCursor ()) { #if DEBUG_STATUS_BAR && 1 qCDebug(kpLogMainWindow) << "\t\tnot resizing & viewUnderCursor - message is wrong - clearing"; #endif d->scrollView->blockSignals (true); d->scrollView->clearStatusMessage (); d->scrollView->blockSignals (false); scrollViewMessage.clear (); #if DEBUG_STATUS_BAR && 1 qCDebug(kpLogMainWindow) << "\t\t\tdone"; #endif } if (!scrollViewMessage.isEmpty ()) { setStatusBarMessage (scrollViewMessage); } else { const kpTool *t = tool (); if (t) { setStatusBarMessage (t->userMessage ()); } else { setStatusBarMessage (); } } } //--------------------------------------------------------------------- // private slot void kpMainWindow::recalculateStatusBarShape () { +#if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "kpMainWindow::recalculateStatusBarShape()"; +#endif QSize docResizeTo = d->scrollView->newDocSize (); +#if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "\tdocResizeTo=" << docResizeTo; +#endif if (docResizeTo.isValid ()) { const QPoint startPoint (d->document->width (), d->document->height ()); + #if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "\thavedMovedFromOrgSize=" << d->scrollView->haveMovedFromOriginalDocSize (); + #endif if (!d->scrollView->haveMovedFromOriginalDocSize ()) { setStatusBarShapePoints (startPoint); setStatusBarShapeSize (); } else { const int newWidth = docResizeTo.width (); const int newHeight = docResizeTo.height (); setStatusBarShapePoints (startPoint, QPoint (newWidth, newHeight)); const QPoint sizeAsPoint (QPoint (newWidth, newHeight) - startPoint); setStatusBarShapeSize (QSize (sizeAsPoint.x (), sizeAsPoint.y ())); } } else { const kpTool *t = tool (); + #if DEBUG_STATUS_BAR && 0 qCDebug(kpLogMainWindow) << "\ttool=" << t; + #endif if (t) { setStatusBarShapePoints (t->userShapeStartPoint (), t->userShapeEndPoint ()); setStatusBarShapeSize (t->userShapeSize ()); } else { setStatusBarShapePoints (); setStatusBarShapeSize (); } } } //--------------------------------------------------------------------- // private slot void kpMainWindow::recalculateStatusBar () { +#if DEBUG_STATUS_BAR && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::recalculateStatusBar() ok=" << d->statusBarCreated; +#endif if (!d->statusBarCreated) { return; } recalculateStatusBarMessage (); recalculateStatusBarShape (); if (d->document) { setStatusBarDocSize (QSize (d->document->width (), d->document->height ())); setStatusBarDocDepth (d->document->image ().depth ()); } else { setStatusBarDocSize (); setStatusBarDocDepth (); } if (d->mainView) { setStatusBarZoom (d->mainView->zoomLevelX ()); } else { setStatusBarZoom (); } } //--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Text.cpp b/mainWindow/kpMainWindow_Text.cpp index 6379d263..bce89c91 100644 --- a/mainWindow/kpMainWindow_Text.cpp +++ b/mainWindow/kpMainWindow_Text.cpp @@ -1,419 +1,436 @@ /* Copyright (c) 2003-2007 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include #include #include #include "kpLogCategories.h" #include #include #include #include #include #include #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "layers/selections/text/kpTextStyle.h" #include "tools/selection/text/kpToolText.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" #include "views/kpZoomedView.h" // private void kpMainWindow::setupTextToolBarActions () { KActionCollection *ac = actionCollection (); d->actionTextFontFamily = ac->add ("text_font_family"); d->actionTextFontFamily->setText (i18n ("Font Family")); connect (d->actionTextFontFamily, static_cast(&KFontAction::triggered), this, &kpMainWindow::slotTextFontFamilyChanged); d->actionTextFontSize = ac->add ("text_font_size"); d->actionTextFontSize->setText (i18n ("Font Size")); connect (d->actionTextFontSize, static_cast(&KFontSizeAction::triggered), this, &kpMainWindow::slotTextFontSizeChanged); d->actionTextBold = ac->add ("text_bold"); d->actionTextBold->setIcon(KDE::icon("format-text-bold")); d->actionTextBold->setText (i18n ("Bold")); connect (d->actionTextBold, &KToggleAction::triggered, this, &kpMainWindow::slotTextBoldChanged); d->actionTextItalic = ac->add ("text_italic"); d->actionTextItalic->setIcon (KDE::icon("format-text-italic")); d->actionTextItalic->setText (i18n ("Italic")); connect (d->actionTextItalic, &KToggleAction::triggered, this, &kpMainWindow::slotTextItalicChanged); d->actionTextUnderline = ac->add ("text_underline"); d->actionTextUnderline->setIcon (KDE::icon("format-text-underline")); d->actionTextUnderline->setText (i18n ("Underline")); connect (d->actionTextUnderline, &KToggleAction::triggered, this, &kpMainWindow::slotTextUnderlineChanged); d->actionTextStrikeThru = ac->add ("text_strike_thru"); d->actionTextStrikeThru->setIcon(KDE::icon("format-text-strikethrough")); d->actionTextStrikeThru->setText (i18n ("Strike Through")); connect (d->actionTextStrikeThru, &KToggleAction::triggered, this, &kpMainWindow::slotTextStrikeThruChanged); readAndApplyTextSettings (); enableTextToolBarActions (false); } // private void kpMainWindow::readAndApplyTextSettings () { KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); const QString font (cfg.readEntry (kpSettingFontFamily, QString::fromLatin1 ("Times"))); d->actionTextFontFamily->setFont (font); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "asked setFont to set to=" << font << "- got back=" << d->actionTextFontFamily->font (); - +#endif d->actionTextFontSize->setFontSize (cfg.readEntry (kpSettingFontSize, 14)); d->actionTextBold->setChecked (cfg.readEntry (kpSettingBold, false)); d->actionTextItalic->setChecked (cfg.readEntry (kpSettingItalic, false)); d->actionTextUnderline->setChecked (cfg.readEntry (kpSettingUnderline, false)); d->actionTextStrikeThru->setChecked (cfg.readEntry (kpSettingStrikeThru, false)); d->textOldFontFamily = d->actionTextFontFamily->font (); d->textOldFontSize = d->actionTextFontSize->fontSize (); } // public void kpMainWindow::enableTextToolBarActions (bool enable) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::enableTextToolBarActions(" << enable << ")"; +#endif d->actionTextFontFamily->setEnabled (enable); d->actionTextFontSize->setEnabled (enable); d->actionTextBold->setEnabled (enable); d->actionTextItalic->setEnabled (enable); d->actionTextUnderline->setEnabled (enable); d->actionTextStrikeThru->setEnabled (enable); if (textToolBar ()) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\thave toolbar - setShown"; #endif // COMPAT: KDE4 does not place the Text Tool Bar in a new row, underneath // the Main Tool Bar, if there isn't enough room. This makes // accessing the Text Tool Bar's buttons difficult. textToolBar ()->setVisible (enable); } } // private slot void kpMainWindow::slotTextFontFamilyChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontFamilyChanged() alive=" << d->isFullyConstructed << "fontFamily=" << d->actionTextFontFamily->font () << "action.currentItem=" << d->actionTextFontFamily->currentItem (); +#endif if (!d->isFullyConstructed) { return; } if (d->toolText && d->toolText->hasBegun ()) { toolEndShape (); d->toolText->slotFontFamilyChanged (d->actionTextFontFamily->font (), d->textOldFontFamily); } // Since editable KSelectAction's steal focus from view, switch back to mainView // TODO: back to the last view if (d->mainView) { d->mainView->setFocus (); } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); cfg.writeEntry (kpSettingFontFamily, d->actionTextFontFamily->font ()); cfg.sync (); d->textOldFontFamily = d->actionTextFontFamily->font (); } // private slot void kpMainWindow::slotTextFontSizeChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontSizeChanged() alive=" << d->isFullyConstructed << " fontSize=" << d->actionTextFontSize->fontSize (); +#endif if (!d->isFullyConstructed) { return; } if (d->toolText && d->toolText->hasBegun ()) { toolEndShape (); d->toolText->slotFontSizeChanged (d->actionTextFontSize->fontSize (), d->textOldFontSize); } // Since editable KSelectAction's steal focus from view, switch back to mainView // TODO: back to the last view if (d->mainView) { d->mainView->setFocus (); } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); cfg.writeEntry (kpSettingFontSize, d->actionTextFontSize->fontSize ()); cfg.sync (); d->textOldFontSize = d->actionTextFontSize->fontSize (); } // private slot void kpMainWindow::slotTextBoldChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontBoldChanged() alive=" << d->isFullyConstructed << " bold=" << d->actionTextBold->isChecked (); +#endif if (!d->isFullyConstructed) { return; } if (d->toolText && d->toolText->hasBegun ()) { toolEndShape (); d->toolText->slotBoldChanged (d->actionTextBold->isChecked ()); } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); cfg.writeEntry (kpSettingBold, d->actionTextBold->isChecked ()); cfg.sync (); } // private slot void kpMainWindow::slotTextItalicChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontItalicChanged() alive=" << d->isFullyConstructed << " bold=" << d->actionTextItalic->isChecked (); +#endif if (!d->isFullyConstructed) { return; } if (d->toolText && d->toolText->hasBegun ()) { toolEndShape (); d->toolText->slotItalicChanged (d->actionTextItalic->isChecked ()); } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); cfg.writeEntry (kpSettingItalic, d->actionTextItalic->isChecked ()); cfg.sync (); } // private slot void kpMainWindow::slotTextUnderlineChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontUnderlineChanged() alive=" << d->isFullyConstructed << " underline=" << d->actionTextUnderline->isChecked (); +#endif if (!d->isFullyConstructed) { return; } if (d->toolText && d->toolText->hasBegun ()) { toolEndShape (); d->toolText->slotUnderlineChanged (d->actionTextUnderline->isChecked ()); } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); cfg.writeEntry (kpSettingUnderline, d->actionTextUnderline->isChecked ()); cfg.sync (); } // private slot void kpMainWindow::slotTextStrikeThruChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextStrikeThruChanged() alive=" << d->isFullyConstructed << " strikeThru=" << d->actionTextStrikeThru->isChecked (); +#endif if (!d->isFullyConstructed) { return; } if (d->toolText && d->toolText->hasBegun ()) { toolEndShape (); d->toolText->slotStrikeThruChanged (d->actionTextStrikeThru->isChecked ()); } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); cfg.writeEntry (kpSettingStrikeThru, d->actionTextStrikeThru->isChecked ()); cfg.sync (); } // public KToolBar *kpMainWindow::textToolBar () { return toolBar ("textToolBar"); } bool kpMainWindow::isTextStyleBackgroundOpaque () const { if (d->toolToolBar) { kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); if (oot) { return oot->isOpaque (); } } return true; } // public kpTextStyle kpMainWindow::textStyle () const { return kpTextStyle (d->actionTextFontFamily->font (), d->actionTextFontSize->fontSize (), d->actionTextBold->isChecked (), d->actionTextItalic->isChecked (), d->actionTextUnderline->isChecked (), d->actionTextStrikeThru->isChecked (), d->colorToolBar ? d->colorToolBar->foregroundColor () : kpColor::Invalid, d->colorToolBar ? d->colorToolBar->backgroundColor () : kpColor::Invalid, isTextStyleBackgroundOpaque ()); } // public void kpMainWindow::setTextStyle (const kpTextStyle &textStyle_) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::setTextStyle()"; +#endif d->settingTextStyle++; if (textStyle_.fontFamily () != d->actionTextFontFamily->font ()) { d->actionTextFontFamily->setFont (textStyle_.fontFamily ()); slotTextFontFamilyChanged (); } if (textStyle_.fontSize () != d->actionTextFontSize->fontSize ()) { d->actionTextFontSize->setFontSize (textStyle_.fontSize ()); slotTextFontSizeChanged (); } if (textStyle_.isBold () != d->actionTextBold->isChecked ()) { d->actionTextBold->setChecked (textStyle_.isBold ()); slotTextBoldChanged (); } if (textStyle_.isItalic () != d->actionTextItalic->isChecked ()) { d->actionTextItalic->setChecked (textStyle_.isItalic ()); slotTextItalicChanged (); } if (textStyle_.isUnderline () != d->actionTextUnderline->isChecked ()) { d->actionTextUnderline->setChecked (textStyle_.isUnderline ()); slotTextUnderlineChanged (); } if (textStyle_.isStrikeThru () != d->actionTextStrikeThru->isChecked ()) { d->actionTextStrikeThru->setChecked (textStyle_.isStrikeThru ()); slotTextStrikeThruChanged (); } if (textStyle_.foregroundColor () != d->colorToolBar->foregroundColor ()) { d->colorToolBar->setForegroundColor (textStyle_.foregroundColor ()); } if (textStyle_.backgroundColor () != d->colorToolBar->backgroundColor ()) { d->colorToolBar->setBackgroundColor (textStyle_.backgroundColor ()); } if (textStyle_.isBackgroundOpaque () != isTextStyleBackgroundOpaque ()) { if (d->toolToolBar) { kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); if (oot) { oot->setOpaque (textStyle_.isBackgroundOpaque ()); } } } d->settingTextStyle--; } // public int kpMainWindow::settingTextStyle () const { return d->settingTextStyle; } diff --git a/mainWindow/kpMainWindow_Tools.cpp b/mainWindow/kpMainWindow_Tools.cpp index 0647888c..776a9033 100644 --- a/mainWindow/kpMainWindow_Tools.cpp +++ b/mainWindow/kpMainWindow_Tools.cpp @@ -1,812 +1,834 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include #include #include #include #include #include "kpLogCategories.h" #include #include "widgets/toolbars/kpColorToolBar.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "layers/selections/image/kpImageSelectionTransparency.h" #include "tools/kpTool.h" #include "tools/kpToolAction.h" #include "tools/flow/kpToolBrush.h" #include "tools/flow/kpToolColorEraser.h" #include "tools/kpToolColorPicker.h" #include "tools/polygonal/kpToolCurve.h" #include "tools/selection/image/kpToolEllipticalSelection.h" #include "tools/rectangular/kpToolEllipse.h" #include "tools/flow/kpToolEraser.h" #include "tools/kpToolFloodFill.h" #include "tools/selection/image/kpToolFreeFormSelection.h" #include "tools/polygonal/kpToolLine.h" #include "tools/flow/kpToolPen.h" #include "tools/polygonal/kpToolPolygon.h" #include "tools/polygonal/kpToolPolyline.h" #include "tools/rectangular/kpToolRectangle.h" #include "tools/selection/image/kpToolRectSelection.h" #include "tools/rectangular/kpToolRoundedRectangle.h" #include "environments/tools/selection/kpToolSelectionEnvironment.h" #include "tools/flow/kpToolSpraycan.h" #include "tools/selection/text/kpToolText.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" #include "tools/kpToolZoom.h" #include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" #include "kpViewScrollableContainer.h" #include "views/kpZoomedView.h" //--------------------------------------------------------------------- // private kpToolSelectionEnvironment *kpMainWindow::toolSelectionEnvironment () { if (!d->toolSelectionEnvironment) { d->toolSelectionEnvironment = new kpToolSelectionEnvironment (this); } return d->toolSelectionEnvironment; } //--------------------------------------------------------------------- // private kpToolEnvironment *kpMainWindow::toolEnvironment () { // It's fine to return a more complex environment than required. return toolSelectionEnvironment (); } //--------------------------------------------------------------------- // private void kpMainWindow::setupToolActions () { kpToolSelectionEnvironment *toolSelEnv = toolSelectionEnvironment (); kpToolEnvironment *toolEnv = toolEnvironment (); d->tools.append (d->toolFreeFormSelection = new kpToolFreeFormSelection (toolSelEnv, this)); d->tools.append (d->toolRectSelection = new kpToolRectSelection (toolSelEnv, this)); d->tools.append (d->toolEllipticalSelection = new kpToolEllipticalSelection (toolSelEnv, this)); d->tools.append (d->toolText = new kpToolText (toolSelEnv, this)); d->tools.append (d->toolLine = new kpToolLine (toolEnv, this)); d->tools.append (d->toolPen = new kpToolPen (toolEnv, this)); d->tools.append (d->toolEraser = new kpToolEraser (toolEnv, this)); d->tools.append (d->toolBrush = new kpToolBrush (toolEnv, this)); d->tools.append (d->toolFloodFill = new kpToolFloodFill (toolEnv, this)); d->tools.append (d->toolColorPicker = new kpToolColorPicker (toolEnv, this)); d->tools.append (d->toolColorEraser = new kpToolColorEraser (toolEnv, this)); d->tools.append (d->toolSpraycan = new kpToolSpraycan (toolEnv, this)); d->tools.append (d->toolRoundedRectangle = new kpToolRoundedRectangle (toolEnv, this)); d->tools.append (d->toolRectangle = new kpToolRectangle (toolEnv, this)); d->tools.append (d->toolPolygon = new kpToolPolygon (toolEnv, this)); d->tools.append (d->toolEllipse = new kpToolEllipse (toolEnv, this)); d->tools.append (d->toolPolyline = new kpToolPolyline (toolEnv, this)); d->tools.append (d->toolCurve = new kpToolCurve (toolEnv, this)); d->tools.append (d->toolZoom = new kpToolZoom (toolEnv, this)); KActionCollection *ac = actionCollection (); d->actionPrevToolOptionGroup1 = ac->addAction ("prev_tool_option_group_1"); d->actionPrevToolOptionGroup1->setText (i18n ("Previous Tool Option (Group #1)")); ac->setDefaultShortcuts (d->actionPrevToolOptionGroup1, kpTool::shortcutForKey (Qt::Key_1)); connect (d->actionPrevToolOptionGroup1, &QAction::triggered, this, &kpMainWindow::slotActionPrevToolOptionGroup1); d->actionNextToolOptionGroup1 = ac->addAction ("next_tool_option_group_1"); d->actionNextToolOptionGroup1->setText (i18n ("Next Tool Option (Group #1)")); ac->setDefaultShortcuts (d->actionNextToolOptionGroup1, kpTool::shortcutForKey (Qt::Key_2)); connect (d->actionNextToolOptionGroup1, &QAction::triggered, this, &kpMainWindow::slotActionNextToolOptionGroup1); d->actionPrevToolOptionGroup2 = ac->addAction ("prev_tool_option_group_2"); d->actionPrevToolOptionGroup2->setText (i18n ("Previous Tool Option (Group #2)")); ac->setDefaultShortcuts (d->actionPrevToolOptionGroup2, kpTool::shortcutForKey (Qt::Key_3)); connect (d->actionPrevToolOptionGroup2, &QAction::triggered, this, &kpMainWindow::slotActionPrevToolOptionGroup2); d->actionNextToolOptionGroup2 = ac->addAction ("next_tool_option_group_2"); d->actionNextToolOptionGroup2->setText (i18n ("Next Tool Option (Group #2)")); ac->setDefaultShortcuts (d->actionNextToolOptionGroup2, kpTool::shortcutForKey (Qt::Key_4)); connect (d->actionNextToolOptionGroup2, &QAction::triggered, this, &kpMainWindow::slotActionNextToolOptionGroup2); // // Implemented in this file (kpMainWindow_Tools.cpp), not // kpImageWindow_Image.cpp since they're really setting tool options. // d->actionDrawOpaque = ac->add ("image_draw_opaque"); d->actionDrawOpaque->setText (i18n ("&Draw Opaque")); connect (d->actionDrawOpaque, &QAction::triggered, this, &kpMainWindow::slotActionDrawOpaqueToggled); d->actionDrawColorSimilarity = ac->addAction ("image_draw_color_similarity"); d->actionDrawColorSimilarity->setText (i18n ("Draw With Color Similarity...")); connect (d->actionDrawColorSimilarity, &QAction::triggered, this, &kpMainWindow::slotActionDrawColorSimilarity); } //--------------------------------------------------------------------- // private void kpMainWindow::createToolBox () { d->toolToolBar = new kpToolToolBar(QLatin1String("Tool Box"), 2/*columns/rows*/, this); d->toolToolBar->setWindowTitle(i18n("Tool Box")); connect (d->toolToolBar, &kpToolToolBar::sigToolSelected, this, &kpMainWindow::slotToolSelected); connect (d->toolToolBar, &kpToolToolBar::toolWidgetOptionSelected, this, &kpMainWindow::updateToolOptionPrevNextActionsEnabled); connect (d->toolToolBar->toolWidgetOpaqueOrTransparent(), &kpToolWidgetOpaqueOrTransparent::isOpaqueChanged, this, &kpMainWindow::updateActionDrawOpaqueChecked); updateActionDrawOpaqueChecked (); for (auto *tool : d->tools) { d->toolToolBar->registerTool(tool); } // (from config file) readLastTool (); } //--------------------------------------------------------------------- // private void kpMainWindow::enableToolsDocumentActions (bool enable) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::enableToolsDocumentsAction(" << enable << ")"; +#endif d->toolActionsEnabled = enable; if (enable && !d->toolToolBar->isEnabled ()) { kpTool *previousTool = d->toolToolBar->previousTool (); // select tool for enabled Tool Box if (previousTool) { d->toolToolBar->selectPreviousTool (); } else { if (d->lastToolNumber >= 0 && d->lastToolNumber < d->tools.count ()) { d->toolToolBar->selectTool (d->tools.at (d->lastToolNumber)); } else { d->toolToolBar->selectTool (d->toolPen); } } } else if (!enable && d->toolToolBar->isEnabled ()) { // don't have a disabled Tool Box with a checked Tool d->toolToolBar->selectTool (nullptr); } d->toolToolBar->setEnabled (enable); for (auto *tool : d->tools) { kpToolAction *action = tool->action(); if (!enable && action->isChecked()) { action->setChecked(false); } action->setEnabled(enable); } updateToolOptionPrevNextActionsEnabled (); updateActionDrawOpaqueEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::updateToolOptionPrevNextActionsEnabled () { const bool enable = d->toolActionsEnabled; d->actionPrevToolOptionGroup1->setEnabled (enable && d->toolToolBar->shownToolWidget (0) && d->toolToolBar->shownToolWidget (0)->hasPreviousOption ()); d->actionNextToolOptionGroup1->setEnabled (enable && d->toolToolBar->shownToolWidget (0) && d->toolToolBar->shownToolWidget (0)->hasNextOption ()); d->actionPrevToolOptionGroup2->setEnabled (enable && d->toolToolBar->shownToolWidget (1) && d->toolToolBar->shownToolWidget (1)->hasPreviousOption ()); d->actionNextToolOptionGroup2->setEnabled (enable && d->toolToolBar->shownToolWidget (1) && d->toolToolBar->shownToolWidget (1)->hasNextOption ()); } //--------------------------------------------------------------------- // private slot void kpMainWindow::updateActionDrawOpaqueChecked () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::updateActionDrawOpaqueChecked()"; +#endif const bool drawOpaque = (d->toolToolBar->toolWidgetOpaqueOrTransparent ()->selectedRow () == 0); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdrawOpaque=" << drawOpaque; +#endif d->actionDrawOpaque->setChecked (drawOpaque); } //--------------------------------------------------------------------- // private void kpMainWindow::updateActionDrawOpaqueEnabled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::updateActionDrawOpaqueEnabled()"; +#endif const bool enable = d->toolActionsEnabled; +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tenable=" << enable << " tool=" << (tool () ? tool ()->objectName () : nullptr) << " (is selection=" << toolIsASelectionTool () << ")"; +#endif d->actionDrawOpaque->setEnabled (enable && toolIsASelectionTool ()); } //--------------------------------------------------------------------- // public QActionGroup *kpMainWindow::toolsActionGroup () { if (!d->toolsActionGroup) { d->toolsActionGroup = new QActionGroup (this); } return d->toolsActionGroup; } //--------------------------------------------------------------------- // public kpTool *kpMainWindow::tool () const { return d->toolToolBar ? d->toolToolBar->tool () : nullptr; } //--------------------------------------------------------------------- // public bool kpMainWindow::toolHasBegunShape () const { kpTool *currentTool = tool (); return (currentTool && currentTool->hasBegunShape ()); } //--------------------------------------------------------------------- // public bool kpMainWindow::toolIsASelectionTool (bool includingTextTool) const { kpTool *currentTool = tool (); return ((currentTool == d->toolFreeFormSelection) || (currentTool == d->toolRectSelection) || (currentTool == d->toolEllipticalSelection) || (currentTool == d->toolText && includingTextTool)); } //--------------------------------------------------------------------- // public bool kpMainWindow::toolIsTextTool () const { return (tool () == d->toolText); } //--------------------------------------------------------------------- // private void kpMainWindow::toolEndShape () { if (toolHasBegunShape ()) { tool ()->endShapeInternal (); } } //--------------------------------------------------------------------- // public kpImageSelectionTransparency kpMainWindow::imageSelectionTransparency () const { kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); Q_ASSERT (oot); return kpImageSelectionTransparency (oot->isOpaque (), backgroundColor (), d->colorToolBar->colorSimilarity ()); } //--------------------------------------------------------------------- // public void kpMainWindow::setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, bool forceColorChange) { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::setImageSelectionTransparency() isOpaque=" << transparency.isOpaque () << " color=" << (transparency.transparentColor ().isValid () ? (int *) transparency.transparentColor ().toQRgb () : nullptr) << " forceColorChange=" << forceColorChange; +#endif kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); Q_ASSERT (oot); d->settingImageSelectionTransparency++; oot->setOpaque (transparency.isOpaque ()); if (transparency.isTransparent () || forceColorChange) { d->colorToolBar->setColor (1, transparency.transparentColor ()); d->colorToolBar->setColorSimilarity (transparency.colorSimilarity ()); } d->settingImageSelectionTransparency--; } //--------------------------------------------------------------------- // public int kpMainWindow::settingImageSelectionTransparency () const { return d->settingImageSelectionTransparency; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotToolSelected (kpTool *tool) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotToolSelected (" << tool << ")"; #endif kpTool *previousTool = d->toolToolBar ? d->toolToolBar->previousTool () : nullptr; if (previousTool) { disconnect (previousTool, &kpTool::movedAndAboutToDraw, this, &kpMainWindow::slotDragScroll); disconnect (previousTool, &kpTool::endedDraw, this, &kpMainWindow::slotEndDragScroll); disconnect (previousTool, &kpTool::cancelledShape, this, &kpMainWindow::slotEndDragScroll); disconnect (previousTool, &kpTool::userMessageChanged, this, &kpMainWindow::recalculateStatusBarMessage); disconnect (previousTool, &kpTool::userShapePointsChanged, this, &kpMainWindow::recalculateStatusBarShape); disconnect (previousTool, &kpTool::userShapeSizeChanged, this, &kpMainWindow::recalculateStatusBarShape); disconnect (d->colorToolBar, &kpColorToolBar::colorsSwapped, previousTool, &kpTool::slotColorsSwappedInternal); disconnect (d->colorToolBar, &kpColorToolBar::foregroundColorChanged, previousTool, &kpTool::slotForegroundColorChangedInternal); disconnect (d->colorToolBar, &kpColorToolBar::backgroundColorChanged, previousTool, &kpTool::slotBackgroundColorChangedInternal); disconnect (d->colorToolBar, &kpColorToolBar::colorSimilarityChanged, previousTool, &kpTool::slotColorSimilarityChangedInternal); } if (tool) { connect (tool, &kpTool::movedAndAboutToDraw, this, &kpMainWindow::slotDragScroll); connect (tool, &kpTool::endedDraw, this, &kpMainWindow::slotEndDragScroll); connect (tool, &kpTool::cancelledShape, this, &kpMainWindow::slotEndDragScroll); connect (tool, &kpTool::userMessageChanged, this, &kpMainWindow::recalculateStatusBarMessage); connect (tool, &kpTool::userShapePointsChanged, this, &kpMainWindow::recalculateStatusBarShape); connect (tool, &kpTool::userShapeSizeChanged, this, &kpMainWindow::recalculateStatusBarShape); recalculateStatusBar (); connect (d->colorToolBar, &kpColorToolBar::colorsSwapped, tool, &kpTool::slotColorsSwappedInternal); connect (d->colorToolBar, &kpColorToolBar::foregroundColorChanged, tool, &kpTool::slotForegroundColorChangedInternal); connect (d->colorToolBar, &kpColorToolBar::backgroundColorChanged, tool, &kpTool::slotBackgroundColorChangedInternal); connect (d->colorToolBar, &kpColorToolBar::colorSimilarityChanged, tool, &kpTool::slotColorSimilarityChangedInternal); saveLastTool (); } updateToolOptionPrevNextActionsEnabled (); updateActionDrawOpaqueEnabled (); } //--------------------------------------------------------------------- // private void kpMainWindow::readLastTool () { KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupTools); d->lastToolNumber = cfg.readEntry (kpSettingLastTool, -1); } //--------------------------------------------------------------------- // private int kpMainWindow::toolNumber () const { int number = 0; for (QList ::const_iterator it = d->tools.constBegin (); it != d->tools.constEnd (); ++it) { if (*it == tool ()) { return number; } number++; } return -1; } //--------------------------------------------------------------------- // private void kpMainWindow::saveLastTool () { int number = toolNumber (); if (number < 0 || number >= d->tools.count ()) { return; } KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupTools); cfg.writeEntry (kpSettingLastTool, number); cfg.sync (); } //--------------------------------------------------------------------- // private bool kpMainWindow::maybeDragScrollingMainView () const { return (tool () && d->mainView && tool ()->viewUnderStartPoint () == d->mainView); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotDragScroll (const QPoint &docPoint, const QPoint &docLastPoint, int zoomLevel, bool *scrolled) { Q_UNUSED(docPoint) Q_UNUSED(docLastPoint) +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotDragScroll() maybeDragScrolling=" << maybeDragScrollingMainView (); +#endif if (maybeDragScrollingMainView ()) { return d->scrollView->beginDragScroll(zoomLevel, scrolled); } return false; } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotEndDragScroll () { // (harmless if haven't started drag scroll) return d->scrollView->endDragScroll (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotBeganDocResize () { toolEndShape (); recalculateStatusBarShape (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotContinuedDocResize (const QSize &) { recalculateStatusBarShape (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotCancelledDocResize () { recalculateStatusBar (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotEndedDocResize (const QSize &size) { #define DOC_RESIZE_COMPLETED() \ { \ d->docResizeToBeCompleted = false; \ recalculateStatusBar (); \ } // Prevent statusbar updates d->docResizeToBeCompleted = true; d->docResizeWidth = (size.width () > 0 ? size.width () : 1); d->docResizeHeight = (size.height () > 0 ? size.height () : 1); if (d->docResizeWidth == d->document->width () && d->docResizeHeight == d->document->height ()) { DOC_RESIZE_COMPLETED (); return; } // Blank status to avoid confusion if dialog comes up setStatusBarMessage (); setStatusBarShapePoints (); setStatusBarShapeSize (); if (kpTool::warnIfBigImageSize (d->document->width (), d->document->height (), d->docResizeWidth, d->docResizeHeight, i18n ("

Resizing the image to" " %1x%2 may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to resize the" " image?

", d->docResizeWidth, d->docResizeHeight), i18nc ("@title:window", "Resize Image?"), i18n ("R&esize Image"), this)) { d->commandHistory->addCommand ( new kpTransformResizeScaleCommand ( false/*doc, not sel*/, d->docResizeWidth, d->docResizeHeight, kpTransformResizeScaleCommand::Resize, commandEnvironment ())); saveDefaultDocSize (QSize (d->docResizeWidth, d->docResizeHeight)); } DOC_RESIZE_COMPLETED (); #undef DOC_RESIZE_COMPLETED } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotDocResizeMessageChanged (const QString &string) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotDocResizeMessageChanged(" << string << ") docResizeToBeCompleted=" << d->docResizeToBeCompleted; +#else + (void) string; +#endif if (d->docResizeToBeCompleted) { return; } recalculateStatusBarMessage (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotActionPrevToolOptionGroup1 () { if (!d->toolToolBar->shownToolWidget (0)) { return; } // We don't call toolEndShape() here because we want #23 in the file BUGS // to later work. d->toolToolBar->shownToolWidget (0)->selectPreviousOption (); updateToolOptionPrevNextActionsEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotActionNextToolOptionGroup1 () { if (!d->toolToolBar->shownToolWidget (0)) { return; } // We don't call toolEndShape() here because we want #23 in the file BUGS // to later work. d->toolToolBar->shownToolWidget (0)->selectNextOption (); updateToolOptionPrevNextActionsEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotActionPrevToolOptionGroup2 () { if (!d->toolToolBar->shownToolWidget (1)) { return; } // We don't call toolEndShape() here because we want #23 in the file BUGS // to later work. d->toolToolBar->shownToolWidget (1)->selectPreviousOption (); updateToolOptionPrevNextActionsEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotActionNextToolOptionGroup2 () { if (!d->toolToolBar->shownToolWidget (1)) { return; } // We don't call toolEndShape() here because we want #23 in the file BUGS // to later work. d->toolToolBar->shownToolWidget (1)->selectNextOption (); updateToolOptionPrevNextActionsEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotActionDrawOpaqueToggled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotActionDrawOpaqueToggled()"; +#endif toolEndShape (); // TODO: How does this differ to setImageSelectionTransparency()? // ("kpToolWidgetBase::" is to access one overload shadowed by the override // of the other overload) d->toolToolBar->toolWidgetOpaqueOrTransparent ()->kpToolWidgetBase::setSelected ( (d->actionDrawOpaque->isChecked () ? 0/*row 0 = opaque*/ : 1/*row 1 = transparent*/), 0/*column*/); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotActionDrawColorSimilarity () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotActionDrawColorSimilarity()"; +#endif toolEndShape (); d->colorToolBar->openColorSimilarityDialog (); } //--------------------------------------------------------------------- // public slots #define SLOT_TOOL(toolName) \ void kpMainWindow::slotTool##toolName () \ { \ if (!d->toolToolBar) \ return; \ \ if (tool () == d->tool##toolName) \ return; \ \ d->toolToolBar->selectTool (d->tool##toolName); \ } SLOT_TOOL (RectSelection) SLOT_TOOL (EllipticalSelection) SLOT_TOOL (FreeFormSelection) SLOT_TOOL (Text) diff --git a/mainWindow/kpMainWindow_View.cpp b/mainWindow/kpMainWindow_View.cpp index d58611a6..e7dc2ef1 100644 --- a/mainWindow/kpMainWindow_View.cpp +++ b/mainWindow/kpMainWindow_View.cpp @@ -1,157 +1,163 @@ /* Copyright (c) 2003-2007 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "kpLogCategories.h" #include #include #include #include #include #include "kpDefs.h" #include "document/kpDocument.h" #include "kpThumbnail.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/kpUnzoomedThumbnailView.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include "generic/kpWidgetMapper.h" #include "views/kpZoomedView.h" #include "views/kpZoomedThumbnailView.h" // private void kpMainWindow::setupViewMenuActions () { KActionCollection *ac = actionCollection (); /*d->actionFullScreen = KStandardAction::fullScreen (0, 0, ac); d->actionFullScreen->setEnabled (false);*/ setupViewMenuZoomActions (); d->actionShowGrid = ac->add ("view_show_grid"); d->actionShowGrid->setText (i18n ("Show &Grid")); ac->setDefaultShortcut (d->actionShowGrid, Qt::CTRL + Qt::Key_G); //d->actionShowGrid->setCheckedState (KGuiItem(i18n ("Hide &Grid"))); connect (d->actionShowGrid, &KToggleAction::triggered, this, &kpMainWindow::slotShowGridToggled); setupViewMenuThumbnailActions (); enableViewMenuDocumentActions (false); } //--------------------------------------------------------------------- // private bool kpMainWindow::viewMenuDocumentActionsEnabled () const { return d->viewMenuDocumentActionsEnabled; } //--------------------------------------------------------------------- // private void kpMainWindow::enableViewMenuDocumentActions (bool enable) { d->viewMenuDocumentActionsEnabled = enable; enableViewMenuZoomDocumentActions (enable); actionShowGridUpdate (); enableViewMenuThumbnailDocumentActions (enable); } //--------------------------------------------------------------------- // private void kpMainWindow::actionShowGridUpdate () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::actionShowGridUpdate()"; +#endif const bool enable = (viewMenuDocumentActionsEnabled () && d->mainView && d->mainView->canShowGrid ()); d->actionShowGrid->setEnabled (enable); d->actionShowGrid->setChecked (enable && d->configShowGrid); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotShowGridToggled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotActionShowGridToggled()"; +#endif updateMainViewGrid (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingShowGrid, d->configShowGrid = d->actionShowGrid->isChecked ()); cfg.sync (); } //--------------------------------------------------------------------- // private void kpMainWindow::updateMainViewGrid () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::updateMainViewGrid ()"; +#endif if (d->mainView) { d->mainView->showGrid (d->actionShowGrid->isChecked ()); } } //--------------------------------------------------------------------- // private QRect kpMainWindow::mapToGlobal (const QRect &rect) const { return kpWidgetMapper::toGlobal (this, rect); } //--------------------------------------------------------------------- // private QRect kpMainWindow::mapFromGlobal (const QRect &rect) const { return kpWidgetMapper::fromGlobal (this, rect); } //--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_View_Thumbnail.cpp b/mainWindow/kpMainWindow_View_Thumbnail.cpp index 535d13f4..608f80c1 100644 --- a/mainWindow/kpMainWindow_View_Thumbnail.cpp +++ b/mainWindow/kpMainWindow_View_Thumbnail.cpp @@ -1,423 +1,479 @@ /* Copyright (c) 2003-2007 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "kpLogCategories.h" #include #include #include #include #include #include #include "kpDefs.h" #include "document/kpDocument.h" #include "kpThumbnail.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/kpUnzoomedThumbnailView.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include "generic/kpWidgetMapper.h" #include "views/kpZoomedView.h" #include "views/kpZoomedThumbnailView.h" // private void kpMainWindow::setupViewMenuThumbnailActions () { d->thumbnailSaveConfigTimer = nullptr; KActionCollection *ac = actionCollection (); d->actionShowThumbnail = ac->add ("view_show_thumbnail"); d->actionShowThumbnail->setText (i18n ("Show T&humbnail")); - // TODO: This doesn't work when the thumbnail has focus. + // TODO: This doesn't work when the thumbnail has focus. // Testcase: Press CTRL+H twice on a fresh KolourPaint. // The second CTRL+H doesn't close the thumbnail. ac->setDefaultShortcut (d->actionShowThumbnail, Qt::CTRL + Qt::Key_H); //d->actionShowThumbnail->setCheckedState (KGuiItem(i18n ("Hide T&humbnail"))); connect (d->actionShowThumbnail, &KToggleAction::triggered, this, &kpMainWindow::slotShowThumbnailToggled); // Please do not use setCheckedState() here - it wouldn't make sense d->actionZoomedThumbnail = ac->add ("view_zoomed_thumbnail"); d->actionZoomedThumbnail->setText (i18n ("Zoo&med Thumbnail Mode")); connect (d->actionZoomedThumbnail, &KToggleAction::triggered, this, &kpMainWindow::slotZoomedThumbnailToggled); // For consistency with the above action, don't use setCheckedState() // // Also, don't use "Show Thumbnail Rectangle" because if entire doc // can be seen in scrollView, checking option won't "Show" anything // since rect _surrounds_ entire doc (hence, won't be rendered). d->actionShowThumbnailRectangle = ac->add ("view_show_thumbnail_rectangle"); d->actionShowThumbnailRectangle->setText (i18n ("Enable Thumbnail &Rectangle")); connect (d->actionShowThumbnailRectangle, &KToggleAction::triggered, this, &kpMainWindow::slotThumbnailShowRectangleToggled); } // private void kpMainWindow::enableViewMenuThumbnailDocumentActions (bool enable) { d->actionShowThumbnail->setEnabled (enable); enableThumbnailOptionActions (enable); } // private slot void kpMainWindow::slotDestroyThumbnail () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotDestroyThumbnail()"; +#endif d->actionShowThumbnail->setChecked (false); enableThumbnailOptionActions (false); updateThumbnail (); } // private slot void kpMainWindow::slotDestroyThumbnailInitatedByUser () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotDestroyThumbnailInitiatedByUser()"; +#endif d->actionShowThumbnail->setChecked (false); slotShowThumbnailToggled (); } // private slot void kpMainWindow::slotCreateThumbnail () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotCreateThumbnail()"; +#endif d->actionShowThumbnail->setChecked (true); enableThumbnailOptionActions (true); updateThumbnail (); } // public void kpMainWindow::notifyThumbnailGeometryChanged () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::notifyThumbnailGeometryChanged()"; +#endif if (!d->thumbnailSaveConfigTimer) { d->thumbnailSaveConfigTimer = new QTimer (this); d->thumbnailSaveConfigTimer->setSingleShot (true); connect (d->thumbnailSaveConfigTimer, &QTimer::timeout, this, &kpMainWindow::slotSaveThumbnailGeometry); } // (single shot) d->thumbnailSaveConfigTimer->start (500/*msec*/); } // private slot void kpMainWindow::slotSaveThumbnailGeometry () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::saveThumbnailGeometry()"; +#endif if (!d->thumbnail) { return; } QRect rect (d->thumbnail->x (), d->thumbnail->y (), d->thumbnail->width (), d->thumbnail->height ()); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tthumbnail relative geometry=" << rect; +#endif d->configThumbnailGeometry = mapFromGlobal (rect); - qCDebug(kpLogMainWindow) << "\tCONFIG: saving thumbnail geometry "; +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tCONFIG: saving thumbnail geometry " + << d->configThumbnailGeometry; +#endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); cfg.writeEntry (kpSettingThumbnailGeometry, d->configThumbnailGeometry); cfg.sync (); } // private slot void kpMainWindow::slotShowThumbnailToggled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotShowThumbnailToggled()"; +#endif d->configThumbnailShown = d->actionShowThumbnail->isChecked (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); cfg.writeEntry (kpSettingThumbnailShown, d->configThumbnailShown); cfg.sync (); enableThumbnailOptionActions (d->actionShowThumbnail->isChecked ()); updateThumbnail (); } // private slot void kpMainWindow::updateThumbnailZoomed () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::updateThumbnailZoomed() zoomed=" << d->actionZoomedThumbnail->isChecked (); +#endif if (!d->thumbnailView) { return; } destroyThumbnailView (); createThumbnailView (); } // private slot void kpMainWindow::slotZoomedThumbnailToggled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoomedThumbnailToggled()"; +#endif d->configZoomedThumbnail = d->actionZoomedThumbnail->isChecked (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); cfg.writeEntry (kpSettingThumbnailZoomed, d->configZoomedThumbnail); cfg.sync (); updateThumbnailZoomed (); } // private slot void kpMainWindow::slotThumbnailShowRectangleToggled () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotThumbnailShowRectangleToggled()"; +#endif d->configThumbnailShowRectangle = d->actionShowThumbnailRectangle->isChecked (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); cfg.writeEntry (kpSettingThumbnailShowRectangle, d->configThumbnailShowRectangle); cfg.sync (); if (d->thumbnailView) { d->thumbnailView->showBuddyViewScrollableContainerRectangle ( d->actionShowThumbnailRectangle->isChecked ()); } } // private void kpMainWindow::enableViewZoomedThumbnail (bool enable) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::enableSettingsViewZoomedThumbnail()"; +#endif d->actionZoomedThumbnail->setEnabled (enable && d->actionShowThumbnail->isChecked ()); // Note: Don't uncheck if disabled - being able to see the zoomed state // before turning on the thumbnail can be useful. d->actionZoomedThumbnail->setChecked (d->configZoomedThumbnail); } // private void kpMainWindow::enableViewShowThumbnailRectangle (bool enable) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::enableViewShowThumbnailRectangle()"; +#endif d->actionShowThumbnailRectangle->setEnabled (enable && d->actionShowThumbnail->isChecked ()); // Note: Don't uncheck if disabled for consistency with // enableViewZoomedThumbnail() d->actionShowThumbnailRectangle->setChecked ( d->configThumbnailShowRectangle); } // private void kpMainWindow::enableThumbnailOptionActions (bool enable) { enableViewZoomedThumbnail (enable); enableViewShowThumbnailRectangle (enable); } // private void kpMainWindow::createThumbnailView () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tcreating new kpView:"; +#endif if (d->thumbnailView) { qCDebug(kpLogMainWindow) << "kpMainWindow::createThumbnailView() had to destroy view"; destroyThumbnailView (); } if (d->actionZoomedThumbnail->isChecked ()) { d->thumbnailView = new kpZoomedThumbnailView ( d->document, d->toolToolBar, d->viewManager, d->mainView, nullptr/*scrollableContainer*/, d->thumbnail); d->thumbnailView->setObjectName ( QLatin1String("thumbnailView" )); } else { d->thumbnailView = new kpUnzoomedThumbnailView ( d->document, d->toolToolBar, d->viewManager, d->mainView, nullptr/*scrollableContainer*/, d->thumbnail); d->thumbnailView->setObjectName ( QLatin1String("thumbnailView" )); } d->thumbnailView->showBuddyViewScrollableContainerRectangle ( d->actionShowThumbnailRectangle->isChecked ()); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tgive kpThumbnail the kpView:"; +#endif + if (d->thumbnail) { d->thumbnail->setView (d->thumbnailView); } else { qCCritical(kpLogMainWindow) << "kpMainWindow::createThumbnailView() no thumbnail"; } +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tregistering the kpView:"; +#endif if (d->viewManager) { d->viewManager->registerView (d->thumbnailView); } } // private void kpMainWindow::destroyThumbnailView () { if (!d->thumbnailView) { return; } if (d->viewManager) { d->viewManager->unregisterView (d->thumbnailView); } if (d->thumbnail) { d->thumbnail->setView (nullptr); } d->thumbnailView->deleteLater (); d->thumbnailView = nullptr; } // private void kpMainWindow::updateThumbnail () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::updateThumbnail()"; +#endif bool enable = d->actionShowThumbnail->isChecked (); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tthumbnail=" << bool (d->thumbnail) << " action_isChecked=" << enable; +#endif if (bool (d->thumbnail) == enable) { return; } if (!d->thumbnail) { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcreating thumbnail"; + #endif // Read last saved geometry before creating thumbnail & friends // in case they call notifyThumbnailGeometryChanged() QRect thumbnailGeometry = d->configThumbnailGeometry; + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tlast used geometry=" << thumbnailGeometry; + #endif d->thumbnail = new kpThumbnail (this); createThumbnailView (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tmoving thumbnail to right place"; + #endif if (!thumbnailGeometry.isEmpty () && QRect (0, 0, width (), height ()).intersects (thumbnailGeometry)) { const QRect geometry = mapToGlobal (thumbnailGeometry); d->thumbnail->resize (geometry.size ()); d->thumbnail->move (geometry.topLeft ()); } else { if (d->scrollView) { const int margin = 20; const int initialWidth = 160, initialHeight = 120; QRect geometryRect (width () - initialWidth - margin * 2, d->scrollView->y () + margin, initialWidth, initialHeight); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tcreating geometry=" << geometryRect; + #endif geometryRect = mapToGlobal (geometryRect); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tmap to global=" << geometryRect; + #endif d->thumbnail->resize (geometryRect.size ()); d->thumbnail->move (geometryRect.topLeft ()); } } + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tshowing thumbnail"; + #endif d->thumbnail->show (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tconnecting signal thumbnail::windowClosed to destroy slot"; + #endif connect (d->thumbnail, &kpThumbnail::windowClosed, this, &kpMainWindow::slotDestroyThumbnailInitatedByUser); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tDONE"; + #endif } else { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdestroying thumbnail d->thumbnail=" << d->thumbnail; + #endif if (d->thumbnailSaveConfigTimer && d->thumbnailSaveConfigTimer->isActive ()) { d->thumbnailSaveConfigTimer->stop (); slotSaveThumbnailGeometry (); } // Must be done before hiding the thumbnail to avoid triggering // this signal - re-entering this code. disconnect (d->thumbnail, &kpThumbnail::windowClosed, this, &kpMainWindow::slotDestroyThumbnailInitatedByUser); // Avoid change/flicker of caption due to view delete // (destroyThumbnailView()) d->thumbnail->hide (); destroyThumbnailView (); d->thumbnail->deleteLater (); d->thumbnail = nullptr; } } diff --git a/mainWindow/kpMainWindow_View_Zoom.cpp b/mainWindow/kpMainWindow_View_Zoom.cpp index bd86b3fc..410d409b 100644 --- a/mainWindow/kpMainWindow_View_Zoom.cpp +++ b/mainWindow/kpMainWindow_View_Zoom.cpp @@ -1,640 +1,693 @@ // REFACTOR: Clean up bits of this file /* Copyright (c) 2003-2007 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 "mainWindow/kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "kpLogCategories.h" #include #include #include #include #include #include #include "kpDefs.h" #include "document/kpDocument.h" #include "kpThumbnail.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/kpUnzoomedThumbnailView.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include "generic/kpWidgetMapper.h" #include "views/kpZoomedView.h" #include "views/kpZoomedThumbnailView.h" static int ZoomLevelFromString (const QString &stringIn) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow_View.cpp:ZoomLevelFromString(" << stringIn << ")"; +#endif // Remove any non-digits kdelibs sometimes adds behind our back :( e.g.: // // 1. kdelibs adds accelerators to actions' text directly // 2. ',' is automatically added to change "1000%" to "1,000%" QString string = stringIn; string.remove (QRegExp ("[^0-9]")); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\twithout non-digits='" << string << "'"; +#endif // Convert zoom level to number. bool ok = false; int zoomLevel = string.toInt (&ok); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tzoomLevel=" << zoomLevel; +#endif if (!ok || zoomLevel < kpView::MinZoomLevel || zoomLevel > kpView::MaxZoomLevel) { return 0; // error } return zoomLevel; } //--------------------------------------------------------------------- static QString ZoomLevelToString (int zoomLevel) { return i18n ("%1%", zoomLevel); } //--------------------------------------------------------------------- // private void kpMainWindow::setupViewMenuZoomActions () { KActionCollection *ac = actionCollection (); d->actionActualSize = KStandardAction::actualSize (this, SLOT (slotActualSize()), ac); d->actionFitToPage = KStandardAction::fitToPage (this, SLOT (slotFitToPage()), ac); d->actionFitToWidth = KStandardAction::fitToWidth (this, SLOT (slotFitToWidth()), ac); d->actionFitToHeight = KStandardAction::fitToHeight (this, SLOT (slotFitToHeight()), ac); d->actionZoomIn = KStandardAction::zoomIn (this, SLOT (slotZoomIn()), ac); d->actionZoomOut = KStandardAction::zoomOut (this, SLOT (slotZoomOut()), ac); d->actionZoom = ac->add ("view_zoom_to"); d->actionZoom->setText (i18n ("&Zoom")); connect (d->actionZoom, static_cast(&KSelectAction::triggered), this, &kpMainWindow::slotZoom); d->actionZoom->setEditable (true); // create the zoom list for the 1st call to zoomTo() below d->zoomList.append (10); d->zoomList.append (25); d->zoomList.append (33); d->zoomList.append (50); d->zoomList.append (67); d->zoomList.append (75); d->zoomList.append (100); d->zoomList.append (200); d->zoomList.append (300); d->zoomList.append (400); d->zoomList.append (600); d->zoomList.append (800); d->zoomList.append (1000); d->zoomList.append (1200); d->zoomList.append (1600); } //--------------------------------------------------------------------- // private void kpMainWindow::enableViewMenuZoomDocumentActions (bool enable) { d->actionActualSize->setEnabled (enable); d->actionFitToPage->setEnabled (enable); d->actionFitToWidth->setEnabled (enable); d->actionFitToHeight->setEnabled (enable); d->actionZoomIn->setEnabled (enable); d->actionZoomOut->setEnabled (enable); d->actionZoom->setEnabled (enable); // TODO: for the time being, assume that we start at zoom 100% // with no grid // This function is only called when a new document is created // or an existing document is closed. So the following will // always be correct: zoomTo (100); } //--------------------------------------------------------------------- // private void kpMainWindow::sendZoomListToActionZoom () { QStringList items; const QList ::ConstIterator zoomListEnd (d->zoomList.end ()); for (QList ::ConstIterator it = d->zoomList.constBegin (); it != zoomListEnd; ++it) { items << ::ZoomLevelToString (*it); } // Work around a KDE bug - KSelectAction::setItems() enables the action. // David Faure said it won't be fixed because it's a feature used by // KRecentFilesAction. bool e = d->actionZoom->isEnabled (); d->actionZoom->setItems (items); if (e != d->actionZoom->isEnabled ()) { d->actionZoom->setEnabled (e); } } //--------------------------------------------------------------------- // private void kpMainWindow::zoomToPre (int zoomLevel) { // We're called quite early in the init process and/or when there might // not be a document or a view so we have a lot of "if (ptr)" guards. +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToPre(" << zoomLevel << ")"; +#endif zoomLevel = qBound (kpView::MinZoomLevel, zoomLevel, kpView::MaxZoomLevel); int index = 0; QList ::Iterator it = d->zoomList.begin (); while (index < d->zoomList.count () && zoomLevel > *it) { it++; index++; } if (zoomLevel != *it) { d->zoomList.insert (it, zoomLevel); } // OPT: We get called twice on startup. sendZoomListToActionZoom() is very slow. sendZoomListToActionZoom (); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tsetCurrentItem(" << index << ")"; +#endif d->actionZoom->setCurrentItem (index); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcurrentItem=" << d->actionZoom->currentItem () << " action=" << d->actionZoom->action (d->actionZoom->currentItem ()) << " checkedAction" << d->actionZoom->selectableActionGroup ()->checkedAction (); +#endif if (viewMenuDocumentActionsEnabled ()) { d->actionActualSize->setEnabled (zoomLevel != 100); d->actionZoomIn->setEnabled (d->actionZoom->currentItem () < d->zoomList.count () - 1); d->actionZoomOut->setEnabled (d->actionZoom->currentItem () > 0); } // TODO: Is this actually needed? if (d->viewManager) { d->viewManager->setQueueUpdates (); } if (d->scrollView) { d->scrollView->setUpdatesEnabled (false); } } //--------------------------------------------------------------------- // private void kpMainWindow::zoomToPost () { +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToPost()"; +#endif if (d->mainView) { actionShowGridUpdate (); updateMainViewGrid (); // Since Zoom Level KSelectAction on ToolBar grabs focus after changing // Zoom, switch back to the Main View. // TODO: back to the last view d->mainView->setFocus (); } // The view magnified and moved beneath the cursor if (tool ()) { tool ()->somethingBelowTheCursorChanged (); } if (d->scrollView) { // TODO: setUpdatesEnabled() should really return to old value // - not necessarily "true" d->scrollView->setUpdatesEnabled (true); } if (d->viewManager && d->viewManager->queueUpdates ()/*just in case*/) { d->viewManager->restoreQueueUpdates (); } setStatusBarZoom (d->mainView ? d->mainView->zoomLevelX () : 0); +#if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToPost() done"; +#endif } //--------------------------------------------------------------------- // private void kpMainWindow::zoomTo (int zoomLevel, bool centerUnderCursor) { zoomToPre (zoomLevel); if (d->scrollView && d->mainView) { + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\tscrollView contentsX=" << d->scrollView->horizontalScrollBar()->value () << " contentsY=" << d->scrollView->verticalScrollBar()->value () << " contentsWidth=" << d->scrollView->widget()->width () << " contentsHeight=" << d->scrollView->widget()->height () << " visibleWidth=" << d->scrollView->viewport()->width () << " visibleHeight=" << d->scrollView->viewport()->height () << " oldZoomX=" << d->mainView->zoomLevelX () << " oldZoomY=" << d->mainView->zoomLevelY () << " newZoom=" << zoomLevel; + #endif // TODO: when changing from no scrollbars to scrollbars, Qt lies about // visibleWidth() & visibleHeight() (doesn't take into account the // space taken by the would-be scrollbars) until it updates the // scrollview; hence the centering is off by about 5-10 pixels. // TODO: Use visibleRect() for greater accuracy? // Or use kpAbstractScrollAreaUtils::EstimateUsableArea() // instead of ScrollView::visible{Width,Height}(), as // per zoomToRect()? int viewX, viewY; bool targetDocAvail = false; double targetDocX = -1, targetDocY = -1; if (centerUnderCursor && d->viewManager && d->viewManager->viewUnderCursor ()) { kpView *const vuc = d->viewManager->viewUnderCursor (); QPoint viewPoint = vuc->mouseViewPoint (); // vuc->transformViewToDoc() returns QPoint which only has int // accuracy so we do X and Y manually. targetDocX = vuc->transformViewToDocX (viewPoint.x ()); targetDocY = vuc->transformViewToDocY (viewPoint.y ()); targetDocAvail = true; if (vuc != d->mainView) { viewPoint = vuc->transformViewToOtherView (viewPoint, d->mainView); } viewX = viewPoint.x (); viewY = viewPoint.y (); } else { viewX = d->scrollView->horizontalScrollBar()->value () + qMin (d->mainView->width (), d->scrollView->viewport()->width ()) / 2; viewY = d->scrollView->verticalScrollBar()->value () + qMin (d->mainView->height (), d->scrollView->viewport()->height ()) / 2; } int newCenterX = viewX * zoomLevel / d->mainView->zoomLevelX (); int newCenterY = viewY * zoomLevel / d->mainView->zoomLevelY (); // Do the zoom. d->mainView->setZoomLevel (zoomLevel, zoomLevel); + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\tvisibleWidth=" << d->scrollView->viewport()->width () << " visibleHeight=" << d->scrollView->viewport()->height (); qCDebug(kpLogMainWindow) << "\tnewCenterX=" << newCenterX << " newCenterY=" << newCenterY; + #endif d->scrollView->horizontalScrollBar()->setValue(newCenterX - (d->scrollView->viewport()->width() / 2)); d->scrollView->verticalScrollBar()->setValue(newCenterY - (d->scrollView->viewport()->height() / 2)); if (centerUnderCursor && targetDocAvail && d->viewManager && d->viewManager->viewUnderCursor ()) { // Move the mouse cursor so that it is still above the same // document pixel as before the zoom. kpView *const vuc = d->viewManager->viewUnderCursor (); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcenterUnderCursor: reposition cursor; viewUnderCursor=" << vuc->objectName (); + #endif const auto viewX = vuc->transformDocToViewX (targetDocX); const auto viewY = vuc->transformDocToViewY (targetDocY); // Rounding error from zooming in and out :( // TODO: do everything in terms of tool doc points in type "double". const QPoint viewPoint (static_cast (viewX), static_cast (viewY)); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tdoc: (" << targetDocX << "," << targetDocY << ")" << " viewUnderCursor: (" << viewX << "," << viewY << ")"; + #endif if (vuc->visibleRegion ().contains (viewPoint)) { const QPoint globalPoint = kpWidgetMapper::toGlobal (vuc, viewPoint); + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tglobalPoint=" << globalPoint; + #endif // TODO: Determine some sane cursor flashing indication - // cursor movement is convenient but not conventional. // // Major problem: if using QApplication::setOverrideCursor() // and in some stage of flash and window quits. // // Or if using kpView::setCursor() and change tool. QCursor::setPos (globalPoint); } // e.g. Zoom to 200%, scroll mainView to bottom-right. // Unzoomed Thumbnail shows top-left portion of bottom-right of // mainView. // // Aim cursor at bottom-right of thumbnail and zoom out with // CTRL+Wheel. // // If mainView is now small enough to largely not need scrollbars, // Unzoomed Thumbnail scrolls to show _top-left_ portion // _of top-left_ of mainView. // // Unzoomed Thumbnail no longer contains the point we zoomed out // on top of. else { + #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\twon't move cursor - would get outside view"; + #endif // TODO: Sane cursor flashing indication that indicates // that the normal cursor movement didn't happen. } } + #if DEBUG_KP_MAIN_WINDOW && 1 qCDebug(kpLogMainWindow) << "\t\tcheck (contentsX=" << d->scrollView->horizontalScrollBar()->value () << ",contentsY=" << d->scrollView->verticalScrollBar()->value () << ")"; + #endif } zoomToPost (); } //--------------------------------------------------------------------- // private void kpMainWindow::zoomToRect (const QRect &normalizedDocRect, bool accountForGrips, bool careAboutWidth, bool careAboutHeight) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToRect(normalizedDocRect=" << normalizedDocRect << ",accountForGrips=" << accountForGrips << ",careAboutWidth=" << careAboutWidth << ",careAboutHeight=" << careAboutHeight << ")"; +#endif // You can't care about nothing. Q_ASSERT (careAboutWidth || careAboutHeight); // The size of the scroll view minus the current or future scrollbars. const QSize usableScrollArea (d->scrollView->maximumViewportSize().width() - d->scrollView->verticalScrollBar()->sizeHint().width(), d->scrollView->maximumViewportSize().height() - d->scrollView->horizontalScrollBar()->sizeHint().height()); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "size=" << d->scrollView->maximumViewportSize() << "scrollbar w=" << d->scrollView->verticalScrollBar()->sizeHint().width() << "usableSize=" << usableScrollArea; +#endif // Handle rounding error, mis-estimating the scroll view size and // cosmic rays. We do this because we really don't want unnecessary // scrollbars. This seems to need to be at least 2 for slotFitToWidth() // and friends. // least 2. // TODO: I might have fixed this but check later. const int slack = 0; // The grip and slack are in view coordinates but are never zoomed. const int viewWidth = usableScrollArea.width () - (accountForGrips ? kpGrip::Size : 0) - slack; const int viewHeight = usableScrollArea.height () - (accountForGrips ? kpGrip::Size : 0) - slack; // We want the selected document rectangle to fill the scroll view. // // The integer arithmetic rounds down, rather than to the nearest zoom // level, as rounding down guarantees that the view, at the zoom level, // will fit inside x . const int zoomX = careAboutWidth ? qMax (1, viewWidth * 100 / normalizedDocRect.width ()) : INT_MAX; const int zoomY = careAboutHeight ? qMax (1, viewHeight * 100 / normalizedDocRect.height ()) : INT_MAX; // Since kpView only supports identical horizontal and vertical zooms, // choose the one that will show the greatest amount of document // content. const int zoomLevel = qMin (zoomX, zoomY); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tzoomX=" << zoomX << " zoomY=" << zoomY << " -> zoomLevel=" << zoomLevel; + << " -> zoomLevel=" << zoomLevel << endl; +#endif zoomToPre (zoomLevel); { d->mainView->setZoomLevel (zoomLevel, zoomLevel); const QPoint viewPoint = d->mainView->transformDocToView (normalizedDocRect.topLeft ()); d->scrollView->horizontalScrollBar()->setValue(viewPoint.x()); d->scrollView->verticalScrollBar()->setValue(viewPoint.y()); } zoomToPost (); } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotActualSize () { zoomTo (100); } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotFitToPage () { if ( d->document ) { zoomToRect ( d->document->rect (), true/*account for grips*/, true/*care about width*/, true/*care about height*/); } } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotFitToWidth () { if ( d->document ) { const QRect docRect ( 0/*x*/, static_cast (d->mainView->transformViewToDocY (d->scrollView->verticalScrollBar()->value ()))/*maintain y*/, d->document->width (), 1/*don't care about height*/); zoomToRect ( docRect, true/*account for grips*/, true/*care about width*/, false/*don't care about height*/); } } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotFitToHeight () { if ( d->document ) { const QRect docRect ( static_cast (d->mainView->transformViewToDocX (d->scrollView->horizontalScrollBar()->value ()))/*maintain x*/, 0/*y*/, 1/*don't care about width*/, d->document->height ()); zoomToRect ( docRect, true/*account for grips*/, false/*don't care about width*/, true/*care about height*/); } } //--------------------------------------------------------------------- // public void kpMainWindow::zoomIn (bool centerUnderCursor) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::zoomIn(centerUnderCursor=" << centerUnderCursor << ") currentItem=" << d->actionZoom->currentItem (); +#endif const int targetItem = d->actionZoom->currentItem () + 1; if (targetItem >= static_cast (d->zoomList.count ())) { return; } d->actionZoom->setCurrentItem (targetItem); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tnew currentItem=" << d->actionZoom->currentItem (); +#endif zoomAccordingToZoomAction (centerUnderCursor); } //--------------------------------------------------------------------- // public void kpMainWindow::zoomOut (bool centerUnderCursor) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::zoomOut(centerUnderCursor=" << centerUnderCursor << ") currentItem=" << d->actionZoom->currentItem (); +#endif const int targetItem = d->actionZoom->currentItem () - 1; if (targetItem < 0) { return; } d->actionZoom->setCurrentItem (targetItem); +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tnew currentItem=" << d->actionZoom->currentItem (); +#endif zoomAccordingToZoomAction (centerUnderCursor); } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotZoomIn () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoomIn ()"; +#endif zoomIn (false/*don't center under cursor*/); } //--------------------------------------------------------------------- // public slot void kpMainWindow::slotZoomOut () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoomOut ()"; +#endif zoomOut (false/*don't center under cursor*/); } //--------------------------------------------------------------------- // public void kpMainWindow::zoomAccordingToZoomAction (bool centerUnderCursor) { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::zoomAccordingToZoomAction(centerUnderCursor=" << centerUnderCursor << ") currentItem=" << d->actionZoom->currentItem () << " currentText=" << d->actionZoom->currentText (); +#endif // This might be a new zoom level the user has typed in. zoomTo (::ZoomLevelFromString (d->actionZoom->currentText ()), centerUnderCursor); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotZoom () { +#if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoom () index=" << d->actionZoom->currentItem () << " text='" << d->actionZoom->currentText () << "'"; +#endif zoomAccordingToZoomAction (false/*don't center under cursor*/); } //--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_DrawShapes.cpp b/pixmapfx/kpPixmapFX_DrawShapes.cpp index 07e6c6f8..6e164e10 100644 --- a/pixmapfx/kpPixmapFX_DrawShapes.cpp +++ b/pixmapfx/kpPixmapFX_DrawShapes.cpp @@ -1,281 +1,285 @@ /* Copyright (c) 2003-2007 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_PIXMAP_FX 0 #include "kpPixmapFX.h" #include #include #include #include #include #include "kpLogCategories.h" #include "layers/selections/kpAbstractSelection.h" #include "imagelib/kpColor.h" #include "kpDefs.h" //--------------------------------------------------------------------- // Returns whether there is only 1 distinct point in . bool kpPixmapFX::Only1PixelInPointArray (const QPolygon &points) { if (points.count () == 0) { return false; } for (int i = 1; i < static_cast (points.count ()); i++) { if (points [i] != points [0]) { return false; } } return true; } //--------------------------------------------------------------------- // Warp the given from 1 to 0. // This is not always done (specifically if ) because // width 0 sometimes looks worse. // // Qt lines of width 1 look like they have a width between 1-2 i.e.: // // # // ## // # // # // // compared to Qt's special "width 0" which just means a "proper" width 1: // // # // # // # // # // static int WidthToQPenWidth (int width, bool drawingEllipse = false) { if (width == 1) { // 3x10 ellipse with Qt width 0 looks like rectangle. // Therefore, do not apply this 1 -> 0 transformations for ellipses. if (!drawingEllipse) { // Closer to looking width 1, for lines at least. return 0; } } return width; } //--------------------------------------------------------------------- static void QPainterSetPenWithStipple (QPainter *p, const kpColor &fColor, int penWidth, const kpColor &fStippleColor = kpColor::Invalid, bool isEllipseLike = false) { if (!fStippleColor.isValid ()) { p->setPen ( kpPixmapFX::QPainterDrawLinePen ( fColor.toQColor(), ::WidthToQPenWidth (penWidth, isEllipseLike))); } else { QPen usePen = kpPixmapFX::QPainterDrawLinePen ( fColor.toQColor(), ::WidthToQPenWidth (penWidth, isEllipseLike)); usePen.setStyle (Qt::DashLine); p->setPen (usePen); p->setBackground (fStippleColor.toQColor()); p->setBackgroundMode (Qt::OpaqueMode); } } //--------------------------------------------------------------------- // public static QPen kpPixmapFX::QPainterDrawRectPen (const QColor &color, int qtWidth) { return QPen (color, qtWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); } //--------------------------------------------------------------------- // public static QPen kpPixmapFX::QPainterDrawLinePen (const QColor &color, int qtWidth) { return QPen (color, qtWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); } //--------------------------------------------------------------------- // // drawPolyline() / drawLine() // // public static void kpPixmapFX::drawPolyline (QImage *image, const QPolygon &points, const kpColor &color, int penWidth, const kpColor &stippleColor) { QPainter painter(image); ::QPainterSetPenWithStipple(&painter, color, penWidth, stippleColor); // Qt bug: single point doesn't show up depending on penWidth. if (Only1PixelInPointArray(points)) { + #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\tinvoking single point hack"; + #endif painter.drawPoint(points[0]); return; } painter.drawPolyline(points); } //--------------------------------------------------------------------- // // drawPolygon() // // public static void kpPixmapFX::drawPolygon (QImage *image, const QPolygon &points, const kpColor &fcolor, int penWidth, const kpColor &bcolor, bool isFinal, const kpColor &fStippleColor) { QPainter p(image); ::QPainterSetPenWithStipple (&p, fcolor, penWidth, fStippleColor); if (bcolor.isValid ()) { p.setBrush (QBrush (bcolor.toQColor())); } // HACK: seems to be needed if set_Pen_(Qt::color0) else fills with Qt::color0. else { p.setBrush (Qt::NoBrush); } // Qt bug: single point doesn't show up depending on penWidth. if (Only1PixelInPointArray (points)) { + #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\tinvoking single point hack"; + #endif p.drawPoint(points [0]); return; } // TODO: why aren't the ends rounded? p.drawPolygon(points, Qt::OddEvenFill); if ( isFinal ) { return; } if ( points.count() <= 2 ) { return; } p.setCompositionMode(QPainter::RasterOp_SourceXorDestination); p.setPen(QPen(Qt::white)); p.drawLine(points[0], points[points.count() - 1]); } //--------------------------------------------------------------------- // public static void kpPixmapFX::fillRect (QImage *image, int x, int y, int width, int height, const kpColor &color, const kpColor &stippleColor) { QPainter painter(image); if (!stippleColor.isValid ()) { painter.fillRect (x, y, width, height, color.toQColor()); } else { const int StippleSize = 4; painter.setClipRect (x, y, width, height); for (int dy = 0; dy < height; dy += StippleSize) { for (int dx = 0; dx < width; dx += StippleSize) { const bool parity = ((dy + dx) / StippleSize) % 2; kpColor useColor; if (!parity) { useColor = color; } else { useColor = stippleColor; } painter.fillRect (x + dx, y + dy, StippleSize, StippleSize, useColor.toQColor()); } } } } //--------------------------------------------------------------------- void kpPixmapFX::drawStippleRect(QImage *image, int x, int y, int width, int height, const kpColor &fColor, const kpColor &fStippleColor) { QPainter painter(image); painter.setPen(QPen(fColor.toQColor(), 1, Qt::DashLine)); painter.setBackground(fStippleColor.toQColor()); painter.setBackgroundMode(Qt::OpaqueMode); painter.drawRect(x, y, width - 1, height - 1); } //--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp b/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp index a0203275..2c28747e 100644 --- a/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp +++ b/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp @@ -1,140 +1,142 @@ /* Copyright (c) 2003-2007 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_PIXMAP_FX 0 #include "kpPixmapFX.h" #include #include #include #include #include "kpLogCategories.h" #include "imagelib/kpColor.h" //--------------------------------------------------------------------- // public static QImage kpPixmapFX::getPixmapAt (const QImage &image, const QRect &rect) { return image.copy(rect); } //--------------------------------------------------------------------- // public static void kpPixmapFX::setPixmapAt(QImage *destPtr, const QRect &destRect, const QImage &src) { +#if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kpPixmapFX::setPixmapAt(destPixmap->rect=" << destPtr->rect () << ",destRect=" << destRect << ",src.rect=" << src.rect () << ")"; +#endif Q_ASSERT (destPtr); // You cannot copy more than what you have. Q_ASSERT (destRect.width () <= src.width () && destRect.height () <= src.height ()); QPainter painter(destPtr); // destination shall be source only painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(destRect.topLeft(), src, QRect(0, 0, destRect.width(), destRect.height())); } //--------------------------------------------------------------------- // public static void kpPixmapFX::setPixmapAt (QImage *destPtr, const QPoint &destAt, const QImage &src) { kpPixmapFX::setPixmapAt (destPtr, QRect (destAt.x (), destAt.y (), src.width (), src.height ()), src); } //--------------------------------------------------------------------- // public static void kpPixmapFX::setPixmapAt (QImage *destPtr, int destX, int destY, const QImage &src) { kpPixmapFX::setPixmapAt (destPtr, QPoint (destX, destY), src); } //--------------------------------------------------------------------- // public static void kpPixmapFX::paintPixmapAt (QImage *destPtr, const QPoint &destAt, const QImage &src) { // draw image with SourceOver composition mode QPainter painter(destPtr); painter.drawImage(destAt, src); } //--------------------------------------------------------------------- // public static void kpPixmapFX::paintPixmapAt (QImage *destPtr, int destX, int destY, const QImage &src) { kpPixmapFX::paintPixmapAt(destPtr, QPoint (destX, destY), src); } //--------------------------------------------------------------------- // public static kpColor kpPixmapFX::getColorAtPixel (const QImage &img, const QPoint &at) { if (!img.valid (at.x (), at.y ())) { return kpColor::Invalid; } QRgb rgba = img.pixel(at); return kpColor (rgba); } //--------------------------------------------------------------------- // public static kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y) { return kpPixmapFX::getColorAtPixel (img, QPoint (x, y)); } //--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_Transforms.cpp b/pixmapfx/kpPixmapFX_Transforms.cpp index 3d1260ae..a761a1c3 100644 --- a/pixmapfx/kpPixmapFX_Transforms.cpp +++ b/pixmapfx/kpPixmapFX_Transforms.cpp @@ -1,612 +1,669 @@ /* Copyright (c) 2003-2007 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_PIXMAP_FX 0 #include "kpPixmapFX.h" #include #include #include #include #include #include "kpLogCategories.h" #include "layers/selections/kpAbstractSelection.h" #include "imagelib/kpColor.h" #include "kpDefs.h" //--------------------------------------------------------------------- // public static void kpPixmapFX::resize (QImage *destPtr, int w, int h, const kpColor &backgroundColor) { +#if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kpPixmapFX::resize()"; +#endif if (!destPtr) { return; } const int oldWidth = destPtr->width (); const int oldHeight = destPtr->height (); if (w == oldWidth && h == oldHeight) { return; } QImage newImage (w, h, QImage::Format_ARGB32_Premultiplied); // Would have new undefined areas? if (w > oldWidth || h > oldHeight) { newImage.fill (backgroundColor.toQRgb ()); } // Copy over old pixmap. QPainter painter(&newImage); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(0, 0, *destPtr); painter.end(); // Replace pixmap with new one. *destPtr = newImage; } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::resize (const QImage &image, int w, int h, const kpColor &backgroundColor) { QImage ret = image; kpPixmapFX::resize (&ret, w, h, backgroundColor); return ret; } //--------------------------------------------------------------------- // public static void kpPixmapFX::scale (QImage *destPtr, int w, int h, bool pretty) { if (!destPtr) { return; } *destPtr = kpPixmapFX::scale (*destPtr, w, h, pretty); } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::scale (const QImage &image, int w, int h, bool pretty) { +#if DEBUG_KP_PIXMAP_FX && 0 qCDebug(kpLogPixmapfx) << "kpPixmapFX::scale(oldRect=" << image.rect () << ",w=" << w << ",h=" << h << ",pretty=" << pretty << ")"; +#endif if (w == image.width () && h == image.height ()) { return image; } return image.scaled(w, h, Qt::IgnoreAspectRatio, pretty ? Qt::SmoothTransformation : Qt::FastTransformation); } //--------------------------------------------------------------------- // public static const double kpPixmapFX::AngleInDegreesEpsilon = qRadiansToDegrees (std::tan (1.0 / 10000.0)) / (2.0/*max error allowed*/ * 2.0/*for good measure*/); static void MatrixDebug (const QString& matrixName, const QTransform &matrix, int srcPixmapWidth = -1, int srcPixmapHeight = -1) { +#if DEBUG_KP_PIXMAP_FX const int w = srcPixmapWidth, h = srcPixmapHeight; qCDebug(kpLogPixmapfx) << matrixName << "=" << matrix; // Sometimes this precision lets us see unexpected rounding errors. fprintf (stderr, "m11=%.24f m12=%.24f m21=%.24f m22=%.24f dx=%.24f dy=%.24f\n", matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (), matrix.dx (), matrix.dy ()); if (w > 0 && h > 0) { qCDebug(kpLogPixmapfx) << "(0,0) ->" << matrix.map (QPoint (0, 0)); qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << matrix.map (QPoint (w - 1, 0)); qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << matrix.map (QPoint (0, h - 1)); qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << matrix.map (QPoint (w - 1, h - 1)); } + +#else + + Q_UNUSED (matrixName); + Q_UNUSED (matrix); + Q_UNUSED (srcPixmapWidth); + Q_UNUSED (srcPixmapHeight); + +#endif // DEBUG_KP_PIXMAP_FX } //--------------------------------------------------------------------- // Theoretically, this should act the same as QPixmap::trueMatrix() but // it doesn't. As an example, if you rotate tests/transforms.png by 90 // degrees clockwise, this returns the correct of 26 but // QPixmap::trueMatrix() returns 27. // // You should use the returned matrix to map points accurately (e.g. selection // borders). For QPainter::drawPixmap()/drawImage() + setWorldMatrix() // rendering accuracy, pass the returned matrix through QPixmap::trueMatrix() // and use that. // // TODO: If you put the flipMatrix() of tests/transforms.png through this, // the output is the same as QPixmap::trueMatrix(): is one off // (dy=27 instead of 26). // SYNC: I bet this is a Qt4 bug. static QTransform MatrixWithZeroOrigin (const QTransform &matrix, int width, int height) { +#if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")"; qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << matrix.m11 () << "m12=" << matrix.m12 () << "m21=" << matrix.m21 () << "m22=" << matrix.m22 () << "dx=" << matrix.dx () << "dy=" << matrix.dy (); +#endif QRect mappedRect = matrix.mapRect (QRect (0, 0, width, height)); +#if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\tmappedRect=" << mappedRect; +#endif QTransform translatedMatrix ( matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (), matrix.dx () - mappedRect.left (), matrix.dy () - mappedRect.top ()); +#if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\treturning" << translatedMatrix; qCDebug(kpLogPixmapfx) << "(0,0) ->" << translatedMatrix.map (QPoint (0, 0)); qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << translatedMatrix.map (QPoint (width - 1, 0)); qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << translatedMatrix.map (QPoint (0, height - 1)); qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << translatedMatrix.map (QPoint (width - 1, height - 1)); +#endif return translatedMatrix; } //--------------------------------------------------------------------- static double TrueMatrixEpsilon = 0.000001; // An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix() // when skewing tests/transforms.png by 45% horizontally (with TransformPixmap() // using a QPixmap painter, prior to the 2007-10-09 change -- did not test after // the change). // Unfortunately, this does not work enough to stop the rendering errors // that follow. But it was worth a try and might still help us given the // sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when // QPainter::SmoothPixmapTransform is disabled. static double TrueMatrixFixInts (double x) { if (std::fabs (x - qRound (x)) < TrueMatrixEpsilon) { return qRound (x); } return x; } //--------------------------------------------------------------------- static QTransform TrueMatrix (const QTransform &matrix, int srcPixmapWidth, int srcPixmapHeight) { ::MatrixDebug ("TrueMatrix(): org", matrix); - + const QTransform truMat = QPixmap::trueMatrix (matrix, srcPixmapWidth, srcPixmapHeight); ::MatrixDebug ("TrueMatrix(): passed through QPixmap::trueMatrix()", truMat); const QTransform retMat ( ::TrueMatrixFixInts (truMat.m11 ()), ::TrueMatrixFixInts (truMat.m12 ()), ::TrueMatrixFixInts (truMat.m21 ()), ::TrueMatrixFixInts (truMat.m22 ()), ::TrueMatrixFixInts (truMat.dx ()), ::TrueMatrixFixInts (truMat.dy ())); ::MatrixDebug ("TrueMatrix(): fixed ints", retMat); return retMat; } //--------------------------------------------------------------------- // Like QPixmap::transformed() but fills new areas with // (unless is invalid) and works around internal QTransform // floating point -> integer oddities, that would otherwise give fatally // incorrect results. If you don't believe me on this latter point, compare // QPixmap::transformed() to us using a flip matrix or a rotate-by-multiple-of-90 // matrix on tests/transforms.png -- QPixmap::transformed()'s output is 1 // pixel too high or low depending on whether the matrix is passed through // QPixmap::trueMatrix(). // // Use and to specify the intended output size // of the pixmap. -1 if don't care. static QImage TransformPixmap (const QImage &pm, const QTransform &transformMatrix_, const kpColor &backgroundColor, int targetWidth, int targetHeight) { QTransform transformMatrix = transformMatrix_; +#if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () << ",targetWidth=" << targetWidth << ",targetHeight=" << targetHeight << ")"; +#endif QRect newRect = transformMatrix.mapRect (pm.rect ()); +#if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tmappedRect=" << newRect; +#endif QTransform scaleMatrix; if (targetWidth > 0 && targetWidth != newRect.width ()) { + #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tadjusting for targetWidth"; + #endif scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1); } if (targetHeight > 0 && targetHeight != newRect.height ()) { + #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tadjusting for targetHeight"; + #endif scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ())); } if (!scaleMatrix.isIdentity ()) { + #if DEBUG_KP_PIXMAP_FX && 1 // TODO: What is going on here??? Why isn't matrix * working properly? QTransform wrongMatrix = transformMatrix * scaleMatrix; QTransform oldHat = transformMatrix; if (targetWidth > 0 && targetWidth != newRect.width ()) { oldHat.scale (double (targetWidth) / double (newRect.width ()), 1); } if (targetHeight > 0 && targetHeight != newRect.height ()) { oldHat.scale (1, double (targetHeight) / double (newRect.height ())); } QTransform altHat = transformMatrix; altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1, (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1); QTransform correctMatrix = scaleMatrix * transformMatrix; qCDebug(kpLogPixmapfx) << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 () // <<<---- this is the correct matrix??? << " m12=" << wrongMatrix.m12 () << " m21=" << wrongMatrix.m21 () << " m22=" << wrongMatrix.m22 () << " dx=" << wrongMatrix.dx () << " dy=" << wrongMatrix.dy () << " rect=" << wrongMatrix.mapRect (pm.rect ()) << "\n" << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 () << " m12=" << oldHat.m12 () << " m21=" << oldHat.m21 () << " m22=" << oldHat.m22 () << " dx=" << oldHat.dx () << " dy=" << oldHat.dy () << " rect=" << oldHat.mapRect (pm.rect ()) << "\n" << "\tabove but scaled at the same time: m11=" << altHat.m11 () << " m12=" << altHat.m12 () << " m21=" << altHat.m21 () << " m22=" << altHat.m22 () << " dx=" << altHat.dx () << " dy=" << altHat.dy () << " rect=" << altHat.mapRect (pm.rect ()) << "\n" << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 () << " m12=" << correctMatrix.m12 () << " m21=" << correctMatrix.m21 () << " m22=" << correctMatrix.m22 () << " dx=" << correctMatrix.dx () << " dy=" << correctMatrix.dy () << " rect=" << correctMatrix.mapRect (pm.rect ()); + #endif transformMatrix = transformMatrix * scaleMatrix; newRect = transformMatrix.mapRect (pm.rect ()); + #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tnewRect after targetWidth,targetHeight adjust=" << newRect; + #endif } ::MatrixDebug ("TransformPixmap(): before trueMatrix", transformMatrix, pm.width (), pm.height ()); - QTransform oldMatrix = transformMatrix; +#if DEBUG_KP_PIXMAP_FX && 1 + QMatrix oldMatrix = transformMatrix; +#endif // Translate the matrix to account for Qt rounding errors, // so that flipping (if it used this method) and rotating by a multiple // of 90 degrees actually work as expected (try tests/transforms.png). // // SYNC: This was not required with Qt3 so we are actually working // around a Qt4 bug/feature. // // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway // but does this reduce quality even further? // // With or without it, skews by 45 degrees with the QImage // painter below look bad (with it, you get an extra transparent // line on the right; without, you get only about 1/4 of a source // line on the left). In Qt3, with TrueMatrix(), the source // image is translated 1 pixel off the destination image. // // Also, if you skew a rectangular selection, the skewed selection - // border does not line up with the skewed image data. + // border does not line up with the skewed image data. // TODO: do we need to pass through this new matrix? transformMatrix = ::TrueMatrix (transformMatrix, pm.width (), pm.height ()); +#if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "trueMatrix changed matrix?" << (oldMatrix == transformMatrix); +#endif ::MatrixDebug ("TransformPixmap(): after trueMatrix", transformMatrix, pm.width (), pm.height ()); QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (), targetHeight > 0 ? targetHeight : newRect.height (), QImage::Format_ARGB32_Premultiplied); if ((targetWidth > 0 && targetWidth != newRect.width ()) || (targetHeight > 0 && targetHeight != newRect.height ())) { + #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () << ",targetWidth=" << targetWidth << ",targetHeight=" << targetHeight << ") newRect=" << newRect << " (you are a victim of rounding error)"; + #endif } + +#if DEBUG_KP_PIXMAP_FX && 0 + qCDebug(kpLogPixmapfx) << "\ttranslate top=" << painter.xForm (QPoint (0, 0)); + qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << painter.worldMatrix ().m11 () + << " m12=" << painter.worldMatrix ().m12 () + << " m21=" << painter.worldMatrix ().m21 () + << " m22=" << painter.worldMatrix ().m22 () + << " dx=" << painter.worldMatrix ().dx () + << " dy=" << painter.worldMatrix ().dy () + << endl; +#endif + + // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);" // as the user does not want their image to get blurier every // time they e.g. rotate it (especially important for multiples // of 90 degrees but also true for every other angle). Being a // pixel-based program, we generally like to preserve RGB values // and avoid unnecessary blurs -- in the worst case, we'd rather // drop pixels, than blur. QPainter p (&newQImage); { // Make sure transparent pixels are drawn into the destination image. p.setCompositionMode (QPainter::CompositionMode_Source); - + // Fill the entire new image with the background color. if (backgroundColor.isValid ()) { p.fillRect (newQImage.rect (), backgroundColor.toQColor ()); } p.setWorldTransform (transformMatrix); p.drawImage (QPoint (0, 0), pm); } p.end (); +#if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "Done"; +#endif return newQImage; } //--------------------------------------------------------------------- // public static QTransform kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle) { if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) { return {}; } /* Diagram for completeness :) * * |---------- w ----------| * (0,0) * _ _______________________ (w,0) * | |\~_ va | * | | \ ~_ | * | |ha\ ~__ | * | \ ~__ | dy * h | \ ~___ | * | \ ~___ | * | | \ ~___| (w,w*tan(va)=dy) * | | \ * \ * _ |________\________|_____|\ vertical shear factor * (0,h) dx ^~_ | \ | * | ~_ \________\________ General Point (x,y) V * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va)) * (h*tan(ha)=dx,h) ~__ \ ^ * ~___ \ | * ~___ \ horizontal shear factor * Key: ~___\ * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy) * va = vangle * * Skewing really just twists a rectangle into a parallelogram. * */ //QTransform matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0); // I think this is clearer than above :) QTransform matrix; matrix.shear (std::tan (qDegreesToRadians (hangle)), std::tan (qDegreesToRadians (vangle))); return ::MatrixWithZeroOrigin (matrix, width, height); } //--------------------------------------------------------------------- // public static QTransform kpPixmapFX::skewMatrix (const QImage &pixmap, double hangle, double vangle) { return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle); } //--------------------------------------------------------------------- // public static void kpPixmapFX::skew (QImage *destPtr, double hangle, double vangle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { if (!destPtr) { return; } *destPtr = kpPixmapFX::skew (*destPtr, hangle, vangle, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::skew (const QImage &pm, double hangle, double vangle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { +#if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "kpPixmapFX::skew() pm.width=" << pm.width () << " pm.height=" << pm.height () << " hangle=" << hangle << " vangle=" << vangle << " targetWidth=" << targetWidth << " targetHeight=" << targetHeight; +#endif if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) { return pm; } if (std::fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || std::fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) { qCCritical(kpLogPixmapfx) << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)"; return pm; } QTransform matrix = skewMatrix (pm, hangle, vangle); return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- // public static QTransform kpPixmapFX::rotateMatrix (int width, int height, double angle) { if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) { return {}; } QTransform matrix; matrix.translate (width / 2, height / 2); matrix.rotate (angle); return ::MatrixWithZeroOrigin (matrix, width, height); } //--------------------------------------------------------------------- // public static QTransform kpPixmapFX::rotateMatrix (const QImage &pixmap, double angle) { return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle); } //--------------------------------------------------------------------- // public static bool kpPixmapFX::isLosslessRotation (double angle) { const double angleIn = angle; // Reflect angle into positive if negative if (angle < 0) { angle = -angle; } // Remove multiples of 90 to make sure 0 <= angle <= 90 angle -= (static_cast (angle)) / 90 * 90; // "Impossible" situation? if (angle < 0 || angle > 90) { qCCritical(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn << ") result=" << angle; return false; // better safe than sorry } const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon || 90 - angle < kpPixmapFX::AngleInDegreesEpsilon); +#if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn << ")" << " residual angle=" << angle << " returning " << ret; +#endif return ret; } //--------------------------------------------------------------------- // public static void kpPixmapFX::rotate (QImage *destPtr, double angle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { if (!destPtr) { return; } *destPtr = kpPixmapFX::rotate (*destPtr, angle, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::rotate (const QImage &pm, double angle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) { return pm; } QTransform matrix = rotateMatrix (pm, angle); return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- diff --git a/tools/flow/kpToolColorEraser.cpp b/tools/flow/kpToolColorEraser.cpp index aff0f955..f3c8f908 100644 --- a/tools/flow/kpToolColorEraser.cpp +++ b/tools/flow/kpToolColorEraser.cpp @@ -1,152 +1,160 @@ /* Copyright (c) 2003-2007 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_TOOL_COLOR_ERASER 0 #include "kpToolColorEraser.h" #include #include "kpLogCategories.h" #include #include "imagelib/kpColor.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "commands/kpMacroCommand.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "commands/tools/flow/kpToolFlowCommand.h" #include "environments/tools/kpToolEnvironment.h" //-------------------------------------------------------------------------------- kpToolColorEraser::kpToolColorEraser (kpToolEnvironment *environ, QObject *parent) : kpToolFlowBase (i18n ("Color Eraser"), i18n ("Replaces pixels of the foreground color with the background color"), Qt::Key_O, environ, parent, "tool_color_eraser") { } //-------------------------------------------------------------------------------- kpToolColorEraser::~kpToolColorEraser () = default; //-------------------------------------------------------------------------------- // public virtual [base kpTool] void kpToolColorEraser::globalDraw () { +#if DEBUG_KP_TOOL_COLOR_ERASER qCDebug(kpLogTools) << "kpToolColorEraser::globalDraw()"; +#endif if (!drawShouldProceed (QPoint ()/*unused*/, QPoint ()/*unused*/, QRect ()/*unused*/)) { return; } QApplication::setOverrideCursor (Qt::WaitCursor); environ ()->flashColorSimilarityToolBarItem (); kpToolFlowCommand *cmd = new kpToolFlowCommand ( i18n ("Color Eraser"), environ ()->commandEnvironment ()); const QRect dirtyRect = kpPainter::washRect (document ()->imagePointer (), 0, 0, document ()->width (), document ()->height (), backgroundColor ()/*color to draw in*/, foregroundColor ()/*color to replace*/, processedColorSimilarity ()); if (!dirtyRect.isEmpty ()) { document ()->slotContentsChanged (dirtyRect); cmd->updateBoundingRect (dirtyRect); cmd->finalize (); commandHistory ()->addCommand (cmd, false /* don't exec */); // don't delete - it's up to the commandHistory cmd = nullptr; } else { + #if DEBUG_KP_TOOL_COLOR_ERASER qCDebug(kpLogTools) << "\tisNOP"; + #endif delete cmd; cmd = nullptr; } QApplication::restoreOverrideCursor (); } //-------------------------------------------------------------------------------- QString kpToolColorEraser::haventBegunDrawUserMessage () const { return i18n ("Click or drag to erase pixels of the foreground color."); } //-------------------------------------------------------------------------------- bool kpToolColorEraser::drawShouldProceed (const QPoint & /*thisPoint*/, const QPoint & /*lastPoint*/, const QRect & /*normalizedRect*/) { return !(foregroundColor () == backgroundColor () && processedColorSimilarity () == 0); } //-------------------------------------------------------------------------------- QRect kpToolColorEraser::drawLine (const QPoint &thisPoint, const QPoint &lastPoint) { +#if DEBUG_KP_TOOL_COLOR_ERASER qCDebug(kpLogTools) << "kpToolColorEraser::drawLine(thisPoint=" << thisPoint << ",lastPoint=" << lastPoint << ")"; +#endif environ ()->flashColorSimilarityToolBarItem (); const QRect dirtyRect = kpPainter::washLine (document ()->imagePointer (), lastPoint.x (), lastPoint.y (), thisPoint.x (), thisPoint.y (), color (mouseButton ())/*color to draw in*/, brushWidth (), brushHeight (), color (1 - mouseButton ())/*color to replace*/, processedColorSimilarity ()); +#if DEBUG_KP_TOOL_COLOR_ERASER qCDebug(kpLogTools) << "\tdirtyRect=" << dirtyRect; +#endif if (!dirtyRect.isEmpty ()) { document ()->slotContentsChanged (dirtyRect); return dirtyRect; } return {}; } //-------------------------------------------------------------------------------- diff --git a/tools/flow/kpToolEraser.cpp b/tools/flow/kpToolEraser.cpp index 438d924c..f26c3420 100644 --- a/tools/flow/kpToolEraser.cpp +++ b/tools/flow/kpToolEraser.cpp @@ -1,78 +1,79 @@ /* Copyright (c) 2003-2007 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_TOOL_ERASER 0 #include "kpToolEraser.h" #include "commands/kpCommandHistory.h" #include "commands/imagelib/effects/kpEffectClearCommand.h" #include "environments/tools/kpToolEnvironment.h" -#include "kpLogCategories.h" #include //--------------------------------------------------------------------- kpToolEraser::kpToolEraser (kpToolEnvironment *environ, QObject *parent) : kpToolFlowPixmapBase (i18n ("Eraser"), i18n ("Lets you rub out mistakes"), Qt::Key_A, environ, parent, "tool_eraser") { } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolEraser::globalDraw () { +#if DEBUG_KP_TOOL_ERASER qCDebug(kpLogTools) << "kpToolEraser::globalDraw()"; +#endif commandHistory ()->addCommand ( new kpEffectClearCommand ( false/*act on doc, not sel*/, backgroundColor (), environ ()->commandEnvironment ())); } //--------------------------------------------------------------------- // protected virtual [base kpToolFlowBase] QString kpToolEraser::haventBegunDrawUserMessage () const { return i18n ("Click or drag to erase."); } //--------------------------------------------------------------------- // See the our corresponding .h for brush selection. // Logic is in kpToolFlowPixmapBase. diff --git a/tools/flow/kpToolFlowBase.cpp b/tools/flow/kpToolFlowBase.cpp index 4677a78d..1f4f4db1 100644 --- a/tools/flow/kpToolFlowBase.cpp +++ b/tools/flow/kpToolFlowBase.cpp @@ -1,476 +1,493 @@ /* Copyright (c) 2003-2007 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_TOOL_FLOW_BASE 0 #include "kpToolFlowBase.h" #include #include #include #include "kpLogCategories.h" #include #include "imagelib/kpColor.h" #include "commands/kpCommandHistory.h" #include "cursors/kpCursorProvider.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "imagelib/kpImage.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/tempImage/kpTempImage.h" #include "environments/tools/kpToolEnvironment.h" #include "commands/tools/flow/kpToolFlowCommand.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetBrush.h" #include "widgets/toolbars/options/kpToolWidgetEraserSize.h" #include "views/manager/kpViewManager.h" //--------------------------------------------------------------------- struct kpToolFlowBasePrivate { kpToolWidgetBrush *toolWidgetBrush{}; kpToolWidgetEraserSize *toolWidgetEraserSize{}; // // Cursor and Brush Data // (must be zero if unused) // kpTempImage::UserFunctionType brushDrawFunc{}, cursorDrawFunc{}; // Can't use union since package types contain fields requiring // constructors. kpToolWidgetBrush::DrawPackage brushDrawPackageForMouseButton [2]; kpToolWidgetEraserSize::DrawPackage eraserDrawPackageForMouseButton [2]; // Each element points to one of the above (both elements from the same // array). void *drawPackageForMouseButton [2]{}; int brushWidth{}, brushHeight{}; int cursorWidth{}, cursorHeight{}; bool brushIsDiagonalLine{}; kpToolFlowCommand *currentCommand{}; }; //--------------------------------------------------------------------- kpToolFlowBase::kpToolFlowBase (const QString &text, const QString &description, int key, kpToolEnvironment *environ, QObject *parent, const QString &name) : kpTool (text, description, key, environ, parent, name), d (new kpToolFlowBasePrivate ()) { d->toolWidgetBrush = nullptr; d->toolWidgetEraserSize = nullptr; clearBrushCursorData (); d->currentCommand = nullptr; } //--------------------------------------------------------------------- kpToolFlowBase::~kpToolFlowBase () { delete d; } //--------------------------------------------------------------------- // private void kpToolFlowBase::clearBrushCursorData () { d->brushDrawFunc = d->cursorDrawFunc = nullptr; memset (&d->brushDrawPackageForMouseButton, 0, sizeof (d->brushDrawPackageForMouseButton)); memset (&d->eraserDrawPackageForMouseButton, 0, sizeof (d->eraserDrawPackageForMouseButton)); memset (&d->drawPackageForMouseButton, 0, sizeof (d->drawPackageForMouseButton)); d->brushWidth = d->brushHeight = 0; d->cursorWidth = d->cursorHeight = 0; d->brushIsDiagonalLine = false; } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::begin () { kpToolToolBar *tb = toolToolBar (); Q_ASSERT (tb); // TODO: Bad smell. Mutually exclusive. Use inheritance. if (haveSquareBrushes ()) { d->toolWidgetEraserSize = tb->toolWidgetEraserSize (); connect (d->toolWidgetEraserSize, &kpToolWidgetEraserSize::eraserSizeChanged, this, &kpToolFlowBase::updateBrushAndCursor); d->toolWidgetEraserSize->show (); updateBrushAndCursor (); viewManager ()->setCursor (kpCursorProvider::lightCross ()); } else if (haveDiverseBrushes ()) { d->toolWidgetBrush = tb->toolWidgetBrush (); connect (d->toolWidgetBrush, &kpToolWidgetBrush::brushChanged, this, &kpToolFlowBase::updateBrushAndCursor); d->toolWidgetBrush->show (); updateBrushAndCursor (); viewManager ()->setCursor (kpCursorProvider::lightCross ()); } setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::end () { if (d->toolWidgetEraserSize) { disconnect (d->toolWidgetEraserSize, &kpToolWidgetEraserSize::eraserSizeChanged, this, &kpToolFlowBase::updateBrushAndCursor); d->toolWidgetEraserSize = nullptr; } else if (d->toolWidgetBrush) { disconnect (d->toolWidgetBrush, &kpToolWidgetBrush::brushChanged, this, &kpToolFlowBase::updateBrushAndCursor); d->toolWidgetBrush = nullptr; } kpViewManager *vm = viewManager (); Q_ASSERT (vm); if (vm->tempImage () && vm->tempImage ()->isBrush ()) { vm->invalidateTempImage (); } if (haveAnyBrushes ()) { vm->unsetCursor (); } clearBrushCursorData (); } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::beginDraw () { d->currentCommand = new kpToolFlowCommand (text (), environ ()->commandEnvironment ()); // We normally show the brush cursor in the foreground colour but if the // user starts drawing in the background color, we don't want to leave // the brush cursor in the foreground colour -- just hide it in all cases // to avoid confusion. viewManager ()->invalidateTempImage (); setUserMessage (cancelUserMessage ()); } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::hover (const QPoint &point) { +#if DEBUG_KP_TOOL_FLOW_BASE && 0 + qCDebug(kpLogTools) << "kpToolFlowBase::hover(" << point << ")" + << " hasBegun=" << hasBegun () + << " hasBegunDraw=" << hasBegunDraw () + << " cursorPixmap.isNull=" << m_cursorPixmap.isNull () + << endl; +#endif if (point != KP_INVALID_POINT && d->cursorDrawFunc) { viewManager ()->setFastUpdates (); viewManager ()->setTempImage ( kpTempImage (true/*brush*/, hotRect ().topLeft (), d->cursorDrawFunc, d->drawPackageForMouseButton [0/*left button*/], d->cursorWidth, d->cursorHeight)); viewManager ()->restoreFastUpdates (); } setUserShapePoints (point); } //--------------------------------------------------------------------- // virtual QRect kpToolFlowBase::drawPoint (const QPoint &point) { return drawLine (point, point); } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::draw (const QPoint &thisPoint, const QPoint &lastPoint, const QRect &normalizedRect) { if (!/*virtual*/drawShouldProceed (thisPoint, lastPoint, normalizedRect)) { return; } // sync: remember to restoreFastUpdates() in all exit paths viewManager ()->setFastUpdates (); QRect dirtyRect; // TODO: I'm beginning to wonder this drawPoint() "optimization" actually // optimises. Is it worth the complexity? Hence drawPoint() impl above. if (d->brushIsDiagonalLine ? currentPointCardinallyNextToLast () : currentPointNextToLast ()) { dirtyRect = drawPoint (thisPoint); } // in reality, the system is too slow to give us all the MouseMove events // so we "interpolate" the missing points :) else { dirtyRect = drawLine (thisPoint, lastPoint); } d->currentCommand->updateBoundingRect (dirtyRect); viewManager ()->restoreFastUpdates (); setUserShapePoints (thisPoint); } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::cancelShape () { d->currentCommand->finalize (); d->currentCommand->cancel (); delete d->currentCommand; d->currentCommand = nullptr; updateBrushAndCursor (); setUserMessage (i18n ("Let go of all the mouse buttons.")); } //--------------------------------------------------------------------- void kpToolFlowBase::releasedAllButtons () { setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- // virtual void kpToolFlowBase::endDraw (const QPoint &, const QRect &) { d->currentCommand->finalize (); environ ()->commandHistory ()->addCommand (d->currentCommand, false/*don't exec*/); // don't delete - it's up to the commandHistory d->currentCommand = nullptr; updateBrushAndCursor (); setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- // TODO: maybe the base should be virtual? kpColor kpToolFlowBase::color (int which) { +#if DEBUG_KP_TOOL_FLOW_BASE && 0 qCDebug(kpLogTools) << "kpToolFlowBase::color (" << which << ")"; +#endif // Pen & Brush if (!colorsAreSwapped ()) { return kpTool::color (which); } // only the (Color) Eraser uses the opposite color return kpTool::color (which ? 0 : 1); // don't trust !0 == 1 } //--------------------------------------------------------------------- // protected kpTempImage::UserFunctionType kpToolFlowBase::brushDrawFunction () const { return d->brushDrawFunc; } //--------------------------------------------------------------------- // protected void *kpToolFlowBase::brushDrawFunctionData () const { return d->drawPackageForMouseButton [mouseButton ()]; } // protected int kpToolFlowBase::brushWidth () const { return d->brushWidth; } // protected int kpToolFlowBase::brushHeight () const { return d->brushHeight; } // protected bool kpToolFlowBase::brushIsDiagonalLine () const { return d->brushIsDiagonalLine; } // protected kpToolFlowCommand *kpToolFlowBase::currentCommand () const { return d->currentCommand; } //--------------------------------------------------------------------- // protected slot void kpToolFlowBase::updateBrushAndCursor () { +#if DEBUG_KP_TOOL_FLOW_BASE && 1 qCDebug(kpLogTools) << "kpToolFlowBase::updateBrushAndCursor()"; +#endif if (haveSquareBrushes ()) { d->brushDrawFunc = d->toolWidgetEraserSize->drawFunction (); d->cursorDrawFunc = d->toolWidgetEraserSize->drawCursorFunction (); for (int i = 0; i < 2; i++) { d->drawPackageForMouseButton [i] = &(d->eraserDrawPackageForMouseButton [i] = d->toolWidgetEraserSize->drawFunctionData (color (i))); } d->brushWidth = d->brushHeight = d->cursorWidth = d->cursorHeight = d->toolWidgetEraserSize->eraserSize (); d->brushIsDiagonalLine = false; } else if (haveDiverseBrushes ()) { d->brushDrawFunc = d->cursorDrawFunc = d->toolWidgetBrush->drawFunction (); for (int i = 0; i < 2; i++) { d->drawPackageForMouseButton [i] = &(d->brushDrawPackageForMouseButton [i] = d->toolWidgetBrush->drawFunctionData (color (i))); } d->brushWidth = d->brushHeight = d->cursorWidth = d->cursorHeight = d->toolWidgetBrush->brushSize (); d->brushIsDiagonalLine = d->toolWidgetBrush->brushIsDiagonalLine (); } hover (hasBegun () ? currentPoint () : calculateCurrentPoint ()); } //--------------------------------------------------------------------- // virtual private slot void kpToolFlowBase::slotForegroundColorChanged (const kpColor & /*col*/) { +#if DEBUG_KP_TOOL_FLOW_BASE qCDebug(kpLogTools) << "kpToolFlowBase::slotForegroundColorChanged()"; +#endif + updateBrushAndCursor (); } //--------------------------------------------------------------------- // virtual private slot void kpToolFlowBase::slotBackgroundColorChanged (const kpColor & /*col*/) { +#if DEBUG_KP_TOOL_FLOW_BASE qCDebug(kpLogTools) << "kpToolFlowBase::slotBackgroundColorChanged()"; +#endif + updateBrushAndCursor (); } //--------------------------------------------------------------------- // public static QRect kpToolFlowBase::hotRectForMousePointAndBrushWidthHeight ( const QPoint &mousePoint, int brushWidth, int brushHeight) { /* * e.g. * Width 5: * 0 1 2 3 4 * ^ * | * Center */ return {mousePoint.x () - brushWidth / 2, mousePoint.y () - brushHeight / 2, brushWidth, brushHeight}; } //--------------------------------------------------------------------- // protected QRect kpToolFlowBase::hotRect () const { return hotRectForMousePointAndBrushWidthHeight (currentPoint (), d->brushWidth, d->brushHeight); } //--------------------------------------------------------------------- diff --git a/tools/flow/kpToolSpraycan.cpp b/tools/flow/kpToolSpraycan.cpp index c2bb8145..018fe1ec 100644 --- a/tools/flow/kpToolSpraycan.cpp +++ b/tools/flow/kpToolSpraycan.cpp @@ -1,241 +1,259 @@ /* Copyright (c) 2003-2007 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_TOOL_SPRAYCAN 0 #include "kpToolSpraycan.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "environments/tools/kpToolEnvironment.h" #include "commands/tools/flow/kpToolFlowCommand.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetSpraycanSize.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" #include #include "kpLogCategories.h" #include #include #include #include //--------------------------------------------------------------------- kpToolSpraycan::kpToolSpraycan (kpToolEnvironment *environ, QObject *parent) : kpToolFlowBase (i18n ("Spraycan"), i18n ("Sprays graffiti"), Qt::Key_Y, environ, parent, "tool_spraycan"), m_toolWidgetSpraycanSize(nullptr) { m_timer = new QTimer (this); m_timer->setInterval (25/*ms*/); connect (m_timer, &QTimer::timeout, this, &kpToolSpraycan::timeoutDraw); } //--------------------------------------------------------------------- // protected virtual [base kpToolFlowBase] QString kpToolSpraycan::haventBegunDrawUserMessage () const { return i18n ("Click or drag to spray graffiti."); } //--------------------------------------------------------------------- // public virtual [base kpToolFlowBase] void kpToolSpraycan::begin () { kpToolToolBar *tb = toolToolBar (); Q_ASSERT (tb); m_toolWidgetSpraycanSize = tb->toolWidgetSpraycanSize (); connect (m_toolWidgetSpraycanSize, &kpToolWidgetSpraycanSize::spraycanSizeChanged, this, &kpToolSpraycan::slotSpraycanSizeChanged); m_toolWidgetSpraycanSize->show (); kpToolFlowBase::begin (); } // public virtual [base kpToolFlowBase] void kpToolSpraycan::end () { kpToolFlowBase::end (); disconnect (m_toolWidgetSpraycanSize, &kpToolWidgetSpraycanSize::spraycanSizeChanged, this, &kpToolSpraycan::slotSpraycanSizeChanged); m_toolWidgetSpraycanSize = nullptr; } // public virtual [base kpToolFlowBase] void kpToolSpraycan::beginDraw () { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "kpToolSpraycan::beginDraw()"; +#endif kpToolFlowBase::beginDraw (); // We draw even if the user doesn't move the mouse. // We still timeout-draw even if the user _does_ move the mouse. m_timer->start (); } // protected QRect kpToolSpraycan::drawLineWithProbability (const QPoint &thisPoint, const QPoint &lastPoint, double probability) { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "CALL(thisPoint=" << thisPoint << ",lastPoint=" << lastPoint << ")"; +#endif QList docPoints = kpPainter::interpolatePoints (lastPoint, thisPoint, false/*no need for cardinally adjacency points*/, probability); +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "\tdocPoints=" << docPoints; +#endif + // By chance no points to draw? if (docPoints.empty ()) { return {}; } + // For efficiency, only get image after NOP check above. QRect docRect = kpPainter::normalizedRect(thisPoint, lastPoint); docRect = neededRect (docRect, spraycanSize ()); kpImage image = document ()->getImageAt (docRect); // Spray at each point, onto the image. // // Note in passing: Unlike other tools such as the Brush, drawing // over the same point does result in a different // appearance. QList imagePoints; for (const auto &dp : docPoints) imagePoints.append (dp - docRect.topLeft ()); kpPainter::sprayPoints (&image, imagePoints, color (mouseButton ()), spraycanSize ()); viewManager ()->setFastUpdates (); document ()->setImageAt (image, docRect.topLeft ()); viewManager ()->restoreFastUpdates (); return docRect; } // public virtual [base kpToolFlowBase] QRect kpToolSpraycan::drawPoint (const QPoint &point) { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "kpToolSpraycan::drawPoint" << point << " lastPoint=" << lastPoint (); +#endif // If this is the first in the flow or if the user is moving the spray, // make the spray line continuous. if (point != lastPoint ()) { // Draw at this single point without delay. return drawLineWithProbability (point, point, 1.0/*100% chance of drawing*/); } return {}; } // public virtual [base kpToolFlowBase] QRect kpToolSpraycan::drawLine (const QPoint &thisPoint, const QPoint &lastPoint) { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "CALL(thisPoint=" << thisPoint << ",lastPoint=" << lastPoint; +#endif // Draw only every so often in response to movement. return drawLineWithProbability (thisPoint, lastPoint, 0.05/*less dense: select 5% of adjacent pixels - not all*/); } // protected slot void kpToolSpraycan::timeoutDraw () { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "kpToolSpraycan::timeoutDraw()"; +#endif // Draw at this single point without delay. const QRect drawnRect = drawLineWithProbability (currentPoint (), currentPoint (), 1.0/*100% chance of drawing*/); // kpToolFlowBase() does this after calling drawPoint() and drawLine() so // we need to do it too. currentCommand ()->updateBoundingRect (drawnRect); } // public virtual [base kpToolFlowBase] void kpToolSpraycan::cancelShape () { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "kpToolSpraycan::cancelShape()"; +#endif m_timer->stop (); kpToolFlowBase::cancelShape (); } // public virtual [base kpToolFlowBase] void kpToolSpraycan::endDraw (const QPoint &thisPoint, const QRect &normalizedRect) { +#if DEBUG_KP_TOOL_SPRAYCAN qCDebug(kpLogTools) << "kpToolSpraycan::endDraw(thisPoint=" << thisPoint << ")"; +#endif m_timer->stop (); kpToolFlowBase::endDraw (thisPoint, normalizedRect); } // protected int kpToolSpraycan::spraycanSize () const { return m_toolWidgetSpraycanSize->spraycanSize (); } // protected slot void kpToolSpraycan::slotSpraycanSizeChanged (int size) { (void) size; } diff --git a/tools/kpToolColorPicker.cpp b/tools/kpToolColorPicker.cpp index 484f9c44..f5d1c3aa 100644 --- a/tools/kpToolColorPicker.cpp +++ b/tools/kpToolColorPicker.cpp @@ -1,135 +1,137 @@ /* Copyright (c) 2003-2007 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_TOOL_COLOR_PICKER 0 #include "kpToolColorPicker.h" #include "kpLogCategories.h" #include "widgets/toolbars/kpColorToolBar.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "pixmapfx/kpPixmapFX.h" #include "commands/tools/kpToolColorPickerCommand.h" #include "environments/tools/kpToolEnvironment.h" #include kpToolColorPicker::kpToolColorPicker (kpToolEnvironment *environ, QObject *parent) : kpTool (i18n ("Color Picker"), i18n ("Lets you select a color from the image"), Qt::Key_C, environ, parent, "tool_color_picker") { } kpToolColorPicker::~kpToolColorPicker () = default; // private kpColor kpToolColorPicker::colorAtPixel (const QPoint &p) { +#if DEBUG_KP_TOOL_COLOR_PICKER && 0 qCDebug(kpLogTools) << "kpToolColorPicker::colorAtPixel" << p; +#endif return kpPixmapFX::getColorAtPixel (document ()->image (), p); } // private QString kpToolColorPicker::haventBegunDrawUserMessage () const { return i18n ("Click to select a color."); } // public virtual [base kpTool] void kpToolColorPicker::begin () { setUserMessage (haventBegunDrawUserMessage ()); } // public virtual [base kpTool] void kpToolColorPicker::beginDraw () { m_oldColor = color (mouseButton ()); setUserMessage (cancelUserMessage ()); } // public virtual [base kpTool] void kpToolColorPicker::draw (const QPoint &thisPoint, const QPoint &, const QRect &) { const kpColor color = colorAtPixel (thisPoint); if (color.isValid ()) { environ ()->setColor (mouseButton (), color); setUserShapePoints (thisPoint); } else { environ ()->setColor (mouseButton (), m_oldColor); setUserShapePoints (); } } // public virtual [base kpTool] void kpToolColorPicker::cancelShape () { environ ()->setColor (mouseButton (), m_oldColor); setUserMessage (i18n ("Let go of all the mouse buttons.")); } // public virtual [base kpTool] void kpToolColorPicker::releasedAllButtons () { setUserMessage (haventBegunDrawUserMessage ()); } // public virtual [base kpTool] void kpToolColorPicker::endDraw (const QPoint &thisPoint, const QRect &) { const kpColor color = colorAtPixel (thisPoint); if (color.isValid ()) { auto *cmd = new kpToolColorPickerCommand ( mouseButton (), color, m_oldColor, environ ()->commandEnvironment ()); environ ()->commandHistory ()->addCommand (cmd, false/*no exec*/); setUserMessage (haventBegunDrawUserMessage ()); } else { cancelShape (); } } diff --git a/tools/kpToolFloodFill.cpp b/tools/kpToolFloodFill.cpp index 31ee00f6..185093a8 100644 --- a/tools/kpToolFloodFill.cpp +++ b/tools/kpToolFloodFill.cpp @@ -1,165 +1,169 @@ /* Copyright (c) 2003-2007 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_TOOL_FLOOD_FILL 0 #include "kpToolFloodFill.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "environments/tools/kpToolEnvironment.h" #include "commands/tools/kpToolFloodFillCommand.h" #include "kpLogCategories.h" #include #include //--------------------------------------------------------------------- struct kpToolFloodFillPrivate { kpToolFloodFillCommand *currentCommand; }; //--------------------------------------------------------------------- kpToolFloodFill::kpToolFloodFill (kpToolEnvironment *environ, QObject *parent) : kpTool (i18n ("Flood Fill"), i18n ("Fills regions in the image"), Qt::Key_F, environ, parent, "tool_flood_fill"), d (new kpToolFloodFillPrivate ()) { d->currentCommand = nullptr; } //--------------------------------------------------------------------- kpToolFloodFill::~kpToolFloodFill () { delete d; } //--------------------------------------------------------------------- // private QString kpToolFloodFill::haventBegunDrawUserMessage () const { return i18n ("Click to fill a region."); } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolFloodFill::begin () { setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolFloodFill::beginDraw () { +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 qCDebug(kpLogTools) << "kpToolFloodFill::beginDraw()"; +#endif QApplication::setOverrideCursor (Qt::WaitCursor); { environ ()->flashColorSimilarityToolBarItem (); // Flood Fill is an expensive CPU operation so we only fill at a // mouse click (beginDraw ()), not on mouse move (virtually draw()) d->currentCommand = new kpToolFloodFillCommand ( currentPoint ().x (), currentPoint ().y (), color (mouseButton ()), processedColorSimilarity (), environ ()->commandEnvironment ()); + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 qCDebug(kpLogTools) << "\tperforming new-doc-corner-case check"; + #endif if (document ()->url ().isEmpty () && !document ()->isModified ()) { // Collect the colour that gets changed before we change the pixels // (execute() below). Needed in unexecute(). d->currentCommand->prepareColorToChange (); d->currentCommand->setFillEntireImage (); } d->currentCommand->execute (); } QApplication::restoreOverrideCursor (); setUserMessage (cancelUserMessage ()); } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolFloodFill::draw (const QPoint &thisPoint, const QPoint &, const QRect &) { setUserShapePoints (thisPoint); } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolFloodFill::cancelShape () { d->currentCommand->unexecute (); delete d->currentCommand; d->currentCommand = nullptr; setUserMessage (i18n ("Let go of all the mouse buttons.")); } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolFloodFill::releasedAllButtons () { setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- // public virtual [base kpTool] void kpToolFloodFill::endDraw (const QPoint &, const QRect &) { environ ()->commandHistory ()->addCommand (d->currentCommand, false/*no exec - we already did it up there*/); // Don't delete - it just got added to the history. d->currentCommand = nullptr; setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- diff --git a/tools/kpToolZoom.cpp b/tools/kpToolZoom.cpp index 8f1096b8..a0e86b7d 100644 --- a/tools/kpToolZoom.cpp +++ b/tools/kpToolZoom.cpp @@ -1,248 +1,255 @@ /* Copyright (c) 2003-2007 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_TOOL_ZOOM 0 #include "kpToolZoom.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "pixmapfx/kpPixmapFX.h" #include "generic/kpSetOverrideCursorSaver.h" #include "layers/tempImage/kpTempImage.h" #include "environments/tools/kpToolEnvironment.h" #include "tools/kpToolAction.h" #include "views/manager/kpViewManager.h" #include "kpLogCategories.h" #include #include #include //--------------------------------------------------------------------- struct DrawZoomRectPackage { QRect normalizedRect; }; static void DrawZoomRect (kpImage *destImage, const QPoint &topLeft, void *userData) { auto *pack = static_cast (userData); kpPixmapFX::drawStippleRect(destImage, topLeft.x (), topLeft.y (), pack->normalizedRect.width (), pack->normalizedRect.height (), kpColor::Yellow, kpColor::Green); } struct kpToolZoomPrivate { bool dragHasBegun{}, dragCompleted{}; DrawZoomRectPackage drawPackage; }; kpToolZoom::kpToolZoom (kpToolEnvironment *environ, QWidget *parent) : kpTool (i18n ("Zoom"), i18n ("Zooms in and out of the image"), Qt::Key_Z, environ, parent, "tool_zoom"), d (new kpToolZoomPrivate ()) { // different from objectName() action()->setIcon(KDE::icon("zoom-original")); } //--------------------------------------------------------------------- kpToolZoom::~kpToolZoom () { delete d; } //--------------------------------------------------------------------- // public virtual [base kpTool] bool kpToolZoom::returnToPreviousToolAfterEndDraw () const { // If the user clicks to zoom in or out, s/he generally wants to click // some more to get the exact zoom level wanted. // // However, if they drag out a rectangle to zoom into a particular area, // they probably don't need to do any further zooming so we can return // them to their previous tool. // // Note that if they cancel a drag (cancelShape()), we do _not_ return // them to their previous tool, unlike the Color Picker. This is because // cancelling a drag generally means that the user got the top-left of // the drag wrong and wants to try a different top-left. In contrast, // with the Color Picket, if you've made a mistake while pressing the // mouse, you can just keep holding down the mouse and drag to the intended // color -- a cancel with a Color Picker really means "I've decided not // to pick another color after all", not "I got the start of the drag wrong" // because you can correct that drag. return d->dragCompleted; } // private QString kpToolZoom::haventBegunDrawUserMessage () const { return i18n ("Click to zoom in/out or left drag to zoom into a specific area."); } // public virtual [base kpTool] void kpToolZoom::begin () { viewManager ()->setCursor (Qt::CrossCursor); setUserMessage (haventBegunDrawUserMessage ()); } // public virtual [base kpTool] void kpToolZoom::end () { viewManager ()->unsetCursor (); } // public virtual [base kpTool] void kpToolZoom::globalDraw () { +#if DEBUG_KP_TOOL_ZOOM qCDebug(kpLogTools) << "CALL"; +#endif environ ()->fitToPage (); } // public virtual [base kpTool] void kpToolZoom::beginDraw () { d->dragHasBegun = false; d->dragCompleted = false; setUserMessage (cancelUserMessage ()); } // public virtual [base kpTool] void kpToolZoom::draw (const QPoint &thisPoint, const QPoint &, const QRect &normalizedRect) { +#if DEBUG_KP_TOOL_ZOOM qCDebug(kpLogTools) << "kpToomZoom::draw() currentPoint=" << currentPoint () - << " lastPoint=" << lastPoint (); + << " lastPoint=" << lastPoint () + << endl; +#endif // TODO: Need accidental drag detection from selection tool (when dragging // out new selection) if (!d->dragHasBegun) { if (thisPoint == startPoint ()) { return; } // Left mouse drags select an area to zoom into. // However, it wouldn't make sense to select an area to "zoom out of" // (using the right mouse button). Therefore, make RMB drags do the // same as RMB clicks i.e. a simple zoom out, with no "area" to worry // about. if (mouseButton () == 1/*RMB*/) { return; } d->dragHasBegun = true; } d->drawPackage.normalizedRect = normalizedRect; kpTempImage newTempImage (false/*always display*/, normalizedRect.topLeft (), &::DrawZoomRect, &d->drawPackage, normalizedRect.width (), normalizedRect.height ()); viewManager ()->setFastUpdates (); { viewManager ()->setTempImage (newTempImage); } viewManager ()->restoreFastUpdates (); } // public virtual [base kpTool] void kpToolZoom::cancelShape () { viewManager ()->invalidateTempImage (); // LOREFACTOR: A lot of tools use this - push up to kpTool? setUserMessage (i18n ("Let go of all the mouse buttons.")); } // public virtual [base kpTool] void kpToolZoom::releasedAllButtons () { setUserMessage (haventBegunDrawUserMessage ()); } // public virtual [base kpTool] void kpToolZoom::endDraw (const QPoint &, const QRect &normalizedRect) { +#if DEBUG_KP_TOOL_ZOOM qCDebug(kpLogTools) << "kpToolZoom::endDraw(rect=" << normalizedRect << ")" - << " dragHasBegun=" << d->dragHasBegun; + << " dragHasBegun=" << d->dragHasBegun << endl; +#endif // TODO: This cursor doesn't stay on for long enough because zooming uses // event loop tricks. kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); viewManager ()->invalidateTempImage (); // Click? if (!d->dragHasBegun) { if (mouseButton () == 0/*LMB*/) { environ ()->zoomIn (true/*center under cursor*/); } else { environ ()->zoomOut (false/*don't center under cursor - as is confusing behaviour when zooming out*/); } } // Drag? else if (normalizedRect.isValid()) { environ ()->zoomToRect ( normalizedRect, false/*don't account for grips*/, true/*care about width*/, true/*care about height*/); d->dragCompleted = true; } } diff --git a/tools/kpTool_Drawing.cpp b/tools/kpTool_Drawing.cpp index 6d5a5e9f..1ce028da 100644 --- a/tools/kpTool_Drawing.cpp +++ b/tools/kpTool_Drawing.cpp @@ -1,407 +1,423 @@ /* Copyright (c) 2003-2007 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. */ // // Tool methods for drawing shapes (subclasses reimplement most of these). // #define DEBUG_KP_TOOL 0 #include "tools/kpTool.h" #include "kpToolPrivate.h" #include #include "kpLogCategories.h" #include "environments/tools/kpToolEnvironment.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" #include "imagelib/kpPainter.h" #undef environ // macro on win32 //--------------------------------------------------------------------- // protected int kpTool::mouseButton () const { return d->mouseButton; } //--------------------------------------------------------------------- // protected bool kpTool::shiftPressed () const { return d->shiftPressed; } //--------------------------------------------------------------------- // protected bool kpTool::controlPressed () const { return d->controlPressed; } //--------------------------------------------------------------------- // protected bool kpTool::altPressed () const { return d->altPressed; } // protected QPoint kpTool::startPoint () const { return d->startPoint; } //--------------------------------------------------------------------- // protected QPoint kpTool::currentPoint () const { // TODO: Q_ASSERT (hasBegun()) and similar in other accessors. // We currently violate these kinds of invariants. return d->currentPoint; } //--------------------------------------------------------------------- // protected QPoint kpTool::currentViewPoint () const { return d->currentViewPoint; } //--------------------------------------------------------------------- // protected QRect kpTool::normalizedRect () const { return kpPainter::normalizedRect(d->startPoint, d->currentPoint); } //--------------------------------------------------------------------- // protected QPoint kpTool::lastPoint () const { return d->lastPoint; } //--------------------------------------------------------------------- // protected kpView *kpTool::viewUnderStartPoint () const { return d->viewUnderStartPoint; } //--------------------------------------------------------------------- // protected kpView *kpTool::viewUnderCursor () const { kpViewManager *vm = viewManager (); return vm ? vm->viewUnderCursor () : nullptr; } //--------------------------------------------------------------------- void kpTool::beginInternal () { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool::beginInternal()"; +#endif if (!d->began) { // clear leftover statusbar messages setUserMessage (); d->currentPoint = calculateCurrentPoint (); d->currentViewPoint = calculateCurrentPoint (false/*view point*/); setUserShapePoints (d->currentPoint); // TODO: Audit all the code in this file - states like "d->began" & // "d->beganDraw" should be set before calling user func. // Also, d->currentPoint should be more frequently initialised. // call user virtual func begin (); // we've starting using the tool... d->began = true; // but we haven't started drawing with it d->beganDraw = false; uint keyState = QApplication::keyboardModifiers (); d->shiftPressed = (keyState & Qt::ShiftModifier); d->controlPressed = (keyState & Qt::ControlModifier); // TODO: Can't do much about ALT - unless it's always KApplication::Modifier1? // Ditto for everywhere else where I set SHIFT & CTRL but not alt. // COMPAT: Later: This is now supported by Qt. d->altPressed = false; } } //--------------------------------------------------------------------- void kpTool::endInternal () { if (d->began) { // before we can stop using the tool, we must stop the current drawing operation (if any) if (hasBegunShape ()) { endShapeInternal (d->currentPoint, normalizedRect ()); } // call user virtual func end (); // clear leftover statusbar messages setUserMessage (); setUserShapePoints (calculateCurrentPoint ()); // we've stopped using the tool... d->began = false; // and so we can't be drawing with it d->beganDraw = false; d->environ->hideAllToolWidgets (); } } //--------------------------------------------------------------------- // virtual void kpTool::begin () { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool::begin() base implementation"; +#endif } //--------------------------------------------------------------------- // virtual void kpTool::end () { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool::end() base implementation"; +#endif } //--------------------------------------------------------------------- bool kpTool::hasBegun () const { return d->began; } //--------------------------------------------------------------------- bool kpTool::hasBegunDraw () const { return d->beganDraw; } //--------------------------------------------------------------------- // virtual bool kpTool::hasBegunShape () const { return hasBegunDraw (); } //--------------------------------------------------------------------- void kpTool::beginDrawInternal () { if (!d->beganDraw) { beginDraw (); d->beganDraw = true; emit beganDraw (d->currentPoint); } } //--------------------------------------------------------------------- // virtual void kpTool::beginDraw () { } //--------------------------------------------------------------------- // virtual void kpTool::hover (const QPoint &point) { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool::hover" << point << " base implementation"; +#endif setUserShapePoints (point); } //--------------------------------------------------------------------- // virtual void kpTool::globalDraw () { } //--------------------------------------------------------------------- // virtual void kpTool::reselect () { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool::reselect() base implementation"; +#endif } //--------------------------------------------------------------------- // virtual void kpTool::draw (const QPoint &, const QPoint &, const QRect &) { } //--------------------------------------------------------------------- // private void kpTool::drawInternal () { draw (d->currentPoint, d->lastPoint, normalizedRect ()); } //--------------------------------------------------------------------- // also called by kpView void kpTool::cancelShapeInternal () { if (hasBegunShape ()) { d->beganDraw = false; cancelShape (); d->viewUnderStartPoint = nullptr; emit cancelledShape (viewUnderCursor () ? d->currentPoint : KP_INVALID_POINT); if (viewUnderCursor ()) { hover (d->currentPoint); } else { d->currentPoint = KP_INVALID_POINT; d->currentViewPoint = KP_INVALID_POINT; hover (d->currentPoint); } if (returnToPreviousToolAfterEndDraw ()) { d->environ->selectPreviousTool (); } } } //--------------------------------------------------------------------- // virtual void kpTool::cancelShape () { qCWarning(kpLogTools) << "Tool cannot cancel operation!" ; } //--------------------------------------------------------------------- void kpTool::releasedAllButtons () { } //--------------------------------------------------------------------- void kpTool::endDrawInternal (const QPoint &thisPoint, const QRect &normalizedRect, bool wantEndShape) { +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::endDrawInternal() wantEndShape=" << wantEndShape; +#endif if (wantEndShape && !hasBegunShape ()) { return; } if (!wantEndShape && !hasBegunDraw ()) { return; } d->beganDraw = false; if (wantEndShape) { + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tcalling endShape()"; + #endif endShape (thisPoint, normalizedRect); } else { + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tcalling endDraw()"; + #endif endDraw (thisPoint, normalizedRect); } d->viewUnderStartPoint = nullptr; emit endedDraw (d->currentPoint); if (viewUnderCursor ()) { hover (d->currentPoint); } else { d->currentPoint = KP_INVALID_POINT; d->currentViewPoint = KP_INVALID_POINT; hover (d->currentPoint); } if (returnToPreviousToolAfterEndDraw ()) { d->environ->selectPreviousTool (); } } //--------------------------------------------------------------------- // private void kpTool::endShapeInternal (const QPoint &thisPoint, const QRect &normalizedRect) { endDrawInternal (thisPoint, normalizedRect, true/*end shape*/); } //--------------------------------------------------------------------- // virtual void kpTool::endDraw (const QPoint &, const QRect &) { } //--------------------------------------------------------------------- diff --git a/tools/kpTool_KeyboardEvents.cpp b/tools/kpTool_KeyboardEvents.cpp index 877d0022..56636ef7 100644 --- a/tools/kpTool_KeyboardEvents.cpp +++ b/tools/kpTool_KeyboardEvents.cpp @@ -1,396 +1,430 @@ /* Copyright (c) 2003-2007 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. */ // // Tool reaction to view keyboard input. // #define DEBUG_KP_TOOL 0 // TODO: reduce number of includes #include "tools/kpTool.h" #include "kpToolPrivate.h" #include #include #include #include #include #include "kpLogCategories.h" #include #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpToolAction.h" #include "environments/tools/kpToolEnvironment.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" //--------------------------------------------------------------------- void kpTool::seeIfAndHandleModifierKey (QKeyEvent *e) { switch (e->key ()) { case 0: case Qt::Key_unknown: + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::seeIfAndHandleModifierKey() picked up unknown key!"; + #endif // HACK: around Qt bug: if you hold a modifier before you start the // program and then release it over the view, // Qt reports it as the release of an unknown key // Qt4 update: I don't think this happens anymore... // --- fall thru and update all modifiers --- case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Control: + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::setIfAndHandleModifierKey() accepting"; + #endif keyUpdateModifierState (e); e->accept (); break; } } //--------------------------------------------------------------------- // Returns in and the direction the arrow key "e->key()" is // pointing in or (0,0) if it's not a recognised arrow key. void kpTool::arrowKeyPressDirection (const QKeyEvent *e, int *dx, int *dy) { int dxLocal = 0, dyLocal = 0; switch (e->key ()) { case Qt::Key_Home: dxLocal = -1; dyLocal = -1; break; case Qt::Key_Up: dyLocal = -1; break; case Qt::Key_PageUp: dxLocal = +1; dyLocal = -1; break; case Qt::Key_Left: dxLocal = -1; break; case Qt::Key_Right: dxLocal = +1; break; case Qt::Key_End: dxLocal = -1; dyLocal = +1; break; case Qt::Key_Down: dyLocal = +1; break; case Qt::Key_PageDown: dxLocal = +1; dyLocal = +1; break; } if (dx) { *dx = dxLocal; } if (dy) { *dy = dyLocal; } } //--------------------------------------------------------------------- void kpTool::seeIfAndHandleArrowKeyPress (QKeyEvent *e) { int dx, dy; arrowKeyPressDirection (e, &dx, &dy); if (dx == 0 && dy == 0) { return; } kpView * const view = viewUnderCursor (); if (!view) { return; } const QPoint oldPoint = view->mapFromGlobal (QCursor::pos ()); +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\toldPoint=" << oldPoint - << " dx=" << dx << " dy=" << dy; + << " dx=" << dx << " dy=" << dy << endl; +#endif + const int viewIncX = (dx ? qMax (1, view->zoomLevelX () / 100) * dx : 0); const int viewIncY = (dy ? qMax (1, view->zoomLevelY () / 100) * dy : 0); int newViewX = oldPoint.x () + viewIncX; int newViewY = oldPoint.y () + viewIncY; + +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tnewPoint=" << QPoint (newViewX, newViewY); +#endif // Make sure we really moved at least one doc point (needed due to // rounding error). if (view->transformViewToDoc (QPoint (newViewX, newViewY)) == view->transformViewToDoc (oldPoint)) { newViewX += viewIncX; newViewY += viewIncY; + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tneed adjust for doc - newPoint=" - << QPoint (newViewX, newViewY); + << QPoint (newViewX, newViewY) << endl; + #endif } // TODO: visible width/height (e.g. with scrollbars) const int x = qMin (qMax (newViewX, 0), view->width () - 1); const int y = qMin (qMax (newViewY, 0), view->height () - 1); // QCursor::setPos conveniently causes mouseMoveEvents QCursor::setPos (view->mapToGlobal (QPoint (x, y))); e->accept (); } //--------------------------------------------------------------------- bool kpTool::isDrawKey (int key) { return (key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Insert || key == Qt::Key_Clear/*Numpad 5 Key*/ || key == Qt::Key_L); } //--------------------------------------------------------------------- void kpTool::seeIfAndHandleBeginDrawKeyPress (QKeyEvent *e) { if (e->isAutoRepeat ()) { return; } if (!isDrawKey (e->key ())) { return; } +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::seeIfAndHandleBeginDrawKeyPress() accept"; +#endif + // TODO: wrong for dragging lines outside of view (for e.g.) kpView * const view = viewUnderCursor (); if (!view) { return; } // TODO: what about the modifiers? QMouseEvent me (QEvent::MouseButtonPress, view->mapFromGlobal (QCursor::pos ()), Qt::LeftButton, Qt::LeftButton/*button state after event*/, Qt::NoModifier); mousePressEvent (&me); e->accept (); } void kpTool::seeIfAndHandleEndDrawKeyPress (QKeyEvent *e) { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::setIfAndHandleEndDrawKeyPress() key=" << e->key () << " isAutoRepeat=" << e->isAutoRepeat () << " isDrawKey=" << isDrawKey (e->key ()) - << " view=" << viewUnderCursor (); + << " view=" << viewUnderCursor () + << endl; +#endif if (e->isAutoRepeat ()) { return; } if (!isDrawKey (e->key ())) { return; } +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::seeIfAndHandleEndDrawKeyPress() accept"; +#endif + kpView * const view = viewUnderCursor (); if (!view) { return; } // TODO: what about the modifiers? QMouseEvent me (QEvent::MouseButtonRelease, view->mapFromGlobal (QCursor::pos ()), Qt::LeftButton, Qt::NoButton/*button state after event*/, Qt::NoModifier); mouseReleaseEvent (&me); e->accept (); } //--------------------------------------------------------------------- void kpTool::keyPressEvent (QKeyEvent *e) { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::keyPressEvent() key=" << (int *) e->key () << " stateAfter: modifiers=" << (int *) (int) e->modifiers () << " isAutoRep=" << e->isAutoRepeat (); +#endif e->ignore (); + seeIfAndHandleModifierKey (e); if (e->isAccepted ()) { return; } seeIfAndHandleArrowKeyPress (e); if (e->isAccepted ()) { return; } seeIfAndHandleBeginDrawKeyPress (e); if (e->isAccepted ()) { return; } + switch (e->key ()) { case Qt::Key_Delete: d->environ->deleteSelection (); break; case Qt::Key_Escape: if (hasBegunDraw ()) { cancelShapeInternal (); e->accept (); } break; } } //--------------------------------------------------------------------- void kpTool::keyReleaseEvent (QKeyEvent *e) { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::keyReleaseEvent() key=" << (int *) e->key () << " stateAfter: modifiers=" << (int *) (int) e->modifiers () << " isAutoRep=" << e->isAutoRepeat (); +#endif e->ignore (); seeIfAndHandleModifierKey (e); if (e->isAccepted ()) { return; } seeIfAndHandleEndDrawKeyPress (e); if (e->isAccepted ()) { return; } } //--------------------------------------------------------------------- // private void kpTool::keyUpdateModifierState (QKeyEvent *e) { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::keyUpdateModifierState() e->key=" << (int *) e->key (); qCDebug(kpLogTools) << "\tshift=" << (e->modifiers () & Qt::ShiftModifier) << " control=" << (e->modifiers () & Qt::ControlModifier) << " alt=" - << (e->modifiers () & Qt::AltModifier); + << (e->modifiers () & Qt::AltModifier) + << endl; +#endif if (e->key () & (Qt::Key_Alt | Qt::Key_Shift | Qt::Key_Control)) { + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\t\tmodifier changed - use e's claims"; + #endif setShiftPressed (e->modifiers () & Qt::ShiftModifier); setControlPressed (e->modifiers () & Qt::ControlModifier); setAltPressed (e->modifiers () & Qt::AltModifier); } // See seeIfAndHandleModifierKey() for why this code path exists. else { + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\t\tmodifiers not changed - figure out the truth"; + #endif const Qt::KeyboardModifiers keyState = QApplication::keyboardModifiers (); setShiftPressed (keyState & Qt::ShiftModifier); setControlPressed (keyState & Qt::ControlModifier); setAltPressed (keyState & Qt::AltModifier); } } //--------------------------------------------------------------------- void kpTool::notifyModifierStateChanged () { if (careAboutModifierState ()) { if (d->beganDraw) { draw (d->currentPoint, d->lastPoint, normalizedRect ()); } else { d->currentPoint = calculateCurrentPoint (); d->currentViewPoint = calculateCurrentPoint (false/*view point*/); hover (d->currentPoint); } } } //--------------------------------------------------------------------- void kpTool::setShiftPressed (bool pressed) { if (pressed == d->shiftPressed) { return; } d->shiftPressed = pressed; notifyModifierStateChanged (); } //--------------------------------------------------------------------- void kpTool::setControlPressed (bool pressed) { if (pressed == d->controlPressed) { return; } d->controlPressed = pressed; notifyModifierStateChanged (); } //--------------------------------------------------------------------- void kpTool::setAltPressed (bool pressed) { if (pressed == d->altPressed) { return; } d->altPressed = pressed; notifyModifierStateChanged (); } //--------------------------------------------------------------------- diff --git a/tools/kpTool_MouseEvents.cpp b/tools/kpTool_MouseEvents.cpp index 04cbc6d9..546baf4e 100644 --- a/tools/kpTool_MouseEvents.cpp +++ b/tools/kpTool_MouseEvents.cpp @@ -1,301 +1,337 @@ /* Copyright (c) 2003-2007 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. */ // // Tool reaction to view mouse input. // #define DEBUG_KP_TOOL 0 #include "tools/kpTool.h" #include "kpToolPrivate.h" #include "kpLogCategories.h" #include "environments/tools/kpToolEnvironment.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" #include #include #include //--------------------------------------------------------------------- // HITODO: If you press a mouse button and move it out of the view _really_ fast // and let go of the mouse button outside of the view, a mouseRelease // event will not be generated, so the tool will still be in drawing mode // (this is especially noticeable with the spraycan). // // When you move the mouse back into the view, it will still continue // continue drawing even though no mouse button is held down. // // It is somewhat hard to reproduce so the best way is to position the // mouse close to an edge of the view. If you do it right, no mouseMoveEvent // is generated at _all_, until you move it back into the view. void kpTool::mousePressEvent (QMouseEvent *e) { +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::mousePressEvent pos=" << e->pos () << " button=" << (int) e->button () << " stateAfter: buttons=" << (int *) (int) e->buttons () << " modifiers=" << (int *) (int) e->modifiers () - << " beganDraw=" << d->beganDraw; + << " beganDraw=" << d->beganDraw << endl; +#endif if (e->button () == Qt::MidButton) { const QString text = QApplication::clipboard ()->text (QClipboard::Selection); + #if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "\tMMB pasteText='" << text << "'"; + #endif if (!text.isEmpty ()) { if (hasBegunShape ()) { + #if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "\t\thasBegunShape - end"; + #endif endShapeInternal (d->currentPoint, normalizedRect ()); } if (viewUnderCursor ()) { d->environ->pasteTextAt (text, viewUnderCursor ()->transformViewToDoc (e->pos ()), true/*adjust topLeft so that cursor isn't on top of resize handle*/); } return; } } int mb = mouseButton (e->buttons ()); +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "\tmb=" << mb << " d->beganDraw=" << d->beganDraw; +#endif if (mb == -1 && !d->beganDraw) { // Ignore mouse press. return; } if (d->beganDraw) { if (mb == -1 || mb != d->mouseButton) { + #if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "\tCancelling operation as " << mb << " == -1 or != " << d->mouseButton; + #endif kpView *view = viewUnderStartPoint (); Q_ASSERT (view); // if we get a mousePressEvent when we're drawing, then the other // mouse button must have been pressed d->currentPoint = view->transformViewToDoc (e->pos ()); d->currentViewPoint = e->pos (); cancelShapeInternal (); } return; } kpView *view = viewUnderCursor (); Q_ASSERT (view); +#if DEBUG_KP_TOOL && 1 if (view) { qCDebug(kpLogTools) << "\tview=" << view->objectName (); } +#endif // let user know what mouse button is being used for entire draw d->mouseButton = mouseButton (e->buttons ()); d->shiftPressed = (e->modifiers () & Qt::ShiftModifier); d->controlPressed = (e->modifiers () & Qt::ControlModifier); d->altPressed = (e->modifiers () & Qt::AltModifier); d->startPoint = d->currentPoint = view->transformViewToDoc (e->pos ()); d->currentViewPoint = e->pos (); d->viewUnderStartPoint = view; d->lastPoint = QPoint (-1, -1); +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "\tBeginning draw @ " << d->currentPoint; +#endif beginDrawInternal (); draw (d->currentPoint, d->lastPoint, QRect (d->currentPoint, d->currentPoint)); d->lastPoint = d->currentPoint; } //--------------------------------------------------------------------- // OPT: If the mouse is moving in terms of view pixels, it still might // not be moving in terms of document pixels (when zoomed in). // // So we should detect this and not call draw() or hover(). // // However, kpToolSelection needs hover() to be called on all view // point changes, not just document points, since the selection resize // handles may be smaller than document points. Also, I wonder if // selections' accidental drag detection feature cares? void kpTool::mouseMoveEvent (QMouseEvent *e) { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::mouseMoveEvent pos=" << e->pos () << " stateAfter: buttons=" << (int *) (int) e->buttons () << " modifiers=" << (int *) (int) e->modifiers (); kpView *v0 = viewUnderCursor (), *v1 = viewManager ()->viewUnderCursor (true/*use Qt*/), *v2 = viewUnderStartPoint (); qCDebug(kpLogTools) << "\tviewUnderCursor=" << (v0 ? v0->objectName () : "(none)") << " viewUnderCursorQt=" << (v1 ? v1->objectName () : "(none)") << " viewUnderStartPoint=" << (v2 ? v2->objectName () : "(none)"); - qCDebug(kpLogTools) << "\tfocusWidget=" << qApp->focusWidget (); + qCDebug(kpLogTools) << "\tfocusWidget=" << kapp->focusWidget (); qCDebug(kpLogTools) << "\tbeganDraw=" << d->beganDraw; +#endif d->shiftPressed = (e->modifiers () & Qt::ShiftModifier); d->controlPressed = (e->modifiers () & Qt::ControlModifier); d->altPressed = (e->modifiers () & Qt::AltModifier); if (d->beganDraw) { kpView *view = viewUnderStartPoint (); Q_ASSERT (view); d->currentPoint = view->transformViewToDoc (e->pos ()); d->currentViewPoint = e->pos (); + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tDraw!"; + #endif bool dragScrolled = false; movedAndAboutToDraw (d->currentPoint, d->lastPoint, view->zoomLevelX (), &dragScrolled); if (dragScrolled) { d->currentPoint = calculateCurrentPoint (); d->currentViewPoint = calculateCurrentPoint (false/*view point*/); // Scrollview has scrolled contents and has scheduled an update // for the newly exposed region. If draw() schedules an update // as well (instead of immediately updating), the scrollview's // update will be executed first and it'll only update part of // the screen resulting in ugly tearing of the viewManager's // tempImage. viewManager ()->setFastUpdates (); } drawInternal (); if (dragScrolled) { viewManager ()->restoreFastUpdates (); } d->lastPoint = d->currentPoint; } else { kpView *view = viewUnderCursor (); if (!view) // possible if cancelShape()'ed but still holding down initial mousebtn { d->currentPoint = KP_INVALID_POINT; d->currentViewPoint = KP_INVALID_POINT; return; } d->currentPoint = view->transformViewToDoc (e->pos ()); d->currentViewPoint = e->pos (); hover (d->currentPoint); } } //--------------------------------------------------------------------- void kpTool::mouseReleaseEvent (QMouseEvent *e) { +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::mouseReleaseEvent pos=" << e->pos () << " button=" << (int) e->button () << " stateAfter: buttons=" << (int *) (int) e->buttons () << " modifiers=" << (int *) (int) e->modifiers () << " beganDraw=" << d->beganDraw; +#endif // Have _not_ already cancelShape()'ed by pressing other mouse button? // (e.g. you can cancel a line dragged out with the LMB, by pressing // the RMB) if (d->beganDraw) { kpView *view = viewUnderStartPoint (); Q_ASSERT (view); d->currentPoint = view->transformViewToDoc (e->pos ()); d->currentViewPoint = e->pos (); drawInternal (); endDrawInternal (d->currentPoint, normalizedRect ()); } if ((e->buttons () & Qt::MouseButtonMask) == 0) { releasedAllButtons (); } } //--------------------------------------------------------------------- void kpTool::wheelEvent (QWheelEvent *e) { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool::wheelEvent() modifiers=" << (int *) (int) e->modifiers () << " hasBegunDraw=" << hasBegunDraw () << " delta=" << e->delta (); +#endif e->ignore (); // If CTRL not pressed, bye. if ((e->modifiers () & Qt::ControlModifier) == 0) { + #if DEBUG_KP_TOOL qCDebug(kpLogTools) << "\tno CTRL -> bye"; + #endif return; } // If drawing, bye; don't care if a shape in progress though. if (hasBegunDraw ()) { + #if DEBUG_KP_TOOL qCDebug(kpLogTools) << "\thasBegunDraw() -> bye"; + #endif return; } // Zoom in/out depending on wheel direction. // Moved wheel away from user? if (e->delta () > 0) { + #if DEBUG_KP_TOOL qCDebug(kpLogTools) << "\tzoom in"; + #endif d->environ->zoomIn (true/*center under cursor*/); e->accept (); } // Moved wheel towards user? else if (e->delta () < 0) { + #if DEBUG_KP_TOOL qCDebug(kpLogTools) << "\tzoom out"; + #endif + #if 1 d->environ->zoomOut (true/*center under cursor - make zoom in/out stay under same doc pos*/); + #else + d->environ->zoomOut (false/*don't center under cursor - as is + confusing behaviour when zooming + out*/); + #endif e->accept (); } } //--------------------------------------------------------------------- diff --git a/tools/kpTool_OtherEvents.cpp b/tools/kpTool_OtherEvents.cpp index f942be21..dc67513e 100644 --- a/tools/kpTool_OtherEvents.cpp +++ b/tools/kpTool_OtherEvents.cpp @@ -1,152 +1,168 @@ /* Copyright (c) 2003-2007 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. */ // // Tool reaction to all remaining events. // // 1. View events // 2. Non-view events // + +#define DEBUG_KP_TOOL 0 + + #include "tools/kpTool.h" #include "kpToolPrivate.h" #include "kpLogCategories.h" #include "imagelib/kpColor.h" -#include - //--------------------------------------------------------------------- // // 1. View Events // bool kpTool::viewEvent (QEvent *e) { +#if DEBUG_KP_TOOL qCDebug(kpLogTools) << "kpTool<" << objectName () << "," << this << ">::viewEvent(type=" << e->type () - << ") returning false"; + << ") returning false" << endl; +#else + (void) e; +#endif // Don't handle. return false; } //--------------------------------------------------------------------- void kpTool::focusInEvent (QFocusEvent *) { } //--------------------------------------------------------------------- void kpTool::focusOutEvent (QFocusEvent *) { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::focusOutEvent() beganDraw=" << d->beganDraw; +#endif if (d->beganDraw) { endDrawInternal (d->currentPoint, normalizedRect ()); } } //--------------------------------------------------------------------- void kpTool::enterEvent (QEvent *) { +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::enterEvent() beganDraw=" << d->beganDraw; +#endif } //--------------------------------------------------------------------- void kpTool::leaveEvent (QEvent *) { +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::leaveEvent() beganDraw=" << d->beganDraw; +#endif // if we haven't started drawing (e.g. dragging a rectangle)... if (!d->beganDraw) { d->currentPoint = KP_INVALID_POINT; d->currentViewPoint = KP_INVALID_POINT; hover (d->currentPoint); } } //--------------------------------------------------------------------- // // 2. Non-view events // void kpTool::slotColorsSwappedInternal (const kpColor &newForegroundColor, const kpColor &newBackgroundColor) { if (careAboutColorsSwapped ()) { slotColorsSwapped (newForegroundColor, newBackgroundColor); d->ignoreColorSignals = 2; } else { d->ignoreColorSignals = 0; } } //--------------------------------------------------------------------- void kpTool::slotForegroundColorChangedInternal (const kpColor &color) { if (d->ignoreColorSignals > 0) { + #if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::slotForegroundColorChangedInternal() ignoreColorSignals=" << d->ignoreColorSignals; + #endif d->ignoreColorSignals--; return; } slotForegroundColorChanged (color); } //--------------------------------------------------------------------- void kpTool::slotBackgroundColorChangedInternal (const kpColor &color) { if (d->ignoreColorSignals > 0) { + #if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::slotBackgroundColorChangedInternal() ignoreColorSignals=" << d->ignoreColorSignals; + #endif d->ignoreColorSignals--; return; } slotBackgroundColorChanged (color); } //--------------------------------------------------------------------- void kpTool::slotColorSimilarityChangedInternal (double similarity, int processedSimilarity) { slotColorSimilarityChanged (similarity, processedSimilarity); } //--------------------------------------------------------------------- diff --git a/tools/kpTool_Utilities.cpp b/tools/kpTool_Utilities.cpp index 6bf53a01..51588f24 100644 --- a/tools/kpTool_Utilities.cpp +++ b/tools/kpTool_Utilities.cpp @@ -1,270 +1,294 @@ /* Copyright (c) 2003-2007 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. */ // // Tool utility methods - mainly for subclasses' convenience. // #define DEBUG_KP_TOOL 0 #include "tools/kpTool.h" #include "kpToolPrivate.h" #include #include #include "kpLogCategories.h" #include #include "commands/kpCommandSize.h" #include "kpDefs.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "views/kpView.h" //--------------------------------------------------------------------- // static QRect kpTool::neededRect (const QRect &rect, int lineWidth) { int x1, y1, x2, y2; rect.getCoords (&x1, &y1, &x2, &y2); if (lineWidth < 1) { lineWidth = 1; } // TODO: why not divide by 2? return QRect (QPoint (x1 - lineWidth + 1, y1 - lineWidth + 1), QPoint (x2 + lineWidth - 1, y2 + lineWidth - 1)); } //--------------------------------------------------------------------- // static QImage kpTool::neededPixmap (const QImage &image, const QRect &boundingRect) { return kpPixmapFX::getPixmapAt (image, boundingRect); } //--------------------------------------------------------------------- // public bool kpTool::hasCurrentPoint () const { return (viewUnderStartPoint () || viewUnderCursor ()); } //--------------------------------------------------------------------- // public QPoint kpTool::calculateCurrentPoint (bool zoomToDoc) const { +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "kpTool::currentPoint(zoomToDoc=" << zoomToDoc << ")"; qCDebug(kpLogTools) << "\tviewUnderStartPoint=" << (viewUnderStartPoint () ? viewUnderStartPoint ()->objectName () : "(none)") << " viewUnderCursor=" - << (viewUnderCursor () ? viewUnderCursor ()->objectName () : "(none)"); + << (viewUnderCursor () ? viewUnderCursor ()->objectName () : "(none)") + << endl; +#endif kpView *v = viewUnderStartPoint (); if (!v) { v = viewUnderCursor (); if (!v) { + #if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tno view - returning sentinel"; + #endif return KP_INVALID_POINT; } } const QPoint globalPos = QCursor::pos (); const QPoint viewPos = v->mapFromGlobal (globalPos); +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tglobalPos=" << globalPos << " viewPos=" << viewPos; +#endif if (!zoomToDoc) { return viewPos; } const QPoint docPos = v->transformViewToDoc (viewPos); +#if DEBUG_KP_TOOL && 0 qCDebug(kpLogTools) << "\tdocPos=" << docPos; +#endif return docPos; } //--------------------------------------------------------------------- // public slot void kpTool::somethingBelowTheCursorChanged () { somethingBelowTheCursorChanged (calculateCurrentPoint (), calculateCurrentPoint (false/*view point*/)); } //--------------------------------------------------------------------- // private // TODO: don't dup code from mouseMoveEvent() void kpTool::somethingBelowTheCursorChanged (const QPoint ¤tPoint_, const QPoint ¤tViewPoint_) { +#if DEBUG_KP_TOOL && 1 qCDebug(kpLogTools) << "kpTool::somethingBelowTheCursorChanged(docPoint=" << currentPoint_ << " viewPoint=" << currentViewPoint_ - << ")"; + << ")" << endl; qCDebug(kpLogTools) << "\tviewUnderStartPoint=" << (viewUnderStartPoint () ? viewUnderStartPoint ()->objectName () : "(none)") << " viewUnderCursor=" - << (viewUnderCursor () ? viewUnderCursor ()->objectName () : "(none)"); + << (viewUnderCursor () ? viewUnderCursor ()->objectName () : "(none)") + << endl; qCDebug(kpLogTools) << "\tbegan draw=" << d->beganDraw; +#endif d->currentPoint = currentPoint_; d->currentViewPoint = currentViewPoint_; if (d->beganDraw) { if (d->currentPoint != KP_INVALID_POINT) { draw (d->currentPoint, d->lastPoint, normalizedRect ()); d->lastPoint = d->currentPoint; } } else { hover (d->currentPoint); } } //--------------------------------------------------------------------- bool kpTool::currentPointNextToLast () const { if (d->lastPoint == QPoint (-1, -1)) { return true; } int dx = qAbs (d->currentPoint.x () - d->lastPoint.x ()); int dy = qAbs (d->currentPoint.y () - d->lastPoint.y ()); return (dx <= 1 && dy <= 1); } //--------------------------------------------------------------------- bool kpTool::currentPointCardinallyNextToLast () const { if (d->lastPoint == QPoint (-1, -1)) { return true; } return (d->currentPoint == d->lastPoint || kpPainter::pointsAreCardinallyAdjacent (d->currentPoint, d->lastPoint)); } //--------------------------------------------------------------------- // static // TODO: we don't handle Qt::XButton1 and Qt::XButton2 at the moment. int kpTool::mouseButton (Qt::MouseButtons mouseButtons) { // we have nothing to do with mid-buttons if (mouseButtons & Qt::MidButton) { return -1; } // both left & right together is quite meaningless... const Qt::MouseButtons bothButtons = (Qt::LeftButton | Qt::RightButton); if ((mouseButtons & bothButtons) == bothButtons) { return -1; } if (mouseButtons & Qt::LeftButton) { return 0; } if (mouseButtons & Qt::RightButton) { return 1; } return -1; } //--------------------------------------------------------------------- // public static int kpTool::calculateLength (int start, int end) { if (start <= end) { return end - start + 1; } return end - start - 1; } //--------------------------------------------------------------------- // public static bool kpTool::warnIfBigImageSize (int oldWidth, int oldHeight, int newWidth, int newHeight, const QString &text, const QString &caption, const QString &continueButtonText, QWidget *parent) { +#if DEBUG_KP_TOOL + qCDebug(kpLogTools) << "kpTool::warnIfBigImageSize()" + << " old: w=" << oldWidth << " h=" << oldWidth + << " new: w=" << newWidth << " h=" << newHeight + << " pixmapSize=" + << kpPixmapFX::pixmapSize (newWidth, + newHeight, + QPixmap::defaultDepth ()) + << " vs BigImageSize=" << KP_BIG_IMAGE_SIZE + << endl; +#endif + // Only got smaller or unchanged - don't complain if (!(newWidth > oldWidth || newHeight > oldHeight)) { return true; } // Was already large - user was warned before, don't annoy him/her again if (kpCommandSize::PixmapSize (oldWidth, oldHeight, QPixmap::defaultDepth ()) >= KP_BIG_IMAGE_SIZE) { return true; } if (kpCommandSize::PixmapSize (newWidth, newHeight, QPixmap::defaultDepth ()) >= KP_BIG_IMAGE_SIZE) { int accept = KMessageBox::warningContinueCancel (parent, text, caption, KGuiItem (continueButtonText), KStandardGuiItem::cancel(), QLatin1String ("BigImageDontAskAgain")); return (accept == KMessageBox::Continue); } return true; } //--------------------------------------------------------------------- diff --git a/tools/polygonal/kpToolCurve.cpp b/tools/polygonal/kpToolCurve.cpp index 73819e83..ccd852c4 100644 --- a/tools/polygonal/kpToolCurve.cpp +++ b/tools/polygonal/kpToolCurve.cpp @@ -1,192 +1,196 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2017 Martin Koller 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_TOOL_CURVE 0 #include "kpToolCurve.h" #include "kpLogCategories.h" #include "environments/tools/kpToolEnvironment.h" #include "pixmapfx/kpPixmapFX.h" #include #include #include #include //-------------------------------------------------------------------------------- static void DrawCurveShape (kpImage *image, const QPolygon &points, const kpColor &fcolor, int penWidth, const kpColor &bcolor, bool isFinal) { (void) bcolor; (void) isFinal; Q_ASSERT (points.count () >= 2 && points.count () <= 4); const QPoint startPoint = points [0]; const QPoint endPoint = points [1]; QPoint controlPointP, controlPointQ; switch (points.count ()) { // Just a line? case 2: controlPointP = startPoint; controlPointQ = endPoint; break; // Single control point? case 3: controlPointP = controlPointQ = points [2]; break; // Two control points? case 4: controlPointP = points [2]; controlPointQ = points [3]; break; } QPainter painter(image); painter.setRenderHint(QPainter::Antialiasing, kpToolEnvironment::drawAntiAliased); painter.setPen(QPen(fcolor.toQColor(), penWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); if ( kpPixmapFX::Only1PixelInPointArray(points) ) { painter.drawPoint(points[0]); return; } QPainterPath curvePath; curvePath.moveTo(startPoint); curvePath.cubicTo(controlPointP, controlPointQ, endPoint); painter.strokePath(curvePath, painter.pen()); } //-------------------------------------------------------------------------------- kpToolCurve::kpToolCurve (kpToolEnvironment *environ, QObject *parent) : kpToolPolygonalBase ( i18n ("Curve"), i18n ("Draws curves"), &::DrawCurveShape, Qt::Key_V, environ, parent, "tool_curve") { } kpToolCurve::~kpToolCurve () = default; // protected virtual [base kpToolPolygonalBase] QString kpToolCurve::haventBegunShapeUserMessage () const { return i18n ("Drag out the start and end points."); } // protected virtual [base kpToolPolygonalBase] bool kpToolCurve::drawingALine () const { // On the initial drag (consisting of 2 points) creates a line. // Future drags are for control points. return (points ()->count () == 2); } // public virtual [base kpTool] void kpToolCurve::endDraw (const QPoint &, const QRect &) { +#if DEBUG_KP_TOOL_CURVE qCDebug(kpLogTools) << "kpToolCurve::endDraw() points=" << points ()->toList (); +#endif switch (points ()->count ()) { // A click of the other mouse button (to finish shape, instead of adding // another control point) would have caused endShape() to have been // called in kpToolPolygonalBase::beginDraw(). The points list would now // be empty. We are being called by kpTool::mouseReleaseEvent(). case 0: break; case 1: Q_ASSERT (!"kpToolPolygonalBase::beginDraw() ensures we have >= 2 ctrl points"); break; // Just completed initial line? case 2: if (originatingMouseButton () == 0) { setUserMessage ( i18n ("Left drag to set the first control point or right click to finish.")); } else { setUserMessage ( i18n ("Right drag to set the first control point or left click to finish.")); } break; // Have initial line and first control point? case 3: if (originatingMouseButton () == 0) { setUserMessage ( i18n ("Left drag to set the last control point or right click to finish.")); } else { setUserMessage ( i18n ("Right drag to set the last control point or left click to finish.")); } break; // Have initial line and both control points? case 4: + #if DEBUG_KP_TOOL_CURVE qCDebug(kpLogTools) << "\tending shape"; + #endif endShape (); break; default: Q_ASSERT (!"Impossible number of points"); break; } } diff --git a/tools/polygonal/kpToolLine.cpp b/tools/polygonal/kpToolLine.cpp index e0d3f618..b250480e 100644 --- a/tools/polygonal/kpToolLine.cpp +++ b/tools/polygonal/kpToolLine.cpp @@ -1,71 +1,73 @@ /* Copyright (c) 2003-2007 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_TOOL_LINE 0 #include "kpToolLine.h" #include "kpToolPolyline.h" #include "kpLogCategories.h" #include //-------------------------------------------------------------------------------- kpToolLine::kpToolLine (kpToolEnvironment *environ, QObject *parent) : kpToolPolygonalBase ( i18n ("Line"), i18n ("Draws lines"), &kpToolPolyline::drawShape, Qt::Key_L, environ, parent, "tool_line") { } //-------------------------------------------------------------------------------- // private virtual [base kpToolPolygonalBase] QString kpToolLine::haventBegunShapeUserMessage () const { return i18n ("Drag to draw."); } //-------------------------------------------------------------------------------- // public virtual [base kpTool] void kpToolLine::endDraw (const QPoint &, const QRect &) { +#if DEBUG_KP_TOOL_LINE qCDebug(kpLogTools) << "kpToolLine::endDraw() points=" - << points ()->toList (); + << points ()->toList () << endl; +#endif // After the first drag, we should have a line. Q_ASSERT (points ()->count () == 2); endShape (); } //-------------------------------------------------------------------------------- diff --git a/tools/polygonal/kpToolPolygon.cpp b/tools/polygonal/kpToolPolygon.cpp index b115d7db..3c370484 100644 --- a/tools/polygonal/kpToolPolygon.cpp +++ b/tools/polygonal/kpToolPolygon.cpp @@ -1,183 +1,190 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2017 Martin Koller 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_TOOL_POLYGON 0 + + #include "kpToolPolygon.h" #include "widgets/toolbars/kpToolToolBar.h" #include "environments/tools/kpToolEnvironment.h" #include "imagelib/kpColor.h" #include "pixmapfx/kpPixmapFX.h" #include #include #include -#include "kpLogCategories.h" //-------------------------------------------------------------------------------- static void DrawPolygonShape (kpImage *image, const QPolygon &points, const kpColor &fcolor, int penWidth, const kpColor &bcolor, bool isFinal) { QPainter painter(image); painter.setRenderHint(QPainter::Antialiasing, kpToolEnvironment::drawAntiAliased); painter.setPen(QPen(fcolor.toQColor(), penWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); if ( kpPixmapFX::Only1PixelInPointArray(points) ) { painter.drawPoint(points[0]); return; } if ( bcolor.isValid() ) { painter.setBrush(QBrush(bcolor.toQColor())); } else { painter.setBrush(Qt::NoBrush); } painter.drawPolygon(points, Qt::OddEvenFill); if ( isFinal ) { return; } if ( points.count() <= 2 ) { return; } painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter.setPen(QPen(Qt::white)); painter.drawLine(points[0], points[points.count() - 1]); } //-------------------------------------------------------------------------------- struct kpToolPolygonPrivate { kpToolWidgetFillStyle *toolWidgetFillStyle; }; kpToolPolygon::kpToolPolygon (kpToolEnvironment *environ, QObject *parent) : kpToolPolygonalBase ( i18n ("Polygon"), i18n ("Draws polygons"), &::DrawPolygonShape, Qt::Key_G, environ, parent, "tool_polygon"), d (new kpToolPolygonPrivate ()) { } kpToolPolygon::~kpToolPolygon () { delete d; } // private virtual [base kpToolPolygonBase] QString kpToolPolygon::haventBegunShapeUserMessage () const { return i18n ("Drag to draw the first line."); } // public virtual [base kpToolPolygonalBase] void kpToolPolygon::begin () { kpToolPolygonalBase::begin (); kpToolToolBar *tb = toolToolBar (); Q_ASSERT (tb); d->toolWidgetFillStyle = tb->toolWidgetFillStyle (); connect (d->toolWidgetFillStyle, &kpToolWidgetFillStyle::fillStyleChanged, this, &kpToolPolygon::updateShape); d->toolWidgetFillStyle->show (); } // public virtual [base kpToolPolygonalBase] void kpToolPolygon::end () { kpToolPolygonalBase::end (); disconnect (d->toolWidgetFillStyle, &kpToolWidgetFillStyle::fillStyleChanged, this, &kpToolPolygon::updateShape); d->toolWidgetFillStyle = nullptr; } // TODO: code dup with kpToolRectangle // protected virtual [base kpToolPolygonalBase] kpColor kpToolPolygon::drawingBackgroundColor () const { const kpColor foregroundColor = color (originatingMouseButton ()); const kpColor backgroundColor = color (1 - originatingMouseButton ()); return d->toolWidgetFillStyle->drawingBackgroundColor ( foregroundColor, backgroundColor); } // public virtual [base kpTool] // TODO: dup with kpToolPolyline but we don't want to create another level of // inheritance and readability. void kpToolPolygon::endDraw (const QPoint &, const QRect &) { +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygon::endDraw() points=" << points ()->toList (); +#endif // A click of the other mouse button (to finish shape, instead of adding // another control point) would have caused endShape() to have been // called in kpToolPolygonalBase::beginDraw(). The points list would now // be empty. We are being called by kpTool::mouseReleaseEvent(). if (points ()->count () == 0) { return; } if (points ()->count () >= kpToolPolygonalBase::MaxPoints) { + #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "\tending shape"; + #endif endShape (); return; } if (originatingMouseButton () == 0) { setUserMessage (i18n ("Left drag another line or right click to finish.")); } else { setUserMessage (i18n ("Right drag another line or left click to finish.")); } } diff --git a/tools/polygonal/kpToolPolygonalBase.cpp b/tools/polygonal/kpToolPolygonalBase.cpp index 34037426..131b4852 100644 --- a/tools/polygonal/kpToolPolygonalBase.cpp +++ b/tools/polygonal/kpToolPolygonalBase.cpp @@ -1,479 +1,501 @@ /* Copyright (c) 2003-2007 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_TOOL_POLYGON 0 #include "kpToolPolygonalBase.h" #include #include #include #include #include #include #include #include "kpLogCategories.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "kpDefs.h" #include "imagelib/kpImage.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/tempImage/kpTempImage.h" #include "environments/tools/kpToolEnvironment.h" #include "commands/tools/polygonal/kpToolPolygonalCommand.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetLineWidth.h" #include "views/manager/kpViewManager.h" struct kpToolPolygonalBasePrivate { kpToolPolygonalBasePrivate () : drawShapeFunc(nullptr), toolWidgetLineWidth(nullptr), originatingMouseButton(-1) { } kpToolPolygonalBase::DrawShapeFunc drawShapeFunc; kpToolWidgetLineWidth *toolWidgetLineWidth; int originatingMouseButton; QPolygon points; }; //--------------------------------------------------------------------- kpToolPolygonalBase::kpToolPolygonalBase ( const QString &text, const QString &description, DrawShapeFunc drawShapeFunc, int key, kpToolEnvironment *environ, QObject *parent, const QString &name) : kpTool (text, description, key, environ, parent, name), d (new kpToolPolygonalBasePrivate ()) { d->drawShapeFunc = drawShapeFunc; d->toolWidgetLineWidth = nullptr; // (hopefully cause crash if we use it before initialising it) d->originatingMouseButton = -1; } //--------------------------------------------------------------------- kpToolPolygonalBase::~kpToolPolygonalBase () { delete d; } //--------------------------------------------------------------------- // virtual void kpToolPolygonalBase::begin () { kpToolToolBar *tb = toolToolBar (); Q_ASSERT (tb); +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::begin() tb=" << tb; +#endif d->toolWidgetLineWidth = tb->toolWidgetLineWidth (); connect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged, this, &kpToolPolygonalBase::updateShape); d->toolWidgetLineWidth->show (); viewManager ()->setCursor (QCursor (Qt::ArrowCursor)); d->originatingMouseButton = -1; setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); } //--------------------------------------------------------------------- // virtual void kpToolPolygonalBase::end () { // TODO: needed? endShape (); disconnect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged, this, &kpToolPolygonalBase::updateShape); d->toolWidgetLineWidth = nullptr; viewManager ()->unsetCursor (); } void kpToolPolygonalBase::beginDraw () { +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::beginDraw() d->points=" << d->points.toList () << ", startPoint=" << startPoint (); +#endif bool endedShape = false; // We now need to start with dragging out the initial line? if (d->points.count () == 0) { d->originatingMouseButton = mouseButton (); // The line starts and ends at the start point of the drag. // draw() will modify the last point in d->points to reflect the // mouse drag, as the drag proceeds. d->points.append (startPoint ()); d->points.append (startPoint ()); } // Already have control points - not dragging out initial line. else { // Clicking the other mouse button? if (mouseButton () != d->originatingMouseButton) { // Finish shape. TODO: I suspect we need to call endShapeInternal instead. endShape (); endedShape = true; } // Are we dragging out an extra control point? else { // Add another control point. d->points.append (startPoint ()); } } +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "\tafterwards, d->points=" << d->points.toList (); +#endif if (!endedShape) { // We've started dragging. Print instructions on how to cancel shape. setUserMessage (cancelUserMessage ()); } } // protected void kpToolPolygonalBase::applyModifiers () { const int count = d->points.count (); QPoint &lineStartPoint = d->points [count - 2]; QPoint &lineEndPoint = d->points [count - 1]; +#if DEBUG_KP_TOOL_POLYGON && 1 qCDebug(kpLogTools) << "kpToolPolygonalBase::applyModifiers() #pts=" << count << " line: startPt=" << lineStartPoint << " endPt=" << lineEndPoint << " modifiers: shift=" << shiftPressed () << " alt=" << altPressed () << " ctrl=" << controlPressed (); +#endif // angles if (shiftPressed () || controlPressed ()) { int diffx = lineEndPoint.x () - lineStartPoint.x (); int diffy = lineEndPoint.y () - lineStartPoint.y (); double ratio; if (diffx == 0) { ratio = DBL_MAX; } else { ratio = fabs (double (diffy) / double (diffx)); } - qCDebug(kpLogTools) << "\tdiffx=" << diffx << " diffy=" << diffy << " ratio=" << ratio; + #if DEBUG_KP_TOOL_POLYGON && 1 + qCDebug(kpLogTools) << "\tdiffx=" << diffx << " diffy=" << diffy + << " ratio=" << ratio; + #endif // Shift = 0, 45, 90 // Ctrl = 0, 30, 60, 90 // Shift + Ctrl = 0, 30, 45, 60, 90 double angles [10]; // "ought to be enough for anybody" int numAngles = 0; angles [numAngles++] = 0; if (controlPressed ()) { angles [numAngles++] = M_PI / 6; } if (shiftPressed ()) { angles [numAngles++] = M_PI / 4; } if (controlPressed ()) { angles [numAngles++] = M_PI / 3; } angles [numAngles++] = M_PI / 2; Q_ASSERT (numAngles <= int (sizeof (angles) / sizeof (angles [0]))); double angle = angles [numAngles - 1]; for (int i = 0; i < numAngles - 1; i++) { double acceptingRatio = std::tan ((angles [i] + angles [i + 1]) / 2.0); if (ratio < acceptingRatio) { angle = angles [i]; break; } } // horizontal (dist from start not maintained) if (std::fabs (qRadiansToDegrees (angle) - 0) < kpPixmapFX::AngleInDegreesEpsilon) { lineEndPoint = QPoint (lineEndPoint.x (), lineStartPoint.y ()); } // vertical (dist from start not maintained) else if (std::fabs (qRadiansToDegrees (angle) - 90) < kpPixmapFX::AngleInDegreesEpsilon) { lineEndPoint = QPoint (lineStartPoint.x (), lineEndPoint.y ()); } // diagonal (dist from start maintained) else { const double dist = std::sqrt (static_cast (diffx * diffx + diffy * diffy)); #define sgn(a) ((a)<0?-1:1) // Round distances _before_ adding to any coordinate // (ensures consistent rounding behaviour in x & y directions) const int newdx = qRound (dist * cos (angle) * sgn (diffx)); const int newdy = qRound (dist * sin (angle) * sgn (diffy)); #undef sgn lineEndPoint = QPoint (lineStartPoint.x () + newdx, lineStartPoint.y () + newdy); + #if DEBUG_KP_TOOL_POLYGON && 1 qCDebug(kpLogTools) << "\t\tdiagonal line: dist=" << dist << " angle=" << (angle * 180 / M_PI) << " endPoint=" << lineEndPoint; + #endif } } // if (shiftPressed () || controlPressed ()) { // centring if (altPressed () && 0/*ALT is unreliable*/) { // start = start - diff // = start - (end - start) // = start - end + start // = 2 * start - end if (count == 2) { lineStartPoint += (lineStartPoint - lineEndPoint); } else { lineEndPoint += (lineEndPoint - lineStartPoint); } } // if (altPressed ()) { } // protected QPolygon *kpToolPolygonalBase::points () const { return &d->points; } // protected int kpToolPolygonalBase::originatingMouseButton () const { Q_ASSERT (hasBegunShape ()); return d->originatingMouseButton; } // virtual void kpToolPolygonalBase::draw (const QPoint &, const QPoint &, const QRect &) { // A click of the other mouse button (to finish shape, instead of adding // another control point) would have caused endShape() to have been // called in kpToolPolygonalBase::beginDraw(). The points list would now // be empty. We are being called by kpTool::mouseReleaseEvent(). if (d->points.count () == 0) { return; } +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::draw() d->points=" << d->points.toList () << ", endPoint=" << currentPoint (); +#endif // Update points() so that last point reflects current mouse position. const int count = d->points.count (); d->points [count - 1] = currentPoint (); +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "\tafterwards, d->points=" << d->points.toList (); +#endif // Are we drawing a line? if (/*virtual*/drawingALine ()) { // Adjust the line (end points given by the last 2 points of points()) // in response to keyboard modifiers. applyModifiers (); // Update the preview of the shape. updateShape (); // Inform the user that we're dragging out a line with 2 control points. setUserShapePoints (d->points [count - 2], d->points [count - 1]); } // We're modifying a point. else { // Update the preview of the shape. updateShape (); // Informs the user that we're just modifying a point (perhaps, a control // point of a Bezier). setUserShapePoints (d->points [count - 1]); } } // TODO: code dup with kpToolRectangle // private kpColor kpToolPolygonalBase::drawingForegroundColor () const { return color (originatingMouseButton ()); } // protected virtual kpColor kpToolPolygonalBase::drawingBackgroundColor () const { return kpColor::Invalid; } // TODO: code dup with kpToolRectangle // protected slot void kpToolPolygonalBase::updateShape () { if (d->points.count () == 0) { return; } const QRect boundingRect = kpTool::neededRect ( d->points.boundingRect (), d->toolWidgetLineWidth->lineWidth ()); +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::updateShape() boundingRect=" << boundingRect << " lineWidth=" - << d->toolWidgetLineWidth->lineWidth (); + << d->toolWidgetLineWidth->lineWidth () + << endl; +#endif kpImage image = document ()->getImageAt (boundingRect); QPolygon pointsTranslated = d->points; pointsTranslated.translate (-boundingRect.x (), -boundingRect.y ()); (*d->drawShapeFunc) (&image, pointsTranslated, drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), /*virtual*/drawingBackgroundColor (), false/*not final*/); kpTempImage newTempImage (false/*always display*/, kpTempImage::SetImage/*render mode*/, boundingRect.topLeft (), image); viewManager ()->setFastUpdates (); { viewManager ()->setTempImage (newTempImage); } viewManager ()->restoreFastUpdates (); } // virtual void kpToolPolygonalBase::cancelShape () { viewManager ()->invalidateTempImage (); d->points.resize (0); setUserMessage (i18n ("Let go of all the mouse buttons.")); } void kpToolPolygonalBase::releasedAllButtons () { if (!hasBegunShape ()) { setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); } // --- else case already handled by endDraw() --- } // public virtual [base kpTool] void kpToolPolygonalBase::endShape (const QPoint &, const QRect &) { +#if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::endShape() d->points=" - << d->points.toList (); + << d->points.toList () << endl; +#endif if (!hasBegunShape ()) { return; } viewManager ()->invalidateTempImage (); QRect boundingRect = kpTool::neededRect ( d->points.boundingRect (), d->toolWidgetLineWidth->lineWidth ()); commandHistory ()->addCommand ( new kpToolPolygonalCommand ( text (), d->drawShapeFunc, d->points, boundingRect, drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), /*virtual*/drawingBackgroundColor (), environ ()->commandEnvironment ())); d->points.resize (0); setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); } // public virtual [base kpTool] bool kpToolPolygonalBase::hasBegunShape () const { return (d->points.count () > 0); } // virtual protected slot [base kpTool] void kpToolPolygonalBase::slotForegroundColorChanged (const kpColor &) { updateShape (); } // virtual protected slot [base kpTool] void kpToolPolygonalBase::slotBackgroundColorChanged (const kpColor &) { updateShape (); } diff --git a/tools/rectangular/kpToolRectangularBase.cpp b/tools/rectangular/kpToolRectangularBase.cpp index 8abfa85a..ef961ebb 100644 --- a/tools/rectangular/kpToolRectangularBase.cpp +++ b/tools/rectangular/kpToolRectangularBase.cpp @@ -1,379 +1,388 @@ /* Copyright (c) 2003-2007 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_TOOL_RECTANGULAR_BASE 0 #include "tools/rectangular/kpToolRectangularBase.h" #include #include "kpLogCategories.h" #include #include "imagelib/kpColor.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/tempImage/kpTempImage.h" #include "environments/tools/kpToolEnvironment.h" #include "commands/tools/rectangular/kpToolRectangularCommand.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetFillStyle.h" #include "widgets/toolbars/options/kpToolWidgetLineWidth.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" //--------------------------------------------------------------------- struct kpToolRectangularBasePrivate { kpToolRectangularBase::DrawShapeFunc drawShapeFunc{}; kpToolWidgetLineWidth *toolWidgetLineWidth{}; kpToolWidgetFillStyle *toolWidgetFillStyle{}; QRect toolRectangleRect; }; //--------------------------------------------------------------------- kpToolRectangularBase::kpToolRectangularBase ( const QString &text, const QString &description, DrawShapeFunc drawShapeFunc, int key, kpToolEnvironment *environ, QObject *parent, const QString &name) : kpTool (text, description, key, environ, parent, name), d (new kpToolRectangularBasePrivate ()) { d->drawShapeFunc = drawShapeFunc; d->toolWidgetLineWidth = nullptr; d->toolWidgetFillStyle = nullptr; } //--------------------------------------------------------------------- kpToolRectangularBase::~kpToolRectangularBase () { delete d; } //--------------------------------------------------------------------- // private slot virtual void kpToolRectangularBase::slotLineWidthChanged () { if (hasBegunDraw ()) { updateShape (); } } //--------------------------------------------------------------------- // private slot virtual void kpToolRectangularBase::slotFillStyleChanged () { if (hasBegunDraw ()) { updateShape (); } } //--------------------------------------------------------------------- // private QString kpToolRectangularBase::haventBegunDrawUserMessage () const { return i18n ("Drag to draw."); } //--------------------------------------------------------------------- // virtual void kpToolRectangularBase::begin () { +#if DEBUG_KP_TOOL_RECTANGULAR_BASE qCDebug(kpLogTools) << "kpToolRectangularBase::begin ()"; +#endif kpToolToolBar *tb = toolToolBar (); Q_ASSERT (tb); +#if DEBUG_KP_TOOL_RECTANGULAR_BASE qCDebug(kpLogTools) << "\ttoolToolBar=" << tb; +#endif d->toolWidgetLineWidth = tb->toolWidgetLineWidth (); connect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged, this, &kpToolRectangularBase::slotLineWidthChanged); d->toolWidgetLineWidth->show (); d->toolWidgetFillStyle = tb->toolWidgetFillStyle (); connect (d->toolWidgetFillStyle, &kpToolWidgetFillStyle::fillStyleChanged, this, &kpToolRectangularBase::slotFillStyleChanged); d->toolWidgetFillStyle->show (); viewManager ()->setCursor (QCursor (Qt::ArrowCursor)); setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- // virtual void kpToolRectangularBase::end () { +#if DEBUG_KP_TOOL_RECTANGULAR_BASE qCDebug(kpLogTools) << "kpToolRectangularBase::end ()"; +#endif if (d->toolWidgetLineWidth) { disconnect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged, this, &kpToolRectangularBase::slotLineWidthChanged); d->toolWidgetLineWidth = nullptr; } if (d->toolWidgetFillStyle) { disconnect (d->toolWidgetFillStyle, &kpToolWidgetFillStyle::fillStyleChanged, this, &kpToolRectangularBase::slotFillStyleChanged); d->toolWidgetFillStyle = nullptr; } viewManager ()->unsetCursor (); } //--------------------------------------------------------------------- void kpToolRectangularBase::applyModifiers () { QRect rect = normalizedRect (); +#if DEBUG_KP_TOOL_RECTANGULAR_BASE qCDebug(kpLogTools) << "kpToolRectangularBase::applyModifiers(" << rect << ") shift=" << shiftPressed () - << " ctrl=" << controlPressed (); + << " ctrl=" << controlPressed () + << endl; +#endif // user wants to startPoint () == center if (controlPressed ()) { int xdiff = qAbs (startPoint ().x () - currentPoint ().x ()); int ydiff = qAbs (startPoint ().y () - currentPoint ().y ()); rect = QRect (startPoint ().x () - xdiff, startPoint ().y () - ydiff, xdiff * 2 + 1, ydiff * 2 + 1); } // user wants major axis == minor axis: // rectangle --> square // rounded rectangle --> rounded square // ellipse --> circle if (shiftPressed ()) { if (!controlPressed ()) { if (rect.width () < rect.height ()) { if (startPoint ().y () == rect.y ()) { rect.setHeight (rect.width ()); } else { rect.setY (rect.bottom () - rect.width () + 1); } } else { if (startPoint ().x () == rect.x ()) { rect.setWidth (rect.height ()); } else { rect.setX (rect.right () - rect.height () + 1); } } } // have to maintain the center else { if (rect.width () < rect.height ()) { QPoint center = rect.center (); rect.setHeight (rect.width ()); rect.moveCenter (center); } else { QPoint center = rect.center (); rect.setWidth (rect.height ()); rect.moveCenter (center); } } } d->toolRectangleRect = rect; } //--------------------------------------------------------------------- void kpToolRectangularBase::beginDraw () { setUserMessage (cancelUserMessage ()); } //--------------------------------------------------------------------- // private kpColor kpToolRectangularBase::drawingForegroundColor () const { return color (mouseButton ()); } //--------------------------------------------------------------------- // private kpColor kpToolRectangularBase::drawingBackgroundColor () const { const kpColor foregroundColor = color (mouseButton ()); const kpColor backgroundColor = color (1 - mouseButton ()); return d->toolWidgetFillStyle->drawingBackgroundColor ( foregroundColor, backgroundColor); } //--------------------------------------------------------------------- // private void kpToolRectangularBase::updateShape () { kpImage image = document ()->getImageAt (d->toolRectangleRect); // Invoke shape drawing function passed in ctor. (*d->drawShapeFunc) (&image, 0, 0, d->toolRectangleRect.width (), d->toolRectangleRect.height (), drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), drawingBackgroundColor ()); kpTempImage newTempImage (false/*always display*/, kpTempImage::SetImage/*render mode*/, d->toolRectangleRect.topLeft (), image); viewManager ()->setFastUpdates (); viewManager ()->setTempImage (newTempImage); viewManager ()->restoreFastUpdates (); } //--------------------------------------------------------------------- void kpToolRectangularBase::draw (const QPoint &, const QPoint &, const QRect &) { applyModifiers (); updateShape (); // Recover the start and end points from the transformed & normalized d->toolRectangleRect // S. or S or SC or S == C // .C C if (currentPoint ().x () >= startPoint ().x () && currentPoint ().y () >= startPoint ().y ()) { setUserShapePoints (d->toolRectangleRect.topLeft (), d->toolRectangleRect.bottomRight ()); } // .C or C // S. S else if (currentPoint ().x () >= startPoint ().x () && currentPoint ().y () < startPoint ().y ()) { setUserShapePoints (d->toolRectangleRect.bottomLeft (), d->toolRectangleRect.topRight ()); } // .S or CS // C. else if (currentPoint ().x () < startPoint ().x () && currentPoint ().y () >= startPoint ().y ()) { setUserShapePoints (d->toolRectangleRect.topRight (), d->toolRectangleRect.bottomLeft ()); } // C. // .S else { setUserShapePoints (d->toolRectangleRect.bottomRight (), d->toolRectangleRect.topLeft ()); } } //--------------------------------------------------------------------- void kpToolRectangularBase::cancelShape () { viewManager ()->invalidateTempImage (); setUserMessage (i18n ("Let go of all the mouse buttons.")); } //--------------------------------------------------------------------- void kpToolRectangularBase::releasedAllButtons () { setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- void kpToolRectangularBase::endDraw (const QPoint &, const QRect &) { applyModifiers (); // TODO: flicker // Later: So why can't we use kpViewManager::setQueueUpdates()? Check SVN // log to see if this method was not available at the time of the // TODO, hence justifying the TODO. // Later2: kpToolPolygonalBase, and perhaps, other shapes will have the // same problem. viewManager ()->invalidateTempImage (); environ ()->commandHistory ()->addCommand ( new kpToolRectangularCommand ( text (), d->drawShapeFunc, d->toolRectangleRect, drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), drawingBackgroundColor (), environ ()->commandEnvironment ())); setUserMessage (haventBegunDrawUserMessage ()); } //--------------------------------------------------------------------- diff --git a/views/kpThumbnailView.cpp b/views/kpThumbnailView.cpp index f459cf54..e001aba5 100644 --- a/views/kpThumbnailView.cpp +++ b/views/kpThumbnailView.cpp @@ -1,88 +1,96 @@ /* Copyright (c) 2003-2007 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_THUMBNAIL_VIEW 0 #include "views/kpThumbnailView.h" #include "kpLogCategories.h" kpThumbnailView::kpThumbnailView (kpDocument *document, kpToolToolBar *toolToolBar, kpViewManager *viewManager, kpView *buddyView, kpViewScrollableContainer *scrollableContainer, QWidget *parent) : kpView (document, toolToolBar, viewManager, buddyView, scrollableContainer, parent) { } kpThumbnailView::~kpThumbnailView () = default; // protected void kpThumbnailView::setMaskToCoverDocument () { +#if DEBUG_KP_THUMBNAIL_VIEW qCDebug(kpLogViews) << "kpThumbnailView::setMaskToCoverDocument()" << " origin=" << origin () << " zoomedDoc: width=" << zoomedDocWidth () - << " height=" << zoomedDocHeight (); + << " height=" << zoomedDocHeight () + << endl; +#endif setMask (QRegion (QRect (origin ().x (), origin ().y (), zoomedDocWidth (), zoomedDocHeight ()))); } // protected virtual [base kpView] void kpThumbnailView::resizeEvent (QResizeEvent *e) { +#if DEBUG_KP_THUMBNAIL_VIEW + qCDebug(kpLogViews) << "kpThumbnailView(" << name () << ")::resizeEvent()" + << endl; +#endif + // For QResizeEvent's, Qt already throws an entire widget repaint into // the event loop. So eat useless update() calls that can only slow // things down. // TODO: this doesn't seem to work. // Later: In Qt4, setUpdatesEnabled(true) calls update(). const bool oldIsUpdatesEnabled = updatesEnabled (); setUpdatesEnabled (false); { kpView::resizeEvent (e); adjustToEnvironment (); } setUpdatesEnabled (oldIsUpdatesEnabled); } diff --git a/views/kpUnzoomedThumbnailView.cpp b/views/kpUnzoomedThumbnailView.cpp index 740c758d..ee31f432 100644 --- a/views/kpUnzoomedThumbnailView.cpp +++ b/views/kpUnzoomedThumbnailView.cpp @@ -1,204 +1,222 @@ /* Copyright (c) 2003-2007 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_UNZOOMED_THUMBNAIL_VIEW 0 #include "views/kpUnzoomedThumbnailView.h" #include "kpLogCategories.h" #include "document/kpDocument.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" #include #include //--------------------------------------------------------------------- struct kpUnzoomedThumbnailViewPrivate { }; kpUnzoomedThumbnailView::kpUnzoomedThumbnailView ( kpDocument *document, kpToolToolBar *toolToolBar, kpViewManager *viewManager, kpView *buddyView, kpViewScrollableContainer *scrollableContainer, QWidget *parent) : kpThumbnailView (document, toolToolBar, viewManager, buddyView, scrollableContainer, parent), d (new kpUnzoomedThumbnailViewPrivate ()) { if (buddyViewScrollableContainer ()) { connect (buddyViewScrollableContainer(), &kpViewScrollableContainer::contentsMoved, this, &kpUnzoomedThumbnailView::adjustToEnvironment); } // Call to virtual function - this is why the class is sealed adjustToEnvironment (); } //--------------------------------------------------------------------- kpUnzoomedThumbnailView::~kpUnzoomedThumbnailView () { delete d; } //--------------------------------------------------------------------- // public virtual [base kpThumbnailView] QString kpUnzoomedThumbnailView::caption () const { return i18n ("Unzoomed Mode - Thumbnail"); } //--------------------------------------------------------------------- // public slot virtual [base kpView] void kpUnzoomedThumbnailView::adjustToEnvironment () { if (!buddyView () || !buddyViewScrollableContainer () || !document ()) { return; } const int scrollViewContentsX = buddyViewScrollableContainer()->horizontalScrollBar()->value(); const int scrollViewContentsY = buddyViewScrollableContainer ()->verticalScrollBar()->value(); +#if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + qCDebug(kpLogViews) << "kpUnzoomedThumbnailView(" << name () + << ")::adjustToEnvironment(" + << scrollViewContentsX + << "," + << scrollViewContentsY + << ") width=" << width () + << " height=" << height () + << endl; +#endif + + #if 1 int x; if (document ()->width () > width ()) { x = static_cast (buddyView ()->transformViewToDocX (scrollViewContentsX)); const int rightMostAllowedX = qMax (0, document ()->width () - width ()); + #if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW qCDebug(kpLogViews) << "\tdocX=" << x << " docWidth=" << document ()->width () << " rightMostAllowedX=" << rightMostAllowedX; + #endif if (x > rightMostAllowedX) { x = rightMostAllowedX; } } // Thumbnail width <= doc width else { // Center X (rather than flush left to be consistent with // kpZoomedThumbnailView) x = -(width () - document ()->width ()) / 2; } int y; if (document ()->height () > height ()) { y = static_cast (buddyView ()->transformViewToDocY (scrollViewContentsY)); const int bottomMostAllowedY = qMax (0, document ()->height () - height ()); + #if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW qCDebug(kpLogViews) << "\tdocY=" << y << " docHeight=" << document ()->height () << " bottomMostAllowedY=" << bottomMostAllowedY; + #endif if (y > bottomMostAllowedY) { y = bottomMostAllowedY; } } // Thumbnail height <= doc height else { // Center Y (rather than flush top to be consistent with // kpZoomedThumbnailView) y = -(height () - document ()->height ()) / 2; } // Prefer to keep visible area centred in thumbnail instead of flushed left. // Gives more editing context to the left and top. // But feels awkward for left-to-right users. So disabled for now. // Not totally tested. #else if (!buddyViewScrollableContainer ()) { return; } QRect docRect = buddyView ()->transformViewToDoc ( QRect (buddyViewScrollableContainer ()->horizontalScrollBar()->value(), buddyViewScrollableContainer ()->verticalScrollBar()->value(), qMin (buddyView ()->width (), buddyViewScrollableContainer ()->viewport()->width ()), qMin (buddyView ()->height (), buddyViewScrollableContainer ()->viewport()->height ()))); x = docRect.x () - (width () - docRect.width ()) / 2; qCDebug(kpLogViews) << "\tnew suggest x=" << x; const int rightMostAllowedX = qMax (0, document ()->width () - width ()); if (x < 0) { x = 0; } if (x > rightMostAllowedX) { x = rightMostAllowedX; } y = docRect.y () - (height () - docRect.height ()) / 2; qCDebug(kpLogViews) << "\tnew suggest y=" << y; const int bottomMostAllowedY = qMax (0, document ()->height () - height ()); if (y < 0) { y = 0; } if (y > bottomMostAllowedY) { y = bottomMostAllowedY; } #endif if (viewManager ()) { viewManager ()->setFastUpdates (); viewManager ()->setQueueUpdates (); } { // OPT: scrollView impl would be much, much faster setOrigin (QPoint (-x, -y)); setMaskToCoverDocument (); // Above might be a NOP even if e.g. doc size changed so force // update if (viewManager ()) { viewManager ()->updateView (this); } } if (viewManager ()) { viewManager ()->restoreQueueUpdates (); viewManager ()->restoreFastUpdates (); } } + + diff --git a/views/kpView.cpp b/views/kpView.cpp index 872cb607..0497016e 100644 --- a/views/kpView.cpp +++ b/views/kpView.cpp @@ -1,669 +1,688 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2005 Kazuki Ohta Copyright (c) 2010 Tasuku Suzuki 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_VIEW 0 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) #include "kpView.h" #include "kpViewPrivate.h" #include #include #include #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" //--------------------------------------------------------------------- // public static const int kpView::MinZoomLevel = 1; const int kpView::MaxZoomLevel = 3200; //--------------------------------------------------------------------- kpView::kpView (kpDocument *document, kpToolToolBar *toolToolBar, kpViewManager *viewManager, kpView *buddyView, kpViewScrollableContainer *scrollableContainer, QWidget *parent) : QWidget (parent), d (new kpViewPrivate ()) { d->document = document; d->toolToolBar = toolToolBar; d->viewManager = viewManager; d->buddyView = buddyView; d->scrollableContainer = scrollableContainer; d->hzoom = 100; d->vzoom = 100; d->origin = QPoint (0, 0); d->showGrid = false; d->isBuddyViewScrollableContainerRectangleShown = false; // Don't waste CPU drawing default background since its overridden by // our fully opaque drawing. In reality, this seems to make no // difference in performance. setAttribute(Qt::WA_OpaquePaintEvent, true); setFocusPolicy (Qt::WheelFocus); setMouseTracking (true); // mouseMoveEvent's even when no mousebtn down setAttribute (Qt::WA_KeyCompression, true); } //--------------------------------------------------------------------- kpView::~kpView () { setHasMouse (false); delete d; } //--------------------------------------------------------------------- // public kpDocument *kpView::document () const { return d->document; } //--------------------------------------------------------------------- // protected kpAbstractSelection *kpView::selection () const { return document () ? document ()->selection () : nullptr; } //--------------------------------------------------------------------- // protected kpTextSelection *kpView::textSelection () const { return document () ? document ()->textSelection () : nullptr; } //--------------------------------------------------------------------- // public kpToolToolBar *kpView::toolToolBar () const { return d->toolToolBar; } // protected kpTool *kpView::tool () const { return toolToolBar () ? toolToolBar ()->tool () : nullptr; } // public kpViewManager *kpView::viewManager () const { return d->viewManager; } // public kpView *kpView::buddyView () const { return d->buddyView; } // public kpViewScrollableContainer *kpView::buddyViewScrollableContainer () const { return (buddyView () ? buddyView ()->scrollableContainer () : nullptr); } // public kpViewScrollableContainer *kpView::scrollableContainer () const { return d->scrollableContainer; } // public int kpView::zoomLevelX () const { return d->hzoom; } // public int kpView::zoomLevelY () const { return d->vzoom; } // public virtual void kpView::setZoomLevel (int hzoom, int vzoom) { hzoom = qBound (MinZoomLevel, hzoom, MaxZoomLevel); vzoom = qBound (MinZoomLevel, vzoom, MaxZoomLevel); if (hzoom == d->hzoom && vzoom == d->vzoom) { return; } d->hzoom = hzoom; d->vzoom = vzoom; if (viewManager ()) { viewManager ()->updateView (this); } emit zoomLevelChanged (hzoom, vzoom); } // public QPoint kpView::origin () const { return d->origin; } // public virtual void kpView::setOrigin (const QPoint &origin) { +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "kpView(" << objectName () << ")::setOrigin" << origin; +#endif if (origin == d->origin) { + #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tNOP"; + #endif return; } d->origin = origin; if (viewManager ()) { viewManager ()->updateView (this); } emit originChanged (origin); } // public bool kpView::canShowGrid () const { // (minimum zoom level < 400% would probably be reported as a bug by // users who thought that the grid was a part of the image!) return ((zoomLevelX () >= 400 && zoomLevelX () % 100 == 0) && (zoomLevelY () >= 400 && zoomLevelY () % 100 == 0)); } // public bool kpView::isGridShown () const { return d->showGrid; } // public void kpView::showGrid (bool yes) { if (d->showGrid == yes) { return; } if (yes && !canShowGrid ()) { return; } d->showGrid = yes; if (viewManager ()) { viewManager ()->updateView (this); } } // public bool kpView::isBuddyViewScrollableContainerRectangleShown () const { return d->isBuddyViewScrollableContainerRectangleShown; } // public void kpView::showBuddyViewScrollableContainerRectangle (bool yes) { if (yes == d->isBuddyViewScrollableContainerRectangleShown) { return; } d->isBuddyViewScrollableContainerRectangleShown = yes; if (d->isBuddyViewScrollableContainerRectangleShown) { // Got these connect statements by analysing deps of // updateBuddyViewScrollableContainerRectangle() rect update code. connect (this, &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (this, &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); if (buddyViewScrollableContainer ()) { connect (buddyViewScrollableContainer (), &kpViewScrollableContainer::contentsMoved, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (buddyViewScrollableContainer (), &kpViewScrollableContainer::resized, this, &kpView::updateBuddyViewScrollableContainerRectangle); } if (buddyView ()) { connect (buddyView (), &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (buddyView (), &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (buddyView (), static_cast(&kpView::sizeChanged), this, &kpView::updateBuddyViewScrollableContainerRectangle); } } else { disconnect (this, &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (this, &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); if (buddyViewScrollableContainer ()) { disconnect (buddyViewScrollableContainer (), &kpViewScrollableContainer::contentsMoved, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (buddyViewScrollableContainer (), &kpViewScrollableContainer::resized, this, &kpView::updateBuddyViewScrollableContainerRectangle); } if (buddyView ()) { disconnect (buddyView (), &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (buddyView (), &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (buddyView (), static_cast(&kpView::sizeChanged), this, &kpView::updateBuddyViewScrollableContainerRectangle); } } updateBuddyViewScrollableContainerRectangle (); } // protected QRect kpView::buddyViewScrollableContainerRectangle () const { return d->buddyViewScrollableContainerRectangle; } // protected slot void kpView::updateBuddyViewScrollableContainerRectangle () { if (viewManager ()) { viewManager ()->setQueueUpdates (); } { if (d->buddyViewScrollableContainerRectangle.isValid ()) { if (viewManager ()) { // Erase last viewManager ()->updateViewRectangleEdges (this, d->buddyViewScrollableContainerRectangle); } } QRect newRect; if (isBuddyViewScrollableContainerRectangleShown () && buddyViewScrollableContainer () && buddyView ()) { QRect docRect = buddyView ()->transformViewToDoc ( QRect (buddyViewScrollableContainer ()->horizontalScrollBar()->value(), buddyViewScrollableContainer ()->verticalScrollBar()->value(), qMin (buddyView ()->width (), buddyViewScrollableContainer ()->viewport()->width ()), qMin (buddyView ()->height (), buddyViewScrollableContainer ()->viewport()->height ()))); QRect viewRect = this->transformDocToView (docRect); // (Surround the area of interest by moving outwards by 1 pixel in each // direction - don't overlap area) newRect = QRect (viewRect.x () - 1, viewRect.y () - 1, viewRect.width () + 2, viewRect.height () + 2); } else { newRect = QRect (); } if (newRect != d->buddyViewScrollableContainerRectangle) { // (must set before updateView() for paintEvent() to see new // rect) d->buddyViewScrollableContainerRectangle = newRect; if (newRect.isValid ()) { if (viewManager ()) { viewManager ()->updateViewRectangleEdges (this, d->buddyViewScrollableContainerRectangle); } } } } if (viewManager ()) { viewManager ()->restoreQueueUpdates (); } } //--------------------------------------------------------------------- // public double kpView::transformViewToDocX (double viewX) const { return (viewX - origin ().x ()) * 100.0 / zoomLevelX (); } //--------------------------------------------------------------------- // public double kpView::transformViewToDocY (double viewY) const { return (viewY - origin ().y ()) * 100.0 / zoomLevelY (); } //--------------------------------------------------------------------- // public QPoint kpView::transformViewToDoc (const QPoint &viewPoint) const { return {static_cast (transformViewToDocX (viewPoint.x ())), static_cast (transformViewToDocY (viewPoint.y ()))}; } //--------------------------------------------------------------------- // public QRect kpView::transformViewToDoc (const QRect &viewRect) const { if (zoomLevelX () == 100 && zoomLevelY () == 100) { return {viewRect.x () - origin ().x (), viewRect.y () - origin ().y (), viewRect.width (), viewRect.height ()}; } const QPoint docTopLeft = transformViewToDoc (viewRect.topLeft ()); // (don't call transformViewToDoc[XY]() - need to round up dimensions) const auto docWidth = qRound (double (viewRect.width ()) * 100.0 / double (zoomLevelX ())); const auto docHeight = qRound (double (viewRect.height ()) * 100.0 / double (zoomLevelY ())); // (like QWMatrix::Areas) return {docTopLeft.x (), docTopLeft.y (), docWidth, docHeight}; } //--------------------------------------------------------------------- // public double kpView::transformDocToViewX (double docX) const { return (docX * zoomLevelX () / 100.0) + origin ().x (); } // public double kpView::transformDocToViewY (double docY) const { return (docY * zoomLevelY () / 100.0) + origin ().y (); } // public QPoint kpView::transformDocToView (const QPoint &docPoint) const { return {static_cast (transformDocToViewX (docPoint.x ())), static_cast (transformDocToViewY (docPoint.y ()))}; } // public QRect kpView::transformDocToView (const QRect &docRect) const { if (zoomLevelX () == 100 && zoomLevelY () == 100) { return {docRect.x () + origin ().x (), docRect.y () + origin ().y (), docRect.width (), docRect.height ()}; } const QPoint viewTopLeft = transformDocToView (docRect.topLeft ()); // (don't call transformDocToView[XY]() - need to round up dimensions) const int viewWidth = qRound (double (docRect.width ()) * double (zoomLevelX ()) / 100.0); const int viewHeight = qRound (double (docRect.height ()) * double (zoomLevelY ()) / 100.0); // (like QWMatrix::Areas) return QRect (viewTopLeft.x (), viewTopLeft.y (), viewWidth, viewHeight); } // public QPoint kpView::transformViewToOtherView (const QPoint &viewPoint, const kpView *otherView) { if (this == otherView) { return viewPoint; } const double docX = transformViewToDocX (viewPoint.x ()); const double docY = transformViewToDocY (viewPoint.y ()); const double otherViewX = otherView->transformDocToViewX (docX); const double otherViewY = otherView->transformDocToViewY (docY); return {static_cast (otherViewX), static_cast (otherViewY)}; } // public int kpView::zoomedDocWidth () const { return document () ? document ()->width () * zoomLevelX () / 100 : 0; } // public int kpView::zoomedDocHeight () const { return document () ? document ()->height () * zoomLevelY () / 100 : 0; } // public void kpView::setHasMouse (bool yes) { kpViewManager *vm = viewManager (); if (!vm) { return; } +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::setHasMouse(" << yes << ") existing viewUnderCursor=" << (vm->viewUnderCursor () ? vm->viewUnderCursor ()->objectName () : "(none)"); +#endif if (yes && vm->viewUnderCursor () != this) { vm->setViewUnderCursor (this); } else if (!yes && vm->viewUnderCursor () == this) { vm->setViewUnderCursor (nullptr); } } //--------------------------------------------------------------------- // public void kpView::addToQueuedArea (const QRegion ®ion) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::addToQueuedArea() already=" << d->queuedUpdateArea - << " - plus - " << region; + << " - plus - " << region + << endl; +#endif d->queuedUpdateArea += region; } //--------------------------------------------------------------------- // public void kpView::addToQueuedArea (const QRect &rect) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::addToQueuedArea() already=" << d->queuedUpdateArea - << " - plus - " << rect; + << " - plus - " << rect + << endl; +#endif d->queuedUpdateArea += rect; } //--------------------------------------------------------------------- // public void kpView::invalidateQueuedArea () { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView::invalidateQueuedArea()"; +#endif d->queuedUpdateArea = QRegion (); } //--------------------------------------------------------------------- // public void kpView::updateQueuedArea () { kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::updateQueuedArea() vm=" << (bool) vm << " queueUpdates=" << (vm && vm->queueUpdates ()) << " fastUpdates=" << (vm && vm->fastUpdates ()) - << " area=" << d->queuedUpdateArea; + << " area=" << d->queuedUpdateArea + << endl; +#endif if (!vm) { return; } if (vm->queueUpdates ()) { return; } if (!d->queuedUpdateArea.isEmpty ()) { vm->updateView (this, d->queuedUpdateArea); } invalidateQueuedArea (); } //--------------------------------------------------------------------- // public QPoint kpView::mouseViewPoint (const QPoint &returnViewPoint) const { if (returnViewPoint != KP_INVALID_POINT) { return returnViewPoint; } // TODO: I don't think this is right for the main view since that's // inside the scrollview (which can scroll). return mapFromGlobal (QCursor::pos ()); } //--------------------------------------------------------------------- // public virtual QVariant kpView::inputMethodQuery (Qt::InputMethodQuery query) const { +#if DEBUG_KP_VIEW && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::inputMethodQuery()"; +#endif QVariant ret; switch (query) { case Qt::ImMicroFocus: { QRect r = d->viewManager->textCursorRect (); r.setTopLeft (r.topLeft () + origin ()); r.setHeight (r.height() + 2); r = transformDocToView (r); ret = r; break; } case Qt::ImFont: { if (textSelection ()) { ret = textSelection ()->textStyle ().font (); } break; } default: break; } return ret; } //--------------------------------------------------------------------- diff --git a/views/kpView_Events.cpp b/views/kpView_Events.cpp index 8c3b9376..e47d7c9d 100644 --- a/views/kpView_Events.cpp +++ b/views/kpView_Events.cpp @@ -1,239 +1,281 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2005 Kazuki Ohta Copyright (c) 2010 Tasuku Suzuki 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_VIEW 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) + + #include "views/kpView.h" #include "kpViewPrivate.h" +#if DEBUG_KP_VIEW #include "kpLogCategories.h" +#endif #include #include #include "tools/kpTool.h" //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpView::mouseMoveEvent (QMouseEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::mouseMoveEvent (" - << e->x () << "," << e->y () << ")"; + << e->x () << "," << e->y () << ")" + << endl; +#endif // TODO: This is wrong if you leaveEvent the mainView by mouseMoving on the // mainView, landing on top of the thumbnailView cleverly put on top // of the mainView. setHasMouse (rect ().contains (e->pos ())); if (tool ()) { tool ()->mouseMoveEvent (e); } e->accept (); } // protected virtual [base QWidget] void kpView::mousePressEvent (QMouseEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::mousePressEvent (" - << e->x () << "," << e->y () << ")"; + << e->x () << "," << e->y () << ")" + << endl; +#endif setHasMouse (true); if (tool ()) { tool ()->mousePressEvent (e); } e->accept (); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpView::mouseReleaseEvent (QMouseEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::mouseReleaseEvent (" - << e->x () << "," << e->y () << ")"; + << e->x () << "," << e->y () << ")" + << endl; +#endif setHasMouse (rect ().contains (e->pos ())); if (tool ()) { tool ()->mouseReleaseEvent (e); } e->accept (); } //--------------------------------------------------------------------- // public virtual [base QWidget] void kpView::wheelEvent (QWheelEvent *e) { if (tool ()) { tool ()->wheelEvent (e); } } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpView::keyPressEvent (QKeyEvent *e) { +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "kpView(" << objectName () << ")::keyPressEvent()" << e->text(); +#endif if (tool ()) { tool ()->keyPressEvent (e); } e->accept (); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpView::keyReleaseEvent (QKeyEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::keyReleaseEvent()"; +#endif if (tool ()) { tool ()->keyReleaseEvent (e); } e->accept (); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpView::inputMethodEvent (QInputMethodEvent *e) { +#if DEBUG_KP_VIEW && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::inputMethodEvent()"; +#endif if (tool ()) { tool ()->inputMethodEvent (e); } e->accept (); } // protected virtual [base QWidget] bool kpView::event (QEvent *e) { +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "kpView::event() invoking kpTool::event()"; +#endif if (tool () && tool ()->viewEvent (e)) { + #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tkpView::event() - tool said eat event, ret true"; + #endif return true; } +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tkpView::event() - no tool or said false, call QWidget::event()"; +#endif return QWidget::event (e); } // protected virtual [base QWidget] void kpView::focusInEvent (QFocusEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::focusInEvent()"; +#endif if (tool ()) { tool ()->focusInEvent (e); } } // protected virtual [base QWidget] void kpView::focusOutEvent (QFocusEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::focusOutEvent()"; +#endif if (tool ()) { tool ()->focusOutEvent (e); } } // protected virtual [base QWidget] void kpView::enterEvent (QEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::enterEvent()"; +#endif // Don't call setHasMouse(true) as it displays the brush cursor (if // active) when dragging open a menu and then dragging // past the extents of the menu due to Qt sending us an EnterEvent. // We're already covered by MouseMoveEvent anyway. // // But disabling this causes a more serious problem: RMB on a text // box and Esc. We have no other reliable way to determine if the // mouse is still above the view (user could have moved mouse out // while RMB menu was up) and hence the cursor is not updated. setHasMouse (true); if (tool ()) { tool ()->enterEvent (e); } } // protected virtual [base QWidget] void kpView::leaveEvent (QEvent *e) { +#if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::leaveEvent()"; +#endif setHasMouse (false); if (tool ()) { tool ()->leaveEvent (e); } } // protected virtual [base QWidget] void kpView::dragEnterEvent (QDragEnterEvent *) { +#if DEBUG_KP_VIEW && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::dragEnterEvent()"; +#endif setHasMouse (true); } // protected virtual [base QWidget] void kpView::dragLeaveEvent (QDragLeaveEvent *) { +#if DEBUG_KP_VIEW && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::dragLeaveEvent"; +#endif setHasMouse (false); } // protected virtual [base QWidget] void kpView::resizeEvent (QResizeEvent *e) { +#if DEBUG_KP_VIEW && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::resizeEvent(" << e->size () << " vs actual=" << size () - << ") old=" << e->oldSize (); + << ") old=" << e->oldSize () << endl; +#endif QWidget::resizeEvent (e); emit sizeChanged (width (), height ()); emit sizeChanged (size ()); } diff --git a/views/kpView_Paint.cpp b/views/kpView_Paint.cpp index 70704951..1a30359b 100644 --- a/views/kpView_Paint.cpp +++ b/views/kpView_Paint.cpp @@ -1,574 +1,644 @@ /* Copyright (c) 2003-2007 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_VIEW 0 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) #include "views/kpView.h" #include "kpViewPrivate.h" #include #include #include #include #include "kpLogCategories.h" #include "layers/selections/kpAbstractSelection.h" #include "imagelib/kpColor.h" #include "document/kpDocument.h" #include "layers/tempImage/kpTempImage.h" #include "layers/selections/text/kpTextSelection.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" //--------------------------------------------------------------------- // protected QRect kpView::paintEventGetDocRect (const QRect &viewRect) const { +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "kpView::paintEventGetDocRect(" << viewRect << ")"; +#endif QRect docRect; // From the "we aren't sure whether to round up or round down" department: if (zoomLevelX () < 100 || zoomLevelY () < 100) { docRect = transformViewToDoc (viewRect); } else { // think of a grid - you need to fully cover the zoomed-in pixels // when docRect is zoomed back to the view later docRect = QRect (transformViewToDoc (viewRect.topLeft ()), // round down transformViewToDoc (viewRect.bottomRight ())); // round down } if (zoomLevelX () % 100 || zoomLevelY () % 100) { // at least round up the bottom-right point and deal with matrix weirdness: // - helpful because it ensures we at least cover the required area // at e.g. 67% or 573% docRect.setBottomRight (docRect.bottomRight () + QPoint (2, 2)); } +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tdocRect=" << docRect; +#endif kpDocument *doc = document (); if (doc) { docRect = docRect.intersected (doc->rect ()); #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tintersected with doc=" << docRect; #endif } return docRect; } //--------------------------------------------------------------------- // public static void kpView::drawTransparentBackground (QPainter *painter, const QPoint &patternOrigin, const QRect &viewRect, bool isPreview) { +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "kpView::drawTransparentBackground() patternOrigin=" << patternOrigin << " viewRect=" << viewRect - << " isPreview=" << isPreview; + << " isPreview=" << isPreview + << endl; +#endif const int cellSize = !isPreview ? 16 : 10; // TODO: % is unpredictable with negatives. int starty = viewRect.y (); if ((starty - patternOrigin.y ()) % cellSize) { starty -= ((starty - patternOrigin.y ()) % cellSize); } int startx = viewRect.x (); if ((startx - patternOrigin.x ()) % cellSize) { startx -= ((startx - patternOrigin.x ()) % cellSize); } +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tstartXY=" << QPoint (startx, starty); +#endif painter->save (); // Clip to as we may draw outside it on all sides. painter->setClipRect (viewRect, Qt::IntersectClip/*honor existing clip*/); for (int y = starty; y <= viewRect.bottom (); y += cellSize) { for (int x = startx; x <= viewRect.right (); x += cellSize) { bool parity = ((x - patternOrigin.x ()) / cellSize + (y - patternOrigin.y ()) / cellSize) % 2; QColor col; if (parity) { if (!isPreview) { col = QColor (213, 213, 213); } else { col = QColor (224, 224, 224); } } else { col = Qt::white; } painter->fillRect (x, y, cellSize, cellSize, col); } } painter->restore (); } //--------------------------------------------------------------------- // protected void kpView::paintEventDrawCheckerBoard (QPainter *painter, const QRect &viewRect) { +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::paintEventDrawCheckerBoard(viewRect=" << viewRect << ") origin=" << origin (); +#endif kpDocument *doc = document (); if (!doc) { return; } QPoint patternOrigin = origin (); if (scrollableContainer ()) { + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tscrollableContainer: contents[XY]=" << QPoint (scrollableContainer ()->horizontalScrollBar()->value (), - scrollableContainer ()->verticalScrollBar()->value ()); + scrollableContainer ()->verticalScrollBar()->value ()) + << endl; + #endif // Make checkerboard appear static relative to the scroll view. // This makes it more obvious that any visible bits of the // checkboard represent transparent pixels and not gray and white // squares. patternOrigin = QPoint (scrollableContainer ()->horizontalScrollBar()->value(), scrollableContainer ()->verticalScrollBar()->value()); + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\t\tpatternOrigin=" << patternOrigin; + #endif } // TODO: this static business doesn't work yet patternOrigin = QPoint (0, 0); drawTransparentBackground (painter, patternOrigin, viewRect); } //--------------------------------------------------------------------- // protected void kpView::paintEventDrawSelection (QImage *destPixmap, const QRect &docRect) { +#if DEBUG_KP_VIEW_RENDERER && 1 || 0 qCDebug(kpLogViews) << "kpView::paintEventDrawSelection() docRect=" << docRect; +#endif kpDocument *doc = document (); if (!doc) { + #if DEBUG_KP_VIEW_RENDERER && 1 || 0 qCDebug(kpLogViews) << "\tno doc - abort"; + #endif return; } kpAbstractSelection *sel = doc->selection (); if (!sel) { + #if DEBUG_KP_VIEW_RENDERER && 1 || 0 qCDebug(kpLogViews) << "\tno sel - abort"; + #endif return; } // // Draw selection pixmap (if there is one) // +#if DEBUG_KP_VIEW_RENDERER && 1 || 0 qCDebug(kpLogViews) << "\tdraw sel pixmap @ " << sel->topLeft (); +#endif sel->paint (destPixmap, docRect); // // Draw selection border // kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW_RENDERER && 1 || 0 qCDebug(kpLogViews) << "\tsel border visible=" << vm->selectionBorderVisible (); +#endif if (vm->selectionBorderVisible ()) { sel->paintBorder (destPixmap, docRect, vm->selectionBorderFinished ()); } // // Draw text cursor // // TODO: It would be nice to display the text cursor even if it's not // within the text box (this can happen if the text box is too // small for the text it contains). // // However, too much selection repaint code assumes that it // only paints inside its kpAbstractSelection::boundingRect(). auto *textSel = dynamic_cast (sel); if (textSel && vm->textCursorEnabled () && (vm->textCursorBlinkState () || // For the current main window: // As long as _any_ view has focus, blink _all_ views not just the // one with focus. !vm->hasAViewWithFocus ())) // sync: call will break when vm is not held by 1 mainWindow { QRect rect = vm->textCursorRect (); rect = rect.intersected (textSel->textAreaRect ()); if (!rect.isEmpty ()) { kpPixmapFX::fillRect(destPixmap, rect.x () - docRect.x (), rect.y () - docRect.y (), rect.width (), rect.height (), kpColor::LightGray, kpColor::DarkGray); } } } //--------------------------------------------------------------------- // protected void kpView::paintEventDrawSelectionResizeHandles (const QRect &clipRect) { +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "kpView::paintEventDrawSelectionResizeHandles(" << clipRect << ")"; +#endif if (!selectionLargeEnoughToHaveResizeHandles ()) { + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tsel not large enough to have resize handles"; + #endif return; } kpViewManager *vm = viewManager (); if (!vm || !vm->selectionBorderVisible () || !vm->selectionBorderFinished ()) { + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tsel border not visible or not finished"; + #endif + return; } const QRect selViewRect = selectionViewRect (); +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tselViewRect=" << selViewRect; +#endif if (!selViewRect.intersects (clipRect)) { + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tdoesn't intersect viewRect"; + #endif return; } QRegion selResizeHandlesRegion = selectionResizeHandlesViewRegion (true/*for renderer*/); +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tsel resize handles view region=" - << selResizeHandlesRegion; + << selResizeHandlesRegion << endl; +#endif QPainter painter(this); painter.setPen(Qt::black); painter.setBrush(Qt::cyan); for (const auto &r : selResizeHandlesRegion.rects()) { painter.drawRect(r); } } //--------------------------------------------------------------------- // protected void kpView::paintEventDrawTempImage (QImage *destPixmap, const QRect &docRect) { kpViewManager *vm = viewManager (); if (!vm) { return; } const kpTempImage *tpi = vm->tempImage (); +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "kpView::paintEventDrawTempImage() tempImage=" << tpi << " isVisible=" - << (tpi ? tpi->isVisible (vm) : false); + << (tpi ? tpi->isVisible (vm) : false) + << endl; +#endif if (!tpi || !tpi->isVisible (vm)) { return; } tpi->paint (destPixmap, docRect); } //--------------------------------------------------------------------- // protected void kpView::paintEventDrawGridLines (QPainter *painter, const QRect &viewRect) { int hzoomMultiple = zoomLevelX () / 100; int vzoomMultiple = zoomLevelY () / 100; painter->setPen(Qt::gray); // horizontal lines int starty = viewRect.top(); if (starty % vzoomMultiple) { starty = (starty + vzoomMultiple) / vzoomMultiple * vzoomMultiple; } for (int y = starty; y <= viewRect.bottom(); y += vzoomMultiple) { painter->drawLine(viewRect.left(), y, viewRect.right(), y); } // vertical lines int startx = viewRect.left(); if (startx % hzoomMultiple) { startx = (startx + hzoomMultiple) / hzoomMultiple * hzoomMultiple; } for (int x = startx; x <= viewRect.right(); x += hzoomMultiple) { painter->drawLine(x, viewRect.top (), x, viewRect.bottom()); } } //--------------------------------------------------------------------- // This is called "_Unclipped" because it may draw outside of // . // // There are 2 reasons for doing so: // // A. If, for instance: // // 1. = QRect (0, 0, 2, 3) [top-left of the view] // 2. zoomLevelX() == 800 // 3. zoomLevelY() == 800 // // Then, the local variable will be QRect (0, 0, 1, 1). // When the part of the document corresponding to // (a single document pixel) is drawn with QPainter::scale(), the // view rectangle QRect (0, 0, 7, 7) will be overwritten due to the // 8x zoom. This view rectangle is bigger than . // // We can't use QPainter::setClipRect() since it is buggy in Qt 4.3.1 // and clips too many pixels when used in combination with scale() // [qt-bugs@trolltech.com issue N181038]. ==> MK 10.2.2011 - fixed since Qt-4.4.4 // // B. paintEventGetDocRect() may, by design, return a larger document // rectangle than what corresponds to, if the zoom levels // are not perfectly divisible by 100. // // This over-drawing is dangerous -- see the comments in paintEvent(). // This over-drawing is only safe from Qt's perspective since Qt // automatically clips all drawing in paintEvent() (which calls us) to // QPaintEvent::region(). void kpView::paintEventDrawDoc_Unclipped (const QRect &viewRect) { +#if DEBUG_KP_VIEW_RENDERER QTime timer; timer.start (); qCDebug(kpLogViews) << "\tviewRect=" << viewRect; +#endif kpViewManager *vm = viewManager (); const kpDocument *doc = document (); Q_ASSERT (vm); Q_ASSERT (doc); if (viewRect.isEmpty ()) { return; } QRect docRect = paintEventGetDocRect (viewRect); +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tdocRect=" << docRect; +#endif QPainter painter (this); //painter.setCompositionMode(QPainter::CompositionMode_Source); QImage docPixmap; bool tempImageWillBeRendered = false; // LOTODO: I think being empty would be a bug. if (!docRect.isEmpty ()) { docPixmap = doc->getImageAt (docRect); + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tdocPixmap.hasAlphaChannel()=" << docPixmap.hasAlphaChannel (); + #endif tempImageWillBeRendered = (!doc->selection () && vm->tempImage () && vm->tempImage ()->isVisible (vm) && docRect.intersects (vm->tempImage ()->rect ())); + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\ttempImageWillBeRendered=" << tempImageWillBeRendered << " (sel=" << doc->selection () << " tempImage=" << vm->tempImage () << " tempImage.isVisible=" << (vm->tempImage () ? vm->tempImage ()->isVisible (vm) : false) << " docRect.intersects(tempImage.rect)=" << (vm->tempImage () ? docRect.intersects (vm->tempImage ()->rect ()) : false) - << ")"; + << ")" + << endl; + #endif } // // Draw checkboard for transparent images and/or views with borders // if (docPixmap.hasAlphaChannel() || (tempImageWillBeRendered && vm->tempImage ()->paintMayAddMask ())) { paintEventDrawCheckerBoard (&painter, viewRect); } if (!docRect.isEmpty ()) { // // Draw docPixmap + tempImage // if (doc->selection ()) { paintEventDrawSelection (&docPixmap, docRect); } else if (tempImageWillBeRendered) { paintEventDrawTempImage (&docPixmap, docRect); } + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\torigin=" << origin (); + #endif // Blit scaled version of docPixmap + tempImage. + #if DEBUG_KP_VIEW_RENDERER && 1 QTime scaleTimer; scaleTimer.start (); + #endif // This is the only troublesome part of the method that draws unclipped. painter.translate (origin ().x (), origin ().y ()); painter.scale (double (zoomLevelX ()) / 100.0, double (zoomLevelY ()) / 100.0); painter.drawImage (docRect, docPixmap); //painter.resetMatrix (); // back to 1-1 scaling + #if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tscale time=" << scaleTimer.elapsed (); + #endif } // if (!docRect.isEmpty ()) { +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tdrawDocRect done in: " << timer.restart () << "ms"; +#endif } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpView::paintEvent (QPaintEvent *e) { // sync: kpViewPrivate // WARNING: document(), viewManager() and friends might be 0 in this method. // TODO: I'm not 100% convinced that we always check if their friends are 0. +#if DEBUG_KP_VIEW_RENDERER && 1 QTime timer; timer.start (); +#endif kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::paintEvent() vm=" << (bool) vm << " queueUpdates=" << (vm && vm->queueUpdates ()) << " fastUpdates=" << (vm && vm->fastUpdates ()) << " viewRect=" << e->rect () - << " topLeft=" << QPoint (x (), y ()); + << " topLeft=" << QPoint (x (), y ()) + << endl; +#endif if (!vm) { return; } if (vm->queueUpdates ()) { // OPT: if this update was due to the document, // use document coordinates (in case of a zoom change in // which view coordinates become out of date) addToQueuedArea (e->region ()); return; } kpDocument *doc = document (); if (!doc) { return; } // It seems that e->region() is already clipped by Qt to the visible // part of the view (which could be quite small inside a scrollview). const auto& viewRegion = e->region (); QVector rects = viewRegion.rects (); +#if DEBUG_KP_VIEW_RENDERER qCDebug(kpLogViews) << "\t#rects = " << rects.count (); +#endif // Draw all of the requested regions of the document _before_ drawing // the grid lines, buddy rectangle and selection resize handles. // This ordering is important since paintEventDrawDoc_Unclipped() // may draw outside of the view rectangle passed to it. // // To illustrate this, suppose we changed each iteration of the loop // to call paintEventDrawDoc_Unclipped() _and_ then, // paintEventDrawGridLines(). If there are 2 or more iterations of this // loop, paintEventDrawDoc_Unclipped() in one iteration may draw over // parts of nearby grid lines (which were drawn in a previous iteration) // with document pixels. Those grid line parts are probably not going to // be redrawn, so will appear to be missing. for (const auto &r : rects) { paintEventDrawDoc_Unclipped (r); } // // Draw Grid Lines // if ( isGridShown() ) { QPainter painter(this); for (const auto &r : rects) paintEventDrawGridLines(&painter, r); } const QRect r = buddyViewScrollableContainerRectangle(); if ( !r.isEmpty() ) { QPainter painter(this); painter.setPen(QPen(Qt::lightGray, 1/*width*/, Qt::DotLine)); painter.setBackground(Qt::darkGray); painter.setBackgroundMode(Qt::OpaqueMode); painter.drawRect(r.x(), r.y(), r.width() - 1, r.height() - 1); } if (doc->selection ()) { // Draw resize handles on top of possible grid lines paintEventDrawSelectionResizeHandles (e->rect ()); } +#if DEBUG_KP_VIEW_RENDERER && 1 qCDebug(kpLogViews) << "\tall done in: " << timer.restart () << "ms"; +#endif } + +//--------------------------------------------------------------------- diff --git a/views/kpView_Selections.cpp b/views/kpView_Selections.cpp index ce28d399..d5edd96e 100644 --- a/views/kpView_Selections.cpp +++ b/views/kpView_Selections.cpp @@ -1,353 +1,381 @@ /* Copyright (c) 2003-2007 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_VIEW 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) + + #include "views/kpView.h" #include "kpViewPrivate.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" -#include "kpLogCategories.h" // public QRect kpView::selectionViewRect () const { return selection () ? transformDocToView (selection ()->boundingRect ()) : QRect (); } // public QPoint kpView::mouseViewPointRelativeToSelection (const QPoint &viewPoint) const { if (!selection ()) { return KP_INVALID_POINT; } return mouseViewPoint (viewPoint) - transformDocToView (selection ()->topLeft ()); } // public bool kpView::mouseOnSelection (const QPoint &viewPoint) const { const QRect selViewRect = selectionViewRect (); if (!selViewRect.isValid ()) { return false; } return selViewRect.contains (mouseViewPoint (viewPoint)); } // public int kpView::textSelectionMoveBorderAtomicSize () const { if (!textSelection ()) { return 0; } return qMax (4, zoomLevelX () / 100); } // public bool kpView::mouseOnSelectionToMove (const QPoint &viewPoint) const { if (!mouseOnSelection (viewPoint)) { return false; } if (!textSelection ()) { return true; } if (mouseOnSelectionResizeHandle (viewPoint)) { return false; } const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); // Middle point should always be selectable const QPoint selCenterDocPoint = selection ()->boundingRect ().center (); if (tool () && tool ()->calculateCurrentPoint () == selCenterDocPoint) { return false; } const int atomicSize = textSelectionMoveBorderAtomicSize (); const QRect selViewRect = selectionViewRect (); return (viewPointRelSel.x () < atomicSize || viewPointRelSel.x () >= selViewRect.width () - atomicSize || viewPointRelSel.y () < atomicSize || viewPointRelSel.y () >= selViewRect.height () - atomicSize); } //--------------------------------------------------------------------- // protected bool kpView::selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const { if (!selection ()) { return false; } const QRect selViewRect = selectionViewRect (); return (selViewRect.width () >= atomicSize * 5 || selViewRect.height () >= atomicSize * 5); } //--------------------------------------------------------------------- // public int kpView::selectionResizeHandleAtomicSize () const { int atomicSize = qMin (13, qMax (9, zoomLevelX () / 100)); while (atomicSize > 0 && !selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (atomicSize)) { atomicSize--; } return atomicSize; } //--------------------------------------------------------------------- // public bool kpView::selectionLargeEnoughToHaveResizeHandles () const { return (selectionResizeHandleAtomicSize () > 0); } //--------------------------------------------------------------------- // public QRegion kpView::selectionResizeHandlesViewRegion (bool forRenderer) const { const int atomicLength = selectionResizeHandleAtomicSize (); if (atomicLength <= 0) { return QRegion (); } // HACK: At low zoom (e.g. 100%), resize handles will probably be too // big and overlap text / cursor / too much of selection. // // So limit the _visual_ size of handles at low zoom. The // handles' grab area remains the same for usability; so yes, // there are a few pixels that don't look grabable but they are. // // The real solution is to be able to partially render the // handles outside of the selection view rect. If not possible, // at least for text boxes, render text on top of handles. int normalAtomicLength = atomicLength; int vertEdgeAtomicLength = atomicLength; if (forRenderer && selection ()) { if (zoomLevelX () <= 150) { if (normalAtomicLength > 1) { normalAtomicLength--; } if (vertEdgeAtomicLength > 1) { vertEdgeAtomicLength--; } } // 1 line of text? if (textSelection () && textSelection ()->textLines ().size () == 1) { if (zoomLevelX () <= 150) { vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (2, zoomLevelX () / 100)); } else if (zoomLevelX () <= 250) { vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (3, zoomLevelX () / 100)); } } } const QRect selViewRect = selectionViewRect (); QRegion ret; // top left ret += QRect(0, 0, normalAtomicLength, normalAtomicLength); // top middle ret += QRect((selViewRect.width() - normalAtomicLength) / 2, 0, normalAtomicLength, normalAtomicLength); // top right ret += QRect(selViewRect.width() - normalAtomicLength - 1, 0, normalAtomicLength, normalAtomicLength); // left middle ret += QRect(0, (selViewRect.height() - vertEdgeAtomicLength) / 2, vertEdgeAtomicLength, vertEdgeAtomicLength); // right middle ret += QRect(selViewRect.width() - vertEdgeAtomicLength - 1, (selViewRect.height() - vertEdgeAtomicLength) / 2, vertEdgeAtomicLength, vertEdgeAtomicLength); // bottom left ret += QRect(0, selViewRect.height() - normalAtomicLength - 1, normalAtomicLength, normalAtomicLength); // bottom middle ret += QRect((selViewRect.width() - normalAtomicLength) / 2, selViewRect.height() - normalAtomicLength - 1, normalAtomicLength, normalAtomicLength); // bottom right ret += QRect(selViewRect.width() - normalAtomicLength - 1, selViewRect.height() - normalAtomicLength - 1, normalAtomicLength, normalAtomicLength); ret.translate (selViewRect.x (), selViewRect.y ()); ret = ret.intersected (selViewRect); return ret; } //--------------------------------------------------------------------- // public // REFACTOR: use QFlags as the return type for better type safety. int kpView::mouseOnSelectionResizeHandle (const QPoint &viewPoint) const { +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "kpView::mouseOnSelectionResizeHandle(viewPoint=" - << viewPoint << ")"; + << viewPoint << ")" << endl; +#endif if (!mouseOnSelection (viewPoint)) { + #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tmouse not on sel"; + #endif return 0; } const QRect selViewRect = selectionViewRect (); +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tselViewRect=" << selViewRect; +#endif const int atomicLength = selectionResizeHandleAtomicSize (); +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tatomicLength=" << atomicLength; +#endif if (atomicLength <= 0) { + #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tsel not large enough to have resize handles"; + #endif // Want to make it possible to move a small selection return 0; } const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tviewPointRelSel=" << viewPointRelSel; +#endif #define LOCAL_POINT_IN_BOX_AT(x,y) \ QRect ((x), (y), atomicLength, atomicLength).contains (viewPointRelSel) // Favour the bottom & right and the corners. if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, selViewRect.height () - atomicLength)) { return kpView::Bottom | kpView::Right; } if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0)) { return kpView::Top | kpView::Right; } if (LOCAL_POINT_IN_BOX_AT (0, selViewRect.height () - atomicLength)) { return kpView::Bottom | kpView::Left; } if (LOCAL_POINT_IN_BOX_AT (0, 0)) { return kpView::Top | kpView::Left; } if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, (selViewRect.height () - atomicLength) / 2)) { return kpView::Right; } if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, selViewRect.height () - atomicLength)) { return kpView::Bottom; } if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, 0)) { return kpView::Top; } if (LOCAL_POINT_IN_BOX_AT (0, (selViewRect.height () - atomicLength) / 2)) { return kpView::Left; } - - qCDebug(kpLogViews) << "\tnot on sel resize handle"; - return 0; - + else + { + #if DEBUG_KP_VIEW + qCDebug(kpLogViews) << "\tnot on sel resize handle"; + #endif + return 0; + } #undef LOCAL_POINT_IN_BOX_AT } // public bool kpView::mouseOnSelectionToSelectText (const QPoint &viewPoint) const { +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "kpView::mouseOnSelectionToSelectText(viewPoint=" - << viewPoint << ")"; + << viewPoint << ")" << endl; +#endif if (!mouseOnSelection (viewPoint)) { + #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tmouse non on sel"; + #endif return false; } if (!textSelection ()) { + #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tsel not text"; + #endif return false; } +#if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tmouse on sel: to move=" << mouseOnSelectionToMove () - << " to resize=" << mouseOnSelectionResizeHandle (); + << " to resize=" << mouseOnSelectionResizeHandle () + << endl; +#endif return (!mouseOnSelectionToMove (viewPoint) && !mouseOnSelectionResizeHandle (viewPoint)); } diff --git a/views/kpZoomedThumbnailView.cpp b/views/kpZoomedThumbnailView.cpp index 7f5dcf59..8a4b7494 100644 --- a/views/kpZoomedThumbnailView.cpp +++ b/views/kpZoomedThumbnailView.cpp @@ -1,126 +1,144 @@ /* Copyright (c) 2003-2007 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_ZOOMED_THUMBNAIL_VIEW 0 + + #include "views/kpZoomedThumbnailView.h" #include "kpLogCategories.h" #include "document/kpDocument.h" #include "views/manager/kpViewManager.h" -#include "kpLogCategories.h" #include //-------------------------------------------------------------------------------- kpZoomedThumbnailView::kpZoomedThumbnailView (kpDocument *document, kpToolToolBar *toolToolBar, kpViewManager *viewManager, kpView *buddyView, kpViewScrollableContainer *scrollableContainer, QWidget *parent ) : kpThumbnailView (document, toolToolBar, viewManager, buddyView, scrollableContainer, parent) { // Call to virtual function - this is why the class is sealed adjustToEnvironment (); } kpZoomedThumbnailView::~kpZoomedThumbnailView () = default; // public virtual [base kpThumbnailView] QString kpZoomedThumbnailView::caption () const { return i18n ("%1% - Thumbnail", zoomLevelX ()); } // public slot virtual [base kpView] void kpZoomedThumbnailView::adjustToEnvironment () { +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW + qCDebug(kpLogViews) << "kpZoomedThumbnailView(" << name () + << ")::adjustToEnvironment()" + << " width=" << width () + << " height=" << height () + << endl; +#endif + if (!document ()) { return; } +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW qCDebug(kpLogViews) << "\tdoc: width=" << document ()->width () - << " height=" << document ()->height (); + << " height=" << document ()->height () + << endl; +#endif if (document ()->width () <= 0 || document ()->height () <= 0) { qCCritical(kpLogViews) << "kpZoomedThumbnailView::adjustToEnvironment() doc:" << " width=" << document ()->width () << " height=" << document ()->height (); return; } int hzoom = qMax (1, width () * 100 / document ()->width ()); int vzoom = qMax (1, height () * 100 / document ()->height ()); // keep aspect ratio if (hzoom < vzoom) { vzoom = hzoom; } else { hzoom = vzoom; } +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW && 1 qCDebug(kpLogViews) << "\tproposed zoom=" << hzoom; +#endif if (hzoom > 100 || vzoom > 100) { + #if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW && 1 qCDebug(kpLogViews) << "\twon't magnify - setting zoom to 100%"; + #endif hzoom = 100; vzoom = 100; } if (viewManager ()) { viewManager ()->setQueueUpdates (); } { setZoomLevel (hzoom, vzoom); setOrigin (QPoint ((width () - zoomedDocWidth ()) / 2, (height () - zoomedDocHeight ()) / 2)); setMaskToCoverDocument (); if (viewManager ()) { viewManager ()->updateView (this); } } if (viewManager ()) { viewManager ()->restoreQueueUpdates (); } } diff --git a/views/kpZoomedView.cpp b/views/kpZoomedView.cpp index 4c471a3f..09310979 100644 --- a/views/kpZoomedView.cpp +++ b/views/kpZoomedView.cpp @@ -1,90 +1,102 @@ /* Copyright (c) 2003-2007 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_ZOOMED_VIEW 0 #include "views/kpZoomedView.h" #include "kpLogCategories.h" #include "document/kpDocument.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" kpZoomedView::kpZoomedView (kpDocument *document, kpToolToolBar *toolToolBar, kpViewManager *viewManager, kpView *buddyView, kpViewScrollableContainer *scrollableContainer, QWidget *parent) : kpView (document, toolToolBar, viewManager, buddyView, scrollableContainer, parent) { // Call to virtual function - this is why the class is sealed adjustToEnvironment (); } kpZoomedView::~kpZoomedView () = default; // public virtual [base kpView] void kpZoomedView::setZoomLevel (int hzoom, int vzoom) { +#if DEBUG_KP_ZOOMED_VIEW + qCDebug(kpLogViews) << "kpZoomedView(" << name () << ")::setZoomLevel(" + << hzoom << "," << vzoom << ")" << endl; +#endif + if (viewManager ()) { viewManager ()->setQueueUpdates (); } { kpView::setZoomLevel (hzoom, vzoom); adjustToEnvironment (); } if (viewManager ()) { viewManager ()->restoreQueueUpdates (); } } // public slot virtual [base kpView] void kpZoomedView::adjustToEnvironment () { +#if DEBUG_KP_ZOOMED_VIEW + qCDebug(kpLogViews) << "kpZoomedView(" << name () << ")::adjustToEnvironment()" + << " doc: width=" << document ()->width () + << " height=" << document ()->height () + << endl; +#endif + if (document ()) { // TODO: use zoomedDocWidth() & zoomedDocHeight()? resize (static_cast (transformDocToViewX (document ()->width ())), static_cast (transformDocToViewY (document ()->height ()))); } } diff --git a/views/manager/kpViewManager.cpp b/views/manager/kpViewManager.cpp index c81fafdf..da10bd85 100644 --- a/views/manager/kpViewManager.cpp +++ b/views/manager/kpViewManager.cpp @@ -1,359 +1,374 @@ /* Copyright (c) 2003-2007 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_VIEW_MANAGER 0 #include "views/manager/kpViewManager.h" #include "kpViewManagerPrivate.h" #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "mainWindow/kpMainWindow.h" #include "layers/tempImage/kpTempImage.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "views/kpView.h" //--------------------------------------------------------------------- kpViewManager::kpViewManager (kpMainWindow *mainWindow) : d (new kpViewManagerPrivate ()) { Q_ASSERT (mainWindow); d->mainWindow = mainWindow; // d->views d->viewUnderCursor = nullptr; // d->cursor d->tempImage = nullptr; d->selectionBorderVisible = false; d->selectionBorderFinished = false; d->textCursorBlinkTimer = nullptr; d->textCursorRow = -1; d->textCursorCol = -1; d->textCursorBlinkState = true; d->queueUpdatesCounter = d->fastUpdatesCounter = 0; d->inputMethodEnabled = false; } //--------------------------------------------------------------------- kpViewManager::~kpViewManager () { unregisterAllViews (); delete d->tempImage; delete d; } //--------------------------------------------------------------------- // private kpDocument *kpViewManager::document () const { return d->mainWindow->document (); } //--------------------------------------------------------------------- // public void kpViewManager::registerView (kpView *view) { +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "kpViewManager::registerView (" << view << ")"; +#endif Q_ASSERT (view); Q_ASSERT (!d->views.contains (view)); +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "\tadded view"; +#endif view->setCursor (d->cursor); d->views.append (view); } //--------------------------------------------------------------------- // public void kpViewManager::unregisterView (kpView *view) { Q_ASSERT (view); Q_ASSERT (d->views.contains (view)); if (view == d->viewUnderCursor) { d->viewUnderCursor = nullptr; } view->unsetCursor (); d->views.removeAll (view); } //--------------------------------------------------------------------- // public void kpViewManager::unregisterAllViews () { d->views.clear (); } //--------------------------------------------------------------------- // public kpView *kpViewManager::viewUnderCursor (bool usingQt) const { if (!usingQt) { Q_ASSERT (!d->viewUnderCursor || d->views.contains (d->viewUnderCursor)); return d->viewUnderCursor; } for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { if ((*it)->underMouse ()) { return (*it); } } return nullptr; } //--------------------------------------------------------------------- // public void kpViewManager::setViewUnderCursor (kpView *view) { +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "kpViewManager::setViewUnderCursor (" << (view ? view->objectName () : "(none)") << ")" << " old=" << (d->viewUnderCursor ? d->viewUnderCursor->objectName () : "(none)"); +#endif if (view == d->viewUnderCursor) { return; } d->viewUnderCursor = view; if (d->viewUnderCursor) { d->viewUnderCursor->setAttribute (Qt::WA_InputMethodEnabled, d->inputMethodEnabled); } if (!d->viewUnderCursor) { // Hide the brush if the mouse cursor just left the view. if (d->tempImage && d->tempImage->isBrush ()) { + #if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "\thiding brush pixmap since cursor left view"; + #endif updateViews (d->tempImage->rect ()); } } else { if (d->mainWindow->tool ()) { + #if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "\tnotify tool that something changed below cursor"; + #endif d->mainWindow->tool ()->somethingBelowTheCursorChanged (); } } } //--------------------------------------------------------------------- // public bool kpViewManager::hasAViewWithFocus () const { for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { if ((*it)->isActiveWindow ()) { return true; } } return false; } //--------------------------------------------------------------------- // public void kpViewManager::setCursor (const QCursor &cursor) { for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { (*it)->setCursor (cursor); } d->cursor = cursor; } //--------------------------------------------------------------------- // public void kpViewManager::unsetCursor () { for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { (*it)->unsetCursor (); } d->cursor = QCursor (); } //--------------------------------------------------------------------- // public const kpTempImage *kpViewManager::tempImage () const { return d->tempImage; } //--------------------------------------------------------------------- // public void kpViewManager::setTempImage (const kpTempImage &tempImage) { +#if DEBUG_KP_VIEW_MANAGER qCDebug(kpLogViews) << "kpViewManager::setTempImage(isBrush=" << tempImage.isBrush () << ",topLeft=" << tempImage.topLeft () << ",image.rect=" << tempImage.image ().rect () << ")"; +#endif QRect oldRect; if (d->tempImage) { oldRect = d->tempImage->rect (); delete d->tempImage; d->tempImage = nullptr; } d->tempImage = new kpTempImage (tempImage); setQueueUpdates (); { if (oldRect.isValid ()) { updateViews (oldRect); } updateViews (d->tempImage->rect ()); } restoreQueueUpdates (); } //--------------------------------------------------------------------- // public void kpViewManager::invalidateTempImage () { if (!d->tempImage) { return; } QRect oldRect = d->tempImage->rect (); delete d->tempImage; d->tempImage = nullptr; updateViews (oldRect); } //--------------------------------------------------------------------- // public bool kpViewManager::selectionBorderVisible () const { return d->selectionBorderVisible; } //--------------------------------------------------------------------- // public void kpViewManager::setSelectionBorderVisible (bool yes) { if (d->selectionBorderVisible == yes) { return; } d->selectionBorderVisible = yes; if (document ()->selection ()) { updateViews (document ()->selection ()->boundingRect ()); } } //--------------------------------------------------------------------- // public bool kpViewManager::selectionBorderFinished () const { return d->selectionBorderFinished; } //--------------------------------------------------------------------- // public void kpViewManager::setSelectionBorderFinished (bool yes) { if (d->selectionBorderFinished == yes) { return; } d->selectionBorderFinished = yes; if (document ()->selection ()) { updateViews (document ()->selection ()->boundingRect ()); } } //--------------------------------------------------------------------- void kpViewManager::setInputMethodEnabled (bool inputMethodEnabled) { d->inputMethodEnabled = inputMethodEnabled; if (d->viewUnderCursor) { d->viewUnderCursor->setAttribute (Qt::WA_InputMethodEnabled, inputMethodEnabled); } } + +//--------------------------------------------------------------------- + diff --git a/views/manager/kpViewManager_TextCursor.cpp b/views/manager/kpViewManager_TextCursor.cpp index 91230644..c5e319fd 100644 --- a/views/manager/kpViewManager_TextCursor.cpp +++ b/views/manager/kpViewManager_TextCursor.cpp @@ -1,230 +1,236 @@ // TODO: This is bad design as it's easy to get out of sync with the selection. // e.g. You could have textCursorEnabled() but no text selection or // vice versa. And the cursor could be outside of the selection. // // In fact, our text commands momentarily violate these "invariants": // // 1. A text box with content must have the cursor somewhere on an // existing text line, possibly 1 position after the last character // on a line. // // 2. Special case: A content-less text box (i.e. no text lines) must // have the cursor at (0,0). // // We don't assert-check them at the moment. We should when we fix // the design so that the invariants are always maintained. /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2005 Kazuki Ohta Copyright (c) 2010 Tasuku Suzuki 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_VIEW_MANAGER 0 #include "kpViewManager.h" #include "kpViewManagerPrivate.h" #include #include #include //#include #include "kpLogCategories.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "mainWindow/kpMainWindow.h" #include "layers/tempImage/kpTempImage.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "views/kpView.h" // public bool kpViewManager::textCursorEnabled () const { return static_cast (d->textCursorBlinkTimer); } // public void kpViewManager::setTextCursorEnabled (bool yes) { +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "kpViewManager::setTextCursorEnabled(" << yes << ")"; +#endif if (yes == textCursorEnabled ()) { return; } delete d->textCursorBlinkTimer; d->textCursorBlinkTimer = nullptr; setFastUpdates (); setQueueUpdates (); { if (yes) { d->textCursorBlinkTimer = new QTimer (this); d->textCursorBlinkTimer->setSingleShot (true); connect (d->textCursorBlinkTimer, &QTimer::timeout, this, &kpViewManager::slotTextCursorBlink); d->textCursorBlinkState = true; slotTextCursorBlink (); } else { d->textCursorBlinkState = false; updateTextCursor (); } } restoreQueueUpdates (); restoreFastUpdates (); } // public bool kpViewManager::textCursorBlinkState () const { return d->textCursorBlinkState; } // public void kpViewManager::setTextCursorBlinkState (bool on) { if (on == d->textCursorBlinkState) { return; } d->textCursorBlinkState = on; updateTextCursor (); } // public int kpViewManager::textCursorRow () const { return d->textCursorRow; } // public int kpViewManager::textCursorCol () const { return d->textCursorCol; } // public void kpViewManager::setTextCursorPosition (int row, int col) { if (row == d->textCursorRow && col == d->textCursorCol) { return; } setFastUpdates (); setQueueUpdates (); { // Clear the old cursor. d->textCursorBlinkState = false; updateTextCursor (); d->textCursorRow = row; d->textCursorCol = col; // Render the new cursor. d->textCursorBlinkState = true; updateTextCursor (); } restoreQueueUpdates (); restoreFastUpdates (); } // public QRect kpViewManager::textCursorRect () const { kpTextSelection *textSel = document ()->textSelection (); if (!textSel) { return {}; } QPoint topLeft = textSel->pointForTextRowCol (d->textCursorRow, d->textCursorCol); if (topLeft == KP_INVALID_POINT) { // Text cursor row/col hasn't been specified yet? if (textSel->hasContent ()) { return {}; } // Empty text box should still display a cursor so that the user // knows where typed text will go. topLeft = textSel->textAreaRect ().topLeft (); } Q_ASSERT (topLeft != KP_INVALID_POINT); return {topLeft.x (), topLeft.y (), 1, textSel->textStyle ().fontMetrics ().height ()}; } // protected void kpViewManager::updateTextCursor () { +#if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "kpViewManager::updateTextCursor()"; +#endif const QRect r = textCursorRect (); if (!r.isValid ()) { return; } setFastUpdates (); { // If !textCursorEnabled(), this will clear. updateViews (r); } restoreFastUpdates (); } // protected slot void kpViewManager::slotTextCursorBlink () { +#if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "kpViewManager::slotTextCursorBlink() cursorBlinkState=" << d->textCursorBlinkState; +#endif if (d->textCursorBlinkTimer) { // (single shot) d->textCursorBlinkTimer->start (QApplication::cursorFlashTime () / 2); } updateTextCursor (); // TODO: Shouldn't this be done _before_ updating the text cursor // because textCursorBlinkState() is supposed to reflect what // updateTextCursor() just rendered, until the next timer tick? d->textCursorBlinkState = !d->textCursorBlinkState; } diff --git a/views/manager/kpViewManager_ViewUpdates.cpp b/views/manager/kpViewManager_ViewUpdates.cpp index 40e63c9e..824528fa 100644 --- a/views/manager/kpViewManager_ViewUpdates.cpp +++ b/views/manager/kpViewManager_ViewUpdates.cpp @@ -1,235 +1,260 @@ /* Copyright (c) 2003-2007 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_VIEW_MANAGER 0 #include "views/manager/kpViewManager.h" #include "kpViewManagerPrivate.h" #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "mainWindow/kpMainWindow.h" #include "layers/tempImage/kpTempImage.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "views/kpView.h" // public slot bool kpViewManager::queueUpdates () const { return (d->queueUpdatesCounter > 0); } // public slot void kpViewManager::setQueueUpdates () { d->queueUpdatesCounter++; +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "kpViewManager::setQueueUpdates() counter=" - << d->queueUpdatesCounter; + << d->queueUpdatesCounter << endl; +#endif } // public slot void kpViewManager::restoreQueueUpdates () { d->queueUpdatesCounter--; +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "kpViewManager::restoreQueueUpdates() counter=" << d->queueUpdatesCounter; +#endif Q_ASSERT (d->queueUpdatesCounter >= 0); if (d->queueUpdatesCounter == 0) { for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { (*it)->updateQueuedArea (); } } } // public slot bool kpViewManager::fastUpdates () const { return (d->fastUpdatesCounter > 0); } // public slot void kpViewManager::setFastUpdates () { d->fastUpdatesCounter++; +#if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "kpViewManager::setFastUpdates() counter=" - << d->fastUpdatesCounter; + << d->fastUpdatesCounter << endl; +#endif } // public slot void kpViewManager::restoreFastUpdates () { d->fastUpdatesCounter--; +#if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "kpViewManager::restoreFastUpdates() counter=" - << d->fastUpdatesCounter; + << d->fastUpdatesCounter << endl; +#endif Q_ASSERT (d->fastUpdatesCounter >= 0); } // public slot void kpViewManager::updateView (kpView *v) { updateView (v, QRect (0, 0, v->width (), v->height ())); } // public slot void kpViewManager::updateView (kpView *v, const QRect &viewRect) { if (!queueUpdates ()) { if (fastUpdates ()) { v->repaint (viewRect); } else { v->update (viewRect); } } else { v->addToQueuedArea (viewRect); } } // public slot void kpViewManager::updateView (kpView *v, int x, int y, int w, int h) { updateView (v, QRect (x, y, w, h)); } // public slot void kpViewManager::updateView (kpView *v, const QRegion &viewRegion) { if (!queueUpdates ()) { if (fastUpdates ()) { v->repaint (viewRegion); } else { v->update (viewRegion.boundingRect ()); } } else { v->addToQueuedArea (viewRegion); } } // public slot void kpViewManager::updateViewRectangleEdges (kpView *v, const QRect &viewRect) { if (viewRect.height () <= 0 || viewRect.width () <= 0) { return; } // Top line updateView (v, QRect (viewRect.x (), viewRect.y (), viewRect.width (), 1)); if (viewRect.height () >= 2) { // Bottom line updateView (v, QRect (viewRect.x (), viewRect.bottom (), viewRect.width (), 1)); if (viewRect.height () > 2) { // Left line updateView (v, QRect (viewRect.x (), viewRect.y () + 1, 1, viewRect.height () - 2)); if (viewRect.width () >= 2) { // Right line updateView (v, QRect (viewRect.right (), viewRect.y () + 1, 1, viewRect.height () - 2)); } } } } // public slot void kpViewManager::updateViews (const QRect &docRect) { +#if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "kpViewManager::updateViews (" << docRect << ")"; +#endif for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { kpView *view = *it; + #if DEBUG_KP_VIEW_MANAGER && 0 + qCDebug(kpLogViews) << "\tupdating view " << view->name (); + #endif if (view->zoomLevelX () % 100 == 0 && view->zoomLevelY () % 100 == 0) { + #if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "\t\tviewRect=" << view->transformDocToView (docRect); + #endif updateView (view, view->transformDocToView (docRect)); } else { QRect viewRect = view->transformDocToView (docRect); int diff = qRound (double (qMax (view->zoomLevelX (), view->zoomLevelY ())) / 100.0) + 1; QRect newRect = QRect (viewRect.x () - diff, viewRect.y () - diff, viewRect.width () + 2 * diff, viewRect.height () + 2 * diff) .intersected (QRect (0, 0, view->width (), view->height ())); + #if DEBUG_KP_VIEW_MANAGER && 0 qCDebug(kpLogViews) << "\t\tviewRect (+compensate)=" << newRect; + #endif updateView (view, newRect); } } } // public slot void kpViewManager::adjustViewsToEnvironment () { +#if DEBUG_KP_VIEW_MANAGER && 1 qCDebug(kpLogViews) << "kpViewManager::adjustViewsToEnvironment()" - << " numViews=" << d->views.count (); + << " numViews=" << d->views.count () + << endl; +#endif for (QLinkedList ::const_iterator it = d->views.begin (); it != d->views.end (); ++it) { kpView *view = *it; + + #if DEBUG_KP_VIEW_MANAGER && 1 + qCDebug(kpLogViews) << "\tview: " << view->name () + << endl; + #endif view->adjustToEnvironment (); } } diff --git a/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp b/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp index a732b49d..3150f11d 100644 --- a/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp +++ b/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp @@ -1,229 +1,236 @@ /* Copyright (c) 2003-2007 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_COLOR_SIMILARITY_CUBE 0 #include "kpColorSimilarityCubeRenderer.h" #include #include #include #include "kpLogCategories.h" #include "widgets/colorSimilarity/kpColorSimilarityHolder.h" #include "kpDefs.h" //--------------------------------------------------------------------- static QColor Color (int redOrGreenOrBlue, int baseBrightness, double colorSimilarity, int similarityDirection, int highlight) { int brightness = int (baseBrightness + similarityDirection * 0.5 * colorSimilarity * kpColorSimilarityHolder::ColorCubeDiagonalDistance); if (brightness < 0) { brightness = 0; } else if (brightness > 255) { brightness = 255; } switch (redOrGreenOrBlue) { default: case 0: return {brightness, highlight, highlight}; case 1: return {highlight, brightness, highlight}; case 2: return {highlight, highlight, brightness}; } } //--------------------------------------------------------------------- static QPointF PointBetween(const QPointF &p, const QPointF &q) { return {(p.x() + q.x()) / 2.0, (p.y() + q.y()) / 2.0}; } //--------------------------------------------------------------------- static void DrawQuadrant(QPaintDevice *target, const QColor &col, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &pointNotOnOutline) { QPolygonF points (4); points [0] = p1; points [1] = p2; points [2] = p3; points [3] = pointNotOnOutline; QPainter p(target); p.setRenderHints(QPainter::Antialiasing, true); // Polygon fill. p.setPen(col); p.setBrush(col); p.drawPolygon(points); // do not draw a black border. It looks ugly } //--------------------------------------------------------------------- static void DrawFace (QPaintDevice *target, double colorSimilarity, int redOrGreenOrBlue, const QPointF &tl, const QPointF &tr, const QPointF &bl, const QPointF &br, int highlight) { +#if DEBUG_KP_COLOR_SIMILARITY_CUBE qCDebug(kpLogWidgets) << "kpColorSimilarityCubeRenderer.cpp:DrawFace(RorGorB=" << redOrGreenOrBlue << ",tl=" << tl << ",tr=" << tr << ",bl=" << bl << ",br=" << br - << ")"; + << ")" + << endl; +#endif // tl --- tm --- tr // | | | // | | | // ml --- mm --- mr // | | | // | | | // bl --- bm --- br const QPointF tm (::PointBetween (tl, tr)); const QPointF bm (::PointBetween (bl, br)); const QPointF ml (::PointBetween (tl, bl)); const QPointF mr (::PointBetween (tr, br)); const QPointF mm (::PointBetween (ml, mr)); const int baseBrightness = qMax (127, 255 - int (kpColorSimilarityHolder::MaxColorSimilarity * kpColorSimilarityHolder::ColorCubeDiagonalDistance / 2)); QColor colors [2] = { ::Color (redOrGreenOrBlue, baseBrightness, colorSimilarity, -1, highlight), ::Color (redOrGreenOrBlue, baseBrightness, colorSimilarity, +1, highlight) }; +#if DEBUG_KP_COLOR_SIMILARITY_CUBE qCDebug(kpLogWidgets) << "\tmaxColorSimilarity=" << kpColorSimilarityHolder::MaxColorSimilarity << " colorCubeDiagDist=" << kpColorSimilarityHolder::ColorCubeDiagonalDistance << "\n" << "\tbaseBrightness=" << baseBrightness << " color[0]=" << ((colors [0].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) - << " color[1]=" << ((colors [1].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)); + << " color[1]=" << ((colors [1].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) + << endl; +#endif + ::DrawQuadrant(target, colors [0], tm, tl, ml, mm); ::DrawQuadrant(target, colors [1], tm, tr, mr, mm); ::DrawQuadrant(target, colors [1], ml, bl, bm, mm); ::DrawQuadrant(target, colors [0], bm, br, mr, mm); } //--------------------------------------------------------------------- // public static void kpColorSimilarityCubeRenderer::Paint(QPaintDevice *target, int x, int y, int cubeRectSize, double colorSimilarity, int highlight) { Q_ASSERT (highlight >= 0 && highlight <= 255); // // P------- Q --- --- // / / | | | // /A / | side | // R-------S T --- cubeRectSize // | | / / | // S | | / side | // U-------V --- --- // |-------| // side // |-----------| // cubeRectSize // // const double angle = qDegreesToRadians (45.0); // S + S sin A = cubeRectSize // (1 + sin A) x S = cubeRectSize const double side = double(cubeRectSize) / (1.0 + std::sin(angle)); const QPointF pointP(x + (side * std::cos (angle)), y); const QPointF pointQ(x + cubeRectSize - 1, y); const QPointF pointR(x, y + (side * std::sin (angle))); const QPointF pointS(x + (side), y + (side * std::sin (angle))); const QPointF pointT(x + cubeRectSize - 1, y + (side)); const QPointF pointU(x, y + cubeRectSize - 1); const QPointF pointV(x + (side), y + cubeRectSize - 1); // Top Face ::DrawFace(target, colorSimilarity, 0/*red*/, pointP, pointQ, pointR, pointS, highlight); // Front Face ::DrawFace(target, colorSimilarity, 1/*green*/, pointR, pointS, pointU, pointV, highlight); // Right Face ::DrawFace(target, colorSimilarity, 2/*blue*/, pointS, pointQ, pointV, pointT, highlight); } //--------------------------------------------------------------------- diff --git a/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp b/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp index be03de8d..7bcca39b 100644 --- a/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp +++ b/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp @@ -1,266 +1,286 @@ /* Copyright (c) 2003-2007 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_COLOR_SIMILARITY_TOOL_BAR_ITEM 0 + + #include "kpColorSimilarityToolBarItem.h" #include #include #include #include #include #include "imagelib/kpColor.h" #include "dialogs/kpColorSimilarityDialog.h" #include "kpColorSimilarityCubeRenderer.h" #include "kpDefs.h" -#include "kpLogCategories.h" //--------------------------------------------------------------------- kpColorSimilarityToolBarItem::kpColorSimilarityToolBarItem (QWidget *parent) : QToolButton (parent), kpColorSimilarityHolder (), m_oldColorSimilarity (0), m_processedColorSimilarity (kpColor::Exact), m_flashTimer (new QTimer (this)), m_flashHighlight (0), m_suppressingFlashCounter (0) { setAutoRaise (true); setFixedSize (52, 52); setWhatsThis (WhatsThisWithClickInstructions ()); connect (this, &kpColorSimilarityToolBarItem::clicked, this, &kpColorSimilarityToolBarItem::openDialog); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); setColorSimilarityInternal (cfg.readEntry (kpSettingColorSimilarity, 0.0), false/*don't write config*/); m_flashTimer->setInterval (100/*ms*/); connect (m_flashTimer, &QTimer::timeout, this, &kpColorSimilarityToolBarItem::slotFlashTimerTimeout); } //--------------------------------------------------------------------- // public int kpColorSimilarityToolBarItem::processedColorSimilarity () const { return m_processedColorSimilarity; } //--------------------------------------------------------------------- // private void kpColorSimilarityToolBarItem::setColorSimilarityInternal (double similarity, bool writeConfig) { +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "kpColorSimilarityToolBarItem::setColorSimilarityInternal(" << "similarity=" << similarity << ",writeConfig=" << writeConfig << ")"; +#endif m_oldColorSimilarity = colorSimilarity (); kpColorSimilarityHolder::setColorSimilarity (similarity); m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity ()); updateIcon (); updateToolTip (); if (writeConfig) { KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingColorSimilarity, colorSimilarity ()); cfg.sync (); } emit colorSimilarityChanged (colorSimilarity (), m_processedColorSimilarity); } //--------------------------------------------------------------------- // public virtual [base kopColorSimilarityHolder] void kpColorSimilarityToolBarItem::setColorSimilarity (double similarity) { // (this calls the base setColorSimilarity() as required by base) setColorSimilarityInternal (similarity, true/*write config*/); } //--------------------------------------------------------------------- // public double kpColorSimilarityToolBarItem::oldColorSimilarity () const { return m_oldColorSimilarity; } //--------------------------------------------------------------------- // public void kpColorSimilarityToolBarItem::openDialog () { kpColorSimilarityDialog dialog (this); dialog.setColorSimilarity (colorSimilarity ()); if (dialog.exec ()) { setColorSimilarity (dialog.colorSimilarity ()); } } //--------------------------------------------------------------------- // private slot: void kpColorSimilarityToolBarItem::slotFlashTimerTimeout () { +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "kpColorSimilarityToolBarItem::slotFlashTimerTimeout()" - << " highlight=" << m_flashHighlight; + << " highlight=" << m_flashHighlight << endl; +#endif int newHigh = m_flashHighlight - 20; if (newHigh < 0) { newHigh = 0; } m_flashHighlight = newHigh; updateIcon (); if (newHigh == 0) { m_flashTimer->stop (); } } //--------------------------------------------------------------------- // public void kpColorSimilarityToolBarItem::flash () { +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "kpColorSimilarityToolBarItem::flash()"; +#endif if (isSuppressingFlash ()) { return; } if (m_flashHighlight == 255) { + #if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "\tNOP"; + #endif } else { m_flashHighlight = 255; updateIcon (); } m_flashTimer->start (); } //--------------------------------------------------------------------- // public bool kpColorSimilarityToolBarItem::isSuppressingFlash () const { return (m_suppressingFlashCounter > 0); } //--------------------------------------------------------------------- // public void kpColorSimilarityToolBarItem::suppressFlash () { m_suppressingFlashCounter++; } //--------------------------------------------------------------------- // public void kpColorSimilarityToolBarItem::unsupressFlash () { m_suppressingFlashCounter--; Q_ASSERT (m_suppressingFlashCounter >= 0); } //--------------------------------------------------------------------- // private void kpColorSimilarityToolBarItem::updateToolTip () { +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "kpColorSimilarityToolBarItem::updateToolTip()"; +#endif if (colorSimilarity () > 0) { setToolTip ( i18n ("

Color Similarity: %1%

" "

Click to configure.

", qRound (colorSimilarity () * 100))); } else { setToolTip ( i18n ("

Color Similarity: Exact Match

" "

Click to configure.

")); } } //--------------------------------------------------------------------- // private // LOOPT: This gets called twice on KolourPaint startup by: // // 1. setColorSimilarityInternal() called by the ctor // 2. resizeEvent() when it's first shown() // // We could get rid of the first and save a few milliseconds. void kpColorSimilarityToolBarItem::updateIcon () { const int side = width () * 6 / 8; +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "kpColorSimilarityToolBarItem::updateIcon() width=" << width () << " side=" << side; +#endif QPixmap icon(side, side); icon.fill(Qt::transparent); kpColorSimilarityCubeRenderer::Paint (&icon, 0/*x*/, 0/*y*/, side, colorSimilarity (), m_flashHighlight); setIconSize(QSize(side, side)); setIcon(icon); } //--------------------------------------------------------------------- // private virtual [base QWidget] void kpColorSimilarityToolBarItem::resizeEvent (QResizeEvent *e) { +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM qCDebug(kpLogWidgets) << "kpColorSimilarityToolBarItem::resizeEvent() size=" << size () << " oldSize=" << e->oldSize (); +#endif QToolButton::resizeEvent (e); updateIcon (); } + +//--------------------------------------------------------------------- + diff --git a/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp b/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp index a5a34e94..a4a2bbf7 100644 --- a/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp +++ b/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp @@ -1,177 +1,184 @@ /* Copyright (c) 2003-2007 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_EFFECT_BLUR_SHARPEN 0 #include "kpEffectBlurSharpenWidget.h" #include "commands/imagelib/effects/kpEffectBlurSharpenCommand.h" #include "kpLogCategories.h" #include #include "kpNumInput.h" #include #include #include kpEffectBlurSharpenWidget::kpEffectBlurSharpenWidget (bool actOnSelection, QWidget *parent) : kpEffectWidgetBase (actOnSelection, parent) { auto *lay = new QGridLayout (this); lay->setContentsMargins(0, 0, 0, 0); auto *amountLabel = new QLabel (i18n ("&Amount:"), this); m_amountInput = new kpIntNumInput (this); m_amountInput->setRange (-kpEffectBlurSharpen::MaxStrength/*- for blur*/, +kpEffectBlurSharpen::MaxStrength/*+ for sharpen*/); m_typeLabel = new QLabel (this); // Make sure doesn't expand when the effect type changes, // as otherwise, that would cause the preview pixmap label in the // "More Effects" dialog (which our widget is inside) to contract, // which would look a bit weird. // // We do this by setting the label to every possible string it could // contain and fixing its height to the maximum seen size hint height. auto h = m_typeLabel->sizeHint ().height (); +#if DEBUG_KP_EFFECT_BLUR_SHARPEN qCDebug(kpLogWidgets) << "initial size hint height=" << h; +#endif m_typeLabel->setText ( kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::Blur)); h = qMax (h, m_typeLabel->sizeHint ().height ()); m_typeLabel->setText ( kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::Sharpen)); h = qMax (h, m_typeLabel->sizeHint ().height ()); // Set this text last as the label's text needs to reflect the default // effect of "None". m_typeLabel->setText ( kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::None)); h = qMax (h, m_typeLabel->sizeHint ().height ()); +#if DEBUG_KP_EFFECT_BLUR_SHARPEN qCDebug(kpLogWidgets) << "maximum size hint height" << h; +#endif m_typeLabel->setFixedHeight (h); m_typeLabel->setAlignment (Qt::AlignCenter); amountLabel->setBuddy (m_amountInput); lay->addWidget (amountLabel, 0, 0); lay->addWidget (m_amountInput, 0, 1); lay->addWidget (m_typeLabel, 1, 0, 1, 2, Qt::AlignCenter); lay->setColumnStretch (1, 1); connect (m_amountInput, &kpIntNumInput::valueChanged, this, &kpEffectBlurSharpenWidget::settingsChangedDelayed); connect (m_amountInput, &kpIntNumInput::valueChanged, this, &kpEffectBlurSharpenWidget::slotUpdateTypeLabel); } kpEffectBlurSharpenWidget::~kpEffectBlurSharpenWidget () = default; // public virtual [base kpEffectWidgetBase] QString kpEffectBlurSharpenWidget::caption () const { return QString(); } // public virtual [base kpEffectWidgetBase] bool kpEffectBlurSharpenWidget::isNoOp () const { return (type () == kpEffectBlurSharpen::None); } // public virtual [base kpEffectWidgetBase] kpImage kpEffectBlurSharpenWidget::applyEffect (const kpImage &image) { return kpEffectBlurSharpen::applyEffect (image, type (), strength ()); } // public virtual [base kpEffectWidgetBase] kpEffectCommandBase *kpEffectBlurSharpenWidget::createCommand ( kpCommandEnvironment *cmdEnviron) const { return new kpEffectBlurSharpenCommand (type (), strength (), m_actOnSelection, cmdEnviron); } // protected slot void kpEffectBlurSharpenWidget::slotUpdateTypeLabel () { QString text = kpEffectBlurSharpenCommand::nameForType (type ()); - qCDebug(kpLogWidgets) << "kpEffectBlurSharpenWidget::slotUpdateTypeLabel() text=" << text; +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + qCDebug(kpLogWidgets) << "kpEffectBlurSharpenWidget::slotUpdateTypeLabel() text=" + << text; +#endif const int h = m_typeLabel->height (); m_typeLabel->setText (text); if (m_typeLabel->height () != h) { qCCritical(kpLogWidgets) << "Label changed height despite the hack in ctor:" - << "was=" << h + << "was=" << h << "now=" << m_typeLabel->height (); } } // protected kpEffectBlurSharpen::Type kpEffectBlurSharpenWidget::type () const { if (m_amountInput->value () == 0) { return kpEffectBlurSharpen::None; } if (m_amountInput->value () < 0) { return kpEffectBlurSharpen::Blur; } return kpEffectBlurSharpen::Sharpen; } // protected int kpEffectBlurSharpenWidget::strength () const { return qAbs (m_amountInput->value ()); } diff --git a/widgets/imagelib/effects/kpEffectFlattenWidget.cpp b/widgets/imagelib/effects/kpEffectFlattenWidget.cpp index 85e75b0c..cfe8c52e 100644 --- a/widgets/imagelib/effects/kpEffectFlattenWidget.cpp +++ b/widgets/imagelib/effects/kpEffectFlattenWidget.cpp @@ -1,177 +1,182 @@ /* Copyright (c) 2003-2007 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_EFFECT_FLATTEN 0 #include "kpEffectFlattenWidget.h" #include "kpDefs.h" #include "imagelib/effects/kpEffectFlatten.h" #include "commands/imagelib/effects/kpEffectFlattenCommand.h" #include "kpLogCategories.h" #include #include #include #include #include #include // public static QColor kpEffectFlattenWidget::s_lastColor1; QColor kpEffectFlattenWidget::s_lastColor2; kpEffectFlattenWidget::kpEffectFlattenWidget (bool actOnSelection, QWidget *parent) : kpEffectWidgetBase (actOnSelection, parent) { if (!s_lastColor1.isValid () || !s_lastColor2.isValid ()) { KConfigGroup cfgGroupSaver (KSharedConfig::openConfig (), kpSettingsGroupFlattenEffect); s_lastColor1 = cfgGroupSaver.readEntry (kpSettingFlattenEffectColor1, QColor ()); if (!s_lastColor1.isValid ()) { s_lastColor1 = Qt::red; } s_lastColor2 = cfgGroupSaver.readEntry (kpSettingFlattenEffectColor2, QColor ()); if (!s_lastColor2.isValid ()) { s_lastColor2 = Qt::blue; } } m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); m_color1Button = new KColorButton (s_lastColor1, this); m_color2Button = new KColorButton (s_lastColor2, this); m_color1Button->setEnabled (false); m_color2Button->setEnabled (false); auto *lay = new QVBoxLayout (this); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget (m_enableCheckBox); lay->addWidget (m_color1Button); lay->addWidget (m_color2Button); connect (m_enableCheckBox, &QCheckBox::toggled, this, &kpEffectFlattenWidget::slotEnableChanged); connect (m_color1Button, &KColorButton::changed, this, &kpEffectFlattenWidget::settingsChanged); connect (m_color2Button, &KColorButton::changed, this, &kpEffectFlattenWidget::settingsChanged); } kpEffectFlattenWidget::~kpEffectFlattenWidget () { s_lastColor1 = color1 (); s_lastColor2 = color2 (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupFlattenEffect); cfg.writeEntry (kpSettingFlattenEffectColor1, s_lastColor1); cfg.writeEntry (kpSettingFlattenEffectColor2, s_lastColor2); cfg.sync (); } // public QColor kpEffectFlattenWidget::color1 () const { return m_color1Button->color (); } // public QColor kpEffectFlattenWidget::color2 () const { return m_color2Button->color (); } // // kpEffectFlattenWidget implements kpEffectWidgetBase interface // // public virtual [base kpEffectWidgetBase] QString kpEffectFlattenWidget::caption () const { return i18n ("Colors"); } // public virtual [base kpEffectWidgetBase] bool kpEffectFlattenWidget::isNoOp () const { return !m_enableCheckBox->isChecked (); } // public virtual [base kpEffectWidgetBase] kpImage kpEffectFlattenWidget::applyEffect (const kpImage &image) { +#if DEBUG_KP_EFFECT_FLATTEN qCDebug(kpLogWidgets) << "kpEffectFlattenWidget::applyEffect() nop=" - << isNoOp (); + << isNoOp () << endl; +#endif if (isNoOp ()) { return image; } return kpEffectFlatten::applyEffect (image, color1 (), color2 ()); } // public virtual [base kpEffectWidgetBase] kpEffectCommandBase *kpEffectFlattenWidget::createCommand ( kpCommandEnvironment *cmdEnviron) const { return new kpEffectFlattenCommand (color1 (), color2 (), m_actOnSelection, cmdEnviron); } // protected slot: void kpEffectFlattenWidget::slotEnableChanged (bool enable) { +#if DEBUG_KP_EFFECT_FLATTEN qCDebug(kpLogWidgets) << "kpEffectFlattenWidget::slotEnableChanged(" << enable - << ") enableButton=" << m_enableCheckBox->isChecked (); + << ") enableButton=" << m_enableCheckBox->isChecked () + << endl; +#endif m_color1Button->setEnabled (enable); m_color2Button->setEnabled (enable); emit settingsChanged (); } diff --git a/widgets/imagelib/effects/kpEffectInvertWidget.cpp b/widgets/imagelib/effects/kpEffectInvertWidget.cpp index 4601850c..f14cf3bf 100644 --- a/widgets/imagelib/effects/kpEffectInvertWidget.cpp +++ b/widgets/imagelib/effects/kpEffectInvertWidget.cpp @@ -1,207 +1,212 @@ /* Copyright (c) 2003-2007 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_EFFECT_INVERT 0 #include "kpEffectInvertWidget.h" #include "imagelib/effects/kpEffectInvert.h" #include "commands/imagelib/effects/kpEffectInvertCommand.h" #include "pixmapfx/kpPixmapFX.h" #include "kpLogCategories.h" #include #include #include #include #include kpEffectInvertWidget::kpEffectInvertWidget (bool actOnSelection, QWidget *parent) : kpEffectWidgetBase (actOnSelection, parent) { auto *topLevelLay = new QVBoxLayout (this); topLevelLay->setContentsMargins(0, 0, 0, 0); auto *centerWidget = new QWidget (this); topLevelLay->addWidget (centerWidget, 0/*stretch*/, Qt::AlignCenter); auto *centerWidgetLay = new QVBoxLayout (centerWidget ); centerWidgetLay->setContentsMargins(0, 0, 0, 0); m_redCheckBox = new QCheckBox (i18n ("&Red"), centerWidget); m_greenCheckBox = new QCheckBox (i18n ("&Green"), centerWidget); m_blueCheckBox = new QCheckBox (i18n ("&Blue"), centerWidget); auto *spaceWidget = new QWidget (centerWidget); spaceWidget->setFixedSize (1, fontMetrics ().height () / 4); m_allCheckBox = new QCheckBox (i18n ("&All"), centerWidget); m_redCheckBox->setChecked (false); m_greenCheckBox->setChecked (false); m_blueCheckBox->setChecked (false); m_allCheckBox->setChecked (false); centerWidgetLay->addWidget (m_redCheckBox); centerWidgetLay->addWidget (m_greenCheckBox); centerWidgetLay->addWidget (m_blueCheckBox); centerWidgetLay->addWidget (spaceWidget); centerWidgetLay->addWidget (m_allCheckBox); m_inSignalHandler = false; connect (m_redCheckBox, &QCheckBox::toggled, this, &kpEffectInvertWidget::slotRGBCheckBoxToggled); connect (m_greenCheckBox, &QCheckBox::toggled, this, &kpEffectInvertWidget::slotRGBCheckBoxToggled); connect (m_blueCheckBox, &QCheckBox::toggled, this, &kpEffectInvertWidget::slotRGBCheckBoxToggled); connect (m_allCheckBox, &QCheckBox::toggled, this, &kpEffectInvertWidget::slotAllCheckBoxToggled); } kpEffectInvertWidget::~kpEffectInvertWidget () = default; // public int kpEffectInvertWidget::channels () const { +#if DEBUG_KP_EFFECT_INVERT qCDebug(kpLogWidgets) << "kpEffectInvertWidget::channels()" << " isChecked: r=" << m_redCheckBox->isChecked () << " g=" << m_greenCheckBox->isChecked () - << " b=" << m_blueCheckBox->isChecked (); + << " b=" << m_blueCheckBox->isChecked () + << endl; +#endif int channels = 0; if (m_redCheckBox->isChecked ()) { channels |= kpEffectInvert::Red; } if (m_greenCheckBox->isChecked ()) { channels |= kpEffectInvert::Green; } if (m_blueCheckBox->isChecked ()) { channels |= kpEffectInvert::Blue; } +#if DEBUG_KP_EFFECT_INVERT qCDebug(kpLogWidgets) << "\treturning channels=" << (int *) channels; +#endif return channels; } // // kpEffectInvertWidget implements kpEffectWidgetBase interface // // public virtual [base kpEffectWidgetBase] QString kpEffectInvertWidget::caption () const { return i18n ("Channels"); } // public virtual [base kpEffectWidgetBase] bool kpEffectInvertWidget::isNoOp () const { return (channels () == kpEffectInvert::None); } // public virtual [base kpEffectWidgetBase] kpImage kpEffectInvertWidget::applyEffect (const kpImage &image) { return kpEffectInvert::applyEffect (image, channels ()); } // public virtual [base kpEffectWidgetBase] kpEffectCommandBase *kpEffectInvertWidget::createCommand ( kpCommandEnvironment *cmdEnviron) const { return new kpEffectInvertCommand (channels (), m_actOnSelection, cmdEnviron); } // protected slots void kpEffectInvertWidget::slotRGBCheckBoxToggled () { if (m_inSignalHandler) { return; } m_inSignalHandler = true; //blockSignals (true); m_allCheckBox->setChecked (m_redCheckBox->isChecked () && m_blueCheckBox->isChecked () && m_greenCheckBox->isChecked ()); //blockSignals (false); emit settingsChanged (); m_inSignalHandler = false; } // protected slot void kpEffectInvertWidget::slotAllCheckBoxToggled () { if (m_inSignalHandler) { return; } m_inSignalHandler = true; //blockSignals (true); m_redCheckBox->setChecked (m_allCheckBox->isChecked ()); m_greenCheckBox->setChecked (m_allCheckBox->isChecked ()); m_blueCheckBox->setChecked (m_allCheckBox->isChecked ()); //blockSignals (false); emit settingsChanged (); m_inSignalHandler = false; } diff --git a/widgets/kpColorCells.cpp b/widgets/kpColorCells.cpp index 45fe8a16..3bba3d36 100644 --- a/widgets/kpColorCells.cpp +++ b/widgets/kpColorCells.cpp @@ -1,588 +1,604 @@ /* Copyright (c) 2003-2007 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_COLOR_CELLS 0 + + #include "widgets/kpColorCells.h" #include "imagelib/kpColor.h" #include "lgpl/generic/kpColorCollection.h" #include "widgets/kpDefaultColorCollection.h" #include "kpLogCategories.h" #include #include #include #include #include //--------------------------------------------------------------------- // // Table Geometry // // The number of columns that the table normally has. const int TableDefaultNumColumns = 11; const int TableDefaultWidth = ::TableDefaultNumColumns * 26; const int TableDefaultHeight = 52; static int TableNumColumns (const kpColorCollection &colorCol) { if (colorCol.count () == 0) { return 0; } return ::TableDefaultNumColumns; } static int TableNumRows (const kpColorCollection &colorCol) { const int cols = ::TableNumColumns (colorCol); if (cols == 0) { return 0; } return (colorCol.count () + (cols - 1)) / cols; } static int TableCellWidth (const kpColorCollection &colorCol) { Q_UNUSED (colorCol); return ::TableDefaultWidth / ::TableDefaultNumColumns; } static int TableCellHeight (const kpColorCollection &colorCol) { if (::TableNumRows (colorCol) <= 2) { return ::TableDefaultHeight / 2; } return ::TableDefaultHeight / 3; } // // kpColorCells // struct kpColorCellsPrivate { Qt::Orientation orientation{}; // REFACTOR: This is data duplication with kpColorCellsBase::color[]. // We've probably forgotten to synchronize them in some points. // // Calls to kpColorCellsBase::setColor() (which also come from // kpColorCellsBase itself) will automatically update both // kpColorCellsBase::d->color[] and the table cells. setColor() emits // colorChanged(), which is caught by our slotColorChanged(), // which synchronizes this color collection and updates the modified flag. // // Avoid calling our grandparent's, QTableWidget's, mutating methods as we // don't override enough of them, to fire signals that we can catch to update // this color collection. // // If you modify this color collection directly (e.g. in setColorCollection(), // openColorCollection(), appendRow(), deleteLastRow(), ...), you must work // the other way and call makeCellsMatchColorCollection() to synchronize // kpColorCellsBase::d->color[] and the table cells. You still need to update // the modified flag. kpColorCollection colorCol; QUrl url; bool isModified{}; bool blockColorChangedSig{}; }; //--------------------------------------------------------------------- kpColorCells::kpColorCells (QWidget *parent, Qt::Orientation o) : kpColorCellsBase (parent, 0/*rows for now*/, 0/*cols for now*/), d (new kpColorCellsPrivate ()) { d->orientation = o; d->isModified = false; d->blockColorChangedSig = false; // When a text box is active, clicking to change the background color // should not move the keyboard focus away from the text box. setFocusPolicy (Qt::TabFocus); setShading (false); // no 3D look setAcceptDrops (true); setAcceptDrags (true); setCellsResizable (false); if (o == Qt::Horizontal) { // Reserve enough room for the default color collection's cells _and_ // a vertical scrollbar, which only appears when it's required. // This ensures that if the vertical scrollbar appears, it does not obscure // any cells or require the addition of a horizontal scrollbar, which would // look ugly and take even more precious room. // // We do not dynamically reserve room based on the actual number of rows // of cells, as that would make our containing widgets too big. setMinimumSize (::TableDefaultWidth + frameWidth () * 2 + verticalScrollBar()->sizeHint().width(), ::TableDefaultHeight + frameWidth () * 2); } else { Q_ASSERT (!"implemented"); } setVerticalScrollBarPolicy (Qt::ScrollBarAsNeeded); // The default QTableWidget policy of QSizePolicy::Expanding forces our // containing widgets to get too big. Override it. setSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum); connect (this, &kpColorCells::colorSelectedWhitButton, this, &kpColorCells::slotColorSelected); connect (this, &kpColorCells::colorDoubleClicked, this, &kpColorCells::slotColorDoubleClicked); connect (this, &kpColorCells::colorChanged, this, &kpColorCells::slotColorChanged); setColorCollection (DefaultColorCollection ()); setWhatsThis ( i18n ( "" "

To select the foreground color that tools use to draw," " left-click on a filled-in color cell." " To select the background color, right-click instead.

" "

To change the color of a color cell itself, double-click on it.

" "

You can also swap the color of a filled-in cell with any other" " cell using drag and drop." " Also, if you hold down the Ctrl key, the destination" " cell's color will be" " overwritten, instead of being swapped with the color of the source cell.

" "
")); } //--------------------------------------------------------------------- kpColorCells::~kpColorCells () { delete d; } //--------------------------------------------------------------------- // public static kpColorCollection kpColorCells::DefaultColorCollection () { return kpDefaultColorCollection (); } //--------------------------------------------------------------------- // public Qt::Orientation kpColorCells::orientation () const { return d->orientation; } //--------------------------------------------------------------------- // public void kpColorCells::setOrientation (Qt::Orientation o) { d->orientation = o; makeCellsMatchColorCollection (); } //--------------------------------------------------------------------- // protected // OPT: Find out why this is being called multiple times on startup. void kpColorCells::makeCellsMatchColorCollection () { int c, r; if (orientation () == Qt::Horizontal) { c = ::TableNumColumns (d->colorCol); r = ::TableNumRows (d->colorCol); } else { c = ::TableNumRows (d->colorCol); r = ::TableNumColumns (d->colorCol); } +#if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "kpColorCells::makeCellsMatchColorCollection():" << "r=" << r << "c=" << c; qCDebug(kpLogWidgets) << "verticalScrollBar=" << verticalScrollBar () << " sizeHint=" << (verticalScrollBar () ? verticalScrollBar ()->sizeHint () : QSize (-12, -34)); +#endif // Delete all cell widgets. This ensures that there will be no left-over // cell widgets, for the colors in the new color collection that are // actually invalid (which should not have cell widgets). clearContents (); setRowCount (r); setColumnCount (c); int CellWidth = ::TableCellWidth (d->colorCol), CellHeight = ::TableCellHeight (d->colorCol); // TODO: Take a screenshot of KolourPaint, magnify it and you'll find the // cells don't have exactly the sizes requested here. e.g. the // top row of cells is 1 pixel shorter than the bottom row. There // are probably other glitches. for (int y = 0; y < r; y++) { setRowHeight (y, CellHeight); } for (int x = 0; x < c; x++) { setColumnWidth (x, CellWidth); } const bool oldBlockColorChangedSig = d->blockColorChangedSig; d->blockColorChangedSig = true; // The last "(rowCount() * columnCount()) - d->colorCol.count()" cells // will be empty because we did not initialize them. for (int i = 0; i < d->colorCol.count (); i++) { int y, x; int pos; if (orientation () == Qt::Horizontal) { y = i / c; x = i % c; pos = i; } else { y = i % r; x = i / r; // int x = c - 1 - i / r; pos = y * c + x; } + #if DEBUG_KP_COLOR_CELLS && 0 qCDebug(kpLogWidgets) << "\tSetting cell " << i << ": y=" << y << " x=" << x << " pos=" << pos; qCDebug(kpLogWidgets) << "\t\tcolor=" << (int *) d->colorCol.color (i).rgba() << "isValid=" << d->colorCol.color (i).isValid (); + #endif // (color may be invalid resulting in a hole in the middle of the table) setColor (pos, d->colorCol.color (i)); //this->setToolTip( cellGeometry (y, x), colors [i].name ()); } d->blockColorChangedSig = oldBlockColorChangedSig; } //--------------------------------------------------------------------- bool kpColorCells::isModified () const { return d->isModified; } //--------------------------------------------------------------------- void kpColorCells::setModified (bool yes) { +#if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "kpColorCells::setModified(" << yes << ")"; +#endif if (yes == d->isModified) { return; } d->isModified = yes; emit isModifiedChanged (yes); } //--------------------------------------------------------------------- void kpColorCells::setModified () { setModified (true); } //--------------------------------------------------------------------- QUrl kpColorCells::url () const { return d->url; } //--------------------------------------------------------------------- QString kpColorCells::name () const { return d->colorCol.name (); } //--------------------------------------------------------------------- const kpColorCollection *kpColorCells::colorCollection () const { return &d->colorCol; } //--------------------------------------------------------------------- void kpColorCells::ensureHaveAtLeastOneRow () { if (d->colorCol.count () == 0) { d->colorCol.resize (::TableDefaultNumColumns); } } //--------------------------------------------------------------------- void kpColorCells::setColorCollection (const kpColorCollection &colorCol, const QUrl &url) { d->colorCol = colorCol; ensureHaveAtLeastOneRow (); d->url = url; setModified (false); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); emit urlChanged (d->url); emit nameChanged (name ()); } //--------------------------------------------------------------------- bool kpColorCells::openColorCollection (const QUrl &url) { // (this will pop up an error dialog on failure) if (d->colorCol.open (url, this)) { ensureHaveAtLeastOneRow (); d->url = url; setModified (false); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); emit urlChanged (d->url); emit nameChanged (name ()); return true; } return false; } //--------------------------------------------------------------------- bool kpColorCells::saveColorCollectionAs (const QUrl &url) { // (this will pop up an error dialog on failure) if (d->colorCol.saveAs (url, true/*show overwrite prompt*/, this)) { d->url = url; setModified (false); emit urlChanged (d->url); return true; } return false; } //--------------------------------------------------------------------- bool kpColorCells::saveColorCollection () { // (this will pop up an error dialog on failure) if (d->colorCol.saveAs (d->url, false/*no overwrite prompt*/, this)) { setModified (false); return true; } return false; } //--------------------------------------------------------------------- void kpColorCells::appendRow () { // This is the easiest implementation: change the color collection // and then synchronize the table cells. The other way is to call // setRowCount() and then, synchronize the color collection. const int targetNumCells = (rowCount () + 1) * ::TableDefaultNumColumns; d->colorCol.resize (targetNumCells); setModified (true); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); } //--------------------------------------------------------------------- void kpColorCells::deleteLastRow () { // This is the easiest implementation: change the color collection // and then synchronize the table cells. The other way is to call // setRowCount() and then, synchronize the color collection. const int targetNumCells = qMax (0, (rowCount () - 1) * ::TableDefaultNumColumns); d->colorCol.resize (targetNumCells); // If there was only one row of colors to start with, the effect of this // line (after the above resize()) is to change that row to a row of // invalid colors. ensureHaveAtLeastOneRow (); setModified (true); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpColorCells::contextMenuEvent (QContextMenuEvent *e) { // Eat right-mouse press to prevent it from getting to the toolbar. e->accept (); } //--------------------------------------------------------------------- // protected slot void kpColorCells::slotColorSelected (int cell, const QColor &color, Qt::MouseButton button) { +#if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "kpColorCells::slotColorSelected(cell=" << cell << ") mouseButton = " << button << " rgb=" << (int *) color.rgba(); +#else + Q_UNUSED (cell); +#endif if (button == Qt::LeftButton) { emit foregroundColorChanged (kpColor (color.rgba())); } else if (button == Qt::RightButton) { emit backgroundColorChanged (kpColor (color.rgba())); } // REFACTOR: Make selectedness configurable inside kpColorCellsBase? // // Deselect the selected cell (selected by above kpColorCellsBase::mouseReleaseEvent()). // KolourPaint's palette has no concept of a current cell/color: you can // pick a color but you can't mark a cell as selected. In any case, a // selected cell would be rendered as violet, which would ruin the cell. // // setSelectionMode (kpColorCellsBase::NoSelection); does not work so we // clearSelection(). I think setSelectionMode() concerns when the user // directly selects a cell - not when kpColorCellsBase::mouseReleaseEvent() // selects a cell programmatically. clearSelection (); } //--------------------------------------------------------------------- // protected slot void kpColorCells::slotColorDoubleClicked (int cell, const QColor &) { QColorDialog dialog(this); dialog.setCurrentColor(kpColorCellsBase::color(cell)); dialog.setOptions(QColorDialog::ShowAlphaChannel); if ( dialog.exec() == QDialog::Accepted ) setColor (cell, dialog.currentColor()); } //--------------------------------------------------------------------- // protected slot void kpColorCells::slotColorChanged (int cell, const QColor &color) { +#if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "cell=" << cell << "color=" << (const int *) color.rgba() << "d->colorCol.count()=" << d->colorCol.count (); +#endif if (d->blockColorChangedSig) { return; } // Cater for adding new colors to the end. if (cell >= d->colorCol.count ()) { d->colorCol.resize (cell + 1); } // TODO: We lose color names on a color swap (during drag-and-drop). const int ret = d->colorCol.changeColor (cell, color, QString ()/*color name*/); Q_ASSERT (ret == cell); setModified (true); } diff --git a/widgets/kpColorPalette.cpp b/widgets/kpColorPalette.cpp index 3259266e..66793e60 100644 --- a/widgets/kpColorPalette.cpp +++ b/widgets/kpColorPalette.cpp @@ -1,122 +1,126 @@ /* Copyright (c) 2003-2007 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_COLOR_PALETTE 0 + + #include "kpColorPalette.h" #include "kpColorCells.h" #include "kpTransparentColorCell.h" #include //--------------------------------------------------------------------- struct kpColorPalettePrivate { Qt::Orientation orientation; QBoxLayout *boxLayout; kpTransparentColorCell *transparentColorCell; kpColorCells *colorCells; }; //--------------------------------------------------------------------- kpColorPalette::kpColorPalette (QWidget *parent, Qt::Orientation o) : QWidget (parent), d (new kpColorPalettePrivate ()) { d->boxLayout = nullptr; d->transparentColorCell = new kpTransparentColorCell (this); connect (d->transparentColorCell, &kpTransparentColorCell::foregroundColorChanged, this, &kpColorPalette::foregroundColorChanged); connect (d->transparentColorCell, &kpTransparentColorCell::backgroundColorChanged, this, &kpColorPalette::backgroundColorChanged); d->colorCells = new kpColorCells (this); connect (d->colorCells, &kpColorCells::foregroundColorChanged, this, &kpColorPalette::foregroundColorChanged); connect (d->colorCells, &kpColorCells::backgroundColorChanged, this, &kpColorPalette::backgroundColorChanged); setOrientation (o); } //--------------------------------------------------------------------- kpColorPalette::~kpColorPalette () { delete d; } //--------------------------------------------------------------------- // public Qt::Orientation kpColorPalette::orientation () const { return d->orientation; } //--------------------------------------------------------------------- void kpColorPalette::setOrientation (Qt::Orientation o) { d->colorCells->setOrientation (o); delete d->boxLayout; if (o == Qt::Horizontal) { d->boxLayout = new QBoxLayout (QBoxLayout::LeftToRight, this); d->boxLayout->addWidget (d->transparentColorCell, 0/*stretch*/, Qt::AlignTop); d->boxLayout->addWidget (d->colorCells); } else { d->boxLayout = new QBoxLayout (QBoxLayout::TopToBottom, this); d->boxLayout->addWidget (d->transparentColorCell, 0/*stretch*/, Qt::AlignTop); d->boxLayout->addWidget (d->colorCells); } d->boxLayout->setSpacing (5); d->orientation = o; } //--------------------------------------------------------------------- // public kpColorCells *kpColorPalette::colorCells () const { return d->colorCells; } //--------------------------------------------------------------------- diff --git a/widgets/kpDefaultColorCollection.cpp b/widgets/kpDefaultColorCollection.cpp index cec8e1b3..c4ca7031 100644 --- a/widgets/kpDefaultColorCollection.cpp +++ b/widgets/kpDefaultColorCollection.cpp @@ -1,68 +1,69 @@ /* Copyright (c) 2003-2007 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 "kpDefaultColorCollection.h" #include "imagelib/kpColor.h" + kpDefaultColorCollection::kpDefaultColorCollection () { kpColor colors [] = { kpColor::Black, kpColor::Gray, kpColor::Red, kpColor::Orange, kpColor::Yellow, kpColor::Green, kpColor::Aqua, kpColor::Blue, kpColor::Purple, kpColor::Pink, kpColor::LightGreen, kpColor::White, kpColor::LightGray, kpColor::DarkRed, kpColor::DarkOrange, kpColor::DarkYellow, kpColor::DarkGreen, kpColor::DarkAqua, kpColor::DarkBlue, kpColor::DarkPurple, kpColor::LightBlue, kpColor::Tan }; for (const auto & color : colors) { addColor (color.toQColor ()); } } kpDefaultColorCollection::~kpDefaultColorCollection () = default; diff --git a/widgets/kpDocumentSaveOptionsWidget.cpp b/widgets/kpDocumentSaveOptionsWidget.cpp index a4bf7f66..f658af75 100644 --- a/widgets/kpDocumentSaveOptionsWidget.cpp +++ b/widgets/kpDocumentSaveOptionsWidget.cpp @@ -1,697 +1,754 @@ /* Copyright (c) 2003-2007 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_SAVE_OPTIONS_WIDGET 0 + + #include "widgets/kpDocumentSaveOptionsWidget.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "dialogs/kpDocumentSaveOptionsPreviewDialog.h" #include "pixmapfx/kpPixmapFX.h" #include "generic/widgets/kpResizeSignallingLabel.h" #include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" #include "generic/kpWidgetMapper.h" #include "kpLogCategories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include kpDocumentSaveOptionsWidget::kpDocumentSaveOptionsWidget ( const QImage &docPixmap, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, QWidget *parent) : QWidget (parent), m_visualParent (parent) { init (); setDocumentSaveOptions (saveOptions); setDocumentPixmap (docPixmap); setDocumentMetaInfo (metaInfo); } kpDocumentSaveOptionsWidget::kpDocumentSaveOptionsWidget ( QWidget *parent) : QWidget (parent), m_visualParent (parent) { init (); } // private void kpDocumentSaveOptionsWidget::init () { m_documentPixmap = nullptr; m_previewDialog = nullptr; m_visualParent = nullptr; m_colorDepthLabel = new QLabel (i18n ("Convert &to:"), this); m_colorDepthCombo = new QComboBox (this); m_colorDepthSpaceWidget = new QWidget (this); m_qualityLabel = new QLabel(i18n ("Quali&ty:"), this); m_qualityInput = new QSpinBox(this); // Note that we set min to 1 not 0 since "0 Quality" is a bit misleading // and 101 quality settings would be weird. So we lose 1 quality setting // according to QImage::save(). // TODO: 100 quality is also misleading since that implies perfect quality. m_qualityInput->setRange (1, 100); m_previewButton = new QPushButton (i18n ("&Preview"), this); m_previewButton->setCheckable (true); m_colorDepthLabel->setBuddy (m_colorDepthCombo); m_qualityLabel->setBuddy (m_qualityInput); auto *lay = new QHBoxLayout (this); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget (m_colorDepthLabel, 0/*stretch*/, Qt::AlignLeft); lay->addWidget (m_colorDepthCombo, 0/*stretch*/); lay->addWidget (m_colorDepthSpaceWidget, 1/*stretch*/); lay->addWidget (m_qualityLabel, 0/*stretch*/, Qt::AlignLeft); lay->addWidget (m_qualityInput, 2/*stretch*/); lay->addWidget (m_previewButton, 0/*stretch*/, Qt::AlignRight); connect (m_colorDepthCombo, static_cast(&QComboBox::activated), this, &kpDocumentSaveOptionsWidget::slotColorDepthSelected); connect (m_colorDepthCombo, static_cast(&QComboBox::activated), this, &kpDocumentSaveOptionsWidget::updatePreview); connect (m_qualityInput, static_cast(&QSpinBox::valueChanged), this, &kpDocumentSaveOptionsWidget::updatePreviewDelayed); connect (m_previewButton, &QPushButton::toggled, this, &kpDocumentSaveOptionsWidget::showPreview); m_updatePreviewDelay = 200/*ms*/; m_updatePreviewTimer = new QTimer (this); m_updatePreviewTimer->setSingleShot (true); connect (m_updatePreviewTimer, &QTimer::timeout, this, &kpDocumentSaveOptionsWidget::updatePreview); m_updatePreviewDialogLastRelativeGeometryTimer = new QTimer (this); connect (m_updatePreviewDialogLastRelativeGeometryTimer, &QTimer::timeout, this, &kpDocumentSaveOptionsWidget::updatePreviewDialogLastRelativeGeometry); setMode (None); slotColorDepthSelected (); } kpDocumentSaveOptionsWidget::~kpDocumentSaveOptionsWidget () { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::()"; +#endif hidePreview (); delete m_documentPixmap; } // public void kpDocumentSaveOptionsWidget::setVisualParent (QWidget *visualParent) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::setVisualParent(" - << visualParent << ")"; + << visualParent << ")" << endl; +#endif m_visualParent = visualParent; } // protected bool kpDocumentSaveOptionsWidget::mimeTypeHasConfigurableColorDepth () const { return kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (mimeType ()); } // protected bool kpDocumentSaveOptionsWidget::mimeTypeHasConfigurableQuality () const { return kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (mimeType ()); } // public QString kpDocumentSaveOptionsWidget::mimeType () const { return m_baseDocumentSaveOptions.mimeType (); } // public slots void kpDocumentSaveOptionsWidget::setMimeType (const QString &string) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::setMimeType(" << string << ") maxColorDepth=" - << kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string); + << kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string) + << endl; +#endif const int newMimeTypeMaxDepth = kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string); +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\toldMimeType=" << mimeType () << " maxColorDepth=" << kpDocumentSaveOptions::mimeTypeMaximumColorDepth ( - mimeType ()); + mimeType ()) + << endl; +#endif if (mimeType ().isEmpty () || kpDocumentSaveOptions::mimeTypeMaximumColorDepth (mimeType ()) != newMimeTypeMaxDepth) { m_colorDepthCombo->clear (); m_colorDepthCombo->insertItem (0, i18n ("Monochrome")); m_colorDepthCombo->insertItem (1, i18n ("Monochrome (Dithered)")); if (newMimeTypeMaxDepth >= 8) { m_colorDepthCombo->insertItem (2, i18n ("256 Color")); m_colorDepthCombo->insertItem (3, i18n ("256 Color (Dithered)")); } if (newMimeTypeMaxDepth >= 24) { m_colorDepthCombo->insertItem (4, i18n ("24-bit Color")); } if (m_colorDepthComboLastSelectedItem >= 0 && m_colorDepthComboLastSelectedItem < m_colorDepthCombo->count ()) { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tsetting colorDepthCombo to " - << m_colorDepthComboLastSelectedItem; + << m_colorDepthComboLastSelectedItem << endl; + #endif m_colorDepthCombo->setCurrentIndex (m_colorDepthComboLastSelectedItem); } else { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tsetting colorDepthCombo to max item since" << " m_colorDepthComboLastSelectedItem=" << m_colorDepthComboLastSelectedItem - << " out of range"; + << " out of range" << endl; + #endif m_colorDepthCombo->setCurrentIndex (m_colorDepthCombo->count () - 1); } } + m_baseDocumentSaveOptions.setMimeType (string); if (mimeTypeHasConfigurableColorDepth ()) { setMode (ColorDepth); } else if (mimeTypeHasConfigurableQuality ()) { setMode (Quality); } else { setMode (None); } updatePreview (); } // public int kpDocumentSaveOptionsWidget::colorDepth () const { if (mode () & ColorDepth) { // The returned values match QImage's supported depths. switch (m_colorDepthCombo->currentIndex ()) { case 0: case 1: return 1; case 2: case 3: return 8; case 4: // 24-bit is known as 32-bit with QImage. return 32; default: return kpDocumentSaveOptions::invalidColorDepth (); } } else { return m_baseDocumentSaveOptions.colorDepth (); } } // public bool kpDocumentSaveOptionsWidget::dither () const { if (mode () & ColorDepth) { return (m_colorDepthCombo->currentIndex () == 1 || m_colorDepthCombo->currentIndex () == 3); } return m_baseDocumentSaveOptions.dither (); } // protected static int kpDocumentSaveOptionsWidget::colorDepthComboItemFromColorDepthAndDither ( int depth, bool dither) { switch (depth) { case 1: if (!dither) { return 0; } return 1; case 8: if (!dither) { return 2; } return 3; case 32: return 4; default: return -1; } } // public slots void kpDocumentSaveOptionsWidget::setColorDepthDither (int newDepth, bool newDither) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::setColorDepthDither(" << "depth=" << newDepth << ",dither=" << newDither - << ")"; + << ")" << endl; +#endif m_baseDocumentSaveOptions.setColorDepth (newDepth); m_baseDocumentSaveOptions.setDither (newDither); const int comboItem = colorDepthComboItemFromColorDepthAndDither ( newDepth, newDither); // TODO: Ignoring when comboItem >= m_colorDepthCombo->count() is wrong. // This happens if this mimeType has configurable colour depth // and an incorrect maximum colour depth (less than a QImage of // this mimeType, opened by kpDocument). if (comboItem >= 0 && comboItem < m_colorDepthCombo->count ()) { m_colorDepthCombo->setCurrentIndex (comboItem); } slotColorDepthSelected (); } // protected slot void kpDocumentSaveOptionsWidget::slotColorDepthSelected () { if (mode () & ColorDepth) { m_colorDepthComboLastSelectedItem = m_colorDepthCombo->currentIndex (); } else { m_colorDepthComboLastSelectedItem = colorDepthComboItemFromColorDepthAndDither ( m_baseDocumentSaveOptions.colorDepth (), m_baseDocumentSaveOptions.dither ()); } +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::slotColorDepthSelected()" << " mode&ColorDepth=" << (mode () & ColorDepth) << " colorDepthComboLastSelectedItem=" - << m_colorDepthComboLastSelectedItem; + << m_colorDepthComboLastSelectedItem + << endl; +#endif } // public int kpDocumentSaveOptionsWidget::quality () const { if (mode () & Quality) { return m_qualityInput->value (); } return m_baseDocumentSaveOptions.quality (); } // public void kpDocumentSaveOptionsWidget::setQuality (int newQuality) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::setQuality(" - << newQuality << ")"; + << newQuality << ")" << endl; +#endif m_baseDocumentSaveOptions.setQuality (newQuality); m_qualityInput->setValue (newQuality == -1/*QImage::save() default*/ ? 75 : newQuality); } // public kpDocumentSaveOptions kpDocumentSaveOptionsWidget::documentSaveOptions () const { return kpDocumentSaveOptions (mimeType (), colorDepth (), dither (), quality ()); } // public void kpDocumentSaveOptionsWidget::setDocumentSaveOptions ( const kpDocumentSaveOptions &saveOptions) { setMimeType (saveOptions.mimeType ()); setColorDepthDither (saveOptions.colorDepth (), saveOptions.dither ()); setQuality (saveOptions.quality ()); } // public void kpDocumentSaveOptionsWidget::setDocumentPixmap (const QImage &documentPixmap) { delete m_documentPixmap; m_documentPixmap = new QImage (documentPixmap); updatePreview (); } // public void kpDocumentSaveOptionsWidget::setDocumentMetaInfo ( const kpDocumentMetaInfo &metaInfo) { m_documentMetaInfo = metaInfo; updatePreview (); } // public kpDocumentSaveOptionsWidget::Mode kpDocumentSaveOptionsWidget::mode () const { return m_mode; } // public void kpDocumentSaveOptionsWidget::setMode (Mode mode) { m_mode = mode; // If mode == None, we show still show the Color Depth widgets but disabled m_colorDepthLabel->setVisible (mode != Quality); m_colorDepthCombo->setVisible (mode != Quality); m_colorDepthSpaceWidget->setVisible (mode != Quality); m_qualityLabel->setVisible (mode == Quality); m_qualityInput->setVisible (mode == Quality); m_colorDepthLabel->setEnabled (mode == ColorDepth); m_colorDepthCombo->setEnabled (mode == ColorDepth); m_qualityLabel->setEnabled (mode == Quality); m_qualityInput->setEnabled (mode == Quality); // SYNC: HACK: When changing between color depth and quality widgets, // we change the height of "this", causing the text on the labels // to move but the first instance of the text doesn't get erased. // Qt bug. QTimer::singleShot (0, this, SLOT (repaintLabels())); } // protected slot void kpDocumentSaveOptionsWidget::repaintLabels () { if (mode () != Quality) { m_colorDepthLabel->repaint (); } if (mode () == Quality) { m_qualityLabel->repaint (); } } // protected slot void kpDocumentSaveOptionsWidget::showPreview (bool yes) { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::showPreview(" << yes << ")" - << " m_previewDialog=" << bool (m_previewDialog); + << " m_previewDialog=" << bool (m_previewDialog) + << endl; +#endif if (yes == bool (m_previewDialog)) { return; } if (!m_visualParent) { return; } if (yes) { m_previewDialog = new kpDocumentSaveOptionsPreviewDialog( m_visualParent ); m_previewDialog->setObjectName( QLatin1String( "previewSaveDialog" ) ); updatePreview (); connect (m_previewDialog, &kpDocumentSaveOptionsPreviewDialog::finished, this, &kpDocumentSaveOptionsWidget::hidePreview); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupPreviewSave); if (cfg.hasKey (kpSettingPreviewSaveUpdateDelay)) { m_updatePreviewDelay = cfg.readEntry (kpSettingPreviewSaveUpdateDelay, 0); } else { cfg.writeEntry (kpSettingPreviewSaveUpdateDelay, m_updatePreviewDelay); cfg.sync (); } if (m_updatePreviewDelay < 0) { m_updatePreviewDelay = 0; } + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tread cfg preview dialog update delay=" << m_updatePreviewDelay; + #endif if (m_previewDialogLastRelativeGeometry.isEmpty ()) { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tread cfg preview dialog last rel geometry"; + #endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupPreviewSave); m_previewDialogLastRelativeGeometry = cfg.readEntry ( kpSettingPreviewSaveGeometry, QRect ()); } + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tpreviewDialogLastRelativeGeometry=" << m_previewDialogLastRelativeGeometry - << " visualParent->rect()=" << m_visualParent->rect (); + << " visualParent->rect()=" << m_visualParent->rect () + << endl; + #endif QRect relativeGeometry; if (!m_previewDialogLastRelativeGeometry.isEmpty () && m_visualParent->rect ().intersects (m_previewDialogLastRelativeGeometry)) { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tok"; + #endif relativeGeometry = m_previewDialogLastRelativeGeometry; } else { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\t\tinvalid"; + #endif const int margin = 20; relativeGeometry = QRect (m_visualParent->width () - m_previewDialog->preferredMinimumSize ().width () - margin, margin * 2, // Avoid folder combo m_previewDialog->preferredMinimumSize ().width (), m_previewDialog->preferredMinimumSize ().height ()); } const QRect globalGeometry = kpWidgetMapper::toGlobal (m_visualParent, relativeGeometry); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\trelativeGeometry=" << relativeGeometry - << " globalGeometry=" << globalGeometry; + << " globalGeometry=" << globalGeometry + << endl; + #endif m_previewDialog->resize (globalGeometry.size ()); m_previewDialog->move (globalGeometry.topLeft ()); m_previewDialog->show (); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tgeometry after show=" << QRect (m_previewDialog->x (), m_previewDialog->y (), - m_previewDialog->width (), m_previewDialog->height ()); + m_previewDialog->width (), m_previewDialog->height ()) + << endl; + #endif updatePreviewDialogLastRelativeGeometry (); connect (m_previewDialog, &kpDocumentSaveOptionsPreviewDialog::moved, this, &kpDocumentSaveOptionsWidget::updatePreviewDialogLastRelativeGeometry); connect (m_previewDialog, &kpDocumentSaveOptionsPreviewDialog::resized, this, &kpDocumentSaveOptionsWidget::updatePreviewDialogLastRelativeGeometry); m_updatePreviewDialogLastRelativeGeometryTimer->start (200/*ms*/); } else { m_updatePreviewDialogLastRelativeGeometryTimer->stop (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupPreviewSave); cfg.writeEntry (kpSettingPreviewSaveGeometry, m_previewDialogLastRelativeGeometry); cfg.sync (); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tsaving preview geometry " << m_previewDialogLastRelativeGeometry << " (Qt would have us believe " << kpWidgetMapper::fromGlobal (m_visualParent, QRect (m_previewDialog->x (), m_previewDialog->y (), m_previewDialog->width (), m_previewDialog->height ())) - << ")"; + << ")" + << endl; + #endif m_previewDialog->deleteLater (); m_previewDialog = nullptr; } } // protected slot void kpDocumentSaveOptionsWidget::hidePreview () { if (m_previewButton->isChecked ()) { m_previewButton->toggle (); } } // protected slot void kpDocumentSaveOptionsWidget::updatePreviewDelayed () { // (single shot) m_updatePreviewTimer->start (m_updatePreviewDelay); } // protected slot void kpDocumentSaveOptionsWidget::updatePreview () { if (!m_previewDialog || !m_documentPixmap) { return; } m_updatePreviewTimer->stop (); QApplication::setOverrideCursor (Qt::WaitCursor); QByteArray data; QBuffer buffer (&data); buffer.open (QIODevice::WriteOnly); bool savedOK = kpDocument::savePixmapToDevice (*m_documentPixmap, &buffer, documentSaveOptions (), m_documentMetaInfo, false/*no lossy prompt*/, this); buffer.close (); QImage image; // Ignore any failed saves. // // Failed saves might literally have written half a file. The final // save (when the user clicks OK), _will_ fail so we shouldn't have a // preview even if this "half a file" is actually loadable by // QImage::loadFormData(). if (savedOK) { image.loadFromData(data); } else { // Leave as invalid. // TODO: This code path has not been well tested. // Will we trigger divide by zero errors in "m_previewDialog"? } // REFACTOR: merge with kpDocument::getPixmapFromFile() m_previewDialog->setFilePixmapAndSize (image, data.size ()); QApplication::restoreOverrideCursor (); } // protected slot void kpDocumentSaveOptionsWidget::updatePreviewDialogLastRelativeGeometry () { +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveOptionsWidget::" - << "updatePreviewDialogLastRelativeGeometry()"; + << "updatePreviewDialogLastRelativeGeometry()" + << endl; +#endif if (m_previewDialog && m_previewDialog->isVisible ()) { m_previewDialogLastRelativeGeometry = kpWidgetMapper::fromGlobal (m_visualParent, QRect (m_previewDialog->x (), m_previewDialog->y (), m_previewDialog->width (), m_previewDialog->height ())); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tcaching pos = " << m_previewDialogLastRelativeGeometry; + #endif } else { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tnot visible - ignoring geometry"; + #endif } } + + diff --git a/widgets/kpDualColorButton.cpp b/widgets/kpDualColorButton.cpp index cd4da1ff..1e8c6c8a 100644 --- a/widgets/kpDualColorButton.cpp +++ b/widgets/kpDualColorButton.cpp @@ -1,444 +1,470 @@ /* Copyright (c) 2003-2007 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_DUAL_COLOR_BUTTON 0 #include "kpDualColorButton.h" #include "views/kpView.h" #include #include "kpLogCategories.h" #include #include #include #include #include #include #include #include //--------------------------------------------------------------------- kpDualColorButton::kpDualColorButton (QWidget *parent) : QFrame (parent), m_dragStartPoint (KP_INVALID_POINT) { setSizePolicy (QSizePolicy::Fixed/*horizontal*/, QSizePolicy::Fixed/*vertical*/); setFrameStyle (QFrame::Panel | QFrame::Sunken); m_color [0] = kpColor (0, 0, 0); // black m_color [1] = kpColor (255, 255, 255); // white setAcceptDrops (true); } //--------------------------------------------------------------------- kpColor kpDualColorButton::color (int which) const { Q_ASSERT (which == 0 || which == 1); return m_color [which]; } //--------------------------------------------------------------------- kpColor kpDualColorButton::foregroundColor () const { return color (0); } //--------------------------------------------------------------------- kpColor kpDualColorButton::backgroundColor () const { return color (1); } //--------------------------------------------------------------------- void kpDualColorButton::setColor (int which, const kpColor &color) { Q_ASSERT (which == 0 || which == 1); if (m_color [which] == color) { return; } m_oldColor [which] = m_color [which]; m_color [which] = color; update (); if (which == 0) { emit foregroundColorChanged (color); } else { emit backgroundColorChanged (color); } } //--------------------------------------------------------------------- void kpDualColorButton::setForegroundColor (const kpColor &color) { setColor (0, color); } //--------------------------------------------------------------------- void kpDualColorButton::setBackgroundColor (const kpColor &color) { setColor (1, color); } //--------------------------------------------------------------------- // public kpColor kpDualColorButton::oldForegroundColor () const { return m_oldColor [0]; } //--------------------------------------------------------------------- // public kpColor kpDualColorButton::oldBackgroundColor () const { return m_oldColor [1]; } //--------------------------------------------------------------------- // public virtual [base QWidget] QSize kpDualColorButton::sizeHint () const { return {52, 52}; } //--------------------------------------------------------------------- // protected QRect kpDualColorButton::swapPixmapRect () const { QPixmap swapPixmap = UserIcon ("colorbutton_swap_16x16"); return {contentsRect ().width () - swapPixmap.width (), 0, swapPixmap.width (), swapPixmap.height ()}; } //--------------------------------------------------------------------- // protected QRect kpDualColorButton::foregroundBackgroundRect () const { QRect cr (contentsRect ()); return {cr.width () / 8, cr.height () / 8, cr.width () * 6 / 8, cr.height () * 6 / 8}; } //--------------------------------------------------------------------- // protected QRect kpDualColorButton::foregroundRect () const { QRect fbr (foregroundBackgroundRect ()); return {fbr.x (), fbr.y (), fbr.width () * 3 / 4, fbr.height () * 3 / 4}; } //--------------------------------------------------------------------- // protected QRect kpDualColorButton::backgroundRect () const { QRect fbr (foregroundBackgroundRect ()); return {fbr.x () + fbr.width () / 4, fbr.y () + fbr.height () / 4, fbr.width () * 3 / 4, fbr.height () * 3 / 4}; } //--------------------------------------------------------------------- // protected virtual void kpDualColorButton::dragEnterEvent (QDragEnterEvent *e) { +#if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "kpDualColorButton::dragEnterEvent() canDecode=" << KColorMimeData::canDecode (e->mimeData ()); +#endif e->accept (); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpDualColorButton::dragMoveEvent (QDragMoveEvent *e) { +#if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "kpDualColorButton::dragMoveEvent() canDecode=" << KColorMimeData::canDecode (e->mimeData ()); +#endif e->setAccepted ( (foregroundRect ().contains (e->pos ()) || backgroundRect ().contains (e->pos ())) && KColorMimeData::canDecode (e->mimeData ())); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpDualColorButton::dropEvent (QDropEvent *e) { QColor col = KColorMimeData::fromMimeData (e->mimeData ()); +#if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "kpDualColorButton::dropEvent() col=" << (int *) col.rgba() << " (with alpha=" << (int *) col.rgba () << ")"; +#endif if (col.isValid ()) { if (foregroundRect ().contains (e->pos ())) { setForegroundColor (kpColor (col.rgba())); } else if (backgroundRect ().contains (e->pos ())) { setBackgroundColor (kpColor (col.rgba())); } } } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpDualColorButton::mousePressEvent (QMouseEvent *e) { +#if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "kpDualColorButton::mousePressEvent() pos=" << e->pos (); +#endif m_dragStartPoint = KP_INVALID_POINT; if (e->button () == Qt::LeftButton) { m_dragStartPoint = e->pos (); } } //--------------------------------------------------------------------- void kpDualColorButton::mouseMoveEvent (QMouseEvent *e) { +#if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "kpDualColorButton::mouseMoveEvent() pos=" << e->pos () << " buttons=" << e->buttons () - << " dragStartPoint=" << m_dragStartPoint; + << " dragStartPoint=" << m_dragStartPoint << endl; +#endif if (m_dragStartPoint == KP_INVALID_POINT) { return; } if (!(e->buttons () & Qt::LeftButton)) { m_dragStartPoint = KP_INVALID_POINT; return; } const int delay = QApplication::startDragDistance (); if (e->x () < m_dragStartPoint.x () - delay || e->x () > m_dragStartPoint.x () + delay || e->y () < m_dragStartPoint.y () - delay || e->y () > m_dragStartPoint.y () + delay) { + #if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "\tstarting drag as long as it's in a rectangle"; + #endif kpColor color; if (foregroundRect ().contains (m_dragStartPoint)) { color = foregroundColor (); } else if (backgroundRect ().contains (m_dragStartPoint)) { color = backgroundColor (); } + #if DEBUG_KP_DUAL_COLOR_BUTTON qCDebug(kpLogWidgets) << "\tcolor.isValid=" << color.isValid () - << " rgb=" << (color.isValid () ? (int *) color.toQRgb () : 0); + << " rgb=" << (color.isValid () ? (int *) color.toQRgb () : 0) + << endl; + #endif if (color.isValid ()) { if (!color.isTransparent ()) { KColorMimeData::createDrag (color.toQColor (), this)->exec (); } } m_dragStartPoint = KP_INVALID_POINT; } } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpDualColorButton::mouseReleaseEvent (QMouseEvent *e) { m_dragStartPoint = KP_INVALID_POINT; if (swapPixmapRect ().contains (e->pos ()) && m_color [0] != m_color [1]) { + #if DEBUG_KP_DUAL_COLOR_BUTTON && 1 qCDebug(kpLogWidgets) << "kpDualColorButton::mouseReleaseEvent() swap colors:"; + #endif m_oldColor [0] = m_color [0]; m_oldColor [1] = m_color [1]; kpColor temp = m_color [0]; m_color [0] = m_color [1]; m_color [1] = temp; update (); emit colorsSwapped (m_color [0], m_color [1]); emit foregroundColorChanged (m_color [0]); emit backgroundColorChanged (m_color [1]); } } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpDualColorButton::mouseDoubleClickEvent (QMouseEvent *e) { int whichColor = -1; if (foregroundRect ().contains (e->pos ())) { whichColor = 0; } else if (backgroundRect ().contains (e->pos ())) { whichColor = 1; } if (whichColor == 0 || whichColor == 1) { QColorDialog dialog(this); dialog.setCurrentColor(color(whichColor).toQColor()); dialog.setOptions(QColorDialog::ShowAlphaChannel); if ( dialog.exec() == QDialog::Accepted ) { setColor(whichColor, kpColor(dialog.currentColor().rgba())); } } } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpDualColorButton::paintEvent (QPaintEvent *e) { +#if DEBUG_KP_DUAL_COLOR_BUTTON && 1 qCDebug(kpLogWidgets) << "kpDualColorButton::draw() rect=" << rect () - << " contentsRect=" << contentsRect (); + << " contentsRect=" << contentsRect () + << endl; +#endif // Draw frame first. QFrame::paintEvent (e); QPainter painter (this); // Fill with background. if (isEnabled ()) { kpView::drawTransparentBackground (&painter, contentsRect ().topLeft ()/*checkerboard top-left*/, contentsRect (), true/*preview*/); } else { // Use default widget background. } painter.translate (contentsRect ().x (), contentsRect ().y ()); // Draw "Swap Colours" button (top-right). QPixmap swapPixmap = UserIcon ("colorbutton_swap_16x16"); if (!isEnabled ()) { // Don't let the fill() touch the mask. QBitmap swapBitmapMask = swapPixmap.mask (); swapPixmap.setMask (QBitmap ()); // Grey out the opaque parts of "swapPixmap". swapPixmap.fill (palette ().color (QPalette::Dark)); swapPixmap.setMask (swapBitmapMask); } painter.drawPixmap (swapPixmapRect ().topLeft (), swapPixmap); // Draw background colour patch. QRect bgRect = backgroundRect (); QRect bgRectInside = QRect (bgRect.x () + 2, bgRect.y () + 2, bgRect.width () - 4, bgRect.height () - 4); if (isEnabled ()) { - qCDebug(kpLogWidgets) << "\tbackgroundColor=" << (int *) m_color [1].toQRgb (); - if (m_color [1].isTransparent ()) { // only if fully transparent + #if DEBUG_KP_DUAL_COLOR_BUTTON && 1 + qCDebug(kpLogWidgets) << "\tbackgroundColor=" << (int *) m_color [1].toQRgb () + << endl; + #endif + if (m_color [1].isTransparent ()) { // only if fully transparent painter.drawPixmap (bgRectInside, UserIcon ("color_transparent_26x26")); } else { painter.fillRect (bgRectInside, m_color [1].toQColor ()); } } else { painter.fillRect (bgRectInside, palette().color (QPalette::Button)); } qDrawShadePanel (&painter, bgRect, palette(), false/*not sunken*/, 2/*lineWidth*/, nullptr/*never fill*/); // Draw foreground colour patch. // Must be drawn after background patch since we're on top. QRect fgRect = foregroundRect (); QRect fgRectInside = QRect (fgRect.x () + 2, fgRect.y () + 2, fgRect.width () - 4, fgRect.height () - 4); if (isEnabled ()) { - qCDebug(kpLogWidgets) << "\tforegroundColor=" << (int *) m_color [0].toQRgb (); - if (m_color [0].isTransparent ()) { // only if fully transparent + #if DEBUG_KP_DUAL_COLOR_BUTTON && 1 + qCDebug(kpLogWidgets) << "\tforegroundColor=" << (int *) m_color [0].toQRgb () + << endl; + #endif + if (m_color [0].isTransparent ()) { // only if fully transparent painter.drawPixmap (fgRectInside, UserIcon ("color_transparent_26x26")); } else { painter.fillRect (fgRectInside, m_color [0].toQColor ()); } } else { painter.fillRect (fgRectInside, palette ().color (QPalette::Button)); } qDrawShadePanel (&painter, fgRect, palette (), false/*not sunken*/, 2/*lineWidth*/, nullptr/*never fill*/); } diff --git a/widgets/kpPrintDialogPage.cpp b/widgets/kpPrintDialogPage.cpp index e8e84782..a7b11574 100644 --- a/widgets/kpPrintDialogPage.cpp +++ b/widgets/kpPrintDialogPage.cpp @@ -1,96 +1,102 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2007 John Layt 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_PRINT_DIALOG_PAGE 0 #include "kpPrintDialogPage.h" #include #include #include #include "kpLogCategories.h" #include #include "kpDefs.h" struct kpPrintDialogPagePrivate { QRadioButton *printCenteredRadio, *printTopLeftRadio; }; kpPrintDialogPage::kpPrintDialogPage (QWidget *parent) : QWidget (parent), d (new kpPrintDialogPagePrivate ()) { +#if DEBUG_KP_PRINT_DIALOG_PAGE qCDebug(kpLogWidgets) << "kpPrintDialogPage::()"; +#endif setWindowTitle (i18nc ("@title:tab", "I&mage Position")); d->printCenteredRadio = new QRadioButton (i18n ("&Center of the page"), this); d->printTopLeftRadio = new QRadioButton (i18n ("Top-&left of the page"), this); auto *lay = new QVBoxLayout (this); lay->addWidget (d->printCenteredRadio); lay->addWidget (d->printTopLeftRadio); lay->addStretch (); setPrintImageCenteredOnPage (true); } kpPrintDialogPage::~kpPrintDialogPage () { delete d; } bool kpPrintDialogPage::printImageCenteredOnPage () { +#if DEBUG_KP_PRINT_DIALOG_PAGE qCDebug(kpLogWidgets) << "kpPrintDialogPage::printImageCenteredOnPage()" << " returning " << d->printCenteredRadio->isChecked(); +#endif return d->printCenteredRadio->isChecked (); } void kpPrintDialogPage::setPrintImageCenteredOnPage (bool printCentered) { +#if DEBUG_KP_PRINT_DIALOG_PAGE qCDebug(kpLogWidgets) << "kpPrintDialogPage::setOptions(" << printCentered << ")"; +#endif if (printCentered) { d->printCenteredRadio->setChecked (true); } else { d->printTopLeftRadio->setChecked (true); } } diff --git a/widgets/kpTransparentColorCell.cpp b/widgets/kpTransparentColorCell.cpp index e4586e20..6c8dffe4 100644 --- a/widgets/kpTransparentColorCell.cpp +++ b/widgets/kpTransparentColorCell.cpp @@ -1,126 +1,128 @@ /* Copyright (c) 2003-2007 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_TRANSPARENT_COLOR_CELL 0 #include "kpTransparentColorCell.h" #include "imagelib/kpColor.h" #include #include #include #include #include -#include "kpLogCategories.h" //--------------------------------------------------------------------- kpTransparentColorCell::kpTransparentColorCell (QWidget *parent) : QFrame (parent) { setSizePolicy (QSizePolicy::Fixed/*horizontal*/, QSizePolicy::Fixed/*vertical*/); setFrameStyle (QFrame::Panel | QFrame::Sunken); m_pixmap = UserIcon ("color_transparent_26x26"); this->setToolTip( i18n ("Transparent")); } //--------------------------------------------------------------------- // public virtual [base QWidget] QSize kpTransparentColorCell::sizeHint () const { return {m_pixmap.width () + frameWidth () * 2, m_pixmap.height () + frameWidth () * 2}; } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpTransparentColorCell::mousePressEvent (QMouseEvent * /*e*/) { // Eat press so that we own the mouseReleaseEvent(). // [http://blogs.qtdeveloper.net/archives/2006/05/27/mouse-event-propagation/] // // However, contrary to that blog, it doesn't seem to be needed? } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpTransparentColorCell::contextMenuEvent (QContextMenuEvent *e) { // Eat right-mouse press to prevent it from getting to the toolbar. e->accept (); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpTransparentColorCell::mouseReleaseEvent (QMouseEvent *e) { if (rect ().contains (e->pos ())) { if (e->button () == Qt::LeftButton) { emit transparentColorSelected (0); emit foregroundColorChanged (kpColor::Transparent); } else if (e->button () == Qt::RightButton) { emit transparentColorSelected (1); emit backgroundColorChanged (kpColor::Transparent); } } } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpTransparentColorCell::paintEvent (QPaintEvent *e) { // Draw frame first. QFrame::paintEvent (e); if (isEnabled ()) { + #if DEBUG_KP_TRANSPARENT_COLOR_CELL qCDebug(kpLogWidgets) << "kpTransparentColorCell::paintEvent() contentsRect=" - << contentsRect (); + << contentsRect () + << endl; + #endif QPainter p (this); p.drawPixmap (contentsRect (), m_pixmap); } } //--------------------------------------------------------------------- diff --git a/widgets/toolbars/kpColorToolBar.cpp b/widgets/toolbars/kpColorToolBar.cpp index 69acde75..fce3a9d8 100644 --- a/widgets/toolbars/kpColorToolBar.cpp +++ b/widgets/toolbars/kpColorToolBar.cpp @@ -1,328 +1,341 @@ /* Copyright (c) 2003-2007 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_COLOR_TOOL_BAR 0 #include "widgets/toolbars/kpColorToolBar.h" #include #include #include #include #include #include "kpLogCategories.h" #include "widgets/kpColorCells.h" #include "widgets/kpColorPalette.h" #include "widgets/colorSimilarity/kpColorSimilarityToolBarItem.h" #include "kpDefs.h" #include "widgets/kpDualColorButton.h" #include "lgpl/generic/kpUrlFormatter.h" //--------------------------------------------------------------------- kpColorToolBar::kpColorToolBar (const QString &label, QWidget *parent) : QDockWidget (parent) { setWindowTitle (label); // not closable, as it's not a KDE toolbar yet and can not be made shown easily again setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); setAcceptDrops (true); QWidget *base = new QWidget (this); m_boxLayout = new QBoxLayout (QBoxLayout::LeftToRight, base); m_boxLayout->setMargin (5); m_boxLayout->setSpacing (10 * 3); // This holds the current global foreground and background colors, for // tools. m_dualColorButton = new kpDualColorButton (base); connect (m_dualColorButton, &kpDualColorButton::colorsSwapped, this, &kpColorToolBar::colorsSwapped); connect (m_dualColorButton, &kpDualColorButton::foregroundColorChanged, this, &kpColorToolBar::foregroundColorChanged); connect (m_dualColorButton, &kpDualColorButton::backgroundColorChanged, this, &kpColorToolBar::backgroundColorChanged); m_boxLayout->addWidget (m_dualColorButton, 0/*stretch*/, Qt::AlignVCenter); m_colorPalette = new kpColorPalette (base); connect (m_colorPalette, &kpColorPalette::foregroundColorChanged, m_dualColorButton, &kpDualColorButton::setForegroundColor); connect (m_colorPalette, &kpColorPalette::backgroundColorChanged, m_dualColorButton, &kpDualColorButton::setBackgroundColor); connect (m_colorPalette->colorCells (), &kpColorCells::isModifiedChanged, this, &kpColorToolBar::updateNameOrUrlLabel); connect (m_colorPalette->colorCells (), &kpColorCells::urlChanged, this, &kpColorToolBar::updateNameOrUrlLabel); connect (m_colorPalette->colorCells (), &kpColorCells::nameChanged, this, &kpColorToolBar::updateNameOrUrlLabel); updateNameOrUrlLabel (); m_boxLayout->addWidget (m_colorPalette, 0/*stretch*/); m_colorSimilarityToolBarItem = new kpColorSimilarityToolBarItem (base); connect (m_colorSimilarityToolBarItem, &kpColorSimilarityToolBarItem::colorSimilarityChanged, this, &kpColorToolBar::colorSimilarityChanged); m_boxLayout->addWidget (m_colorSimilarityToolBarItem, 0/*stretch*/); // Pad out all the horizontal space on the right of the Color Tool Bar so that // that the real Color Tool Bar widgets aren't placed in the center of the // Color Tool Bar. m_boxLayout->addItem ( new QSpacerItem (1, 1, QSizePolicy::Expanding, QSizePolicy::Preferred)); adjustToOrientation (Qt::Horizontal); setWidget (base); } //--------------------------------------------------------------------- void kpColorToolBar::adjustToOrientation (Qt::Orientation o) { +#if DEBUG_KP_COLOR_TOOL_BAR qCDebug(kpLogWidgets) << "kpColorToolBar::adjustToOrientation(" << (o == Qt::Vertical ? "vertical" : "horizontal") << ") called!"; +#endif Q_ASSERT (o == Qt::Horizontal); if (o == Qt::Horizontal) { m_boxLayout->setDirection (QBoxLayout::LeftToRight); } else { m_boxLayout->setDirection (QBoxLayout::TopToBottom); } m_colorPalette->setOrientation (o); } //--------------------------------------------------------------------- // public kpColorCells *kpColorToolBar::colorCells () const { return m_colorPalette->colorCells (); } //--------------------------------------------------------------------- kpColor kpColorToolBar::color (int which) const { Q_ASSERT (which == 0 || which == 1); return m_dualColorButton->color (which); } //--------------------------------------------------------------------- void kpColorToolBar::setColor (int which, const kpColor &color) { Q_ASSERT (which == 0 || which == 1); m_dualColorButton->setColor (which, color); } //--------------------------------------------------------------------- kpColor kpColorToolBar::foregroundColor () const { return m_dualColorButton->foregroundColor (); } //--------------------------------------------------------------------- void kpColorToolBar::setForegroundColor (const kpColor &color) { +#if DEBUG_KP_COLOR_TOOL_BAR qCDebug(kpLogWidgets) << "kpColorToolBar::setForegroundColor(" << (int *) color.toQRgb () << ")"; +#endif m_dualColorButton->setForegroundColor (color); } //--------------------------------------------------------------------- kpColor kpColorToolBar::backgroundColor () const { return m_dualColorButton->backgroundColor (); } //--------------------------------------------------------------------- void kpColorToolBar::setBackgroundColor (const kpColor &color) { +#if DEBUG_KP_COLOR_TOOL_BAR qCDebug(kpLogWidgets) << "kpColorToolBar::setBackgroundColor(" << (int *) color.toQRgb () << ")"; +#endif m_dualColorButton->setBackgroundColor (color); } //--------------------------------------------------------------------- kpColor kpColorToolBar::oldForegroundColor () const { return m_dualColorButton->oldForegroundColor (); } //--------------------------------------------------------------------- kpColor kpColorToolBar::oldBackgroundColor () const { return m_dualColorButton->oldBackgroundColor (); } //--------------------------------------------------------------------- double kpColorToolBar::oldColorSimilarity () const { return m_colorSimilarityToolBarItem->oldColorSimilarity (); } //--------------------------------------------------------------------- double kpColorToolBar::colorSimilarity () const { return m_colorSimilarityToolBarItem->colorSimilarity (); } //--------------------------------------------------------------------- void kpColorToolBar::setColorSimilarity (double similarity) { m_colorSimilarityToolBarItem->setColorSimilarity (similarity); } //--------------------------------------------------------------------- int kpColorToolBar::processedColorSimilarity () const { return m_colorSimilarityToolBarItem->processedColorSimilarity (); } //--------------------------------------------------------------------- void kpColorToolBar::openColorSimilarityDialog () { m_colorSimilarityToolBarItem->openDialog (); } //--------------------------------------------------------------------- void kpColorToolBar::flashColorSimilarityToolBarItem () { m_colorSimilarityToolBarItem->flash (); } //--------------------------------------------------------------------- // private slot void kpColorToolBar::updateNameOrUrlLabel () { QString name; kpColorCells *colorCells = m_colorPalette->colorCells (); if (!colorCells->url ().isEmpty ()) { name = kpUrlFormatter::PrettyFilename (colorCells->url ()); } else { if (!colorCells->name ().isEmpty ()) { name = colorCells->name (); } else { name = i18n ("KolourPaint Defaults"); } } if (name.isEmpty ()) { name = i18n ("Untitled"); } KLocalizedString labelStr; if (!m_colorPalette->colorCells ()->isModified ()) { labelStr = ki18nc ("Colors: name_or_url_of_color_palette", "Colors: %1") .subs (name); } else { labelStr = ki18nc ("Colors: name_or_url_of_color_palette [modified]", "Colors: %1 [modified]") .subs (name); } // Kill 2 birds with 1 stone: // // 1. Hide the windowTitle() when it's docked. // 2. Add a label containing the name of the open color palette. // // TODO: This currently hides the windowTitle() even when it's not docked, // because we've abused it to show the name of open color palette // instead. setWindowTitle (labelStr.toString ()); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpColorToolBar::dragEnterEvent (QDragEnterEvent *e) { // Grab the color drag for this widget, preventing it from being // handled by our parent, the main window. e->setAccepted (KColorMimeData::canDecode (e->mimeData ())); +#if DEBUG_KP_COLOR_TOOL_BAR qCDebug(kpLogWidgets) << "isAccepted=" << e->isAccepted (); +#endif } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpColorToolBar::dragMoveEvent (QDragMoveEvent *e) { // Stop the grabbed drag from being dropped. e->setAccepted (!KColorMimeData::canDecode (e->mimeData ())); +#if DEBUG_KP_COLOR_TOOL_BAR qCDebug(kpLogWidgets) << "isAccepted=" << e->isAccepted (); +#endif } + +//--------------------------------------------------------------------- + diff --git a/widgets/toolbars/kpToolToolBar.cpp b/widgets/toolbars/kpToolToolBar.cpp index b0c2a572..f0cf1fd7 100644 --- a/widgets/toolbars/kpToolToolBar.cpp +++ b/widgets/toolbars/kpToolToolBar.cpp @@ -1,458 +1,471 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller 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_TOOL_TOOL_BAR 0 #include "widgets/toolbars/kpToolToolBar.h" #include #include #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" #include "tools/kpTool.h" #include "tools/kpToolAction.h" #include "widgets/toolbars/options/kpToolWidgetBrush.h" #include "widgets/toolbars/options/kpToolWidgetEraserSize.h" #include "widgets/toolbars/options/kpToolWidgetFillStyle.h" #include "widgets/toolbars/options/kpToolWidgetLineWidth.h" #include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" #include "widgets/toolbars/options/kpToolWidgetSpraycanSize.h" //--------------------------------------------------------------------- class kpToolButton : public QToolButton { public: kpToolButton (kpTool *tool, QWidget *parent) : QToolButton (parent), m_tool (tool) { } kpTool *tool() const { return m_tool; } protected: void mouseDoubleClickEvent(QMouseEvent *e) override { if (e->button () == Qt::LeftButton && m_tool) { m_tool->globalDraw (); } } kpTool *m_tool; }; //--------------------------------------------------------------------- kpToolToolBar::kpToolToolBar(const QString &name, int colsOrRows, QMainWindow *parent) : KToolBar(name, parent, Qt::LeftToolBarArea), m_vertCols (colsOrRows), m_buttonGroup (nullptr), m_baseWidget (nullptr), m_baseLayout (nullptr), m_toolLayout (nullptr), m_previousTool (nullptr), m_currentTool (nullptr) { m_baseWidget = new QWidget(this); m_toolWidgets.append (m_toolWidgetBrush = new kpToolWidgetBrush (m_baseWidget, "Tool Widget Brush")); m_toolWidgets.append (m_toolWidgetEraserSize = new kpToolWidgetEraserSize (m_baseWidget, "Tool Widget Eraser Size")); m_toolWidgets.append (m_toolWidgetFillStyle = new kpToolWidgetFillStyle (m_baseWidget, "Tool Widget Fill Style")); m_toolWidgets.append (m_toolWidgetLineWidth = new kpToolWidgetLineWidth (m_baseWidget, "Tool Widget Line Width")); m_toolWidgets.append (m_toolWidgetOpaqueOrTransparent = new kpToolWidgetOpaqueOrTransparent (m_baseWidget, "Tool Widget Opaque/Transparent")); m_toolWidgets.append (m_toolWidgetSpraycanSize = new kpToolWidgetSpraycanSize (m_baseWidget, "Tool Widget Spraycan Size")); for (auto *w : m_toolWidgets) { connect (w, &kpToolWidgetBase::optionSelected, this, &kpToolToolBar::toolWidgetOptionSelected); } adjustToOrientation(orientation()); connect (this, &kpToolToolBar::orientationChanged, this, &kpToolToolBar::adjustToOrientation); m_buttonGroup = new QButtonGroup (this); connect (m_buttonGroup, static_cast(&QButtonGroup::buttonClicked), this, &kpToolToolBar::slotToolButtonClicked); hideAllToolWidgets (); addWidget(m_baseWidget); connect (this, &kpToolToolBar::iconSizeChanged, this, &kpToolToolBar::slotIconSizeChanged); connect (this, &kpToolToolBar::toolButtonStyleChanged, this, &kpToolToolBar::slotToolButtonStyleChanged); } //--------------------------------------------------------------------- kpToolToolBar::~kpToolToolBar() { while ( !m_toolButtons.isEmpty() ) { delete m_toolButtons.takeFirst(); } } //--------------------------------------------------------------------- // public void kpToolToolBar::registerTool (kpTool *tool) { for (const auto *b : m_toolButtons) { if ( b->tool() == tool ) { // already given return; } } auto *b = new kpToolButton(tool, m_baseWidget); b->setToolButtonStyle(toolButtonStyle()); b->setIconSize(iconSize()); b->setAutoRaise(true); // tell layout to make all with equal width (much better when text-below-icon) b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); b->setDefaultAction(tool->action()); m_buttonGroup->addButton(b); addButton(b, orientation(), m_toolButtons.count()); m_toolButtons.append(b); connect (tool, &kpTool::actionActivated, this, &kpToolToolBar::slotToolActionActivated); adjustSizeConstraint(); } //--------------------------------------------------------------------- // public void kpToolToolBar::unregisterTool(kpTool *tool) { for (int i = 0; i < m_toolButtons.count(); i++) { if ( m_toolButtons[i]->tool() == tool ) { delete m_toolButtons.takeAt(i); disconnect (tool, &kpTool::actionActivated, this, &kpToolToolBar::slotToolActionActivated); return; } } } //--------------------------------------------------------------------- // public kpTool *kpToolToolBar::tool () const { return m_currentTool; } //--------------------------------------------------------------------- // public void kpToolToolBar::selectTool (const kpTool *tool, bool reselectIfSameTool) { +#if DEBUG_KP_TOOL_TOOL_BAR qCDebug(kpLogWidgets) << "kpToolToolBar::selectTool (tool=" << tool - << ") currentTool=" << m_currentTool; + << ") currentTool=" << m_currentTool + << endl; +#endif if (!reselectIfSameTool && tool == m_currentTool) { return; } if (tool) { tool->action()->setChecked(true); slotToolButtonClicked(); } else { QAbstractButton *b = m_buttonGroup->checkedButton(); if (b) { // HACK: qbuttongroup.html says the following about exclusive // button groups: // // "to untoggle a button you must click on another button // in the group" // // But we don't want any button to be selected. // So don't be an exclusive button group temporarily. m_buttonGroup->setExclusive (false); b->setChecked (false); m_buttonGroup->setExclusive (true); slotToolButtonClicked (); } } } //--------------------------------------------------------------------- // public kpTool *kpToolToolBar::previousTool () const { return m_previousTool; } //--------------------------------------------------------------------- // public void kpToolToolBar::selectPreviousTool () { selectTool (m_previousTool); } //--------------------------------------------------------------------- // public void kpToolToolBar::hideAllToolWidgets () { for (auto *w : m_toolWidgets) { w->hide (); } } //--------------------------------------------------------------------- // public kpToolWidgetBase *kpToolToolBar::shownToolWidget (int which) const { int uptoVisibleWidget = 0; for(auto *w : m_toolWidgets) { if ( !w->isHidden() ) { if (which == uptoVisibleWidget) { return w; } uptoVisibleWidget++; } } return nullptr; } //--------------------------------------------------------------------- // private slot void kpToolToolBar::slotToolButtonClicked () { QAbstractButton *b = m_buttonGroup->checkedButton(); +#if DEBUG_KP_TOOL_TOOL_BAR qCDebug(kpLogWidgets) << "kpToolToolBar::slotToolButtonClicked() button=" << b; +#endif kpTool *tool = nullptr; for (const auto *button : m_toolButtons) { if ( button == b ) { tool = button->tool(); break; } } +#if DEBUG_KP_TOOL_TOOL_BAR qCDebug(kpLogWidgets) << "\ttool=" << tool - << " currentTool=" << m_currentTool; + << " currentTool=" << m_currentTool + << endl; +#endif if (tool == m_currentTool) { if (m_currentTool) { m_currentTool->reselect (); } return; } if (m_currentTool) { m_currentTool->endInternal (); } m_previousTool = m_currentTool; m_currentTool = tool; if (m_currentTool) { kpToolAction *action = m_currentTool->action (); if (action) { action->setChecked (true); } m_currentTool->beginInternal (); } emit sigToolSelected (m_currentTool); m_baseLayout->activate(); adjustSizeConstraint(); } //--------------------------------------------------------------------- // private slot void kpToolToolBar::slotToolActionActivated () { const auto *tool = dynamic_cast(sender()); +#if DEBUG_KP_TOOL_TOOL_BAR qCDebug(kpLogWidgets) << "kpToolToolBar::slotToolActionActivated() tool=" - << (tool ? tool->objectName () : "null"); + << (tool ? tool->objectName () : "null") + << endl; +#endif selectTool (tool, true/*reselect if same tool*/); } //--------------------------------------------------------------------- // public void kpToolToolBar::adjustToOrientation(Qt::Orientation o) { +#if DEBUG_KP_TOOL_TOOL_BAR qCDebug(kpLogWidgets) << "kpToolToolBar::adjustToOrientation(" << (o == Qt::Vertical ? "vertical" : "horizontal") - << ") called!"; + << ") called!" << endl; +#endif delete m_baseLayout; if (o == Qt::Vertical) { m_baseLayout = new QBoxLayout (QBoxLayout::TopToBottom, m_baseWidget); } else // if (o == Qt::Horizontal) { m_baseLayout = new QBoxLayout (QBoxLayout::LeftToRight, m_baseWidget); } m_baseLayout->setSizeConstraint(QLayout::SetFixedSize); m_baseLayout->setContentsMargins(0, 0, 0, 0); m_toolLayout = new QGridLayout(); m_toolLayout->setContentsMargins(0, 0, 0, 0); // (ownership is transferred to m_baseLayout) m_baseLayout->addItem (m_toolLayout); auto num = 0; for (auto *b : m_toolButtons) { addButton(b, o, num); num++; } for (auto *w : m_toolWidgets) { m_baseLayout->addWidget(w, 0/*stretch*/, o == Qt::Vertical ? Qt::AlignHCenter : Qt::AlignVCenter); } adjustSizeConstraint(); } //--------------------------------------------------------------------- // this makes the size handled correctly during dragging/undocking the toolbar void kpToolToolBar::adjustSizeConstraint() { // remove constraints setFixedSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); if ( orientation() == Qt::Vertical ) { setFixedWidth(m_baseLayout->sizeHint().width() + layout()->contentsMargins().left() + layout()->contentsMargins().right()); } else { setFixedHeight(m_baseLayout->sizeHint().height() + layout()->contentsMargins().top() + layout()->contentsMargins().bottom()); } } //--------------------------------------------------------------------- // private void kpToolToolBar::addButton(QAbstractButton *button, Qt::Orientation o, int num) { if (o == Qt::Vertical) { m_toolLayout->addWidget (button, num / m_vertCols, num % m_vertCols); } else { // maps Left (o = vertical) to Bottom (o = horizontal) int row = (m_vertCols - 1) - (num % m_vertCols); m_toolLayout->addWidget (button, row, num / m_vertCols); } } //--------------------------------------------------------------------- void kpToolToolBar::slotIconSizeChanged(const QSize &size) { for (auto *b : m_toolButtons) { b->setIconSize(size); } m_baseLayout->activate(); adjustSizeConstraint(); } //--------------------------------------------------------------------- void kpToolToolBar::slotToolButtonStyleChanged(Qt::ToolButtonStyle style) { for (auto *b : m_toolButtons) { b->setToolButtonStyle(style); } m_baseLayout->activate(); adjustSizeConstraint(); } //---------------------------------------------------------------------