1 /* This file is part of the KDE project
2 *
3 * Copyright (c) 2005-2010 Boudewijn Rempt <boud@valdyas.org>
4 * Copyright (C) 2006-2008 Thomas Zander <zander@kde.org>
5 * Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
6 * Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
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 // flake
25 #include "KoToolManager.h"
26 #include "KoToolManager_p.h"
27
28 #include "KoToolRegistry.h"
29 #include "KoToolProxy.h"
30 #include "KoToolProxy_p.h"
31 #include "KoSelection.h"
32 #include "KoCanvasController.h"
33 #include "KoCanvasControllerWidget.h"
34 #include "KoShape.h"
35 #include "KoShapeLayer.h"
36 #include "KoShapeRegistry.h"
37 #include "KoShapeManager.h"
38 #include "KoCanvasBase.h"
39 #include "KoInputDeviceHandlerRegistry.h"
40 #include "KoInputDeviceHandlerEvent.h"
41 #include "KoPointerEvent.h"
42 #include "tools/KoCreateShapesTool.h"
43 #include "tools/KoZoomTool.h"
44 #include "tools/KoPanTool.h"
45 #include "FlakeDebug.h"
46
47 // KF5
48 #include <KActionCollection>
49 #include <KLocalizedString>
50
51 // Qt
52 #include <QWidget>
53 #include <QEvent>
54 #include <QWheelEvent>
55 #include <QMouseEvent>
56 #include <QPaintEvent>
57 #include <QTabletEvent>
58 #include <QKeyEvent>
59 #include <QVBoxLayout>
60 #include <QStringList>
61 #include <QApplication>
62 #include <QAction>
63 #include <QKeySequence>
64 #include <QStack>
65 #include <QLabel>
66 #include <QGlobalStatic>
67
68 Q_GLOBAL_STATIC(KoToolManager, s_instance)
69
70 class Q_DECL_HIDDEN KoToolAction::Private
71 {
72 public:
73 ToolHelper* toolHelper;
74 };
75
KoToolAction(ToolHelper * toolHelper)76 KoToolAction::KoToolAction(ToolHelper* toolHelper)
77 : QObject(toolHelper)
78 , d(new Private)
79 {
80 d->toolHelper = toolHelper;
81 }
82
~KoToolAction()83 KoToolAction::~KoToolAction()
84 {
85 delete d;
86 }
87
trigger()88 void KoToolAction::trigger()
89 {
90 d->toolHelper->activate();
91 }
92
93
iconText() const94 QString KoToolAction::iconText() const
95 {
96 return d->toolHelper->iconText();
97 }
98
toolTip() const99 QString KoToolAction::toolTip() const
100 {
101 return d->toolHelper->toolTip();
102 }
103
id() const104 QString KoToolAction::id() const
105 {
106 return d->toolHelper->id();
107 }
108
iconName() const109 QString KoToolAction::iconName() const
110 {
111 return d->toolHelper->iconName();
112 }
113
shortcut() const114 QKeySequence KoToolAction::shortcut() const
115 {
116 return d->toolHelper->shortcut();
117 }
118
119
section() const120 QString KoToolAction::section() const
121 {
122 return d->toolHelper->toolType();
123 }
124
priority() const125 int KoToolAction::priority() const
126 {
127 return d->toolHelper->priority();
128 }
129
buttonGroupId() const130 int KoToolAction::buttonGroupId() const
131 {
132 return d->toolHelper->uniqueId();
133 }
134
visibilityCode() const135 QString KoToolAction::visibilityCode() const
136 {
137 return d->toolHelper->activationShapeId();
138 }
139
140
141 class CanvasData
142 {
143 public:
CanvasData(KoCanvasController * cc,const KoInputDevice & id)144 CanvasData(KoCanvasController *cc, const KoInputDevice &id)
145 : activeTool(0),
146 canvas(cc),
147 inputDevice(id),
148 dummyToolWidget(0),
149 dummyToolLabel(0)
150 {
151 }
152
~CanvasData()153 ~CanvasData()
154 {
155 // the dummy tool widget does not necessarily have a parent and we create it, so we delete it.
156 delete dummyToolWidget;
157 }
158
activateToolActions()159 void activateToolActions()
160 {
161 disabledDisabledActions.clear();
162 disabledActions.clear();
163 disabledCanvasShortcuts.clear();
164 // we do several things here
165 // 1. enable the actions of the active tool
166 // 2. disable conflicting actions
167 // 3. replace conflicting actions in the action collection
168 KActionCollection *canvasActionCollection = canvas->actionCollection();
169
170 QHash<QString, QAction *> toolActions = activeTool->actions();
171 QHash<QString, QAction *>::const_iterator it(toolActions.constBegin());
172
173 for (; it != toolActions.constEnd(); ++it) {
174 if (canvasActionCollection) {
175
176 QString toolActionID = it.key();
177 QAction *toolAction = it.value();
178
179 QAction * action = qobject_cast<QAction*>(canvasActionCollection->action(it.key()));
180 if (action) {
181 canvasActionCollection->takeAction(action);
182 if (action != it.value()) {
183 if (action->isEnabled()) {
184 action->setEnabled(false);
185 disabledActions.append(action);
186 } else {
187 disabledDisabledActions.append(action);
188 }
189 }
190 }
191 foreach(QAction *a, canvasActionCollection->actions()) {
192 QAction *canvasAction = dynamic_cast<QAction*>(a);
193 if (canvasAction && !canvasAction->shortcut().toString().isEmpty() && canvasAction->shortcut() == toolAction->shortcut()) {
194 warnFlake << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString();
195 disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString();
196 canvasAction->setShortcut(QKeySequence());
197 }
198 }
199 canvasActionCollection->addAction(toolActionID, toolAction);
200 }
201 it.value()->setEnabled(true);
202 }
203 canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime.
204 }
205
deactivateToolActions()206 void deactivateToolActions()
207 {
208
209 if (!activeTool)
210 return;
211 // disable actions of active tool
212 foreach(QAction *action, activeTool->actions()) {
213 action->setEnabled(false);
214 }
215
216 // enable actions which where disabled on activating the active tool
217 // and re-add them to the action collection
218 KActionCollection *ac = canvas->actionCollection();
219 foreach(QPointer<QAction> action, disabledDisabledActions) {
220 if (action) {
221 if (ac) {
222 ac->addAction(action->objectName(), action);
223 }
224 }
225 }
226 disabledDisabledActions.clear();
227
228 foreach(QPointer<QAction> action, disabledActions) {
229 if (action) {
230 action->setEnabled(true);
231 if(ac) {
232 ac->addAction(action->objectName(), action);
233 }
234 }
235 }
236 disabledActions.clear();
237
238 QMap<QPointer<QAction>, QString>::const_iterator it(disabledCanvasShortcuts.constBegin());
239 for (; it != disabledCanvasShortcuts.constEnd(); ++it) {
240 QAction *action = it.key();
241 QString shortcut = it.value();
242 action->setShortcut(shortcut);
243 }
244 disabledCanvasShortcuts.clear();
245 }
246
247 KoToolBase *activeTool; // active Tool
248 QString activeToolId; // the id of the active Tool
249 QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to.
250 QHash<QString, KoToolBase*> allTools; // all the tools that are created for this canvas.
251 QStack<QString> stack; // stack of temporary tools
252 KoCanvasController *const canvas;
253 const KoInputDevice inputDevice;
254 QWidget *dummyToolWidget; // the widget shown in the toolDocker.
255 QLabel *dummyToolLabel;
256 QList<QPointer<QAction> > disabledActions; ///< disabled conflicting actions
257 QList<QPointer<QAction> > disabledDisabledActions; ///< disabled conflicting actions that were already disabled
258 QMap<QPointer<QAction>, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides
259 };
260
Private(KoToolManager * qq)261 KoToolManager::Private::Private(KoToolManager *qq)
262 : q(qq),
263 canvasData(0),
264 layerExplicitlyDisabled(false)
265 {
266 }
267
~Private()268 KoToolManager::Private::~Private()
269 {
270 qDeleteAll(tools);
271 }
272
273 // helper method.
createCanvasData(KoCanvasController * controller,const KoInputDevice & device)274 CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device)
275 {
276 QHash<QString, KoToolBase*> toolsHash;
277 foreach(ToolHelper *tool, tools) {
278 QPair<QString, KoToolBase*> toolPair = q->createTools(controller, tool);
279 if (toolPair.second) { // only if a real tool was created
280 toolsHash.insert(toolPair.first, toolPair.second);
281 }
282 }
283 KoCreateShapesTool *createShapesTool = dynamic_cast<KoCreateShapesTool*>(toolsHash.value(KoCreateShapesTool_ID));
284 Q_ASSERT(createShapesTool);
285 QString id = KoShapeRegistry::instance()->keys()[0];
286 createShapesTool->setShapeId(id);
287
288 CanvasData *cd = new CanvasData(controller, device);
289 cd->allTools = toolsHash;
290 return cd;
291 }
292
setup()293 void KoToolManager::Private::setup()
294 {
295 if (tools.size() > 0)
296 return;
297
298 KoShapeRegistry::instance();
299 KoToolRegistry *registry = KoToolRegistry::instance();
300 foreach(const QString & id, registry->keys()) {
301 ToolHelper *t = new ToolHelper(registry->value(id));
302 tools.append(t);
303 }
304
305 // connect to all tools so we can hear their button-clicks
306 foreach(ToolHelper *tool, tools)
307 connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*)));
308
309 // load pluggable input devices
310 KoInputDeviceHandlerRegistry::instance();
311 }
312
connectActiveTool()313 void KoToolManager::Private::connectActiveTool()
314 {
315 if (canvasData->activeTool) {
316 connect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)),
317 q, SLOT(updateCursor(QCursor)));
318 connect(canvasData->activeTool, SIGNAL(activateTool(QString)),
319 q, SLOT(switchToolRequested(QString)));
320 connect(canvasData->activeTool, SIGNAL(activateTemporary(QString)),
321 q, SLOT(switchToolTemporaryRequested(QString)));
322 connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
323 connect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)),
324 q, SIGNAL(changedStatusText(QString)));
325 }
326
327 // we expect the tool to emit a cursor on activation.
328 updateCursor(Qt::ForbiddenCursor);
329 }
330
disconnectActiveTool()331 void KoToolManager::Private::disconnectActiveTool()
332 {
333 if (canvasData->activeTool) {
334 canvasData->deactivateToolActions();
335 // repaint the decorations before we deactivate the tool as it might deleted
336 // data needed for the repaint
337 canvasData->activeTool->deactivate();
338 disconnect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)),
339 q, SLOT(updateCursor(QCursor)));
340 disconnect(canvasData->activeTool, SIGNAL(activateTool(QString)),
341 q, SLOT(switchToolRequested(QString)));
342 disconnect(canvasData->activeTool, SIGNAL(activateTemporary(QString)),
343 q, SLOT(switchToolTemporaryRequested(QString)));
344 disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
345 disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)),
346 q, SIGNAL(changedStatusText(QString)));
347 }
348
349 // emit a empty status text to clear status text from last active tool
350 emit q->changedStatusText(QString());
351 }
352
switchTool(KoToolBase * tool,bool temporary)353 void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary)
354 {
355
356 Q_ASSERT(tool);
357 if (canvasData == 0)
358 return;
359
360 if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID)
361 return;
362
363 disconnectActiveTool();
364 canvasData->activeTool = tool;
365 connectActiveTool();
366 postSwitchTool(temporary);
367 }
368
switchTool(const QString & id,bool temporary)369 void KoToolManager::Private::switchTool(const QString &id, bool temporary)
370 {
371 Q_ASSERT(canvasData);
372 if (!canvasData) return;
373
374 if (canvasData->activeTool && temporary)
375 canvasData->stack.push(canvasData->activeToolId);
376 canvasData->activeToolId = id;
377 KoToolBase *tool = canvasData->allTools.value(id);
378 if (! tool) {
379 return;
380 }
381
382 foreach(ToolHelper *th, tools) {
383 if (th->id() == id) {
384 canvasData->activationShapeId = th->activationShapeId();
385 break;
386 }
387 }
388
389 switchTool(tool, temporary);
390 }
391
postSwitchTool(bool temporary)392 void KoToolManager::Private::postSwitchTool(bool temporary)
393 {
394 #ifndef NDEBUG
395 int canvasCount = 1;
396 foreach(QList<CanvasData*> list, canvasses) {
397 bool first = true;
398 foreach(CanvasData *data, list) {
399 if (first) {
400 debugFlake << "Canvas" << canvasCount++;
401 }
402 debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : "");
403 first = false;
404 }
405 }
406 #endif
407 Q_ASSERT(canvasData);
408 if (!canvasData) return;
409
410 KoToolBase::ToolActivation toolActivation;
411 if (temporary)
412 toolActivation = KoToolBase::TemporaryActivation;
413 else
414 toolActivation = KoToolBase::DefaultActivation;
415 QSet<KoShape*> shapesToOperateOn;
416 if (canvasData->activeTool
417 && canvasData->activeTool->canvas()
418 && canvasData->activeTool->canvas()->shapeManager()) {
419 KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection();
420 Q_ASSERT(selection);
421
422 foreach(KoShape *shape, selection->selectedShapes()) {
423 QSet<KoShape*> delegates = shape->toolDelegates();
424 if (delegates.isEmpty()) { // no delegates, just the orig shape
425 shapesToOperateOn << shape;
426 } else {
427 shapesToOperateOn += delegates;
428 }
429 }
430 }
431
432 if (canvasData->canvas->canvas()) {
433 // Caller of postSwitchTool expect this to be called to update the selected tool
434 updateToolForProxy();
435 canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
436 KoCanvasBase *canvas = canvasData->canvas->canvas();
437 canvas->updateInputMethodInfo();
438 } else {
439 canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
440 }
441
442 QList<QPointer<QWidget> > optionWidgetList = canvasData->activeTool->optionWidgets();
443 if (optionWidgetList.empty()) { // no option widget.
444 QWidget *toolWidget;
445 QString title;
446 foreach(ToolHelper *tool, tools) {
447 if (tool->id() == canvasData->activeTool->toolId()) {
448 title = tool->toolTip();
449 break;
450 }
451 }
452 toolWidget = canvasData->dummyToolWidget;
453 if (toolWidget == 0) {
454 toolWidget = new QWidget();
455 toolWidget->setObjectName("DummyToolWidget");
456 QVBoxLayout *layout = new QVBoxLayout(toolWidget);
457 layout->setMargin(3);
458 canvasData->dummyToolLabel = new QLabel(toolWidget);
459 layout->addWidget(canvasData->dummyToolLabel);
460 layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
461 toolWidget->setLayout(layout);
462 canvasData->dummyToolWidget = toolWidget;
463 }
464 canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title));
465 optionWidgetList.append(toolWidget);
466 }
467
468 // Activate the actions for the currently active tool
469 canvasData->activateToolActions();
470
471 emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool));
472
473 KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
474 if (canvasControllerWidget) {
475 canvasControllerWidget->setToolOptionWidgets(optionWidgetList);
476 }
477 }
478
479
switchCanvasData(CanvasData * cd)480 void KoToolManager::Private::switchCanvasData(CanvasData *cd)
481 {
482
483 Q_ASSERT(cd);
484
485 KoCanvasBase *oldCanvas = 0;
486 KoInputDevice oldInputDevice;
487
488 if (canvasData) {
489 oldCanvas = canvasData->canvas->canvas();
490 oldInputDevice = canvasData->inputDevice;
491
492 if (canvasData->activeTool) {
493 disconnectActiveTool();
494 }
495
496 KoToolProxy *proxy = proxies.value(oldCanvas);
497 Q_ASSERT(proxy);
498 proxy->setActiveTool(0);
499 }
500
501 canvasData = cd;
502 inputDevice = canvasData->inputDevice;
503
504 if (canvasData->activeTool) {
505 connectActiveTool();
506 postSwitchTool(false);
507 }
508
509 if (oldInputDevice != canvasData->inputDevice) {
510 emit q->inputDeviceChanged(canvasData->inputDevice);
511 }
512
513 if (oldCanvas != canvasData->canvas->canvas()) {
514 emit q->changedCanvas(canvasData->canvas->canvas());
515 }
516 }
517
518
toolActivated(ToolHelper * tool)519 void KoToolManager::Private::toolActivated(ToolHelper *tool)
520 {
521 Q_ASSERT(tool);
522
523 Q_ASSERT(canvasData);
524 if (!canvasData) return;
525 KoToolBase *t = canvasData->allTools.value(tool->id());
526 Q_ASSERT(t);
527
528 canvasData->activeToolId = tool->id();
529 canvasData->activationShapeId = tool->activationShapeId();
530
531 switchTool(t, false);
532 }
533
detachCanvas(KoCanvasController * controller)534 void KoToolManager::Private::detachCanvas(KoCanvasController *controller)
535 {
536 Q_ASSERT(controller);
537 // check if we are removing the active canvas controller
538 if (canvasData && canvasData->canvas == controller) {
539 KoCanvasController *newCanvas = 0;
540 // try to find another canvas controller beside the one we are removing
541 foreach(KoCanvasController* canvas, canvasses.keys()) {
542 if (canvas != controller) {
543 // yay found one
544 newCanvas = canvas;
545 break;
546 }
547 }
548 if (newCanvas) {
549 switchCanvasData(canvasses.value(newCanvas).first());
550 } else {
551 KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
552 if (canvasControllerWidget) {
553 canvasControllerWidget->setToolOptionWidgets(QList<QPointer<QWidget> >());
554 }
555 // as a last resort just set a blank one
556 canvasData = 0;
557 }
558 }
559
560 KoToolProxy *proxy = proxies.value(controller->canvas());
561 if (proxy)
562 proxy->setActiveTool(0);
563
564 QList<KoToolBase *> tools;
565 foreach(CanvasData *canvasData, canvasses.value(controller)) {
566 foreach(KoToolBase *tool, canvasData->allTools) {
567 if (! tools.contains(tool)) {
568 tools.append(tool);
569 }
570 }
571 delete canvasData;
572 }
573 foreach(KoToolBase *tool, tools) {
574 uniqueToolIds.remove(tool);
575 delete tool;
576 }
577 canvasses.remove(controller);
578 emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
579 }
580
attachCanvas(KoCanvasController * controller)581 void KoToolManager::Private::attachCanvas(KoCanvasController *controller)
582 {
583 Q_ASSERT(controller);
584 CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse());
585
586 // switch to new canvas as the active one.
587 switchCanvasData(cd);
588
589 inputDevice = cd->inputDevice;
590 QList<CanvasData*> canvasses_;
591 canvasses_.append(cd);
592 canvasses[controller] = canvasses_;
593
594 KoToolProxy *tp = proxies[controller->canvas()];
595 if (tp)
596 tp->priv()->setCanvasController(controller);
597
598 if (cd->activeTool == 0) {
599 // no active tool, so we activate the highest priority main tool
600 int highestPriority = INT_MAX;
601 ToolHelper * helper = 0;
602 foreach(ToolHelper * th, tools) {
603 if (th->toolType() == KoToolFactoryBase::mainToolType()) {
604 if (th->priority() < highestPriority) {
605 highestPriority = qMin(highestPriority, th->priority());
606 helper = th;
607 }
608 }
609 }
610 if (helper)
611 toolActivated(helper);
612 }
613
614 Connector *connector = new Connector(controller->canvas()->shapeManager());
615 connect(connector, SIGNAL(selectionChanged(QList<KoShape*>)), q,
616 SLOT(selectionChanged(QList<KoShape*>)));
617 connect(controller->canvas()->shapeManager()->selection(),
618 SIGNAL(currentLayerChanged(const KoShapeLayer*)),
619 q, SLOT(currentLayerChanged(const KoShapeLayer*)));
620
621 emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
622 }
623
movedFocus(QWidget * from,QWidget * to)624 void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to)
625 {
626 Q_UNUSED(from);
627 // no canvas anyway or no focus set anyway?
628 if (!canvasData || to == 0) {
629 return;
630 }
631
632 // Check if this app is about QWidget-based KoCanvasControllerWidget canvasses
633 // XXX: Focus handling for non-qwidget based canvases!
634 KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
635 if (!canvasControllerWidget) {
636 return;
637 }
638
639 // canvasWidget is set as focusproxy for KoCanvasControllerWidget,
640 // so all focus checks are to be done against canvasWidget objects
641
642 // focus returned to current canvas?
643 if (to == canvasData->canvas->canvas()->canvasWidget()) {
644 // nothing to do
645 return;
646 }
647
648 // if the 'to' is one of our canvasWidgets, then switch.
649
650 // for code simplicity the current canvas will be checked again,
651 // but would have been caught already in the lines above, so no issue
652 KoCanvasController *newCanvas = 0;
653 foreach(KoCanvasController* canvas, canvasses.keys()) {
654 if (canvas->canvas()->canvasWidget() == to) {
655 newCanvas = canvas;
656 break;
657 }
658 }
659
660 // none of our canvasWidgets got focus?
661 if (newCanvas == 0) {
662 return;
663 }
664
665 // switch to canvasdata matching inputdevice used last with this app instance
666 foreach(CanvasData *data, canvasses.value(newCanvas)) {
667 if (data->inputDevice == inputDevice) {
668 switchCanvasData(data);
669 return;
670 }
671 }
672 // if no such inputDevice for this canvas, then simply fallback to first one
673 switchCanvasData(canvasses.value(newCanvas).first());
674 }
675
updateCursor(const QCursor & cursor)676 void KoToolManager::Private::updateCursor(const QCursor &cursor)
677 {
678 Q_ASSERT(canvasData);
679 Q_ASSERT(canvasData->canvas);
680 Q_ASSERT(canvasData->canvas->canvas());
681 canvasData->canvas->canvas()->setCursor(cursor);
682 }
683
selectionChanged(const QList<KoShape * > & shapes)684 void KoToolManager::Private::selectionChanged(const QList<KoShape*> &shapes)
685 {
686 QList<QString> types;
687 foreach(KoShape *shape, shapes) {
688 QSet<KoShape*> delegates = shape->toolDelegates();
689 if (delegates.isEmpty()) { // no delegates, just the orig shape
690 delegates << shape;
691 }
692
693 foreach (KoShape *shape2, delegates) {
694 Q_ASSERT(shape2);
695 if (! types.contains(shape2->shapeId())) {
696 types.append(shape2->shapeId());
697 }
698 }
699 }
700
701 // check if there is still a shape selected the active tool can work on
702 // there needs to be at least one shape for a tool without an activationShapeId
703 // to work
704 // if not change the current tool to the default tool
705 if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0)
706 && canvasData->activationShapeId != "flake/always"
707 && canvasData->activationShapeId != "flake/edit") {
708
709 bool currentToolWorks = false;
710 foreach (const QString &type, types) {
711 if (canvasData->activationShapeId.split(',').contains(type)) {
712 currentToolWorks = true;
713 break;
714 }
715 }
716 if (!currentToolWorks) {
717 switchTool(KoInteractionTool_ID, false);
718 }
719 }
720 emit q->toolCodesSelected(types);
721 // First time the tool is activated, it is not shown
722 // because activetool must be set before optionwidgets are set.
723 // Activetool is not set until q->toolCodesSelected() is emitted above,
724 // so the setting in postSwitchTool() does not work.
725 // NOTE: May only be true for non-default tools like for chart, formula etc,
726 // so do not remove the postSwitchTool() setting until you are absolutely certain.
727 QList<QPointer<QWidget> > optionWidgetList = canvasData->activeTool->optionWidgets();
728 KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
729 if (canvasControllerWidget && !optionWidgetList.isEmpty()) {
730 canvasControllerWidget->setToolOptionWidgets(optionWidgetList);
731 }
732 }
733
currentLayerChanged(const KoShapeLayer * layer)734 void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer)
735 {
736 emit q->currentLayerChanged(canvasData->canvas, layer);
737 layerExplicitlyDisabled = layer && !layer->isEditable();
738 updateToolForProxy();
739
740 debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled;
741 }
742
updateToolForProxy()743 void KoToolManager::Private::updateToolForProxy()
744 {
745 KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas());
746 if(!proxy) return;
747
748 bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always"));
749 proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0);
750 }
751
switchInputDevice(const KoInputDevice & device)752 void KoToolManager::Private::switchInputDevice(const KoInputDevice &device)
753 {
754 Q_ASSERT(canvasData);
755 if (!canvasData) return;
756 if (inputDevice == device) return;
757 if (inputDevice.isMouse() && device.isMouse()) return;
758 if (device.isMouse() && !inputDevice.isMouse()) {
759 // we never switch back to mouse from a tablet input device, so the user can use the
760 // mouse to edit the settings for a tool activated by a tablet. See bugs
761 // https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501.
762 // We do continue to switch between tablet devices, thought.
763 return;
764 }
765
766 QList<CanvasData*> items = canvasses[canvasData->canvas];
767
768 // disable all actions for all tools in the all canvasdata objects for this canvas.
769 foreach(CanvasData *cd, items) {
770 foreach(KoToolBase* tool, cd->allTools) {
771 foreach(QAction * action, tool->actions()) {
772 action->setEnabled(false);
773 }
774 }
775 }
776
777 // search for a canvasdata object for the current input device
778 foreach(CanvasData *cd, items) {
779 if (cd->inputDevice == device) {
780 switchCanvasData(cd);
781
782 if (!canvasData->activeTool) {
783 switchTool(KoInteractionTool_ID, false);
784 }
785
786 return;
787 }
788 }
789
790 // still here? That means we need to create a new CanvasData instance with the current InputDevice.
791 CanvasData *cd = createCanvasData(canvasData->canvas, device);
792 // switch to new canvas as the active one.
793 QString oldTool = canvasData->activeToolId;
794
795 items.append(cd);
796 canvasses[cd->canvas] = items;
797
798 switchCanvasData(cd);
799
800 q->switchToolRequested(oldTool);
801 }
802
registerToolProxy(KoToolProxy * proxy,KoCanvasBase * canvas)803 void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas)
804 {
805 proxies.insert(canvas, proxy);
806 foreach(KoCanvasController *controller, canvasses.keys()) {
807 if (controller->canvas() == canvas) {
808 proxy->priv()->setCanvasController(controller);
809 break;
810 }
811 }
812 }
813
switchToolByShortcut(QKeyEvent * event)814 void KoToolManager::Private::switchToolByShortcut(QKeyEvent *event)
815 {
816 if (event->key() == Qt::Key_Space && event->modifiers() == 0) {
817 switchTool(KoPanTool_ID, true);
818 } else if (event->key() == Qt::Key_Escape && event->modifiers() == 0) {
819 switchTool(KoInteractionTool_ID, false);
820 }
821 }
822
823 // ******** KoToolManager **********
KoToolManager()824 KoToolManager::KoToolManager()
825 : QObject(),
826 d(new Private(this))
827 {
828 connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)),
829 this, SLOT(movedFocus(QWidget*,QWidget*)));
830 }
831
~KoToolManager()832 KoToolManager::~KoToolManager()
833 {
834 delete d;
835 }
836
toolActionList() const837 QList<KoToolAction*> KoToolManager::toolActionList() const
838 {
839 QList<KoToolAction*> answer;
840 answer.reserve(d->tools.count());
841 foreach(ToolHelper *tool, d->tools) {
842 if (tool->id() == KoCreateShapesTool_ID)
843 continue; // don't show this one.
844 answer.append(tool->toolAction());
845 }
846 return answer;
847 }
848
requestToolActivation(KoCanvasController * controller)849 void KoToolManager::requestToolActivation(KoCanvasController * controller)
850 {
851 if (d->canvasses.contains(controller)) {
852 QString activeToolId = d->canvasses.value(controller).first()->activeToolId;
853 foreach(ToolHelper * th, d->tools) {
854 if (th->id() == activeToolId) {
855 d->toolActivated(th);
856 break;
857 }
858 }
859 }
860 }
861
currentInputDevice() const862 KoInputDevice KoToolManager::currentInputDevice() const
863 {
864 return d->inputDevice;
865 }
866
registerTools(KActionCollection * ac,KoCanvasController * controller)867 void KoToolManager::registerTools(KActionCollection *ac, KoCanvasController *controller)
868 {
869 Q_ASSERT(controller);
870 Q_ASSERT(ac);
871
872 d->setup();
873
874 if (!d->canvasses.contains(controller)) {
875 return;
876 }
877
878 // Actions available during the use of individual tools
879 CanvasData *cd = d->canvasses.value(controller).first();
880 foreach(KoToolBase *tool, cd->allTools) {
881 QHash<QString, QAction*> actions = tool->actions();
882 QHash<QString, QAction*>::const_iterator action(actions.constBegin());
883 for (; action != actions.constEnd(); ++action) {
884 if (!ac->action(action.key()))
885 ac->addAction(action.key(), action.value());
886 }
887 }
888
889 // Actions used to switch tools via shortcuts
890 foreach(ToolHelper * th, d->tools) {
891 if (ac->action(th->id())) {
892 continue;
893 }
894 ShortcutToolAction* action = th->createShortcutToolAction(ac);
895 ac->addAction(th->id(), action);
896 }
897 }
898
addController(KoCanvasController * controller)899 void KoToolManager::addController(KoCanvasController *controller)
900 {
901 Q_ASSERT(controller);
902 if (d->canvasses.contains(controller))
903 return;
904 d->setup();
905 d->attachCanvas(controller);
906 connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*)));
907 connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
908 connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
909 }
910
removeCanvasController(KoCanvasController * controller)911 void KoToolManager::removeCanvasController(KoCanvasController *controller)
912 {
913 Q_ASSERT(controller);
914 disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
915 disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
916 d->detachCanvas(controller);
917 }
918
attemptCanvasControllerRemoval(QObject * controller)919 void KoToolManager::attemptCanvasControllerRemoval(QObject* controller)
920 {
921 KoCanvasControllerProxyObject* controllerActual = qobject_cast<KoCanvasControllerProxyObject*>(controller);
922 if (controllerActual) {
923 removeCanvasController(controllerActual->canvasController());
924 }
925 }
926
updateShapeControllerBase(KoShapeBasedDocumentBase * shapeController,KoCanvasController * canvasController)927 void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController)
928 {
929 if (!d->canvasses.contains(canvasController))
930 return;
931
932 QList<CanvasData *> canvasses = d->canvasses[canvasController];
933 foreach(CanvasData *canvas, canvasses) {
934 foreach(KoToolBase *tool, canvas->allTools) {
935 tool->updateShapeController(shapeController);
936 }
937 }
938 }
939
switchToolRequested(const QString & id)940 void KoToolManager::switchToolRequested(const QString & id)
941 {
942 Q_ASSERT(d->canvasData);
943 if (!d->canvasData) return;
944
945 while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack
946 d->canvasData->stack.pop();
947 d->switchTool(id, false);
948 }
949
switchInputDeviceRequested(const KoInputDevice & id)950 void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id)
951 {
952 if (!d->canvasData) return;
953 d->switchInputDevice(id);
954 }
955
switchToolTemporaryRequested(const QString & id)956 void KoToolManager::switchToolTemporaryRequested(const QString &id)
957 {
958 d->switchTool(id, true);
959 }
960
switchBackRequested()961 void KoToolManager::switchBackRequested()
962 {
963 if (!d->canvasData) return;
964
965 if (d->canvasData->stack.isEmpty()) {
966 // default to changing to the interactionTool
967 d->switchTool(KoInteractionTool_ID, false);
968 return;
969 }
970 d->switchTool(d->canvasData->stack.pop(), false);
971 }
972
shapeCreatorTool(KoCanvasBase * canvas) const973 KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const
974 {
975 Q_ASSERT(canvas);
976 foreach(KoCanvasController *controller, d->canvasses.keys()) {
977 if (controller->canvas() == canvas) {
978 KoCreateShapesTool *createTool = dynamic_cast<KoCreateShapesTool*>
979 (d->canvasData->allTools.value(KoCreateShapesTool_ID));
980 Q_ASSERT(createTool /* ID changed? */);
981 return createTool;
982 }
983 }
984 Q_ASSERT(0); // this should not happen
985 return 0;
986 }
987
toolById(KoCanvasBase * canvas,const QString & id) const988 KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const
989 {
990 Q_ASSERT(canvas);
991 foreach(KoCanvasController *controller, d->canvasses.keys()) {
992 if (controller->canvas() == canvas)
993 return d->canvasData->allTools.value(id);
994 }
995 return 0;
996 }
997
activeCanvasController() const998 KoCanvasController *KoToolManager::activeCanvasController() const
999 {
1000 if (! d->canvasData) return 0;
1001 return d->canvasData->canvas;
1002 }
1003
preferredToolForSelection(const QList<KoShape * > & shapes)1004 QString KoToolManager::preferredToolForSelection(const QList<KoShape*> &shapes)
1005 {
1006 QList<QString> types;
1007 foreach(KoShape *shape, shapes)
1008 if (! types.contains(shape->shapeId()))
1009 types.append(shape->shapeId());
1010
1011 QString toolType = KoInteractionTool_ID;
1012 int prio = INT_MAX;
1013 foreach(ToolHelper *helper, d->tools) {
1014 if (helper->priority() >= prio)
1015 continue;
1016 if (helper->toolType() == KoToolFactoryBase::mainToolType())
1017 continue;
1018
1019 bool toolWillWork = false;
1020 foreach (const QString &type, types) {
1021 if (helper->activationShapeId().split(',').contains(type)) {
1022 toolWillWork = true;
1023 break;
1024 }
1025 }
1026 if (toolWillWork) {
1027 toolType = helper->id();
1028 prio = helper->priority();
1029 }
1030 }
1031 return toolType;
1032 }
1033
injectDeviceEvent(KoInputDeviceHandlerEvent * event)1034 void KoToolManager::injectDeviceEvent(KoInputDeviceHandlerEvent * event)
1035 {
1036 if (d->canvasData && d->canvasData->canvas->canvas()) {
1037 if (static_cast<KoInputDeviceHandlerEvent::Type>(event->type()) == KoInputDeviceHandlerEvent::ButtonPressed)
1038 d->canvasData->activeTool->customPressEvent(event->pointerEvent());
1039 else if (static_cast<KoInputDeviceHandlerEvent::Type>(event->type()) == KoInputDeviceHandlerEvent::ButtonReleased)
1040 d->canvasData->activeTool->customReleaseEvent(event->pointerEvent());
1041 else if (static_cast<KoInputDeviceHandlerEvent::Type>(event->type()) == KoInputDeviceHandlerEvent::PositionChanged)
1042 d->canvasData->activeTool->customMoveEvent(event->pointerEvent());
1043 }
1044 }
1045
addDeferredToolFactory(KoToolFactoryBase * toolFactory)1046 void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory)
1047 {
1048 ToolHelper *tool = new ToolHelper(toolFactory);
1049 // make sure all plugins are loaded as otherwise we will not load them
1050 d->setup();
1051 d->tools.append(tool);
1052
1053 // connect to all tools so we can hear their button-clicks
1054 connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*)));
1055
1056 // now create tools for all existing canvases
1057 foreach(KoCanvasController *controller, d->canvasses.keys()) {
1058
1059 // this canvascontroller is unknown, which is weird
1060 if (!d->canvasses.contains(controller)) {
1061 continue;
1062 }
1063
1064 // create a tool for all canvasdata objects (i.e., all input devices on this canvas)
1065 foreach (CanvasData *cd, d->canvasses[controller]) {
1066 QPair<QString, KoToolBase*> toolPair = createTools(controller, tool);
1067 if (toolPair.second) {
1068 cd->allTools.insert(toolPair.first, toolPair.second);
1069 }
1070 }
1071
1072 // Then create a button for the toolbox for this canvas
1073 if (tool->id() == KoCreateShapesTool_ID) {
1074 continue;
1075 }
1076
1077 emit addedTool(tool->toolAction(), controller);
1078 }
1079 }
1080
createTools(KoCanvasController * controller,ToolHelper * tool)1081 QPair<QString, KoToolBase*> KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool)
1082 {
1083 // XXX: maybe this method should go into the private class?
1084
1085 QHash<QString, KoToolBase*> origHash;
1086
1087 if (d->canvasses.contains(controller)) {
1088 origHash = d->canvasses.value(controller).first()->allTools;
1089 }
1090
1091 if (origHash.contains(tool->id())) {
1092 return QPair<QString, KoToolBase*>(tool->id(), origHash.value(tool->id()));
1093 }
1094
1095 debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority();
1096
1097 KoToolBase *tl = tool->createTool(controller->canvas());
1098 if (tl) {
1099 d->uniqueToolIds.insert(tl, tool->uniqueId());
1100
1101 tl->setObjectName(tool->id());
1102
1103 foreach(QAction *action, tl->actions()) {
1104 action->setEnabled(false);
1105 }
1106
1107 }
1108
1109 KoZoomTool *zoomTool = dynamic_cast<KoZoomTool*>(tl);
1110 if (zoomTool) {
1111 zoomTool->setCanvasController(controller);
1112 }
1113
1114 KoPanTool *panTool = dynamic_cast<KoPanTool*>(tl);
1115 if (panTool) {
1116 panTool->setCanvasController(controller);
1117 }
1118
1119 return QPair<QString, KoToolBase*>(tool->id(), tl);
1120 }
1121
1122
instance()1123 KoToolManager* KoToolManager::instance()
1124 {
1125 return s_instance;
1126 }
1127
activeToolId() const1128 QString KoToolManager::activeToolId() const
1129 {
1130 if (!d->canvasData) return QString();
1131 return d->canvasData->activeToolId;
1132 }
1133
priv()1134 KoToolManager::Private *KoToolManager::priv()
1135 {
1136 return d;
1137 }
1138
1139 //have to include this because of Q_PRIVATE_SLOT
1140 #include "moc_KoToolManager.cpp"
1141