diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp index 61e194c30f..16f817f548 100644 --- a/libs/flake/KoToolManager.cpp +++ b/libs/flake/KoToolManager.cpp @@ -1,1056 +1,1061 @@ /* This file is part of the KDE project * * Copyright (c) 2005-2010 Boudewijn Rempt * Copyright (C) 2006-2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // flake #include "KoToolManager.h" #include "KoToolManager_p.h" #include "KoToolRegistry.h" #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include "KoSelection.h" #include "KoCanvasController.h" #include "KoCanvasControllerWidget.h" #include "KoShape.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeManager.h" #include "KoCanvasBase.h" #include "KoInputDeviceHandlerRegistry.h" #include "KoInputDeviceHandlerEvent.h" #include "KoPointerEvent.h" #include "tools/KoCreateShapesTool.h" #include "tools/KoZoomTool.h" #include "tools/KoPanTool.h" // Qt + kde #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class CanvasData { public: CanvasData(KoCanvasController *cc, const KoInputDevice &id) : activeTool(0), canvas(cc), inputDevice(id), dummyToolWidget(0), dummyToolLabel(0) { } ~CanvasData() { // the dummy tool widget does not necessarily have a parent and we create it, so we delete it. delete dummyToolWidget; } void activateToolActions() { disabledDisabledActions.clear(); disabledActions.clear(); disabledCanvasShortcuts.clear(); // we do several things here // 1. enable the actions of the active tool // 2. disable conflicting actions // 3. replace conflicting actions in the action collection KActionCollection *canvasActionCollection = canvas->actionCollection(); QHash toolActions = activeTool->actions(); QHash::const_iterator it(toolActions.constBegin()); for (; it != toolActions.constEnd(); ++it) { if (canvasActionCollection) { QString toolActionID = it.key(); KAction *toolAction = it.value(); KAction* action = qobject_cast(canvasActionCollection->action(it.key())); if (action) { canvasActionCollection->takeAction(action); if (action != it.value()) { if (action->isEnabled()) { action->setEnabled(false); disabledActions.append(action); } else { disabledDisabledActions.append(action); } } } foreach(QAction *a, canvasActionCollection->actions()) { KAction *canvasAction = dynamic_cast(a); if (canvasAction && canvasAction->shortcut().toString() != "" && canvasAction->shortcut() == toolAction->shortcut()) { kWarning() << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString(); disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString(); canvasAction->setShortcut(QKeySequence()); } } canvasActionCollection->addAction(toolActionID, toolAction); } it.value()->setEnabled(true); } canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime. } void deactivateToolActions() { if (!activeTool) return; // disable actions of active tool foreach(KAction *action, activeTool->actions()) { action->setEnabled(false); } // enable actions which where disabled on activating the active tool // and re-add them to the action collection KActionCollection *ac = canvas->actionCollection(); foreach(QPointer action, disabledDisabledActions) { if (action) { if (ac) { ac->addAction(action->objectName(), action); } } } disabledDisabledActions.clear(); foreach(QPointer action, disabledActions) { if (action) { action->setEnabled(true); if(ac) { ac->addAction(action->objectName(), action); } } } disabledActions.clear(); QMap, QString>::const_iterator it(disabledCanvasShortcuts.constBegin()); for (; it != disabledCanvasShortcuts.constEnd(); ++it) { KAction *action = it.key(); QString shortcut = it.value(); action->setShortcut(shortcut); } disabledCanvasShortcuts.clear(); } KoToolBase *activeTool; // active Tool QString activeToolId; // the id of the active Tool QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to. QHash allTools; // all the tools that are created for this canvas. QStack stack; // stack of temporary tools KoCanvasController *const canvas; const KoInputDevice inputDevice; QWidget *dummyToolWidget; // the widget shown in the toolDocker. QLabel *dummyToolLabel; QList > disabledActions; ///< disabled conflicting actions QList > disabledDisabledActions; ///< disabled conflicting actions that were already disabled QMap, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides }; KoToolManager::Private::Private(KoToolManager *qq) : q(qq), canvasData(0), layerExplicitlyDisabled(false) { } KoToolManager::Private::~Private() { qDeleteAll(tools); } // helper method. CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device) { QHash toolsHash; foreach(ToolHelper *tool, tools) { QPair toolPair = q->createTools(controller, tool); if (toolPair.second) { // only if a real tool was created toolsHash.insert(toolPair.first, toolPair.second); } } KoCreateShapesTool *createShapesTool = dynamic_cast(toolsHash.value(KoCreateShapesTool_ID)); Q_ASSERT(createShapesTool); QString id = KoShapeRegistry::instance()->keys()[0]; createShapesTool->setShapeId(id); CanvasData *cd = new CanvasData(controller, device); cd->allTools = toolsHash; return cd; } void KoToolManager::Private::setup() { if (tools.size() > 0) return; KoShapeRegistry::instance(); KoToolRegistry *registry = KoToolRegistry::instance(); foreach(const QString & id, registry->keys()) { ToolHelper *t = new ToolHelper(registry->value(id)); tools.append(t); } // connect to all tools so we can hear their button-clicks foreach(ToolHelper *tool, tools) connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*))); // load pluggable input devices KoInputDeviceHandlerRegistry::instance(); } void KoToolManager::Private::connectActiveTool() { if (canvasData->activeTool) { connect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor &)), q, SLOT(updateCursor(const QCursor &))); connect(canvasData->activeTool, SIGNAL(activateTool(const QString &)), q, SLOT(switchToolRequested(const QString &))); connect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)), q, SLOT(switchToolTemporaryRequested(const QString &))); connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); connect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)), q, SIGNAL(changedStatusText(const QString &))); } // we expect the tool to emit a cursor on activation. updateCursor(Qt::ForbiddenCursor); } void KoToolManager::Private::disconnectActiveTool() { if (canvasData->activeTool) { canvasData->deactivateToolActions(); // repaint the decorations before we deactivate the tool as it might deleted // data needed for the repaint canvasData->activeTool->deactivate(); disconnect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor&)), q, SLOT(updateCursor(const QCursor&))); disconnect(canvasData->activeTool, SIGNAL(activateTool(const QString &)), q, SLOT(switchToolRequested(const QString &))); disconnect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)), q, SLOT(switchToolTemporaryRequested(const QString &))); disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)), q, SIGNAL(changedStatusText(const QString &))); } // emit a empty status text to clear status text from last active tool emit q->changedStatusText(QString()); } void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary) { Q_ASSERT(tool); if (canvasData == 0) return; if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID) return; disconnectActiveTool(); canvasData->activeTool = tool; connectActiveTool(); postSwitchTool(temporary); } void KoToolManager::Private::switchTool(const QString &id, bool temporary) { Q_ASSERT(canvasData); if (!canvasData) return; if (canvasData->activeTool && temporary) canvasData->stack.push(canvasData->activeToolId); canvasData->activeToolId = id; KoToolBase *tool = canvasData->allTools.value(id); if (! tool) { return; } foreach(ToolHelper *th, tools) { if (th->id() == id) { canvasData->activationShapeId = th->activationShapeId(); break; } } switchTool(tool, temporary); } void KoToolManager::Private::postSwitchTool(bool temporary) { #ifndef NDEBUG int canvasCount = 1; foreach(QList list, canvasses) { bool first = true; foreach(CanvasData *data, list) { if (first) { kDebug(30006) << "Canvas" << canvasCount++; } kDebug(30006) << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : ""); first = false; } } #endif Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase::ToolActivation toolActivation; if (temporary) toolActivation = KoToolBase::TemporaryActivation; else toolActivation = KoToolBase::DefaultActivation; QSet shapesToOperateOn; if (canvasData->activeTool && canvasData->activeTool->canvas() && canvasData->activeTool->canvas()->shapeManager()) { KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection(); Q_ASSERT(selection); foreach(KoShape *shape, selection->selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { // no delegates, just the orig shape shapesToOperateOn << shape; } else { shapesToOperateOn += delegates; } } } if (canvasData->canvas->canvas()) { // Caller of postSwitchTool expect this to be called to update the selected tool updateToolForProxy(); canvasData->activeTool->activate(toolActivation, shapesToOperateOn); KoCanvasBase *canvas = canvasData->canvas->canvas(); canvas->updateInputMethodInfo(); } else { canvasData->activeTool->activate(toolActivation, shapesToOperateOn); } QList > optionWidgetList = canvasData->activeTool->optionWidgets(); if (optionWidgetList.empty()) { // no option widget. QWidget *toolWidget; QString title; foreach(ToolHelper *tool, tools) { if (tool->id() == canvasData->activeTool->toolId()) { title = tool->toolTip(); break; } } toolWidget = canvasData->dummyToolWidget; if (toolWidget == 0) { toolWidget = new QWidget(); toolWidget->setObjectName("DummyToolWidget"); QVBoxLayout *layout = new QVBoxLayout(toolWidget); layout->setMargin(3); canvasData->dummyToolLabel = new QLabel(toolWidget); layout->addWidget(canvasData->dummyToolLabel); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding)); toolWidget->setLayout(layout); canvasData->dummyToolWidget = toolWidget; } canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title)); optionWidgetList.append(toolWidget); } // Activate the actions for the currently active tool canvasData->activateToolActions(); emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool)); KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (canvasControllerWidget) { canvasControllerWidget->setToolOptionWidgets(optionWidgetList); } } void KoToolManager::Private::switchCanvasData(CanvasData *cd) { Q_ASSERT(cd); KoCanvasBase *oldCanvas = 0; KoInputDevice oldInputDevice; if (canvasData) { oldCanvas = canvasData->canvas->canvas(); oldInputDevice = canvasData->inputDevice; if (canvasData->activeTool) { disconnectActiveTool(); } KoToolProxy *proxy = proxies.value(oldCanvas); Q_ASSERT(proxy); proxy->setActiveTool(0); } canvasData = cd; inputDevice = canvasData->inputDevice; if (canvasData->activeTool) { connectActiveTool(); postSwitchTool(false); } if (oldInputDevice != canvasData->inputDevice) { emit q->inputDeviceChanged(canvasData->inputDevice); } if (oldCanvas != canvasData->canvas->canvas()) { emit q->changedCanvas(canvasData->canvas->canvas()); } } void KoToolManager::Private::toolActivated(ToolHelper *tool) { Q_ASSERT(tool); Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase *t = canvasData->allTools.value(tool->id()); Q_ASSERT(t); canvasData->activeToolId = tool->id(); canvasData->activationShapeId = tool->activationShapeId(); switchTool(t, false); } void KoToolManager::Private::detachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); // check if we are removing the active canvas controller if (canvasData && canvasData->canvas == controller) { KoCanvasController *newCanvas = 0; // try to find another canvas controller beside the one we are removing foreach(KoCanvasController* canvas, canvasses.keys()) { if (canvas != controller) { // yay found one newCanvas = canvas; break; } } if (newCanvas) { switchCanvasData(canvasses.value(newCanvas).first()); } else { KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (canvasControllerWidget) { canvasControllerWidget->setToolOptionWidgets(QList >()); } // as a last resort just set a blank one canvasData = 0; } } KoToolProxy *proxy = proxies.value(controller->canvas()); if (proxy) proxy->setActiveTool(0); QList tools; foreach(CanvasData *canvasData, canvasses.value(controller)) { foreach(KoToolBase *tool, canvasData->allTools) { if (! tools.contains(tool)) { tools.append(tool); } } delete canvasData; } foreach(KoToolBase *tool, tools) { uniqueToolIds.remove(tool); delete tool; } canvasses.remove(controller); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::attachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse()); // switch to new canvas as the active one. switchCanvasData(cd); inputDevice = cd->inputDevice; QList canvasses_; canvasses_.append(cd); canvasses[controller] = canvasses_; KoToolProxy *tp = proxies[controller->canvas()]; if (tp) tp->priv()->setCanvasController(controller); if (cd->activeTool == 0) { // no active tool, so we activate the highest priority main tool int highestPriority = INT_MAX; ToolHelper * helper = 0; foreach(ToolHelper * th, tools) { if (th->toolType() == KoToolFactoryBase::mainToolType()) { if (th->priority() < highestPriority) { highestPriority = qMin(highestPriority, th->priority()); helper = th; } } } if (helper) toolActivated(helper); } Connector *connector = new Connector(controller->canvas()->shapeManager()); connect(connector, SIGNAL(selectionChanged(QList)), q, SLOT(selectionChanged(QList))); connect(controller->canvas()->shapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), q, SLOT(currentLayerChanged(const KoShapeLayer*))); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to) { Q_UNUSED(from); // XXX: Focus handling for non-qwidget based canvases! if (!canvasData) { return; } KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (!canvasControllerWidget) { return; } if (to == 0 || to == canvasControllerWidget) { return; } KoCanvasController *newCanvas = 0; // if the 'to' is one of our canvasses, or one of its children, then switch. foreach(KoCanvasController* canvas, canvasses.keys()) { if (canvasControllerWidget == to || canvas->canvas()->canvasWidget() == to) { newCanvas = canvas; break; } } if (newCanvas == 0) { return; } if (canvasData && newCanvas == canvasData->canvas) { return; } if (!canvasses.contains(newCanvas)) { return; } foreach(CanvasData *data, canvasses.value(newCanvas)) { if (data->inputDevice == inputDevice) { switchCanvasData(data); return; } } // no such inputDevice for this canvas... switchCanvasData(canvasses.value(newCanvas).first()); } void KoToolManager::Private::updateCursor(const QCursor &cursor) { Q_ASSERT(canvasData); Q_ASSERT(canvasData->canvas); Q_ASSERT(canvasData->canvas->canvas()); canvasData->canvas->canvas()->setCursor(cursor); } void KoToolManager::Private::selectionChanged(const QList &shapes) { QList types; foreach(KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { // no delegates, just the orig shape delegates << shape; } foreach (KoShape *shape2, delegates) { Q_ASSERT(shape2); if (! types.contains(shape2->shapeId())) { types.append(shape2->shapeId()); } } } // check if there is still a shape selected the active tool can work on // there needs to be at least one shape for a tool without an activationShapeId // to work // if not change the current tool to the default tool if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0) && canvasData->activationShapeId != "flake/always" && canvasData->activationShapeId != "flake/edit") { bool currentToolWorks = false; foreach (const QString &type, types) { if (canvasData->activationShapeId.split(',').contains(type)) { currentToolWorks = true; break; } } if (!currentToolWorks) { switchTool(KoInteractionTool_ID, false); } } emit q->toolCodesSelected(types); } void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer) { emit q->currentLayerChanged(canvasData->canvas, layer); layerExplicitlyDisabled = layer && !layer->isEditable(); updateToolForProxy(); kDebug(30006) << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled; } void KoToolManager::Private::updateToolForProxy() { KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas()); if(!proxy) return; bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always")); proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0); } void KoToolManager::Private::switchInputDevice(const KoInputDevice &device) { Q_ASSERT(canvasData); if (!canvasData) return; if (inputDevice == device) return; if (inputDevice.isMouse() && device.isMouse()) return; if (device.isMouse() && !inputDevice.isMouse()) { // we never switch back to mouse from a tablet input device, so the user can use the // mouse to edit the settings for a tool activated by a tablet. See bugs // https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501. // We do continue to switch between tablet devices, thought. return; } QList items = canvasses[canvasData->canvas]; // disable all actions for all tools in the all canvasdata objects for this canvas. foreach(CanvasData *cd, items) { foreach(KoToolBase* tool, cd->allTools) { foreach(KAction* action, tool->actions()) { action->setEnabled(false); } } } // search for a canvasdata object for the current input device foreach(CanvasData *cd, items) { if (cd->inputDevice == device) { switchCanvasData(cd); if (!canvasData->activeTool) { switchTool(KoInteractionTool_ID, false); } return; } } // still here? That means we need to create a new CanvasData instance with the current InputDevice. CanvasData *cd = createCanvasData(canvasData->canvas, device); // switch to new canvas as the active one. QString oldTool = canvasData->activeToolId; items.append(cd); canvasses[cd->canvas] = items; switchCanvasData(cd); q->switchToolRequested(oldTool); } void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas) { proxies.insert(canvas, proxy); foreach(KoCanvasController *controller, canvasses.keys()) { if (controller->canvas() == canvas) { proxy->priv()->setCanvasController(controller); break; } } } void KoToolManager::Private::switchToolByShortcut(QKeyEvent *event) { QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (event->key() == Qt::Key_Space && event->modifiers() == 0) { switchTool(KoPanTool_ID, true); } else if (event->key() == Qt::Key_Escape && event->modifiers() == 0) { switchTool(KoInteractionTool_ID, false); } } // ******** KoToolManager ********** KoToolManager::KoToolManager() : QObject(), d(new Private(this)) { connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(movedFocus(QWidget*, QWidget*))); } KoToolManager::~KoToolManager() { delete d; } QList KoToolManager::createToolList() const { QList answer; foreach(ToolHelper *tool, d->tools) { if (tool->id() == KoCreateShapesTool_ID) continue; // don't show this one. KoToolButton button; button.button = tool->createButton(); button.section = tool->toolType(); button.priority = tool->priority(); button.buttonGroupId = tool->uniqueId(); button.visibilityCode = tool->activationShapeId(); answer.append(button); } return answer; } void KoToolManager::requestToolActivation(KoCanvasController * controller) { if (d->canvasses.contains(controller)) { QString activeToolId = d->canvasses.value(controller).first()->activeToolId; foreach(ToolHelper * th, d->tools) { if (th->id() == activeToolId) { d->toolActivated(th); break; } } } } KoInputDevice KoToolManager::currentInputDevice() const { return d->inputDevice; } void KoToolManager::registerTools(KActionCollection *ac, KoCanvasController *controller) { Q_ASSERT(controller); Q_ASSERT(ac); d->setup(); if (!d->canvasses.contains(controller)) { return; } + // Actions available during the use of individual tools CanvasData *cd = d->canvasses.value(controller).first(); foreach(KoToolBase *tool, cd->allTools) { QHash actions = tool->actions(); - QHash::const_iterator it(actions.constBegin()); - for (; it != actions.constEnd(); ++it) { - if (!ac->action(it.key())) - ac->addAction(it.key(), it.value()); + QHash::const_iterator action(actions.constBegin()); + for (; action != actions.constEnd(); ++action) { + if (!ac->action(action.key())) + ac->addAction(action.key(), action.value()); } } + + // Actions used to switch tools; connect slot to keep button tooltips updated foreach(ToolHelper * th, d->tools) { ToolAction* action = new ToolAction(this, th->id(), th->toolTip(), ac); action->setShortcut(th->shortcut()); ac->addAction(th->id(), action); + th->setAction(action); + connect(action, SIGNAL(changed()), th, SLOT(actionUpdated())); } } void KoToolManager::addController(KoCanvasController *controller) { Q_ASSERT(controller); if (d->canvasses.keys().contains(controller)) return; d->setup(); d->attachCanvas(controller); connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*))); connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); } void KoToolManager::removeCanvasController(KoCanvasController *controller) { Q_ASSERT(controller); disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); d->detachCanvas(controller); } void KoToolManager::attemptCanvasControllerRemoval(QObject* controller) { KoCanvasControllerProxyObject* controllerActual = qobject_cast(controller); if (controllerActual) { removeCanvasController(controllerActual->canvasController()); } } void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController) { if (!d->canvasses.keys().contains(canvasController)) return; QList canvasses = d->canvasses[canvasController]; foreach(CanvasData *canvas, canvasses) { foreach(KoToolBase *tool, canvas->allTools.values()) { tool->updateShapeController(shapeController); } } } void KoToolManager::switchToolRequested(const QString & id) { Q_ASSERT(d->canvasData); if (!d->canvasData) return; while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack d->canvasData->stack.pop(); d->switchTool(id, false); } void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id) { if (!d->canvasData) return; d->switchInputDevice(id); } void KoToolManager::switchToolTemporaryRequested(const QString &id) { d->switchTool(id, true); } void KoToolManager::switchBackRequested() { if (!d->canvasData) return; if (d->canvasData->stack.isEmpty()) { // default to changing to the interactionTool d->switchTool(KoInteractionTool_ID, false); return; } d->switchTool(d->canvasData->stack.pop(), false); } KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const { Q_ASSERT(canvas); foreach(KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) { KoCreateShapesTool *createTool = dynamic_cast (d->canvasData->allTools.value(KoCreateShapesTool_ID)); Q_ASSERT(createTool /* ID changed? */); return createTool; } } Q_ASSERT(0); // this should not happen return 0; } KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const { Q_ASSERT(canvas); foreach(KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) return d->canvasData->allTools.value(id); } return 0; } KoCanvasController *KoToolManager::activeCanvasController() const { if (! d->canvasData) return 0; return d->canvasData->canvas; } QString KoToolManager::preferredToolForSelection(const QList &shapes) { QList types; foreach(KoShape *shape, shapes) if (! types.contains(shape->shapeId())) types.append(shape->shapeId()); QString toolType = KoInteractionTool_ID; int prio = INT_MAX; foreach(ToolHelper *helper, d->tools) { if (helper->priority() >= prio) continue; if (helper->toolType() == KoToolFactoryBase::mainToolType()) continue; bool toolWillWork = false; foreach (const QString &type, types) { if (helper->activationShapeId().split(',').contains(type)) { toolWillWork = true; break; } } if (toolWillWork) { toolType = helper->id(); prio = helper->priority(); } } return toolType; } void KoToolManager::injectDeviceEvent(KoInputDeviceHandlerEvent * event) { if (d->canvasData && d->canvasData->canvas->canvas()) { if (static_cast(event->type()) == KoInputDeviceHandlerEvent::ButtonPressed) d->canvasData->activeTool->customPressEvent(event->pointerEvent()); else if (static_cast(event->type()) == KoInputDeviceHandlerEvent::ButtonReleased) d->canvasData->activeTool->customReleaseEvent(event->pointerEvent()); else if (static_cast(event->type()) == KoInputDeviceHandlerEvent::PositionChanged) d->canvasData->activeTool->customMoveEvent(event->pointerEvent()); } } void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory) { ToolHelper *tool = new ToolHelper(toolFactory); // make sure all plugins are loaded as otherwise we will not load them d->setup(); d->tools.append(tool); // connect to all tools so we can hear their button-clicks connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*))); // now create tools for all existing canvases foreach(KoCanvasController *controller, d->canvasses.keys()) { // this canvascontroller is unknown, which is weird if (!d->canvasses.contains(controller)) { continue; } // create a tool for all canvasdata objects (i.e., all input devices on this canvas) foreach (CanvasData *cd, d->canvasses[controller]) { QPair toolPair = createTools(controller, tool); if (toolPair.second) { cd->allTools.insert(toolPair.first, toolPair.second); } } // Then create a button for the toolbox for this canvas if (tool->id() == KoCreateShapesTool_ID) { continue; } KoToolButton button; button.button = tool->createButton(); button.section = tool->toolType(); button.priority = tool->priority(); button.buttonGroupId = tool->uniqueId(); button.visibilityCode = tool->activationShapeId(); emit addedTool(button, controller); } } QPair KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool) { // XXX: maybe this method should go into the private class? QHash origHash; if (d->canvasses.contains(controller)) { origHash = d->canvasses.value(controller).first()->allTools; } if (origHash.contains(tool->id())) { return QPair(tool->id(), origHash.value(tool->id())); } kDebug(30006) << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority(); KoToolBase *tl = tool->createTool(controller->canvas()); if (tl) { d->uniqueToolIds.insert(tl, tool->uniqueId()); tl->setObjectName(tool->id()); foreach(KAction *action, tl->actions()) { action->setEnabled(false); } } KoZoomTool *zoomTool = dynamic_cast(tl); if (zoomTool) { zoomTool->setCanvasController(controller); } KoPanTool *panTool = dynamic_cast(tl); if (panTool) { panTool->setCanvasController(controller); } return QPair(tool->id(), tl); } KoToolManager* KoToolManager::instance() { K_GLOBAL_STATIC(KoToolManager, s_instance) return s_instance; } QString KoToolManager::activeToolId() const { if (!d->canvasData) return QString(); return d->canvasData->activeToolId; } KoToolManager::Private *KoToolManager::priv() { return d; } #include diff --git a/libs/flake/KoToolManager.h b/libs/flake/KoToolManager.h index 6567346587..fa12272fe8 100644 --- a/libs/flake/KoToolManager.h +++ b/libs/flake/KoToolManager.h @@ -1,308 +1,308 @@ /* This file is part of the KDE project * Copyright (c) 2005-2006 Boudewijn Rempt * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_TOOL_MANAGER #define KO_TOOL_MANAGER #include "KoInputDevice.h" #include "flake_export.h" #include #include class KoCanvasController; class KoShapeBasedDocumentBase; class KoToolFactoryBase; class KoCanvasBase; class KoToolBase; class KoCreateShapesTool; class KActionCollection; class KoShape; class QToolButton; class KoInputDeviceHandlerEvent; class KoShapeLayer; class ToolHelper; class QCursor; /// Struct for the createToolList return type. struct KoToolButton { - QToolButton *button;///< a newly created button. + QToolButton *button; ///< a newly created button. QString section; ///< The section the button wants to be in. int priority; ///< Lower number (higher priority) means coming first in the section. int buttonGroupId; ///< An unique ID for this button as passed by changedTool() QString visibilityCode; ///< This button should become visible when we emit this string in toolCodesSelected() }; /** * This class manages the activation and deactivation of tools for * each input device. * * Managing the active tool and switching tool based on various variables. * * The state of the toolbox will be the same for all views in the process so practically * you can say we have one toolbox per application instance (process). Implementation * does not allow one widget to be in more then one view, so we just make sure the toolbox * is hidden in not-in-focus views. * * The ToolManager is a singleton and will manage all views in all applications that * are loaded in this process. This means you will have to register and unregister your view. * When creating your new view you should use a KoCanvasController() and register that * with the ToolManager like this: @code MyGuiWidget::MyGuiWidget() { m_canvasController = new KoCanvasController(this); m_canvasController->setCanvas(m_canvas); KoToolManager::instance()->addControllers(m_canvasController)); } MyGuiWidget::~MyGuiWidget() { KoToolManager::instance()->removeCanvasController(m_canvasController); } @endcode * * For a new view that extends KoView all you need to do is implement KoView::createToolBox() * * KoToolManager also keeps track of the current tool based on a complex set of conditions and heuristics: - there is one active tool per KoCanvasController (and there is one KoCanvasController per view, because this is a class with scrollbars and a zoomlevel and so on) - for every pointing device (determined by the unique id of tablet, or 0 for mice -- you may have more than one mouse attached, but Qt cannot distinquish between them, there is an associated tool. - depending on things like tablet leave/enter proximity, incoming mouse or tablet events and a little timer (that gets stopped when we know what is what), the active pointing device is determined, and the active tool is set accordingly. Nota bene: if you use KoToolManager and register your canvases with it you no longer have to manually implement methods to route mouse, tablet, key or wheel events to the active tool. In fact, it's no longer interesting to you which tool is active; you can safely route the paint event through KoToolProxy::paint(). (The reason the input events are handled completely by the toolmanager and the paint events not is that, generally speaking, it's okay if the tools get the input events first, but you want to paint your shapes or other canvas stuff first and only then paint the tool stuff.) */ class FLAKE_EXPORT KoToolManager : public QObject { Q_OBJECT public: /// Return the toolmanager singleton static KoToolManager* instance(); ~KoToolManager(); /** * Register actions for switching to tools at the actionCollection parameter. * The actions will have the text / shortcut as stated by the toolFactory. * If the application calls this in their KoView extending class they will have all the benefits * from allowing this in the menus and to allow the use to configure the shortcuts used. * @param ac the actionCollection that will be the parent of the actions. * @param controller tools registered with this controller will have all their actions added as well. */ void registerTools(KActionCollection *ac, KoCanvasController *controller); /** * Register a new canvas controller * @param controller the view controller that this toolmanager will manage the tools for */ void addController(KoCanvasController *controller); /** * Remove a set of controllers * When the controller is no longer used it should be removed so all tools can be * deleted and stop eating memory. * @param controller the controller that is removed */ void removeCanvasController(KoCanvasController *controller); /** * Attempt to remove a controller. * This is automatically called when a controller's proxy object is deleted, and * it ensures that the controller is, in fact, removed, even if the creator forgot * to do so. * @param controller the proxy object of the controller to be removed */ Q_SLOT void attemptCanvasControllerRemoval(QObject *controller); /// @return the active canvas controller KoCanvasController *activeCanvasController() const; /** * Connect all the tools for the given canvas to the new shape controller. * * @param shapecontroller the new shape controller * @param canvasController the canvas */ void updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController); /** * Return the tool that is able to create shapes for this param canvas. * This is typically used by the KoShapeSelector to set which shape to create next. * @param canvas the canvas that is a child of a previously registered controller * who's tool you want. * @see addController() */ KoCreateShapesTool *shapeCreatorTool(KoCanvasBase *canvas) const; /** * Returns the tool for the given tool id. * @param canvas the canvas that is a child of a previously registered controller * who's tool you want. * @see addController() */ KoToolBase *toolById(KoCanvasBase *canvas, const QString &id) const; /// @return the currently active pointing device KoInputDevice currentInputDevice() const; /** * For the list of shapes find out which tool is the highest priorty tool that can handle it. * @returns the toolId for the shapes. * @param shapes a list of shapes, a selection for example, that is used to look for the tool. */ QString preferredToolForSelection(const QList &shapes); /** * Create a list of buttons to represent all the tools. * @returns a list of Buttons. * This is a factory method for buttons and meta information on the button to better display the button. */ QList createToolList() const; /// Request tool activation for the given canvas controller void requestToolActivation(KoCanvasController *controller); /// Injects an input event from a plugin based input device void injectDeviceEvent(KoInputDeviceHandlerEvent *event); /// Returns the toolId of the currently active tool QString activeToolId() const; class Private; /** * \internal return the private object for the toolmanager. */ KoToolManager::Private *priv(); public Q_SLOTS: /** * Request switching tool * @param id the id of the tool */ void switchToolRequested(const QString &id); /** * Request change input device * @param id the id of the input device */ void switchInputDeviceRequested(const KoInputDevice &id); /** * a new tool has become known to mankind */ void addDeferredToolFactory(KoToolFactoryBase *toolFactory); /** * Request for temporary switching the tools. * This switch can be later reverted with switchBackRequested(). * @param id the id of the tool * * @see switchBackRequested() */ void switchToolTemporaryRequested(const QString &id); /** * Switches back to the original tool after the temporary switch * has been done. It the user changed the tool manually on the way, * then it switches to the interaction tool */ void switchBackRequested(); Q_SIGNALS: /** * Emitted when a new tool was selected or became active. * @param canvas the currently active canvas. * @param uniqueToolId a random but unique code for the new tool. */ void changedTool(KoCanvasController *canvas, int uniqueToolId); /** * Emitted after the selection changed to state which unique shape-types are now * in the selection. * @param canvas the currently active canvas. * @param types a list of string that are the shape types of the selected objects. */ void toolCodesSelected(const QList &types); /** * Emitted after the current layer changed either its properties or to a new layer. * @param canvas the currently active canvas. * @param layer the layer that is selected. */ void currentLayerChanged(const KoCanvasController *canvas, const KoShapeLayer *layer); /** * Every time a new input device gets used by a tool, this event is emitted. * @param device the new input device that the user picked up. */ void inputDeviceChanged(const KoInputDevice &device); /** * Emitted whenever the active canvas changed. * @param canvas the new activated canvas (might be 0) */ void changedCanvas(const KoCanvasBase *canvas); /** * Emitted whenever the active tool changes the status text. * @param statusText the new status text */ void changedStatusText(const QString &statusText); /** * emitted whenever a new tool is dynamically added for the given canvas */ void addedTool(const KoToolButton &button, KoCanvasController *canvas); private: KoToolManager(); KoToolManager(const KoToolManager&); KoToolManager operator=(const KoToolManager&); Q_PRIVATE_SLOT(d, void toolActivated(ToolHelper *tool)) Q_PRIVATE_SLOT(d, void detachCanvas(KoCanvasController *controller)) Q_PRIVATE_SLOT(d, void attachCanvas(KoCanvasController *controller)) Q_PRIVATE_SLOT(d, void movedFocus(QWidget *from, QWidget *to)) Q_PRIVATE_SLOT(d, void updateCursor(const QCursor &cursor)) Q_PRIVATE_SLOT(d, void selectionChanged(const QList &shapes)) Q_PRIVATE_SLOT(d, void currentLayerChanged(const KoShapeLayer *layer)) QPair createTools(KoCanvasController *controller, ToolHelper *tool); Private *const d; }; #endif diff --git a/libs/flake/KoToolManager_p.cpp b/libs/flake/KoToolManager_p.cpp index bb0ab1a41f..c4d7d087c1 100644 --- a/libs/flake/KoToolManager_p.cpp +++ b/libs/flake/KoToolManager_p.cpp @@ -1,125 +1,157 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoToolManager_p.h" #include #include #include #include #include #include +#include #include // for qrand() -// ************ ToolHelper ********** +/* ************ ToolHelper ********** + * This class wrangles the tool factory, toolbox button and switch-tool action + * for a single tool. It assumes the will continue to live once it is created. + * (Hiding the toolbox is OK.) + */ + ToolHelper::ToolHelper(KoToolFactoryBase *tool) + : m_toolFactory(tool), + m_uniqueId((int)qrand()), + button(0), + action(0) { - m_toolFactory = tool; - m_uniqueId = (int) qrand(); } QToolButton* ToolHelper::createButton() { - QToolButton *but = new QToolButton(); - but->setObjectName(m_toolFactory->id()); - but->setIcon(KIcon(m_toolFactory->iconName())); - but->setToolTip(m_toolFactory->toolTip()); - connect(but, SIGNAL(clicked()), this, SLOT(buttonPressed())); - return but; + button = new QToolButton(); + button->setObjectName(m_toolFactory->id()); + button->setIcon(KIcon(m_toolFactory->iconName())); + button->setToolTip(buttonToolTip()); + + connect(button, SIGNAL(clicked()), this, SLOT(buttonPressed())); + return button; } void ToolHelper::buttonPressed() { emit toolActivated(this); } QString ToolHelper::id() const { return m_toolFactory->id(); } QString ToolHelper::activationShapeId() const { return m_toolFactory->activationShapeId(); } QString ToolHelper::toolTip() const { return m_toolFactory->toolTip(); } +QString ToolHelper::buttonToolTip() const +{ + return shortcut().isEmpty() ? + i18nc("@info:tooltip", "%1", toolTip()) : + i18nc("@info:tooltip %2 is shortcut", "%1 (%2)", toolTip(), + shortcut().toString()); +} + +void ToolHelper::actionUpdated() +{ + if (button) + button->setToolTip(buttonToolTip()); +} + KoToolBase *ToolHelper::createTool(KoCanvasBase *canvas) const { KoToolBase *tool = m_toolFactory->createTool(canvas); if (tool) { tool->setToolId(id()); } return tool; } QString ToolHelper::toolType() const { return m_toolFactory->toolType(); } int ToolHelper::priority() const { return m_toolFactory->priority(); } KShortcut ToolHelper::shortcut() const { + if (action) { + return action->shortcut(); + } + return m_toolFactory->shortcut(); } +void ToolHelper::setAction(KAction *a) +{ + action = a; +} + // ************ Connector ********** Connector::Connector(KoShapeManager *parent) : QObject(parent), m_shapeManager(parent) { connect(m_shapeManager, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); } void Connector::selectionChanged() { emit selectionChanged(m_shapeManager->selection()->selectedShapes()); } // ************ ToolAction ********** ToolAction::ToolAction(KoToolManager* toolManager, const QString &id, const QString &name, QObject *parent) : KAction(name, parent), m_toolManager(toolManager), m_toolID(id) { connect(this, SIGNAL(triggered(bool)), this, SLOT(actionTriggered())); } ToolAction::~ToolAction() { } void ToolAction::actionTriggered() { m_toolManager->switchToolRequested(m_toolID); } #include diff --git a/libs/flake/KoToolManager_p.h b/libs/flake/KoToolManager_p.h index b8ce53980c..e7e1b0f922 100644 --- a/libs/flake/KoToolManager_p.h +++ b/libs/flake/KoToolManager_p.h @@ -1,180 +1,187 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_TOOL_MANAGER_P #define KO_TOOL_MANAGER_P #include #include #include #include #include #include #include "KoInputDevice.h" #include "KoToolManager.h" #include class KoToolFactoryBase; class KoShapeManager; class KoCanvasBase; class KoToolBase; class KoShape; class KoToolManager; class KoCanvasController; class KoShapeLayer; class ToolHelper; class CanvasData; class QToolButton; class KoToolProxy; class KoToolManager::Private { public: Private(KoToolManager *qq); ~Private(); void setup(); void connectActiveTool(); void disconnectActiveTool(); void switchTool(KoToolBase *tool, bool temporary); void switchTool(const QString &id, bool temporary); void postSwitchTool(bool temporary); void switchCanvasData(CanvasData *cd); bool eventFilter(QObject *object, QEvent *event); void toolActivated(ToolHelper *tool); void detachCanvas(KoCanvasController *controller); void attachCanvas(KoCanvasController *controller); void movedFocus(QWidget *from, QWidget *to); void updateCursor(const QCursor &cursor); void switchBackRequested(); void selectionChanged(const QList &shapes); void currentLayerChanged(const KoShapeLayer *layer); void updateToolForProxy(); void switchToolTemporaryRequested(const QString &id); CanvasData *createCanvasData(KoCanvasController *controller, const KoInputDevice &device); /** * Request a switch from to the param input device. * This will cause the tool for that device to be selected. */ void switchInputDevice(const KoInputDevice &device); /** * Whenever a new tool proxy class is instantiated, it will use this method to register itself * so the toolManager can update it to the latest active tool. * @param proxy the proxy to register. * @param canvas which canvas the proxy is associated with; whenever a new tool is selected for that canvas, * the proxy gets an update. */ void registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas); void switchToolByShortcut(QKeyEvent *event); KoToolManager *q; QList tools; // list of all available tools via their factories. QHash uniqueToolIds; // for the changedTool signal QHash > canvasses; QHash proxies; CanvasData *canvasData; // data about the active canvas. KoInputDevice inputDevice; bool layerExplicitlyDisabled; }; /// \internal class ToolHelper : public QObject { Q_OBJECT public: explicit ToolHelper(KoToolFactoryBase *tool); QToolButton *createButton(); /// wrapper around KoToolFactoryBase::id(); QString id() const; /// wrapper around KoToolFactoryBase::toolTip(); QString toolTip() const; /// wrapper around KoToolFactoryBase::toolType(); QString toolType() const; /// wrapper around KoToolFactoryBase::activationShapeId(); QString activationShapeId() const; /// wrapper around KoToolFactoryBase::priority(); int priority() const; KoToolBase *createTool(KoCanvasBase *canvas) const; int uniqueId() const { return m_uniqueId; } - /// wrapper around KoToolFactoryBase::shortcut() + /// KAction->shortcut() if it exists, otherwise KoToolFactoryBase::shortcut() KShortcut shortcut() const; + /// Writes a tooltip for a button, appending the keyboard shortcut if we have one + QString buttonToolTip() const; + /// Associate an action with this tool + void setAction(KAction *a); Q_SIGNALS: - /// emitted when one of the generated buttons was pressed. + /// Emitted when the generated toolbox button is pressed. void toolActivated(ToolHelper *tool); private Q_SLOTS: void buttonPressed(); + void actionUpdated(); private: KoToolFactoryBase *m_toolFactory; int m_uniqueId; + QToolButton *button; + KAction *action; }; /// \internal /// Helper class to transform a simple signal selection changed into a signal with a parameter class Connector : public QObject { Q_OBJECT public: explicit Connector(KoShapeManager *parent); public Q_SLOTS: void selectionChanged(); Q_SIGNALS: void selectionChanged(const QList &shape); private: KoShapeManager *m_shapeManager; }; /// \internal /// Helper class to provide a action for tool shortcuts class ToolAction : public KAction { Q_OBJECT public: ToolAction(KoToolManager* toolManager, const QString &id, const QString &name, QObject *parent); virtual ~ToolAction(); private Q_SLOTS: void actionTriggered(); private: KoToolManager* m_toolManager; QString m_toolID; }; #endif