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