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