File indexing completed on 2024-05-12 15:56:52

0001 /* This file is part of the KDE project
0002  *
0003  * SPDX-FileCopyrightText: 2005-2010 Boudewijn Rempt <boud@valdyas.org>
0004  * SPDX-FileCopyrightText: 2006-2008 Thomas Zander <zander@kde.org>
0005  * SPDX-FileCopyrightText: 2006 Thorsten Zachmann <zachmann@kde.org>
0006  * SPDX-FileCopyrightText: 2008 Jan Hambrecht <jaham@gmx.net>
0007  *
0008  * SPDX-License-Identifier: LGPL-2.0-or-later
0009  */
0010 // flake
0011 #include "KoToolManager.h"
0012 #include "KoToolManager_p.h"
0013 #include "KoToolRegistry.h"
0014 #include "KoToolProxy.h"
0015 #include "KoToolProxy_p.h"
0016 #include "KoSelection.h"
0017 #include "KoCanvasController.h"
0018 #include "KoCanvasControllerWidget.h"
0019 #include "KoShape.h"
0020 #include "KoShapeLayer.h"
0021 #include "KoShapeRegistry.h"
0022 #include "KoShapeManager.h"
0023 #include "KoSelectedShapesProxy.h"
0024 #include "KoCanvasBase.h"
0025 #include "KoPointerEvent.h"
0026 #include "tools/KoZoomTool.h"
0027 #include "kis_action_registry.h"
0028 #include "KoToolFactoryBase.h"
0029 #include "kis_assert.h"
0030 
0031 #include <krita_container_utils.h>
0032 
0033 // Qt + kde
0034 #include <QWidget>
0035 #include <QEvent>
0036 #include <QWheelEvent>
0037 #include <QMouseEvent>
0038 #include <QPaintEvent>
0039 #include <QTabletEvent>
0040 #include <QVBoxLayout>
0041 #include <QStringList>
0042 #include <QApplication>
0043 #include <kactioncollection.h>
0044 #include <kactioncategory.h>
0045 #include <FlakeDebug.h>
0046 
0047 #include <QAction>
0048 #include <klocalizedstring.h>
0049 #include <QKeySequence>
0050 #include <QStack>
0051 #include <QLabel>
0052 #include <QGlobalStatic>
0053 
0054 Q_GLOBAL_STATIC(KoToolManager, s_instance)
0055 
0056 
0057 class CanvasData
0058 {
0059 public:
0060     CanvasData(KoCanvasController *cc, const KoInputDevice &id)
0061         : activeTool(0),
0062           canvas(cc),
0063           inputDevice(id),
0064           dummyToolWidget(0),
0065           dummyToolLabel(0)
0066     {
0067     }
0068 
0069     ~CanvasData()
0070     {
0071         // the dummy tool widget does not necessarily have a parent and we create it, so we delete it.
0072         delete dummyToolWidget;
0073     }
0074 
0075     void activateToolActions()
0076     {
0077         toolActions.clear();
0078         disabledGlobalActions.clear();
0079 
0080         KisKActionCollection *windowActionCollection = canvas->actionCollection();
0081 
0082         if (!windowActionCollection) {
0083             qWarning() << "We haven't got an action collection";
0084             return;
0085         }
0086 
0087         QStringList globalActions;
0088 
0089         QMap<QKeySequence, QStringList> shortcutMap;
0090 
0091 //        qDebug() << "................... activating tool" << activeToolId;
0092 
0093         Q_FOREACH(QAction *action, windowActionCollection->actions()) {
0094 
0095             if (action->property("tool_action").isValid()) {
0096                 QStringList tools = action->property("tool_action").toStringList();
0097 
0098                 if (KoToolRegistry::instance()->keys().contains(action->objectName())) {
0099                     //qDebug() << "This action needs to be enabled!";
0100                     action->setEnabled(true);
0101                     toolActions << action->objectName();
0102                 }
0103                 else {
0104                     if (tools.contains(activeToolId) || action->property("always_enabled").toBool()) {
0105                         //qDebug() << "\t\tenabling";
0106                         action->setEnabled(true);
0107                         toolActions << action->objectName();
0108                     }
0109                     else {
0110                         //qDebug() << "\t\tDISabling";
0111                         action->setDisabled(true);
0112                     }
0113                 }
0114             }
0115             else {
0116                 globalActions << action->objectName();
0117             }
0118 
0119             Q_FOREACH(QKeySequence keySequence, action->shortcuts()) {
0120                 // After loading a custom shortcut profile, shortcuts can be defined as an empty string, which is not an empty shortcut
0121                 if (keySequence.toString() != "") {
0122                     if (shortcutMap.contains(keySequence)) {
0123                         shortcutMap[keySequence].append(action->objectName());
0124                     }
0125                     else {
0126                         shortcutMap[keySequence] = QStringList() << action->objectName();
0127                     }
0128                 }
0129             }
0130         }
0131 
0132         // Make sure the tool's actions override the global actions that aren't associated with the tool.
0133         Q_FOREACH(const QKeySequence &k, shortcutMap.keys()) {
0134             if (shortcutMap[k].size() > 1) {
0135                 QStringList actions = shortcutMap[k];
0136                 //qDebug() << k << actions;
0137                 bool toolActionFound = false;
0138                 Q_FOREACH(const QString &action, actions) {
0139                     if (toolActions.contains(action)) {
0140                         toolActionFound = true;
0141                     }
0142                 }
0143                 Q_FOREACH(const QString &action, actions) {
0144                     if (toolActionFound && globalActions.contains(action)) {
0145                         //qDebug() << "\tdisabling global action" << action;
0146                         windowActionCollection->action(action)->setEnabled(false);
0147                         disabledGlobalActions << action;
0148                     }
0149                 }
0150                 //qDebug() << k << shortcutMap[k];
0151             }
0152         }
0153 
0154         windowActionCollection->readSettings(); // The shortcuts might have been configured in the meantime.
0155     }
0156 
0157     void deactivateToolActions()
0158     {
0159         if (!activeTool)
0160             return;
0161 
0162         //qDebug() << "............... deactivating previous tool because activating" << activeToolId;
0163 
0164         KisKActionCollection *windowActionCollection = canvas->actionCollection();
0165 
0166         Q_FOREACH(const QString &action, toolActions) {
0167             //qDebug() << "disabling" << action;
0168             windowActionCollection->action(action)->setDisabled(true);
0169         }
0170         Q_FOREACH(const QString &action, disabledGlobalActions) {
0171             //qDebug() << "enabling" << action;
0172             windowActionCollection->action(action)->setEnabled(true);
0173         }
0174     }
0175 
0176     KoToolBase *activeTool;     // active Tool
0177     QString activeToolId;   // the id of the active Tool
0178     QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to.
0179     QHash<QString, KoToolBase*> allTools; // all the tools that are created for this canvas.
0180     QList<KoToolBase*> mostRecentTools; // ordered unique list of tools starting from the most recently used, except for the active tool.
0181     KoCanvasController *const canvas;
0182     const KoInputDevice inputDevice;
0183     QWidget *dummyToolWidget;  // the widget shown in the toolDocker.
0184     QLabel *dummyToolLabel;
0185     QStringList toolActions;
0186     QStringList disabledGlobalActions;
0187 };
0188 
0189 
0190 // ******** KoToolManager **********
0191 KoToolManager::KoToolManager()
0192     : QObject(),
0193       d(new Private(this))
0194 {
0195     connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)),
0196             this, SLOT(movedFocus(QWidget*,QWidget*)));
0197 }
0198 
0199 KoToolManager::~KoToolManager()
0200 {
0201     delete d;
0202 }
0203 
0204 QList<KoToolAction*> KoToolManager::toolActionList() const
0205 {
0206     return d->toolActionList;
0207 }
0208 
0209 void KoToolManager::requestToolActivation(KoCanvasController * controller)
0210 {
0211     if (d->canvasses.contains(controller)) {
0212         d->switchTool(d->canvasses.value(controller).first()->activeToolId);
0213     }
0214 }
0215 
0216 KoInputDevice KoToolManager::currentInputDevice() const
0217 {
0218     return d->inputDevice;
0219 }
0220 
0221 void KoToolManager::registerToolActions(KisKActionCollection *ac, KoCanvasController *controller)
0222 {
0223     Q_ASSERT(controller);
0224     Q_ASSERT(ac);
0225 
0226     d->setup();
0227 
0228     if (!d->canvasses.contains(controller)) {
0229         return;
0230     }
0231 }
0232 
0233 void KoToolManager::addController(KoCanvasController *controller)
0234 {
0235     Q_ASSERT(controller);
0236     if (d->canvasses.contains(controller))
0237         return;
0238     d->setup();
0239     d->attachCanvas(controller);
0240     connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*)));
0241     connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
0242     connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
0243 }
0244 
0245 void KoToolManager::removeCanvasController(KoCanvasController *controller)
0246 {
0247     Q_ASSERT(controller);
0248     disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
0249     disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
0250     d->detachCanvas(controller);
0251 }
0252 
0253 void KoToolManager::attemptCanvasControllerRemoval(QObject* controller)
0254 {
0255     KoCanvasControllerProxyObject* controllerActual = qobject_cast<KoCanvasControllerProxyObject*>(controller);
0256     if (controllerActual) {
0257         removeCanvasController(controllerActual->canvasController());
0258     }
0259 }
0260 
0261 void KoToolManager::switchToolRequested(const QString & id)
0262 {
0263     d->switchTool(id);
0264 }
0265 
0266 void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id)
0267 {
0268     if (!d->canvasData) return;
0269     d->switchInputDevice(id);
0270 }
0271 
0272 void KoToolManager::switchBackRequested()
0273 {
0274     if (!d->canvasData) return;
0275     if (d->canvasData->mostRecentTools.isEmpty()) return;
0276     d->switchTool(d->canvasData->mostRecentTools.first()->toolId());
0277 }
0278 
0279 KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const
0280 {
0281     Q_ASSERT(canvas);
0282     Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
0283         if (controller->canvas() == canvas)
0284             return d->canvasData->allTools.value(id);
0285     }
0286     return 0;
0287 }
0288 
0289 KoCanvasController *KoToolManager::activeCanvasController() const
0290 {
0291     if (! d->canvasData) return 0;
0292     return d->canvasData->canvas;
0293 }
0294 
0295 QString KoToolManager::preferredToolForSelection(const QList<KoShape*> &shapes)
0296 {
0297     QSet<QString> shapeTypes;
0298     Q_FOREACH (KoShape *shape, shapes) {
0299         shapeTypes << shape->shapeId();
0300     }
0301     //KritaUtils::makeContainerUnique(types);
0302 
0303     QString toolType = KoInteractionTool_ID;
0304     int prio = INT_MAX;
0305     Q_FOREACH (KoToolAction *helper, d->toolActionList) {
0306         if (helper->priority() >= prio)
0307             continue;
0308 
0309         bool toolWillWork = false;
0310         foreach (const QString &type, shapeTypes) {
0311             if (helper->toolFactory()->activationShapeId().split(',').contains(type)) {
0312                 toolWillWork = true;
0313                 break;
0314             }
0315         }
0316 
0317         if (toolWillWork) {
0318             toolType = helper->id();
0319             prio = helper->priority();
0320         }
0321     }
0322     return toolType;
0323 }
0324 
0325 void KoToolManager::initializeCurrentToolForCanvas()
0326 {
0327     KIS_ASSERT_RECOVER_RETURN(d->canvasData);
0328 
0329     // make a full reconnect cycle for the currently active tool
0330     d->disconnectActiveTool();
0331     d->connectActiveTool();
0332     d->postSwitchTool();
0333 }
0334 
0335 void KoToolManager::themeChanged()
0336 {
0337     for (const QList<CanvasData*> &canvasDataList : d->canvasses) {
0338         for (CanvasData *canvasData : canvasDataList) {
0339             for (KoToolBase *tool : canvasData->allTools) {
0340                 tool->updateOptionsWidgetIcons();
0341             }
0342         }
0343     }
0344 }
0345 
0346 KoToolManager* KoToolManager::instance()
0347 {
0348     return s_instance;
0349 }
0350 
0351 QString KoToolManager::activeToolId() const
0352 {
0353     if (!d->canvasData) return QString();
0354     return d->canvasData->activeToolId;
0355 }
0356 
0357 
0358 KoToolManager::Private *KoToolManager::priv()
0359 {
0360     return d;
0361 }
0362 
0363 
0364 /**** KoToolManager::Private ****/
0365 
0366 KoToolManager::Private::Private(KoToolManager *qq)
0367     : q(qq),
0368       canvasData(0),
0369       layerExplicitlyDisabled(false)
0370 {
0371 }
0372 
0373 KoToolManager::Private::~Private()
0374 {
0375     qDeleteAll(toolActionList);
0376 }
0377 
0378 // helper method.
0379 CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device)
0380 {
0381     QHash<QString, KoToolBase*> toolsHash;
0382     Q_FOREACH (KoToolAction *toolAction, toolActionList) {
0383         KoToolBase* tool = createTool(controller, toolAction);
0384         if (tool) { // only if a real tool was created
0385             toolsHash.insert(tool->toolId(), tool);
0386         }
0387     }
0388 
0389     CanvasData *cd = new CanvasData(controller, device);
0390     cd->allTools = toolsHash;
0391     return cd;
0392 }
0393 
0394 KoToolBase *KoToolManager::Private::createTool(KoCanvasController *controller, KoToolAction *toolAction)
0395 {
0396     QHash<QString, KoToolBase*> origHash;
0397 
0398     if (canvasses.contains(controller)) {
0399         origHash = canvasses.value(controller).first()->allTools;
0400     }
0401 
0402     if (origHash.contains(toolAction->id())) {
0403         return origHash.value(toolAction->id());
0404     }
0405 
0406     debugFlake << "Creating tool" << toolAction->id() << ". Activated on:" << toolAction->visibilityCode() << ", prio:" << toolAction->priority();
0407 
0408     KoToolBase *tool = toolAction->toolFactory()->createTool(controller->canvas());
0409     if (tool) {
0410         tool->setFactory(toolAction->toolFactory());
0411         tool->setObjectName(toolAction->id());
0412     }
0413 
0414     KoZoomTool *zoomTool = dynamic_cast<KoZoomTool*>(tool);
0415     if (zoomTool) {
0416         zoomTool->setCanvasController(controller);
0417     }
0418 
0419     return tool;
0420 }
0421 
0422 void KoToolManager::Private::setup()
0423 {
0424     if (toolActionList.size() > 0)
0425         return;
0426 
0427     KoShapeRegistry::instance();
0428     KoToolRegistry *registry = KoToolRegistry::instance();
0429     Q_FOREACH (const QString & id, registry->keys()) {
0430         toolActionList.append(new KoToolAction(registry->value(id)));
0431     }
0432 }
0433 
0434 void KoToolManager::Private::connectActiveTool()
0435 {
0436     if (canvasData->activeTool) {
0437         connect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)),
0438                 q, SLOT(updateCursor(QCursor)));
0439         connect(canvasData->activeTool, SIGNAL(activateTool(QString)),
0440                 q, SLOT(switchToolRequested(QString)));
0441         connect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)),
0442                 q, SIGNAL(changedStatusText(QString)));
0443     }
0444 
0445     // we expect the tool to emit a cursor on activation.
0446     updateCursor(Qt::ForbiddenCursor);
0447 }
0448 
0449 
0450 
0451 void KoToolManager::Private::disconnectActiveTool()
0452 {
0453     if (canvasData->activeTool) {
0454         canvasData->deactivateToolActions();
0455         // repaint the decorations before we deactivate the tool as it might deleted
0456         // data needed for the repaint
0457         emit q->aboutToChangeTool(canvasData->canvas);
0458         canvasData->activeTool->deactivate();
0459         disconnect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)),
0460                    q, SLOT(updateCursor(QCursor)));
0461         disconnect(canvasData->activeTool, SIGNAL(activateTool(QString)),
0462                    q, SLOT(switchToolRequested(QString)));
0463         disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)),
0464                    q, SIGNAL(changedStatusText(QString)));
0465     }
0466 
0467     // emit a empty status text to clear status text from last active tool
0468     emit q->changedStatusText(QString());
0469 }
0470 
0471 void KoToolManager::Private::switchTool(const QString &id)
0472 {
0473     if (!canvasData) return;
0474 
0475     canvasData->activeToolId = id;
0476     KoToolBase *tool = canvasData->allTools.value(id);
0477     if (! tool) {
0478         return;
0479     }
0480 
0481     canvasData->activationShapeId = tool->factory()->activationShapeId();
0482 
0483     if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID)
0484         return;
0485 
0486     disconnectActiveTool();
0487     if (canvasData->activeTool) {
0488         canvasData->mostRecentTools.prepend(canvasData->activeTool);
0489     }
0490     canvasData->activeTool = tool;
0491     canvasData->mostRecentTools.removeOne(tool);
0492     connectActiveTool();
0493     postSwitchTool();
0494 }
0495 
0496 void KoToolManager::Private::postSwitchTool()
0497 {
0498 #ifndef NDEBUG
0499     int canvasCount = 1;
0500     Q_FOREACH (QList<CanvasData*> list, canvasses) {
0501         bool first = true;
0502         Q_FOREACH (CanvasData *data, list) {
0503             if (first) {
0504                 debugFlake << "Canvas" << canvasCount++;
0505             }
0506             debugFlake << "  +- Tool:" << data->activeToolId  << (data == canvasData ? " *" : "");
0507             first = false;
0508         }
0509     }
0510 #endif
0511     Q_ASSERT(canvasData);
0512     if (!canvasData) return;
0513 
0514     QSet<KoShape*> shapesToOperateOn;
0515     if (canvasData->activeTool
0516             && canvasData->activeTool->canvas()
0517             && canvasData->activeTool->canvas()->shapeManager()) {
0518         KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection();
0519         Q_ASSERT(selection);
0520 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0521         QList<KoShape *> shapesDelegatesList = selection->selectedEditableShapesAndDelegates();
0522         if (!shapesDelegatesList.isEmpty()) {
0523             shapesToOperateOn = QSet<KoShape*>(shapesDelegatesList.begin(),
0524                                                shapesDelegatesList.end());
0525         }
0526 #else
0527         shapesToOperateOn = QSet<KoShape*>::fromList(selection->selectedEditableShapesAndDelegates());
0528 #endif
0529     }
0530 
0531     if (canvasData->canvas->canvas()) {
0532         // Caller of postSwitchTool expect this to be called to update the selected tool
0533         updateToolForProxy();
0534         canvasData->activeTool->activate(shapesToOperateOn);
0535         KoCanvasBase *canvas = canvasData->canvas->canvas();
0536         canvas->updateInputMethodInfo();
0537     } else {
0538         canvasData->activeTool->activate(shapesToOperateOn);
0539     }
0540 
0541     QList<QPointer<QWidget> > optionWidgetList = canvasData->activeTool->optionWidgets();
0542     if (optionWidgetList.empty()) { // no option widget.
0543         QWidget *toolWidget;
0544         QString title = canvasData->activeTool->factory()->toolTip();
0545         toolWidget = canvasData->dummyToolWidget;
0546         if (toolWidget == 0) {
0547             toolWidget = new QWidget();
0548             toolWidget->setObjectName("DummyToolWidget");
0549             QVBoxLayout *layout = new QVBoxLayout(toolWidget);
0550             layout->setMargin(3);
0551             canvasData->dummyToolLabel = new QLabel(toolWidget);
0552             layout->addWidget(canvasData->dummyToolLabel);
0553             layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
0554             canvasData->dummyToolWidget = toolWidget;
0555         }
0556         canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title));
0557         optionWidgetList.append(toolWidget);
0558     }
0559 
0560     // Activate the actions for the currently active tool
0561     canvasData->activateToolActions();
0562 
0563     emit q->changedTool(canvasData->canvas);
0564 
0565     emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList);
0566 }
0567 
0568 
0569 void KoToolManager::Private::switchCanvasData(CanvasData *cd)
0570 {
0571     Q_ASSERT(cd);
0572 
0573     KoCanvasBase *oldCanvas = 0;
0574     KoInputDevice oldInputDevice;
0575 
0576     if (canvasData) {
0577         oldCanvas = canvasData->canvas->canvas();
0578         oldInputDevice = canvasData->inputDevice;
0579 
0580         if (canvasData->activeTool) {
0581             disconnectActiveTool();
0582         }
0583 
0584         KoToolProxy *proxy = proxies.value(oldCanvas);
0585         Q_ASSERT(proxy);
0586         proxy->setActiveTool(0);
0587     }
0588 
0589     canvasData = cd;
0590     inputDevice = canvasData->inputDevice;
0591 
0592     if (canvasData->activeTool) {
0593         connectActiveTool();
0594         postSwitchTool();
0595     }
0596 
0597     if (oldInputDevice != canvasData->inputDevice) {
0598         emit q->inputDeviceChanged(canvasData->inputDevice);
0599     }
0600 
0601     if (oldCanvas != canvasData->canvas->canvas()) {
0602         emit q->changedCanvas(canvasData->canvas->canvas());
0603     }
0604 }
0605 
0606 void KoToolManager::Private::detachCanvas(KoCanvasController *controller)
0607 {
0608     Q_ASSERT(controller);
0609     // check if we are removing the active canvas controller
0610     if (canvasData && canvasData->canvas == controller) {
0611         KoCanvasController *newCanvas = 0;
0612         // try to find another canvas controller beside the one we are removing
0613         Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
0614             if (canvas != controller) {
0615                 // yay found one
0616                 newCanvas = canvas;
0617                 break;
0618             }
0619         }
0620         if (newCanvas) {
0621             switchCanvasData(canvasses.value(newCanvas).first());
0622         } else {
0623             disconnectActiveTool();
0624             emit q->toolOptionWidgetsChanged(controller, QList<QPointer<QWidget> >());
0625             // as a last resort just set a blank one
0626             canvasData = 0;
0627         }
0628     }
0629 
0630     KoToolProxy *proxy = proxies.value(controller->canvas());
0631     if (proxy)
0632         proxy->setActiveTool(0);
0633 
0634     QList<KoToolBase *> tools;
0635     Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) {
0636         Q_FOREACH (KoToolBase *tool, canvasData->allTools) {
0637             if (! tools.contains(tool)) {
0638                 tools.append(tool);
0639             }
0640         }
0641         delete canvasData;
0642     }
0643     Q_FOREACH (KoToolBase *tool, tools) {
0644         delete tool;
0645     }
0646     canvasses.remove(controller);
0647     emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
0648 }
0649 
0650 void KoToolManager::Private::attachCanvas(KoCanvasController *controller)
0651 {
0652     Q_ASSERT(controller);
0653     CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse());
0654 
0655     // switch to new canvas as the active one.
0656     switchCanvasData(cd);
0657 
0658     inputDevice = cd->inputDevice;
0659     QList<CanvasData*> canvasses_;
0660     canvasses_.append(cd);
0661     canvasses[controller] = canvasses_;
0662 
0663     KoToolProxy *tp = proxies[controller->canvas()];
0664     if (tp)
0665         tp->priv()->setCanvasController(controller);
0666 
0667     if (cd->activeTool == 0) {
0668         // no active tool, so we activate the highest priority main tool
0669         int highestPriority = INT_MAX;
0670         KoToolAction * helper = 0;
0671         Q_FOREACH (KoToolAction * th, toolActionList) {
0672             if (th->section() == ToolBoxSection::Main) {
0673                 if (th->priority() < highestPriority) {
0674                     highestPriority = qMin(highestPriority, th->priority());
0675                     helper = th;
0676                 }
0677             }
0678         }
0679         if (helper)
0680             switchTool(helper->id());
0681     }
0682 
0683     Connector *connector = new Connector(controller->canvas()->shapeManager());
0684     connect(connector, SIGNAL(selectionChanged(QList<KoShape*>)), q,
0685             SLOT(selectionChanged(QList<KoShape*>)));
0686     connect(controller->canvas()->selectedShapesProxy(),
0687             SIGNAL(currentLayerChanged(const KoShapeLayer*)),
0688             q, SLOT(currentLayerChanged(const KoShapeLayer*)));
0689 
0690     emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
0691 }
0692 
0693 void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to)
0694 {
0695     Q_UNUSED(from);
0696     // no canvas anyway or no focus set anyway?
0697     if (!canvasData || to == 0) {
0698         return;
0699     }
0700 
0701     // Check if this app is about QWidget-based KoCanvasControllerWidget canvasses
0702     // XXX: Focus handling for non-qwidget based canvases!
0703     KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
0704     if (!canvasControllerWidget) {
0705         return;
0706     }
0707 
0708     // canvasWidget is set as focusproxy for KoCanvasControllerWidget,
0709     // so all focus checks are to be done against canvasWidget objects
0710 
0711     // focus returned to current canvas?
0712     if (to == canvasData->canvas->canvas()->canvasWidget()) {
0713         // nothing to do
0714         return;
0715     }
0716 
0717     // if the 'to' is one of our canvasWidgets, then switch.
0718 
0719     // for code simplicity the current canvas will be checked again,
0720     // but would have been caught already in the lines above, so no issue
0721     KoCanvasController *newCanvas = 0;
0722     Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
0723         if (canvas->canvas()->canvasWidget() == to) {
0724             newCanvas = canvas;
0725             break;
0726         }
0727     }
0728 
0729     // none of our canvasWidgets got focus?
0730     if (newCanvas == 0) {
0731         return;
0732     }
0733 
0734     // switch to canvasdata matching inputdevice used last with this app instance
0735     Q_FOREACH (CanvasData *data, canvasses.value(newCanvas)) {
0736         if (data->inputDevice == inputDevice) {
0737             switchCanvasData(data);
0738             return;
0739         }
0740     }
0741     // if no such inputDevice for this canvas, then simply fallback to first one
0742     switchCanvasData(canvasses.value(newCanvas).first());
0743 }
0744 
0745 void KoToolManager::Private::updateCursor(const QCursor &cursor)
0746 {
0747     Q_ASSERT(canvasData);
0748     Q_ASSERT(canvasData->canvas);
0749     Q_ASSERT(canvasData->canvas->canvas());
0750     canvasData->canvas->canvas()->setCursor(cursor);
0751 }
0752 
0753 void KoToolManager::Private::selectionChanged(const QList<KoShape*> &shapes)
0754 {
0755     QList<QString> types;
0756     Q_FOREACH (KoShape *shape, shapes) {
0757         QSet<KoShape*> delegates = shape->toolDelegates();
0758         if (delegates.isEmpty()) { // no delegates, just the orig shape
0759             delegates << shape;
0760         }
0761 
0762         foreach (KoShape *shape2, delegates) {
0763             Q_ASSERT(shape2);
0764             if (! types.contains(shape2->shapeId())) {
0765                 types.append(shape2->shapeId());
0766             }
0767         }
0768     }
0769 
0770     // check if there is still a shape selected the active tool can work on
0771     // there needs to be at least one shape for a tool without an activationShapeId
0772     // to work
0773     // if not change the current tool to the default tool
0774 
0775     const QStringList activationShapeIds = canvasData->activationShapeId.split(',');
0776 
0777     if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0)
0778             && !activationShapeIds.contains("flake/always")
0779             && !activationShapeIds.contains("flake/edit")) {
0780 
0781         bool currentToolWorks = false;
0782         foreach (const QString &type, types) {
0783             if (activationShapeIds.contains(type)) {
0784                 currentToolWorks = true;
0785                 break;
0786             }
0787         }
0788         if (!currentToolWorks) {
0789             switchTool(KoInteractionTool_ID);
0790         }
0791     }
0792 
0793     emit q->toolCodesSelected(types);
0794 }
0795 
0796 void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer)
0797 {
0798     emit q->currentLayerChanged(canvasData->canvas, layer);
0799     layerExplicitlyDisabled = layer && !layer->isShapeEditable();
0800     updateToolForProxy();
0801 
0802     debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled;
0803 }
0804 
0805 void KoToolManager::Private::updateToolForProxy()
0806 {
0807     KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas());
0808     if(!proxy) return;
0809 
0810     bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always"));
0811     proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0);
0812 }
0813 
0814 void KoToolManager::Private::switchInputDevice(const KoInputDevice &device)
0815 {
0816     Q_ASSERT(canvasData);
0817     if (!canvasData) return;
0818     if (inputDevice == device) return;
0819     if (inputDevice.isMouse() && device.isMouse()) return;
0820     if (device.isMouse() && !inputDevice.isMouse()) {
0821         // we never switch back to mouse from a tablet input device, so the user can use the
0822         // mouse to edit the settings for a tool activated by a tablet. See bugs
0823         // https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501.
0824         // We do continue to switch between tablet devices, thought.
0825         return;
0826     }
0827 
0828     QList<CanvasData*> items = canvasses[canvasData->canvas];
0829 
0830     // search for a canvasdata object for the current input device
0831     Q_FOREACH (CanvasData *cd, items) {
0832         if (cd->inputDevice == device) {
0833             switchCanvasData(cd);
0834 
0835             if (!canvasData->activeTool) {
0836                 switchTool(KoInteractionTool_ID);
0837             }
0838 
0839             return;
0840         }
0841     }
0842 
0843     // still here?  That means we need to create a new CanvasData instance with the current InputDevice.
0844     CanvasData *cd = createCanvasData(canvasData->canvas, device);
0845     // switch to new canvas as the active one.
0846     QString oldTool = canvasData->activeToolId;
0847 
0848     items.append(cd);
0849     canvasses[cd->canvas] = items;
0850 
0851     switchCanvasData(cd);
0852 
0853     switchTool(oldTool);
0854 }
0855 
0856 void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas)
0857 {
0858     proxies.insert(canvas, proxy);
0859     Q_FOREACH (KoCanvasController *controller, canvasses.keys()) {
0860         if (controller->canvas() == canvas) {
0861             proxy->priv()->setCanvasController(controller);
0862             break;
0863         }
0864     }
0865 }
0866 
0867 //have to include this because of Q_PRIVATE_SLOT
0868 #include "moc_KoToolManager.cpp"