diff --git a/libs/global/KisHandlePainterHelper.cpp b/libs/global/KisHandlePainterHelper.cpp index 6d830e4b62..0361e76f14 100644 --- a/libs/global/KisHandlePainterHelper.cpp +++ b/libs/global/KisHandlePainterHelper.cpp @@ -1,280 +1,293 @@ /* * 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 "KisHandlePainterHelper.h" #include #include "kis_algebra_2d.h" #include "kis_painting_tweaks.h" using KisPaintingTweaks::PenBrushSaver; KisHandlePainterHelper::KisHandlePainterHelper(QPainter *_painter, qreal handleRadius) : m_painter(_painter), m_originalPainterTransform(m_painter->transform()), m_painterTransform(m_painter->transform()), m_handleRadius(handleRadius), m_decomposedMatrix(m_painterTransform) { init(); } KisHandlePainterHelper::KisHandlePainterHelper(QPainter *_painter, const QTransform &originalPainterTransform, qreal handleRadius) : m_painter(_painter), m_originalPainterTransform(originalPainterTransform), m_painterTransform(m_painter->transform()), m_handleRadius(handleRadius), m_decomposedMatrix(m_painterTransform) { init(); } KisHandlePainterHelper::KisHandlePainterHelper(KisHandlePainterHelper &&rhs) : m_painter(rhs.m_painter), m_originalPainterTransform(rhs.m_originalPainterTransform), m_painterTransform(rhs.m_painterTransform), m_handleRadius(rhs.m_handleRadius), m_decomposedMatrix(rhs.m_decomposedMatrix), m_handleTransform(rhs.m_handleTransform), m_handlePolygon(rhs.m_handlePolygon), m_handleStyle(rhs.m_handleStyle) { // disable the source helper rhs.m_painter = 0; } void KisHandlePainterHelper::init() { m_handleStyle = KisHandleStyle::inheritStyle(); m_painter->setTransform(QTransform()); m_handleTransform = m_decomposedMatrix.shearTransform() * m_decomposedMatrix.rotateTransform(); if (m_handleRadius > 0.0) { const QRectF handleRect(-m_handleRadius, -m_handleRadius, 2 * m_handleRadius, 2 * m_handleRadius); m_handlePolygon = m_handleTransform.map(QPolygonF(handleRect)); } } KisHandlePainterHelper::~KisHandlePainterHelper() { if (m_painter) { m_painter->setTransform(m_originalPainterTransform); } } void KisHandlePainterHelper::setHandleStyle(const KisHandleStyle &style) { m_handleStyle = style; } void KisHandlePainterHelper::drawHandleRect(const QPointF ¢er, qreal radius) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius); QPolygonF handlePolygon = m_handleTransform.map(QPolygonF(handleRect)); handlePolygon.translate(m_painterTransform.map(center)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPolygon(handlePolygon); } } void KisHandlePainterHelper::drawHandleCircle(const QPointF ¢er, qreal radius) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius); handleRect.translate(m_painterTransform.map(center)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawEllipse(handleRect); } } void KisHandlePainterHelper::drawHandleCircle(const QPointF ¢er) { drawHandleCircle(center, m_handleRadius); } void KisHandlePainterHelper::drawHandleSmallCircle(const QPointF ¢er) { drawHandleCircle(center, 0.7 * m_handleRadius); } void KisHandlePainterHelper::drawHandleRect(const QPointF ¢er) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QPolygonF paintingPolygon = m_handlePolygon.translated(m_painterTransform.map(center)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPolygon(paintingPolygon); } } void KisHandlePainterHelper::drawGradientHandle(const QPointF ¢er, qreal radius) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QPolygonF handlePolygon; handlePolygon << QPointF(-radius, 0); handlePolygon << QPointF(0, radius); handlePolygon << QPointF(radius, 0); handlePolygon << QPointF(0, -radius); handlePolygon = m_handleTransform.map(handlePolygon); handlePolygon.translate(m_painterTransform.map(center)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPolygon(handlePolygon); } } void KisHandlePainterHelper::drawGradientHandle(const QPointF ¢er) { drawGradientHandle(center, 1.41 * m_handleRadius); } void KisHandlePainterHelper::drawGradientCrossHandle(const QPointF ¢er, qreal radius) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); { // Draw a cross QPainterPath p; p.moveTo(-radius, -radius); p.lineTo(radius, radius); p.moveTo(radius, -radius); p.lineTo(-radius, radius); p = m_handleTransform.map(p); p.translate(m_painterTransform.map(center)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPath(p); } } { // Draw a square const qreal halfRadius = 0.5 * radius; QPolygonF handlePolygon; handlePolygon << QPointF(-halfRadius, 0); handlePolygon << QPointF(0, halfRadius); handlePolygon << QPointF(halfRadius, 0); handlePolygon << QPointF(0, -halfRadius); handlePolygon = m_handleTransform.map(handlePolygon); handlePolygon.translate(m_painterTransform.map(center)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPolygon(handlePolygon); } } } void KisHandlePainterHelper::drawArrow(const QPointF &pos, const QPointF &from, qreal radius) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QPainterPath p; QLineF line(pos, from); line.setLength(radius); QPointF norm = KisAlgebra2D::leftUnitNormal(pos - from); norm *= 0.34 * radius; p.moveTo(line.p2() + norm); p.lineTo(line.p1()); p.lineTo(line.p2() - norm); p.translate(-pos); p = m_handleTransform.map(p).translated(m_painterTransform.map(pos)); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPath(p); } } void KisHandlePainterHelper::drawGradientArrow(const QPointF &start, const QPointF &end, qreal radius) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QPainterPath p; p.moveTo(start); p.lineTo(end); p = m_painterTransform.map(p); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPath(p); } const qreal length = kisDistance(start, end); const QPointF diff = end - start; if (length > 5 * radius) { drawArrow(start + 0.33 * diff, start, radius); drawArrow(start + 0.66 * diff, start, radius); } else if (length > 3 * radius) { drawArrow(start + 0.5 * diff, start, radius); } } void KisHandlePainterHelper::drawRubberLine(const QPolygonF &poly) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QPolygonF paintingPolygon = m_painterTransform.map(poly); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPolygon(paintingPolygon); } } void KisHandlePainterHelper::drawConnectionLine(const QLineF &line) { drawConnectionLine(line.p1(), line.p2()); } void KisHandlePainterHelper::drawConnectionLine(const QPointF &p1, const QPointF &p2) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QPointF realP1 = m_painterTransform.map(p1); QPointF realP2 = m_painterTransform.map(p2); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawLine(realP1, realP2); } } void KisHandlePainterHelper::drawPath(const QPainterPath &path) { const QPainterPath realPath = m_painterTransform.map(path); Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPath(realPath); } } + +void KisHandlePainterHelper::drawPixmap(const QPixmap &pixmap, QPointF position, int size, QRectF sourceRect) +{ + QPointF handlePolygon = m_painterTransform.map(position); + + handlePolygon -= QPointF(size*0.5,size*0.5); + + m_painter->drawPixmap(QRect(handlePolygon.x(), + handlePolygon.y(), + size, + size), + pixmap, sourceRect); +} diff --git a/libs/global/KisHandlePainterHelper.h b/libs/global/KisHandlePainterHelper.h index 8cb8b2c82d..414ed70f9c 100644 --- a/libs/global/KisHandlePainterHelper.h +++ b/libs/global/KisHandlePainterHelper.h @@ -1,169 +1,171 @@ /* * 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 KISHANDLEPAINTERHELPER_H #define KISHANDLEPAINTERHELPER_H #include "kritaglobal_export.h" #include "kis_algebra_2d.h" #include #include class QPainter; class KoShape; class KoViewConverter; /** * @brief The KisHandlePainterHelper class is a special helper for * painting handles around objects. It ensures the handlesare painted * with the same size and line width whatever transformation is setup * in the painter. The handles will also be rotated/skewed if the object * itself has these transformations. * * On construction it resets QPainter transformation and on destruction * recovers it back. * * Please consider using KoShape::createHandlePainterHelper instead of direct * construction of the helper. This factory method will also apply the * transformations needed for a shape. */ class KRITAGLOBAL_EXPORT KisHandlePainterHelper { public: /** * Creates the helper, initializes all the internal transformations and * *resets* the transformation of the painter. */ KisHandlePainterHelper(QPainter *_painter, qreal handleRadius = 0.0); /** * Creates the helper, initializes all the internal transformations and * *resets* the transformation of the painter. This override also adjusts the * transformation of the painter into the coordinate system of the shape */ KisHandlePainterHelper(QPainter *_painter, const QTransform &originalPainterTransform, qreal handleRadius); /** * Move c-tor. Used to create and return the helper from functions by-value. */ KisHandlePainterHelper(KisHandlePainterHelper &&rhs); KisHandlePainterHelper(KisHandlePainterHelper &rhs) = delete; /** * Restores the transformation of the painter */ ~KisHandlePainterHelper(); /** * Sets style used for painting the handles. Please use static methods of * KisHandleStyle to select predefined styles. */ void setHandleStyle(const KisHandleStyle &style); /** * Draws a handle rect with a custom \p radius at position \p center */ void drawHandleRect(const QPointF ¢er, qreal radius); /** * Draws a handle circle with a custom \p radius at position \p center */ void drawHandleCircle(const QPointF ¢er, qreal radius); /** * Optimized version of the drawing method for drawing handles of * predefined size */ void drawHandleRect(const QPointF ¢er); /** * Optimized version of the drawing method for drawing handles of * predefined size */ void drawHandleCircle(const QPointF ¢er); /** * Optimized version of the drawing method for drawing handles of * predefined size */ void drawHandleSmallCircle(const QPointF ¢er); /** * Draw a rotated handle representing the gradient handle */ void drawGradientHandle(const QPointF ¢er, qreal radius); /** * Draw a rotated handle representing the gradient handle */ void drawGradientHandle(const QPointF ¢er); /** * Draw a special handle representing the center of the gradient */ void drawGradientCrossHandle(const QPointF ¢er, qreal radius); /** * Draw an arrow representing gradient position */ void drawGradientArrow(const QPointF &start, const QPointF &end, qreal radius); /** * Draw a line showing the bounding box of the selection */ void drawRubberLine(const QPolygonF &poly); /** * Draw a line connecting two points */ void drawConnectionLine(const QLineF &line); /** * Draw a line connecting two points */ void drawConnectionLine(const QPointF &p1, const QPointF &p2); /** * Draw an arbitrary path */ void drawPath(const QPainterPath &path); + void drawPixmap(const QPixmap &pixmap, QPointF position, int size, QRectF sourceRect); + private: /** * Draw a single arrow with the tip at position \p pos, directed from \p from, * of size \p radius. */ void drawArrow(const QPointF &pos, const QPointF &from, qreal radius); void init(); private: QPainter *m_painter; QTransform m_originalPainterTransform; QTransform m_painterTransform; qreal m_handleRadius; KisAlgebra2D::DecomposedMatix m_decomposedMatrix; QTransform m_handleTransform; QPolygonF m_handlePolygon; KisHandleStyle m_handleStyle; }; #endif // KISHANDLEPAINTERHELPER_H diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index 6cce400c78..5f80bbe932 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1692 +1,1757 @@ /* 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 #include #include #include "kis_action_registry.h" #include "kis_node.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include #include "kis_document_aware_spin_box_unit_manager.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"; enum TransformActionType { TransformRotate90CW, TransformRotate90CCW, TransformRotate180, TransformMirrorX, TransformMirrorY, TransformReset }; enum BooleanOp { BooleanUnion, BooleanIntersection, BooleanSubtraction }; } 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) override { 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) override { KoShapeRubberSelectStrategy::paint(painter, converter); } void finishInteraction(Qt::KeyboardModifiers modifiers) override { Q_UNUSED(modifiers); DefaultTool *defaultTool = dynamic_cast(tool()); KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool); KoSelection * selection = defaultTool->koSelection(); const bool useContainedMode = currentMode() == CoveringSelection; QList shapes = defaultTool->shapeManager()-> shapesAt(selectedRectangle(), true, useContainedMode); Q_FOREACH (KoShape * shape, shapes) { if (!shape->isSelectable()) continue; selection->select(shape); } defaultTool->repaintDecorations(); defaultTool->canvas()->updateCanvas(selectedRectangle()); } }; #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() override { 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()) { } bool hasSelection() override { if (m_selection) { return m_selection->count(); } return false; } private: QPointer m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeft) , m_mouseWasInsideHandles(false) , m_selectionHandler(new SelectionHandler(this)) , 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::addMappedAction(QSignalMapper *mapper, const QString &actionId, int commandType) { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *action = actionRegistry->makeQAction(actionId, this); addAction(actionId, action); connect(action, SIGNAL(triggered()), mapper, SLOT(map())); mapper->setMapping(action, commandType); } 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())); QSignalMapper *alignSignalsMapper = new QSignalMapper(this); connect(alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int))); addMappedAction(alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment); QSignalMapper *distributeSignalsMapper = new QSignalMapper(this); connect(distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int))); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution); 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())); QSignalMapper *transformSignalsMapper = new QSignalMapper(this); connect(transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int))); addMappedAction(transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW); addMappedAction(transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW); addMappedAction(transformSignalsMapper, "object_transform_rotate_180", TransformRotate180); addMappedAction(transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX); addMappedAction(transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY); addMappedAction(transformSignalsMapper, "object_transform_reset", TransformReset); QSignalMapper *booleanSignalsMapper = new QSignalMapper(this); connect(booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int))); addMappedAction(booleanSignalsMapper, "object_unite", BooleanUnion); addMappedAction(booleanSignalsMapper, "object_intersect", BooleanIntersection); addMappedAction(booleanSignalsMapper, "object_subtract", BooleanSubtraction); QAction *actionSplit = actionRegistry->makeQAction("object_split", this); addAction("object_split", actionSplit); connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes())); 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; KoSelection *selection = koSelection(); if (selection && selection->count() > 0) { // has a selection bool editable = !selection->selectedEditableShapes().isEmpty(); 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) { KoSelection *selection = koSelection(); if (selection) { - SelectionDecorator decorator(canvas()->resourceManager()); - decorator.setSelection(selection); - decorator.setHandleRadius(handleRadius()); - decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); - decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); - decorator.paint(painter, converter); + decorator = new SelectionDecorator(canvas()->resourceManager(), dynamic_cast(canvas())->coordinatesConverter() ); + decorator->setSelection(selection); + 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(); } bool DefaultTool::isValidForCurrentLayer() const { KisNodeSP currentNode = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return !currentNode.isNull() && currentNode->inherits("KisShapeLayer"); } KoShapeManager *DefaultTool::shapeManager() const { return canvas()->shapeManager(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { // this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it if (!isValidForCurrentLayer()) { KisCanvas2 *kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage( i18n("This tool only works on vector layers. You probably want the move tool."), QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); return; } KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection() && 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... :'''( } + isSelectingTextEditorButton(event->point); + updateCursor(); } QRectF DefaultTool::handlesSize() { KoSelection *selection = koSelection(); if (!selection || !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(); + + // updates the whole canvas. This makes sure the decorations that are shown are refreshed + canvas()->updateCanvas(QRectF(0,0,canvas()->canvasWidget()->width(), canvas()->canvasWidget()->height())); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoSelection *selection = koSelection(); KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && selection && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } explicitUserStrokeEndRequest(); } 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 shapes = koSelection()->selectedEditableShapes(); if (!shapes.isEmpty()) { canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y))); result = true; } } 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() { if (koSelection() && koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { // all the selected shapes, not only editable! QList shapes = koSelection()->selectedShapes(); if (!shapes.isEmpty()) { KoDrag drag; drag.setSvg(shapes); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, koSelection()->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; } KoSelection *DefaultTool::koSelection() const { Q_ASSERT(canvas()); Q_ASSERT(canvas()->selectedShapesProxy()); return canvas()->selectedShapesProxy()->selection(); } + +bool DefaultTool::isSelectingTextEditorButton(const QPointF &mousePosition) +{ + if (canvas()) { + } else { + return false; + } + + + // calculate position for textEditorBoxButton + KoSelection *selection = koSelection(); + const KoViewConverter *converter = canvas()->viewConverter(); + + if (!selection || !selection->count() || !converter) { + return false; + } + + QRectF outline = selection->boundingRect(); + + QPointF absoluteTransormPosition( + outline.x() + outline.width()*0.5, + outline.y() + outline.height() + 10); + + QPointF textEditorAbsPosition = converter->documentToView(absoluteTransormPosition); + + + + // check to see if the text decorator is checked (only for text objects) + + const QPointF viewPoint = converter->documentToView(mousePosition); + //const QPointF handlePoint = converter->documentToView(textEditorAbsPosition); + const QPointF handlePoint = textEditorAbsPosition; + + const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); + + + //qWarning() << "viewPoint: " << QString::number(viewPoint.x()) << " " + // << QString::number(viewPoint.y()); + + //qWarning() << "handlePoint: " << QString::number(textEditorAbsPosition.x()) << " " + // << QString::number(textEditorAbsPosition.y()); + + // qWarning() << "distance from text button: " << QString::number(distanceSq); + + + if (distanceSq < 18 * 18) { // 22 is "handle" area (previously 16). Probably can refactor this a bit + //qWarning() << "over Text area: "; + return true; + } + else { + // qWarning() << "NOT over Text area: "; + return false; + } + +} + 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 || !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 std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); std::swap(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::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); if (selectedShapes.isEmpty()) return; const int groupZIndex = selectedShapes.last()->zIndex(); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(groupZIndex); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd); canvas()->shapeController()->addShapeDirect(group, 0, cmd); new KoShapeGroupCommand(group, selectedShapes, true, cmd); new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KUndo2Command *cmd = 0; QList newShapes; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, selectedShapes) { KoShapeGroup *group = dynamic_cast(shape); if (group) { if (!cmd) { cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd); } newShapes << group->shapes(); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); } } void DefaultTool::selectionTransform(int transformAction) { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } QTransform applyTransform; bool shouldReset = false; KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action"); switch (TransformActionType(transformAction)) { case TransformRotate90CW: applyTransform.rotate(90.0); actionName = kundo2_i18n("Rotate Object 90° CW"); break; case TransformRotate90CCW: applyTransform.rotate(-90.0); actionName = kundo2_i18n("Rotate Object 90° CCW"); break; case TransformRotate180: applyTransform.rotate(180.0); actionName = kundo2_i18n("Rotate Object 180°"); break; case TransformMirrorX: applyTransform.scale(-1.0, 1.0); actionName = kundo2_i18n("Mirror Object Horizontally"); break; case TransformMirrorY: applyTransform.scale(1.0, -1.0); actionName = kundo2_i18n("Mirror Object Vertically"); break; case TransformReset: shouldReset = true; actionName = kundo2_i18n("Reset Object Transformations"); break; } if (!shouldReset && applyTransform.isIdentity()) return; QList oldTransforms; QList newTransforms; const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes); const QPointF centerPoint = outlineRect.center(); const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y()); const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y()); // we also add selection to the list of trasformed shapes, so that its outline is updated correctly QList transformedShapes = editableShapes; transformedShapes << selection; Q_FOREACH (KoShape *shape, transformedShapes) { oldTransforms.append(shape->transformation()); QTransform t; if (!shouldReset) { const QTransform world = shape->absoluteTransformation(0); t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation(); } else { const QPointF center = shape->outlineRect().center(); const QPointF offset = shape->transformation().map(center) - center; t = QTransform::fromTranslate(offset.x(), offset.y()); } newTransforms.append(t); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms); cmd->setText(actionName); canvas()->addCommand(cmd); } void DefaultTool::selectionBooleanOp(int booleanOp) { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } QVector srcOutlines; QPainterPath dstOutline; KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name"); // TODO: implement a reference shape selection dialog! const int referenceShapeIndex = 0; KoShape *referenceShape = editableShapes[referenceShapeIndex]; Q_FOREACH (KoShape *shape, editableShapes) { srcOutlines << shape->absoluteTransformation(0).map(shape->outline()); } if (booleanOp == BooleanUnion) { Q_FOREACH (const QPainterPath &path, srcOutlines) { dstOutline |= path; } actionName = kundo2_i18n("Unite Shapes"); } else if (booleanOp == BooleanIntersection) { for (int i = 0; i < srcOutlines.size(); i++) { if (i == 0) { dstOutline = srcOutlines[i]; } else { dstOutline &= srcOutlines[i]; } } // there is a bug in Qt, sometimes it leaves the resulting // outline open, so just close it explicitly. dstOutline.closeSubpath(); actionName = kundo2_i18n("Intersect Shapes"); } else if (booleanOp == BooleanSubtraction) { for (int i = 0; i < srcOutlines.size(); i++) { dstOutline = srcOutlines[referenceShapeIndex]; if (i != referenceShapeIndex) { dstOutline -= srcOutlines[i]; } } actionName = kundo2_i18n("Subtract Shapes"); } KoShape *newShape = 0; if (!dstOutline.isEmpty()) { newShape = KoPathShape::createShapeFromPainterPath(dstOutline); } KUndo2Command *cmd = new KUndo2Command(actionName); new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd); QList newSelectedShapes; if (newShape) { newShape->setBackground(referenceShape->background()); newShape->setStroke(referenceShape->stroke()); newShape->setZIndex(referenceShape->zIndex()); KoShapeContainer *parent = referenceShape->parent(); canvas()->shapeController()->addShapeDirect(newShape, parent, cmd); newSelectedShapes << newShape; } canvas()->shapeController()->removeShapes(editableShapes, cmd); new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); } void DefaultTool::selectionSplitShapes() { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes")); new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd); QList newShapes; Q_FOREACH (KoShape *shape, editableShapes) { KoPathShape *pathShape = dynamic_cast(shape); if (!pathShape) return; QList splitShapes; if (pathShape->separate(splitShapes)) { QList normalShapes = implicitCastList(splitShapes); KoShapeContainer *parent = shape->parent(); canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd); canvas()->shapeController()->removeShape(shape, cmd); newShapes << normalShapes; } } new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); } void DefaultTool::selectionAlign(int _align) { KoShapeAlignCommand::Align align = static_cast(_align); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } // 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 { bb = KoShape::absoluteOutlineRect(editableShapes); } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionDistribute(int _distribute) { KoShapeDistributeCommand::Distribute distribute = static_cast(_distribute); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.size() < 3) { return; } QRectF bb = KoShape::absoluteOutlineRect(editableShapes); KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, 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 = koSelection(); if (!selection) { return; } QList selectedShapes = selection->selectedEditableShapes(); if (selectedShapes.isEmpty()) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, 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) { KoSelection *selection = koSelection(); if (!selection) return nullptr; bool insideSelection = false; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); + isSelectingTextEditorButton(event->point); bool editableShape = !selection->selectedEditableShapes().isEmpty(); 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) { bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling(); return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, selection, event->point, handle); } // rotating is allowed for right mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, selection, event->point, event->buttons()); } } if (!selectMultiple && !selectNextInStack) { if (insideSelection) { return new ShapeMoveStrategy(this, selection, 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) { 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, selection, event->point); } return 0; } void DefaultTool::updateActions() { QList editableShapes; if (koSelection()) { editableShapes = koSelection()->selectedEditableShapes(); } const bool hasEditableShapes = !editableShapes.isEmpty(); action("object_order_front")->setEnabled(hasEditableShapes); action("object_order_raise")->setEnabled(hasEditableShapes); action("object_order_lower")->setEnabled(hasEditableShapes); action("object_order_back")->setEnabled(hasEditableShapes); action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes); action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes); action("object_transform_rotate_180")->setEnabled(hasEditableShapes); action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes); action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes); action("object_transform_reset")->setEnabled(hasEditableShapes); const bool multipleSelected = editableShapes.size() > 1; const bool alignmentEnabled = multipleSelected || (!editableShapes.isEmpty() && 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); const bool distributionEnabled = editableShapes.size() > 2; action("object_distribute_horizontal_left")->setEnabled(distributionEnabled); action("object_distribute_horizontal_center")->setEnabled(distributionEnabled); action("object_distribute_horizontal_right")->setEnabled(distributionEnabled); action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled); action("object_distribute_vertical_top")->setEnabled(distributionEnabled); action("object_distribute_vertical_center")->setEnabled(distributionEnabled); action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled); action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled); updateDistinctiveActions(editableShapes); emit selectionChanged(editableShapes.size()); } void DefaultTool::updateDistinctiveActions(const QList &editableShapes) { const bool multipleSelected = editableShapes.size() > 1; action("object_group")->setEnabled(multipleSelected); action("object_unite")->setEnabled(multipleSelected); action("object_intersect")->setEnabled(multipleSelected); action("object_subtract")->setEnabled(multipleSelected); bool hasShapesWithMultipleSegments = false; Q_FOREACH (KoShape *shape, editableShapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape && pathShape->subpathCount() > 1) { hasShapesWithMultipleSegments = true; break; } } action("object_split")->setEnabled(hasShapesWithMultipleSegments); bool hasGroupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { hasGroupShape = true; break; } } action("object_ungroup")->setEnabled(hasGroupShape); } 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")); } m_contextMenu->addSeparator(); QMenu *transform = m_contextMenu->addMenu(i18n("Transform")); transform->addAction(action("object_transform_rotate_90_cw")); transform->addAction(action("object_transform_rotate_90_ccw")); transform->addAction(action("object_transform_rotate_180")); transform->addSeparator(); transform->addAction(action("object_transform_mirror_horizontally")); transform->addAction(action("object_transform_mirror_vertically")); transform->addSeparator(); transform->addAction(action("object_transform_reset")); if (action("object_unite")->isEnabled() || action("object_intersect")->isEnabled() || action("object_subtract")->isEnabled() || action("object_split")->isEnabled()) { QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations")); transform->addAction(action("object_unite")); transform->addAction(action("object_intersect")); transform->addAction(action("object_subtract")); transform->addAction(action("object_split")); } } return m_contextMenu.data(); } void DefaultTool::addTransformActions(QMenu *menu) const { menu->addAction(action("object_transform_rotate_90_cw")); menu->addAction(action("object_transform_rotate_90_ccw")); menu->addAction(action("object_transform_rotate_180")); menu->addSeparator(); menu->addAction(action("object_transform_mirror_horizontally")); menu->addAction(action("object_transform_mirror_vertically")); menu->addSeparator(); menu->addAction(action("object_transform_reset")); } void DefaultTool::explicitUserStrokeEndRequest() { 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 feb2283603..88a318db9c 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.h +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.h @@ -1,189 +1,195 @@ /* 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 "SelectionDecorator.h" #include #include class QSignalMapper; class KoInteractionStrategy; class KoShapeMoveCommand; class KoSelection; class DefaultToolTabbedWidget; class KisViewManager; /** * 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); ~DefaultTool() override; enum CanvasResource { HotPosition = 1410100299 }; public: bool wantsAutoScroll() const override; void paint(QPainter &painter, const KoViewConverter &converter) override; void repaintDecorations() override; ///reimplemented void copy() const override; ///reimplemented void deleteSelection() override; ///reimplemented bool paste() override; ///reimplemented KoToolSelection *selection() override; 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); + /// similar to normal bounds handles, but checks to see if user wants to edit text + bool isSelectingTextEditorButton(const QPointF &point); + public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; private Q_SLOTS: void selectionAlign(int _align); void selectionDistribute(int _distribute); void selectionBringToFront(); void selectionSendToBack(); void selectionMoveUp(); void selectionMoveDown(); void selectionGroup(); void selectionUngroup(); void selectionTransform(int transformAction); void selectionBooleanOp(int booleanOp); void selectionSplitShapes(); void slotActivateEditFillGradient(bool value); void slotActivateEditStrokeGradient(bool value); protected Q_SLOTS: /// 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 explicitUserStrokeEndRequest() override; protected: QList > createOptionWidgets() override; KoInteractionStrategy *createStrategy(KoPointerEvent *event) override; protected: friend class SelectionInteractionStrategy; virtual bool isValidForCurrentLayer() const; virtual KoShapeManager *shapeManager() const; virtual KoSelection *koSelection() const; /** * Enable/disable actions specific to the tool (vector vs. reference images) */ virtual void updateDistinctiveActions(const QList &editableShapes); void addTransformActions(QMenu *menu) const; QScopedPointer m_contextMenu; 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 addMappedAction(QSignalMapper *mapper, const QString &actionId, int type); void selectionReorder(KoShapeReorderCommand::MoveShapeType order); bool moveSelection(int direction, Qt::KeyboardModifiers modifiers); /// Returns selection rectangle adjusted by handle proximity threshold QRectF handlesSize(); void canvasResourceChanged(int key, const QVariant &res) override; KoFlake::SelectionHandle m_lastHandle; KoFlake::AnchorPosition m_hotPosition; bool m_mouseWasInsideHandles; QPointF m_selectionBox[8]; QPolygonF m_selectionOutline; QPointF m_lastPoint; + SelectionDecorator *decorator; + // 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; DefaultToolTabbedWidget *m_tabbedOptionWidget; }; #endif diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp index f2171a7313..8b59a2e3d6 100644 --- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp +++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp @@ -1,172 +1,211 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2007 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 "SelectionDecorator.h" #include #include #include +#include +#include #include "kis_algebra_2d.h" #include "kis_debug.h" #include #include #include #include "KoShapeGradientHandles.h" +#include "kis_coordinates_converter.h" #include "kis_painting_tweaks.h" +#include "kis_icon_utils.h" #define HANDLE_DISTANCE 10 -SelectionDecorator::SelectionDecorator(KoCanvasResourceManager *resourceManager) +SelectionDecorator::SelectionDecorator(KoCanvasResourceManager* resourceManager, const KisCoordinatesConverter* coordConverter) : m_hotPosition(KoFlake::Center) , m_handleRadius(7) , m_lineWidth(2) , m_showFillGradientHandles(false) , m_showStrokeFillGradientHandles(false) { + + // this will help size the decorator buttons in the paint function later + m_coordConverter = const_cast(coordConverter); + + m_hotPosition = KoFlake::AnchorPosition( resourceManager->resource(KoFlake::HotPosition).toInt()); } void SelectionDecorator::setSelection(KoSelection *selection) { m_selection = selection; } void SelectionDecorator::setHandleRadius(int radius) { m_handleRadius = radius; m_lineWidth = qMax(1, (int)(radius / 2)); } void SelectionDecorator::setShowFillGradientHandles(bool value) { m_showFillGradientHandles = value; } void SelectionDecorator::setShowStrokeFillGradientHandles(bool value) { m_showStrokeFillGradientHandles = value; } +QPointF SelectionDecorator::textEditorButtonPos() +{ + return m_textEditorButtonPosition; +} + void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter) { QList selectedShapes = m_selection->selectedVisibleShapes(); if (selectedShapes.isEmpty()) return; const bool haveOnlyOneEditableShape = m_selection->selectedEditableShapes().size() == 1 && selectedShapes.size() == 1; bool editable = false; Q_FOREACH (KoShape *shape, KoShape::linearizeSubtree(selectedShapes)) { if (!haveOnlyOneEditableShape || !m_showStrokeFillGradientHandles) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::secondarySelection()); helper.drawRubberLine(shape->outlineRect()); } if (shape->isShapeEditable()) { editable = true; } } const QRectF handleArea = m_selection->outlineRect(); // draw extra rubber line around all the shapes if (selectedShapes.size() > 1) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_selection, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::primarySelection()); helper.drawRubberLine(handleArea); } // if we have no editable shape selected there // is no need drawing the selection handles if (editable) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_selection, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::primarySelection()); QPolygonF outline = handleArea; { helper.drawHandleRect(outline.value(0)); helper.drawHandleRect(outline.value(1)); helper.drawHandleRect(outline.value(2)); helper.drawHandleRect(outline.value(3)); helper.drawHandleRect(0.5 * (outline.value(0) + outline.value(1))); helper.drawHandleRect(0.5 * (outline.value(1) + outline.value(2))); helper.drawHandleRect(0.5 * (outline.value(2) + outline.value(3))); helper.drawHandleRect(0.5 * (outline.value(3) + outline.value(0))); QPointF hotPos = KoFlake::anchorToPoint(m_hotPosition, handleArea); helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles()); helper.drawHandleRect(hotPos); } } if (haveOnlyOneEditableShape) { KoShape *shape = selectedShapes.first(); + + // draw a button with "edit text" on it that activates the text editor + KoSvgTextShape *textShape = dynamic_cast(shape); + if (textShape) { + KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_selection, converter, m_handleRadius); + + QPolygonF outline = handleArea; + m_textEditorButtonPosition = QPointF(0.5 * (outline.value(2) + outline.value(3))); + m_textEditorButtonPosition += QPointF(0, 10); + + const QPointF finalHandleRect = m_textEditorButtonPosition; + helper.drawHandleRect(finalHandleRect, 15); + + + // T icon inside box + QSize buttonSize(20,20); + const QPixmap textEditorIcon = KisIconUtils::loadIcon("draw-text").pixmap(buttonSize); + const QRectF iconSourceRect(QPointF(0, 0), textEditorIcon.size()); + helper.drawPixmap(textEditorIcon, m_textEditorButtonPosition, 20, iconSourceRect); // icon, position, size, sourceRect + + } else { + + } + + if (m_showFillGradientHandles) { paintGradientHandles(shape, KoFlake::Fill, painter, converter); } else if (m_showStrokeFillGradientHandles) { paintGradientHandles(shape, KoFlake::StrokeFill, painter, converter); } } } void SelectionDecorator::paintGradientHandles(KoShape *shape, KoFlake::FillVariant fillVariant, QPainter &painter, const KoViewConverter &converter) { KoShapeGradientHandles gradientHandles(fillVariant, shape); QVector handles = gradientHandles.handles(); KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); const QTransform t = shape->absoluteTransformation(0).inverted(); if (gradientHandles.type() == QGradient::LinearGradient) { KIS_SAFE_ASSERT_RECOVER_NOOP(handles.size() == 2); if (handles.size() == 2) { helper.setHandleStyle(KisHandleStyle::gradientArrows()); helper.drawGradientArrow(t.map(handles[0].pos), t.map(handles[1].pos), 1.5 * m_handleRadius); } } helper.setHandleStyle(KisHandleStyle::gradientHandles()); Q_FOREACH (const KoShapeGradientHandles::Handle &h, handles) { if (h.type == KoShapeGradientHandles::Handle::RadialCenter) { helper.drawGradientCrossHandle(t.map(h.pos), 1.2 * m_handleRadius); } else { helper.drawGradientHandle(t.map(h.pos), 1.2 * m_handleRadius); } } } diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h index 8b679749f2..55980ad4ac 100644 --- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h +++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h @@ -1,92 +1,98 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2007 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 SELECTIONDECORATOR_H #define SELECTIONDECORATOR_H #include #include +#include #include #include class KoSelection; class KoCanvasResourceManager; /** * The SelectionDecorator is used to paint extra user-interface items on top of a selection. */ class SelectionDecorator { public: /** * Constructor. * @param arrows the direction that needs highlighting. (currently unused) * @param rotationHandles if true; the rotation handles will be drawn * @param shearHandles if true; the shearhandles will be drawn */ - SelectionDecorator(KoCanvasResourceManager *resourceManager); + SelectionDecorator(KoCanvasResourceManager *resourceManager, const KisCoordinatesConverter* coordConverter); ~SelectionDecorator() {} /** * paint the decortations. * @param painter the painter to paint to. * @param converter to convert between internal and view coordinates. */ void paint(QPainter &painter, const KoViewConverter &converter); /** * set the selection that is to be painted. * @param selection the current selection. */ void setSelection(KoSelection *selection); /** * set the radius of the selection handles * @param radius the new handle radius */ void setHandleRadius(int radius); /** * Set true if you want to render gradient handles on the canvas. * Default value: false */ void setShowFillGradientHandles(bool value); /** * Set true if you want to render gradient handles on the canvas. * Default value: false */ void setShowStrokeFillGradientHandles(bool value); + QPointF textEditorButtonPos(); + private: void paintGradientHandles(KoShape *shape, KoFlake::FillVariant fillVariant, QPainter &painter, const KoViewConverter &converter); private: KoFlake::AnchorPosition m_hotPosition; KoSelection *m_selection; int m_handleRadius; int m_lineWidth; bool m_showFillGradientHandles; bool m_showStrokeFillGradientHandles; + QPointF m_textEditorButtonPosition; + + KisCoordinatesConverter *m_coordConverter; }; #endif