Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_select_base.h
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#ifndef KISTOOLSELECTBASE_H
9#define KISTOOLSELECTBASE_H
10
11#include "KoPointerEvent.h"
12#include "kis_tool.h"
13#include "kis_canvas2.h"
14#include "kis_selection.h"
17#include "KisViewManager.h"
22#include "kis_image.h"
23#include "kis_cursor.h"
24#include "kis_action_manager.h"
25#include "kis_action.h"
28#include "kis_assert.h"
30
61template <class BaseClass>
62class KisToolSelectBase : public BaseClass
63{
64
65public:
66
67 KisToolSelectBase(KoCanvasBase* canvas, const QString toolName)
68 : BaseClass(canvas)
69 , m_widgetHelper(toolName)
71 {
73 }
74
75 KisToolSelectBase(KoCanvasBase* canvas, const QCursor cursor, const QString toolName)
76 : BaseClass(canvas, cursor)
77 , m_widgetHelper(toolName)
79 {
81 }
82
83 KisToolSelectBase(KoCanvasBase* canvas, QCursor cursor, QString toolName, KoToolBase *delegateTool)
84 : BaseClass(canvas, cursor, delegateTool)
85 , m_widgetHelper(toolName)
87 {
89 }
90
98
105
108 if (widget) {
111 this->action("selection_tool_mode_replace")->shortcut());
114 this->action("selection_tool_mode_add")->shortcut());
117 this->action("selection_tool_mode_subtract")->shortcut());
120 this->action("selection_tool_mode_intersect")->shortcut());
121 }
122 }
123
124 void activate(const QSet<KoShape *> &shapes) override
125 {
126 BaseClass::activate(shapes);
127
129 this->action("selection_tool_mode_replace"), SIGNAL(triggered()),
130 &m_widgetHelper, SLOT(slotReplaceModeRequested()));
131
133 this->action("selection_tool_mode_add"), SIGNAL(triggered()),
134 &m_widgetHelper, SLOT(slotAddModeRequested()));
135
137 this->action("selection_tool_mode_subtract"), SIGNAL(triggered()),
138 &m_widgetHelper, SLOT(slotSubtractModeRequested()));
139
141 this->action("selection_tool_mode_intersect"), SIGNAL(triggered()),
142 &m_widgetHelper, SLOT(slotIntersectModeRequested()));
143
145
147 if (isPixelOnly()) {
150 true);
151 }
154 }
155 }
156
157 void deactivate() override
158 {
160 BaseClass::deactivate();
162 }
163
164 QWidget *createOptionWidget() override
165 {
166 m_widgetHelper.createOptionWidget(this->toolId());
168
169 this->connect(this, SIGNAL(isActiveChanged(bool)), &m_widgetHelper, SLOT(slotToolActivatedChanged(bool)));
170 this->connect(&m_widgetHelper,
171 SIGNAL(selectionActionChanged(SelectionAction)),
172 this,
173 SLOT(resetCursorStyle()));
174
177 m_widgetHelper.optionWidget()->setContentsMargins(0, 10, 0, 10);
178 if (isPixelOnly()) {
181 true);
182 }
185 }
186
188 }
189
191 {
193 }
194
202
204 {
206 }
207
209 {
211 }
212
213 int growSelection() const
214 {
216 }
217
222
224 {
226 }
227
232
234 {
237 if (referenceLayers == KisSelectionOptions::AllLayers) {
238 return SampleAllLayers;
239 } else if (referenceLayers == KisSelectionOptions::CurrentLayer) {
240 return SampleCurrentLayer;
241 } else if (referenceLayers == KisSelectionOptions::ColorLabeledLayers) {
243 }
245 return SampleAllLayers;
246 }
247
252
257
259 {
261 }
262
264 {
265 Q_UNUSED(action);
266 BaseClass::activatePrimaryAction();
267 }
268
270 {
271 Q_UNUSED(action);
272 BaseClass::deactivatePrimaryAction();
273 }
274
276 KisTool::AlternateAction action) override
277 {
278 Q_UNUSED(action);
279 beginPrimaryAction(event);
280 }
281
283 KisTool::AlternateAction action) override
284 {
285 Q_UNUSED(action);
287 }
288
290 KisTool::AlternateAction action) override
291 {
292 Q_UNUSED(action);
293 endPrimaryAction(event);
294 }
295
297 {
299 }
300
302 {
304 }
305
309
313
316 if (!cancel) {
317 this->image()->endStroke(m_moveStrokeId);
318 } else {
319 this->image()->cancelStroke(m_moveStrokeId);
320 }
321 m_moveStrokeId.clear();
322 m_accumulatedOffset = QPoint();
323 m_dragStartOffset = QPoint();
325 return;
326 }
327 }
328
329 KisNodeSP locateSelectionMaskUnderCursor(const QPointF &pos, Qt::KeyboardModifiers modifiers) {
330 if (modifiers != Qt::NoModifier) return 0;
331
332 KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas());
334
335 KisSelectionSP selection = canvas->viewManager()->selection();
336 if (selection &&
337 selection->outlineCacheValid()) {
338
339 const qreal handleRadius = qreal(this->handleRadius()) / canvas->coordinatesConverter()->effectiveZoom();
340 QPainterPath samplePath;
341 samplePath.addEllipse(pos, handleRadius, handleRadius);
342
343 const QPainterPath selectionPath = selection->outlineCache();
344
345 if (selectionPath.intersects(samplePath) && !selectionPath.contains(samplePath)) {
346 KisNodeSP parent = selection->parentNode();
347 if (parent && parent->isEditable()) {
348 return parent;
349 }
350 }
351 }
352
353 return 0;
354 }
355
356 void keyPressEvent(QKeyEvent *event) override
357 {
359 // Assume all the modifiers were unpressed...
360 m_currentModifiers = Qt::NoModifier;
361 // ...and add those which are right now
362 if (key == Qt::Key_Control || event->modifiers().testFlag(Qt::ControlModifier)) {
363 m_currentModifiers.setFlag(Qt::ControlModifier);
364 }
365 if (key == Qt::Key_Shift || event->modifiers().testFlag(Qt::ShiftModifier)) {
366 m_currentModifiers.setFlag(Qt::ShiftModifier);
367 }
368 if (key == Qt::Key_Alt || event->modifiers().testFlag(Qt::AltModifier)) {
369 m_currentModifiers.setFlag(Qt::AltModifier);
370 }
371
372 // Avoid changing the cursor if the user is interacting
373 if (isSelecting()) {
374 BaseClass::keyPressEvent(event);
375 return;
376 }
377 if (isMovingSelection()) {
378 return;
379 }
380
382 updateCursor();
383 }
384
385 void keyReleaseEvent(QKeyEvent *event) override
386 {
388 // Assume all the modifiers were pressed...
389 m_currentModifiers = Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier;
390 // ...and remove those which aren't right now
391 if (key == Qt::Key_Control || !event->modifiers().testFlag(Qt::ControlModifier)) {
392 m_currentModifiers.setFlag(Qt::ControlModifier, false);
393 }
394 if (key == Qt::Key_Shift || !event->modifiers().testFlag(Qt::ShiftModifier)) {
395 m_currentModifiers.setFlag(Qt::ShiftModifier, false);
396 }
397 if (key == Qt::Key_Alt || !event->modifiers().testFlag(Qt::AltModifier)) {
398 m_currentModifiers.setFlag(Qt::AltModifier, false);
399 }
400
401 // Avoid changing the selection mode and cursor if the user is interacting
402 if (isSelecting()) {
403 BaseClass::keyReleaseEvent(event);
404 return;
405 }
406 if (isMovingSelection()) {
407 return;
408 }
409
411 if (m_currentModifiers == Qt::NoModifier) {
412 updateCursor();
413 }
414 else {
415 this->resetCursorStyle();
416 }
417 }
418
419 void mouseMoveEvent(KoPointerEvent *event) override
420 {
421 m_currentPos = this->convertToPixelCoord(event->point);
422 m_currentModifiers = event->modifiers();
423
424 updateCursor();
425 BaseClass::mouseMoveEvent(event);
426 }
427
428 CursorHit checkCursorHit(const QPointF &pos, Qt::KeyboardModifiers modifiers) const
429 {
430 KisCanvas2 *canvas = dynamic_cast<KisCanvas2*>(this->canvas());
432 KisSelectionSP selection = canvas->viewManager()->selection();
433
434 if (!selection || !selection->outlineCacheValid()) {
435 return CursorHit_Outside;
436 }
437
438 const QPainterPath selectionPath = selection->outlineCache();
439
440 if (modifiers == Qt::NoModifier) {
441 const qreal handleRadius = qreal(this->handleRadius()) / canvas->coordinatesConverter()->effectiveZoom();
442
443 QPainterPath samplePath;
444 samplePath.addEllipse(pos, handleRadius, handleRadius);
445
446 if (selectionPath.intersects(samplePath) && !selectionPath.contains(samplePath)) {
447 return CursorHit_Border;
448 }
449 }
450
451 if (selectionPath.contains(pos)) {
452 return CursorHit_Inside;
453 }
454
455 return CursorHit_Outside;
456 }
457
458 inline bool canBeginNewAction(KoPointerEvent *event, const QPointF &pos, CursorHit hit)
459 {
460 /* Prevent interrupting while selecting a region */
461 if (isSelecting()) {
462 BaseClass::beginPrimaryAction(event);
463 return false;
464 }
465
466 /* Prevent interrupting while moving */
467 if (isMovingContent()) {
468 /* We must update the offsets here, so the offset is sane later */
469 m_dragStartPos = pos;
471 /* User clicked outside? Commit changes and start a new stroke */
472 /* This eliminates extra keystrokes to start a new transaction */
473 if (hit == CursorHit_Outside) {
475 BaseClass::beginPrimaryAction(event);
476 return false;
477 }
478 }
479
480 return true;
481 }
482
483 void beginPrimaryAction(KoPointerEvent *event) override
484 {
485 const QPointF pos = this->convertToPixelCoord(event->point);
486 const CursorHit hit = checkCursorHit(pos, event->modifiers());
487
488 if (!canBeginNewAction(event, pos, hit)) {
489 return;
490 }
491
492 KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas());
494
495 if (hit == CursorHit_Inside && this->moveSelectedContent()) {
496 KisSelectionSP selection = canvas->viewManager()->selection();
497 KisPaintLayerSP layer = dynamic_cast<KisPaintLayer*>(this->currentNode().data());
498 if (this->beginMoveContentInteraction() && selection && layer) {
499 KisStrokeStrategy *strategy =
500 new MoveSelectionStrokeStrategy(layer, selection, this->image().data(), this->image().data());
501 initializeStrokeAttributes(pos, strategy, true);
502 updateCursor();
503 }
504 return;
505 }
506
508 (hit == CursorHit_Inside || hit == CursorHit_Border)) {
509 // we shouldn't pass the control to the parent tool
510 // when we have the already started the move content action
511 return;
512 }
513
514 KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers());
515 if (selectionMask) {
516 if (this->beginMoveSelectionInteraction()) {
517 KisStrokeStrategy *strategy =
518 new MoveStrokeStrategy({selectionMask}, this->image().data(), this->image().data());
519 initializeStrokeAttributes(pos, strategy, true);
520 updateCursor();
521 return;
522 }
523 }
524
525 m_didMove = false;
526 BaseClass::beginPrimaryAction(event);
527 }
528
530 {
532 const QPointF pos = this->convertToPixelCoord(event->point);
533 const QPoint delta = (pos - m_dragStartPos).toPoint();
534 const QPoint offset = m_dragStartOffset + delta;
535 m_accumulatedOffset = offset;
536 this->image()->addJob(m_moveStrokeId, new MoveStrokeStrategy::Data(offset));
537 return;
538 }
539
540 BaseClass::continuePrimaryAction(event);
541 }
542
543 void endPrimaryAction(KoPointerEvent *event) override
544 {
545 if (isMovingContent()) {
546 const QPointF pos = this->convertToPixelCoord(event->point);
547 const QPoint delta = (pos - m_dragStartPos).toPoint();
549 return;
550 }
551 if (isMovingSelection()) {
552 this->image()->endStroke(m_moveStrokeId);
553 m_moveStrokeId.clear();
555 return;
556 }
557
558 BaseClass::endPrimaryAction(event);
559 }
560
561 bool selectionDidMove() const
562 {
563 return m_didMove;
564 }
565
566 QMenu *popupActionsMenu() override
567 {
568 if (isSelecting()) {
569 return BaseClass::popupActionsMenu();
570 }
571
572 KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
574
576 }
577
579 {
580 if (isSelecting()) {
581 return BaseClass::popupWidget();
582 }
583 return nullptr;
584 }
585
586 inline void initializeStrokeAttributes(const QPointF &pos, KisStrokeStrategy *strategy, bool moved) {
587 m_moveStrokeId = this->image()->startStroke(strategy);
588 m_dragStartPos = pos;
589 m_didMove = moved;
590 m_accumulatedOffset = QPoint();
591 m_dragStartOffset = QPoint();
592 }
593
596 return false;
597 }
599 return true;
600 }
601
603 if (!isMovingSelection()) {
604 return false;
605 }
608 return true;
609 }
610
613 return false;
614 }
616 return true;
617 }
618
620 if (!isMovingContent()) {
621 return false;
622 }
625 return true;
626 }
627
630 return false;
631 }
633 return true;
634 }
635
637 if (!isSelecting()) {
638 return false;
639 }
642 return true;
643 }
644
648
652
653 bool isSelecting() const {
655 }
656
658 {
659
660 const Interaction interaction = currentInteraction();
662
663 switch (interaction)
664 {
666 switch (hit)
667 {
668 case CursorHit_Border:
669 this->useCursor(KisCursor::moveCursor());
670 break;
671 case CursorHit_Inside:
672 this->useCursor(KisCursor::moveCursor());
673 break;
675 this->resetCursorStyle();
676 break;
677 case CursorHit_None:
678 default:
679 break;
680 }
681 break;
683 switch (hit)
684 {
685 case CursorHit_Border:
686 this->useCursor(KisCursor::moveSelectionCursor());
687 break;
688 case CursorHit_Inside:
689 this->resetCursorStyle();
690 break;
692 this->resetCursorStyle();
693 break;
694 case CursorHit_None:
695 default:
696 break;
697 }
698 break;
700 this->useCursor(KisCursor::moveCursor());
701 break;
702 case Interaction_None:
703 if (hit == CursorHit_Border){
704 this->useCursor(KisCursor::moveSelectionCursor());
705 }
706 else if (hit == CursorHit_Inside && this->moveSelectedContent()) {
707 this->useCursor(KisCursor::moveCursor());
708 } else {
709 this->resetCursorStyle();
710 }
711 break;
712 default:
713 this->resetCursorStyle();
714 break;
715 }
716 }
717
720 QTimer::singleShot(100, Qt::CoarseTimer,
721 this,
722 [this]()
723 {
724 updateCursor();
725 }
726 );
727 }
728
729protected:
730 using BaseClass::canvas;
733
734 virtual bool isPixelOnly() const {
735 return false;
736 }
737
738 virtual bool usesColorLabels() const {
739 return false;
740 }
741
742private:
750
752
756
757 Qt::KeyboardModifiers m_currentModifiers;
758
764 bool m_didMove = false;
765
767};
768
770{
772 : KisTool(canvas, QCursor())
773 {
774 }
775
776 FakeBaseTool(KoCanvasBase* canvas, const QString &toolName)
777 : KisTool(canvas, QCursor())
778 {
779 Q_UNUSED(toolName);
780 }
781
784 {
785 }
786};
787
788
790
791
792#endif // KISTOOLSELECTBASE_H
SelectionMode
SelectionAction
@ SELECTION_REPLACE
@ SELECTION_INTERSECT
@ SELECTION_DEFAULT
@ SELECTION_SUBTRACT
@ SELECTION_ADD
KisCoordinatesConverter * coordinatesConverter
KisViewManager * viewManager() const
static QCursor moveSelectionCursor()
static QCursor moveCursor()
static Qt::Key workaroundShiftAltMetaHell(const QKeyEvent *keyEvent)
The PopupWidgetInterface abstract class defines the basic interface that will be used by all popup wi...
void updateActionButtonToolTip(SelectionAction action, const QKeySequence &shortcut)
void setModeSectionVisible(bool visible)
void setAdjustmentsSectionVisible(bool visible)
void setReferenceSectionVisible(bool visible)
KisSelectionOptions::ReferenceLayers referenceLayers() const
static QMenu * getSelectionContextMenu(KisCanvas2 *canvas)
void addUniqueConnection(Sender sender, Signal signal, Receiver receiver, Method method)
void initializeStrokeAttributes(const QPointF &pos, KisStrokeStrategy *strategy, bool moved)
SelectionAction alternateSelectionAction() const
KisSelectionOptions * selectionOptionWidget()
SampleLayersMode sampleLayersMode() const
KisToolSelectBase(KoCanvasBase *canvas, const QString toolName)
void keyReleaseEvent(QKeyEvent *event) override
void requestStrokeCancellation() override
Interaction currentInteraction() const
void mouseMoveEvent(KoPointerEvent *event) override
SelectionAction selectionAction() const
void beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) override
void beginPrimaryAction(KoPointerEvent *event) override
void endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) override
virtual void setAlternateSelectionAction(SelectionAction action)
void explicitUserStrokeEndRequest() override
QList< int > colorLabelsSelected() const
virtual bool isPixelOnly() const
KisToolSelectBase(KoCanvasBase *canvas, const QCursor cursor, const QString toolName)
void commitMoveSelectionStrokeImpl(bool cancel)
void activate(const QSet< KoShape * > &shapes) override
virtual bool usesColorLabels() const
KisSelectionToolConfigWidgetHelper m_widgetHelper
void deactivateAlternateAction(KisTool::AlternateAction action) override
KisSignalAutoConnectionsStore m_modeConnections
SelectionMode selectionMode() const
CursorHit checkCursorHit(const QPointF &pos, Qt::KeyboardModifiers modifiers) const
void keyPressEvent(QKeyEvent *event) override
void continuePrimaryAction(KoPointerEvent *event) override
void activateAlternateAction(KisTool::AlternateAction action) override
KisNodeSP locateSelectionMaskUnderCursor(const QPointF &pos, Qt::KeyboardModifiers modifiers)
void endPrimaryAction(KoPointerEvent *event) override
void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) override
QMenu * popupActionsMenu() override
bool stopGrowingAtDarkestPixel() const
void deactivate() override
bool moveSelectedContent() const
bool canBeginNewAction(KoPointerEvent *event, const QPointF &pos, CursorHit hit)
QWidget * createOptionWidget() override
bool antiAliasSelection() const
SelectionAction m_selectionActionAlternate
Qt::KeyboardModifiers m_currentModifiers
KisPopupWidgetInterface * popupWidget() override
KisToolSelectBase(KoCanvasBase *canvas, QCursor cursor, QString toolName, KoToolBase *delegateTool)
KisSelectionSP selection()
Qt::KeyboardModifiers modifiers() const
QPointF point
The point in document coordinates.
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
KisToolSelectBase< FakeBaseTool > KisToolSelect
FakeBaseTool(KoCanvasBase *canvas)
FakeBaseTool(KoCanvasBase *canvas, const QString &toolName)
FakeBaseTool(KoCanvasBase *canvas, const QCursor &cursor)
static KisSelectionModifierMapper * instance()
SelectionAction map(Qt::KeyboardModifiers m)
bool outlineCacheValid() const
KisNodeWSP parentNode
QPainterPath outlineCache() const
QCursor cursor
Definition kis_tool.cc:64
AlternateAction
Definition kis_tool.h:134
KisCanvas2 * canvas