diff --git a/libs/ui/canvas/kis_change_guides_command.cpp b/libs/ui/canvas/kis_change_guides_command.cpp index 06706f5ad2..5644a08efb 100644 --- a/libs/ui/canvas/kis_change_guides_command.cpp +++ b/libs/ui/canvas/kis_change_guides_command.cpp @@ -1,77 +1,135 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_change_guides_command.h" #include "kis_guides_config.h" #include "KisDocument.h" #include +#include +#include struct KisChangeGuidesCommand::Private { Private(KisDocument *_doc) : doc(_doc) {} + bool sameOrOnlyMovedOneGuideBetween(const KisGuidesConfig &first, const KisGuidesConfig &second); + enum Status { + NO_DIFF = 0, + ONE_DIFF = 1, /// only one difference including adding or removing + OTHER_DIFF = 4 + }; + Status diff(const QList &first, const QList &second); + KisDocument *doc; KisGuidesConfig oldGuides; KisGuidesConfig newGuides; }; +bool KisChangeGuidesCommand::Private::sameOrOnlyMovedOneGuideBetween(const KisGuidesConfig &first, const KisGuidesConfig &second) +{ + return diff(first.horizontalGuideLines(), second.horizontalGuideLines()) + + diff(first.verticalGuideLines(), second.verticalGuideLines()) <= 1; +} + +KisChangeGuidesCommand::Private::Status KisChangeGuidesCommand::Private::diff(const QList &first, const QList &second) +{ + if (first.size() == second.size()) { + int diffCount = 0; + for (int i = 0; i < first.size(); ++i) { + if (first[i] != second[i]) { + ++diffCount; + if (diffCount > 1) { + return OTHER_DIFF; + } + } + } + return diffCount == 0 ? NO_DIFF : ONE_DIFF; + } else if (first.size() - second.size() == -1) { // added a guide + QList beforeRemoval = second; + beforeRemoval.takeLast(); + return first == beforeRemoval ? ONE_DIFF : OTHER_DIFF; + } else if (first.size() - second.size() == 1) { // removed a guide + bool skippedItem = false; + for (QListIterator i(first), j(second); i.hasNext() && j.hasNext(); ) { + qreal curFirst = i.next(); + qreal curSecond = j.next(); + if (!skippedItem && curFirst != curSecond) { + curFirst = i.next(); // try to go to the next item and see if it matches + } + if (curFirst != curSecond) { + return OTHER_DIFF; + } + } + // here we conclude only one guide is removed + return ONE_DIFF; + } else { + return OTHER_DIFF; + } +} KisChangeGuidesCommand::KisChangeGuidesCommand(KisDocument *doc, const KisGuidesConfig &newGuides) : KUndo2Command(kundo2_i18n("Edit Guides")), m_d(new Private(doc)) { m_d->oldGuides = doc->guidesConfig(); m_d->newGuides = newGuides; } KisChangeGuidesCommand::~KisChangeGuidesCommand() { } void KisChangeGuidesCommand::undo() { m_d->doc->setGuidesConfig(m_d->oldGuides); } void KisChangeGuidesCommand::redo() { m_d->doc->setGuidesConfig(m_d->newGuides); } int KisChangeGuidesCommand::id() const { return 1863; } bool KisChangeGuidesCommand::mergeWith(const KUndo2Command *command) { bool result = false; const KisChangeGuidesCommand *rhs = dynamic_cast(command); if (rhs) { - m_d->newGuides = rhs->m_d->newGuides; - result = true; + // we want to only merge consecutive movements, or creation then movement, or movement then deletion + // there should not be any changes not on the stack (see kis_guides_manager.cpp) + // nor any addition/removal of guides + // nor the movement of other guides + if (m_d->newGuides == rhs->m_d->oldGuides && + m_d->sameOrOnlyMovedOneGuideBetween(m_d->oldGuides, rhs->m_d->newGuides)) { + m_d->newGuides = rhs->m_d->newGuides; + result = true; + } } return result; } diff --git a/libs/ui/canvas/kis_guides_config.cpp b/libs/ui/canvas/kis_guides_config.cpp index 16ea47d9fc..53bd58a9a0 100644 --- a/libs/ui/canvas/kis_guides_config.cpp +++ b/libs/ui/canvas/kis_guides_config.cpp @@ -1,296 +1,302 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2008 Jan Hambrecht Copyright (c) 2015 Dmitry Kazakov 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. */ #include "kis_guides_config.h" #include #include #include #include "kis_config.h" #include "kis_dom_utils.h" class Q_DECL_HIDDEN KisGuidesConfig::Private { public: Private() : showGuides(false) , snapToGuides(false) , lockGuides(false) , rulersMultiple2(false) , unitType(KoUnit::Pixel) {} bool operator==(const Private &rhs) { return horzGuideLines == rhs.horzGuideLines && vertGuideLines == rhs.vertGuideLines && showGuides == rhs.showGuides && snapToGuides == rhs.snapToGuides && lockGuides == rhs.lockGuides && guidesColor == rhs.guidesColor && guidesLineType == rhs.guidesLineType && rulersMultiple2 == rhs.rulersMultiple2 && unitType == rhs.unitType; } QList horzGuideLines; QList vertGuideLines; bool showGuides; bool snapToGuides; bool lockGuides; bool rulersMultiple2; KoUnit::Type unitType; QColor guidesColor; LineTypeInternal guidesLineType; Qt::PenStyle toPenStyle(LineTypeInternal type); }; KisGuidesConfig::KisGuidesConfig() : d(new Private()) { loadStaticData(); } KisGuidesConfig::~KisGuidesConfig() { } KisGuidesConfig::KisGuidesConfig(const KisGuidesConfig &rhs) : d(new Private(*rhs.d)) { } KisGuidesConfig& KisGuidesConfig::operator=(const KisGuidesConfig &rhs) { if (&rhs != this) { *d = *rhs.d; } return *this; } bool KisGuidesConfig::operator==(const KisGuidesConfig &rhs) const { return *d == *rhs.d; } +bool KisGuidesConfig::hasSamePositionAs(const KisGuidesConfig &rhs) const +{ + return horizontalGuideLines() == rhs.horizontalGuideLines() && + verticalGuideLines() == rhs.verticalGuideLines(); +} + void KisGuidesConfig::setHorizontalGuideLines(const QList &lines) { d->horzGuideLines = lines; } void KisGuidesConfig::setVerticalGuideLines(const QList &lines) { d->vertGuideLines = lines; } void KisGuidesConfig::addGuideLine(Qt::Orientation o, qreal pos) { if (o == Qt::Horizontal) { d->horzGuideLines.append(pos); } else { d->vertGuideLines.append(pos); } } bool KisGuidesConfig::showGuideLines() const { return d->showGuides; } void KisGuidesConfig::setShowGuideLines(bool show) { d->showGuides = show; } bool KisGuidesConfig::showGuides() const { return d->showGuides; } void KisGuidesConfig::setShowGuides(bool value) { d->showGuides = value; } bool KisGuidesConfig::lockGuides() const { return d->lockGuides; } void KisGuidesConfig::setLockGuides(bool value) { d->lockGuides = value; } bool KisGuidesConfig::snapToGuides() const { return d->snapToGuides; } void KisGuidesConfig::setSnapToGuides(bool value) { d->snapToGuides = value; } bool KisGuidesConfig::rulersMultiple2() const { return d->rulersMultiple2; } void KisGuidesConfig::setRulersMultiple2(bool value) { d->rulersMultiple2 = value; } KoUnit::Type KisGuidesConfig::unitType() const { return d->unitType; } void KisGuidesConfig::setUnitType(const KoUnit::Type type) { d->unitType = type; } KisGuidesConfig::LineTypeInternal KisGuidesConfig::guidesLineType() const { return d->guidesLineType; } void KisGuidesConfig::setGuidesLineType(LineTypeInternal value) { d->guidesLineType = value; } QColor KisGuidesConfig::guidesColor() const { return d->guidesColor; } void KisGuidesConfig::setGuidesColor(const QColor &value) { d->guidesColor = value; } Qt::PenStyle KisGuidesConfig::Private::toPenStyle(LineTypeInternal type) { return type == LINE_SOLID ? Qt::SolidLine : type == LINE_DASHED ? Qt::DashLine : type == LINE_DOTTED ? Qt::DotLine : Qt::DashDotDotLine; } QPen KisGuidesConfig::guidesPen() const { return QPen(d->guidesColor, 0, d->toPenStyle(d->guidesLineType)); } const QList& KisGuidesConfig::horizontalGuideLines() const { return d->horzGuideLines; } const QList& KisGuidesConfig::verticalGuideLines() const { return d->vertGuideLines; } bool KisGuidesConfig::hasGuides() const { return !d->horzGuideLines.isEmpty() || !d->vertGuideLines.isEmpty(); } void KisGuidesConfig::loadStaticData() { KisConfig cfg(true); d->guidesLineType = LineTypeInternal(cfg.guidesLineStyle()); d->guidesColor = cfg.guidesColor(); } void KisGuidesConfig::saveStaticData() const { KisConfig cfg(false); cfg.setGuidesLineStyle(d->guidesLineType); cfg.setGuidesColor(d->guidesColor); } QDomElement KisGuidesConfig::saveToXml(QDomDocument& doc, const QString &tag) const { QDomElement guidesElement = doc.createElement(tag); KisDomUtils::saveValue(&guidesElement, "showGuides", d->showGuides); KisDomUtils::saveValue(&guidesElement, "snapToGuides", d->snapToGuides); KisDomUtils::saveValue(&guidesElement, "lockGuides", d->lockGuides); KisDomUtils::saveValue(&guidesElement, "horizontalGuides", d->horzGuideLines.toVector()); KisDomUtils::saveValue(&guidesElement, "verticalGuides", d->vertGuideLines.toVector()); KisDomUtils::saveValue(&guidesElement, "rulersMultiple2", d->rulersMultiple2); KoUnit tmp(d->unitType); KisDomUtils::saveValue(&guidesElement, "unit", tmp.symbol()); return guidesElement; } bool KisGuidesConfig::loadFromXml(const QDomElement &parent) { bool result = true; result &= KisDomUtils::loadValue(parent, "showGuides", &d->showGuides); result &= KisDomUtils::loadValue(parent, "snapToGuides", &d->snapToGuides); result &= KisDomUtils::loadValue(parent, "lockGuides", &d->lockGuides); QVector hGuides; QVector vGuides; result &= KisDomUtils::loadValue(parent, "horizontalGuides", &hGuides); result &= KisDomUtils::loadValue(parent, "verticalGuides", &vGuides); d->horzGuideLines = QList::fromVector(hGuides); d->vertGuideLines = QList::fromVector(vGuides); result &= KisDomUtils::loadValue(parent, "rulersMultiple2", &d->rulersMultiple2); QString unit; result &= KisDomUtils::loadValue(parent, "unit", &unit); bool ok = false; KoUnit tmp = KoUnit::fromSymbol(unit, &ok); if (ok) { d->unitType = tmp.type(); } result &= ok; return result; } bool KisGuidesConfig::isDefault() const { KisGuidesConfig defaultObject; defaultObject.loadStaticData(); return *this == defaultObject; } diff --git a/libs/ui/canvas/kis_guides_config.h b/libs/ui/canvas/kis_guides_config.h index ed97a51333..ca4748f80b 100644 --- a/libs/ui/canvas/kis_guides_config.h +++ b/libs/ui/canvas/kis_guides_config.h @@ -1,130 +1,131 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2008 Jan Hambrecht Copyright (c) 2015 Dmitry Kazakov 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. */ #ifndef KOGUIDESDATA_H #define KOGUIDESDATA_H #include "kritaui_export.h" #include #include #include #include class QDomElement; class QDomDocument; class QColor; class QPen; class KRITAUI_EXPORT KisGuidesConfig : boost::equality_comparable { public: enum LineTypeInternal { LINE_SOLID = 0, LINE_DASHED, LINE_DOTTED }; public: KisGuidesConfig(); ~KisGuidesConfig(); KisGuidesConfig(const KisGuidesConfig &rhs); KisGuidesConfig& operator=(const KisGuidesConfig &rhs); bool operator==(const KisGuidesConfig &rhs) const; + bool hasSamePositionAs(const KisGuidesConfig &rhs) const; /** * @brief Set the positions of the horizontal guide lines * * @param lines a list of positions of the horizontal guide lines */ void setHorizontalGuideLines(const QList &lines); /** * @brief Set the positions of the vertical guide lines * * @param lines a list of positions of the vertical guide lines */ void setVerticalGuideLines(const QList &lines); /** * @brief Add a guide line to the canvas. * * @param orientation the orientation of the guide line * @param position the position in document coordinates of the guide line */ void addGuideLine(Qt::Orientation orientation, qreal position); /** * @brief Display or not guide lines */ bool showGuideLines() const; /** * @param show display or not guide line */ void setShowGuideLines(bool show); bool showGuides() const; void setShowGuides(bool value); bool lockGuides() const; void setLockGuides(bool value); bool snapToGuides() const; void setSnapToGuides(bool value); bool rulersMultiple2() const; void setRulersMultiple2(bool value); KoUnit::Type unitType() const; void setUnitType(KoUnit::Type type); LineTypeInternal guidesLineType() const; void setGuidesLineType(LineTypeInternal value); QColor guidesColor() const; void setGuidesColor(const QColor &value); QPen guidesPen() const; /// Returns the list of horizontal guide lines. const QList& horizontalGuideLines() const; /// Returns the list of vertical guide lines. const QList& verticalGuideLines() const; bool hasGuides() const; void loadStaticData(); void saveStaticData() const; QDomElement saveToXml(QDomDocument& doc, const QString &tag) const; bool loadFromXml(const QDomElement &parent); bool isDefault() const; private: class Private; const QScopedPointer d; }; #endif diff --git a/libs/ui/canvas/kis_guides_manager.cpp b/libs/ui/canvas/kis_guides_manager.cpp index b6e1246435..afb8b1eb09 100644 --- a/libs/ui/canvas/kis_guides_manager.cpp +++ b/libs/ui/canvas/kis_guides_manager.cpp @@ -1,814 +1,828 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_guides_manager.h" #include #include #include "kis_guides_decoration.h" #include #include "kis_guides_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_signals_blocker.h" #include "input/kis_input_manager.h" #include "kis_coordinates_converter.h" #include "kis_zoom_manager.h" #include "kis_signal_auto_connection.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_algebra_2d.h" #include #include "kis_snap_line_strategy.h" #include "kis_change_guides_command.h" #include "kis_snap_config.h" #include "kis_canvas2.h" #include "kis_signal_compressor.h" struct KisGuidesManager::Private { Private(KisGuidesManager *_q) : q(_q), decoration(0), invalidGuide(Qt::Horizontal, -1), currentGuide(invalidGuide), cursorSwitched(false), dragStartGuidePos(0), updateDocumentCompressor(40, KisSignalCompressor::FIRST_ACTIVE), shouldSetModified(false) {} KisGuidesManager *q; KisGuidesDecoration *decoration; KisGuidesConfig guidesConfig; KisSnapConfig snapConfig; QPointer view; typedef QPair GuideHandle; GuideHandle findGuide(const QPointF &docPos); bool isGuideValid(const GuideHandle &h); qreal guideValue(const GuideHandle &h); void setGuideValue(const GuideHandle &h, qreal value); void deleteGuide(const GuideHandle &h); const GuideHandle invalidGuide; bool updateCursor(const QPointF &docPos, bool forceDisableCursor = false); void initDragStart(const GuideHandle &guide, const QPointF &dragStart, qreal guideValue, bool snapToStart); bool mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers); bool mouseReleaseHandler(const QPointF &docPos); void updateSnappingStatus(const KisGuidesConfig &value); QPointF alignToPixels(const QPointF docPoint); QPointF getDocPointFromEvent(QEvent *event); Qt::MouseButton getButtonFromEvent(QEvent *event); QAction* createShortenedAction(const QString &text, const QString &parentId, QObject *parent); void syncAction(const QString &actionName, bool value); + bool needsUndoCommand(); GuideHandle currentGuide; bool cursorSwitched; QCursor oldCursor; QPointF dragStartDoc; QPointF dragPointerOffset; qreal dragStartGuidePos; KisSignalAutoConnectionsStore viewConnections; KisSignalCompressor updateDocumentCompressor; bool shouldSetModified; }; KisGuidesManager::KisGuidesManager(QObject *parent) : QObject(parent), m_d(new Private(this)) { connect(&m_d->updateDocumentCompressor, SIGNAL(timeout()), SLOT(slotUploadConfigToDocument())); } KisGuidesManager::~KisGuidesManager() { } void KisGuidesManager::setGuidesConfig(const KisGuidesConfig &config) { if (config == m_d->guidesConfig) return; setGuidesConfigImpl(config, true); } void KisGuidesManager::slotDocumentRequestedConfig(const KisGuidesConfig &config) { if (config == m_d->guidesConfig) return; setGuidesConfigImpl(config, false); } void KisGuidesManager::slotUploadConfigToDocument() { const KisGuidesConfig &value = m_d->guidesConfig; KisDocument *doc = m_d->view ? m_d->view->document() : 0; if (doc) { KisSignalsBlocker b(doc); if (m_d->shouldSetModified) { - KUndo2Command *cmd = new KisChangeGuidesCommand(doc, value); - doc->addCommand(cmd); + if (m_d->needsUndoCommand()) { + KUndo2Command *cmd = new KisChangeGuidesCommand(doc, value); + doc->addCommand(cmd); + } } else { doc->setGuidesConfig(value); } value.saveStaticData(); } m_d->shouldSetModified = false; } void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified) { m_d->guidesConfig = value; if (m_d->decoration && value != m_d->decoration->guidesConfig()) { m_d->decoration->setVisible(value.showGuides()); m_d->decoration->setGuidesConfig(value); } m_d->shouldSetModified |= emitModified; m_d->updateDocumentCompressor.start(); const bool shouldFilterEvent = value.showGuides() && !value.lockGuides() && value.hasGuides(); attachEventFilterImpl(shouldFilterEvent); syncActionsStatus(); if (!m_d->isGuideValid(m_d->currentGuide)) { m_d->updateSnappingStatus(value); } if (m_d->view) { m_d->view->document()->setUnit(KoUnit(m_d->guidesConfig.unitType())); m_d->view->viewManager()->actionManager()->actionByName("ruler_pixel_multiple2")->setChecked(value.rulersMultiple2()); } emit sigRequestUpdateGuidesConfig(m_d->guidesConfig); } void KisGuidesManager::attachEventFilterImpl(bool value) { if (!m_d->view) return; KisInputManager *inputManager = m_d->view->globalInputManager(); if (inputManager) { if (value) { inputManager->attachPriorityEventFilter(this, 100); } else { inputManager->detachPriorityEventFilter(this); } } } void KisGuidesManager::Private::syncAction(const QString &actionName, bool value) { KisActionManager *actionManager = view->viewManager()->actionManager(); KisAction *action = actionManager->actionByName(actionName); KIS_ASSERT_RECOVER_RETURN(action); KisSignalsBlocker b(action); action->setChecked(value); } +bool KisGuidesManager::Private::needsUndoCommand() +{ + const KisGuidesConfig &value = guidesConfig; + + KisDocument *doc = view ? view->document() : 0; + if (!doc) { + return false; + } + return !(doc->guidesConfig().hasSamePositionAs(value)); +} + void KisGuidesManager::syncActionsStatus() { if (!m_d->view) return; m_d->syncAction("view_show_guides", m_d->guidesConfig.showGuides()); m_d->syncAction("view_lock_guides", m_d->guidesConfig.lockGuides()); m_d->syncAction("view_snap_to_guides", m_d->guidesConfig.snapToGuides()); m_d->syncAction("view_snap_orthogonal", m_d->snapConfig.orthogonal()); m_d->syncAction("view_snap_node", m_d->snapConfig.node()); m_d->syncAction("view_snap_extension", m_d->snapConfig.extension()); m_d->syncAction("view_snap_intersection", m_d->snapConfig.intersection()); m_d->syncAction("view_snap_bounding_box", m_d->snapConfig.boundingBox()); m_d->syncAction("view_snap_image_bounds", m_d->snapConfig.imageBounds()); m_d->syncAction("view_snap_image_center", m_d->snapConfig.imageCenter()); m_d->syncAction("view_snap_to_pixel",m_d->snapConfig.toPixel()); } void KisGuidesManager::Private::updateSnappingStatus(const KisGuidesConfig &value) { if (!view) return; KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); KisSnapLineStrategy *guidesSnap = 0; if (value.snapToGuides()) { guidesSnap = new KisSnapLineStrategy(KoSnapGuide::GuideLineSnapping); guidesSnap->setHorizontalLines(value.horizontalGuideLines()); guidesSnap->setVerticalLines(value.verticalGuideLines()); } snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); snapGuide->enableSnapStrategy(KoSnapGuide::OrthogonalSnapping, snapConfig.orthogonal()); snapGuide->enableSnapStrategy(KoSnapGuide::NodeSnapping, snapConfig.node()); snapGuide->enableSnapStrategy(KoSnapGuide::ExtensionSnapping, snapConfig.extension()); snapGuide->enableSnapStrategy(KoSnapGuide::IntersectionSnapping, snapConfig.intersection()); snapGuide->enableSnapStrategy(KoSnapGuide::BoundingBoxSnapping, snapConfig.boundingBox()); snapGuide->enableSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, snapConfig.imageBounds()); snapGuide->enableSnapStrategy(KoSnapGuide::DocumentCenterSnapping, snapConfig.imageCenter()); snapGuide->enableSnapStrategy(KoSnapGuide::PixelSnapping, snapConfig.toPixel()); snapConfig.saveStaticData(); } bool KisGuidesManager::showGuides() const { return m_d->guidesConfig.showGuides(); } void KisGuidesManager::setShowGuides(bool value) { m_d->guidesConfig.setShowGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::lockGuides() const { return m_d->guidesConfig.lockGuides(); } void KisGuidesManager::setLockGuides(bool value) { m_d->guidesConfig.setLockGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::snapToGuides() const { return m_d->guidesConfig.snapToGuides(); } void KisGuidesManager::setSnapToGuides(bool value) { m_d->guidesConfig.setSnapToGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::rulersMultiple2() const { return m_d->guidesConfig.rulersMultiple2(); } void KisGuidesManager::setRulersMultiple2(bool value) { m_d->guidesConfig.setRulersMultiple2(value); setGuidesConfigImpl(m_d->guidesConfig); } KoUnit::Type KisGuidesManager::unitType() const { return m_d->guidesConfig.unitType(); } void KisGuidesManager::setUnitType(const KoUnit::Type type) { m_d->guidesConfig.setUnitType(type); setGuidesConfigImpl(m_d->guidesConfig, false); } void KisGuidesManager::setup(KisActionManager *actionManager) { KisAction *action = 0; action = actionManager->createAction("view_show_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowGuides(bool))); action = actionManager->createAction("view_lock_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setLockGuides(bool))); action = actionManager->createAction("view_snap_to_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToGuides(bool))); action = actionManager->createAction("show_snap_options_popup"); connect(action, SIGNAL(triggered()), this, SLOT(slotShowSnapOptions())); action = actionManager->createAction("view_snap_orthogonal"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapOrthogonal(bool))); action = actionManager->createAction("view_snap_node"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapNode(bool))); action = actionManager->createAction("view_snap_extension"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapExtension(bool))); action = actionManager->createAction("view_snap_intersection"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapIntersection(bool))); action = actionManager->createAction("view_snap_bounding_box"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapBoundingBox(bool))); action = actionManager->createAction("view_snap_image_bounds"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageBounds(bool))); action = actionManager->createAction("view_snap_image_center"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageCenter(bool))); action = actionManager->createAction("view_snap_to_pixel"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToPixel(bool))); m_d->updateSnappingStatus(m_d->guidesConfig); syncActionsStatus(); } void KisGuidesManager::setView(QPointer view) { if (m_d->view) { KoSnapGuide *snapGuide = m_d->view->canvasBase()->snapGuide(); snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, 0); snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, false); if (m_d->updateDocumentCompressor.isActive()) { m_d->updateDocumentCompressor.stop(); slotUploadConfigToDocument(); } m_d->decoration = 0; m_d->viewConnections.clear(); attachEventFilterImpl(false); } m_d->view = view; if (m_d->view) { KisGuidesDecoration* decoration = qobject_cast(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID).data()); if (!decoration) { decoration = new KisGuidesDecoration(m_d->view); m_d->view->canvasBase()->addDecoration(decoration); } m_d->decoration = decoration; m_d->guidesConfig = m_d->view->document()->guidesConfig(); setGuidesConfigImpl(m_d->guidesConfig, false); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation,QPoint)), this, SLOT(slotGuideCreationInProgress(Qt::Orientation,QPoint))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation,QPoint)), this, SLOT(slotGuideCreationFinished(Qt::Orientation,QPoint))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation,QPoint)), this, SLOT(slotGuideCreationInProgress(Qt::Orientation,QPoint))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation,QPoint)), this, SLOT(slotGuideCreationFinished(Qt::Orientation,QPoint))); m_d->viewConnections.addUniqueConnection( m_d->view->document(), SIGNAL(sigGuidesConfigChanged(KisGuidesConfig)), this, SLOT(slotDocumentRequestedConfig(KisGuidesConfig))); } } KisGuidesManager::Private::GuideHandle KisGuidesManager::Private::findGuide(const QPointF &docPos) { const int snapRadius = 16; GuideHandle nearestGuide = invalidGuide; qreal nearestRadius = std::numeric_limits::max(); for (int i = 0; i < guidesConfig.horizontalGuideLines().size(); i++) { const qreal guide = guidesConfig.horizontalGuideLines()[i]; const qreal radius = qAbs(docPos.y() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Horizontal, i); nearestRadius = radius; } } for (int i = 0; i < guidesConfig.verticalGuideLines().size(); i++) { const qreal guide = guidesConfig.verticalGuideLines()[i]; const qreal radius = qAbs(docPos.x() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Vertical, i); nearestRadius = radius; } } return nearestGuide; } bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h) { return h.second >= 0; } qreal KisGuidesManager::Private::guideValue(const GuideHandle &h) { return h.first == Qt::Horizontal ? guidesConfig.horizontalGuideLines()[h.second] : guidesConfig.verticalGuideLines()[h.second]; } void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value) { if (h.first == Qt::Horizontal) { QList guides = guidesConfig.horizontalGuideLines(); guides[h.second] = value; guidesConfig.setHorizontalGuideLines(guides); } else { QList guides = guidesConfig.verticalGuideLines(); guides[h.second] = value; guidesConfig.setVerticalGuideLines(guides); } } void KisGuidesManager::Private::deleteGuide(const GuideHandle &h) { if (h.first == Qt::Horizontal) { QList guides = guidesConfig.horizontalGuideLines(); guides.removeAt(h.second); guidesConfig.setHorizontalGuideLines(guides); } else { QList guides = guidesConfig.verticalGuideLines(); guides.removeAt(h.second); guidesConfig.setVerticalGuideLines(guides); } } bool KisGuidesManager::Private::updateCursor(const QPointF &docPos, bool forceDisableCursor) { KisCanvas2 *canvas = view->canvasBase(); const GuideHandle guide = findGuide(docPos); const bool guideValid = isGuideValid(guide) && !forceDisableCursor; if (guideValid && !cursorSwitched) { oldCursor = canvas->canvasWidget()->cursor(); } if (guideValid) { cursorSwitched = true; QCursor newCursor = guide.first == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor; canvas->canvasWidget()->setCursor(newCursor); } if (!guideValid && cursorSwitched) { canvas->canvasWidget()->setCursor(oldCursor); cursorSwitched = false; } return guideValid; } void KisGuidesManager::Private::initDragStart(const GuideHandle &guide, const QPointF &dragStart, qreal guideValue, bool snapToStart) { currentGuide = guide; dragStartDoc = dragStart; dragStartGuidePos = guideValue; dragPointerOffset = guide.first == Qt::Horizontal ? QPointF(0, dragStartGuidePos - dragStartDoc.y()) : QPointF(dragStartGuidePos - dragStartDoc.x(), 0); KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); snapGuide->reset(); if (snapToStart) { KisSnapLineStrategy *strategy = new KisSnapLineStrategy(); strategy->addLine(guide.first, guideValue); snapGuide->addCustomSnapStrategy(strategy); } } QPointF KisGuidesManager::Private::alignToPixels(const QPointF docPoint) { KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); QPoint imagePoint = converter->documentToImage(docPoint).toPoint(); return converter->imageToDocument(imagePoint); } bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers) { if (isGuideValid(currentGuide)) { KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); const QPointF snappedPos = snapGuide->snap(docPos, dragPointerOffset, modifiers); const QPointF offset = snappedPos - dragStartDoc; const qreal newValue = dragStartGuidePos + (currentGuide.first == Qt::Horizontal ? offset.y() : offset.x()); setGuideValue(currentGuide, newValue); q->setGuidesConfigImpl(guidesConfig); } return updateCursor(docPos); } bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos) { bool result = false; KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); if (isGuideValid(currentGuide)) { const QRectF docRect = converter->imageRectInDocumentPixels(); // TODO: enable work rect after we fix painting guides // outside canvas in openGL mode const QRectF workRect = KisAlgebra2D::blowRect(docRect, 0 /*0.2*/); if (!workRect.contains(docPos)) { deleteGuide(currentGuide); q->setGuidesConfigImpl(guidesConfig); /** * When we delete a guide, it might happen that we are * deleting the last guide. Therefore we should eat the * corresponding event so that the event filter would stop * the filter processing. */ result = true; } currentGuide = invalidGuide; dragStartDoc = QPointF(); dragPointerOffset = QPointF(); dragStartGuidePos = 0; KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); snapGuide->reset(); updateSnappingStatus(guidesConfig); } return updateCursor(docPos) | result; } QPointF KisGuidesManager::Private::getDocPointFromEvent(QEvent *event) { QPointF result; KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); if (event->type() == QEvent::Enter) { QEnterEvent *enterEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(enterEvent->pos())); } else if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(mouseEvent->pos())); } else if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *tabletEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(tabletEvent->pos())); } else { // we shouldn't silently return QPointF(0,0), higher level code may // snap to some unexpected guide KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "event type is not supported!"); } return result; } Qt::MouseButton KisGuidesManager::Private::getButtonFromEvent(QEvent *event) { Qt::MouseButton button = Qt::NoButton; if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); button = mouseEvent->button(); } else if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *tabletEvent = static_cast(event); button = tabletEvent->button(); } return button; } bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event) { if (!m_d->view || obj != m_d->view->canvasBase()->canvasWidget()) return false; bool retval = false; switch (event->type()) { case QEvent::Leave: m_d->updateCursor(QPointF(), true); break; case QEvent::Enter: case QEvent::TabletMove: case QEvent::MouseMove: { const QPointF docPos = m_d->getDocPointFromEvent(event); const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); // we should never eat Enter events, input manager may get crazy about it retval = m_d->mouseMoveHandler(docPos, modifiers) && event->type() != QEvent::Enter; break; } case QEvent::TabletPress: case QEvent::MouseButtonPress: { if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; const QPointF docPos = m_d->getDocPointFromEvent(event); const Private::GuideHandle guide = m_d->findGuide(docPos); const bool guideValid = m_d->isGuideValid(guide); if (guideValid) { m_d->initDragStart(guide, docPos, m_d->guideValue(guide), true); } retval = m_d->updateCursor(docPos); break; } case QEvent::TabletRelease: case QEvent::MouseButtonRelease: { if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; const QPointF docPos = m_d->getDocPointFromEvent(event); retval = m_d->mouseReleaseHandler(docPos); break; } default: break; } return !retval ? QObject::eventFilter(obj, event) : true; } void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos) { if (m_d->guidesConfig.lockGuides()) return; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); if (m_d->isGuideValid(m_d->currentGuide)) { const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); m_d->mouseMoveHandler(docPos, modifiers); } else { m_d->guidesConfig.setShowGuides(true); if (orientation == Qt::Horizontal) { QList guides = m_d->guidesConfig.horizontalGuideLines(); guides.append(docPos.y()); m_d->currentGuide.first = orientation; m_d->currentGuide.second = guides.size() - 1; m_d->guidesConfig.setHorizontalGuideLines(guides); m_d->initDragStart(m_d->currentGuide, docPos, docPos.y(), false); } else { QList guides = m_d->guidesConfig.verticalGuideLines(); guides.append(docPos.x()); m_d->currentGuide.first = orientation; m_d->currentGuide.second = guides.size() - 1; m_d->guidesConfig.setVerticalGuideLines(guides); m_d->initDragStart(m_d->currentGuide, docPos, docPos.x(), false); } setGuidesConfigImpl(m_d->guidesConfig); } } void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos) { Q_UNUSED(orientation); if (m_d->guidesConfig.lockGuides()) return; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); m_d->mouseReleaseHandler(docPos); } QAction* KisGuidesManager::Private::createShortenedAction(const QString &text, const QString &parentId, QObject *parent) { KisActionManager *actionManager = view->viewManager()->actionManager(); QAction *action = 0; KisAction *parentAction = 0; action = new QAction(text, parent); action->setCheckable(true); parentAction = actionManager->actionByName(parentId); action->setChecked(parentAction->isChecked()); connect(action, SIGNAL(toggled(bool)), parentAction, SLOT(setChecked(bool))); return action; } void KisGuidesManager::slotShowSnapOptions() { const QPoint pos = QCursor::pos(); QMenu menu; menu.addSection(i18n("Snap to:")); menu.addAction(m_d->createShortenedAction(i18n("Grid"), "view_snap_to_grid", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Guides"), "view_snap_to_guides", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Pixel"), "view_snap_to_pixel", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Orthogonal"), "view_snap_orthogonal", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Node"), "view_snap_node", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Extension"), "view_snap_extension", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Intersection"), "view_snap_intersection", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Bounding Box"), "view_snap_bounding_box", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Image Bounds"), "view_snap_image_bounds", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Image Center"), "view_snap_image_center", &menu)); menu.exec(pos); } void KisGuidesManager::setSnapOrthogonal(bool value) { m_d->snapConfig.setOrthogonal(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapNode(bool value) { m_d->snapConfig.setNode(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapExtension(bool value) { m_d->snapConfig.setExtension(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapIntersection(bool value) { m_d->snapConfig.setIntersection(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapBoundingBox(bool value) { m_d->snapConfig.setBoundingBox(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapImageBounds(bool value) { m_d->snapConfig.setImageBounds(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapImageCenter(bool value) { m_d->snapConfig.setImageCenter(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapToPixel(bool value) { m_d->snapConfig.setToPixel(value); m_d->updateSnappingStatus(m_d->guidesConfig); }