1 /* This file is part of the KDE project 2 * Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org> 3 * Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com> 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 */ 20 21 #ifndef KISTOOLSELECTBASE_H 22 #define KISTOOLSELECTBASE_H 23 24 #include "KoPointerEvent.h" 25 #include "kis_tool.h" 26 #include "kis_canvas2.h" 27 #include "kis_selection.h" 28 #include "kis_selection_options.h" 29 #include "kis_selection_tool_config_widget_helper.h" 30 #include "KisViewManager.h" 31 #include "kis_selection_manager.h" 32 #include "kis_selection_modifier_mapper.h" 33 #include "strokes/move_stroke_strategy.h" 34 #include "kis_image.h" 35 #include "kis_cursor.h" 36 #include "kis_action_manager.h" 37 #include "kis_action.h" 38 #include "kis_signal_auto_connection.h" 39 #include "kis_selection_tool_helper.h" 40 #include "kis_assert.h" 41 42 /** 43 * This is a basic template to create selection tools from basic path based drawing tools. 44 * The template overrides the ability to execute alternate actions correctly. 45 * The default behavior for the modifier keys is as follows: 46 * 47 * Shift: add to selection 48 * Alt: subtract from selection 49 * Shift+Alt: intersect current selection 50 * Ctrl: replace selection 51 * 52 * The mapping itself is done in KisSelectionModifierMapper. 53 * 54 * Certain tools also use modifier keys to alter their behavior, e.g. forcing square proportions with the rectangle tool. 55 * The template enables the following rules for forwarding keys: 56 57 * 1) Any modifier keys held *when the tool is first activated* will determine 58 * the new selection method. This is recorded in m_selectionActionAlternate. A 59 * value of m_selectionActionAlternate = SELECTION_DEFAULT means no modifier was 60 * being pressed when the tool was activated. 61 * 62 * 2) If the underlying tool *does not take modifier keys*, pressing modifier 63 * keys in the middle of a stroke will change the selection method. This is 64 * recorded in m_selectionAction. A value of SELECTION_DEFAULT means no modifier 65 * is being pressed. Applies to the lasso tool and polygon tool. 66 * 67 * 3) If the underlying tool *takes modifier keys,* they will always be 68 * forwarded to the underlying tool, and it is not possible to change the 69 * selection method in the middle of a stroke. 70 */ 71 72 template <class BaseClass> 73 class KisToolSelectBase : public BaseClass 74 { 75 76 public: 77 KisToolSelectBase(KoCanvasBase * canvas,const QString toolName)78 KisToolSelectBase(KoCanvasBase* canvas, const QString toolName) 79 : BaseClass(canvas) 80 , m_widgetHelper(toolName) 81 , m_selectionActionAlternate(SELECTION_DEFAULT) 82 { 83 KisSelectionModifierMapper::instance(); 84 } 85 KisToolSelectBase(KoCanvasBase * canvas,const QCursor cursor,const QString toolName)86 KisToolSelectBase(KoCanvasBase* canvas, const QCursor cursor, const QString toolName) 87 : BaseClass(canvas, cursor) 88 , m_widgetHelper(toolName) 89 , m_selectionActionAlternate(SELECTION_DEFAULT) 90 { 91 KisSelectionModifierMapper::instance(); 92 } 93 KisToolSelectBase(KoCanvasBase * canvas,QCursor cursor,QString toolName,KoToolBase * delegateTool)94 KisToolSelectBase(KoCanvasBase* canvas, QCursor cursor, QString toolName, KoToolBase *delegateTool) 95 : BaseClass(canvas, cursor, delegateTool) 96 , m_widgetHelper(toolName) 97 , m_selectionActionAlternate(SELECTION_DEFAULT) 98 { 99 KisSelectionModifierMapper::instance(); 100 } 101 102 enum SampleLayersMode 103 { 104 SampleAllLayers, 105 SampleCurrentLayer, 106 SampleColorLabeledLayers, 107 }; 108 updateActionShortcutToolTips()109 void updateActionShortcutToolTips() { 110 KisSelectionOptions *widget = m_widgetHelper.optionWidget(); 111 if (widget) { 112 widget->updateActionButtonToolTip( 113 SELECTION_REPLACE, 114 this->action("selection_tool_mode_replace")->shortcut()); 115 widget->updateActionButtonToolTip( 116 SELECTION_ADD, 117 this->action("selection_tool_mode_add")->shortcut()); 118 widget->updateActionButtonToolTip( 119 SELECTION_SUBTRACT, 120 this->action("selection_tool_mode_subtract")->shortcut()); 121 widget->updateActionButtonToolTip( 122 SELECTION_INTERSECT, 123 this->action("selection_tool_mode_intersect")->shortcut()); 124 } 125 } 126 activate(KoToolBase::ToolActivation activation,const QSet<KoShape * > & shapes)127 void activate(KoToolBase::ToolActivation activation, const QSet<KoShape*> &shapes) 128 { 129 BaseClass::activate(activation, shapes); 130 131 m_modeConnections.addUniqueConnection( 132 this->action("selection_tool_mode_replace"), SIGNAL(triggered()), 133 &m_widgetHelper, SLOT(slotReplaceModeRequested())); 134 135 m_modeConnections.addUniqueConnection( 136 this->action("selection_tool_mode_add"), SIGNAL(triggered()), 137 &m_widgetHelper, SLOT(slotAddModeRequested())); 138 139 m_modeConnections.addUniqueConnection( 140 this->action("selection_tool_mode_subtract"), SIGNAL(triggered()), 141 &m_widgetHelper, SLOT(slotSubtractModeRequested())); 142 143 m_modeConnections.addUniqueConnection( 144 this->action("selection_tool_mode_intersect"), SIGNAL(triggered()), 145 &m_widgetHelper, SLOT(slotIntersectModeRequested())); 146 147 updateActionShortcutToolTips(); 148 149 if (m_widgetHelper.optionWidget()) { 150 151 m_widgetHelper.optionWidget()->activateConnectionToImage(); 152 153 if (isPixelOnly()) { 154 m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode(); 155 } 156 m_widgetHelper.optionWidget()->setColorLabelsEnabled(usesColorLabels()); 157 } 158 } 159 deactivate()160 void deactivate() 161 { 162 BaseClass::deactivate(); 163 m_modeConnections.clear(); 164 if (m_widgetHelper.optionWidget()) { 165 m_widgetHelper.optionWidget()->deactivateConnectionToImage(); 166 } 167 } 168 createOptionWidget()169 QWidget* createOptionWidget() 170 { 171 KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas()); 172 Q_ASSERT(canvas); 173 174 m_widgetHelper.createOptionWidget(canvas, this->toolId()); 175 this->connect(this, SIGNAL(isActiveChanged(bool)), &m_widgetHelper, SLOT(slotToolActivatedChanged(bool))); 176 this->connect(&m_widgetHelper, SIGNAL(selectionActionChanged(int)), this, SLOT(resetCursorStyle())); 177 178 updateActionShortcutToolTips(); 179 if (m_widgetHelper.optionWidget()) { 180 if (isPixelOnly()) { 181 m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode(); 182 } 183 m_widgetHelper.optionWidget()->setColorLabelsEnabled(usesColorLabels()); 184 } 185 186 return m_widgetHelper.optionWidget(); 187 } 188 selectionMode()189 SelectionMode selectionMode() const 190 { 191 return m_widgetHelper.selectionMode(); 192 } 193 selectionAction()194 SelectionAction selectionAction() const 195 { 196 if (alternateSelectionAction() == SELECTION_DEFAULT) { 197 return m_widgetHelper.selectionAction(); 198 } 199 return alternateSelectionAction(); 200 } 201 antiAliasSelection()202 bool antiAliasSelection() const 203 { 204 return m_widgetHelper.antiAliasSelection(); 205 } 206 colorLabelsSelected()207 QList<int> colorLabelsSelected() const 208 { 209 return m_widgetHelper.colorLabelsSelected(); 210 } 211 sampleLayersMode()212 SampleLayersMode sampleLayersMode() const 213 { 214 QString layersMode = m_widgetHelper.sampleLayersMode(); 215 if (layersMode == m_widgetHelper.optionWidget()->SAMPLE_LAYERS_MODE_ALL) { 216 return SampleAllLayers; 217 } else if (layersMode == m_widgetHelper.optionWidget()->SAMPLE_LAYERS_MODE_CURRENT) { 218 return SampleCurrentLayer; 219 } else if (layersMode == m_widgetHelper.optionWidget()->SAMPLE_LAYERS_MODE_COLOR_LABELED) { 220 return SampleColorLabeledLayers; 221 } 222 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(true, SampleAllLayers); 223 return SampleAllLayers; 224 } 225 alternateSelectionAction()226 SelectionAction alternateSelectionAction() const 227 { 228 return m_selectionActionAlternate; 229 } 230 selectionOptionWidget()231 KisSelectionOptions* selectionOptionWidget() 232 { 233 return m_widgetHelper.optionWidget(); 234 } 235 setAlternateSelectionAction(SelectionAction action)236 virtual void setAlternateSelectionAction(SelectionAction action) 237 { 238 m_selectionActionAlternate = action; 239 dbgKrita << "Changing to selection action" << m_selectionActionAlternate; 240 } 241 activateAlternateAction(KisTool::AlternateAction action)242 void activateAlternateAction(KisTool::AlternateAction action) 243 { 244 Q_UNUSED(action); 245 BaseClass::activatePrimaryAction(); 246 } 247 deactivateAlternateAction(KisTool::AlternateAction action)248 void deactivateAlternateAction(KisTool::AlternateAction action) 249 { 250 Q_UNUSED(action); 251 BaseClass::deactivatePrimaryAction(); 252 } 253 beginAlternateAction(KoPointerEvent * event,KisTool::AlternateAction action)254 void beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { 255 Q_UNUSED(action); 256 beginPrimaryAction(event); 257 } 258 continueAlternateAction(KoPointerEvent * event,KisTool::AlternateAction action)259 void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { 260 Q_UNUSED(action); 261 continuePrimaryAction(event); 262 } 263 endAlternateAction(KoPointerEvent * event,KisTool::AlternateAction action)264 void endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { 265 Q_UNUSED(action); 266 endPrimaryAction(event); 267 } 268 locateSelectionMaskUnderCursor(const QPointF & pos,Qt::KeyboardModifiers modifiers)269 KisNodeSP locateSelectionMaskUnderCursor(const QPointF &pos, Qt::KeyboardModifiers modifiers) { 270 if (modifiers != Qt::NoModifier) return 0; 271 272 KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas()); 273 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas, 0); 274 275 KisSelectionSP selection = canvas->viewManager()->selection(); 276 if (selection && 277 selection->outlineCacheValid()) { 278 279 const qreal handleRadius = qreal(this->handleRadius()) / canvas->coordinatesConverter()->effectiveZoom(); 280 QPainterPath samplePath; 281 samplePath.addEllipse(pos, handleRadius, handleRadius); 282 283 const QPainterPath selectionPath = selection->outlineCache(); 284 285 if (selectionPath.intersects(samplePath) && !selectionPath.contains(samplePath)) { 286 KisNodeSP parent = selection->parentNode(); 287 if (parent && parent->isEditable()) { 288 return parent; 289 } 290 } 291 } 292 293 return 0; 294 } 295 keyPressEvent(QKeyEvent * event)296 void keyPressEvent(QKeyEvent *event) { 297 if (this->mode() != KisTool::PAINT_MODE) { 298 setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); 299 this->resetCursorStyle(); 300 } 301 BaseClass::keyPressEvent(event); 302 } 303 keyReleaseEvent(QKeyEvent * event)304 void keyReleaseEvent(QKeyEvent *event) { 305 if (this->mode() != KisTool::PAINT_MODE) { 306 setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); 307 this->resetCursorStyle(); 308 } 309 BaseClass::keyPressEvent(event); 310 } 311 mouseMoveEvent(KoPointerEvent * event)312 void mouseMoveEvent(KoPointerEvent *event) { 313 if (!this->hasUserInteractionRunning() && 314 (m_moveStrokeId || this->mode() != KisTool::PAINT_MODE)) { 315 316 const QPointF pos = this->convertToPixelCoord(event->point); 317 KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers()); 318 if (selectionMask) { 319 this->useCursor(KisCursor::moveSelectionCursor()); 320 } else { 321 setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); 322 this->resetCursorStyle(); 323 } 324 } 325 326 BaseClass::mouseMoveEvent(event); 327 } 328 329 beginPrimaryAction(KoPointerEvent * event)330 virtual void beginPrimaryAction(KoPointerEvent *event) 331 { 332 if (!this->hasUserInteractionRunning()) { 333 const QPointF pos = this->convertToPixelCoord(event->point); 334 KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas()); 335 KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); 336 337 KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers()); 338 if (selectionMask) { 339 KisStrokeStrategy *strategy = new MoveStrokeStrategy({selectionMask}, this->image().data(), this->image().data()); 340 m_moveStrokeId = this->image()->startStroke(strategy); 341 m_dragStartPos = pos; 342 m_didMove = true; 343 return; 344 } 345 } 346 m_didMove = false; 347 keysAtStart = event->modifiers(); 348 349 setAlternateSelectionAction(KisSelectionModifierMapper::map(keysAtStart)); 350 if (alternateSelectionAction() != SELECTION_DEFAULT) { 351 BaseClass::listenToModifiers(false); 352 } 353 BaseClass::beginPrimaryAction(event); 354 } 355 continuePrimaryAction(KoPointerEvent * event)356 virtual void continuePrimaryAction(KoPointerEvent *event) 357 { 358 if (m_moveStrokeId) { 359 const QPointF pos = this->convertToPixelCoord(event->point); 360 const QPoint offset((pos - m_dragStartPos).toPoint()); 361 362 this->image()->addJob(m_moveStrokeId, new MoveStrokeStrategy::Data(offset)); 363 return; 364 } 365 366 367 //If modifier keys have changed, tell the base tool it can start capturing modifiers 368 if ((keysAtStart != event->modifiers()) && !BaseClass::listeningToModifiers()) { 369 BaseClass::listenToModifiers(true); 370 } 371 372 //Always defer to the base class if it signals it is capturing modifier keys 373 if (!BaseClass::listeningToModifiers()) { 374 setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); 375 } 376 377 BaseClass::continuePrimaryAction(event); 378 } 379 endPrimaryAction(KoPointerEvent * event)380 void endPrimaryAction(KoPointerEvent *event) 381 { 382 if (m_moveStrokeId) { 383 this->image()->endStroke(m_moveStrokeId); 384 385 m_moveStrokeId.clear(); 386 return; 387 } 388 389 390 keysAtStart = Qt::NoModifier; //reset this with each action 391 BaseClass::endPrimaryAction(event); 392 } 393 selectionDragInProgress()394 bool selectionDragInProgress() const { 395 return m_moveStrokeId; 396 } 397 selectionDidMove()398 bool selectionDidMove() const { 399 return m_didMove; 400 } 401 popupActionsMenu()402 QMenu* popupActionsMenu() { 403 KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas()); 404 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, 0); 405 406 return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); 407 } 408 409 410 protected: 411 using BaseClass::canvas; 412 KisSelectionToolConfigWidgetHelper m_widgetHelper; 413 SelectionAction m_selectionActionAlternate; 414 isPixelOnly()415 virtual bool isPixelOnly() const { 416 return false; 417 } 418 usesColorLabels()419 virtual bool usesColorLabels() const { 420 return false; 421 } 422 423 private: 424 Qt::KeyboardModifiers keysAtStart; 425 426 QPointF m_dragStartPos; 427 KisStrokeId m_moveStrokeId; 428 bool m_didMove = false; 429 430 KisSignalAutoConnectionsStore m_modeConnections; 431 }; 432 433 struct FakeBaseTool : KisTool 434 { FakeBaseToolFakeBaseTool435 FakeBaseTool(KoCanvasBase* canvas) 436 : KisTool(canvas, QCursor()) 437 { 438 } 439 FakeBaseToolFakeBaseTool440 FakeBaseTool(KoCanvasBase* canvas, const QString &toolName) 441 : KisTool(canvas, QCursor()) 442 { 443 Q_UNUSED(toolName); 444 } 445 FakeBaseToolFakeBaseTool446 FakeBaseTool(KoCanvasBase* canvas, const QCursor &cursor) 447 : KisTool(canvas, cursor) 448 { 449 } 450 hasUserInteractionRunningFakeBaseTool451 bool hasUserInteractionRunning() const { 452 return false; 453 } 454 455 }; 456 457 458 typedef KisToolSelectBase<FakeBaseTool> KisToolSelect; 459 460 461 #endif // KISTOOLSELECTBASE_H 462