File indexing completed on 2024-12-22 04:12:59

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_selection_tool_helper.h"
0008 
0009 
0010 #include <kundo2command.h>
0011 #include <kactioncollection.h>
0012 
0013 #include <KoShapeController.h>
0014 #include <KoPathShape.h>
0015 
0016 #include "kis_pixel_selection.h"
0017 #include "kis_shape_selection.h"
0018 #include "kis_image.h"
0019 #include "canvas/kis_canvas2.h"
0020 #include "KisViewManager.h"
0021 #include "kis_selection_manager.h"
0022 #include "kis_transaction.h"
0023 #include "commands/kis_selection_commands.h"
0024 #include "kis_shape_controller.h"
0025 
0026 #include <kis_icon.h>
0027 #include "kis_processing_applicator.h"
0028 #include "commands_new/kis_transaction_based_command.h"
0029 #include "kis_gui_context_command.h"
0030 #include "kis_command_utils.h"
0031 #include "commands/kis_deselect_global_selection_command.h"
0032 
0033 #include "kis_algebra_2d.h"
0034 #include "kis_config.h"
0035 #include "kis_action_manager.h"
0036 #include "kis_action.h"
0037 #include <QMenu>
0038 
0039 
0040 KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name)
0041         : m_canvas(canvas)
0042         , m_name(name)
0043 {
0044     m_image = m_canvas->viewManager()->image();
0045 }
0046 
0047 KisSelectionToolHelper::~KisSelectionToolHelper()
0048 {
0049 }
0050 
0051 struct LazyInitGlobalSelection : public KisTransactionBasedCommand {
0052     LazyInitGlobalSelection(KisView *view) : m_view(view) {}
0053     KisView *m_view;
0054 
0055     KUndo2Command* paint() override {
0056         return !m_view->selection() ?
0057             new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0;
0058     }
0059 };
0060 
0061 
0062 void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action)
0063 {
0064     KisView* view = m_canvas->imageView();
0065     KisProcessingApplicator applicator(view->image(),
0066                                        0 /* we need no automatic updates */,
0067                                        KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE,
0068                                        KisImageSignalVector(),
0069                                        m_name);
0070 
0071     selectPixelSelection(applicator, selection, action);
0072 
0073     applicator.end();
0074 
0075 }
0076 
0077 void KisSelectionToolHelper::selectPixelSelection(KisProcessingApplicator& applicator, KisPixelSelectionSP selection, SelectionAction action)
0078 {
0079 
0080     KisView* view = m_canvas->imageView();
0081 
0082     QPointer<KisCanvas2> canvas = m_canvas;
0083 
0084     applicator.applyCommand(new LazyInitGlobalSelection(view), KisStrokeJobData::SEQUENTIAL);
0085 
0086     struct ApplyToPixelSelection : public KisTransactionBasedCommand {
0087         ApplyToPixelSelection(KisView *view,
0088                               KisPixelSelectionSP selection,
0089                               SelectionAction action,
0090                               QPointer<KisCanvas2> canvas) : m_view(view),
0091                                                             m_selection(selection),
0092                                                             m_action(action),
0093                                                             m_canvas(canvas) {}
0094         KisView *m_view;
0095         KisPixelSelectionSP m_selection;
0096         SelectionAction m_action;
0097         QPointer<KisCanvas2> m_canvas;
0098 
0099         KUndo2Command* paint() override {
0100 
0101             KUndo2Command *savedCommand = 0;
0102             if (!m_selection->selectedExactRect().isEmpty()) {
0103 
0104                 KisSelectionSP selection = m_view->selection();
0105                 KIS_SAFE_ASSERT_RECOVER(selection) { return 0; }
0106 
0107                 KisPixelSelectionSP pixelSelection = selection->pixelSelection();
0108                 KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; }
0109 
0110                 bool hasSelection = !pixelSelection->isEmpty();
0111 
0112                 KisSelectionTransaction transaction(pixelSelection);
0113 
0114                 if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) {
0115                     m_action = SELECTION_REPLACE;
0116                 }
0117 
0118                 if (!hasSelection && m_action == SELECTION_SUBTRACT) {
0119                     pixelSelection->invert();
0120                 }
0121 
0122                 pixelSelection->applySelection(m_selection, m_action);
0123 
0124                 QRect dirtyRect = m_view->image()->bounds();
0125                 if (hasSelection &&
0126                     m_action != SELECTION_REPLACE &&
0127                     m_action != SELECTION_INTERSECT &&
0128                     m_action != SELECTION_SYMMETRICDIFFERENCE) {
0129 
0130                     dirtyRect = m_selection->selectedRect();
0131                 }
0132                 m_view->selection()->updateProjection(dirtyRect);
0133 
0134                 savedCommand = transaction.endAndTake();
0135                 pixelSelection->setDirty(dirtyRect);
0136 
0137                 // release resources: transaction will care about
0138                 // undo/redo, we don't need the selection anymore
0139                 m_selection.clear();
0140             }
0141 
0142             if (m_view->selection()->selectedExactRect().isEmpty()) {
0143                 KUndo2Command *deselectCommand = new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image());
0144                 if (savedCommand) {
0145                     KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
0146                     cmd->addCommand(savedCommand);
0147                     cmd->addCommand(deselectCommand);
0148                     savedCommand = cmd;
0149                 } else {
0150                     savedCommand = deselectCommand;
0151                 }
0152             }
0153 
0154             return savedCommand;
0155         }
0156     };
0157 
0158     applicator.applyCommand(new ApplyToPixelSelection(view, selection, action, canvas), KisStrokeJobData::SEQUENTIAL);
0159 
0160 }
0161 
0162 void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action)
0163 {
0164     QList<KoShape*> shapes;
0165     shapes.append(shape);
0166     addSelectionShapes(shapes, action);
0167 }
0168 #include "krita_utils.h"
0169 void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action)
0170 {
0171     KisView *view = m_canvas->imageView();
0172 
0173     if (view->image()->wrapAroundModePermitted()) {
0174         view->showFloatingMessage(
0175             i18n("Shape selection does not fully "
0176                  "support wraparound mode. Please "
0177                  "use pixel selection instead"),
0178                  KisIconUtils::loadIcon("selection-info"));
0179     }
0180 
0181     KisProcessingApplicator applicator(view->image(),
0182                                        0 /* we need no automatic updates */,
0183                                        KisProcessingApplicator::NONE,
0184                                        KisImageSignalVector(),
0185                                        m_name);
0186 
0187     applicator.applyCommand(new LazyInitGlobalSelection(view));
0188 
0189     struct ClearPixelSelection : public KisTransactionBasedCommand {
0190         ClearPixelSelection(KisView *view) : m_view(view) {}
0191         KisView *m_view;
0192 
0193         KUndo2Command* paint() override {
0194 
0195             KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
0196             KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
0197 
0198             KisSelectionTransaction transaction(pixelSelection);
0199             pixelSelection->clear();
0200             return transaction.endAndTake();
0201         }
0202     };
0203 
0204     if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) {
0205         applicator.applyCommand(new ClearPixelSelection(view));
0206     }
0207 
0208     struct AddSelectionShape : public KisTransactionBasedCommand {
0209         AddSelectionShape(KisView *view, QList<KoShape*> shapes, SelectionAction action)
0210             : m_view(view),
0211               m_shapes(shapes),
0212               m_action(action) {}
0213 
0214         KisView *m_view;
0215         QList<KoShape*> m_shapes;
0216         SelectionAction m_action;
0217 
0218         KUndo2Command* paint() override {
0219             KUndo2Command *resultCommand = 0;
0220 
0221 
0222             KisSelectionSP selection = m_view->selection();
0223             if (selection) {
0224                 KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
0225 
0226                 if (shapeSelection) {
0227                     QList<KoShape*> existingShapes = shapeSelection->shapes();
0228 
0229                     QPainterPath path1;
0230                     path1.setFillRule(Qt::WindingFill);
0231                     Q_FOREACH(KoShape *shape, existingShapes) {
0232                         path1 += shape->absoluteTransformation().map(shape->outline());
0233                     }
0234 
0235                     QPainterPath path2;
0236                     path2.setFillRule(Qt::WindingFill);
0237                     Q_FOREACH(KoShape *shape, m_shapes) {
0238                         path2 += shape->absoluteTransformation().map(shape->outline());
0239                     }
0240 
0241                     const QTransform booleanWorkaroundTransform =
0242                         KritaUtils::pathShapeBooleanSpaceWorkaround(m_view->image());
0243 
0244                     path1 = booleanWorkaroundTransform.map(path1);
0245                     path2 = booleanWorkaroundTransform.map(path2);
0246 
0247                     QPainterPath path = path2;
0248 
0249                     switch (m_action) {
0250                     case SELECTION_DEFAULT:
0251                     case SELECTION_REPLACE:
0252                         path = path2;
0253                         break;
0254 
0255                     case SELECTION_INTERSECT:
0256                         path = path1 & path2;
0257                         break;
0258 
0259                     case SELECTION_ADD:
0260                         path = path1 | path2;
0261                         break;
0262 
0263                     case SELECTION_SUBTRACT:
0264                         path = path1 - path2;
0265                         break;
0266                     case SELECTION_SYMMETRICDIFFERENCE:
0267                         path = (path1 | path2) - (path1 & path2);
0268                         break;
0269                     }
0270 
0271                     path = booleanWorkaroundTransform.inverted().map(path);
0272 
0273                     KoShape *newShape = KoPathShape::createShapeFromPainterPath(path);
0274                     newShape->setUserData(new KisShapeSelectionMarker);
0275 
0276                     KUndo2Command *parentCommand = new KUndo2Command();
0277 
0278                     m_view->canvasBase()->shapeController()->removeShapes(existingShapes, parentCommand);
0279                     m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand);
0280 
0281                     if (path.isEmpty()) {
0282                         KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
0283                         cmd->addCommand(parentCommand);
0284                         cmd->addCommand(new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image()));
0285                         parentCommand = cmd;
0286                     }
0287 
0288                     resultCommand = parentCommand;
0289                 }
0290             }
0291 
0292 
0293             if (!resultCommand) {
0294                 /**
0295                  * Mark the shapes that they belong to a shape selection
0296                  */
0297                 Q_FOREACH(KoShape *shape, m_shapes) {
0298                     if(!shape->userData()) {
0299                         shape->setUserData(new KisShapeSelectionMarker);
0300                     }
0301                 }
0302 
0303                 resultCommand = m_view->canvasBase()->shapeController()->addShapesDirect(m_shapes, 0);
0304             }
0305             return resultCommand;
0306         }
0307     };
0308 
0309     applicator.applyCommand(
0310         new KisGuiContextCommand(new AddSelectionShape(view, shapes, action), view));
0311     applicator.end();
0312 }
0313 
0314 bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action)
0315 {
0316     return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE);
0317 }
0318 
0319 bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action)
0320 {
0321     return rect.isEmpty() && action == SELECTION_ADD;
0322 }
0323 
0324 bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
0325 {
0326     bool result = false;
0327 
0328     if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() &&
0329         (action == SELECTION_INTERSECT || action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) {
0330 
0331         // Queueing this action to ensure we avoid a race condition when unlocking the node system
0332         QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect()));
0333         result = true;
0334     }
0335 
0336     return result;
0337 }
0338 
0339 
0340 QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas)
0341 {
0342     QMenu *m_contextMenu = new QMenu();
0343 
0344     KisKActionCollection *actionCollection = canvas->viewManager()->actionCollection();
0345 
0346     m_contextMenu->addSection(i18n("Selection Actions"));
0347     m_contextMenu->addSeparator();
0348 
0349     m_contextMenu->addAction(actionCollection->action("select_all"));
0350     m_contextMenu->addAction(actionCollection->action("deselect"));
0351     m_contextMenu->addAction(actionCollection->action("reselect"));
0352     m_contextMenu->addAction(actionCollection->action("invert_selection"));
0353 
0354 
0355     m_contextMenu->addSeparator();
0356 
0357     m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer"));
0358     m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer"));
0359 
0360     m_contextMenu->addSeparator();
0361 
0362     KisSelectionSP selection = canvas->viewManager()->selection();
0363     if (selection && canvas->viewManager()->selectionEditable()) {
0364         m_contextMenu->addAction(actionCollection->action("edit_selection"));
0365 
0366         if (!selection->hasShapeSelection()) {
0367             m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection"));
0368         } else {
0369             m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection"));
0370         }
0371 
0372         m_contextMenu->addAction(actionCollection->action("convert_selection_to_shape"));
0373 
0374         QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform"));
0375         transformMenu->addAction(actionCollection->action("KisToolTransform"));
0376         transformMenu->addAction(actionCollection->action("selectionscale"));
0377         transformMenu->addAction(actionCollection->action("growselection"));
0378         transformMenu->addAction(actionCollection->action("shrinkselection"));
0379         transformMenu->addAction(actionCollection->action("borderselection"));
0380         transformMenu->addAction(actionCollection->action("smoothselection"));
0381         transformMenu->addAction(actionCollection->action("featherselection"));
0382         transformMenu->addAction(actionCollection->action("stroke_selection"));
0383 
0384         m_contextMenu->addSeparator();
0385     }
0386 
0387     m_contextMenu->addAction(actionCollection->action("resizeimagetoselection"));
0388 
0389     m_contextMenu->addSeparator();
0390 
0391     m_contextMenu->addAction(actionCollection->action("toggle_display_selection"));
0392     m_contextMenu->addAction(actionCollection->action("show-global-selection-mask"));
0393 
0394     return m_contextMenu;
0395 }
0396 
0397 SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
0398 {
0399     if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) {
0400         if (activeSelection) {
0401             currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION;
0402         }
0403     }
0404 
0405     return currentMode;
0406 }