File indexing completed on 2024-06-23 04:28:10
0001 /* This file is part of the KDE project 0002 0003 SPDX-FileCopyrightText: 2006-2008 Thorsten Zachmann <zachmann@kde.org> 0004 SPDX-FileCopyrightText: 2006-2010 Thomas Zander <zander@kde.org> 0005 SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net> 0006 SPDX-FileCopyrightText: 2008 C. Boemann <cbo@boemann.dk> 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "DefaultTool.h" 0012 #include "DefaultToolGeometryWidget.h" 0013 #include "DefaultToolTabbedWidget.h" 0014 #include "SelectionDecorator.h" 0015 #include "ShapeMoveStrategy.h" 0016 #include "ShapeRotateStrategy.h" 0017 #include "ShapeShearStrategy.h" 0018 #include "ShapeResizeStrategy.h" 0019 0020 #include <KoPointerEvent.h> 0021 #include <KoToolSelection.h> 0022 #include <KoToolManager.h> 0023 #include <KoSelection.h> 0024 #include <KoShapeController.h> 0025 #include <KoShapeManager.h> 0026 #include <KoSelectedShapesProxy.h> 0027 #include <KoShapeGroup.h> 0028 #include <KoShapeLayer.h> 0029 #include <KoPathShape.h> 0030 #include <KoDrag.h> 0031 #include <KoCanvasBase.h> 0032 #include <KoCanvasResourceProvider.h> 0033 #include <KoShapeRubberSelectStrategy.h> 0034 #include <commands/KoShapeMoveCommand.h> 0035 #include <commands/KoShapeTransformCommand.h> 0036 #include <commands/KoShapeDeleteCommand.h> 0037 #include <commands/KoShapeCreateCommand.h> 0038 #include <commands/KoShapeGroupCommand.h> 0039 #include <commands/KoShapeUngroupCommand.h> 0040 #include <commands/KoShapeDistributeCommand.h> 0041 #include <commands/KoKeepShapesSelectedCommand.h> 0042 #include <KoSnapGuide.h> 0043 #include <KoStrokeConfigWidget.h> 0044 #include "kis_action_registry.h" 0045 #include "kis_node.h" 0046 #include "kis_node_manager.h" 0047 #include "KisViewManager.h" 0048 #include "kis_canvas2.h" 0049 #include "kis_canvas_resource_provider.h" 0050 #include <KoInteractionStrategyFactory.h> 0051 0052 #include "kis_document_aware_spin_box_unit_manager.h" 0053 0054 #include <KoIcon.h> 0055 0056 #include <QPainterPath> 0057 #include <QPointer> 0058 #include <QAction> 0059 #include <QKeyEvent> 0060 #include <QTimer> 0061 #include <KisSignalMapper.h> 0062 #include <KoResourcePaths.h> 0063 0064 #include <KoCanvasController.h> 0065 #include <kactioncollection.h> 0066 #include <QMenu> 0067 0068 #include <math.h> 0069 #include "kis_assert.h" 0070 #include "kis_global.h" 0071 #include "kis_debug.h" 0072 #include "krita_utils.h" 0073 0074 #include <QVector2D> 0075 0076 #define HANDLE_DISTANCE 10 0077 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE) 0078 0079 #define INNER_HANDLE_DISTANCE_SQ 16 0080 0081 namespace { 0082 static const QString EditFillGradientFactoryId = "edit_fill_gradient"; 0083 static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient"; 0084 static const QString EditFillMeshGradientFactoryId = "edit_fill_meshgradient"; 0085 0086 enum TransformActionType { 0087 TransformRotate90CW, 0088 TransformRotate90CCW, 0089 TransformRotate180, 0090 TransformMirrorX, 0091 TransformMirrorY, 0092 TransformReset 0093 }; 0094 0095 enum BooleanOp { 0096 BooleanUnion, 0097 BooleanIntersection, 0098 BooleanSubtraction 0099 }; 0100 0101 } 0102 0103 class NopInteractionStrategy : public KoInteractionStrategy 0104 { 0105 public: 0106 explicit NopInteractionStrategy(KoToolBase *parent) 0107 : KoInteractionStrategy(parent) 0108 { 0109 } 0110 0111 KUndo2Command *createCommand() override 0112 { 0113 return 0; 0114 } 0115 0116 void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} 0117 void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} 0118 0119 void paint(QPainter &painter, const KoViewConverter &converter) override { 0120 Q_UNUSED(painter); 0121 Q_UNUSED(converter); 0122 } 0123 }; 0124 0125 class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy 0126 { 0127 public: 0128 explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid) 0129 : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid) 0130 { 0131 } 0132 0133 void paint(QPainter &painter, const KoViewConverter &converter) override { 0134 KoShapeRubberSelectStrategy::paint(painter, converter); 0135 } 0136 0137 void cancelInteraction() override 0138 { 0139 tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect()); 0140 } 0141 0142 void finishInteraction(Qt::KeyboardModifiers modifiers = QFlags<Qt::KeyboardModifier>()) override 0143 { 0144 Q_UNUSED(modifiers); 0145 DefaultTool *defaultTool = dynamic_cast<DefaultTool*>(tool()); 0146 KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool); 0147 0148 KoSelection * selection = defaultTool->koSelection(); 0149 0150 const bool useContainedMode = currentMode() == CoveringSelection; 0151 0152 QList<KoShape *> shapes = 0153 defaultTool->shapeManager()-> 0154 shapesAt(selectedRectangle(), true, useContainedMode); 0155 0156 Q_FOREACH (KoShape * shape, shapes) { 0157 if (!shape->isSelectable()) continue; 0158 0159 selection->select(shape); 0160 } 0161 0162 tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect()); 0163 } 0164 }; 0165 #include <KoGradientBackground.h> 0166 #include "KoShapeGradientHandles.h" 0167 #include "ShapeGradientEditStrategy.h" 0168 0169 class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory 0170 { 0171 public: 0172 MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, 0173 int priority, const QString &id, DefaultTool *_q) 0174 : KoInteractionStrategyFactory(priority, id), 0175 q(_q), 0176 m_fillVariant(fillVariant) 0177 { 0178 } 0179 0180 KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override 0181 { 0182 m_currentHandle = handleAt(ev->point); 0183 0184 if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { 0185 KoShape *shape = onlyEditableShape(); 0186 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); 0187 0188 return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point); 0189 } 0190 0191 return 0; 0192 } 0193 0194 bool hoverEvent(KoPointerEvent *ev) override 0195 { 0196 m_currentHandle = handleAt(ev->point); 0197 return false; 0198 } 0199 0200 bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override 0201 { 0202 Q_UNUSED(painter); 0203 Q_UNUSED(converter); 0204 return false; 0205 } 0206 0207 bool tryUseCustomCursor() override { 0208 if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { 0209 q->useCursor(Qt::OpenHandCursor); 0210 return true; 0211 } 0212 0213 return false; 0214 } 0215 0216 private: 0217 0218 KoShape* onlyEditableShape() const { 0219 KoSelection *selection = q->koSelection(); 0220 QList<KoShape*> shapes = selection->selectedEditableShapes(); 0221 0222 KoShape *shape = 0; 0223 if (shapes.size() == 1) { 0224 shape = shapes.first(); 0225 } 0226 0227 return shape; 0228 } 0229 0230 KoShapeGradientHandles::Handle handleAt(const QPointF &pos) { 0231 KoShapeGradientHandles::Handle result; 0232 0233 KoShape *shape = onlyEditableShape(); 0234 if (shape) { 0235 KoFlake::SelectionHandle globalHandle = q->handleAt(pos); 0236 const qreal distanceThresholdSq = 0237 globalHandle == KoFlake::NoHandle ? 0238 HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; 0239 0240 const KoViewConverter *converter = q->canvas()->viewConverter(); 0241 const QPointF viewPoint = converter->documentToView(pos); 0242 qreal minDistanceSq = std::numeric_limits<qreal>::max(); 0243 0244 KoShapeGradientHandles sh(m_fillVariant, shape); 0245 auto handless = sh.handles(); 0246 Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) { 0247 const QPointF handlePoint = converter->documentToView(handle.pos); 0248 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); 0249 0250 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { 0251 result = handle; 0252 minDistanceSq = distanceSq; 0253 } 0254 } 0255 } 0256 0257 return result; 0258 } 0259 0260 private: 0261 DefaultTool *q; 0262 KoFlake::FillVariant m_fillVariant; 0263 KoShapeGradientHandles::Handle m_currentHandle; 0264 }; 0265 0266 #include "KoShapeMeshGradientHandles.h" 0267 #include "ShapeMeshGradientEditStrategy.h" 0268 0269 class DefaultTool::MoveMeshGradientHandleInteractionFactory: public KoInteractionStrategyFactory 0270 { 0271 public: 0272 MoveMeshGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, 0273 int priority, 0274 const QString& id, 0275 DefaultTool* _q) 0276 : KoInteractionStrategyFactory(priority, id) 0277 , m_fillVariant(fillVariant) 0278 , q(_q) 0279 { 0280 } 0281 0282 KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override 0283 { 0284 m_currentHandle = handleAt(ev->point); 0285 q->m_selectedMeshHandle = m_currentHandle; 0286 emit q->meshgradientHandleSelected(m_currentHandle); 0287 0288 0289 if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) { 0290 KoShape *shape = onlyEditableShape(); 0291 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); 0292 0293 return new ShapeMeshGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle, ev->point); 0294 } 0295 0296 return nullptr; 0297 } 0298 0299 bool hoverEvent(KoPointerEvent *ev) override 0300 { 0301 // for custom cursor 0302 KoShapeMeshGradientHandles::Handle handle = handleAt(ev->point); 0303 0304 // refresh 0305 if (handle.type != m_currentHandle.type && handle.type == KoShapeMeshGradientHandles::Handle::None) { 0306 q->repaintDecorations(); 0307 } 0308 0309 m_currentHandle = handle; 0310 q->m_hoveredMeshHandle = m_currentHandle; 0311 0312 // highlight the decoration which is being hovered 0313 if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) { 0314 q->repaintDecorations(); 0315 } 0316 return false; 0317 } 0318 0319 bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override 0320 { 0321 Q_UNUSED(painter); 0322 Q_UNUSED(converter); 0323 return false; 0324 } 0325 0326 bool tryUseCustomCursor() override 0327 { 0328 if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) { 0329 q->useCursor(Qt::OpenHandCursor); 0330 return true; 0331 } 0332 0333 return false; 0334 } 0335 0336 0337 private: 0338 KoShape* onlyEditableShape() const { 0339 // FIXME: copy of KoShapeGradientHandles 0340 KoSelection *selection = q->koSelection(); 0341 QList<KoShape*> shapes = selection->selectedEditableShapes(); 0342 0343 KoShape *shape = 0; 0344 if (shapes.size() == 1) { 0345 shape = shapes.first(); 0346 } 0347 0348 return shape; 0349 } 0350 0351 KoShapeMeshGradientHandles::Handle handleAt(const QPointF &pos) const 0352 { 0353 // FIXME: copy of KoShapeGradientHandles. use a template? 0354 KoShapeMeshGradientHandles::Handle result; 0355 0356 KoShape *shape = onlyEditableShape(); 0357 if (shape) { 0358 KoFlake::SelectionHandle globalHandle = q->handleAt(pos); 0359 const qreal distanceThresholdSq = 0360 globalHandle == KoFlake::NoHandle ? 0361 HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; 0362 0363 const KoViewConverter *converter = q->canvas()->viewConverter(); 0364 const QPointF viewPoint = converter->documentToView(pos); 0365 qreal minDistanceSq = std::numeric_limits<qreal>::max(); 0366 0367 KoShapeMeshGradientHandles sh(m_fillVariant, shape); 0368 0369 for (const auto& handle: sh.handles()) { 0370 const QPointF handlePoint = converter->documentToView(handle.pos); 0371 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); 0372 0373 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { 0374 result = handle; 0375 minDistanceSq = distanceSq; 0376 } 0377 } 0378 } 0379 0380 return result; 0381 } 0382 0383 private: 0384 KoFlake::FillVariant m_fillVariant; 0385 KoShapeMeshGradientHandles::Handle m_currentHandle; 0386 DefaultTool *q; 0387 }; 0388 0389 class SelectionHandler : public KoToolSelection 0390 { 0391 public: 0392 SelectionHandler(DefaultTool *parent) 0393 : KoToolSelection(parent) 0394 , m_selection(parent->koSelection()) 0395 { 0396 } 0397 0398 bool hasSelection() override 0399 { 0400 if (m_selection) { 0401 return m_selection->count(); 0402 } 0403 return false; 0404 } 0405 0406 private: 0407 QPointer<KoSelection> m_selection; 0408 }; 0409 0410 DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy) 0411 : KoInteractionTool(canvas) 0412 , m_lastHandle(KoFlake::NoHandle) 0413 , m_hotPosition(KoFlake::TopLeft) 0414 , m_mouseWasInsideHandles(false) 0415 , m_selectionHandler(new SelectionHandler(this)) 0416 , m_tabbedOptionWidget(0) 0417 { 0418 setupActions(); 0419 0420 QPixmap rotatePixmap, shearPixmap; 0421 rotatePixmap.load(":/cursor_rotate.png"); 0422 Q_ASSERT(!rotatePixmap.isNull()); 0423 shearPixmap.load(":/cursor_shear.png"); 0424 Q_ASSERT(!shearPixmap.isNull()); 0425 0426 m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); 0427 m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); 0428 m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); 0429 m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); 0430 m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); 0431 m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); 0432 m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); 0433 m_rotateCursors[7] = QCursor(rotatePixmap); 0434 /* 0435 m_rotateCursors[0] = QCursor(Qt::RotateNCursor); 0436 m_rotateCursors[1] = QCursor(Qt::RotateNECursor); 0437 m_rotateCursors[2] = QCursor(Qt::RotateECursor); 0438 m_rotateCursors[3] = QCursor(Qt::RotateSECursor); 0439 m_rotateCursors[4] = QCursor(Qt::RotateSCursor); 0440 m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); 0441 m_rotateCursors[6] = QCursor(Qt::RotateWCursor); 0442 m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); 0443 */ 0444 m_shearCursors[0] = QCursor(shearPixmap); 0445 m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); 0446 m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); 0447 m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); 0448 m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); 0449 m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); 0450 m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); 0451 m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); 0452 m_sizeCursors[0] = Qt::SizeVerCursor; 0453 m_sizeCursors[1] = Qt::SizeBDiagCursor; 0454 m_sizeCursors[2] = Qt::SizeHorCursor; 0455 m_sizeCursors[3] = Qt::SizeFDiagCursor; 0456 m_sizeCursors[4] = Qt::SizeVerCursor; 0457 m_sizeCursors[5] = Qt::SizeBDiagCursor; 0458 m_sizeCursors[6] = Qt::SizeHorCursor; 0459 m_sizeCursors[7] = Qt::SizeFDiagCursor; 0460 0461 if (connectToSelectedShapesProxy) { 0462 connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions())); 0463 0464 connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(repaintDecorations())); 0465 connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(repaintDecorations())); 0466 } 0467 } 0468 0469 DefaultTool::~DefaultTool() 0470 { 0471 } 0472 0473 void DefaultTool::slotActivateEditFillGradient(bool value) 0474 { 0475 if (value) { 0476 addInteractionFactory( 0477 new MoveGradientHandleInteractionFactory(KoFlake::Fill, 0478 1, EditFillGradientFactoryId, this)); 0479 } else { 0480 removeInteractionFactory(EditFillGradientFactoryId); 0481 } 0482 repaintDecorations(); 0483 } 0484 0485 void DefaultTool::slotActivateEditStrokeGradient(bool value) 0486 { 0487 if (value) { 0488 addInteractionFactory( 0489 new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill, 0490 0, EditStrokeGradientFactoryId, this)); 0491 } else { 0492 removeInteractionFactory(EditStrokeGradientFactoryId); 0493 } 0494 repaintDecorations(); 0495 } 0496 0497 void DefaultTool::slotActivateEditFillMeshGradient(bool value) 0498 { 0499 if (value) { 0500 connect(this, SIGNAL(meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)), 0501 m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle))); 0502 addInteractionFactory( 0503 new MoveMeshGradientHandleInteractionFactory(KoFlake::Fill, 1, 0504 EditFillMeshGradientFactoryId, this)); 0505 } else { 0506 disconnect(this, SIGNAL(meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)), 0507 m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle))); 0508 removeInteractionFactory(EditFillMeshGradientFactoryId); 0509 } 0510 } 0511 0512 void DefaultTool::slotResetMeshGradientState() 0513 { 0514 m_selectedMeshHandle = KoShapeMeshGradientHandles::Handle(); 0515 } 0516 0517 bool DefaultTool::wantsAutoScroll() const 0518 { 0519 return true; 0520 } 0521 0522 void DefaultTool::addMappedAction(KisSignalMapper *mapper, const QString &actionId, int commandType) 0523 { 0524 QAction *a =action(actionId); 0525 connect(a, SIGNAL(triggered()), mapper, SLOT(map())); 0526 mapper->setMapping(a, commandType); 0527 } 0528 0529 void DefaultTool::setupActions() 0530 { 0531 m_alignSignalsMapper = new KisSignalMapper(this); 0532 0533 addMappedAction(m_alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment); 0534 addMappedAction(m_alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment); 0535 addMappedAction(m_alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment); 0536 addMappedAction(m_alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment); 0537 addMappedAction(m_alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment); 0538 addMappedAction(m_alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment); 0539 0540 m_distributeSignalsMapper = new KisSignalMapper(this); 0541 0542 addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution); 0543 addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution); 0544 addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution); 0545 addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution); 0546 0547 addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution); 0548 addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution); 0549 addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution); 0550 addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution); 0551 0552 m_transformSignalsMapper = new KisSignalMapper(this); 0553 0554 addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW); 0555 addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW); 0556 addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180); 0557 addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX); 0558 addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY); 0559 addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset); 0560 0561 m_booleanSignalsMapper = new KisSignalMapper(this); 0562 0563 addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion); 0564 addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection); 0565 addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction); 0566 0567 m_contextMenu.reset(new QMenu()); 0568 } 0569 0570 qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) 0571 { 0572 QPointF selectionCenter = koSelection()->absolutePosition(); 0573 QPointF direction; 0574 0575 switch (handle) { 0576 case KoFlake::TopMiddleHandle: 0577 if (useEdgeRotation) { 0578 direction = koSelection()->absolutePosition(KoFlake::TopRight) 0579 - koSelection()->absolutePosition(KoFlake::TopLeft); 0580 } else { 0581 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); 0582 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition); 0583 direction = handlePosition - selectionCenter; 0584 } 0585 break; 0586 case KoFlake::TopRightHandle: 0587 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF(); 0588 break; 0589 case KoFlake::RightMiddleHandle: 0590 if (useEdgeRotation) { 0591 direction = koSelection()->absolutePosition(KoFlake::BottomRight) 0592 - koSelection()->absolutePosition(KoFlake::TopRight); 0593 } else { 0594 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight); 0595 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); 0596 direction = handlePosition - selectionCenter; 0597 } 0598 break; 0599 case KoFlake::BottomRightHandle: 0600 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF(); 0601 break; 0602 case KoFlake::BottomMiddleHandle: 0603 if (useEdgeRotation) { 0604 direction = koSelection()->absolutePosition(KoFlake::BottomLeft) 0605 - koSelection()->absolutePosition(KoFlake::BottomRight); 0606 } else { 0607 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft); 0608 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); 0609 direction = handlePosition - selectionCenter; 0610 } 0611 break; 0612 case KoFlake::BottomLeftHandle: 0613 direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter; 0614 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF(); 0615 break; 0616 case KoFlake::LeftMiddleHandle: 0617 if (useEdgeRotation) { 0618 direction = koSelection()->absolutePosition(KoFlake::TopLeft) 0619 - koSelection()->absolutePosition(KoFlake::BottomLeft); 0620 } else { 0621 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); 0622 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition); 0623 direction = handlePosition - selectionCenter; 0624 } 0625 break; 0626 case KoFlake::TopLeftHandle: 0627 direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter; 0628 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF(); 0629 break; 0630 case KoFlake::NoHandle: 0631 return 0.0; 0632 break; 0633 } 0634 0635 qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; 0636 0637 switch (handle) { 0638 case KoFlake::TopMiddleHandle: 0639 if (useEdgeRotation) { 0640 rotation -= 0.0; 0641 } else { 0642 rotation -= 270.0; 0643 } 0644 break; 0645 case KoFlake::TopRightHandle: 0646 rotation -= 315.0; 0647 break; 0648 case KoFlake::RightMiddleHandle: 0649 if (useEdgeRotation) { 0650 rotation -= 90.0; 0651 } else { 0652 rotation -= 0.0; 0653 } 0654 break; 0655 case KoFlake::BottomRightHandle: 0656 rotation -= 45.0; 0657 break; 0658 case KoFlake::BottomMiddleHandle: 0659 if (useEdgeRotation) { 0660 rotation -= 180.0; 0661 } else { 0662 rotation -= 90.0; 0663 } 0664 break; 0665 case KoFlake::BottomLeftHandle: 0666 rotation -= 135.0; 0667 break; 0668 case KoFlake::LeftMiddleHandle: 0669 if (useEdgeRotation) { 0670 rotation -= 270.0; 0671 } else { 0672 rotation -= 180.0; 0673 } 0674 break; 0675 case KoFlake::TopLeftHandle: 0676 rotation -= 225.0; 0677 break; 0678 default: 0679 ; 0680 } 0681 0682 if (rotation < 0.0) { 0683 rotation += 360.0; 0684 } 0685 0686 return rotation; 0687 } 0688 0689 void DefaultTool::updateCursor() 0690 { 0691 if (tryUseCustomCursor()) return; 0692 0693 QCursor cursor = Qt::ArrowCursor; 0694 0695 QString statusText; 0696 0697 KoSelection *selection = koSelection(); 0698 if (selection && selection->count() > 0) { // has a selection 0699 bool editable = !selection->selectedEditableShapes().isEmpty(); 0700 0701 if (!m_mouseWasInsideHandles) { 0702 m_angle = rotationOfHandle(m_lastHandle, true); 0703 int rotOctant = 8 + int(8.5 + m_angle / 45); 0704 0705 bool rotateHandle = false; 0706 bool shearHandle = false; 0707 switch (m_lastHandle) { 0708 case KoFlake::TopMiddleHandle: 0709 cursor = m_shearCursors[(0 + rotOctant) % 8]; 0710 shearHandle = true; 0711 break; 0712 case KoFlake::TopRightHandle: 0713 cursor = m_rotateCursors[(1 + rotOctant) % 8]; 0714 rotateHandle = true; 0715 break; 0716 case KoFlake::RightMiddleHandle: 0717 cursor = m_shearCursors[(2 + rotOctant) % 8]; 0718 shearHandle = true; 0719 break; 0720 case KoFlake::BottomRightHandle: 0721 cursor = m_rotateCursors[(3 + rotOctant) % 8]; 0722 rotateHandle = true; 0723 break; 0724 case KoFlake::BottomMiddleHandle: 0725 cursor = m_shearCursors[(4 + rotOctant) % 8]; 0726 shearHandle = true; 0727 break; 0728 case KoFlake::BottomLeftHandle: 0729 cursor = m_rotateCursors[(5 + rotOctant) % 8]; 0730 rotateHandle = true; 0731 break; 0732 case KoFlake::LeftMiddleHandle: 0733 cursor = m_shearCursors[(6 + rotOctant) % 8]; 0734 shearHandle = true; 0735 break; 0736 case KoFlake::TopLeftHandle: 0737 cursor = m_rotateCursors[(7 + rotOctant) % 8]; 0738 rotateHandle = true; 0739 break; 0740 case KoFlake::NoHandle: 0741 cursor = Qt::ArrowCursor; 0742 break; 0743 } 0744 if (rotateHandle) { 0745 statusText = i18n("Left click rotates around center, right click around highlighted position."); 0746 } 0747 if (shearHandle) { 0748 statusText = i18n("Click and drag to shear selection."); 0749 } 0750 0751 0752 } else { 0753 statusText = i18n("Click and drag to resize selection."); 0754 m_angle = rotationOfHandle(m_lastHandle, false); 0755 int rotOctant = 8 + int(8.5 + m_angle / 45); 0756 bool cornerHandle = false; 0757 switch (m_lastHandle) { 0758 case KoFlake::TopMiddleHandle: 0759 cursor = m_sizeCursors[(0 + rotOctant) % 8]; 0760 break; 0761 case KoFlake::TopRightHandle: 0762 cursor = m_sizeCursors[(1 + rotOctant) % 8]; 0763 cornerHandle = true; 0764 break; 0765 case KoFlake::RightMiddleHandle: 0766 cursor = m_sizeCursors[(2 + rotOctant) % 8]; 0767 break; 0768 case KoFlake::BottomRightHandle: 0769 cursor = m_sizeCursors[(3 + rotOctant) % 8]; 0770 cornerHandle = true; 0771 break; 0772 case KoFlake::BottomMiddleHandle: 0773 cursor = m_sizeCursors[(4 + rotOctant) % 8]; 0774 break; 0775 case KoFlake::BottomLeftHandle: 0776 cursor = m_sizeCursors[(5 + rotOctant) % 8]; 0777 cornerHandle = true; 0778 break; 0779 case KoFlake::LeftMiddleHandle: 0780 cursor = m_sizeCursors[(6 + rotOctant) % 8]; 0781 break; 0782 case KoFlake::TopLeftHandle: 0783 cursor = m_sizeCursors[(7 + rotOctant) % 8]; 0784 cornerHandle = true; 0785 break; 0786 case KoFlake::NoHandle: 0787 cursor = Qt::SizeAllCursor; 0788 statusText = i18n("Click and drag to move selection."); 0789 break; 0790 } 0791 if (cornerHandle) { 0792 statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); 0793 } 0794 } 0795 if (!editable) { 0796 cursor = Qt::ArrowCursor; 0797 } 0798 } else { 0799 // there used to be guides... :'''( 0800 } 0801 useCursor(cursor); 0802 if (currentStrategy() == 0) { 0803 emit statusTextChanged(statusText); 0804 } 0805 } 0806 0807 void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) 0808 { 0809 KoSelection *selection = koSelection(); 0810 if (selection) { 0811 m_decorator.reset(new SelectionDecorator(canvas()->resourceManager())); 0812 0813 { 0814 /** 0815 * Selection masks don't render the outline of the shapes, so we should 0816 * do that explicitly when rendering them via selection 0817 */ 0818 0819 KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas()); 0820 KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode(); 0821 const bool isSelectionMask = node && node->inherits("KisSelectionMask"); 0822 m_decorator->setForceShapeOutlines(isSelectionMask); 0823 } 0824 0825 m_decorator->setSelection(selection); 0826 m_decorator->setHandleRadius(handleRadius()); 0827 m_decorator->setDecorationThickness(decorationThickness()); 0828 m_decorator->setShowFillGradientHandles(hasInteractionFactory(EditFillGradientFactoryId)); 0829 m_decorator->setShowStrokeFillGradientHandles(hasInteractionFactory(EditStrokeGradientFactoryId)); 0830 m_decorator->setShowFillMeshGradientHandles(hasInteractionFactory(EditFillMeshGradientFactoryId)); 0831 m_decorator->setCurrentMeshGradientHandles(m_selectedMeshHandle, m_hoveredMeshHandle); 0832 m_decorator->paint(painter, converter); 0833 } 0834 0835 KoInteractionTool::paint(painter, converter); 0836 0837 painter.save(); 0838 painter.setTransform(converter.documentToView(), true); 0839 canvas()->snapGuide()->paint(painter, converter); 0840 painter.restore(); 0841 } 0842 0843 bool DefaultTool::isValidForCurrentLayer() const 0844 { 0845 // if the currently active node has a shape manager, then it is 0846 // probably our client :) 0847 0848 KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas()); 0849 return bool(kisCanvas->localShapeManager()); 0850 } 0851 0852 KoShapeManager *DefaultTool::shapeManager() const { 0853 return canvas()->shapeManager(); 0854 } 0855 0856 void DefaultTool::mousePressEvent(KoPointerEvent *event) 0857 { 0858 // this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it 0859 if (!isValidForCurrentLayer()) { 0860 KisCanvas2 *kiscanvas = static_cast<KisCanvas2 *>(canvas()); 0861 kiscanvas->viewManager()->showFloatingMessage( 0862 i18n("This tool only works on vector layers. You probably want the move tool."), 0863 QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); 0864 return; 0865 } 0866 0867 KoInteractionTool::mousePressEvent(event); 0868 updateCursor(); 0869 } 0870 0871 void DefaultTool::mouseMoveEvent(KoPointerEvent *event) 0872 { 0873 KoInteractionTool::mouseMoveEvent(event); 0874 if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { 0875 QRectF bound = handlesSize(); 0876 0877 if (bound.contains(event->point)) { 0878 bool inside; 0879 KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); 0880 0881 if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { 0882 m_lastHandle = newDirection; 0883 m_mouseWasInsideHandles = inside; 0884 } 0885 } else { 0886 m_lastHandle = KoFlake::NoHandle; 0887 m_mouseWasInsideHandles = false; 0888 0889 // there used to be guides... :'''( 0890 } 0891 } else { 0892 // there used to be guides... :'''( 0893 } 0894 0895 0896 updateCursor(); 0897 } 0898 0899 QRectF DefaultTool::handlesSize() 0900 { 0901 KoSelection *selection = koSelection(); 0902 if (!selection || !selection->count()) return QRectF(); 0903 0904 recalcSelectionBox(selection); 0905 0906 QRectF bound = m_selectionOutline.boundingRect(); 0907 0908 // expansion Border 0909 if (!canvas() || !canvas()->viewConverter()) { 0910 return bound; 0911 } 0912 0913 QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); 0914 bound.adjust(-border.x(), -border.y(), border.x(), border.y()); 0915 return bound; 0916 } 0917 0918 void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) 0919 { 0920 KoInteractionTool::mouseReleaseEvent(event); 0921 updateCursor(); 0922 } 0923 0924 void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) 0925 { 0926 KoSelection *selection = koSelection(); 0927 0928 KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); 0929 if (shape && selection && !selection->isSelected(shape)) { 0930 0931 if (!(event->modifiers() & Qt::ShiftModifier)) { 0932 selection->deselectAll(); 0933 } 0934 0935 selection->select(shape); 0936 } 0937 0938 explicitUserStrokeEndRequest(); 0939 } 0940 0941 bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) 0942 { 0943 bool result = false; 0944 0945 qreal x = 0.0, y = 0.0; 0946 if (direction == Qt::Key_Left) { 0947 x = -5; 0948 } else if (direction == Qt::Key_Right) { 0949 x = 5; 0950 } else if (direction == Qt::Key_Up) { 0951 y = -5; 0952 } else if (direction == Qt::Key_Down) { 0953 y = 5; 0954 } 0955 0956 if (x != 0.0 || y != 0.0) { // actually move 0957 0958 if ((modifiers & Qt::ShiftModifier) != 0) { 0959 x *= 10; 0960 y *= 10; 0961 } else if ((modifiers & Qt::AltModifier) != 0) { // more precise 0962 x /= 5; 0963 y /= 5; 0964 } 0965 0966 QList<KoShape *> shapes = koSelection()->selectedEditableShapes(); 0967 0968 if (!shapes.isEmpty()) { 0969 canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y))); 0970 result = true; 0971 } 0972 } 0973 0974 return result; 0975 } 0976 0977 void DefaultTool::keyPressEvent(QKeyEvent *event) 0978 { 0979 KoInteractionTool::keyPressEvent(event); 0980 if (currentStrategy() == 0) { 0981 switch (event->key()) { 0982 case Qt::Key_Left: 0983 case Qt::Key_Right: 0984 case Qt::Key_Up: 0985 case Qt::Key_Down: 0986 if (moveSelection(event->key(), event->modifiers())) { 0987 event->accept(); 0988 } 0989 break; 0990 default: 0991 return; 0992 } 0993 } 0994 } 0995 0996 QRectF DefaultTool::decorationsRect() const 0997 { 0998 QRectF dirtyRect; 0999 1000 if (koSelection() && koSelection()->count() > 0) { 1001 /// TODO: avoid cons_cast by implementing proper 1002 /// caching strategy inrecalcSelectionBox() and 1003 /// handlesSize() 1004 dirtyRect = const_cast<DefaultTool*>(this)->handlesSize(); 1005 } 1006 1007 if (canvas()->snapGuide()->isSnapping()) { 1008 dirtyRect |= canvas()->snapGuide()->boundingRect(); 1009 } 1010 1011 return dirtyRect; 1012 } 1013 1014 void DefaultTool::copy() const 1015 { 1016 // all the selected shapes, not only editable! 1017 QList<KoShape *> shapes = koSelection()->selectedShapes(); 1018 1019 if (!shapes.isEmpty()) { 1020 KoDrag drag; 1021 drag.setSvg(shapes); 1022 drag.addToClipboard(); 1023 } 1024 } 1025 1026 void DefaultTool::deleteSelection() 1027 { 1028 QList<KoShape *> shapes; 1029 foreach (KoShape *s, koSelection()->selectedShapes()) { 1030 if (s->isGeometryProtected()) { 1031 continue; 1032 } 1033 shapes << s; 1034 } 1035 if (!shapes.empty()) { 1036 canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); 1037 } 1038 } 1039 1040 bool DefaultTool::paste() 1041 { 1042 // we no longer have to do anything as tool Proxy will do it for us 1043 return false; 1044 } 1045 1046 bool DefaultTool::selectAll() 1047 { 1048 Q_ASSERT(canvas()); 1049 Q_ASSERT(canvas()->selectedShapesProxy()); 1050 Q_FOREACH(KoShape *shape, canvas()->shapeManager()->shapes()) { 1051 if (!shape->isSelectable()) continue; 1052 canvas()->selectedShapesProxy()->selection()->select(shape); 1053 } 1054 repaintDecorations(); 1055 1056 return true; 1057 } 1058 1059 void DefaultTool::deselect() 1060 { 1061 Q_ASSERT(canvas()); 1062 Q_ASSERT(canvas()->selectedShapesProxy()); 1063 canvas()->selectedShapesProxy()->selection()->deselectAll(); 1064 repaintDecorations(); 1065 } 1066 1067 KoSelection *DefaultTool::koSelection() const 1068 { 1069 Q_ASSERT(canvas()); 1070 Q_ASSERT(canvas()->selectedShapesProxy()); 1071 return canvas()->selectedShapesProxy()->selection(); 1072 } 1073 1074 KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) 1075 { 1076 // check for handles in this order; meaning that when handles overlap the one on top is chosen 1077 static const KoFlake::SelectionHandle handleOrder[] = { 1078 KoFlake::BottomRightHandle, 1079 KoFlake::TopLeftHandle, 1080 KoFlake::BottomLeftHandle, 1081 KoFlake::TopRightHandle, 1082 KoFlake::BottomMiddleHandle, 1083 KoFlake::RightMiddleHandle, 1084 KoFlake::LeftMiddleHandle, 1085 KoFlake::TopMiddleHandle, 1086 KoFlake::NoHandle 1087 }; 1088 1089 const KoViewConverter *converter = canvas()->viewConverter(); 1090 KoSelection *selection = koSelection(); 1091 1092 if (!selection || !selection->count() || !converter) { 1093 return KoFlake::NoHandle; 1094 } 1095 1096 recalcSelectionBox(selection); 1097 1098 if (innerHandleMeaning) { 1099 QPainterPath path; 1100 path.addPolygon(m_selectionOutline); 1101 *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); 1102 } 1103 1104 const QPointF viewPoint = converter->documentToView(point); 1105 1106 for (int i = 0; i < KoFlake::NoHandle; ++i) { 1107 KoFlake::SelectionHandle handle = handleOrder[i]; 1108 1109 const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]); 1110 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); 1111 1112 // if just inside the outline 1113 if (distanceSq < HANDLE_DISTANCE_SQ) { 1114 1115 if (innerHandleMeaning) { 1116 if (distanceSq < INNER_HANDLE_DISTANCE_SQ) { 1117 *innerHandleMeaning = true; 1118 } 1119 } 1120 1121 return handle; 1122 } 1123 } 1124 return KoFlake::NoHandle; 1125 } 1126 1127 void DefaultTool::recalcSelectionBox(KoSelection *selection) 1128 { 1129 KIS_ASSERT_RECOVER_RETURN(selection->count()); 1130 1131 QTransform matrix = selection->absoluteTransformation(); 1132 m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect())); 1133 m_angle = 0.0; 1134 1135 QPolygonF outline = m_selectionOutline; //shorter name in the following :) 1136 m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; 1137 m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); 1138 m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; 1139 m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); 1140 m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; 1141 m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); 1142 m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; 1143 m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); 1144 if (selection->count() == 1) { 1145 #if 0 // TODO detect mirroring 1146 KoShape *s = koSelection()->firstSelectedShape(); 1147 1148 if (s->scaleX() < 0) { // vertically mirrored: swap left / right 1149 std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); 1150 std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); 1151 std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); 1152 } 1153 if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom 1154 std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); 1155 std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); 1156 std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); 1157 } 1158 #endif 1159 } 1160 } 1161 1162 void DefaultTool::activate(const QSet<KoShape *> &shapes) 1163 { 1164 KoToolBase::activate(shapes); 1165 1166 QAction *actionBringToFront = action("object_order_front"); 1167 connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection); 1168 1169 QAction *actionRaise = action("object_order_raise"); 1170 connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection); 1171 1172 QAction *actionLower = action("object_order_lower"); 1173 connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); 1174 1175 QAction *actionSendToBack = action("object_order_back"); 1176 connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection); 1177 1178 QAction *actionGroupBottom = action("object_group"); 1179 connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection); 1180 1181 QAction *actionUngroupBottom = action("object_ungroup"); 1182 connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection); 1183 1184 QAction *actionSplit = action("object_split"); 1185 connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection); 1186 1187 connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int))); 1188 connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int))); 1189 connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int))); 1190 connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int))); 1191 1192 m_mouseWasInsideHandles = false; 1193 m_lastHandle = KoFlake::NoHandle; 1194 useCursor(Qt::ArrowCursor); 1195 repaintDecorations(); 1196 updateActions(); 1197 1198 if (m_tabbedOptionWidget) { 1199 m_tabbedOptionWidget->activate(); 1200 } 1201 } 1202 1203 void DefaultTool::deactivate() 1204 { 1205 KoToolBase::deactivate(); 1206 1207 QAction *actionBringToFront = action("object_order_front"); 1208 disconnect(actionBringToFront, 0, this, 0); 1209 1210 QAction *actionRaise = action("object_order_raise"); 1211 disconnect(actionRaise, 0, this, 0); 1212 1213 QAction *actionLower = action("object_order_lower"); 1214 disconnect(actionLower, 0, this, 0); 1215 1216 QAction *actionSendToBack = action("object_order_back"); 1217 disconnect(actionSendToBack, 0, this, 0); 1218 1219 QAction *actionGroupBottom = action("object_group"); 1220 disconnect(actionGroupBottom, 0, this, 0); 1221 1222 QAction *actionUngroupBottom = action("object_ungroup"); 1223 disconnect(actionUngroupBottom, 0, this, 0); 1224 1225 QAction *actionSplit = action("object_split"); 1226 disconnect(actionSplit, 0, this, 0); 1227 1228 disconnect(m_alignSignalsMapper, 0, this, 0); 1229 disconnect(m_distributeSignalsMapper, 0, this, 0); 1230 disconnect(m_transformSignalsMapper, 0, this, 0); 1231 disconnect(m_booleanSignalsMapper, 0, this, 0); 1232 1233 1234 if (m_tabbedOptionWidget) { 1235 m_tabbedOptionWidget->deactivate(); 1236 } 1237 } 1238 1239 void DefaultTool::selectionGroup() 1240 { 1241 KoSelection *selection = koSelection(); 1242 if (!selection) return; 1243 1244 QList<KoShape *> selectedShapes = selection->selectedEditableShapes(); 1245 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); 1246 if (selectedShapes.isEmpty()) return; 1247 1248 const int groupZIndex = selectedShapes.last()->zIndex(); 1249 1250 KoShapeGroup *group = new KoShapeGroup(); 1251 group->setZIndex(groupZIndex); 1252 // TODO what if only one shape is left? 1253 KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); 1254 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd); 1255 canvas()->shapeController()->addShapeDirect(group, 0, cmd); 1256 new KoShapeGroupCommand(group, selectedShapes, true, cmd); 1257 new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd); 1258 canvas()->addCommand(cmd); 1259 1260 // update selection so we can ungroup immediately again 1261 selection->deselectAll(); 1262 selection->select(group); 1263 } 1264 1265 void DefaultTool::selectionUngroup() 1266 { 1267 KoSelection *selection = koSelection(); 1268 if (!selection) return; 1269 1270 QList<KoShape *> selectedShapes = selection->selectedEditableShapes(); 1271 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); 1272 1273 KUndo2Command *cmd = 0; 1274 QList<KoShape*> newShapes; 1275 1276 // add a ungroup command for each found shape container to the macro command 1277 Q_FOREACH (KoShape *shape, selectedShapes) { 1278 KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape); 1279 if (group) { 1280 if (!cmd) { 1281 cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes")); 1282 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd); 1283 } 1284 newShapes << group->shapes(); 1285 new KoShapeUngroupCommand(group, group->shapes(), 1286 group->parent() ? QList<KoShape *>() : shapeManager()->topLevelShapes(), 1287 cmd); 1288 canvas()->shapeController()->removeShape(group, cmd); 1289 } 1290 } 1291 if (cmd) { 1292 new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd); 1293 canvas()->addCommand(cmd); 1294 } 1295 } 1296 1297 void DefaultTool::selectionTransform(int transformAction) 1298 { 1299 KoSelection *selection = koSelection(); 1300 if (!selection) return; 1301 1302 QList<KoShape *> editableShapes = selection->selectedEditableShapes(); 1303 if (editableShapes.isEmpty()) { 1304 return; 1305 } 1306 1307 QTransform applyTransform; 1308 bool shouldReset = false; 1309 KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action"); 1310 1311 1312 switch (TransformActionType(transformAction)) { 1313 case TransformRotate90CW: 1314 applyTransform.rotate(90.0); 1315 actionName = kundo2_i18n("Rotate Object 90° CW"); 1316 break; 1317 case TransformRotate90CCW: 1318 applyTransform.rotate(-90.0); 1319 actionName = kundo2_i18n("Rotate Object 90° CCW"); 1320 break; 1321 case TransformRotate180: 1322 applyTransform.rotate(180.0); 1323 actionName = kundo2_i18n("Rotate Object 180°"); 1324 break; 1325 case TransformMirrorX: 1326 applyTransform.scale(-1.0, 1.0); 1327 actionName = kundo2_i18n("Mirror Object Horizontally"); 1328 break; 1329 case TransformMirrorY: 1330 applyTransform.scale(1.0, -1.0); 1331 actionName = kundo2_i18n("Mirror Object Vertically"); 1332 break; 1333 case TransformReset: 1334 shouldReset = true; 1335 actionName = kundo2_i18n("Reset Object Transformations"); 1336 break; 1337 } 1338 1339 if (!shouldReset && applyTransform.isIdentity()) return; 1340 1341 QList<QTransform> oldTransforms; 1342 QList<QTransform> newTransforms; 1343 1344 const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes); 1345 const QPointF centerPoint = outlineRect.center(); 1346 const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y()); 1347 const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y()); 1348 1349 // we also add selection to the list of transformed shapes, so that its outline is updated correctly 1350 QList<KoShape*> transformedShapes = editableShapes; 1351 transformedShapes << selection; 1352 1353 Q_FOREACH (KoShape *shape, transformedShapes) { 1354 oldTransforms.append(shape->transformation()); 1355 1356 QTransform t; 1357 1358 if (!shouldReset) { 1359 const QTransform world = shape->absoluteTransformation(); 1360 t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation(); 1361 } else { 1362 const QPointF center = shape->outlineRect().center(); 1363 const QPointF offset = shape->transformation().map(center) - center; 1364 t = QTransform::fromTranslate(offset.x(), offset.y()); 1365 } 1366 1367 newTransforms.append(t); 1368 } 1369 1370 KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms); 1371 cmd->setText(actionName); 1372 canvas()->addCommand(cmd); 1373 } 1374 1375 void DefaultTool::selectionBooleanOp(int booleanOp) 1376 { 1377 KoSelection *selection = koSelection(); 1378 if (!selection) return; 1379 1380 QList<KoShape *> editableShapes = selection->selectedEditableShapes(); 1381 if (editableShapes.isEmpty()) { 1382 return; 1383 } 1384 1385 QVector<QPainterPath> srcOutlines; 1386 QPainterPath dstOutline; 1387 KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name"); 1388 1389 // TODO: implement a reference shape selection dialog! 1390 const int referenceShapeIndex = 0; 1391 KoShape *referenceShape = editableShapes[referenceShapeIndex]; 1392 1393 KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas()); 1394 KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); 1395 const QTransform booleanWorkaroundTransform = 1396 KritaUtils::pathShapeBooleanSpaceWorkaround(kisCanvas->image()); 1397 1398 Q_FOREACH (KoShape *shape, editableShapes) { 1399 srcOutlines << 1400 booleanWorkaroundTransform.map( 1401 shape->absoluteTransformation().map( 1402 shape->outline())); 1403 } 1404 1405 if (booleanOp == BooleanUnion) { 1406 Q_FOREACH (const QPainterPath &path, srcOutlines) { 1407 dstOutline |= path; 1408 } 1409 actionName = kundo2_i18n("Unite Shapes"); 1410 } else if (booleanOp == BooleanIntersection) { 1411 for (int i = 0; i < srcOutlines.size(); i++) { 1412 if (i == 0) { 1413 dstOutline = srcOutlines[i]; 1414 } else { 1415 dstOutline &= srcOutlines[i]; 1416 } 1417 } 1418 1419 // there is a bug in Qt, sometimes it leaves the resulting 1420 // outline open, so just close it explicitly. 1421 dstOutline.closeSubpath(); 1422 1423 actionName = kundo2_i18n("Intersect Shapes"); 1424 1425 } else if (booleanOp == BooleanSubtraction) { 1426 for (int i = 0; i < srcOutlines.size(); i++) { 1427 dstOutline = srcOutlines[referenceShapeIndex]; 1428 if (i != referenceShapeIndex) { 1429 dstOutline -= srcOutlines[i]; 1430 } 1431 } 1432 1433 actionName = kundo2_i18n("Subtract Shapes"); 1434 } 1435 1436 dstOutline = booleanWorkaroundTransform.inverted().map(dstOutline); 1437 1438 KoShape *newShape = 0; 1439 1440 if (!dstOutline.isEmpty()) { 1441 newShape = KoPathShape::createShapeFromPainterPath(dstOutline); 1442 } 1443 1444 KUndo2Command *cmd = new KUndo2Command(actionName); 1445 1446 new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd); 1447 1448 QList<KoShape*> newSelectedShapes; 1449 1450 if (newShape) { 1451 newShape->setBackground(referenceShape->background()); 1452 newShape->setStroke(referenceShape->stroke()); 1453 newShape->setZIndex(referenceShape->zIndex()); 1454 1455 KoShapeContainer *parent = referenceShape->parent(); 1456 canvas()->shapeController()->addShapeDirect(newShape, parent, cmd); 1457 1458 newSelectedShapes << newShape; 1459 } 1460 1461 canvas()->shapeController()->removeShapes(editableShapes, cmd); 1462 1463 new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd); 1464 1465 canvas()->addCommand(cmd); 1466 } 1467 1468 void DefaultTool::selectionSplitShapes() 1469 { 1470 KoSelection *selection = koSelection(); 1471 if (!selection) return; 1472 1473 QList<KoShape *> editableShapes = selection->selectedEditableShapes(); 1474 if (editableShapes.isEmpty()) { 1475 return; 1476 } 1477 1478 KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes")); 1479 1480 new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd); 1481 QList<KoShape*> newShapes; 1482 1483 Q_FOREACH (KoShape *shape, editableShapes) { 1484 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape); 1485 if (!pathShape) return; 1486 1487 QList<KoPathShape*> splitShapes; 1488 if (pathShape->separate(splitShapes)) { 1489 QList<KoShape*> normalShapes = implicitCastList<KoShape*>(splitShapes); 1490 1491 KoShapeContainer *parent = shape->parent(); 1492 canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd); 1493 canvas()->shapeController()->removeShape(shape, cmd); 1494 newShapes << normalShapes; 1495 } 1496 } 1497 1498 new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd); 1499 1500 canvas()->addCommand(cmd); 1501 } 1502 1503 void DefaultTool::selectionAlign(int _align) 1504 { 1505 KoShapeAlignCommand::Align align = 1506 static_cast<KoShapeAlignCommand::Align>(_align); 1507 1508 KoSelection *selection = koSelection(); 1509 if (!selection) return; 1510 1511 QList<KoShape *> editableShapes = selection->selectedEditableShapes(); 1512 if (editableShapes.isEmpty()) { 1513 return; 1514 } 1515 1516 // TODO add an option to the widget so that one can align to the page 1517 // with multiple selected shapes too 1518 1519 QRectF bb; 1520 1521 // single selected shape is automatically aligned to document rect 1522 if (editableShapes.count() == 1) { 1523 if (!canvas()->resourceManager()->hasResource(KoCanvasResource::PageSize)) { 1524 return; 1525 } 1526 bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResource::PageSize)); 1527 } else { 1528 bb = KoShape::absoluteOutlineRect(editableShapes); 1529 } 1530 1531 KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); 1532 canvas()->addCommand(cmd); 1533 } 1534 1535 void DefaultTool::selectionDistribute(int _distribute) 1536 { 1537 KoShapeDistributeCommand::Distribute distribute = 1538 static_cast<KoShapeDistributeCommand::Distribute>(_distribute); 1539 1540 KoSelection *selection = koSelection(); 1541 if (!selection) return; 1542 1543 QList<KoShape *> editableShapes = selection->selectedEditableShapes(); 1544 if (editableShapes.size() < 3) { 1545 return; 1546 } 1547 1548 QRectF bb = KoShape::absoluteOutlineRect(editableShapes); 1549 KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb); 1550 canvas()->addCommand(cmd); 1551 } 1552 1553 void DefaultTool::selectionBringToFront() 1554 { 1555 selectionReorder(KoShapeReorderCommand::BringToFront); 1556 } 1557 1558 void DefaultTool::selectionMoveUp() 1559 { 1560 selectionReorder(KoShapeReorderCommand::RaiseShape); 1561 } 1562 1563 void DefaultTool::selectionMoveDown() 1564 { 1565 selectionReorder(KoShapeReorderCommand::LowerShape); 1566 } 1567 1568 void DefaultTool::selectionSendToBack() 1569 { 1570 selectionReorder(KoShapeReorderCommand::SendToBack); 1571 } 1572 1573 void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) 1574 { 1575 KoSelection *selection = koSelection(); 1576 if (!selection) { 1577 return; 1578 } 1579 1580 QList<KoShape *> selectedShapes = selection->selectedEditableShapes(); 1581 if (selectedShapes.isEmpty()) { 1582 return; 1583 } 1584 1585 KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order); 1586 if (cmd) { 1587 canvas()->addCommand(cmd); 1588 } 1589 } 1590 1591 QList<QPointer<QWidget> > DefaultTool::createOptionWidgets() 1592 { 1593 QList<QPointer<QWidget> > widgets; 1594 1595 m_tabbedOptionWidget = new DefaultToolTabbedWidget(this); 1596 1597 if (isActivated()) { 1598 m_tabbedOptionWidget->activate(); 1599 } 1600 widgets.append(m_tabbedOptionWidget); 1601 1602 connect(m_tabbedOptionWidget, 1603 SIGNAL(sigSwitchModeEditFillGradient(bool)), 1604 SLOT(slotActivateEditFillGradient(bool))); 1605 1606 connect(m_tabbedOptionWidget, 1607 SIGNAL(sigSwitchModeEditStrokeGradient(bool)), 1608 SLOT(slotActivateEditStrokeGradient(bool))); 1609 1610 connect(m_tabbedOptionWidget, 1611 SIGNAL(sigSwitchModeEditFillGradient(bool)), 1612 SLOT(slotActivateEditFillMeshGradient(bool))); 1613 // TODO: strokes!! 1614 1615 connect(m_tabbedOptionWidget, 1616 SIGNAL(sigMeshGradientResetted()), 1617 SLOT(slotResetMeshGradientState())); 1618 1619 return widgets; 1620 } 1621 1622 void DefaultTool::canvasResourceChanged(int key, const QVariant &res) 1623 { 1624 if (key == HotPosition) { 1625 m_hotPosition = KoFlake::AnchorPosition(res.toInt()); 1626 repaintDecorations(); 1627 } 1628 } 1629 1630 KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) 1631 { 1632 KoSelection *selection = koSelection(); 1633 if (!selection) return nullptr; 1634 1635 bool insideSelection = false; 1636 KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); 1637 1638 bool editableShape = !selection->selectedEditableShapes().isEmpty(); 1639 1640 const bool selectMultiple = event->modifiers() & Qt::ShiftModifier; 1641 const bool selectNextInStack = event->modifiers() & Qt::ControlModifier; 1642 const bool avoidSelection = event->modifiers() & Qt::AltModifier; 1643 1644 if (selectNextInStack) { 1645 // change the hot selection position when middle clicking on a handle 1646 KoFlake::AnchorPosition newHotPosition = m_hotPosition; 1647 switch (handle) { 1648 case KoFlake::TopMiddleHandle: 1649 newHotPosition = KoFlake::Top; 1650 break; 1651 case KoFlake::TopRightHandle: 1652 newHotPosition = KoFlake::TopRight; 1653 break; 1654 case KoFlake::RightMiddleHandle: 1655 newHotPosition = KoFlake::Right; 1656 break; 1657 case KoFlake::BottomRightHandle: 1658 newHotPosition = KoFlake::BottomRight; 1659 break; 1660 case KoFlake::BottomMiddleHandle: 1661 newHotPosition = KoFlake::Bottom; 1662 break; 1663 case KoFlake::BottomLeftHandle: 1664 newHotPosition = KoFlake::BottomLeft; 1665 break; 1666 case KoFlake::LeftMiddleHandle: 1667 newHotPosition = KoFlake::Left; 1668 break; 1669 case KoFlake::TopLeftHandle: 1670 newHotPosition = KoFlake::TopLeft; 1671 break; 1672 case KoFlake::NoHandle: 1673 default: 1674 // check if we had hit the center point 1675 const KoViewConverter *converter = canvas()->viewConverter(); 1676 QPointF pt = converter->documentToView(event->point); 1677 1678 // TODO: use calculated values instead! 1679 QPointF centerPt = converter->documentToView(selection->absolutePosition()); 1680 1681 if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) { 1682 newHotPosition = KoFlake::Center; 1683 } 1684 1685 break; 1686 } 1687 1688 if (m_hotPosition != newHotPosition) { 1689 canvas()->resourceManager()->setResource(HotPosition, newHotPosition); 1690 return new NopInteractionStrategy(this); 1691 } 1692 } 1693 1694 if (!avoidSelection && editableShape) { 1695 // manipulation of selected shapes goes first 1696 if (handle != KoFlake::NoHandle) { 1697 // resizing or shearing only with left mouse button 1698 if (insideSelection) { 1699 bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling(); 1700 return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling); 1701 } 1702 1703 if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || 1704 handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { 1705 1706 return new ShapeShearStrategy(this, selection, event->point, handle); 1707 } 1708 1709 // rotating is allowed for right mouse button too 1710 if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || 1711 handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { 1712 1713 return new ShapeRotateStrategy(this, selection, event->point, event->buttons()); 1714 } 1715 } 1716 1717 if (!selectMultiple && !selectNextInStack) { 1718 1719 if (insideSelection) { 1720 return new ShapeMoveStrategy(this, selection, event->point); 1721 } 1722 } 1723 } 1724 1725 KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); 1726 1727 if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) { 1728 if (!selectMultiple) { 1729 selection->deselectAll(); 1730 } 1731 return new SelectionInteractionStrategy(this, event->point, false); 1732 } 1733 1734 if (selection->isSelected(shape)) { 1735 if (selectMultiple) { 1736 selection->deselect(shape); 1737 } 1738 } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected 1739 if (!selectMultiple) { 1740 selection->deselectAll(); 1741 } 1742 selection->select(shape); 1743 // tablet selection isn't precise and may lead to a move, preventing that 1744 if (event->isTabletEvent()) { 1745 return new NopInteractionStrategy(this); 1746 } 1747 return new ShapeMoveStrategy(this, selection, event->point); 1748 } 1749 return 0; 1750 } 1751 1752 void DefaultTool::updateActions() 1753 { 1754 QList<KoShape*> editableShapes; 1755 1756 if (koSelection()) { 1757 editableShapes = koSelection()->selectedEditableShapes(); 1758 } 1759 1760 const bool hasEditableShapes = !editableShapes.isEmpty(); 1761 1762 action("object_order_front")->setEnabled(hasEditableShapes); 1763 action("object_order_raise")->setEnabled(hasEditableShapes); 1764 action("object_order_lower")->setEnabled(hasEditableShapes); 1765 action("object_order_back")->setEnabled(hasEditableShapes); 1766 1767 action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes); 1768 action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes); 1769 action("object_transform_rotate_180")->setEnabled(hasEditableShapes); 1770 action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes); 1771 action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes); 1772 action("object_transform_reset")->setEnabled(hasEditableShapes); 1773 1774 const bool multipleSelected = editableShapes.size() > 1; 1775 1776 const bool alignmentEnabled = 1777 multipleSelected || 1778 (!editableShapes.isEmpty() && 1779 canvas()->resourceManager()->hasResource(KoCanvasResource::PageSize)); 1780 1781 action("object_align_horizontal_left")->setEnabled(alignmentEnabled); 1782 action("object_align_horizontal_center")->setEnabled(alignmentEnabled); 1783 action("object_align_horizontal_right")->setEnabled(alignmentEnabled); 1784 action("object_align_vertical_top")->setEnabled(alignmentEnabled); 1785 action("object_align_vertical_center")->setEnabled(alignmentEnabled); 1786 action("object_align_vertical_bottom")->setEnabled(alignmentEnabled); 1787 1788 const bool distributionEnabled = editableShapes.size() > 2; 1789 1790 action("object_distribute_horizontal_left")->setEnabled(distributionEnabled); 1791 action("object_distribute_horizontal_center")->setEnabled(distributionEnabled); 1792 action("object_distribute_horizontal_right")->setEnabled(distributionEnabled); 1793 action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled); 1794 1795 action("object_distribute_vertical_top")->setEnabled(distributionEnabled); 1796 action("object_distribute_vertical_center")->setEnabled(distributionEnabled); 1797 action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled); 1798 action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled); 1799 1800 updateDistinctiveActions(editableShapes); 1801 1802 emit selectionChanged(editableShapes.size()); 1803 } 1804 1805 void DefaultTool::updateDistinctiveActions(const QList<KoShape*> &editableShapes) { 1806 const bool multipleSelected = editableShapes.size() > 1; 1807 1808 action("object_group")->setEnabled(multipleSelected); 1809 1810 action("object_unite")->setEnabled(multipleSelected); 1811 action("object_intersect")->setEnabled(multipleSelected); 1812 action("object_subtract")->setEnabled(multipleSelected); 1813 1814 bool hasShapesWithMultipleSegments = false; 1815 Q_FOREACH (KoShape *shape, editableShapes) { 1816 KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape); 1817 if (pathShape && pathShape->subpathCount() > 1) { 1818 hasShapesWithMultipleSegments = true; 1819 break; 1820 } 1821 } 1822 action("object_split")->setEnabled(hasShapesWithMultipleSegments); 1823 1824 1825 bool hasGroupShape = false; 1826 foreach (KoShape *shape, editableShapes) { 1827 if (dynamic_cast<KoShapeGroup *>(shape)) { 1828 hasGroupShape = true; 1829 break; 1830 } 1831 } 1832 action("object_ungroup")->setEnabled(hasGroupShape); 1833 } 1834 1835 1836 KoToolSelection *DefaultTool::selection() 1837 { 1838 return m_selectionHandler; 1839 } 1840 1841 QMenu* DefaultTool::popupActionsMenu() 1842 { 1843 if (m_contextMenu) { 1844 m_contextMenu->clear(); 1845 1846 m_contextMenu->addSection(i18n("Vector Shape Actions")); 1847 m_contextMenu->addSeparator(); 1848 1849 QMenu *transform = m_contextMenu->addMenu(i18n("Transform")); 1850 1851 transform->addAction(action("object_transform_rotate_90_cw")); 1852 transform->addAction(action("object_transform_rotate_90_ccw")); 1853 transform->addAction(action("object_transform_rotate_180")); 1854 transform->addSeparator(); 1855 transform->addAction(action("object_transform_mirror_horizontally")); 1856 transform->addAction(action("object_transform_mirror_vertically")); 1857 transform->addSeparator(); 1858 transform->addAction(action("object_transform_reset")); 1859 1860 if (action("object_unite")->isEnabled() || 1861 action("object_intersect")->isEnabled() || 1862 action("object_subtract")->isEnabled() || 1863 action("object_split")->isEnabled()) { 1864 1865 QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations")); 1866 transform->addAction(action("object_unite")); 1867 transform->addAction(action("object_intersect")); 1868 transform->addAction(action("object_subtract")); 1869 transform->addAction(action("object_split")); 1870 } 1871 1872 m_contextMenu->addSeparator(); 1873 1874 m_contextMenu->addAction(action("edit_cut")); 1875 m_contextMenu->addAction(action("edit_copy")); 1876 m_contextMenu->addAction(action("edit_paste")); 1877 m_contextMenu->addAction(action("paste_at")); 1878 1879 m_contextMenu->addSeparator(); 1880 1881 m_contextMenu->addAction(action("object_order_front")); 1882 m_contextMenu->addAction(action("object_order_raise")); 1883 m_contextMenu->addAction(action("object_order_lower")); 1884 m_contextMenu->addAction(action("object_order_back")); 1885 1886 if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { 1887 m_contextMenu->addSeparator(); 1888 m_contextMenu->addAction(action("object_group")); 1889 m_contextMenu->addAction(action("object_ungroup")); 1890 } 1891 m_contextMenu->addSeparator(); 1892 m_contextMenu->addAction(action("convert_shapes_to_vector_selection")); 1893 } 1894 1895 return m_contextMenu.data(); 1896 } 1897 1898 void DefaultTool::addTransformActions(QMenu *menu) const { 1899 menu->addAction(action("object_transform_rotate_90_cw")); 1900 menu->addAction(action("object_transform_rotate_90_ccw")); 1901 menu->addAction(action("object_transform_rotate_180")); 1902 menu->addSeparator(); 1903 menu->addAction(action("object_transform_mirror_horizontally")); 1904 menu->addAction(action("object_transform_mirror_vertically")); 1905 menu->addSeparator(); 1906 menu->addAction(action("object_transform_reset")); 1907 } 1908 1909 void DefaultTool::explicitUserStrokeEndRequest() 1910 { 1911 QList<KoShape *> shapes = koSelection()->selectedEditableShapesAndDelegates(); 1912 QString tool = KoToolManager::instance()->preferredToolForSelection(shapes); 1913 QTimer::singleShot(0, [tool = std::move(tool)]() { 1914 KoToolManager::instance()->switchToolRequested(tool); 1915 }); 1916 }