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 }