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