1 /* This file is part of the KDE project
2 
3    Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
4    Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
5    Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
6    Copyright (C) 2008 C. Boemann <cbo@boemann.dk>
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public
10    License as published by the Free Software Foundation; either
11    version 2 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17 
18    You should have received a copy of the GNU Library General Public License
19    along with this library; see the file COPYING.LIB.  If not, write to
20    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22 */
23 
24 #include "DefaultTool.h"
25 #include "DefaultToolWidget.h"
26 #include "DefaultToolArrangeWidget.h"
27 #include "SelectionDecorator.h"
28 #include "ShapeMoveStrategy.h"
29 #include "ShapeRotateStrategy.h"
30 #include "ShapeShearStrategy.h"
31 #include "ShapeResizeStrategy.h"
32 #include "guidestool/GuidesTool.h"
33 #include "guidestool/GuidesToolFactory.h" // for the ID
34 
35 #include <KoGuidesData.h>
36 #include <KoPointerEvent.h>
37 #include <KoToolSelection.h>
38 #include <KoToolManager.h>
39 #include <KoSelection.h>
40 #include <KoShapeController.h>
41 #include <KoShapeManager.h>
42 #include <KoShapeGroup.h>
43 #include <KoShapeLayer.h>
44 #include <KoShapePaste.h>
45 #include <KoShapeOdfSaveHelper.h>
46 #include <KoDrag.h>
47 #include <KoCanvasBase.h>
48 #include <KoCanvasResourceManager.h>
49 #include <KoShapeRubberSelectStrategy.h>
50 #include <commands/KoShapeMoveCommand.h>
51 #include <commands/KoShapeDeleteCommand.h>
52 #include <commands/KoShapeCreateCommand.h>
53 #include <commands/KoShapeGroupCommand.h>
54 #include <commands/KoShapeUngroupCommand.h>
55 #include <KoSnapGuide.h>
56 #include <KoStrokeConfigWidget.h>
57 #include <KoFillConfigWidget.h>
58 #include <KoShadowConfigWidget.h>
59 #include <KoShapeContainer.h>
60 #include <KoShapeContainerModel.h>
61 
62 #include <KoIcon.h>
63 
64 #include <QAction>
65 #include <QKeyEvent>
66 #include <QPainterPath>
67 #include <QClipboard>
68 #include <QStandardPaths>
69 
70 #include <math.h>
71 
72 #include <QVector2D>
73 
74 #define HANDLE_DISTANCE 10
75 
76 class NopInteractionStrategy : public KoInteractionStrategy
77 {
78 public:
NopInteractionStrategy(KoToolBase * parent)79     explicit NopInteractionStrategy(KoToolBase* parent) : KoInteractionStrategy(parent) {}
80 
createCommand()81     KUndo2Command* createCommand() override
82     {
83         return 0;
84     }
85 
handleMouseMove(const QPointF &,Qt::KeyboardModifiers)86     void handleMouseMove(const QPointF& /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {}
finishInteraction(Qt::KeyboardModifiers)87     void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {}
88 };
89 
90 class SelectionHandler : public KoToolSelection
91 {
92 public:
SelectionHandler(DefaultTool * parent)93     SelectionHandler(DefaultTool *parent)
94         : KoToolSelection(parent), m_selection(parent->koSelection())
95     {
96         Q_ASSERT(m_selection);
97     }
98 
hasSelection()99     bool hasSelection() override {
100         return m_selection->count();
101     }
102 
103 private:
104     KoSelection *m_selection;
105 };
106 
107 class DefaultTool::GuideLine
108 {
109 public:
GuideLine()110     GuideLine()
111         : m_orientation(Qt::Horizontal), m_index(0), m_valid(false), m_selected(false)
112     {
113     }
GuideLine(Qt::Orientation orientation,uint index)114     GuideLine(Qt::Orientation orientation, uint index)
115         : m_orientation(orientation), m_index(index), m_valid(true), m_selected(false)
116     {
117     }
118 
isValid() const119     bool isValid() const
120     {
121         return m_valid;
122     }
isSelected() const123     bool isSelected() const
124     {
125         return m_selected;
126     }
select()127     void select()
128     {
129         m_selected = true;
130     }
131 
index() const132     uint index() const
133     {
134         return m_index;
135     }
orientation() const136     Qt::Orientation orientation() const
137     {
138         return m_orientation;
139     }
140 private:
141     Qt::Orientation m_orientation;
142     uint m_index;
143     bool m_valid;
144     bool m_selected;
145 };
146 
147 
DefaultTool(KoCanvasBase * canvas)148 DefaultTool::DefaultTool(KoCanvasBase *canvas)
149     : KoInteractionTool(canvas),
150     m_lastHandle(KoFlake::NoHandle),
151     m_hotPosition(KoFlake::TopLeftCorner),
152     m_mouseWasInsideHandles(false),
153     m_moveCommand(0),
154     m_selectionHandler(new SelectionHandler(this)),
155     m_customEventStrategy(0),
156     m_guideLine(new GuideLine())
157 {
158     setupActions();
159 
160     QPixmap rotatePixmap, shearPixmap;
161     rotatePixmap.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/cursor_rotate.png"));
162     Q_ASSERT(!rotatePixmap.isNull());
163     shearPixmap.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/cursor_shear.png"));
164     Q_ASSERT(!shearPixmap.isNull());
165 
166     m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45)));
167     m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90)));
168     m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135)));
169     m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180)));
170     m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225)));
171     m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270)));
172     m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315)));
173     m_rotateCursors[7] = QCursor(rotatePixmap);
174 /*
175     m_rotateCursors[0] = QCursor(Qt::RotateNCursor);
176     m_rotateCursors[1] = QCursor(Qt::RotateNECursor);
177     m_rotateCursors[2] = QCursor(Qt::RotateECursor);
178     m_rotateCursors[3] = QCursor(Qt::RotateSECursor);
179     m_rotateCursors[4] = QCursor(Qt::RotateSCursor);
180     m_rotateCursors[5] = QCursor(Qt::RotateSWCursor);
181     m_rotateCursors[6] = QCursor(Qt::RotateWCursor);
182     m_rotateCursors[7] = QCursor(Qt::RotateNWCursor);
183 */
184     m_shearCursors[0] = QCursor(shearPixmap);
185     m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45)));
186     m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90)));
187     m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135)));
188     m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180)));
189     m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225)));
190     m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270)));
191     m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315)));
192     m_sizeCursors[0] = Qt::SizeVerCursor;
193     m_sizeCursors[1] = Qt::SizeBDiagCursor;
194     m_sizeCursors[2] = Qt::SizeHorCursor;
195     m_sizeCursors[3] = Qt::SizeFDiagCursor;
196     m_sizeCursors[4] = Qt::SizeVerCursor;
197     m_sizeCursors[5] = Qt::SizeBDiagCursor;
198     m_sizeCursors[6] = Qt::SizeHorCursor;
199     m_sizeCursors[7] = Qt::SizeFDiagCursor;
200 
201     KoShapeManager * manager = canvas->shapeManager();
202     connect(manager, SIGNAL(selectionChanged()), this, SLOT(updateActions()));
203 }
204 
~DefaultTool()205 DefaultTool::~DefaultTool()
206 {
207     delete m_guideLine;
208 }
209 
wantsAutoScroll() const210 bool DefaultTool::wantsAutoScroll() const
211 {
212     return true;
213 }
214 
setupActions()215 void DefaultTool::setupActions()
216 {
217     QAction * actionBringToFront = new QAction(koIcon("object-order-front"),
218                                                i18n("Bring to &Front"), this);
219     addAction("object_order_front", actionBringToFront);
220     actionBringToFront->setShortcut(QKeySequence("Ctrl+Shift+]"));
221     connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()));
222 
223     QAction * actionRaise = new QAction(koIcon("object-order-raise"), i18n("&Raise"), this);
224     addAction("object_order_raise", actionRaise);
225     actionRaise->setShortcut(QKeySequence("Ctrl+]"));
226     connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()));
227 
228     QAction * actionLower = new QAction(koIcon("object-order-lower"), i18n("&Lower"), this);
229     addAction("object_order_lower", actionLower);
230     actionLower->setShortcut(QKeySequence("Ctrl+["));
231     connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
232 
233     QAction * actionSendToBack = new QAction(koIcon("object-order-back"),
234                                              i18n("Send to &Back"), this);
235     addAction("object_order_back", actionSendToBack);
236     actionSendToBack->setShortcut(QKeySequence("Ctrl+Shift+["));
237     connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()));
238 
239     QAction * actionAlignLeft = new QAction(koIcon("align-horizontal-left"),
240                                             i18n("Align Left"), this);
241     addAction("object_align_horizontal_left", actionAlignLeft);
242     connect(actionAlignLeft, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalLeft()));
243 
244     QAction * actionAlignCenter = new QAction(koIcon("align-horizontal-center"),
245                                               i18n("Horizontally Center"), this);
246     addAction("object_align_horizontal_center", actionAlignCenter);
247     connect(actionAlignCenter, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalCenter()));
248 
249     QAction * actionAlignRight = new QAction(koIcon("align-horizontal-right"),
250                                              i18n("Align Right"), this);
251     addAction("object_align_horizontal_right", actionAlignRight);
252     connect(actionAlignRight, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalRight()));
253 
254     QAction * actionAlignTop = new QAction(koIcon("align-vertical-top"), i18n("Align Top"), this);
255     addAction("object_align_vertical_top", actionAlignTop);
256     connect(actionAlignTop, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalTop()));
257 
258     QAction * actionAlignMiddle = new QAction(koIcon("align-vertical-center"),
259                                               i18n("Vertically Center"), this);
260     addAction("object_align_vertical_center", actionAlignMiddle);
261     connect(actionAlignMiddle, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalCenter()));
262 
263     QAction * actionAlignBottom = new QAction(koIcon("align-vertical-bottom"),
264                                               i18n("Align Bottom"), this);
265     addAction("object_align_vertical_bottom", actionAlignBottom);
266     connect(actionAlignBottom, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalBottom()));
267 
268     QAction * actionGroupBottom = new QAction(koIcon("object-group"),
269                                               i18n("Group"), this);
270     addAction("object_group", actionGroupBottom);
271     connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()));
272 
273     QAction * actionUngroupBottom = new QAction(koIcon("object-ungroup"),
274                                                 i18n("Ungroup"), this);
275     addAction("object_ungroup", actionUngroupBottom);
276     connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()));
277 }
278 
rotationOfHandle(KoFlake::SelectionHandle handle,bool useEdgeRotation)279 qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation)
280 {
281     QPointF selectionCenter = koSelection()->absolutePosition();
282     QPointF direction;
283 
284     switch (handle) {
285     case KoFlake::TopMiddleHandle:
286         if (useEdgeRotation) {
287             direction = koSelection()->absolutePosition(KoFlake::TopRightCorner)
288                 - koSelection()->absolutePosition(KoFlake::TopLeftCorner);
289         } else {
290             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner);
291             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRightCorner) - handlePosition);
292             direction = handlePosition - selectionCenter;
293         }
294         break;
295     case KoFlake::TopRightHandle:
296         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized()).toPointF();
297         break;
298     case KoFlake::RightMiddleHandle:
299         if (useEdgeRotation) {
300             direction = koSelection()->absolutePosition(KoFlake::BottomRightCorner)
301                     - koSelection()->absolutePosition(KoFlake::TopRightCorner);
302         } else {
303             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRightCorner);
304             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition);
305             direction = handlePosition - selectionCenter;
306         }
307         break;
308     case KoFlake::BottomRightHandle:
309         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized()).toPointF();
310         break;
311     case KoFlake::BottomMiddleHandle:
312         if (useEdgeRotation) {
313             direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner)
314                     - koSelection()->absolutePosition(KoFlake::BottomRightCorner);
315         } else {
316             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeftCorner);
317             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition);
318             direction = handlePosition - selectionCenter;
319         }
320         break;
321     case KoFlake::BottomLeftHandle:
322         direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - selectionCenter;
323         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized()).toPointF();
324         break;
325     case KoFlake::LeftMiddleHandle:
326         if (useEdgeRotation) {
327             direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner)
328                     - koSelection()->absolutePosition(KoFlake::BottomLeftCorner);
329         } else {
330             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner);
331             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - handlePosition);
332             direction = handlePosition - selectionCenter;
333         }
334         break;
335     case KoFlake::TopLeftHandle:
336         direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - selectionCenter;
337         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized()).toPointF();
338         break;
339     case KoFlake::NoHandle:
340         return 0.0;
341         break;
342     }
343 
344     qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI;
345 
346     switch (handle) {
347     case KoFlake::TopMiddleHandle:
348         if (useEdgeRotation)
349             rotation -= 0.0;
350         else
351             rotation -= 270.0;
352         break;
353     case KoFlake::TopRightHandle:
354         rotation -= 315.0;
355         break;
356     case KoFlake::RightMiddleHandle:
357         if (useEdgeRotation)
358             rotation -= 90.0;
359         else
360             rotation -= 0.0;
361         break;
362     case KoFlake::BottomRightHandle:
363         rotation -= 45.0;
364         break;
365     case KoFlake::BottomMiddleHandle:
366         if (useEdgeRotation)
367             rotation -= 180.0;
368         else
369             rotation -= 90.0;
370         break;
371     case KoFlake::BottomLeftHandle:
372         rotation -= 135.0;
373         break;
374     case KoFlake::LeftMiddleHandle:
375         if (useEdgeRotation)
376             rotation -= 270.0;
377         else
378             rotation -= 180.0;
379         break;
380     case KoFlake::TopLeftHandle:
381         rotation -= 225.0;
382         break;
383     case KoFlake::NoHandle:
384         break;
385     }
386 
387     if (rotation < 0.0)
388         rotation += 360.0;
389 
390     return rotation;
391 }
392 
updateCursor()393 void DefaultTool::updateCursor()
394 {
395     QCursor cursor = Qt::ArrowCursor;
396 
397     QString statusText;
398 
399     if (koSelection()->count() > 0) { // has a selection
400         KoShape::AllowedInteractions interactions = allowedInteractions(koSelection()->selectedShapes(KoFlake::StrippedSelection));
401 
402         if (!m_mouseWasInsideHandles) {
403             m_angle = rotationOfHandle(m_lastHandle, true);
404             int rotOctant = 8 + int(8.5 + m_angle / 45);
405 
406             bool rotateHandle = false;
407             bool shearHandle = false;
408             switch(m_lastHandle) {
409             case KoFlake::TopMiddleHandle:
410                 if (interactions.testFlag(KoShape::ShearingAllowed)) {
411                     cursor = m_shearCursors[(0 +rotOctant)%8];
412                     shearHandle = true;
413                 }
414                 break;
415             case KoFlake::TopRightHandle:
416                 if (interactions.testFlag(KoShape::RotationAllowed)) {
417                     cursor = m_rotateCursors[(1 +rotOctant)%8];
418                     rotateHandle = true;
419                 }
420                 break;
421             case KoFlake::RightMiddleHandle:
422                 if (interactions.testFlag(KoShape::ShearingAllowed)) {
423                     cursor = m_shearCursors[(2 +rotOctant)%8];
424                     shearHandle = true;
425                 }
426                 break;
427             case KoFlake::BottomRightHandle:
428                 if (interactions.testFlag(KoShape::RotationAllowed)) {
429                     cursor = m_rotateCursors[(3 +rotOctant)%8];
430                     rotateHandle = true;
431                 }
432                 break;
433             case KoFlake::BottomMiddleHandle:
434                 if (interactions.testFlag(KoShape::ShearingAllowed)) {
435                     cursor = m_shearCursors[(4 +rotOctant)%8];
436                     shearHandle = true;
437                 }
438                 break;
439             case KoFlake::BottomLeftHandle:
440                 if (interactions.testFlag(KoShape::RotationAllowed)) {
441                     cursor = m_rotateCursors[(5 +rotOctant)%8];
442                     rotateHandle = true;
443                 }
444                 break;
445             case KoFlake::LeftMiddleHandle:
446                 if (interactions.testFlag(KoShape::ShearingAllowed)) {
447                     cursor = m_shearCursors[(6 +rotOctant)%8];
448                     shearHandle = true;
449                 }
450                 break;
451             case KoFlake::TopLeftHandle:
452                 if (interactions.testFlag(KoShape::RotationAllowed)) {
453                     cursor = m_rotateCursors[(7 +rotOctant)%8];
454                     rotateHandle = true;
455                 }
456                 break;
457             case KoFlake::NoHandle:
458                 if (m_guideLine->isValid()) {
459                     cursor = m_guideLine->orientation() == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor;
460                     statusText = i18n("Click and drag to move guide line.");
461                 }
462                 else
463                     cursor = Qt::ArrowCursor;
464                 break;
465             }
466             if (rotateHandle)
467                 statusText = i18n("Left click rotates around center, right click around highlighted position.");
468             if (shearHandle)
469                 statusText = i18n("Click and drag to shear selection.");
470         }
471         else {
472             statusText = i18n("Click and drag to resize selection.");
473             m_angle = rotationOfHandle(m_lastHandle, false);
474             int rotOctant = 8 + int(8.5 + m_angle / 45);
475             bool cornerHandle = false;
476             switch(m_lastHandle) {
477             case KoFlake::TopMiddleHandle:
478                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
479                     cursor = m_sizeCursors[(0 +rotOctant)%8];
480                 }
481                 break;
482             case KoFlake::TopRightHandle:
483                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
484                     cursor = m_sizeCursors[(1 +rotOctant)%8];
485                     cornerHandle = true;
486                 }
487                 break;
488             case KoFlake::RightMiddleHandle:
489                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
490                     cursor = m_sizeCursors[(2 +rotOctant)%8];
491                 }
492                 break;
493             case KoFlake::BottomRightHandle:
494                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
495                     cursor = m_sizeCursors[(3 +rotOctant)%8];
496                     cornerHandle = true;
497                 }
498                 break;
499             case KoFlake::BottomMiddleHandle:
500                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
501                     cursor = m_sizeCursors[(4 +rotOctant)%8];
502                 }
503                 break;
504             case KoFlake::BottomLeftHandle:
505                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
506                     cursor = m_sizeCursors[(5 +rotOctant)%8];
507                     cornerHandle = true;
508                 }
509                 break;
510             case KoFlake::LeftMiddleHandle:
511                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
512                     cursor = m_sizeCursors[(6 +rotOctant)%8];
513                 }
514                 break;
515             case KoFlake::TopLeftHandle:
516                 if (interactions.testFlag(KoShape::ResizeAllowed)) {
517                     cursor = m_sizeCursors[(7 +rotOctant)%8];
518                     cornerHandle = true;
519                 }
520                 break;
521             case KoFlake::NoHandle:
522                 if (interactions.testFlag(KoShape::MoveAllowed)) {
523                     cursor = Qt::SizeAllCursor;
524                     statusText = i18n("Click and drag to move selection.");
525                 }
526                 break;
527             }
528             if (cornerHandle)
529                 statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position.");
530         }
531     }
532     else {
533         if (m_guideLine->isValid()) {
534             cursor = m_guideLine->orientation() == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor;
535             statusText = i18n("Click and drag to move guide line.");
536         }
537     }
538     useCursor(cursor);
539     if (currentStrategy() == 0)
540         emit statusTextChanged(statusText);
541 }
542 
paint(QPainter & painter,const KoViewConverter & converter)543 void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter)
544 {
545     KoInteractionTool::paint(painter, converter);
546     if (currentStrategy() == 0 && koSelection()->count() > 0) {
547         SelectionDecorator decorator(m_mouseWasInsideHandles ? m_lastHandle : KoFlake::NoHandle,
548                  true, true);
549         decorator.setSelection(koSelection());
550         decorator.setHandleRadius(handleRadius());
551         decorator.setHotPosition(m_hotPosition);
552         decorator.paint(painter, converter);
553     }
554     painter.save();
555     KoShape::applyConversion(painter, converter);
556     canvas()->snapGuide()->paint(painter, converter);
557     painter.restore();
558 }
559 
mousePressEvent(KoPointerEvent * event)560 void DefaultTool::mousePressEvent(KoPointerEvent *event)
561 {
562     KoInteractionTool::mousePressEvent(event);
563     updateCursor();
564 }
565 
mouseMoveEvent(KoPointerEvent * event)566 void DefaultTool::mouseMoveEvent(KoPointerEvent *event)
567 {
568     KoInteractionTool::mouseMoveEvent(event);
569     if (currentStrategy() == 0 && koSelection()->count() > 0) {
570         QRectF bound = handlesSize();
571         if (bound.contains(event->point)) {
572             bool inside;
573             KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside);
574             if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) {
575                 m_lastHandle = newDirection;
576                 m_mouseWasInsideHandles = inside;
577                 //repaintDecorations();
578             }
579         } else {
580             /*if (m_lastHandle != KoFlake::NoHandle)
581                 repaintDecorations(); */
582             m_lastHandle = KoFlake::NoHandle;
583             m_mouseWasInsideHandles = false;
584 
585             if (m_guideLine->isSelected()) {
586                 GuidesTool *guidesTool = dynamic_cast<GuidesTool*>(KoToolManager::instance()->toolById(canvas(), GuidesToolId));
587                 if (guidesTool) {
588                     guidesTool->moveGuideLine(m_guideLine->orientation(), m_guideLine->index());
589                     activateTemporary(guidesTool->toolId());
590                 }
591             } else {
592                 selectGuideAtPosition(event->point);
593             }
594         }
595     } else {
596         if (m_guideLine->isSelected()) {
597             GuidesTool *guidesTool = dynamic_cast<GuidesTool*>(KoToolManager::instance()->toolById(canvas(), GuidesToolId));
598             if (guidesTool) {
599                 guidesTool->moveGuideLine(m_guideLine->orientation(), m_guideLine->index());
600                 activateTemporary(guidesTool->toolId());
601             }
602         } else {
603             selectGuideAtPosition(event->point);
604         }
605     }
606 
607     updateCursor();
608 }
609 
selectGuideAtPosition(const QPointF & position)610 void DefaultTool::selectGuideAtPosition(const QPointF &position)
611 {
612     int index = -1;
613     Qt::Orientation orientation = Qt::Horizontal;
614 
615     // check if we are on a guide line
616     KoGuidesData * guidesData = canvas()->guidesData();
617     if (guidesData && guidesData->showGuideLines()) {
618         qreal minDistance = canvas()->viewConverter()->viewToDocumentX(grabSensitivity());
619         uint i = 0;
620         foreach (qreal guidePos, guidesData->horizontalGuideLines()) {
621             qreal distance = qAbs(guidePos - position.y());
622             if (distance < minDistance) {
623                 orientation = Qt::Horizontal;
624                 index = i;
625                 minDistance = distance;
626             }
627             i++;
628         }
629         i = 0;
630         foreach (qreal guidePos, guidesData->verticalGuideLines())
631         {
632             qreal distance = qAbs(guidePos - position.x());
633             if (distance < minDistance) {
634                 orientation = Qt::Vertical;
635                 index = i;
636                 minDistance = distance;
637             }
638             i++;
639         }
640     }
641 
642     delete m_guideLine;
643     if (index >= 0)
644         m_guideLine = new GuideLine(orientation, index);
645     else
646         m_guideLine = new GuideLine();
647 }
648 
handlesSize()649 QRectF DefaultTool::handlesSize()
650 {
651     QRectF bound = koSelection()->boundingRect();
652     // expansion Border
653     if (!canvas() || !canvas()->viewConverter()) return bound;
654 
655     QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE));
656     bound.adjust(-border.x(), -border.y(), border.x(), border.y());
657     return bound;
658 }
659 
mouseReleaseEvent(KoPointerEvent * event)660 void DefaultTool::mouseReleaseEvent(KoPointerEvent *event)
661 {
662     KoInteractionTool::mouseReleaseEvent(event);
663     updateCursor();
664 }
665 
mouseDoubleClickEvent(KoPointerEvent * event)666 void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event)
667 {
668     QList<KoShape*> shapes;
669     foreach(KoShape *shape, koSelection()->selectedShapes()) {
670         if (shape->boundingRect().contains(event->point) && // first 'cheap' check
671                 shape->outline().contains(event->point)) // this is more expensive but weeds out the almost hits
672             shapes.append(shape);
673     }
674     if (shapes.count() == 0) { // nothing in the selection was clicked on.
675         KoShape *shape = canvas()->shapeManager()->shapeAt (event->point, KoFlake::ShapeOnTop);
676         if (shape) {
677             shapes.append(shape);
678         } else if (m_guideLine->isSelected()) {
679             GuidesTool *guidesTool = dynamic_cast<GuidesTool*>(KoToolManager::instance()->toolById(canvas(), GuidesToolId));
680             if (guidesTool) {
681                 guidesTool->editGuideLine(m_guideLine->orientation(), m_guideLine->index());
682                 activateTool(guidesTool->toolId());
683                 return;
684             }
685         }
686     }
687 
688     QList<KoShape*> shapes2;
689     foreach (KoShape *shape, shapes) {
690         QSet<KoShape*> delegates = shape->toolDelegates();
691         if (delegates.isEmpty()) {
692             shapes2.append(shape);
693         } else {
694             foreach (KoShape *delegatedShape, delegates) {
695                 shapes2.append(delegatedShape);
696             }
697         }
698     }
699 
700 
701     KoToolManager::instance()->switchToolRequested(
702             KoToolManager::instance()->preferredToolForSelection(shapes2));
703 }
704 
moveSelection(int direction,Qt::KeyboardModifiers modifiers)705 bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
706 {
707     qreal x=0.0, y=0.0;
708     if (direction == Qt::Key_Left)
709         x = -5;
710     else if (direction == Qt::Key_Right)
711         x = 5;
712     else if (direction == Qt::Key_Up)
713         y = -5;
714     else if (direction == Qt::Key_Down)
715         y = 5;
716 
717     if (x != 0.0 || y != 0.0) { // actually move
718         if ((modifiers & Qt::ShiftModifier) != 0) {
719             x *= 10;
720             y *= 10;
721         } else if ((modifiers & Qt::AltModifier) != 0) { // more precise
722             x /= 5;
723             y /= 5;
724         }
725 
726         QVector<QPointF> prevPos;
727         QVector<QPointF> newPos;
728         QList<KoShape*> shapes;
729         foreach(KoShape* shape, koSelection()->selectedShapes(KoFlake::TopLevelSelection)) {
730             if (shape->isGeometryProtected()) {
731                 continue;
732             }
733             if (shape->parent()) {
734                 QPointF proposed(x, y);
735                 shape->parent()->model()->proposeMove(shape, proposed);
736                 if (!proposed.isNull()) {
737                     shapes.append(shape);
738                     QPointF p = shape->position();
739                     prevPos.append(p);
740                     p += proposed;
741                     newPos.append(p);
742                 }
743             } else {
744                 shapes.append(shape);
745                 QPointF p = shape->position();
746                 prevPos.append(p);
747                 p.setX(p.x() + x);
748                 p.setY(p.y() + y);
749                 newPos.append(p);
750             }
751         }
752         if (shapes.count() > 0) {
753             // use a timeout to make sure we don't reuse a command possibly deleted by the commandHistory
754             if (m_lastUsedMoveCommand.msecsTo(QTime::currentTime()) > 5000)
755                 m_moveCommand = 0;
756             if (m_moveCommand && shapes != m_lastUsedShapes) {
757                 // We are not moving exactly the same shapes in the same order as last time,
758                 // so we cannot reuse the command
759                 m_moveCommand = 0;
760                 m_lastUsedShapes.clear();
761             }
762             if (m_moveCommand) { // alter previous instead of creating new one.
763                 m_moveCommand->setNewPositions(newPos);
764                 m_moveCommand->redo();
765             } else {
766                 m_lastUsedShapes = shapes;
767                 m_moveCommand = new KoShapeMoveCommand(shapes, prevPos, newPos);
768                 canvas()->addCommand(m_moveCommand);
769             }
770             m_lastUsedMoveCommand = QTime::currentTime();
771             return true;
772         }
773     }
774     return false;
775 }
776 
keyPressEvent(QKeyEvent * event)777 void DefaultTool::keyPressEvent(QKeyEvent *event)
778 {
779     KoInteractionTool::keyPressEvent(event);
780     if (currentStrategy() == 0) {
781         switch (event->key()) {
782         case Qt::Key_Left:
783         case Qt::Key_Right:
784         case Qt::Key_Up:
785         case Qt::Key_Down:
786             if (moveSelection(event->key(), event->modifiers()))
787                 event->accept();
788             break;
789         case Qt::Key_1:
790         case Qt::Key_2:
791         case Qt::Key_3:
792         case Qt::Key_4:
793         case Qt::Key_5:
794             canvas()->resourceManager()->setResource(HotPosition, event->key()-Qt::Key_1);
795             event->accept();
796             break;
797         default:
798             return;
799         }
800     }
801 }
802 
customMoveEvent(KoPointerEvent * event)803 void DefaultTool::customMoveEvent(KoPointerEvent * event)
804 {
805     if (! koSelection()->count()) {
806         event->ignore();
807         return;
808     }
809 
810     int move = qMax(qAbs(event->x()), qAbs(event->y()));
811     int zoom = qAbs(event->z());
812     int rotate = qAbs(event->rotationZ());
813     const int threshold = 2;
814 
815     if (move < threshold && zoom < threshold && rotate < threshold) {
816         if (m_customEventStrategy) {
817             m_customEventStrategy->finishInteraction(event->modifiers());
818             KUndo2Command *command = m_customEventStrategy->createCommand();
819             if (command)
820                 canvas()->addCommand(command);
821             delete m_customEventStrategy;
822             m_customEventStrategy = 0;
823             repaintDecorations();
824         }
825         event->accept();
826         return;
827     }
828 
829     // check if the z-movement is dominant
830     if (zoom > move && zoom > rotate) {
831         // zoom
832         if (! m_customEventStrategy)
833             m_customEventStrategy = new ShapeResizeStrategy(this, event->point, KoFlake::TopLeftHandle);
834     } else if (move > zoom && move > rotate) { // check if x-/y-movement is dominant
835         // move
836         if (! m_customEventStrategy)
837             m_customEventStrategy = new ShapeMoveStrategy(this, event->point);
838     } else if (rotate > zoom && rotate > move) // rotation is dominant
839     {
840         // rotate
841         if (! m_customEventStrategy)
842             m_customEventStrategy = new ShapeRotateStrategy(this, event->point, event->buttons());
843     }
844 
845     if (m_customEventStrategy)
846         m_customEventStrategy->handleCustomEvent(event);
847 
848     event->accept();
849 }
850 
repaintDecorations()851 void DefaultTool::repaintDecorations()
852 {
853     Q_ASSERT(koSelection());
854     if (koSelection()->count() > 0)
855         canvas()->updateCanvas(handlesSize());
856 }
857 
copy() const858 void DefaultTool::copy() const
859 {
860     QList<KoShape *> shapes = canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection);
861     if (!shapes.empty()) {
862         KoShapeOdfSaveHelper saveHelper(shapes);
863         KoDrag drag;
864         drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
865         drag.addToClipboard();
866     }
867 }
868 
deleteSelection()869 void DefaultTool::deleteSelection()
870 {
871     QList<KoShape *> shapes;
872     foreach (KoShape *s, canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection)) {
873         if (!s->isDeletable() || s->isGeometryProtected())
874             continue;
875         shapes << s;
876     }
877     if (!shapes.empty()) {
878         canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes));
879     }
880 }
881 
paste()882 bool DefaultTool::paste()
883 {
884     // we no longer have to do anything as tool Proxy will do it for us
885     return false;
886 }
887 
supportedPasteMimeTypes() const888 QStringList DefaultTool::supportedPasteMimeTypes() const
889 {
890     QStringList list;
891     list << KoOdf::mimeType(KoOdf::Text);
892     return list;
893 }
894 
koSelection()895 KoSelection *DefaultTool::koSelection()
896 {
897     Q_ASSERT(canvas());
898     Q_ASSERT(canvas()->shapeManager());
899     return canvas()->shapeManager()->selection();
900 }
901 
handleAt(const QPointF & point,bool * innerHandleMeaning)902 KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning)
903 {
904     // check for handles in this order; meaning that when handles overlap the one on top is chosen
905     static const KoFlake::SelectionHandle handleOrder[] = {
906         KoFlake::BottomRightHandle,
907         KoFlake::TopLeftHandle,
908         KoFlake::BottomLeftHandle,
909         KoFlake::TopRightHandle,
910         KoFlake::BottomMiddleHandle,
911         KoFlake::RightMiddleHandle,
912         KoFlake::LeftMiddleHandle,
913         KoFlake::TopMiddleHandle,
914         KoFlake::NoHandle
915     };
916 
917     if (koSelection()->count() == 0)
918         return KoFlake::NoHandle;
919 
920     recalcSelectionBox();
921     const KoViewConverter *converter = canvas()->viewConverter();
922     if (!converter) return KoFlake::NoHandle;
923 
924     if (innerHandleMeaning != 0)
925     {
926         QPainterPath path;
927         path.addPolygon(m_selectionOutline);
928         *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point));
929     }
930     for (int i = 0; i < KoFlake::NoHandle; ++i) {
931         KoFlake::SelectionHandle handle = handleOrder[i];
932         QPointF pt = converter->documentToView(point) - converter->documentToView(m_selectionBox[handle]);
933 
934         // if just inside the outline
935         if (qAbs(pt.x()) < HANDLE_DISTANCE &&
936                 qAbs(pt.y()) < HANDLE_DISTANCE) {
937             if (innerHandleMeaning != 0)
938             {
939                 if (qAbs(pt.x()) < 4 && qAbs(pt.y()) < 4)
940                     *innerHandleMeaning = true;
941             }
942             return handle;
943         }
944     }
945     return KoFlake::NoHandle;
946 }
947 
recalcSelectionBox()948 void DefaultTool::recalcSelectionBox()
949 {
950     if (koSelection()->count()==0)
951         return;
952 
953     if (koSelection()->count()>1) {
954         QTransform matrix = koSelection()->absoluteTransformation(0);
955         m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->size())));
956         m_angle = 0.0; //koSelection()->rotation();
957     } else {
958         QTransform matrix = koSelection()->firstSelectedShape()->absoluteTransformation(0);
959         m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->firstSelectedShape()->size())));
960         m_angle = 0.0; //koSelection()->firstSelectedShape()->rotation();
961     }
962     QPolygonF outline = m_selectionOutline; //shorter name in the following :)
963     m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0)+outline.value(1))/2;
964     m_selectionBox[KoFlake::TopRightHandle] = outline.value(1);
965     m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1)+outline.value(2))/2;
966     m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2);
967     m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2)+outline.value(3))/2;
968     m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3);
969     m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3)+outline.value(0))/2;
970     m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0);
971     if (koSelection()->count() == 1) {
972 #if 0        // TODO detect mirroring
973         KoShape *s = koSelection()->firstSelectedShape();
974 
975         if (s->scaleX() < 0) { // vertically mirrored: swap left / right
976             qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]);
977             qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]);
978             qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]);
979         }
980         if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
981             qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]);
982             qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]);
983             qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]);
984         }
985 #endif
986     }
987 }
988 
activate(ToolActivation,const QSet<KoShape * > &)989 void DefaultTool::activate(ToolActivation, const QSet<KoShape*> &)
990 {
991     m_mouseWasInsideHandles = false;
992     m_lastHandle = KoFlake::NoHandle;
993     useCursor(Qt::ArrowCursor);
994     repaintDecorations();
995     delete m_guideLine;
996     m_guideLine = new GuideLine();
997     updateActions();
998 }
999 
deactivate()1000 void DefaultTool::deactivate()
1001 {
1002     repaintDecorations();
1003 }
1004 
selectionAlignHorizontalLeft()1005 void DefaultTool::selectionAlignHorizontalLeft()
1006 {
1007     selectionAlign(KoShapeAlignCommand::HorizontalLeftAlignment);
1008 }
1009 
selectionAlignHorizontalCenter()1010 void DefaultTool::selectionAlignHorizontalCenter()
1011 {
1012     selectionAlign(KoShapeAlignCommand::HorizontalCenterAlignment);
1013 }
1014 
selectionAlignHorizontalRight()1015 void DefaultTool::selectionAlignHorizontalRight()
1016 {
1017     selectionAlign(KoShapeAlignCommand::HorizontalRightAlignment);
1018 }
1019 
selectionAlignVerticalTop()1020 void DefaultTool::selectionAlignVerticalTop()
1021 {
1022     selectionAlign(KoShapeAlignCommand::VerticalTopAlignment);
1023 }
1024 
selectionAlignVerticalCenter()1025 void DefaultTool::selectionAlignVerticalCenter()
1026 {
1027     selectionAlign(KoShapeAlignCommand::VerticalCenterAlignment);
1028 }
1029 
selectionAlignVerticalBottom()1030 void DefaultTool::selectionAlignVerticalBottom()
1031 {
1032     selectionAlign(KoShapeAlignCommand::VerticalBottomAlignment);
1033 }
1034 
selectionGroup()1035 void DefaultTool::selectionGroup()
1036 {
1037     KoSelection* selection = koSelection();
1038     if (! selection)
1039         return;
1040 
1041     QList<KoShape*> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
1042     QList<KoShape*> groupedShapes;
1043 
1044     // only group shapes with an unselected parent
1045     foreach (KoShape* shape, selectedShapes) {
1046         if (! selectedShapes.contains(shape->parent()) && isEditable(shape)) {
1047             groupedShapes << shape;
1048         }
1049     }
1050     KoShapeGroup *group = new KoShapeGroup();
1051     // TODO what if only one shape is left?
1052     KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
1053     canvas()->shapeController()->addShapeDirect(group, cmd);
1054     KoShapeGroupCommand::createCommand(group, groupedShapes, cmd);
1055     canvas()->addCommand(cmd);
1056 
1057     // update selection so we can ungroup immediately again
1058     selection->deselectAll();
1059     selection->select(group);
1060 }
1061 
selectionUngroup()1062 void DefaultTool::selectionUngroup()
1063 {
1064     KoSelection* selection = canvas()->shapeManager()->selection();
1065     if (! selection)
1066         return;
1067 
1068     QList<KoShape*> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
1069     QList<KoShape*> containerSet;
1070 
1071     // only ungroup shape groups with an unselected parent
1072     foreach (KoShape* shape, selectedShapes) {
1073         if (!selectedShapes.contains(shape->parent()) && isEditable(shape)) {
1074             containerSet << shape;
1075         }
1076     }
1077 
1078     KUndo2Command *cmd = 0;
1079 
1080     // add a ungroup command for each found shape container to the macro command
1081     foreach(KoShape* shape, containerSet) {
1082         KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
1083         if (group) {
1084             cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes"));
1085             new KoShapeUngroupCommand(group, group->shapes(),
1086                                       group->parent()? QList<KoShape*>(): canvas()->shapeManager()->topLevelShapes(),
1087                                       cmd);
1088             canvas()->shapeController()->removeShape(group, cmd);
1089         }
1090     }
1091     if (cmd) {
1092         canvas()->addCommand(cmd);
1093     }
1094 }
1095 
selectionAlign(KoShapeAlignCommand::Align align)1096 void DefaultTool::selectionAlign(KoShapeAlignCommand::Align align)
1097 {
1098     KoSelection* selection = canvas()->shapeManager()->selection();
1099     if (! selection)
1100         return;
1101 
1102     QList<KoShape*> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
1103     if (selectedShapes.count() < 1)
1104         return;
1105 
1106     QList<KoShape*> editableShapes = filterEditableShapes(selectedShapes);
1107 
1108     // TODO add an option to the widget so that one can align to the page
1109     // with multiple selected shapes too
1110 
1111     QRectF bb;
1112 
1113     // single selected shape is automatically aligned to document rect
1114     if (editableShapes.count() == 1 ) {
1115         if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize))
1116             return;
1117         bb = QRectF(QPointF(0,0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize));
1118     } else {
1119         foreach( KoShape * shape, editableShapes ) {
1120             bb |= shape->boundingRect();
1121         }
1122     }
1123 
1124     KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb);
1125 
1126     canvas()->addCommand(cmd);
1127     selection->updateSizeAndPosition();
1128 }
1129 
selectionBringToFront()1130 void DefaultTool::selectionBringToFront()
1131 {
1132     selectionReorder(KoShapeReorderCommand::BringToFront);
1133 }
1134 
selectionMoveUp()1135 void DefaultTool::selectionMoveUp()
1136 {
1137     selectionReorder(KoShapeReorderCommand::RaiseShape);
1138 }
1139 
selectionMoveDown()1140 void DefaultTool::selectionMoveDown()
1141 {
1142     selectionReorder(KoShapeReorderCommand::LowerShape);
1143 }
1144 
selectionSendToBack()1145 void DefaultTool::selectionSendToBack()
1146 {
1147     selectionReorder(KoShapeReorderCommand::SendToBack);
1148 }
1149 
selectionReorder(KoShapeReorderCommand::MoveShapeType order)1150 void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
1151 {
1152     KoSelection* selection = canvas()->shapeManager()->selection();
1153     if (! selection)
1154         return;
1155 
1156     QList<KoShape*> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
1157     if (selectedShapes.count() < 1)
1158         return;
1159 
1160     QList<KoShape*> editableShapes = filterEditableShapes(selectedShapes);
1161     if (editableShapes.count() < 1)
1162         return;
1163 
1164     KUndo2Command * cmd = KoShapeReorderCommand::createCommand(editableShapes, canvas()->shapeManager(), order);
1165     if (cmd) {
1166         canvas()->addCommand(cmd);
1167     }
1168 }
1169 
createOptionWidgets()1170 QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
1171 {
1172     QList<QPointer<QWidget> > widgets;
1173     DefaultToolArrangeWidget *defaultArrange = new DefaultToolArrangeWidget(this);
1174     defaultArrange->setWindowTitle(i18n("Arrange"));
1175     widgets.append(defaultArrange);
1176     DefaultToolWidget *defaultTool = new DefaultToolWidget(this);
1177     defaultTool->setWindowTitle(i18n("Geometry"));
1178     widgets.append(defaultTool);
1179     KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(0);
1180     strokeWidget->setWindowTitle(i18n("Line"));
1181     strokeWidget->setCanvas(canvas());
1182     widgets.append(strokeWidget);
1183 
1184     KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0);
1185     fillWidget->setWindowTitle(i18n("Fill"));
1186     fillWidget->setCanvas(canvas());
1187     widgets.append(fillWidget);
1188 
1189     KoShadowConfigWidget *shadowWidget = new KoShadowConfigWidget(0);
1190     shadowWidget->setWindowTitle(i18n("Shadow"));
1191     shadowWidget->setCanvas(canvas());
1192     widgets.append(shadowWidget);
1193 
1194     return widgets;
1195 }
1196 
canvasResourceChanged(int key,const QVariant & res)1197 void DefaultTool::canvasResourceChanged(int key, const QVariant & res)
1198 {
1199     if (key == HotPosition) {
1200         m_hotPosition = static_cast<KoFlake::Position>(res.toInt());
1201         repaintDecorations();
1202     }
1203 }
1204 
createStrategy(KoPointerEvent * event)1205 KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event)
1206 {
1207     // reset the move by keys when a new strategy is created otherwise we might change the
1208     // command after a new command was added. This happens when you where faster than the timer.
1209     m_moveCommand = 0;
1210 
1211     KoShapeManager *shapeManager = canvas()->shapeManager();
1212     KoSelection *select = shapeManager->selection();
1213     bool insideSelection;
1214     KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
1215 
1216     bool editableShape = editableShapesCount(select->selectedShapes());
1217     KoShape::AllowedInteractions interactions = allowedInteractions(select->selectedShapes());
1218 
1219     if (event->buttons() & Qt::MidButton) {
1220         // change the hot selection position when middle clicking on a handle
1221         KoFlake::Position newHotPosition = m_hotPosition;
1222         switch (handle) {
1223         case KoFlake::TopLeftHandle:
1224             newHotPosition = KoFlake::TopLeftCorner;
1225             break;
1226         case KoFlake::TopRightHandle:
1227             newHotPosition = KoFlake::TopRightCorner;
1228             break;
1229         case KoFlake::BottomLeftHandle:
1230             newHotPosition = KoFlake::BottomLeftCorner;
1231             break;
1232         case KoFlake::BottomRightHandle:
1233             newHotPosition = KoFlake::BottomRightCorner;
1234             break;
1235         default:
1236         {
1237             // check if we had hit the center point
1238             const KoViewConverter * converter = canvas()->viewConverter();
1239             QPointF pt = converter->documentToView(event->point-select->absolutePosition());
1240             if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE)
1241                 newHotPosition = KoFlake::CenteredPosition;
1242             break;
1243         }
1244         }
1245         if (m_hotPosition != newHotPosition)
1246             canvas()->resourceManager()->setResource(HotPosition, newHotPosition);
1247         return 0;
1248     }
1249 
1250     bool selectMultiple = event->modifiers() & Qt::ControlModifier;
1251     bool selectNextInStack = event->modifiers() & Qt::ShiftModifier;
1252 
1253     if (editableShape) {
1254         // manipulation of selected shapes goes first
1255         if (handle != KoFlake::NoHandle) {
1256             if (event->buttons() == Qt::LeftButton) {
1257                 // resizing or shearing only with left mouse button
1258                 if (insideSelection) {
1259                     if (interactions.testFlag(KoShape::ResizeAllowed)) {
1260                         return new ShapeResizeStrategy(this, event->point, handle);
1261                     }
1262                 } else if (interactions.testFlag(KoShape::ShearingAllowed)) {
1263                     if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
1264                             handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle)
1265                     {
1266                         return new ShapeShearStrategy(this, event->point, handle);
1267                     }
1268                 }
1269             }
1270             // rotating is allowed for right mouse button too
1271             if (interactions.testFlag(KoShape::RotationAllowed)) {
1272                 if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
1273                     handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle)
1274                     return new ShapeRotateStrategy(this, event->point, event->buttons());
1275             }
1276         }
1277         if (! (selectMultiple || selectNextInStack) && event->buttons() == Qt::LeftButton) {
1278             const QPainterPath outlinePath = select->transformation().map(select->outline());
1279             if (outlinePath.contains(event->point) || outlinePath.intersects(handlePaintRect(event->point)))
1280                 return new ShapeMoveStrategy(this, event->point);
1281         }
1282     }
1283 
1284     if ((event->buttons() & Qt::LeftButton) == 0)
1285         return 0;  // Nothing to do for middle/right mouse button
1286 
1287     KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
1288 
1289     if (!shape && handle == KoFlake::NoHandle) {
1290         // check if we have hit a guide
1291         if (m_guideLine->isValid()) {
1292             m_guideLine->select();
1293             return 0;
1294         }
1295         if (! selectMultiple) {
1296             repaintDecorations();
1297             select->deselectAll();
1298         }
1299         return new KoShapeRubberSelectStrategy(this, event->point);
1300     }
1301 
1302     if (select->isSelected(shape)) {
1303         if (selectMultiple) {
1304             repaintDecorations();
1305             select->deselect(shape);
1306         }
1307     }
1308     else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
1309         repaintDecorations();
1310         if (! selectMultiple)
1311             shapeManager->selection()->deselectAll();
1312         select->select(shape, selectNextInStack ? false : true);
1313         repaintDecorations();
1314         // tablet selection isn't precise and may lead to a move, preventing that
1315         if (event->isTabletEvent()) {
1316             return new NopInteractionStrategy(this);
1317         }
1318         return new ShapeMoveStrategy(this, event->point);
1319     }
1320     return 0;
1321 }
1322 
updateActions()1323 void DefaultTool::updateActions()
1324 {
1325     KoSelection * selection(koSelection());
1326     if (!selection) {
1327         action("object_order_front")->setEnabled(false);
1328         action("object_order_raise")->setEnabled(false);
1329         action("object_order_lower")->setEnabled(false);
1330         action("object_order_back")->setEnabled(false);
1331         action("object_align_horizontal_left")->setEnabled(false);
1332         action("object_align_horizontal_center")->setEnabled(false);
1333         action("object_align_horizontal_right")->setEnabled(false);
1334         action("object_align_vertical_top")->setEnabled(false);
1335         action("object_align_vertical_center")->setEnabled(false);
1336         action("object_align_vertical_bottom")->setEnabled(false);
1337         action("object_group")->setEnabled(false);
1338         action("object_ungroup")->setEnabled(false);
1339         return;
1340     }
1341 
1342     QList<KoShape*> editableShapes = filterEditableShapes(selection->selectedShapes(KoFlake::TopLevelSelection));
1343     bool enable = editableShapes.count () > 0;
1344     action("object_order_front")->setEnabled(enable);
1345     action("object_order_raise")->setEnabled(enable);
1346     action("object_order_lower")->setEnabled(enable);
1347     action("object_order_back")->setEnabled(enable);
1348     enable = (editableShapes.count () > 1) || (enable && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize));
1349     action("object_align_horizontal_left")->setEnabled(enable);
1350     action("object_align_horizontal_center")->setEnabled(enable);
1351     action("object_align_horizontal_right")->setEnabled(enable);
1352     action("object_align_vertical_top")->setEnabled(enable);
1353     action("object_align_vertical_center")->setEnabled(enable);
1354     action("object_align_vertical_bottom")->setEnabled(enable);
1355 
1356     action("object_group")->setEnabled(editableShapes.count() > 1);
1357     bool groupShape = false;
1358     foreach (KoShape * shape, editableShapes) {
1359         if (dynamic_cast<KoShapeGroup *>(shape)) {
1360             groupShape = true;
1361             break;
1362         }
1363     }
1364     action("object_ungroup")->setEnabled(groupShape);
1365 
1366     emit selectionChanged(selection->count());
1367 }
1368 
selection()1369 KoToolSelection* DefaultTool::selection()
1370 {
1371     return m_selectionHandler;
1372 }
1373 
filterEditableShapes(const QList<KoShape * > & shapes) const1374 QList<KoShape*> DefaultTool::filterEditableShapes( const QList<KoShape*> &shapes ) const
1375 {
1376     QList<KoShape*> editableShapes;
1377     foreach( KoShape * shape, shapes ) {
1378         if (isEditable(shape))
1379             editableShapes.append(shape);
1380     }
1381 
1382     return editableShapes;
1383 }
1384 
editableShapesCount(const QList<KoShape * > & shapes) const1385 uint DefaultTool::editableShapesCount( const QList<KoShape*> &shapes ) const
1386 {
1387     uint count = 0;
1388     foreach( KoShape * shape, shapes ) {
1389         if (isEditable(shape))
1390             count++;
1391     }
1392 
1393     return count;
1394 }
1395 
isEditable(const KoShape * shape) const1396 bool DefaultTool::isEditable(const KoShape *shape) const
1397 {
1398     return shape->allowedInteractions(false) & (KoShape::MoveAllowed | KoShape::ResizeAllowed); // TODO: check parents ContentChangeAllowed
1399 }
1400 
allowedInteractions(const QList<KoShape * > & shapes) const1401 KoShape::AllowedInteractions DefaultTool::allowedInteractions(const QList<KoShape*> &shapes) const
1402 {
1403     KoShape::AllowedInteractions interactions;
1404     foreach(KoShape *shape, shapes) {
1405         interactions |= shape->allowedInteractions(false);
1406     }
1407     return interactions;
1408 }
1409