diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index 87258b70fb..8cf05f904f 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,229 +1,228 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceManager.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeBasedDocumentBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp - KoShapeManagerPaintingStrategy.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoUnavailShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoShapePaste.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoPanTool.cpp tools/KoPanToolFactory.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoCreateShapesTool.cpp tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp FlakeDebug.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoRTree.h b/libs/flake/KoRTree.h index 0675f80c5b..4a633475aa 100644 --- a/libs/flake/KoRTree.h +++ b/libs/flake/KoRTree.h @@ -1,1132 +1,1152 @@ /* This file is part of the KDE project Copyright (c) 2006 Thorsten Zachmann 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. Based on code from Wolfgang Baer - WBaer@gmx.de */ #ifndef KORTREE_H #define KORTREE_H #include #include #include #include #include #include #include #include +#include "kis_assert.h" // #define CALLIGRA_RTREE_DEBUG #ifdef CALLIGRA_RTREE_DEBUG #include #endif /** * @brief The KoRTree class is a template class that provides a R-tree. * * This class implements a R-tree as described in * "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antomn Guttman * * It only supports 2 dimensional bounding boxes which are repesented by a QRectF. * For node splitting the Quadratic-Cost Algorithm is used as descibed by Guttman. */ template class KoRTree { public: /** * @brief Constructor * * @param capacity the capacity a node can take * @param minimum the minimum filling of a node max 0.5 * capacity */ KoRTree(int capacity, int minimum); /** * @brief Destructor */ virtual ~KoRTree(); /** * @brief Insert data item into the tree * * This will insert a data item into the tree. If necessary the tree will * adjust itself. * * @param data * @param bb */ virtual void insert(const QRectF& bb, const T& data); /** * @brief Remove a data item from the tree * * This removed a data item from the tree. If necessary the tree will * adjust itself. * * @param data */ void remove(const T& data); /** * @brief Find all data items which intersects rect * The items are sorted by insertion time in ascending order. * * @param rect where the objects have to be in * * @return objects intersecting the rect */ virtual QList intersects(const QRectF& rect) const; /** * @brief Find all data item which contain the point * The items are sorted by insertion time in ascending order. * * @param point which should be contained in the objects * * @return objects which contain the point */ QList contains(const QPointF &point) const; /** * @brief Find all data item which contain the point * The items are sorted by insertion time in ascending order. * * @param point which should be contained in the objects * * @return objects which contain the point */ QList contained(const QRectF &point) const; /** * @brief Find all data rectangles * The order is NOT guaranteed to be the same as that used by values(). * * @return a list containing all the data rectangles used in the tree */ QList keys() const; /** * @brief Find all data items * The order is NOT guaranteed to be the same as that used by keys(). * * @return a list containing all the data used in the tree */ QList values() const; virtual void clear() { delete m_root; m_root = createLeafNode(m_capacity + 1, 0, 0); m_leafMap.clear(); } #ifdef CALLIGRA_RTREE_DEBUG /** * @brief Paint the tree * * @param p painter which should be used for painting */ void paint(QPainter & p) const; /** * @brief Print the tree using qdebug */ void debug() const; #endif protected: class NonLeafNode; class LeafNode; class Node { public: #ifdef CALLIGRA_RTREE_DEBUG static int nodeIdCnt; #endif Node(int capacity, int level, Node * parent); virtual ~Node() {} virtual void remove(int index); // move node between nodes of the same type from node virtual void move(Node * node, int index) = 0; virtual LeafNode * chooseLeaf(const QRectF& bb) = 0; virtual NonLeafNode * chooseNode(const QRectF& bb, int level) = 0; virtual void intersects(const QRectF& rect, QMap & result) const = 0; virtual void contains(const QPointF & point, QMap & result) const = 0; virtual void contained(const QRectF & point, QMap & result) const = 0; virtual void keys(QList & result) const = 0; virtual void values(QMap & result) const = 0; virtual Node * parent() const { return m_parent; } virtual void setParent(Node * parent) { m_parent = parent; } virtual int childCount() const { return m_counter; } virtual const QRectF& boundingBox() const { return m_boundingBox; } virtual void updateBoundingBox(); virtual const QRectF& childBoundingBox(int index) const { return m_childBoundingBox[index]; } virtual void setChildBoundingBox(int index, const QRectF& rect) { m_childBoundingBox[index] = rect; } virtual void clear(); virtual bool isRoot() const { return m_parent == 0; } virtual bool isLeaf() const { return false; } virtual int place() const { return m_place; } virtual void setPlace(int place) { m_place = place; } virtual int level() const { return m_level; } virtual void setLevel(int level) { m_level = level; } #ifdef CALLIGRA_RTREE_DEBUG virtual int nodeId() const { return m_nodeId; } virtual void paint(QPainter & p, int level) const = 0; virtual void debug(QString line) const = 0; protected: #define levelColorSize 5 static QColor levelColor[levelColorSize]; virtual void paintRect(QPainter & p, int level) const; #endif protected: Node * m_parent; QRectF m_boundingBox; QVector m_childBoundingBox; int m_counter; // the position in the parent int m_place; #ifdef CALLIGRA_RTREE_DEBUG int m_nodeId; #endif int m_level; }; class NonLeafNode : virtual public Node { public: NonLeafNode(int capacity, int level, Node * parent); virtual ~NonLeafNode(); virtual void insert(const QRectF& bb, Node * data); virtual void remove(int index); virtual void move(Node * node, int index); virtual LeafNode * chooseLeaf(const QRectF& bb); virtual NonLeafNode * chooseNode(const QRectF& bb, int level); virtual void intersects(const QRectF& rect, QMap & result) const; virtual void contains(const QPointF & point, QMap & result) const; virtual void contained(const QRectF & point, QMap & result) const; virtual void keys(QList & result) const; virtual void values(QMap & result) const; virtual Node * getNode(int index) const; #ifdef CALLIGRA_RTREE_DEBUG virtual void paint(QPainter & p, int level) const; virtual void debug(QString line) const; #endif protected: virtual Node * getLeastEnlargement(const QRectF& bb) const; QVector m_childs; }; class LeafNode : virtual public Node { public: static int dataIdCounter; LeafNode(int capacity, int level, Node * parent); virtual ~LeafNode(); virtual void insert(const QRectF& bb, const T& data, int id); virtual void remove(int index); virtual void remove(const T& data); virtual void move(Node * node, int index); virtual LeafNode * chooseLeaf(const QRectF& bb); virtual NonLeafNode * chooseNode(const QRectF& bb, int level); virtual void intersects(const QRectF& rect, QMap & result) const; virtual void contains(const QPointF & point, QMap & result) const; virtual void contained(const QRectF & point, QMap & result) const; virtual void keys(QList & result) const; virtual void values(QMap & result) const; virtual const T& getData(int index) const; virtual int getDataId(int index) const; virtual bool isLeaf() const { return true; } #ifdef CALLIGRA_RTREE_DEBUG virtual void debug(QString line) const; virtual void paint(QPainter & p, int level) const; #endif protected: QVector m_data; QVector m_dataIds; }; // factory methods virtual LeafNode* createLeafNode(int capacity, int level, Node * parent) { return new LeafNode(capacity, level, parent); } virtual NonLeafNode* createNonLeafNode(int capacity, int level, Node * parent) { return new NonLeafNode(capacity, level, parent); } // methods for insert QPair splitNode(Node * node); QPair pickSeeds(Node * node); QPair pickNext(Node * node, QVector & marker, Node * group1, Node * group2); virtual void adjustTree(Node * node1, Node * node2); void insertHelper(const QRectF& bb, const T& data, int id); // methods for delete void insert(Node * node); virtual void condenseTree(Node * node, QVector & reinsert); int m_capacity; int m_minimum; Node * m_root; QMap m_leafMap; }; template KoRTree::KoRTree(int capacity, int minimum) : m_capacity(capacity) , m_minimum(minimum) , m_root(createLeafNode(m_capacity + 1, 0, 0)) { if (minimum > capacity / 2) qFatal("KoRTree::KoRTree minimum can be maximal capacity/2"); //qDebug() << "root node " << m_root->nodeId(); } template KoRTree::~KoRTree() { delete m_root; } template void KoRTree::insert(const QRectF& bb, const T& data) { + // check if the shape is not already registered + KIS_SAFE_ASSERT_RECOVER_NOOP(!m_leafMap[data]); + insertHelper(bb, data, LeafNode::dataIdCounter++); } template void KoRTree::insertHelper(const QRectF& bb, const T& data, int id) { QRectF nbb(bb.normalized()); // This has to be done as it is not possible to use QRectF::united() with a isNull() if (nbb.isNull()) { nbb.setWidth(0.0001); nbb.setHeight(0.0001); qWarning() << "KoRTree::insert boundingBox isNull setting size to" << nbb.size(); } else { // This has to be done as QRectF::intersects() return false if the rect does not have any area overlapping. // If there is no width or height there is no area and therefore no overlapping. if ( nbb.width() == 0 ) { nbb.setWidth(0.0001); } if ( nbb.height() == 0 ) { nbb.setHeight(0.0001); } } LeafNode * leaf = m_root->chooseLeaf(nbb); //qDebug() << " leaf" << leaf->nodeId() << nbb; if (leaf->childCount() < m_capacity) { leaf->insert(nbb, data, id); m_leafMap[data] = leaf; adjustTree(leaf, 0); } else { leaf->insert(nbb, data, id); m_leafMap[data] = leaf; QPair newNodes = splitNode(leaf); LeafNode * l = dynamic_cast(newNodes.first); if (l) for (int i = 0; i < l->childCount(); ++i) m_leafMap[l->getData(i)] = l; l = dynamic_cast(newNodes.second); if (l) for (int i = 0; i < l->childCount(); ++i) m_leafMap[l->getData(i)] = l; adjustTree(newNodes.first, newNodes.second); } } template void KoRTree::insert(Node * node) { if (node->level() == m_root->level()) { adjustTree(m_root, node); } else { QRectF bb(node->boundingBox()); NonLeafNode * newParent = m_root->chooseNode(bb, node->level() + 1); newParent->insert(bb, node); QPair newNodes(node, 0); if (newParent->childCount() > m_capacity) { newNodes = splitNode(newParent); } adjustTree(newNodes.first, newNodes.second); } } template void KoRTree::remove(const T&data) { //qDebug() << "KoRTree remove"; LeafNode * leaf = m_leafMap[data]; - if (leaf == 0) { - qWarning() << "KoRTree::remove( const T&data) data not found"; - return; - } + + // Trying to remove unexistent leaf. Most probably, this leaf hasn't been added + // to the shape manager correctly + KIS_SAFE_ASSERT_RECOVER_RETURN(leaf); + m_leafMap.remove(data); leaf->remove(data); + /** + * WARNING: after calling condenseTree() the temporary enters an inconsistent state! + * m_leafMap still points to the nodes now stored in 'reinsert' list, although + * they are not a part of the hierarchy. This state does not cause any use + * visible changes, but should be considered while implementing sanity checks. + */ + QVector reinsert; condenseTree(leaf, reinsert); for (int i = 0; i < reinsert.size(); ++i) { if (reinsert[i]->isLeaf()) { LeafNode * leaf = dynamic_cast(reinsert[i]); for (int j = 0; j < leaf->childCount(); ++j) { insertHelper(leaf->childBoundingBox(j), leaf->getData(j), leaf->getDataId(j)); } // clear is needed as the data items are not removed when insert into a new node leaf->clear(); delete leaf; } else { NonLeafNode * node = dynamic_cast(reinsert[i]); for (int j = 0; j < node->childCount(); ++j) { insert(node->getNode(j)); } // clear is needed as the data items are not removed when insert into a new node node->clear(); delete node; } } } template QList KoRTree::intersects(const QRectF& rect) const { QMap found; m_root->intersects(rect, found); return found.values(); } template QList KoRTree::contains(const QPointF &point) const { QMap found; m_root->contains(point, found); return found.values(); } template QList KoRTree::contained(const QRectF& rect) const { QMap found; m_root->contained(rect, found); return found.values(); } template QList KoRTree::keys() const { QList found; m_root->keys(found); return found; } template QList KoRTree::values() const { QMap found; m_root->values(found); return found.values(); } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::paint(QPainter & p) const { if (m_root) { m_root->paint(p, 0); } } template void KoRTree::debug() const { QString prefix(""); m_root->debug(prefix); } #endif template QPair< typename KoRTree::Node*, typename KoRTree::Node* > KoRTree::splitNode(typename KoRTree::Node* node) { //qDebug() << "KoRTree::splitNode" << node; Node * n1; Node * n2; if (node->isLeaf()) { n1 = createLeafNode(m_capacity + 1, node->level(), node->parent()); n2 = createLeafNode(m_capacity + 1, node->level(), node->parent()); } else { n1 = createNonLeafNode(m_capacity + 1, node->level(), node->parent()); n2 = createNonLeafNode(m_capacity + 1, node->level(), node->parent()); } //qDebug() << " n1" << n1 << n1->nodeId(); //qDebug() << " n2" << n2 << n2->nodeId(); QVector marker(m_capacity + 1); QPair seeds(pickSeeds(node)); n1->move(node, seeds.first); n2->move(node, seeds.second); marker[seeds.first] = true; marker[seeds.second] = true; // There is one more in a node to split than the capacity and as we // already put the seeds in the new nodes subtract them. int remaining = m_capacity + 1 - 2; while (remaining > 0) { if (m_minimum - n1->childCount() == remaining) { for (int i = 0; i < m_capacity + 1; ++i) { if (!marker[i]) { n1->move(node, i); --remaining; } } } else if (m_minimum - n2->childCount() == remaining) { for (int i = 0; i < m_capacity + 1; ++i) { if (!marker[i]) { n2->move(node, i); --remaining; } } } else { QPair next(pickNext(node, marker, n1, n2)); if (next.first == 0) { n1->move(node, next.second); } else { n2->move(node, next.second); } --remaining; } } Q_ASSERT(n1->childCount() + n2->childCount() == node->childCount()); // move the data back to the old node // this has to be done as the current node is already in the tree. node->clear(); for (int i = 0; i < n1->childCount(); ++i) { node->move(n1, i); } //qDebug() << " delete n1" << n1 << n1->nodeId(); // clear is needed as the data items are not removed n1->clear(); delete n1; return qMakePair(node, n2); } template QPair KoRTree::pickSeeds(Node *node) { int s1 = 0; int s2 = 1; qreal max = 0; for (int i = 0; i < m_capacity + 1; ++i) { for (int j = i+1; j < m_capacity + 1; ++j) { if (i != j) { QRectF bb1(node->childBoundingBox(i)); QRectF bb2(node->childBoundingBox(j)); QRectF comp(node->childBoundingBox(i).united(node->childBoundingBox(j))); qreal area = comp.width() * comp.height() - bb1.width() * bb1.height() - bb2.width() * bb2.height(); //qDebug() << " ps" << i << j << area; if (area > max) { max = area; s1 = i; s2 = j; } } } } return qMakePair(s1, s2); } template QPair KoRTree::pickNext(Node * node, QVector & marker, Node * group1, Node * group2) { //qDebug() << "KoRTree::pickNext" << marker; qreal max = -1.0; int select = 0; int group = 0; for (int i = 0; i < m_capacity + 1; ++i) { if (marker[i] == false) { QRectF bb1 = group1->boundingBox().united(node->childBoundingBox(i)); QRectF bb2 = group2->boundingBox().united(node->childBoundingBox(i)); qreal d1 = bb1.width() * bb1.height() - group1->boundingBox().width() * group1->boundingBox().height(); qreal d2 = bb2.width() * bb2.height() - group2->boundingBox().width() * group2->boundingBox().height(); qreal diff = qAbs(d1 - d2); //qDebug() << " diff" << diff << i << d1 << d2; if (diff > max) { max = diff; select = i; //qDebug() << " i =" << i; if (qAbs(d1) > qAbs(d2)) { group = 1; } else { group = 0; } //qDebug() << " group =" << group; } } } marker[select] = true; return qMakePair(group, select); } template void KoRTree::adjustTree(Node *node1, Node *node2) { //qDebug() << "KoRTree::adjustTree"; if (node1->isRoot()) { //qDebug() << " root"; if (node2) { NonLeafNode * newRoot = createNonLeafNode(m_capacity + 1, node1->level() + 1, 0); newRoot->insert(node1->boundingBox(), node1); newRoot->insert(node2->boundingBox(), node2); m_root = newRoot; //qDebug() << "new root" << m_root->nodeId(); } } else { NonLeafNode * parent = dynamic_cast(node1->parent()); if (!parent) { qFatal("KoRTree::adjustTree: no parent node found!"); return; } //QRectF pbbold( parent->boundingBox() ); parent->setChildBoundingBox(node1->place(), node1->boundingBox()); parent->updateBoundingBox(); //qDebug() << " bb1 =" << node1->boundingBox() << node1->place() << pbbold << "->" << parent->boundingBox() << parent->nodeId(); if (!node2) { //qDebug() << " update"; adjustTree(parent, 0); } else { if (parent->childCount() < m_capacity) { //qDebug() << " no split needed"; parent->insert(node2->boundingBox(), node2); adjustTree(parent, 0); } else { //qDebug() << " split again"; parent->insert(node2->boundingBox(), node2); QPair newNodes = splitNode(parent); adjustTree(newNodes.first, newNodes.second); } } } } template void KoRTree::condenseTree(Node *node, QVector & reinsert) { //qDebug() << "KoRTree::condenseTree begin reinsert.size()" << reinsert.size(); if (!node->isRoot()) { Node * parent = node->parent(); //qDebug() << " !node->isRoot us" << node->childCount(); if (node->childCount() < m_minimum) { //qDebug() << " remove node"; parent->remove(node->place()); reinsert.push_back(node); + + /** + * WARNING: here we leave the tree in an inconsistent state! 'reinsert' + * nodes may still be kept in m_leafMap structure, but we will + * *not* remove them for the efficiency reasons. They are guarenteed + * to be readded in remove(). + */ + } else { //qDebug() << " update BB parent is root" << parent->isRoot(); parent->setChildBoundingBox(node->place(), node->boundingBox()); parent->updateBoundingBox(); } condenseTree(parent, reinsert); } else { //qDebug() << " node->isRoot us" << node->childCount(); if (node->childCount() == 1 && !node->isLeaf()) { //qDebug() << " usedSpace = 1"; NonLeafNode * n = dynamic_cast(node); if (n) { Node * kid = n->getNode(0); // clear is needed as the data items are not removed m_root->clear(); delete m_root; m_root = kid; m_root->setParent(0); //qDebug() << " new root" << m_root; } else { qFatal("KoRTree::condenseTree cast to NonLeafNode failed"); } } } //qDebug() << "KoRTree::condenseTree end reinsert.size()" << reinsert.size(); } #ifdef CALLIGRA_RTREE_DEBUG template QColor KoRTree::Node::levelColor[] = { QColor(Qt::green), QColor(Qt::red), QColor(Qt::cyan), QColor(Qt::magenta), QColor(Qt::yellow), }; template int KoRTree::Node::nodeIdCnt = 0; #endif template KoRTree::Node::Node(int capacity, int level, Node * parent) : m_parent(parent) , m_childBoundingBox(capacity) , m_counter(0) #ifdef CALLIGRA_RTREE_DEBUG , m_nodeId(nodeIdCnt++) #endif , m_level(level) { } template void KoRTree::Node::remove(int index) { for (int i = index + 1; i < m_counter; ++i) { m_childBoundingBox[i-1] = m_childBoundingBox[i]; } --m_counter; updateBoundingBox(); } template void KoRTree::Node::updateBoundingBox() { m_boundingBox = QRectF(); for (int i = 0; i < m_counter; ++i) { m_boundingBox = m_boundingBox.united(m_childBoundingBox[i]); } } template void KoRTree::Node::clear() { m_counter = 0; m_boundingBox = QRectF(); } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::Node::paintRect(QPainter & p, int level) const { QColor c(Qt::black); if (level < levelColorSize) { c = levelColor[level]; } QPen pen(c, 0); p.setPen(pen); QRectF bbdraw(this->m_boundingBox); bbdraw.adjust(level * 2, level * 2, -level * 2, -level * 2); p.drawRect(bbdraw); } #endif template KoRTree::NonLeafNode::NonLeafNode(int capacity, int level, Node * parent) : Node(capacity, level, parent) , m_childs(capacity) { //qDebug() << "NonLeafNode::NonLeafNode()" << this; } template KoRTree::NonLeafNode::~NonLeafNode() { //qDebug() << "NonLeafNode::~NonLeafNode()" << this; for (int i = 0; i < this->m_counter; ++i) { delete m_childs[i]; } } template void KoRTree::NonLeafNode::insert(const QRectF& bb, Node * data) { m_childs[this->m_counter] = data; data->setPlace(this->m_counter); data->setParent(this); this->m_childBoundingBox[this->m_counter] = bb; this->m_boundingBox = this->m_boundingBox.united(bb); //qDebug() << "NonLeafNode::insert" << this->nodeId() << data->nodeId(); ++this->m_counter; } template void KoRTree::NonLeafNode::remove(int index) { for (int i = index + 1; i < this->m_counter; ++i) { m_childs[i-1] = m_childs[i]; m_childs[i-1]->setPlace(i - 1); } Node::remove(index); } template void KoRTree::NonLeafNode::move(Node * node, int index) { //qDebug() << "NonLeafNode::move" << this << node << index << node->nodeId() << "->" << this->nodeId(); NonLeafNode * n = dynamic_cast(node); if (n) { QRectF bb = n->childBoundingBox(index); insert(bb, n->getNode(index)); } } template typename KoRTree::LeafNode * KoRTree::NonLeafNode::chooseLeaf(const QRectF& bb) { return getLeastEnlargement(bb)->chooseLeaf(bb); } template typename KoRTree::NonLeafNode * KoRTree::NonLeafNode::chooseNode(const QRectF& bb, int level) { if (this->m_level > level) { return getLeastEnlargement(bb)->chooseNode(bb, level); } else { return this; } } template void KoRTree::NonLeafNode::intersects(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { m_childs[i]->intersects(rect, result); } } } template void KoRTree::NonLeafNode::contains(const QPointF & point, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].contains(point)) { m_childs[i]->contains(point, result); } } } template void KoRTree::NonLeafNode::contained(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { m_childs[i]->contained(rect, result); } } } template void KoRTree::NonLeafNode::keys(QList & result) const { for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->keys(result); } } template void KoRTree::NonLeafNode::values(QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->values(result); } } template typename KoRTree::Node * KoRTree::NonLeafNode::getNode(int index) const { return m_childs[index]; } template typename KoRTree::Node * KoRTree::NonLeafNode::getLeastEnlargement(const QRectF& bb) const { //qDebug() << "NonLeafNode::getLeastEnlargement"; QVarLengthArray area(this->m_counter); for (int i = 0; i < this->m_counter; ++i) { QSizeF big(this->m_childBoundingBox[i].united(bb).size()); area[i] = big.width() * big.height() - this->m_childBoundingBox[i].width() * this->m_childBoundingBox[i].height(); } int minIndex = 0; qreal minArea = area[minIndex]; //qDebug() << " min" << minIndex << minArea; for (int i = 1; i < this->m_counter; ++i) { if (area[i] < minArea) { minIndex = i; minArea = area[i]; //qDebug() << " min" << minIndex << minArea; } } return m_childs[minIndex]; } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::NonLeafNode::debug(QString line) const { for (int i = 0; i < this->m_counter; ++i) { qDebug("%s %d %d", qPrintable(line), this->nodeId(), i); m_childs[i]->debug(line + " "); } } template void KoRTree::NonLeafNode::paint(QPainter & p, int level) const { this->paintRect(p, level); for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->paint(p, level + 1); } } #endif template int KoRTree::LeafNode::dataIdCounter = 0; template KoRTree::LeafNode::LeafNode(int capacity, int level, Node * parent) : Node(capacity, level, parent) , m_data(capacity) , m_dataIds(capacity) { //qDebug() << "LeafNode::LeafNode" << this; } template KoRTree::LeafNode::~LeafNode() { //qDebug() << "LeafNode::~LeafNode" << this; } template void KoRTree::LeafNode::insert(const QRectF& bb, const T& data, int id) { m_data[this->m_counter] = data; m_dataIds[this->m_counter] = id; this->m_childBoundingBox[this->m_counter] = bb; this->m_boundingBox = this->m_boundingBox.united(bb); ++this->m_counter; } template void KoRTree::LeafNode::remove(int index) { for (int i = index + 1; i < this->m_counter; ++i) { m_data[i-1] = m_data[i]; m_dataIds[i-1] = m_dataIds[i]; } Node::remove(index); } template void KoRTree::LeafNode::remove(const T& data) { int old_counter = this->m_counter; for (int i = 0; i < this->m_counter; ++i) { if (m_data[i] == data) { //qDebug() << "LeafNode::remove id" << i; remove(i); break; } } if (old_counter == this->m_counter) { qWarning() << "LeafNode::remove( const T&data) data not found"; } } template void KoRTree::LeafNode::move(Node * node, int index) { LeafNode * n = dynamic_cast(node); if (n) { //qDebug() << "LeafNode::move" << this << node << index // << node->nodeId() << "->" << this->nodeId() << n->childBoundingBox( index ); QRectF bb = n->childBoundingBox(index); insert(bb, n->getData(index), n->getDataId(index)); } } template typename KoRTree::LeafNode * KoRTree::LeafNode::chooseLeaf(const QRectF& bb) { Q_UNUSED(bb); return this; } template typename KoRTree::NonLeafNode * KoRTree::LeafNode::chooseNode(const QRectF& bb, int level) { Q_UNUSED(bb); Q_UNUSED(level); qFatal("LeafNode::chooseNode called. This should not happen!"); return 0; } template void KoRTree::LeafNode::intersects(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::contains(const QPointF & point, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].contains(point)) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::contained(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (rect.contains(this->m_childBoundingBox[i])) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::keys(QList & result) const { for (int i = 0; i < this->m_counter; ++i) { result.push_back(this->m_childBoundingBox[i]); } } template void KoRTree::LeafNode::values(QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { result.insert(m_dataIds[i], m_data[i]); } } template const T& KoRTree::LeafNode::getData(int index) const { return m_data[ index ]; } template int KoRTree::LeafNode::getDataId(int index) const { return m_dataIds[ index ]; } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::LeafNode::debug(QString line) const { for (int i = 0; i < this->m_counter; ++i) { qDebug("%s %d %d %p", qPrintable(line), this->nodeId(), i, &(m_data[i])); qDebug() << this->m_childBoundingBox[i].toRect(); } } template void KoRTree::LeafNode::paint(QPainter & p, int level) const { if (this->m_counter) { this->paintRect(p, level); } } #endif #endif /* KORTREE_H */ diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 02d471a949..d1bb8c4226 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2399 +1,2398 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 2010 Boudewijn Rempt 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 "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoOdfWorkaround.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include "KoOdfGradientBackground.h" #include // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), shadow(0), border(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q) : q_ptr(q), size(rhs.size), shapeId(rhs.shapeId), name(rhs.name), localMatrix(rhs.localMatrix), connectors(rhs.connectors), parent(0), // to be initialized later shapeManagers(), // to be initialized later toolDelegates(), // FIXME: how to initialize them? userData(rhs.userData ? rhs.userData->clone() : 0), stroke(rhs.stroke), fill(rhs.fill), dependees(), // FIXME: how to initialize them? shadow(0), // WARNING: not implemented in Krita border(0), // WARNING: not implemented in Krita clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0), clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0), additionalAttributes(rhs.additionalAttributes), additionalStyleAttributes(rhs.additionalStyleAttributes), filterEffectStack(0), // WARNING: not implemented in Krita transparency(rhs.transparency), hyperLink(rhs.hyperLink), zIndex(rhs.zIndex), runThrough(rhs.runThrough), visible(rhs.visible), printable(rhs.visible), geometryProtected(rhs.geometryProtected), keepAspect(rhs.keepAspect), selectable(rhs.selectable), detectCollision(rhs.detectCollision), protectContent(rhs.protectContent), textRunAroundSide(rhs.textRunAroundSide), textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft), textRunAroundDistanceTop(rhs.textRunAroundDistanceTop), textRunAroundDistanceRight(rhs.textRunAroundDistanceRight), textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom), textRunAroundThreshold(rhs.textRunAroundThreshold), textRunAroundContour(rhs.textRunAroundContour) { } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); if (parent) parent->removeShape(q); Q_FOREACH (KoShapeManager *manager, shapeManagers) { manager->remove(q); - manager->removeAdditional(q); } if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); Q_FOREACH (KoShape * shape, dependees) { shape->shapeChanged(type, q); } Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { listener->notifyShapeChangedImpl(type, q); } } void KoShapePrivate::updateStroke() { Q_Q(KoShape); if (!stroke) return; KoInsets insets; stroke->strokeInsets(q, insets); QSizeF inner = q->size(); // update left q->update(QRectF(-insets.left, -insets.top, insets.left, inner.height() + insets.top + insets.bottom)); // update top q->update(QRectF(-insets.left, -insets.top, inner.width() + insets.left + insets.right, insets.top)); // update right q->update(QRectF(inner.width(), -insets.top, insets.right, inner.height() + insets.top + insets.bottom)); // update bottom q->update(QRectF(-insets.left, inner.height(), inner.width() + insets.left + insets.right, insets.bottom)); } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate *dd) : d_ptr(dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); delete d_ptr; } KoShape *KoShape::cloneShape() const { return 0; } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb(QPointF(), size()); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } int KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::update(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { QRectF rc(absoluteTransformation(0).mapRect(rect)); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rc); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { Q_D(const KoShape); if (d->fill) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; } void KoShape::notifyChanged() { Q_D(KoShape); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData.data(); } bool KoShape::hasTransparency() const { Q_D(const KoShape); if (! d->fill) return true; else return d->fill->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); d->shapeChanged(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); return d->fill; } void KoShape::setZIndex(int zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (! recursive) return d->visible; if (recursive && ! d->visible) return false; KoShapeContainer * parentShape = parent(); while (parentShape) { if (! parentShape->isVisible()) return false; parentShape = parentShape->parent(); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; d->shapeChanged(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { Q_D(const KoShape); return d->stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { Q_D(KoShape); // TODO: check if it really updates stuff d->updateStroke(); d->stroke = stroke; d->updateStroke(); d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath.reset(clipPath); d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { Q_D(KoShape); d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { Q_D(const KoShape); return d->clipMask.data(); } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isEditable() const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (d->parent && d->parent->isChildLocked(this)) return false; return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModelSP sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); d->stroke.clear(); if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); QSharedPointer stroke(new KoShapeStroke()); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { QSharedPointer stroke(new KoShapeStroke()); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return KoShapeStrokeModelSP(); } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse); d->clipPath.reset(clipPath); } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } context.xmlWriter().addAttributePt("svg:width", s.width()); context.xmlWriter().addAttributePt("svg:height", s.height()); } // The position is implicitly stored in the transformation matrix // if the transformation is saved as well if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) { const QPointF p(position() * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x", p.x()); context.xmlWriter().addAttributePt("svg:y", p.y()); } if (attributes & OdfTransformation) { QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this); if (! matrix.isIdentity()) { if (qAbs(matrix.m11() - 1) < 1E-5 // 1 && qAbs(matrix.m12()) < 1E-5 // 0 && qAbs(matrix.m21()) < 1E-5 // 0 && qAbs(matrix.m22() - 1) < 1E-5) { // 1 context.xmlWriter().addAttributePt("svg:x", matrix.dx()); context.xmlWriter().addAttributePt("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { context.xmlWriter().addAttributePt("svg:x", cp.value().position.x()); context.xmlWriter().addAttributePt("svg:y", cp.value().position.y()); } QString escapeDirection; switch(cp.value().escapeDirection) { case KoConnectionPoint::HorizontalDirections: escapeDirection = "horizontal"; break; case KoConnectionPoint::VerticalDirections: escapeDirection = "vertical"; break; case KoConnectionPoint::LeftDirection: escapeDirection = "left"; break; case KoConnectionPoint::RightDirection: escapeDirection = "right"; break; case KoConnectionPoint::UpDirection: escapeDirection = "up"; break; case KoConnectionPoint::DownDirection: escapeDirection = "down"; break; default: // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour wheras // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform()); KoShape::applyConversion(*painter, converter); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::linearizeSubtree(const QList &shapes) { QList result; Q_FOREACH (KoShape *shape, shapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtree(container->shapes()); } } return result; } diff --git a/libs/flake/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp index 0ba95beb43..cc4c75fbdb 100644 --- a/libs/flake/KoShapeContainer.cpp +++ b/libs/flake/KoShapeContainer.cpp @@ -1,284 +1,240 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * 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 "KoShapeContainer.h" #include "KoShapeContainer_p.h" #include "KoShapeContainerModel.h" #include "KoShapeStrokeModel.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoViewConverter.h" #include #include #include #include "kis_painting_tweaks.h" #include "kis_assert.h" KoShapeContainerPrivate::KoShapeContainerPrivate(KoShapeContainer *q) : KoShapePrivate(q), shapeInterface(q), model(0) { } KoShapeContainerPrivate::~KoShapeContainerPrivate() { delete model; } KoShapeContainerPrivate::KoShapeContainerPrivate(const KoShapeContainerPrivate &rhs, KoShapeContainer *q) : KoShapePrivate(rhs, q), shapeInterface(q), model(0) { } KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model) : KoShape(new KoShapeContainerPrivate(this)) { Q_D(KoShapeContainer); d->model = model; } KoShapeContainer::KoShapeContainer(KoShapeContainerPrivate *dd) : KoShape(dd) { Q_D(KoShapeContainer); // HACK ALERT: the shapes are copied inside the model, // but we still need to connect the to the // hierarchy here! if (d->model) { Q_FOREACH (KoShape *shape, d->model->shapes()) { shape->setParent(this); } } } KoShapeContainer::~KoShapeContainer() { Q_D(KoShapeContainer); if (d->model) { d->model->deleteOwnedShapes(); } } void KoShapeContainer::addShape(KoShape *shape) { shape->setParent(this); } void KoShapeContainer::removeShape(KoShape *shape) { shape->setParent(0); } int KoShapeContainer::shapeCount() const { Q_D(const KoShapeContainer); if (d->model == 0) return 0; return d->model->count(); } bool KoShapeContainer::isChildLocked(const KoShape *child) const { Q_D(const KoShapeContainer); if (d->model == 0) return false; return d->model->isChildLocked(child); } void KoShapeContainer::setClipped(const KoShape *child, bool clipping) { Q_D(KoShapeContainer); if (d->model == 0) return; d->model->setClipped(child, clipping); } void KoShapeContainer::setInheritsTransform(const KoShape *shape, bool inherit) { Q_D(KoShapeContainer); if (d->model == 0) return; d->model->setInheritsTransform(shape, inherit); } bool KoShapeContainer::inheritsTransform(const KoShape *shape) const { Q_D(const KoShapeContainer); if (d->model == 0) return false; return d->model->inheritsTransform(shape); } void KoShapeContainer::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { + // Shape container paints only its internal component part. All the children are rendered + // by the shape manager itself + Q_D(KoShapeContainer); painter.save(); paintComponent(painter, converter, paintcontext); painter.restore(); - if (d->model == 0 || d->model->count() == 0) - return; - - QList sortedObjects = d->model->shapes(); - qSort(sortedObjects.begin(), sortedObjects.end(), KoShape::compareShapeZIndex); - - // Do the following to revert the absolute transformation of the container - // that is re-applied in shape->absoluteTransformation() later on. The transformation matrix - // of the container has already been applied once before this function is called. - const QTransform baseMatrix = absoluteTransformation(&converter).inverted() * painter.transform(); - - // We'll use this clipRect to see if our child shapes lie within it. - // Because shape->boundingRect() uses absoluteTransformation(0) we'll - // use that as well to have the same (absolute) reference transformation - // of our and the child's bounding boxes. - const QRectF fullClipRect = boundingRect(); - - // clip the children to the parent outline. - QTransform m; - qreal zoomX, zoomY; - converter.zoom(&zoomX, &zoomY); - m.scale(zoomX, zoomY); - painter.setClipRect(m.mapRect(absoluteTransformation(0).inverted().mapRect(fullClipRect)), Qt::IntersectClip); - - - Q_FOREACH (KoShape *shape, sortedObjects) { - //debugFlake <<"KoShapeContainer::painting shape:" << shape->shapeId() <<"," << shape->boundingRect(); - if (!shape->isVisible()) - continue; - - // FIXME:this line breaks painting of the grouped shapes (probably deprecate clipping?) - //if (!isClipped(shape)) // the shapeManager will have to draw those, or else we can't do clipRects - // continue; - - // don't try to draw a child shape that is not in the clipping rect of the painter. - if (!fullClipRect.intersects(shape->boundingRect())) - - continue; - - painter.save(); - painter.setTransform(shape->absoluteTransformation(&converter) * baseMatrix); - shape->paint(painter, converter, paintcontext); - painter.restore(); - if (shape->stroke()) { - painter.save(); - painter.setTransform(shape->absoluteTransformation(&converter) * baseMatrix); - shape->stroke()->paint(shape, painter, converter); - painter.restore(); - } - } } void KoShapeContainer::shapeChanged(ChangeType type, KoShape* shape) { Q_UNUSED(shape); Q_D(KoShapeContainer); if (d->model == 0) return; if (!(type == RotationChanged || type == ScaleChanged || type == ShearChanged || type == SizeChanged || type == PositionChanged || type == GenericMatrixChange)) return; d->model->containerChanged(this, type); Q_FOREACH (KoShape *shape, d->model->shapes()) shape->notifyChanged(); } bool KoShapeContainer::isClipped(const KoShape *child) const { Q_D(const KoShapeContainer); if (d->model == 0) // throw exception?? return false; return d->model->isClipped(child); } void KoShapeContainer::update() const { Q_D(const KoShapeContainer); KoShape::update(); if (d->model) Q_FOREACH (KoShape *shape, d->model->shapes()) shape->update(); } QList KoShapeContainer::shapes() const { Q_D(const KoShapeContainer); if (d->model == 0) return QList(); return d->model->shapes(); } KoShapeContainerModel *KoShapeContainer::model() const { Q_D(const KoShapeContainer); return d->model; } KoShapeContainer::ShapeInterface *KoShapeContainer::shapeInterface() { Q_D(KoShapeContainer); return &d->shapeInterface; } KoShapeContainer::ShapeInterface::ShapeInterface(KoShapeContainer *_q) : q(_q) { } void KoShapeContainer::ShapeInterface::addShape(KoShape *shape) { KoShapeContainerPrivate * const d = q->d_func(); - Q_ASSERT(shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(shape); + if (shape->parent() == q && q->shapes().contains(shape)) { return; } // TODO add a method to create a default model depending on the shape container if (!d->model) { d->model = new SimpleShapeContainerModel(); } if (shape->parent() && shape->parent() != q) { shape->parent()->shapeInterface()->removeShape(shape); } d->model->add(shape); + d->model->shapeHasBeenAddedToHierarchy(shape, q); } void KoShapeContainer::ShapeInterface::removeShape(KoShape *shape) { KoShapeContainerPrivate * const d = q->d_func(); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->model); KIS_SAFE_ASSERT_RECOVER_RETURN(d->model->shapes().contains(shape)); + d->model->shapeToBeRemovedFromHierarchy(shape, q); d->model->remove(shape); KoShapeContainer *grandparent = q->parent(); if (grandparent) { grandparent->model()->childChanged(q, KoShape::ChildChanged); } } diff --git a/libs/flake/KoShapeContainerModel.cpp b/libs/flake/KoShapeContainerModel.cpp index 90e28223e6..d400c84d15 100644 --- a/libs/flake/KoShapeContainerModel.cpp +++ b/libs/flake/KoShapeContainerModel.cpp @@ -1,69 +1,85 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * * 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 "KoShapeContainerModel.h" #include "KoShapeContainer.h" #include "kis_assert.h" KoShapeContainerModel::KoShapeContainerModel() { } KoShapeContainerModel::~KoShapeContainerModel() { } void KoShapeContainerModel::deleteOwnedShapes() { QList ownedShapes = this->shapes(); Q_FOREACH (KoShape *shape, ownedShapes) { shape->setParent(0); delete shape; } KIS_SAFE_ASSERT_RECOVER_NOOP(!this->count()); } void KoShapeContainerModel::proposeMove(KoShape *child, QPointF &move) { Q_UNUSED(child); Q_UNUSED(move); } void KoShapeContainerModel::childChanged(KoShape *child, KoShape::ChangeType type) { Q_UNUSED(type); if (type != KoShape::CollisionDetected) { KoShapeContainer * parent = child->parent(); Q_ASSERT(parent); // propagate the change up the hierarchy KoShapeContainer * grandparent = parent->parent(); if (grandparent) { grandparent->model()->childChanged(parent, KoShape::ChildChanged); } } } +void KoShapeContainerModel::shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) +{ + KoShapeContainer *parent = addedToSubtree->parent(); + if (parent) { + parent->model()->shapeHasBeenAddedToHierarchy(shape, parent); + } +} + +void KoShapeContainerModel::shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) +{ + KoShapeContainer *parent = removedFromSubtree->parent(); + if (parent) { + parent->model()->shapeToBeRemovedFromHierarchy(shape, parent); + } +} + KoShapeContainerModel::KoShapeContainerModel(const KoShapeContainerModel &rhs) { Q_UNUSED(rhs); } diff --git a/libs/flake/KoShapeContainerModel.h b/libs/flake/KoShapeContainerModel.h index 7e9c258de6..289a7e2d92 100644 --- a/libs/flake/KoShapeContainerModel.h +++ b/libs/flake/KoShapeContainerModel.h @@ -1,178 +1,181 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * * 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 KOSHAPECONTAINERMODEL_H #define KOSHAPECONTAINERMODEL_H #include "kritaflake_export.h" #include #include #include class KoShapeContainer; /** * The interface for the container model. * This class has no implementation, but only pure virtual methods. You can find a * fully implemented model using KoShapeContainerDefaultModel. Extending this * class and implementing all methods allows you to implement a custom data-backend * for the KoShapeContainer. * @see KoShapeContainer, KoShapeContainerDefaultModel */ class KRITAFLAKE_EXPORT KoShapeContainerModel { public: /// default constructor KoShapeContainerModel(); /// destructor virtual ~KoShapeContainerModel(); void deleteOwnedShapes(); /** * Add a shape to this models store. * @param shape the shape to be managed in the container. */ virtual void add(KoShape *shape) = 0; /** * Remove a shape to be completely separated from the model. * @param shape the shape to be removed. */ virtual void remove(KoShape *shape) = 0; /** * Set the argument shape to have its 'clipping' property set. * * A shape that is clipped by the container will have its visible portion * limited to the area where it intersects with the container. * If a shape is positioned or sized such that it would be painted outside * of the KoShape::outline() of its parent container, setting this property * to true will clip the shape painting to the container outline. * * @param shape the shape for which the property will be changed. * @param clipping the new value */ virtual void setClipped(const KoShape *shape, bool clipping) = 0; /** * Returns if the argument shape has its 'clipping' property set. * * A shape that is clipped by the container will have its visible portion * limited to the area where it intersects with the container. * If a shape is positioned or sized such that it would be painted outside * of the KoShape::outline() of its parent container, setting this property * to true will clip the shape painting to the container outline. * * @return if the argument shape has its 'clipping' property set. * @param shape the shape for which the property will be returned. */ virtual bool isClipped(const KoShape *shape) const = 0; /** * Set the shape to inherit the container transform. * * A shape that inherits the transform of the parent container will have its * share / rotation / skew etc be calculated as being the product of both its * own local transformation and also that of its parent container. * If you set this to true and rotate the container, the shape will get that * rotation as well automatically. * * @param shape the shape for which the property will be changed. * @param inherit the new value */ virtual void setInheritsTransform(const KoShape *shape, bool inherit) = 0; /** * Returns if the shape inherits the container transform. * * A shape that inherits the transform of the parent container will have its * share / rotation / skew etc be calculated as being the product of both its * own local transformation and also that of its parent container. * If you set this to true and rotate the container, the shape will get that * rotation as well automatically. * * @return if the argument shape has its 'inherits transform' property set. * @param shape the shape for which the property will be returned. */ virtual bool inheritsTransform(const KoShape *shape) const = 0; /** * Return wheather the child has the effective state of being locked for user modifications. * The model has to call KoShape::isGeometryProtected() and base its return value upon that, it can * additionally find rules on wheather the child is locked based on the container state. * @param child the shape that the user wants to move. */ virtual bool isChildLocked(const KoShape *child) const = 0; /** * Return the current number of children registered. * @return the current number of children registered. */ virtual int count() const = 0; /** * Return the list of all shapes of this model * @return the list of all shapes */ virtual QList shapes() const = 0; /** * This method is called as a notification that one of the properties of the * container changed. This can be one of size, position, rotation and skew. * Note that clipped children will automatically get all these changes, the model * does not have to do anything for that. * @param container the actual container that changed. * @param type this enum shows which change the container has had. */ virtual void containerChanged(KoShapeContainer *container, KoShape::ChangeType type) = 0; /** * This method is called when the user tries to move a shape that is a shape of the * container this model represents. * The shape itself is not yet moved; it is proposed to be moved over the param move distance. * You can alter the value of the move to affect the actual distance moved. * The default implementation does nothing. * @param shape the shape of this container that the user is trying to move. * @param move the distance that the user proposes to move shape from the current position. */ virtual void proposeMove(KoShape *shape, QPointF &move); /** * This method is called when one of the shape shapes has been modified. * When a shape shape is rotated, moved or scaled/skewed this method will be called * to inform the container of such a change. The change has already happened at the * time this method is called. * The base implementation notifies the grand parent of the shape that there was a * change in a shape. A reimplentation if this function should call this method when * overwriding the function. * * @param shape the shape that has been changed * @param type this enum shows which change the shape has had. */ virtual void childChanged(KoShape *shape, KoShape::ChangeType type); + virtual void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree); + virtual void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree); + protected: KoShapeContainerModel(const KoShapeContainerModel &rhs); }; #endif diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index 92d4cfd834..ef485b9521 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,595 +1,582 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 Jan Hambrecht 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 "KoShapeManager.h" #include "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" -#include "KoShapeManagerPaintingStrategy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoClipMaskPainter.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" +#include "KisQPainterStateSaver.h" #include #include #include #include "kis_painting_tweaks.h" +bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape) +{ + return !dynamic_cast(shape) && !dynamic_cast(shape); +} void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; Q_FOREACH (KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { + if (!shapeUsedInRenderingTree(shape)) continue; + tree.remove(shape); QRectF br(shape->boundingRect()); - strategy->adapt(shape, br); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. - foreach (KoShape *shape, aggregate4update) + foreach (KoShape *shape, aggregate4update) { detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); + } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); - strategy->paint(child, painter, converter, paintContext); + KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); } KoShapeManager::~KoShapeManager() { Q_FOREACH (KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } Q_FOREACH (KoShape *shape, d->additionalShapes) { shape->priv()->removeShapeManager(this); } delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); Q_FOREACH (KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { if (d->shapes.contains(shape)) return; shape->priv()->addShapeManager(this); d->shapes.append(shape); - if (! dynamic_cast(shape) && ! dynamic_cast(shape)) { + if (d->shapeUsedInRenderingTree(shape)) { QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } -void KoShapeManager::addAdditional(KoShape *shape) -{ - if (shape) { - if (d->additionalShapes.contains(shape)) { - return; - } - shape->priv()->addShapeManager(this); - d->additionalShapes.append(shape); - } -} - void KoShapeManager::remove(KoShape *shape) { Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); shape->update(); shape->priv()->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); - d->tree.remove(shape); + if (d->shapeUsedInRenderingTree(shape)) { + d->tree.remove(shape); + } d->shapes.removeAll(shape); // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } // This signal is used in the annotation shape. // FIXME: Is this really what we want? (and shouldn't it be called shapeDeleted()?) shapeRemoved(shape); } -void KoShapeManager::removeAdditional(KoShape *shape) -{ - if (shape) { - shape->priv()->removeShapeManager(this); - d->additionalShapes.removeAll(shape); - } -} - void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter)); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = shapes(); warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible(true)) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); - foreach (KoShape *shape, sortedShapes) { - if (shape->parent() != 0 && shape->parent()->isClipped(shape)) - continue; + KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME - painter.save(); - - // apply shape clipping - KoClipPath::applyClipping(shape, painter, converter); - - // let the painting strategy paint the shape - KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME - d->strategy->paint(shape, painter, converter, paintContext); - - painter.restore(); + foreach (KoShape *shape, sortedShapes) { + renderSingleShape(shape, painter, converter, paintContext); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } -#include "kis_debug.h" + +void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) +{ + KisQPainterStateSaver saver(&painter); + + // apply shape clipping + KoClipPath::applyClipping(shape, painter, converter); + + // apply transformation + painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); + + // paint the shape + paintShape(shape, painter, converter, paintContext); +} + void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect())); shapePainter = clipMaskPainter->shapePainter(); } shapePainter->save(); shape->paint(*shapePainter, converter, paintContext); shapePainter->restore(); if (shape->stroke()) { shapePainter->save(); shape->stroke()->paint(shape, *shapePainter, converter); shapePainter->restore(); } if (clipMask) { shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); } } else { // TODO: clipping mask is not implemented for this case! // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); - d->paintGroup(group, imagePainter, converter, paintContext); + Private::paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); imagePainter.restore(); if (shape->stroke()) { imagePainter.save(); shape->stroke()->paint(shape, imagePainter, converter); imagePainter.restore(); } imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QList inputImages; Q_FOREACH (const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QList sortedShapes(d->tree.contains(position)); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible(true)) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode) { d->updateTree(); QList shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect)); for (int count = shapes.count() - 1; count >= 0; count--) { KoShape *shape = shapes.at(count); if (omitHiddenShapes && !shape->isVisible(true)) { shapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) { shapes.removeAt(count); } else if (containedMode) { QPainterPath containingPath; containingPath.addRect(rect); if (!containingPath.contains(outline)) { shapes.removeAt(count); } } } } return shapes; } void KoShapeManager::update(QRectF &rect, const KoShape *shape, bool selectionHandles) { d->canvas->updateCanvas(rect); if (selectionHandles && d->selection->isSelected(shape)) { if (d->canvas->toolProxy()) d->canvas->toolProxy()->repaintDecorations(); } } void KoShapeManager::notifyShapeChanged(KoShape *shape) { Q_ASSERT(shape); if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) { return; } const bool wasEmpty = d->aggregate4update.isEmpty(); d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); KoShapeContainer *container = dynamic_cast(shape); if (container) { Q_FOREACH (KoShape *child, container->shapes()) notifyShapeChanged(child); } if (wasEmpty && !d->aggregate4update.isEmpty()) QTimer::singleShot(100, this, SLOT(updateTree())); emit shapeChanged(shape); } QList KoShapeManager::shapes() const { return d->shapes; } QList KoShapeManager::topLevelShapes() const { QList shapes; // get all toplevel shapes Q_FOREACH (KoShape *shape, d->shapes) { if (shape->parent() == 0) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } -void KoShapeManager::setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy) -{ - delete d->strategy; - d->strategy = strategy; -} - KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeManager.h b/libs/flake/KoShapeManager.h index afcb51002d..cd5748fdac 100644 --- a/libs/flake/KoShapeManager.h +++ b/libs/flake/KoShapeManager.h @@ -1,216 +1,197 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2007, 2009 Thomas Zander 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 KOSHAPEMANAGER_H #define KOSHAPEMANAGER_H #include #include #include #include "KoFlake.h" #include "kritaflake_export.h" class KoShape; class KoSelection; class KoViewConverter; class KoCanvasBase; class KoPointerEvent; -class KoShapeManagerPaintingStrategy; class KoShapePaintingContext; class QPainter; class QPointF; class QRectF; /** * The shape manager hold a list of all shape which are in scope. * There is one shape manager per canvas. This makes the shape manager * different from QGraphicsScene, which contains the datamodel for all * graphics items: KoShapeManager only contains the subset of shapes * that are shown in its canvas. * * The selection in the different views can be different. */ class KRITAFLAKE_EXPORT KoShapeManager : public QObject { Q_OBJECT public: /// enum for add() enum Repaint { PaintShapeOnAdd, ///< Causes each shapes 'update()' to be called after being added to the shapeManager AddWithoutRepaint ///< Avoids each shapes 'update()' to be called for faster addition when its possible. }; /** * Constructor. */ explicit KoShapeManager(KoCanvasBase *canvas); /** * Constructor that takes a list of shapes, convenience version. * @param shapes the shapes to start out with, see also setShapes() * @param canvas the canvas this shape manager is working on. */ KoShapeManager(KoCanvasBase *canvas, const QList &shapes); virtual ~KoShapeManager(); /** * Remove all previously owned shapes and make the argument list the new shapes * to be managed by this manager. * @param shapes the new shapes to manage. * @param repaint if true it will trigger a repaint of the shapes */ void setShapes(const QList &shapes, Repaint repaint = PaintShapeOnAdd); /// returns the list of maintained shapes QList shapes() const; /** * Get a list of all shapes that don't have a parent. */ QList topLevelShapes() const; public Q_SLOTS: /** * Add a KoShape to be displayed and managed by this manager. * This will trigger a repaint of the shape. * @param shape the shape to add * @param repaint if true it will trigger a repaint of the shape */ void addShape(KoShape *shape, KoShapeManager::Repaint repaint = PaintShapeOnAdd); - /** - * Add an additional shape to the manager. - * - * For additional shapes only updates are handled - */ - void addAdditional(KoShape *shape); /** * Remove a KoShape from this manager * @param shape the shape to remove */ void remove(KoShape *shape); - /** - * Remove an additional shape - * - * For additional shapes only updates are handled - */ - void removeAdditional(KoShape *shape); - public: /// return the selection shapes for this shapeManager KoSelection *selection() const; /** * Paint all shapes and their selection handles etc. * @param painter the painter to paint to. * @param forPrint if true, make sure only actual content is drawn and no decorations. * @param converter to convert between document and view coordinates. */ void paint(QPainter &painter, const KoViewConverter &converter, bool forPrint); /** * Returns the shape located at a specific point in the document. * If more than one shape is located at the specific point, the given selection type * controls which of them is returned. * @param position the position in the document coordinate system. * @param selection controls which shape is returned when more than one shape is at the specific point * @param omitHiddenShapes if true, only visible shapes are considered */ KoShape *shapeAt(const QPointF &position, KoFlake::ShapeSelection selection = KoFlake::ShapeOnTop, bool omitHiddenShapes = true); /** * Returns the shapes which intersects the specific rect in the document. * @param rect the rectangle in the document coordinate system. * @param omitHiddenShapes if true, only visible shapes are considered */ QList shapesAt(const QRectF &rect, bool omitHiddenShapes = true, bool containedMode = false); /** * Request a repaint to be queued. * The repaint will be restricted to the parameters rectangle, which is expected to be * in points (the document coordinates system of KoShape) and it is expected to be * normalized and based in the global coordinates, not any local coordinates. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. * @param rect the rectangle (in pt) to queue for repaint. * @param shape the shape that is going to be redrawn; only needed when selectionHandles=true * @param selectionHandles if true; find out if the shape is selected and repaint its * selection handles at the same time. */ void update(QRectF &rect, const KoShape *shape = 0, bool selectionHandles = false); /** * Update the tree for finding the shapes. * This will remove the shape from the tree and will reinsert it again. * The update to the tree will be posponed until it is needed so that successive calls * will be merged into one. * @param shape the shape to updated its position in the tree. */ void notifyShapeChanged(KoShape *shape); /** * Paint a shape * * @param shape the shape to paint * @param painter the painter to paint to. * @param converter to convert between document and view coordinates. */ - void paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); + static void paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); /** - * Set the strategy of the KoShapeManager - * - * This can be used to change the behaviour of the painting of the shapes. - * @param strategy the new strategy. The ownership of the argument \p - * strategy will be taken by the shape manager. + * @brief renderSingleShape renders a shape on \p painter. This method includes all the + * needed steps for painting a single shape: setting transformations, clipping and masking. */ - void setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy); + static void renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); Q_SIGNALS: /// emitted when the selection is changed void selectionChanged(); /// emitted when an object in the selection is changed (moved/rotated etc) void selectionContentChanged(); /// emitted when any object changed (moved/rotated etc) void contentChanged(); /// emitted when a shape is removed. void shapeRemoved(KoShape *); /// emitted when any shape changed. void shapeChanged(KoShape *); private: - - friend class KoShapeManagerPaintingStrategy; KoCanvasBase *canvas(); class Private; Private * const d; Q_PRIVATE_SLOT(d, void updateTree()) }; #endif diff --git a/libs/flake/KoShapeManagerPaintingStrategy.cpp b/libs/flake/KoShapeManagerPaintingStrategy.cpp deleted file mode 100644 index 72e8f37a4f..0000000000 --- a/libs/flake/KoShapeManagerPaintingStrategy.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* This file is part of the KDE project - - Copyright (C) 2007,2009 Thorsten Zachmann - - 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 "KoShapeManagerPaintingStrategy.h" - -#include "KoShape.h" -#include "KoShapeManager.h" -#include - -class Q_DECL_HIDDEN KoShapeManagerPaintingStrategy::Private -{ -public: - Private(KoShapeManager * manager) - : shapeManager(manager) - {} - - KoShapeManager * shapeManager; -}; - -KoShapeManagerPaintingStrategy::KoShapeManagerPaintingStrategy(KoShapeManager * shapeManager) -: d(new KoShapeManagerPaintingStrategy::Private(shapeManager)) -{ -} - -KoShapeManagerPaintingStrategy::~KoShapeManagerPaintingStrategy() -{ - delete d; -} - -void KoShapeManagerPaintingStrategy::paint(KoShape * shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) -{ - if (d->shapeManager) { - painter.save(); - painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); - d->shapeManager->paintShape(shape, painter, converter, paintContext); - painter.restore(); // for the matrix - } -} - -void KoShapeManagerPaintingStrategy::adapt(KoShape * shape, QRectF & rect) -{ - Q_UNUSED(shape); - Q_UNUSED(rect); -} - -void KoShapeManagerPaintingStrategy::setShapeManager(KoShapeManager * shapeManager) -{ - d->shapeManager = shapeManager; -} - -KoShapeManager * KoShapeManagerPaintingStrategy::shapeManager() -{ - return d->shapeManager; -} diff --git a/libs/flake/KoShapeManagerPaintingStrategy.h b/libs/flake/KoShapeManagerPaintingStrategy.h deleted file mode 100644 index 804eb51ada..0000000000 --- a/libs/flake/KoShapeManagerPaintingStrategy.h +++ /dev/null @@ -1,81 +0,0 @@ -/* This file is part of the KDE project - - Copyright (C) 2007,2009 Thorsten Zachmann - - 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 KOSHAPEMANAGERPAINTINGSTRATEGY_H -#define KOSHAPEMANAGERPAINTINGSTRATEGY_H - -#include "kritaflake_export.h" - -class KoShapeManager; -class KoShape; -class KoViewConverter; -class KoShapePaintingContext; -class QPainter; -class QRectF; - -/** - * This implements the painting strategy for the KoShapeManager - * - * This is done to make it possible to have e.g. animations in kpresenter. - * - * This class implements the default strategy which is normally used. - */ -class KRITAFLAKE_EXPORT KoShapeManagerPaintingStrategy -{ -public: - explicit KoShapeManagerPaintingStrategy(KoShapeManager *shapeManager); - virtual ~KoShapeManagerPaintingStrategy(); - - /** - * Paint the shape - * - * @param shape the shape to paint - * @param painter the painter to paint to. - * @param converter to convert between document and view coordinates. - * @param forPrint if true, make sure only actual content is drawn and no decorations. - */ - virtual void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); - - /** - * Adapt the rect the shape occupies - * - * @param rect rect which will be updated to give the rect the shape occupies. - */ - virtual void adapt(KoShape *shape, QRectF &rect); - - /** - * Set the shape manager - * - * This is needed in case you cannot set the shape manager when creating the paiting strategy. - * It needs to be set before you paint otherwise nothing will be painted. - * - * @param shapeManager The shape manager to use in the painting startegy - */ - void setShapeManager(KoShapeManager *shapeManager); - -protected: - KoShapeManager *shapeManager(); - -private: - class Private; - Private * const d; -}; - -#endif /* KOSHAPEMANAGERPAINTINGSTRATEGY_H */ diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h index e9db0df8ba..1087fdabb5 100644 --- a/libs/flake/KoShapeManager_p.h +++ b/libs/flake/KoShapeManager_p.h @@ -1,111 +1,113 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 Jan Hambrecht 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 KoShapeManager_p_h #define KoShapeManager_p_h #include "KoSelection.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" -#include "KoShapeManagerPaintingStrategy.h" #include class KoShapeManager; class KoCanvasBase; class KoShapeGroup; class KoShapePaintingContext; class QPainter; class Q_DECL_HIDDEN KoShapeManager::Private { public: Private(KoShapeManager *shapeManager, KoCanvasBase *c) : selection(new KoSelection()), canvas(c), tree(4, 2), - strategy(new KoShapeManagerPaintingStrategy(shapeManager)), q(shapeManager) { } ~Private() { delete selection; - delete strategy; } /** * Update the tree when there are shapes in m_aggregate4update. This is done so not all * updates to the tree are done when they are asked for but when they are needed. */ void updateTree(); + /** + * Returns whether the shape should be added to the RTree for collision and ROI + * detection. + */ + bool shapeUsedInRenderingTree(KoShape *shape); + /** * Recursively paints the given group shape to the specified painter * This is needed for filter effects on group shapes where the filter effect * applies to all the children of the group shape at once */ - void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); + static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); class DetectCollision { public: DetectCollision() {} void detect(KoRTree &tree, KoShape *s, int prevZIndex) { Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) { bool isChild = false; KoShapeContainer *parent = s->parent(); while (parent && !isChild) { if (parent == shape) isChild = true; parent = parent->parent(); } if (isChild) continue; if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex()) // Moving a shape will only make it collide with shapes below it. continue; if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape)) shapesWithCollisionDetection.append(shape); } } void fireSignals() { Q_FOREACH (KoShape *shape, shapesWithCollisionDetection) shape->priv()->shapeChanged(KoShape::CollisionDetected); } private: QList shapesWithCollisionDetection; }; QList shapes; QList additionalShapes; // these are shapes that are only handled for updates KoSelection *selection; KoCanvasBase *canvas; KoRTree tree; QSet aggregate4update; QHash shapeIndexesBeforeUpdate; - KoShapeManagerPaintingStrategy *strategy; KoShapeManager *q; }; #endif diff --git a/libs/flake/commands/KoShapeMoveCommand.cpp b/libs/flake/commands/KoShapeMoveCommand.cpp index 3ab9df6eaf..86c2867ac8 100644 --- a/libs/flake/commands/KoShapeMoveCommand.cpp +++ b/libs/flake/commands/KoShapeMoveCommand.cpp @@ -1,97 +1,91 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * 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 "KoShapeMoveCommand.h" #include #include #include "kis_command_ids.h" class Q_DECL_HIDDEN KoShapeMoveCommand::Private { public: QList shapes; QList previousPositions, newPositions; KoFlake::AnchorPosition anchor; }; KoShapeMoveCommand::KoShapeMoveCommand(const QList &shapes, QList &previousPositions, QList &newPositions, KoFlake::AnchorPosition anchor, KUndo2Command *parent) : KUndo2Command(parent), d(new Private()) { d->shapes = shapes; d->previousPositions = previousPositions; d->newPositions = newPositions; d->anchor = anchor; Q_ASSERT(d->shapes.count() == d->previousPositions.count()); Q_ASSERT(d->shapes.count() == d->newPositions.count()); setText(kundo2_i18n("Move shapes")); } KoShapeMoveCommand::~KoShapeMoveCommand() { delete d; } void KoShapeMoveCommand::redo() { KUndo2Command::redo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setAbsolutePosition(d->newPositions.at(i), d->anchor); d->shapes.at(i)->update(); } } void KoShapeMoveCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setAbsolutePosition(d->previousPositions.at(i), d->anchor); d->shapes.at(i)->update(); } } int KoShapeMoveCommand::id() const { return KisCommandUtils::MoveShapeId; } -/// update newPositions list with new postions. -void KoShapeMoveCommand::setNewPositions(QList newPositions) -{ - d->newPositions = newPositions; -} - bool KoShapeMoveCommand::mergeWith(const KUndo2Command *command) { const KoShapeMoveCommand *other = dynamic_cast(command); if (other->d->shapes != d->shapes || other->d->anchor != d->anchor) { return false; } d->newPositions = other->d->newPositions; return true; } diff --git a/libs/flake/commands/KoShapeMoveCommand.h b/libs/flake/commands/KoShapeMoveCommand.h index a6c196d5af..c619edc7dc 100644 --- a/libs/flake/commands/KoShapeMoveCommand.h +++ b/libs/flake/commands/KoShapeMoveCommand.h @@ -1,65 +1,62 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * 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 KOSHAPEMOVECOMMAND_H #define KOSHAPEMOVECOMMAND_H #include "kritaflake_export.h" #include #include #include #include class KoShape; /// The undo / redo command for shape moving. class KRITAFLAKE_EXPORT KoShapeMoveCommand : public KUndo2Command { public: /** * Constructor. * @param shapes the set of objects that are moved. * @param previousPositions the known set of previous positions for each of the objects. * this list naturally must have the same amount of items as the shapes set. * @param newPositions the new positions for the shapes. * this list naturally must have the same amount of items as the shapes set. * @param parent the parent command used for macro commands */ KoShapeMoveCommand(const QList &shapes, QList &previousPositions, QList &newPositions, KoFlake::AnchorPosition anchor = KoFlake::Center, KUndo2Command *parent = 0); ~KoShapeMoveCommand(); /// redo the command void redo() override; /// revert the actions done in redo void undo() override; int id() const override; bool mergeWith(const KUndo2Command *command) override; - /// update newPositions list with new postions. - void setNewPositions(QList newPositions); - private: class Private; Private * const d; }; #endif diff --git a/libs/flake/svg/KoShapePainter.cpp b/libs/flake/svg/KoShapePainter.cpp index 812cc2eabb..610df1f4e7 100644 --- a/libs/flake/svg/KoShapePainter.cpp +++ b/libs/flake/svg/KoShapePainter.cpp @@ -1,222 +1,217 @@ /* This file is part of the KDE project * * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2009 Thomas Zander * * 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 "KoShapePainter.h" #include "KoCanvasBase.h" #include "KoSelectedShapesProxySimple.h" #include "KoShapeManager.h" -#include "KoShapeManagerPaintingStrategy.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoShapeContainer.h" #include #include #include class SimpleCanvas : public KoCanvasBase { public: SimpleCanvas() : KoCanvasBase(0), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { } ~SimpleCanvas() override { } void gridSize(QPointF *offset, QSizeF *spacing) const override { *offset = QPointF(); *spacing = QSizeF(); }; bool snapToGrid() const override { return false; } void addCommand(KUndo2Command *) override { } KoShapeManager *shapeManager() const override { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const override { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) override { } KoToolProxy *toolProxy() const override { return 0; } KoViewConverter *viewConverter() const override { return 0; } QWidget *canvasWidget() override { return 0; } const QWidget *canvasWidget() const override { return 0; } KoUnit unit() const override { return KoUnit(KoUnit::Point); } void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; }; class Q_DECL_HIDDEN KoShapePainter::Private { public: Private() : canvas(new SimpleCanvas()) { } ~Private() { delete canvas; } SimpleCanvas * canvas; }; -KoShapePainter::KoShapePainter(KoShapeManagerPaintingStrategy *strategy) +KoShapePainter::KoShapePainter() : d(new Private()) { - if (strategy) { - strategy->setShapeManager(d->canvas->shapeManager()); - d->canvas->shapeManager()->setPaintingStrategy(strategy); - } } KoShapePainter::~KoShapePainter() { delete d; } void KoShapePainter::setShapes(const QList &shapes) { d->canvas->shapeManager()->setShapes(shapes, KoShapeManager::AddWithoutRepaint); } void KoShapePainter::paint(QPainter &painter, KoViewConverter &converter) { foreach (KoShape *shape, d->canvas->shapeManager()->shapes()) { shape->waitUntilReady(converter, false); } d->canvas->shapeManager()->paint(painter, converter, true); } void KoShapePainter::paint(QPainter &painter, const QRect &painterRect, const QRectF &documentRect) { if (documentRect.width() == 0.0f || documentRect.height() == 0.0f) return; KoViewConverter converter; // calculate the painter destination rectangle size in document coordinates QRectF paintBox = converter.viewToDocument(QRectF(QPointF(), painterRect.size())); // compute the zoom factor based on the bounding rects in document coordinates // so that the content fits into the image qreal zoomW = paintBox.width() / documentRect.width(); qreal zoomH = paintBox.height() / documentRect.height(); qreal zoom = qMin(zoomW, zoomH); // now set the zoom into the zoom handler used for painting the shape converter.setZoom(zoom); painter.save(); // initialize painter painter.setPen(QPen(Qt::NoPen)); painter.setBrush(Qt::NoBrush); painter.setRenderHint(QPainter::Antialiasing); painter.setClipRect(painterRect.adjusted(-1,-1,1,1)); // convert document rectangle to view coordinates QRectF zoomedBound = converter.documentToView(documentRect); // calculate offset between painter rectangle and converted document rectangle QPointF offset = QRectF(painterRect).center() - zoomedBound.center(); // center content in painter rectangle painter.translate(offset.x(), offset.y()); // finally paint the shapes paint(painter, converter); painter.restore(); } void KoShapePainter::paint(QImage &image) { if (image.isNull()) return; QPainter painter(&image); paint(painter, image.rect(), contentRect()); } QRectF KoShapePainter::contentRect() const { QRectF bound; foreach (KoShape *shape, d->canvas->shapeManager()->shapes()) { if (!shape->isVisible(true)) continue; if (dynamic_cast(shape)) continue; QRectF shapeRect = shape->boundingRect(); if (bound.isEmpty()) bound = shapeRect; else bound = bound.united(shapeRect); } return bound; } diff --git a/libs/flake/svg/KoShapePainter.h b/libs/flake/svg/KoShapePainter.h index 7c25677cb5..078361ffd0 100644 --- a/libs/flake/svg/KoShapePainter.h +++ b/libs/flake/svg/KoShapePainter.h @@ -1,84 +1,83 @@ /* This file is part of the KDE project * * Copyright (C) 2007 Jan Hambrecht * * 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 KOSHAPEPAINTER_H #define KOSHAPEPAINTER_H #include #include #include "kritaflake_export.h" class KoShape; class KoViewConverter; -class KoShapeManagerPaintingStrategy; class QPainter; class QImage; /** * A utility class to paint a subset of shapes onto a QPainter. * Notice that using setShapes repeatedly is very expensive, as it populates * the shapeManager and all its caching every time. If at all possible use * a shapeManager directly and avoid loosing the cache between usages. */ class KRITAFLAKE_EXPORT KoShapePainter { public: - explicit KoShapePainter(KoShapeManagerPaintingStrategy *strategy = 0); + explicit KoShapePainter(); ~KoShapePainter(); /** * Sets the shapes to be painted. * @param shapes the shapes to paint */ void setShapes(const QList &shapes); /** * Paints the shapes on the given painter and using the zoom handler. * @param painter the painter to paint on * @param converter the view converter defining the zoom to use */ void paint(QPainter &painter, KoViewConverter &converter); /** * Paints the shapes on the given painter. * The given document rectangle is painted to fit into the given painter rectangle. * * @param painter the painter to paint on * @param painterRect the destination rectangle on the painter * @param documentRect the document region to paint */ void paint(QPainter &painter, const QRect &painterRect, const QRectF &documentRect); /** * Paints shapes to the given image, so that all shapes fit onto it. * @param image the image to paint into * @return false if image is empty, else true */ void paint(QImage &image); /// Returns the bounding rect of the shapes to paint QRectF contentRect() const; private: class Private; Private * const d; }; #endif // KOSHAPEPAINTER_H diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h index 032d64e013..829791931a 100644 --- a/libs/flake/tests/MockShapes.h +++ b/libs/flake/tests/MockShapes.h @@ -1,226 +1,241 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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. */ #ifndef MOCKSHAPES_H #define MOCKSHAPES_H #include #include #include #include #include #include #include "KoShapeManager.h" #include "FlakeDebug.h" #include "KoSnapData.h" #include "KoUnit.h" class MockShape : public KoShape { public: MockShape() : paintedCount(0) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); //qDebug() << "Shape" << kBacktrace( 10 ); paintedCount++; } virtual void saveOdf(KoShapeSavingContext &) const {} virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) { return true; } int paintedCount; }; class MockContainer : public KoShapeContainer { public: MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {} void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); //qDebug() << "Container:" << kBacktrace( 10 ); paintedCount++; } virtual void saveOdf(KoShapeSavingContext &) const {} virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) { return true; } int paintedCount; }; class MockGroup : public KoShapeGroup { void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } }; class KoToolProxy; +class MockShapeController : public KoShapeBasedDocumentBase +{ +public: + void addShape(KoShape* shape) { + m_shapes.insert(shape); + if (m_shapeManager) { + m_shapeManager->addShape(shape); + } + } + void removeShape(KoShape* shape) { + m_shapes.remove(shape); + if (m_shapeManager) { + m_shapeManager->remove(shape); + } + } + bool contains(KoShape* shape) { + return m_shapes.contains(shape); + } + + void setShapeManager(KoShapeManager *shapeManager) { + m_shapeManager = shapeManager; + } + +private: + QSet m_shapes; + KoShapeManager *m_shapeManager = 0; +}; + class MockCanvas : public KoCanvasBase { public: MockCanvas(KoShapeBasedDocumentBase *aKoShapeBasedDocumentBase =0)//made for TestSnapStrategy.cpp : KoCanvasBase(aKoShapeBasedDocumentBase), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { + if (MockShapeController *controller = dynamic_cast(aKoShapeBasedDocumentBase)) { + controller->setShapeManager(m_shapeManager.data()); + } } ~MockCanvas() {} void setHorz(qreal pHorz){ m_horz = pHorz; } void setVert(qreal pVert){ m_vert = pVert; } void gridSize(QPointF *offset, QSizeF *spacing) const { Q_UNUSED(offset); spacing->setWidth(m_horz); spacing->setHeight(m_vert); } bool snapToGrid() const { return true; } void addCommand(KUndo2Command*) { } KoShapeManager *shapeManager() const { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) {} KoToolProxy * toolProxy() const { return 0; } KoViewConverter *viewConverter() const { return 0; } QWidget* canvasWidget() { return 0; } const QWidget* canvasWidget() const { return 0; } KoUnit unit() const { return KoUnit(KoUnit::Millimeter); } void updateInputMethodInfo() {} void setCursor(const QCursor &) {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; qreal m_horz; qreal m_vert; }; -class MockShapeController : public KoShapeBasedDocumentBase -{ -public: - void addShape(KoShape* shape) { - m_shapes.insert(shape); - } - void removeShape(KoShape* shape) { - m_shapes.remove(shape); - } - bool contains(KoShape* shape) { - return m_shapes.contains(shape); - } -private: - QSet m_shapes; -}; - class MockContainerModel : public KoShapeContainerModel { public: MockContainerModel() { resetCounts(); } /// reimplemented void add(KoShape *child) { m_children.append(child); // note that we explicitly do not check for duplicates here! } /// reimplemented void remove(KoShape *child) { m_children.removeAll(child); } /// reimplemented void setClipped(const KoShape *, bool) { } // ignored /// reimplemented bool isClipped(const KoShape *) const { return false; }// ignored /// reimplemented bool isChildLocked(const KoShape *child) const { return child->isGeometryProtected(); } /// reimplemented int count() const { return m_children.count(); } /// reimplemented QList shapes() const { return m_children; } /// reimplemented void containerChanged(KoShapeContainer *, KoShape::ChangeType) { m_containerChangedCalled++; } /// reimplemented void proposeMove(KoShape *, QPointF &) { m_proposeMoveCalled++; } /// reimplemented void childChanged(KoShape *, KoShape::ChangeType) { m_childChangedCalled++; } void setInheritsTransform(const KoShape *, bool) { } bool inheritsTransform(const KoShape *) const { return false; } int containerChangedCalled() const { return m_containerChangedCalled; } int childChangedCalled() const { return m_childChangedCalled; } int proposeMoveCalled() const { return m_proposeMoveCalled; } void resetCounts() { m_containerChangedCalled = 0; m_childChangedCalled = 0; m_proposeMoveCalled = 0; } private: QList m_children; int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled; }; #endif diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index 3c260027a1..b74c4e0a67 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,256 +1,325 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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 "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockContainer *container = new MockContainer(); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container); QCOMPARE(shape2->parent(), container); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, vc, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, vc, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); delete container; } void TestShapePainting::testPaintHiddenShape() { MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); MockContainer *top = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); delete top; } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList &list) : order(list) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override { order.append(this); MockShape::paint(painter, converter, paintcontext); } QList ℴ }; QList order; MockContainer *top = new MockContainer(); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); MockContainer *bottom = new MockContainer(); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); manager.addShape(bottom); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); order.clear(); MockContainer *root = new MockContainer(); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); delete top; delete bottom; delete root; } +#include +#include +#include +#include +#include "kis_debug.h" +void TestShapePainting::testGroupUngroup() +{ + MockShape *shape1 = new MockShape(); + MockShape *shape2 = new MockShape(); + shape1->setName("shape1"); + shape2->setName("shape2"); + + + QList groupedShapes = {shape1, shape2}; + + + MockShapeController controller; + MockCanvas canvas(&controller); + KoShapeManager *manager = canvas.shapeManager(); + + controller.addShape(shape1); + controller.addShape(shape2); + + QImage image(100, 100, QImage::Format_Mono); + QPainter painter(&image); + painter.setClipRect(image.rect()); + KoViewConverter vc; + + KoShapeGroup *group = 0; + + + for (int i = 0; i < 3; i++) { + { + group = new KoShapeGroup(); + group->setName("group"); + + KUndo2Command groupingCommand; + canvas.shapeController()->addShapeDirect(group, &groupingCommand); + new KoShapeGroupCommand(group, groupedShapes, false, true, true, &groupingCommand); + + groupingCommand.redo(); + + manager->paint(painter, vc, false); + + QCOMPARE(shape1->paintedCount, 2 * i + 1); + QCOMPARE(shape2->paintedCount, 2 * i + 1); + QCOMPARE(manager->shapes().size(), 3); + } + + { + KUndo2Command ungroupingCommand; + + new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); + canvas.shapeController()->removeShape(group, &ungroupingCommand); + + ungroupingCommand.redo(); + + manager->paint(painter, vc, false); + + QCOMPARE(shape1->paintedCount, 2 * i + 2); + QCOMPARE(shape2->paintedCount, 2 * i + 2); + QCOMPARE(manager->shapes().size(), 2); + + group = 0; + } + + } +} + QTEST_MAIN(TestShapePainting) diff --git a/libs/flake/tests/TestShapePainting.h b/libs/flake/tests/TestShapePainting.h index b3db8161d9..93f28a7674 100644 --- a/libs/flake/tests/TestShapePainting.h +++ b/libs/flake/tests/TestShapePainting.h @@ -1,35 +1,36 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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. */ #ifndef TESTSHAPEPAINT_H #define TESTSHAPEPAINT_H #include class TestShapePainting : public QObject { Q_OBJECT private Q_SLOTS: void testPaintShape(); void testPaintHiddenShape(); void testPaintOrder(); + void testGroupUngroup(); }; #endif diff --git a/libs/flake/tests/TestSvgParser.cpp b/libs/flake/tests/TestSvgParser.cpp index 18d01a21eb..940ac2d20c 100644 --- a/libs/flake/tests/TestSvgParser.cpp +++ b/libs/flake/tests/TestSvgParser.cpp @@ -1,3352 +1,3386 @@ /* * 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 "TestSvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_algebra_2d.h" struct SvgTester { SvgTester (const QString &data) : parser(&resourceManager) { QVERIFY(doc.setContent(data.toLatin1())); root = doc.documentElement(); parser.setXmlBaseDir("./"); savedData = data; //printf("%s", savedData.toLatin1().data()); } ~SvgTester () { qDeleteAll(shapes); } void run() { shapes = parser.parseSvg(root, &fragmentSize); } KoShape* findShape(const QString &name, KoShape *parent = 0) { if (parent && parent->name() == name) { return parent; } QList children; if (!parent) { children = shapes; } else { KoShapeContainer *cont = dynamic_cast(parent); if (cont) { children = cont->shapes(); } } Q_FOREACH (KoShape *shape, children) { KoShape *result = findShape(name, shape); if (result) { return result; } } return 0; } KoShapeGroup* findGroup(const QString &name) { KoShapeGroup *group = 0; KoShape *shape = findShape(name); if (shape) { group = dynamic_cast(shape); } return group; } KoDocumentResourceManager resourceManager; SvgParser parser; KoXmlDocument doc; KoXmlElement root; QSizeF fragmentSize; QList shapes; QString savedData; }; void TestSvgParser::testUnitPx() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5)); QCOMPARE(shape->absoluteTransformation(0), QTransform()); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20)); } void TestSvgParser::testUnitPxResolution() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10)); } void TestSvgParser::testUnitPt() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5)); QCOMPARE(shape->absoluteTransformation(0), QTransform()); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20)); } void TestSvgParser::testUnitIn() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,720,1440), 36)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(72, 72)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(720,1440)); } void TestSvgParser::testUnitPercentInitial() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 80, 80) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10)); } void TestSvgParser::testScalingViewport() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeet1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeet2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeetAlign() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 24) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,12)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,12)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,28)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,28)); } void TestSvgParser::testScalingViewportKeepSlice1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepSlice2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportResolution() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.25, 0.25)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(1,1)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(4,1)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(1,9)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(4,9)); } void TestSvgParser::testScalingViewportPercentInternal() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testParsePreserveAspectRatio() { { SvgUtil::PreserveAspectRatioParser p(" defer xMinYMax meet"); QCOMPARE(p.defer, true); QCOMPARE(p.mode, Qt::KeepAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Max); } { SvgUtil::PreserveAspectRatioParser p(" xMinYMid slice"); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::KeepAspectRatioByExpanding); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle); } { SvgUtil::PreserveAspectRatioParser p(" xmidYMid "); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::KeepAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Middle); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle); } { SvgUtil::PreserveAspectRatioParser p(" NoNe "); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } { SvgUtil::PreserveAspectRatioParser p("defer NoNe "); QCOMPARE(p.defer, true); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } { SvgUtil::PreserveAspectRatioParser p("sweet brown fox jumps over a nice svg file"); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } } #include "parsers/SvgTransformParser.h" void TestSvgParser::testParseTransform() { { QString str("translate(-111.0, 33) translate(-111.0, 33) matrix (1 1 0 0 1, 3), translate(1)" "scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)"); SvgTransformParser p(str); QCOMPARE(p.isValid(), true); } { // forget about one brace QString str("translate(-111.0, 33) translate(-111.0, 33 matrix (1 1 0 0 1, 3), translate(1)" "scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)"); SvgTransformParser p(str); QCOMPARE(p.isValid(), false); } { SvgTransformParser p("translate(100, 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50)); } { SvgTransformParser p("translate(100 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50)); } { SvgTransformParser p("translate(100)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 0)); } { SvgTransformParser p("scale(100, 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromScale(100, 50)); } { SvgTransformParser p("scale(100)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromScale(100, 100)); } { SvgTransformParser p("rotate(90 70 74.0)"); QCOMPARE(p.isValid(), true); QTransform t; t.rotate(90); t = QTransform::fromTranslate(-70, -74) * t * QTransform::fromTranslate(70, 74); qDebug() << ppVar(p.transform()); QCOMPARE(p.transform(), t); } } void TestSvgParser::testScalingViewportTransform() { /** * Note: 'transform' affects all the attributes of the *current* * element, while 'viewBox' affects only the decendants! */ const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(10, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(11,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(5,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(11,18)); } void TestSvgParser::testTransformNesting() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30)); } void TestSvgParser::testTransformNestingGroups() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30)); } void TestSvgParser::testTransformRotation1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(-20,0,20,10), 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(-20,10)); } void TestSvgParser::testTransformRotation2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(5,5,20,10), 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,15)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(25,5)); } #include "../../sdk/tests/qimage_test_util.h" #ifdef USE_ROUND_TRIP #include "SvgWriter.h" #include #include #endif struct SvgRenderTester : public SvgTester { SvgRenderTester(const QString &data) : SvgTester(data), m_fuzzyThreshold(0) { } void setFuzzyThreshold(int fuzzyThreshold) { m_fuzzyThreshold = fuzzyThreshold; } static void testRender(KoShape *shape, const QString &prefix, const QString &testName, const QSize canvasSize, int fuzzyThreshold = 0) { QImage canvas(canvasSize, QImage::Format_ARGB32); canvas.fill(0); KoViewConverter converter; QPainter painter(&canvas); KoShapePainter p; p.setShapes({shape}); painter.setClipRect(canvas.rect()); p.paint(painter, converter); QVERIFY(TestUtil::checkQImage(canvas, "svg_render", prefix, testName, fuzzyThreshold)); } void test_standard_30px_72ppi(const QString &testName, bool verifyGeometry = true, const QSize &canvasSize = QSize(30,30)) { parser.setResolution(QRectF(0, 0, 30, 30) /* px */, 72 /* ppi */); run(); #ifdef USE_CLONED_SHAPES { QList newShapes; Q_FOREACH (KoShape *shape, shapes) { KoShape *clonedShape = shape->cloneShape(); KIS_ASSERT(clonedShape); newShapes << clonedShape; } qDeleteAll(shapes); shapes = newShapes; } #endif /* USE_CLONED_SHAPES */ #ifdef USE_ROUND_TRIP const QSizeF sizeInPt(30,30); QBuffer writeBuf; writeBuf.open(QIODevice::WriteOnly); { SvgWriter writer(shapes, sizeInPt); writer.save(writeBuf); } QDomDocument prettyDoc; prettyDoc.setContent(savedData); qDebug(); printf("\n=== Original: ===\n\n%s\n", prettyDoc.toByteArray(4).data()); printf("\n=== Saved: ===\n\n%s\n", writeBuf.data().data()); qDebug(); QVERIFY(doc.setContent(writeBuf.data())); root = doc.documentElement(); run(); #endif /* USE_ROUND_TRIP */ KoShape *shape = findShape("testRect"); KIS_ASSERT(shape); if (verifyGeometry) { QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,5)); const QPointF bottomRight= shape->absolutePosition(KoFlake::BottomRight); const QPointF expectedBottomRight(15,25); if (KisAlgebra2D::norm(bottomRight - expectedBottomRight) > 0.0001 ) { QCOMPARE(bottomRight, expectedBottomRight); } } testRender(shape, "load", testName, canvasSize, m_fuzzyThreshold); } private: int m_fuzzyThreshold; }; void TestSvgParser::testRenderStrokeNone() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_none"); } void TestSvgParser::testRenderStrokeColorName() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorHex3() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorHex6() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorRgbValues() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorRgbPercent() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorCurrent() { const QString data = "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorNonexistentIri() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeWidth() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_width_2"); } void TestSvgParser::testRenderStrokeZeroWidth() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_none"); } void TestSvgParser::testRenderStrokeOpacity() { const QString data = "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("stroke_blue_0_3_opacity"); } void TestSvgParser::testRenderStrokeJointRound() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_join_round"); } void TestSvgParser::testRenderStrokeLinecap() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_linecap_round"); } void TestSvgParser::testRenderStrokeMiterLimit() { // TODO:seems like doesn't work!! qWarning() << "WARNING: Miter limit test is skipped!!!"; return; const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_miter_limit"); } void TestSvgParser::testRenderStrokeDashArrayEven() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_even"); } void TestSvgParser::testRenderStrokeDashArrayEvenOffset() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_even_offset"); } void TestSvgParser::testRenderStrokeDashArrayOdd() { // SVG 1.1: if the dasharray is odd, repeat it const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_odd"); } void TestSvgParser::testRenderStrokeDashArrayRelative() { // SVG 1.1: relative to view box // (40 x 50) * sqrt(2) => dash length = 5 px const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_relative"); } void TestSvgParser::testRenderFillDefault() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_black"); } void TestSvgParser::testRenderFillRuleNonZero() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_non_zero"); } void TestSvgParser::testRenderFillRuleEvenOdd() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_even_odd"); } void TestSvgParser::testRenderFillOpacity() { const QString data = "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_opacity_0_3"); } void TestSvgParser::testRenderDisplayAttribute() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } void TestSvgParser::testRenderVisibilityAttribute() { { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), true); } { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } } void TestSvgParser::testRenderVisibilityInheritance() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(false), true); QCOMPARE(shape->isVisible(true), false); } void TestSvgParser::testRenderDisplayInheritance() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(false), true); QEXPECT_FAIL("", "TODO: Fix 'display' attribute not to be inherited in shapes heirarchy!", Continue); QCOMPARE(shape->isVisible(true), true); } void TestSvgParser::testRenderStrokeWithInlineStyle() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_width_2"); } void TestSvgParser::testIccColor() { const QString data = "" "" " " " " " " "" ""; SvgRenderTester t (data); int numFetches = 0; t.parser.setFileFetcher( [&numFetches](const QString &name) { numFetches++; const QString fileName = TestUtil::fetchDataFileLazy(name); QFile file(fileName); KIS_ASSERT(file.exists()); file.open(QIODevice::ReadOnly); return file.readAll(); }); t.test_standard_30px_72ppi("stroke_blue_width_2"); QCOMPARE(numFetches, 1); } void TestSvgParser::testRenderFillLinearGradientRelativePercent() { const QString data = "" "" " " " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientRelativePortion() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientStopPortion() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientTransform() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_vertical"); } void TestSvgParser::testRenderFillLinearGradientTransformUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_vertical_in_user"); } void TestSvgParser::testRenderFillLinearGradientRotatedShape() { // DK: I'm not sure I fully understand if it is a correct transformation, // but inkscape opens the file in the same way... const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_shape_rotated", false); } void TestSvgParser::testRenderFillLinearGradientRotatedShapeUserCoord() { // DK: I'm not sure I fully understand if it is a correct transformation, // but inkscape opens the file in the same way... const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_shape_rotated_in_user", false); } void TestSvgParser::testRenderFillRadialGradient() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_gradient_radial"); } void TestSvgParser::testRenderFillRadialGradientUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_radial_in_user"); } void TestSvgParser::testRenderFillLinearGradientUserCoordPercent() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderStrokeLinearGradient() { const QString data = "" "" " " " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_gradient_dashed"); } QTransform rotateTransform(qreal degree, const QPointF ¢er) { QTransform rotate; rotate.rotate(degree); return QTransform::fromTranslate(-center.x(), -center.y()) * rotate * QTransform::fromTranslate(center.x(), center.y()); } QTransform viewTransform(const QRectF &src, const QRectF &dst) { return QTransform::fromTranslate(-src.x(), -src.y()) * QTransform::fromScale(dst.width() / src.width(), dst.height() / src.height()) * QTransform::fromTranslate(dst.x(), dst.y()); } QPainterPath bakeShape(const QPainterPath &path, const QTransform &bakeTransform, bool contentIsObb = false, const QRectF &shapeBoundingRect = QRectF(), bool contentIsViewBox = false, const QRectF &viewBoxRect= QRectF(), const QRectF &refRect = QRectF()) { const QTransform relativeToShape(shapeBoundingRect.width(), 0, 0, shapeBoundingRect.height(), shapeBoundingRect.x(), shapeBoundingRect.y()); QTransform newTransform = bakeTransform; if (contentIsObb) { newTransform = relativeToShape * newTransform; } if (contentIsViewBox) { newTransform = viewTransform(viewBoxRect, refRect) * newTransform; } return newTransform.map(path); } #include void renderBakedPath(QPainter &painter, const QPainterPath &bakedFillPath, const QTransform &bakedTransform, const QRect &shapeOutline, const QTransform &shapeTransform, const QRectF &referenceRect, bool contentIsObb, const QRectF &bakedShapeBoundingRect, bool referenceIsObb, const QTransform &patternTransform, QImage *stampResult) { painter.setTransform(QTransform()); painter.setPen(Qt::NoPen); QPainterPath shapeOutlinePath; shapeOutlinePath.addRect(shapeOutline); KoBakedShapeRenderer renderer( shapeOutlinePath, shapeTransform, bakedTransform, referenceRect, contentIsObb, bakedShapeBoundingRect, referenceIsObb, patternTransform); QPainter *patchPainter = renderer.bakeShapePainter(); patchPainter->fillPath(bakedFillPath, Qt::blue); patchPainter->end(); renderer.renderShape(painter); if (stampResult) { *stampResult = renderer.patchImage(); } } void TestSvgParser::testManualRenderPattern_ContentUser_RefObb() { const QRectF referenceRect(0, 0, 1.0, 0.5); QPainterPath fillPath; fillPath.addRect(QRect(2, 2, 6, 6)); fillPath.addRect(QRect(8, 4, 3, 2)); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform); QRect shape1OutlineRect(0,0,10,20); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, QRectF(), true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, QRectF(), true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentObb_RefObb() { const QRectF referenceRect(0.3, 0.3, 0.4, 0.4); QPainterPath fillPath; fillPath.addRect(QRectF(0.4, 0.4, 0.2, 0.2)); fillPath.addRect(QRectF(0.6, 0.5, 0.1, 0.1)); fillPath.addRect(QRectF(0.3, 0.4, 0.1, 0.1)); const QRect bakedShapeRect(2,2,10,10); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); // Round trip to the same shape fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, bakedShapeRect, bakedTransform, referenceRect, true, bakedShapeRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill1")); // Move to a different shape QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, bakedShapeRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentUser_RefUser() { const QRectF referenceRect(5, 2, 8, 8); QPainterPath fillPath; fillPath.addRect(QRect(2, 2, 6, 6)); fillPath.addRect(QRect(8, 4, 3, 2)); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform); QRect shape1OutlineRect(0,0,10,20); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, QRectF(), false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, QRectF(), false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill2")); } void TestSvgParser::testManualRenderPattern_ContentObb_RefObb_Transform_Rotate() { const QRectF referenceRect(0.0, 0.0, 0.4, 0.2); QPainterPath fillPath; fillPath.addRect(QRectF(0.0, 0.0, 0.5, 0.1)); fillPath.addRect(QRectF(0.0, 0.1, 0.1, 0.1)); const QRect bakedShapeRect(2,1,10,10); QTransform bakedTransform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(10,10); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); QTransform patternTransform; patternTransform.rotate(90); patternTransform = patternTransform * QTransform::fromTranslate(0.5, 0.0); // Round trip to the same shape fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, bakedShapeRect, bakedTransform, referenceRect, true, bakedShapeRect, true, patternTransform, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, bakedShapeRect, true, patternTransform, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill2")); } void TestSvgParser::testManualRenderPattern_ContentView_RefObb() { const QRectF referenceRect(0, 0, 0.5, 1.0/3.0); const QRectF viewRect(10,10,60,90); QPainterPath fillPath; fillPath.addRect(QRect(30, 10, 20, 60)); fillPath.addRect(QRect(50, 40, 20, 30)); QRect shape1OutlineRect(10,20,40,120); QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, shape1OutlineRect, true, viewRect, referenceRect); QImage stampResult; QImage fillResult(QSize(220,160), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, true, shape1OutlineRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill1")); QRect shape2OutlineRect(20,10,60,90); QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, shape1OutlineRect, true, rotateTransform(90, QPointF(0, 1.0 / 3.0)), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentView_RefUser() { const QRectF referenceRect(60, 0, 30, 20); const QRectF viewRect(10,10,60,90); QPainterPath fillPath; fillPath.addRect(QRect(30, 10, 20, 60)); fillPath.addRect(QRect(50, 40, 20, 30)); QRect shape1OutlineRect(10,20,40,120); QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, false, shape1OutlineRect, true, viewRect, referenceRect); QImage stampResult; QImage fillResult(QSize(220,160), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, shape1OutlineRect, false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill1")); QRect shape2OutlineRect(20,10,60,90); QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50); QTransform patternTransform2 = rotateTransform(90, QPointF()) * QTransform::fromTranslate(40, 10); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, shape1OutlineRect, false, patternTransform2, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill2")); } void TestSvgParser::testRenderPattern_r_User_c_User() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_InfiniteRecursionWhenInherited() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base_black", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_View() { const QString data = "" "" " " " " " " // y is changed to 39 from 40 to fix a rounding issue! " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_Obb() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_View_Rotated() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_rotated", false, QSize(220, 160)); } void TestSvgParser::testRenderPattern_r_Obb_c_View_Rotated() { /** * This test case differs from any application existent in the world :( * * Chrome and Firefox premultiply the patternTransform instead of doing post- * multiplication. Photoshop forgets to multiply the reference rect on it. * * So... */ const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_rotated_odd", false, QSize(220, 160)); } #include #include #include #include void TestSvgParser::testKoClipPathRendering() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); QPainterPath path2; path2.addRect(QRect(10,10,15,15)); QPainterPath clipPath1; clipPath1.addRect(QRect(10, 0, 10, 30)); QPainterPath clipPath2; clipPath2.moveTo(0,7); clipPath2.lineTo(30,7); clipPath2.lineTo(15,30); clipPath2.lineTo(0,7); QScopedPointer shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); QScopedPointer shape2(KoPathShape::createShapeFromPainterPath(path2)); shape2->setBackground(QSharedPointer(new KoColorBackground(Qt::red))); QScopedPointer clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1)); KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse); koClipPath1->setClipRule(Qt::WindingFill); shape1->setClipPath(koClipPath1); QScopedPointer group(new KoShapeGroup()); { QList shapes({shape1.take(), shape2.take()}); KoShapeGroupCommand cmd(group.data(), shapes, false, true, false); cmd.redo(); } QScopedPointer clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2)); KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::UserSpaceOnUse); koClipPath2->setClipRule(Qt::WindingFill); group->setClipPath(koClipPath2); SvgRenderTester::testRender(group.take(), "load", "clip_render_test", QSize(30,30)); } void TestSvgParser::testKoClipPathRelativeRendering() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); QPainterPath path2; path2.addRect(QRect(10,10,15,15)); QPainterPath clipPath1; clipPath1.addRect(QRect(10, 0, 10, 30)); QPainterPath clipPath2; clipPath2.moveTo(0,0); clipPath2.lineTo(1,0); clipPath2.lineTo(0.5,1); clipPath2.lineTo(0,0); QScopedPointer shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); QScopedPointer shape2(KoPathShape::createShapeFromPainterPath(path2)); shape2->setBackground(QSharedPointer(new KoColorBackground(Qt::red))); QScopedPointer clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1)); KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse); koClipPath1->setClipRule(Qt::WindingFill); shape1->setClipPath(koClipPath1); QScopedPointer group(new KoShapeGroup()); { QList shapes({shape1.take(), shape2.take()}); KoShapeGroupCommand cmd(group.data(), shapes, false, true, false); cmd.redo(); } QScopedPointer clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2)); KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::ObjectBoundingBox); koClipPath2->setClipRule(Qt::WindingFill); group->setClipPath(koClipPath2); SvgRenderTester::testRender(group.take(), "load", "relative_clip_render_test", QSize(30,30)); } void TestSvgParser::testRenderClipPath_User() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_render_test", false); } void TestSvgParser::testRenderClipPath_Obb() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("relative_clip_render_test", false); } void TestSvgParser::testRenderClipPath_Obb_Transform() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_render_test_rotated", false); } void TestSvgParser::testRenderClipMask_Obb() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } void TestSvgParser::testRenderClipMask_User_Clip_Obb() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } void TestSvgParser::testRenderClipMask_User_Clip_User() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } QByteArray fileFetcherFunc(const QString &name) { const QString fileName = TestUtil::fetchDataFileLazy(name); QFile file(fileName); KIS_ASSERT(file.exists()); file.open(QIODevice::ReadOnly); return file.readAll(); } void TestSvgParser::testRenderImage_AspectDefault() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_default", false); } void TestSvgParser::testRenderImage_AspectNone() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_none", false); } void TestSvgParser::testRenderImage_AspectMeet() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_meet", false); } void TestSvgParser::testRectShapeRoundUniformX() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_5", false); } void TestSvgParser::testRectShapeRoundUniformY() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_5", false); } void TestSvgParser::testRectShapeRoundXY() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_10", false); } void TestSvgParser::testRectShapeRoundXYOverflow() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_10", false); } void TestSvgParser::testCircleShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("circle", false); } void TestSvgParser::testEllipseShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("ellipse", false); } void TestSvgParser::testLineShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("line", false); } void TestSvgParser::testPolylineShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polyline", false); } void TestSvgParser::testPolygonShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polygon", false); } void TestSvgParser::testPathShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polygon", false); } void TestSvgParser::testDefsHidden() { const QString data = "" "" " " " " " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("test_defs_hidden", false); } void TestSvgParser::testDefsUseInheritance() { const QString data = "" "" " " " " " " "" /** * NOTES: * 1) width/height attributes for are not implemented yet * 2) x and y are summed up * 3) stroke="white" is overridden by the original templated object * 4) fill="green" attribute from is not inherited */ "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("defs_use_inheritance", false); } void TestSvgParser::testUseWithoutDefs() { const QString data = "" // technical rect for rendering "" "" " " "" /** * NOTES: * 1) width/height attributes for are not implemented yet * 2) x and y are summed up * 3) stroke="white" is overridden by the original templated object * 4) fill="green" attribute from is not inherited */ "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("use_without_defs", false); } void TestSvgParser::testMarkersAutoOrientation() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers", false); } void TestSvgParser::testMarkersAutoOrientationScaled() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_scaled", false); } void TestSvgParser::testMarkersAutoOrientationScaledUserCoordinates() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_user_coordinates", false); } void TestSvgParser::testMarkersCustomOrientation() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_custom_orientation", false); } void TestSvgParser::testMarkersDifferent() { const QString data = "" "" " " " " " " "" "" " " " " " " "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_different", false); } void TestSvgParser::testMarkersFillAsShape() { const QString data = "" "" " " " " " " " " " " " " " " " " " " "" "" //" d=\"M5,15 L25,15\"/>" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_scaled_fill_as_shape", false); } void TestSvgParser::testMarkersOnClosedPath() { const QString data = "" "" " " " " " " "" "" " " " " " " "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_on_closed_path", false); } void TestSvgParser::testGradientRecoveringTrasnform() { // used for experimenting purposes only! QImage image(100,100,QImage::Format_ARGB32); image.fill(0); QPainter painter(&image); painter.setPen(QPen(Qt::black, 0)); QLinearGradient gradient(0, 0.5, 1, 0.5); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); //QLinearGradient gradient(0, 50, 100, 50); //gradient.setCoordinateMode(QGradient::LogicalMode); gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(1.0, Qt::blue); QTransform gradientTrasnform; gradientTrasnform.shear(0.2, 0); { QBrush brush(gradient); brush.setTransform(gradientTrasnform); painter.setBrush(brush); } QRect mainShape(3,3,94,94); painter.drawRect(mainShape); QTransform gradientToUser(mainShape.width(), 0, 0, mainShape.height(), mainShape.x(), mainShape.y()); QRect smallShape(0,0,20,20); QTransform smallShapeTransform; { smallShapeTransform = QTransform::fromTranslate(-smallShape.center().x(), -smallShape.center().y()); QTransform r; r.rotate(90); smallShapeTransform *= r; smallShapeTransform *= QTransform::fromTranslate(mainShape.center().x(), mainShape.center().y()); } { gradient.setCoordinateMode(QGradient::LogicalMode); QBrush brush(gradient); brush.setTransform(gradientTrasnform * gradientToUser * smallShapeTransform.inverted()); painter.setBrush(brush); painter.setPen(Qt::NoPen); } painter.setTransform(smallShapeTransform); painter.drawRect(smallShape); //image.save("gradient_recovering_transform.png"); } void TestSvgParser::testMarkersAngularUnits() { const QString data = "" // start "" " " " " " " "" // end "" " " " " " " "" // mid "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_angular_units", false); } #include "KoParameterShape.h" void TestSvgParser::testSodipodiArcShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_closed_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testSodipodiArcShapeOpen() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_open_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testKritaChordShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_chord_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } +void TestSvgParser::testSodipodiChordShape() +{ + const QString data = + "" + + "" + " d=\" some weird unparsable text \" />" + + ""; + + SvgRenderTester t (data); + + t.test_standard_30px_72ppi("sodipodi_chord_arc", false); + + KoShape *shape = t.findShape("testRect"); + QVERIFY(dynamic_cast(shape)); +} + QTEST_MAIN(TestSvgParser) diff --git a/libs/flake/tests/TestSvgParser.h b/libs/flake/tests/TestSvgParser.h index 965c393b58..34351c3422 100644 --- a/libs/flake/tests/TestSvgParser.h +++ b/libs/flake/tests/TestSvgParser.h @@ -1,172 +1,173 @@ /* * 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. */ #ifndef TESTSVGPARSER_H #define TESTSVGPARSER_H #include class TestSvgParser : public QObject { Q_OBJECT private Q_SLOTS: void testUnitPx(); void testUnitPxResolution(); void testUnitPt(); void testUnitIn(); void testUnitPercentInitial(); void testScalingViewport(); void testScalingViewportKeepMeet1(); void testScalingViewportKeepMeet2(); void testScalingViewportKeepMeetAlign(); void testScalingViewportKeepSlice1(); void testScalingViewportKeepSlice2(); void testScalingViewportResolution(); void testScalingViewportPercentInternal(); void testParsePreserveAspectRatio(); void testParseTransform(); void testScalingViewportTransform(); void testTransformNesting(); void testTransformNestingGroups(); void testTransformRotation1(); void testTransformRotation2(); void testRenderStrokeNone(); void testRenderStrokeColorName(); void testRenderStrokeColorHex3(); void testRenderStrokeColorHex6(); void testRenderStrokeColorRgbValues(); void testRenderStrokeColorRgbPercent(); void testRenderStrokeColorCurrent(); void testRenderStrokeColorNonexistentIri(); void testRenderStrokeWidth(); void testRenderStrokeZeroWidth(); void testRenderStrokeOpacity(); void testRenderStrokeJointRound(); void testRenderStrokeLinecap(); void testRenderStrokeMiterLimit(); void testRenderStrokeDashArrayEven(); void testRenderStrokeDashArrayEvenOffset(); void testRenderStrokeDashArrayOdd(); void testRenderStrokeDashArrayRelative(); void testRenderFillDefault(); void testRenderFillRuleNonZero(); void testRenderFillRuleEvenOdd(); void testRenderFillOpacity(); void testRenderDisplayAttribute(); void testRenderVisibilityAttribute(); void testRenderVisibilityInheritance(); void testRenderDisplayInheritance(); void testRenderStrokeWithInlineStyle(); void testIccColor(); void testRenderFillLinearGradientRelativePercent(); void testRenderFillLinearGradientRelativePortion(); void testRenderFillLinearGradientUserCoord(); void testRenderFillLinearGradientStopPortion(); void testRenderFillLinearGradientTransform(); void testRenderFillLinearGradientTransformUserCoord(); void testRenderFillLinearGradientRotatedShape(); void testRenderFillLinearGradientRotatedShapeUserCoord(); void testRenderFillRadialGradient(); void testRenderFillRadialGradientUserCoord(); void testRenderFillLinearGradientUserCoordPercent(); void testRenderStrokeLinearGradient(); void testManualRenderPattern_ContentUser_RefObb(); void testManualRenderPattern_ContentObb_RefObb(); void testManualRenderPattern_ContentUser_RefUser(); void testManualRenderPattern_ContentObb_RefObb_Transform_Rotate(); void testManualRenderPattern_ContentView_RefObb(); void testManualRenderPattern_ContentView_RefUser(); void testRenderPattern_r_User_c_User(); void testRenderPattern_InfiniteRecursionWhenInherited(); void testRenderPattern_r_User_c_View(); void testRenderPattern_r_User_c_Obb(); void testRenderPattern_r_User_c_View_Rotated(); void testRenderPattern_r_Obb_c_View_Rotated(); void testKoClipPathRendering(); void testKoClipPathRelativeRendering(); void testRenderClipPath_User(); void testRenderClipPath_Obb(); void testRenderClipPath_Obb_Transform(); void testRenderClipMask_Obb(); void testRenderClipMask_User_Clip_Obb(); void testRenderClipMask_User_Clip_User(); void testRenderImage_AspectDefault(); void testRenderImage_AspectNone(); void testRenderImage_AspectMeet(); void testRectShapeRoundUniformX(); void testRectShapeRoundUniformY(); void testRectShapeRoundXY(); void testRectShapeRoundXYOverflow(); void testCircleShape(); void testEllipseShape(); void testLineShape(); void testPolylineShape(); void testPolygonShape(); void testPathShape(); void testDefsHidden(); void testDefsUseInheritance(); void testUseWithoutDefs(); void testMarkersAutoOrientation(); void testMarkersAutoOrientationScaled(); void testMarkersAutoOrientationScaledUserCoordinates(); void testMarkersCustomOrientation(); void testMarkersDifferent(); void testMarkersFillAsShape(); void testGradientRecoveringTrasnform(); void testMarkersOnClosedPath(); void testMarkersAngularUnits(); void testSodipodiArcShape(); void testSodipodiArcShapeOpen(); void testKritaChordShape(); + void testSodipodiChordShape(); }; #endif // TESTSVGPARSER_H diff --git a/libs/flake/tools/KoInteractionTool.cpp b/libs/flake/tools/KoInteractionTool.cpp index d5d1c89b1c..ee0971ed59 100644 --- a/libs/flake/tools/KoInteractionTool.cpp +++ b/libs/flake/tools/KoInteractionTool.cpp @@ -1,216 +1,218 @@ /* This file is part of the KDE project Copyright (C) 2006-2007, 2010 Thomas Zander 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 "KoInteractionTool.h" #include "KoInteractionTool_p.h" #include "KoToolBase_p.h" #include "KoPointerEvent.h" #include "KoCanvasBase.h" #include "kis_global.h" #include "kis_assert.h" KoInteractionTool::KoInteractionTool(KoCanvasBase *canvas) : KoToolBase(*(new KoInteractionToolPrivate(this, canvas))) { } KoInteractionTool::~KoInteractionTool() { } void KoInteractionTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->paint(painter, converter); } else { Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { // skip the rest of rendering if the factory asks for it if (factory->paintOnHover(painter, converter)) break; } } } void KoInteractionTool::mousePressEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); if (d->currentStrategy) { // possible if the user presses an extra mouse button cancelCurrentStrategy(); return; } d->currentStrategy = createStrategyBase(event); if (d->currentStrategy == 0) event->ignore(); } void KoInteractionTool::mouseMoveEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); d->lastPoint = event->point; if (d->currentStrategy) d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); else { Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { // skip the rest of rendering if the factory asks for it if (factory->hoverEvent(event)) return; } event->ignore(); } } void KoInteractionTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = d->currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); delete d->currentStrategy; d->currentStrategy = 0; repaintDecorations(); } else event->ignore(); } void KoInteractionTool::keyPressEvent(QKeyEvent *event) { Q_D(KoInteractionTool); event->ignore(); if (d->currentStrategy && (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift || event->key() == Qt::Key_Meta)) { d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); event->accept(); } } void KoInteractionTool::keyReleaseEvent(QKeyEvent *event) { Q_D(KoInteractionTool); + if (!d->currentStrategy) { KoToolBase::keyReleaseEvent(event); + return; } if (event->key() == Qt::Key_Escape) { cancelCurrentStrategy(); event->accept(); } else if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift || event->key() == Qt::Key_Meta) { d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); } } KoInteractionStrategy *KoInteractionTool::currentStrategy() { Q_D(KoInteractionTool); return d->currentStrategy; } void KoInteractionTool::cancelCurrentStrategy() { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->cancelInteraction(); delete d->currentStrategy; d->currentStrategy = 0; } } KoInteractionStrategy *KoInteractionTool::createStrategyBase(KoPointerEvent *event) { Q_D(KoInteractionTool); Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { KoInteractionStrategy *strategy = factory->createStrategy(event); if (strategy) { return strategy; } } return createStrategy(event); } void KoInteractionTool::addInteractionFactory(KoInteractionStrategyFactory *factory) { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { KIS_SAFE_ASSERT_RECOVER_RETURN(f->id() != factory->id()); } d->interactionFactories.append(toQShared(factory)); qSort(d->interactionFactories.begin(), d->interactionFactories.end(), KoInteractionStrategyFactory::compareLess); } void KoInteractionTool::removeInteractionFactory(const QString &id) { Q_D(KoInteractionTool); QList::iterator it = d->interactionFactories.begin(); while (it != d->interactionFactories.end()) { if ((*it)->id() == id) { it = d->interactionFactories.erase(it); } else { ++it; } } } bool KoInteractionTool::hasInteractioFactory(const QString &id) { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { if (f->id() == id) { return true; } } return false; } bool KoInteractionTool::tryUseCustomCursor() { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { if (f->tryUseCustomCursor()) { return true; } } return false; } KoInteractionTool::KoInteractionTool(KoInteractionToolPrivate &dd) : KoToolBase(dd) { } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 45aa0dcc69..e8024eac02 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,700 +1,708 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * 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_shape_layer.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 #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 "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "kis_shape_layer_paste.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); - q->shapeManager()->addShape(child); } void remove(KoShape *child) override { - q->shapeManager()->remove(child); SimpleShapeContainerModel::remove(child); } + void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { + q->shapeManager()->addShape(shape); + SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); + } + + void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { + q->shapeManager()->remove(shape); + SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); + } + private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvas * canvas; KoShapeBasedDocumentBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(controller); Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { addShape(shape->cloneShape()); } } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); m_d->canvas = new KisShapeLayerCanvas(this, image()); m_d->canvas->setProjection(m_d->paintDevice); m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(const QPointF&)), SLOT(slotMoveShapes(const QPointF&))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList prevPos; QList newPos; QList shapes; Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (!dynamic_cast(shape)) { shapes.append(shape); } } Q_FOREACH (KoShape* shape, shapes) { QPointF pos = shape->position(); prevPos << pos; newPos << pos + diff; } KoShapeMoveCommand cmd(shapes, prevPos, newPos); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { KisExternalLayer::setVisible(visible, isLoading); } #include "SvgWriter.h" #include "SvgParser.h" #include bool KisShapeLayer::saveLayer(KoStore * store) const { if (!store->open("content.svg")) { return false; } // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); QList shapes = this->shapes(); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes, sizeInPt); writer.save(storeDev); if (!store->close()) { return false; } #if 0 KoOdfWriteStore odfStore(store); KoXmlWriter* manifestWriter = odfStore.manifestWriter("application/vnd.oasis.opendocument.graphics"); KoEmbeddedDocumentSaver embeddedSaver; KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver); if (!store->open("content.xml")) return false; KoStoreDevice storeDev(store); KoXmlWriter * docWriter = KoOdfWriteStore::createOasisXmlWriter(&storeDev, "office:document-content"); // for office:master-styles // QTemporaryFile masterStyles; // masterStyles.open(); // KoXmlWriter masterStylesTmpWriter(&masterStyles, 1); KoPageLayout page; page.format = KoPageFormat::defaultFormat(); QRectF rc = boundingRect(); page.width = rc.width(); page.height = rc.height(); if (page.width > page.height) { page.orientation = KoPageFormat::Landscape; } else { page.orientation = KoPageFormat::Portrait; } KoGenStyles mainStyles; KoGenStyle pageLayout = page.saveOdf(); QString layoutName = mainStyles.insert(pageLayout, "PL"); KoGenStyle masterPage(KoGenStyle::MasterPageStyle); masterPage.addAttribute("style:page-layout-name", layoutName); mainStyles.insert(masterPage, "Default", KoGenStyles::DontAddNumberToName); QTemporaryFile contentTmpFile; contentTmpFile.open(); KoXmlWriter contentTmpWriter(&contentTmpFile, 1); contentTmpWriter.startElement("office:body"); contentTmpWriter.startElement("office:drawing"); KoShapeSavingContext shapeContext(contentTmpWriter, mainStyles, documentContext.embeddedSaver); shapeContext.xmlWriter().startElement("draw:page"); shapeContext.xmlWriter().addAttribute("draw:name", ""); KoElementReference elementRef("page", 1); elementRef.saveOdf(&shapeContext.xmlWriter(), KoElementReference::DrawId); shapeContext.xmlWriter().addAttribute("draw:master-page-name", "Default"); saveOdf(shapeContext); shapeContext.xmlWriter().endElement(); // draw:page contentTmpWriter.endElement(); // office:drawing contentTmpWriter.endElement(); // office:body mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, docWriter); // And now we can copy over the contents from the tempfile to the real one contentTmpFile.seek(0); docWriter->addCompleteElement(&contentTmpFile); docWriter->endElement(); // Root element docWriter->endDocument(); delete docWriter; if (!store->close()) return false; embeddedSaver.saveEmbeddedDocuments(documentContext); manifestWriter->addManifestEntry("content.xml", "text/xml"); if (! mainStyles.saveOdfStylesDotXml(store, manifestWriter)) { return false; } manifestWriter->addManifestEntry("settings.xml", "text/xml"); if (! shapeContext.saveDataCenter(documentContext.odfStore.store(), documentContext.odfStore.manifestWriter())) return false; // Write out manifest file if (!odfStore.closeManifestWriter()) { dbgImage << "closing manifestWriter failed"; return false; } #endif return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QXmlStreamReader reader(device); reader.setNamespaceProcessing(false); QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->paintDevice->clear(); QList shapes = m_d->canvas->shapeManager()->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = m_d->canvas->shapeManager()->shapes(); if(shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); // this code won't work if there are shapes, that inherit the transformation from the parent container. // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that. Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); if (dynamic_cast(shape)) { newTransformations.append(oldTransform); } else { QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform*oldTransform); } KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } diff --git a/libs/ui/flake/kis_shape_selection_model.cpp b/libs/ui/flake/kis_shape_selection_model.cpp index a6105d6911..3225b05b77 100644 --- a/libs/ui/flake/kis_shape_selection_model.cpp +++ b/libs/ui/flake/kis_shape_selection_model.cpp @@ -1,191 +1,193 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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_shape_selection_model.h" #include "kis_debug.h" #include #include #include #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_image.h" #include "kis_update_selection_job.h" KisShapeSelectionModel::KisShapeSelectionModel(KisImageWSP image, KisSelectionWSP selection, KisShapeSelection* shapeSelection) : m_image(image) , m_parentSelection(selection) , m_shapeSelection(shapeSelection) , m_updateSignalCompressor(new KisSignalCompressor(300, KisSignalCompressor::POSTPONE, this)) , m_updatesEnabled(true) , m_fullUpdateRequested(false) { connect(m_updateSignalCompressor, SIGNAL(timeout()), SLOT(startUpdateJob())); } KisShapeSelectionModel::~KisShapeSelectionModel() { m_image = 0; m_parentSelection = 0; } void KisShapeSelectionModel::requestUpdate(const QRect &updateRect) { m_shapeSelection->recalculateOutlineCache(); if (m_updatesEnabled) { m_fullUpdateRequested |= updateRect.isEmpty(); m_updateRect = !m_fullUpdateRequested ? m_updateRect | updateRect : QRect(); m_updateSignalCompressor->start(); } } void KisShapeSelectionModel::startUpdateJob() { if (m_image.isValid()) { m_image->addSpontaneousJob(new KisUpdateSelectionJob(m_parentSelection, m_updateRect)); } m_updateRect = QRect(); m_fullUpdateRequested = false; } void KisShapeSelectionModel::add(KoShape *child) { if (!m_shapeSelection) return; if (m_shapeMap.contains(child)) return; child->setStroke(KoShapeStrokeModelSP()); child->setBackground( QSharedPointer(0)); m_shapeMap.insert(child, child->boundingRect()); m_shapeSelection->shapeManager()->addShape(child); QRect updateRect = child->boundingRect().toAlignedRect(); if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); updateRect = matrix.mapRect(updateRect); } if (m_shapeMap.count() == 1) { // The shape is the first one, so the shape selection just got created // Pixel selection provides no longer the datamanager of the selection // so update the whole selection requestUpdate(QRect()); } else { requestUpdate(updateRect); } } void KisShapeSelectionModel::remove(KoShape *child) { if (!m_shapeMap.contains(child)) return; QRect updateRect = child->boundingRect().toAlignedRect(); m_shapeMap.remove(child); if (m_shapeSelection) { m_shapeSelection->shapeManager()->remove(child); } if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); updateRect = matrix.mapRect(updateRect); if (m_shapeSelection) { // No m_shapeSelection indicates the selection is being deleted requestUpdate(updateRect); } } } void KisShapeSelectionModel::setUpdatesEnabled(bool enabled) { m_updatesEnabled = enabled; } bool KisShapeSelectionModel::updatesEnabled() const { return m_updatesEnabled; } void KisShapeSelectionModel::setClipped(const KoShape *child, bool clipping) { Q_UNUSED(child); Q_UNUSED(clipping); } bool KisShapeSelectionModel::isClipped(const KoShape *child) const { Q_UNUSED(child); return false; } void KisShapeSelectionModel::setInheritsTransform(const KoShape *shape, bool inherit) { Q_UNUSED(shape); Q_UNUSED(inherit); } bool KisShapeSelectionModel::inheritsTransform(const KoShape *shape) const { Q_UNUSED(shape); return false; } int KisShapeSelectionModel::count() const { return m_shapeMap.count(); } QList KisShapeSelectionModel::shapes() const { return QList(m_shapeMap.keys()); } void KisShapeSelectionModel::containerChanged(KoShapeContainer *, KoShape::ChangeType) { } void KisShapeSelectionModel::childChanged(KoShape * child, KoShape::ChangeType type) { if (!m_shapeSelection) return; + + // TODO: check if still needed if (type == KoShape::ParentChanged) return; QRectF changedRect = m_shapeMap[child]; changedRect = changedRect.united(child->boundingRect()); m_shapeMap[child] = child->boundingRect(); if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); changedRect = matrix.mapRect(changedRect); } requestUpdate(changedRect.toAlignedRect()); } bool KisShapeSelectionModel::isChildLocked(const KoShape *child) const { return child->isGeometryProtected() || child->parent()->isGeometryProtected(); } void KisShapeSelectionModel::setShapeSelection(KisShapeSelection* selection) { m_shapeSelection = selection; } diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp index 8930aaccd6..df50733fcc 100644 --- a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp +++ b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp @@ -1,569 +1,546 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006,2008 Jan Hambrecht Copyright (C) 2009 Thomas Zander 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 "EllipseShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include EllipseShape::EllipseShape() : m_startAngle(0) , m_endAngle(0) , m_kindAngle(M_PI) , m_type(Arc) { QList handles; handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(0, 50)); setHandles(handles); QSizeF size(100, 100); m_radii = QPointF(size.width() / 2.0, size.height() / 2.0); m_center = QPointF(m_radii.x(), m_radii.y()); updatePath(size); } EllipseShape::EllipseShape(const EllipseShape &rhs) : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), m_startAngle(rhs.m_startAngle), m_endAngle(rhs.m_endAngle), m_kindAngle(rhs.m_kindAngle), m_center(rhs.m_center), m_radii(rhs.m_radii), m_type(rhs.m_type) { } EllipseShape::~EllipseShape() { } KoShape *EllipseShape::cloneShape() const { return new EllipseShape(*this); } void EllipseShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:ellipse"); saveOdfAttributes(context, OdfAllAttributes); switch (m_type) { case Arc: context.xmlWriter().addAttribute("draw:kind", sweepAngle() == 360 ? "full" : "arc"); break; case Pie: context.xmlWriter().addAttribute("draw:kind", "section"); break; case Chord: context.xmlWriter().addAttribute("draw:kind", "cut"); break; default: context.xmlWriter().addAttribute("draw:kind", "full"); } if (m_type != Arc || sweepAngle() != 360) { context.xmlWriter().addAttribute("draw:start-angle", m_startAngle); context.xmlWriter().addAttribute("draw:end-angle", m_endAngle); } saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } else { KoPathShape::saveOdf(context); } } bool EllipseShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { QSizeF size; bool radiusGiven = true; QString kind = element.attributeNS(KoXmlNS::draw, "kind", "full"); if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx")); qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry")); size = QSizeF(2 * rx, 2 * ry); } else if (element.hasAttributeNS(KoXmlNS::svg, "r")) { qreal r = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "r")); size = QSizeF(2 * r, 2 * r); } else { size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); #ifndef NWORKAROUND_ODF_BUGS radiusGiven = KoOdfWorkaround::fixEllipse(kind, context); #else radiusGiven = false; #endif } setSize(size); QPointF pos; if (element.hasAttributeNS(KoXmlNS::svg, "cx") && element.hasAttributeNS(KoXmlNS::svg, "cy")) { qreal cx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cx")); qreal cy = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cy")); pos = QPointF(cx - 0.5 * size.width(), cy - 0.5 * size.height()); } else { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); if (kind == "section") { setType(Pie); } else if (kind == "cut") { setType(Chord); } else { setType(Arc); } setStartAngle(element.attributeNS(KoXmlNS::draw, "start-angle", "0").toDouble()); setEndAngle(element.attributeNS(KoXmlNS::draw, "end-angle", "360").toDouble()); if (!radiusGiven) { // is the size was given by width and height we have to reset the data as the size of the // part of the cut/pie is given. setSize(size); setPosition(pos); } loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements); loadText(element, context); return true; } void EllipseShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_center = matrix.map(m_center); m_radii = matrix.map(m_radii); KoParameterShape::setSize(newSize); } QPointF EllipseShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); m_center = matrix.map(m_center); return offset; } void EllipseShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); QPointF diff(m_center - point); diff.setX(-diff.x()); qreal angle = 0; if (diff.x() == 0) { angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0; } else { diff.setY(diff.y() * m_radii.x() / m_radii.y()); angle = atan(diff.y() / diff.x()); if (angle < 0) { angle += M_PI; } if (diff.y() < 0) { angle += M_PI; } } QList handles = this->handles(); switch (handleId) { case 0: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_startAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 1: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_endAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 2: { QList kindHandlePositions; kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()))); kindHandlePositions.push_back(m_center); kindHandlePositions.push_back((handles[0] + handles[1]) / 2.0); QPointF diff = m_center * 2.0; int handlePos = 0; for (int i = 0; i < kindHandlePositions.size(); ++i) { QPointF pointDiff(p - kindHandlePositions[i]); if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) { diff = pointDiff; handlePos = i; } } handles[handleId] = kindHandlePositions[handlePos]; m_type = EllipseType(handlePos); } break; } setHandles(handles); if (handleId != 2) { updateKindHandle(); } } void EllipseShape::updatePath(const QSizeF &size) { Q_D(KoParameterShape); Q_UNUSED(size); QPointF startpoint(handles()[0]); QPointF curvePoints[12]; const qreal distance = sweepAngle(); const bool sameAngles = distance > 359.9; int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, distance, startpoint, curvePoints); KIS_SAFE_ASSERT_RECOVER_RETURN(pointCnt); int curvePointCount = 1 + pointCnt / 3; int requiredPointCount = curvePointCount; if (m_type == Pie) { requiredPointCount++; } else if (m_type == Arc && sameAngles) { curvePointCount--; requiredPointCount--; } createPoints(requiredPointCount); KoSubpath &points = *d->subpaths[0]; int curveIndex = 0; points[0]->setPoint(startpoint); points[0]->removeControlPoint1(); points[0]->setProperty(KoPathPoint::StartSubpath); for (int i = 1; i < curvePointCount; ++i) { points[i - 1]->setControlPoint2(curvePoints[curveIndex++]); points[i]->setControlPoint1(curvePoints[curveIndex++]); points[i]->setPoint(curvePoints[curveIndex++]); points[i]->removeControlPoint2(); } if (m_type == Pie) { points[requiredPointCount - 1]->setPoint(m_center); points[requiredPointCount - 1]->removeControlPoint1(); points[requiredPointCount - 1]->removeControlPoint2(); } else if (m_type == Arc && sameAngles) { points[curvePointCount - 1]->setControlPoint2(curvePoints[curveIndex]); points[0]->setControlPoint1(curvePoints[++curveIndex]); } for (int i = 0; i < requiredPointCount; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } d->subpaths[0]->last()->setProperty(KoPathPoint::StopSubpath); if (m_type == Arc && !sameAngles) { d->subpaths[0]->first()->unsetProperty(KoPathPoint::CloseSubpath); d->subpaths[0]->last()->unsetProperty(KoPathPoint::CloseSubpath); } else { d->subpaths[0]->first()->setProperty(KoPathPoint::CloseSubpath); d->subpaths[0]->last()->setProperty(KoPathPoint::CloseSubpath); } normalize(); } void EllipseShape::createPoints(int requiredPointCount) { Q_D(KoParameterShape); if (d->subpaths.count() != 1) { clear(); d->subpaths.append(new KoSubpath()); } int currentPointCount = d->subpaths[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { delete d->subpaths[0]->front(); d->subpaths[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { d->subpaths[0]->append(new KoPathPoint(this, QPointF())); } } } void EllipseShape::updateKindHandle() { qreal angle = 0.5 * (m_startAngle + m_endAngle); if (m_startAngle > m_endAngle) { angle += 180.0; } m_kindAngle = normalizeAngle(kisDegreesToRadians(angle)); QList handles = this->handles(); switch (m_type) { case Arc: handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()); break; case Pie: handles[2] = m_center; break; case Chord: handles[2] = (handles[0] + handles[1]) / 2.0; break; } setHandles(handles); } void EllipseShape::updateAngleHandles() { qreal startRadian = kisDegreesToRadians(normalizeAngleDegrees(m_startAngle)); qreal endRadian = kisDegreesToRadians(normalizeAngleDegrees(m_endAngle)); QList handles = this->handles(); handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y()); handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y()); setHandles(handles); } qreal EllipseShape::sweepAngle() const { const qreal a1 = normalizeAngle(kisDegreesToRadians(m_startAngle)); const qreal a2 = normalizeAngle(kisDegreesToRadians(m_endAngle)); qreal sAngle = a2 - a1; if (a1 > a2) { sAngle = 2 * M_PI + sAngle; } if (qAbs(a1 - a2) < 0.05 / M_PI) { sAngle = 2 * M_PI; } return kisRadiansToDegrees(sAngle); } void EllipseShape::setType(EllipseType type) { m_type = type; updateKindHandle(); updatePath(size()); } EllipseShape::EllipseType EllipseShape::type() const { return m_type; } void EllipseShape::setStartAngle(qreal angle) { m_startAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::startAngle() const { return m_startAngle; } void EllipseShape::setEndAngle(qreal angle) { m_endAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::endAngle() const { return m_endAngle; } QString EllipseShape::pathShapeId() const { return EllipseShapeId; } -QByteArray buildNamespace(const QString &ns, const QString &tag) { - return (ns + ":" + tag).toLatin1(); -} - bool EllipseShape::saveSvg(SvgSavingContext &context) { if (type() == EllipseShape::Arc && startAngle() == endAngle()) { const QSizeF size = this->size(); const bool isCircle = size.width() == size.height(); context.shapeWriter().startElement(isCircle ? "circle" : "ellipse"); context.shapeWriter().addAttribute("id", context.getID(this)); context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation())); if (isCircle) { context.shapeWriter().addAttributePt("r", 0.5 * size.width()); } else { context.shapeWriter().addAttributePt("rx", 0.5 * size.width()); context.shapeWriter().addAttributePt("ry", 0.5 * size.height()); } context.shapeWriter().addAttributePt("cx", 0.5 * size.width()); context.shapeWriter().addAttributePt("cy", 0.5 * size.height()); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } else { - const QString extendedNamespace = - type() == Arc || type() == Pie ? "sodipodi" : "krita"; - context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(this)); context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation())); - context.shapeWriter().addAttribute(buildNamespace(extendedNamespace, "type"), "arc"); - - context.shapeWriter().addAttributePt(buildNamespace(extendedNamespace, "rx"), m_radii.x()); - context.shapeWriter().addAttributePt(buildNamespace(extendedNamespace, "ry"), m_radii.y()); + context.shapeWriter().addAttribute("sodipodi:type", "arc"); - context.shapeWriter().addAttributePt(buildNamespace(extendedNamespace, "cx"), m_center.x()); - context.shapeWriter().addAttributePt(buildNamespace(extendedNamespace, "cy"), m_center.y()); + context.shapeWriter().addAttributePt("sodipodi:rx", m_radii.x()); + context.shapeWriter().addAttributePt("sodipodi:ry", m_radii.y()); - context.shapeWriter().addAttribute(buildNamespace(extendedNamespace, "start"), 2 * M_PI - kisDegreesToRadians(endAngle())); - context.shapeWriter().addAttribute(buildNamespace(extendedNamespace, "end"), 2 * M_PI - kisDegreesToRadians(startAngle())); + context.shapeWriter().addAttributePt("sodipodi:cx", m_center.x()); + context.shapeWriter().addAttributePt("sodipodi:cy", m_center.y()); - if (extendedNamespace == "krita") { - QString arcType; + context.shapeWriter().addAttribute("sodipodi:start", 2 * M_PI - kisDegreesToRadians(endAngle())); + context.shapeWriter().addAttribute("sodipodi:end", 2 * M_PI - kisDegreesToRadians(startAngle())); - switch (type()) { - case Arc: - arcType = "arc"; - break; - case Pie: - arcType = "pie"; - break; - case Chord: - arcType = "chord"; - break; - } - - context.shapeWriter().addAttribute(buildNamespace(extendedNamespace, "arcType"), arcType); - } else { - switch (type()) { - case Pie: - // noop - break; - case Arc: - case Chord: - context.shapeWriter().addAttribute(buildNamespace(extendedNamespace, "open"), "true"); - break; - } + switch (type()) { + case Pie: + // noop + break; + case Chord: + context.shapeWriter().addAttribute("sodipodi:arc-type", "chord"); + case Arc: + context.shapeWriter().addAttribute("sodipodi:open", "true"); + break; } context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform())); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } return true; } bool EllipseShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { qreal rx = 0, ry = 0; qreal cx = 0; qreal cy = 0; qreal start = 0; qreal end = 0; EllipseType type = Arc; const QString extendedNamespace = element.attribute("sodipodi:type") == "arc" ? "sodipodi" : element.attribute("krita:type") == "arc" ? "krita" : ""; if (element.tagName() == "ellipse") { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute("ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "circle") { rx = ry = SvgUtil::parseUnitXY(context.currentGC(), element.attribute("r")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "path" && !extendedNamespace.isEmpty()) { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":cy", "0")); start = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":end")); end = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":start")); - const QString kritaArcType = element.attribute("krita:arcType"); + const QString kritaArcType = + element.attribute("sodipodi:arc-type", element.attribute("krita:arcType")); if (kritaArcType.isEmpty()) { if (element.attribute("sodipodi:open", "false") == "false") { type = Pie; } } else if (kritaArcType == "pie") { type = Pie; } else if (kritaArcType == "chord") { type = Chord; } } else { return false; } setSize(QSizeF(2 * rx, 2 * ry)); setPosition(QPointF(cx - rx, cy - ry)); if (rx == 0.0 || ry == 0.0) { setVisible(false); } if (start != 0 || start != end) { setStartAngle(kisRadiansToDegrees(start)); setEndAngle(kisRadiansToDegrees(end)); setType(type); } return true; } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index 7dd7cbf5bf..faa81d799f 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1429 +1,1403 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 C. Boemann 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 "DefaultTool.h" #include "DefaultToolGeometryWidget.h" #include "DefaultToolTabbedWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_registry.h" #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" #include #define HANDLE_DISTANCE 10 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE) #define INNER_HANDLE_DISTANCE_SQ 16 namespace { static const QString EditFillGradientFactoryId = "edit_fill_gradient"; static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient"; } QPolygonF selectionPolygon(KoSelection *selection) { QPolygonF result; QList selectedShapes = selection->selectedShapes(); if (!selectedShapes.size()) { return result; } if (selectedShapes.size() > 1) { QTransform matrix = selection->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selection->size()))); } else { KoShape *selectedShape = selectedShapes.first(); QTransform matrix = selectedShape->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selectedShape->size()))); } return result; } class NopInteractionStrategy : public KoInteractionStrategy { public: explicit NopInteractionStrategy(KoToolBase *parent) : KoInteractionStrategy(parent) { } KUndo2Command *createCommand() override { return 0; } void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} void paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } }; class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy { public: explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid) : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid) { } void paint(QPainter &painter, const KoViewConverter &converter) { KoShapeRubberSelectStrategy::paint(painter, converter); } }; #include #include "KoShapeGradientHandles.h" #include "ShapeGradientEditStrategy.h" class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory { public: MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q) : KoInteractionStrategyFactory(priority, id), q(_q), m_fillVariant(fillVariant) { } KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { KoShape *shape = onlyEditableShape(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point); } return 0; } bool hoverEvent(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); return false; } bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); return false; } bool tryUseCustomCursor() { if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { q->useCursor(Qt::OpenHandCursor); } return m_currentHandle.type != KoShapeGradientHandles::Handle::None; } private: KoShape* onlyEditableShape() const { KoSelection *selection = q->koSelection(); QList shapes = selection->selectedEditableShapes(); KoShape *shape = 0; if (shapes.size() == 1) { shape = shapes.first(); } return shape; } KoShapeGradientHandles::Handle handleAt(const QPointF &pos) { KoShapeGradientHandles::Handle result; KoShape *shape = onlyEditableShape(); if (shape) { KoFlake::SelectionHandle globalHandle = q->handleAt(pos); const qreal distanceThresholdSq = globalHandle == KoFlake::NoHandle ? HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; const KoViewConverter *converter = q->canvas()->viewConverter(); const QPointF viewPoint = converter->documentToView(pos); qreal minDistanceSq = std::numeric_limits::max(); KoShapeGradientHandles sh(m_fillVariant, shape); Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) { const QPointF handlePoint = converter->documentToView(handle.pos); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { result = handle; minDistanceSq = distanceSq; } } } return result; } private: DefaultTool *q; KoFlake::FillVariant m_fillVariant; KoShapeGradientHandles::Handle m_currentHandle; }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { Q_ASSERT(m_selection); } bool hasSelection() override { return m_selection->count(); } private: KoSelection *m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeft) , m_mouseWasInsideHandles(false) - , m_moveCommand(0) , m_selectionHandler(new SelectionHandler(this)) , m_customEventStrategy(0) , m_tabbedOptionWidget(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { } void DefaultTool::slotActivateEditFillGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::Fill, 1, EditFillGradientFactoryId, this)); } else { removeInteractionFactory(EditFillGradientFactoryId); } repaintDecorations(); } void DefaultTool::slotActivateEditStrokeGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill, 0, EditStrokeGradientFactoryId, this)); } else { removeInteractionFactory(EditStrokeGradientFactoryId); } repaintDecorations(); } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::setupActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this); addAction("object_order_front", actionBringToFront); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this); addAction("object_order_raise", actionRaise); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this); addAction("object_order_lower", actionLower); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this); addAction("object_order_back", actionSendToBack); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QAction *actionAlignLeft = actionRegistry->makeQAction("object_align_horizontal_left", this); addAction("object_align_horizontal_left", actionAlignLeft); connect(actionAlignLeft, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalLeft())); QAction *actionAlignCenter = actionRegistry->makeQAction("object_align_horizontal_center", this); addAction("object_align_horizontal_center", actionAlignCenter); connect(actionAlignCenter, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalCenter())); QAction *actionAlignRight = actionRegistry->makeQAction("object_align_horizontal_right", this); addAction("object_align_horizontal_right", actionAlignRight); connect(actionAlignRight, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalRight())); QAction *actionAlignTop = actionRegistry->makeQAction("object_align_vertical_top", this); addAction("object_align_vertical_top", actionAlignTop); connect(actionAlignTop, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalTop())); QAction *actionAlignMiddle = actionRegistry->makeQAction("object_align_vertical_center", this); addAction("object_align_vertical_center", actionAlignMiddle); connect(actionAlignMiddle, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalCenter())); QAction *actionAlignBottom = actionRegistry->makeQAction("object_align_vertical_bottom", this); addAction("object_align_vertical_bottom", actionAlignBottom); connect(actionAlignBottom, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalBottom())); QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); m_contextMenu.reset(new QMenu()); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { if (tryUseCustomCursor()) return; QCursor cursor = Qt::ArrowCursor; QString statusText; if (koSelection()->count() > 0) { // has a selection bool editable = editableShapesCount(koSelection()->selectedShapes()); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { SelectionDecorator decorator(canvas()->resourceManager()); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); decorator.paint(painter, converter); KoInteractionTool::paint(painter, converter); painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { KoSelection *selection = koSelection(); if (!selection->count()) return QRectF(); recalcSelectionBox(selection); QRectF bound = m_selectionOutline.boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } requestStrokeEnd(); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { + bool result = false; + qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move + if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList prevPos; QList newPos; - QList shapes; - Q_FOREACH (KoShape *shape, koSelection()->selectedShapes()) { - if (shape->isGeometryProtected()) { - continue; - } - shapes.append(shape); - QPointF p = shape->position(); - prevPos.append(p); - p.setX(p.x() + x); - p.setY(p.y() + y); - newPos.append(p); + QList shapes = koSelection()->selectedEditableShapes(); + + Q_FOREACH (KoShape *shape, shapes) { + QPointF pos = shape->absolutePosition(); + + prevPos.append(pos); + pos.rx() += x; + pos.ry() += y; + newPos.append(pos); } - if (shapes.count() > 0) { - // use a timeout to make sure we don't reuse a command possibly deleted by the commandHistory - if (m_lastUsedMoveCommand.msecsTo(QTime::currentTime()) > 5000) { - m_moveCommand = 0; - } - if (m_moveCommand) { // alter previous instead of creating new one. - m_moveCommand->setNewPositions(newPos); - m_moveCommand->redo(); - } else { - m_moveCommand = new KoShapeMoveCommand(shapes, prevPos, newPos); - canvas()->addCommand(m_moveCommand); - } - m_lastUsedMoveCommand = QTime::currentTime(); - return true; + + if (!shapes.isEmpty()) { + canvas()->addCommand(new KoShapeMoveCommand(shapes, prevPos, newPos)); + result = true; } } - return false; + + return result; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::repaintDecorations() { Q_ASSERT(koSelection()); if (koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { QList shapes = canvas()->selectedShapesProxy()->selection()->selectedShapes(); if (!shapes.empty()) { KoShapeOdfSaveHelper saveHelper(shapes); KoDrag drag; drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->selectedShapesProxy()->selection()->selectedShapes()) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } QStringList DefaultTool::supportedPasteMimeTypes() const { QStringList list; list << KoOdf::mimeType(KoOdf::Text); return list; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->selectedShapesProxy()); return canvas()->selectedShapesProxy()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; const KoViewConverter *converter = canvas()->viewConverter(); KoSelection *selection = koSelection(); if (!selection->count() || !converter) { return KoFlake::NoHandle; } recalcSelectionBox(selection); if (innerHandleMeaning) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } const QPointF viewPoint = converter->documentToView(point); for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); // if just inside the outline if (distanceSq < HANDLE_DISTANCE_SQ) { if (innerHandleMeaning) { if (distanceSq < INNER_HANDLE_DISTANCE_SQ) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox(KoSelection *selection) { KIS_ASSERT_RECOVER_RETURN(selection->count()); QTransform matrix = selection->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect())); m_angle = 0.0; QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (selection->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->activate(); } } void DefaultTool::deactivate() { KoToolBase::deactivate(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->deactivate(); } } void DefaultTool::selectionAlignHorizontalLeft() { selectionAlign(KoShapeAlignCommand::HorizontalLeftAlignment); } void DefaultTool::selectionAlignHorizontalCenter() { selectionAlign(KoShapeAlignCommand::HorizontalCenterAlignment); } void DefaultTool::selectionAlignHorizontalRight() { selectionAlign(KoShapeAlignCommand::HorizontalRightAlignment); } void DefaultTool::selectionAlignVerticalTop() { selectionAlign(KoShapeAlignCommand::VerticalTopAlignment); } void DefaultTool::selectionAlignVerticalCenter() { selectionAlign(KoShapeAlignCommand::VerticalCenterAlignment); } void DefaultTool::selectionAlignVerticalBottom() { selectionAlign(KoShapeAlignCommand::VerticalBottomAlignment); } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) { return; } - QList selectedShapes = selection->selectedShapes(); - QList groupedShapes; + QList selectedShapes = selection->selectedEditableShapes(); + qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); - // only group shapes with an unselected parent - foreach (KoShape *shape, selectedShapes) { - if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) { - groupedShapes << shape; - } - } KoShapeGroup *group = new KoShapeGroup(); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); canvas()->shapeController()->addShapeDirect(group, cmd); - new KoShapeGroupCommand(group, groupedShapes, false, true, true, cmd); + new KoShapeGroupCommand(group, selectedShapes, false, true, true, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (!selection) { return; } - QList selectedShapes = selection->selectedShapes(); - QList containerSet; - - // only ungroup shape groups with an unselected parent - foreach (KoShape *shape, selectedShapes) { - if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) { - containerSet << shape; - } - } + QList selectedShapes = selection->selectedEditableShapes(); + qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command - Q_FOREACH (KoShape *shape, containerSet) { + Q_FOREACH (KoShape *shape, selectedShapes) { KoShapeGroup *group = dynamic_cast(shape); if (group) { cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : canvas()->shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { canvas()->addCommand(cmd); } } void DefaultTool::selectionAlign(KoShapeAlignCommand::Align align) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(); if (selectedShapes.count() < 1) { return; } QList editableShapes = filterEditableShapes(selectedShapes); // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize)); } else { Q_FOREACH (KoShape *shape, editableShapes) { bb |= shape->boundingRect(); } } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(); if (selectedShapes.count() < 1) { return; } QList editableShapes = filterEditableShapes(selectedShapes); if (editableShapes.count() < 1) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(editableShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; m_tabbedOptionWidget = new DefaultToolTabbedWidget(this); if (isActivated()) { m_tabbedOptionWidget->activate(); } widgets.append(m_tabbedOptionWidget); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditFillGradient(bool)), SLOT(slotActivateEditFillGradient(bool))); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditStrokeGradient(bool)), SLOT(slotActivateEditStrokeGradient(bool))); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = KoFlake::AnchorPosition(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { - // reset the move by keys when a new strategy is created otherwise we might change the - // command after a new command was added. This happend when you where faster than the timer. - m_moveCommand = 0; - KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = koSelection(); bool insideSelection = false; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = editableShapesCount(selection->selectedShapes()); const bool selectMultiple = event->modifiers() & Qt::ShiftModifier; const bool selectNextInStack = event->modifiers() & Qt::ControlModifier; const bool avoidSelection = event->modifiers() & Qt::AltModifier; if (selectNextInStack) { // change the hot selection position when middle clicking on a handle KoFlake::AnchorPosition newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopMiddleHandle: newHotPosition = KoFlake::Top; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRight; break; case KoFlake::RightMiddleHandle: newHotPosition = KoFlake::Right; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRight; break; case KoFlake::BottomMiddleHandle: newHotPosition = KoFlake::Bottom; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeft; break; case KoFlake::LeftMiddleHandle: newHotPosition = KoFlake::Left; break; case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeft; break; case KoFlake::NoHandle: default: // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point); // TODO: use calculated values instead! QPointF centerPt = converter->documentToView(selection->absolutePosition()); if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) { newHotPosition = KoFlake::Center; } break; } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); return new NopInteractionStrategy(this); } } if (!avoidSelection && editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { // resizing or shearing only with left mouse button if (insideSelection) { return new ShapeResizeStrategy(this, event->point, handle); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } // rotating is allowed for rigth mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (!selectMultiple && !selectNextInStack) { if (insideSelection) { return new ShapeMoveStrategy(this, event->point); } } } KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) { if (!selectMultiple) { repaintDecorations(); selection->deselectAll(); } return new SelectionInteractionStrategy(this, event->point, false); } if (selection->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); selection->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { shapeManager->selection()->deselectAll(); } selection->select(shape); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, event->point); } return 0; } void DefaultTool::updateActions() { QList selectedShapes; if (koSelection()) { selectedShapes = koSelection()->selectedShapes(); } if (selectedShapes.isEmpty()) { action("object_order_front")->setEnabled(false); action("object_order_raise")->setEnabled(false); action("object_order_lower")->setEnabled(false); action("object_order_back")->setEnabled(false); action("object_align_horizontal_left")->setEnabled(false); action("object_align_horizontal_center")->setEnabled(false); action("object_align_horizontal_right")->setEnabled(false); action("object_align_vertical_top")->setEnabled(false); action("object_align_vertical_center")->setEnabled(false); action("object_align_vertical_bottom")->setEnabled(false); action("object_group")->setEnabled(false); action("object_ungroup")->setEnabled(false); } else { QList editableShapes = filterEditableShapes(selectedShapes); const bool enable = editableShapes.size() > 0; action("object_order_front")->setEnabled(enable); action("object_order_raise")->setEnabled(enable); action("object_order_lower")->setEnabled(enable); action("object_order_back")->setEnabled(enable); const bool alignmentEnabled = editableShapes.size() > 1 || (enable && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(alignmentEnabled); action("object_align_horizontal_center")->setEnabled(alignmentEnabled); action("object_align_horizontal_right")->setEnabled(alignmentEnabled); action("object_align_vertical_top")->setEnabled(alignmentEnabled); action("object_align_vertical_center")->setEnabled(alignmentEnabled); action("object_align_vertical_bottom")->setEnabled(alignmentEnabled); action("object_group")->setEnabled(editableShapes.size() > 1); bool hasGroupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { hasGroupShape = true; break; } } action("object_ungroup")->setEnabled(hasGroupShape); } emit selectionChanged(selectedShapes.size()); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QMenu* DefaultTool::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); m_contextMenu->addAction(collection->action("edit_cut")); m_contextMenu->addAction(collection->action("edit_copy")); m_contextMenu->addAction(collection->action("edit_paste")); m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_order_front")); m_contextMenu->addAction(action("object_order_raise")); m_contextMenu->addAction(action("object_order_lower")); m_contextMenu->addAction(action("object_order_back")); if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_group")); m_contextMenu->addAction(action("object_ungroup")); } } return m_contextMenu.data(); } QList DefaultTool::filterEditableShapes(const QList &shapes) { QList editableShapes; Q_FOREACH (KoShape *shape, shapes) { if (shape->isEditable()) { editableShapes.append(shape); } } return editableShapes; } uint DefaultTool::editableShapesCount(const QList &shapes) { uint count = 0; Q_FOREACH (KoShape *shape, shapes) { if (shape->isEditable()) { count++; } } return count; } void DefaultTool::requestStrokeEnd() { QList shapes = koSelection()->selectedEditableShapesAndDelegates(); emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes)); } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.h b/plugins/tools/defaulttool/defaulttool/DefaultTool.h index 768a245281..ff8e96502c 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.h +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.h @@ -1,185 +1,183 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2008 Thomas Zander 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 DEFAULTTOOL_H #define DEFAULTTOOL_H #include #include #include #include #include #include class KoInteractionStrategy; class KoShapeMoveCommand; class KoSelection; class DefaultToolTabbedWidget; /** * The default tool (associated with the arrow icon) implements the default * interactions you have with flake objects.
* The tool provides scaling, moving, selecting, rotation and soon skewing of * any number of shapes. *

Note that the implementation of those different strategies are delegated * to the InteractionStrategy class and its subclasses. */ class DefaultTool : public KoInteractionTool { Q_OBJECT public: /** * Constructor for basic interaction tool where user actions are translated * and handled by interaction strategies of type KoInteractionStrategy. * @param canvas the canvas this tool will be working for. */ explicit DefaultTool(KoCanvasBase *canvas); virtual ~DefaultTool(); enum CanvasResource { HotPosition = 1410100299 }; public: virtual bool wantsAutoScroll() const; virtual void paint(QPainter &painter, const KoViewConverter &converter); virtual void repaintDecorations(); ///reimplemented virtual void copy() const; ///reimplemented virtual void deleteSelection(); ///reimplemented virtual bool paste(); ///reimplemented virtual QStringList supportedPasteMimeTypes() const; ///reimplemented virtual KoToolSelection *selection(); QMenu* popupActionsMenu() override; /** * Returns which selection handle is at params point (or NoHandle if none). * @return which selection handle is at params point (or NoHandle if none). * @param point the location (in pt) where we should look for a handle * @param innerHandleMeaning this boolean is altered to true if the point * is inside the selection rectangle and false if it is just outside. * The value of innerHandleMeaning is undefined if the handle location is NoHandle */ KoFlake::SelectionHandle handleAt(const QPointF &point, bool *innerHandleMeaning = 0); public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; private Q_SLOTS: void selectionAlignHorizontalLeft(); void selectionAlignHorizontalCenter(); void selectionAlignHorizontalRight(); void selectionAlignVerticalTop(); void selectionAlignVerticalCenter(); void selectionAlignVerticalBottom(); void selectionBringToFront(); void selectionSendToBack(); void selectionMoveUp(); void selectionMoveDown(); void selectionGroup(); void selectionUngroup(); void slotActivateEditFillGradient(bool value); void slotActivateEditStrokeGradient(bool value); /// Update actions on selection change void updateActions(); public: // Events void mousePressEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void requestStrokeEnd() override; protected: QList > createOptionWidgets(); KoInteractionStrategy *createStrategy(KoPointerEvent *event) override; private: class MoveGradientHandleInteractionFactory; private: void setupActions(); void recalcSelectionBox(KoSelection *selection); void updateCursor(); /// Returns rotation angle of given handle of the current selection qreal rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation); void selectionAlign(KoShapeAlignCommand::Align align); void selectionReorder(KoShapeReorderCommand::MoveShapeType order); bool moveSelection(int direction, Qt::KeyboardModifiers modifiers); /// Returns selection rectangle adjusted by handle proximity threshold QRectF handlesSize(); // convenience method; KoSelection *koSelection(); void canvasResourceChanged(int key, const QVariant &res); /// Returns list of editable shapes from the given list of shapes QList filterEditableShapes(const QList &shapes); /// Returns the number of editable shapes from the given list of shapes uint editableShapesCount(const QList &shapes); KoFlake::SelectionHandle m_lastHandle; KoFlake::AnchorPosition m_hotPosition; bool m_mouseWasInsideHandles; QPointF m_selectionBox[8]; QPolygonF m_selectionOutline; QPointF m_lastPoint; - KoShapeMoveCommand *m_moveCommand; - QTime m_lastUsedMoveCommand; // TODO alter these 3 arrays to be static const instead QCursor m_sizeCursors[8]; QCursor m_rotateCursors[8]; QCursor m_shearCursors[8]; qreal m_angle; KoToolSelection *m_selectionHandler; friend class SelectionHandler; KoInteractionStrategy *m_customEventStrategy; QScopedPointer m_contextMenu; DefaultToolTabbedWidget *m_tabbedOptionWidget; }; #endif