File indexing completed on 2024-12-22 04:13:01
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include <QList> 0008 0009 #include <QAction> 0010 0011 #include <KoPointerEvent.h> 0012 #include <KoCanvasBase.h> 0013 #include <KoCanvasController.h> 0014 #include <KoViewConverter.h> 0015 #include <input/kis_input_manager.h> 0016 0017 #include "kis_tool_polyline_base.h" 0018 #include "kis_canvas2.h" 0019 #include <kis_canvas_resource_provider.h> 0020 #include <KisViewManager.h> 0021 #include <kis_action.h> 0022 #include <kactioncollection.h> 0023 #include <kis_icon.h> 0024 0025 #include "kis_action_registry.h" 0026 0027 #define SNAPPING_THRESHOLD 10 0028 #define SNAPPING_HANDLE_RADIUS 8 0029 #define PREVIEW_LINE_WIDTH 1 0030 0031 KisToolPolylineBase::KisToolPolylineBase(KoCanvasBase * canvas, KisToolPolylineBase::ToolType type, const QCursor & cursor) 0032 : KisToolShape(canvas, cursor), 0033 m_dragging(false), 0034 m_type(type), 0035 m_closeSnappingActivated(false) 0036 { 0037 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas); 0038 0039 connect(kritaCanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(resetCursorStyle())); 0040 } 0041 0042 0043 void KisToolPolylineBase::activate(const QSet<KoShape *> &shapes) 0044 { 0045 KisToolShape::activate(shapes); 0046 connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoSelectionOrCancel()), Qt::UniqueConnection); 0047 0048 KisInputManager *inputManager = (static_cast<KisCanvas2*>(canvas()))->globalInputManager(); 0049 if (inputManager) { 0050 inputManager->attachPriorityEventFilter(this); 0051 } 0052 } 0053 0054 void KisToolPolylineBase::deactivate() 0055 { 0056 disconnect(action("undo_polygon_selection"), 0, this, 0); 0057 cancelStroke(); 0058 0059 KisInputManager *inputManager = (static_cast<KisCanvas2*>(canvas()))->globalInputManager(); 0060 if (inputManager) { 0061 inputManager->detachPriorityEventFilter(this); 0062 } 0063 0064 KisToolShape::deactivate(); 0065 } 0066 0067 void KisToolPolylineBase::requestStrokeEnd() 0068 { 0069 endStroke(); 0070 } 0071 0072 void KisToolPolylineBase::requestStrokeCancellation() 0073 { 0074 cancelStroke(); 0075 } 0076 0077 KisPopupWidgetInterface* KisToolPolylineBase::popupWidget() 0078 { 0079 return m_dragging || m_type == SELECT ? nullptr : KisToolShape::popupWidget(); 0080 } 0081 0082 // Install an event filter to catch right-click events. 0083 // The simplest way to accommodate the popup palette binding. 0084 bool KisToolPolylineBase::eventFilter(QObject *obj, QEvent *event) 0085 { 0086 Q_UNUSED(obj); 0087 0088 if (!m_dragging) { 0089 return false; 0090 } 0091 if (event->type() == QEvent::MouseButtonPress || 0092 event->type() == QEvent::MouseButtonDblClick) { 0093 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); 0094 if (mouseEvent->button() == Qt::RightButton) { 0095 undoSelectionOrCancel(); 0096 return true; 0097 } 0098 } else if (event->type() == QEvent::TabletPress) { 0099 QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event); 0100 if (tabletEvent->button() == Qt::RightButton) { 0101 undoSelectionOrCancel(); 0102 return true; 0103 } 0104 } 0105 return false; 0106 } 0107 0108 void KisToolPolylineBase::beginPrimaryAction(KoPointerEvent *event) 0109 { 0110 Q_UNUSED(event); 0111 NodePaintAbility paintability = nodePaintAbility(); 0112 if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability == KisToolPaint::CLONE || paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE)) || 0113 (m_type == SELECT && !selectionEditable())) { 0114 0115 if (paintability == KisToolPaint::CLONE){ 0116 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); 0117 QString message = i18n("This tool cannot paint on clone layers. Please select a paint or vector layer or mask."); 0118 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); 0119 } 0120 0121 if (paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE) { 0122 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); 0123 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace"); 0124 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); 0125 } 0126 0127 event->ignore(); 0128 return; 0129 } 0130 0131 setMode(KisTool::PAINT_MODE); 0132 0133 if(m_dragging && m_closeSnappingActivated) { 0134 m_points.append(m_points.first()); 0135 endStroke(); 0136 } else { 0137 beginShape(); 0138 m_dragging = true; 0139 } 0140 } 0141 0142 void KisToolPolylineBase::endPrimaryAction(KoPointerEvent *event) 0143 { 0144 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0145 setMode(KisTool::HOVER_MODE); 0146 0147 if(m_dragging) { 0148 m_dragStart = convertToPixelCoordAndSnap(event); 0149 m_dragEnd = m_dragStart; 0150 m_points.append(m_dragStart); 0151 } 0152 } 0153 0154 void KisToolPolylineBase::beginPrimaryDoubleClickAction(KoPointerEvent *event) 0155 { 0156 endStroke(); 0157 0158 // this action will have no continuation 0159 event->ignore(); 0160 } 0161 0162 void KisToolPolylineBase::beginAlternateAction(KoPointerEvent *event, AlternateAction action) 0163 { 0164 if ((action != ChangeSize && action != ChangeSizeSnap) || !m_dragging) { 0165 KisToolPaint::beginAlternateAction(event, action); 0166 } 0167 0168 if (m_closeSnappingActivated) { 0169 m_points.append(m_points.first()); 0170 } 0171 endStroke(); 0172 } 0173 0174 void KisToolPolylineBase::mouseMoveEvent(KoPointerEvent *event) 0175 { 0176 if (m_dragging && !m_points.empty()) { 0177 // erase old lines on canvas 0178 QRectF updateRect = dragBoundingRect(); 0179 // get current mouse position 0180 m_dragEnd = convertToPixelCoordAndSnap(event); 0181 // draw new lines on canvas 0182 updateRect |= dragBoundingRect(); 0183 updateCanvasViewRect(updateRect); 0184 0185 0186 QPointF basePoint = pixelToView(m_points.first()); 0187 m_closeSnappingActivated = 0188 m_points.size() > 1 && 0189 (basePoint - pixelToView(m_dragEnd)).manhattanLength() < SNAPPING_THRESHOLD; 0190 0191 updateCanvasViewRect(QRectF(basePoint, 2 * QSize(SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH, SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH)).translated(-SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH,-SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH)); 0192 KisToolPaint::requestUpdateOutline(event->point, event); 0193 } else { 0194 KisToolPaint::mouseMoveEvent(event); 0195 } 0196 } 0197 0198 void KisToolPolylineBase::undoSelection() 0199 { 0200 if (m_dragging) { 0201 // Initialize with the dragging segment's rect 0202 QRectF updateRect = dragBoundingRect(); 0203 0204 if (m_points.size() > 1) { 0205 // Add the rect for the last segment 0206 const QRectF lastSegmentRect = 0207 pixelToView(QRectF(m_points.last(), m_points.at(m_points.size() - 2)).normalized()) 0208 .adjusted(-PREVIEW_LINE_WIDTH, -PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH); 0209 updateRect = updateRect.united(lastSegmentRect); 0210 0211 m_points.pop_back(); 0212 } 0213 m_dragStart = m_points.last(); 0214 0215 // Add the new dragging segment's rect 0216 updateRect = updateRect.united(dragBoundingRect()); 0217 updateCanvasViewRect(updateRect); 0218 } 0219 } 0220 0221 void KisToolPolylineBase::undoSelectionOrCancel() 0222 { 0223 if (m_points.size() > 1) { 0224 undoSelection(); 0225 } else { 0226 cancelStroke(); 0227 } 0228 } 0229 0230 void KisToolPolylineBase::paint(QPainter& gc, const KoViewConverter &converter) 0231 { 0232 Q_UNUSED(converter); 0233 0234 if (!canvas() || !currentImage()) 0235 return; 0236 0237 QPointF start, end; 0238 QPointF startPos; 0239 QPointF endPos; 0240 0241 QPainterPath path; 0242 if (m_dragging && !m_points.empty()) { 0243 startPos = pixelToView(m_dragStart); 0244 endPos = pixelToView(m_dragEnd); 0245 path.moveTo(startPos); 0246 path.lineTo(endPos); 0247 } 0248 0249 for (vQPointF::iterator it = m_points.begin(); it != m_points.end(); ++it) { 0250 0251 if (it == m_points.begin()) { 0252 start = (*it); 0253 } else { 0254 end = (*it); 0255 0256 startPos = pixelToView(start); 0257 endPos = pixelToView(end); 0258 path.moveTo(startPos); 0259 path.lineTo(endPos); 0260 start = end; 0261 } 0262 } 0263 0264 if (m_closeSnappingActivated) { 0265 QPointF basePoint = pixelToView(m_points.first()); 0266 path.addEllipse(basePoint, SNAPPING_HANDLE_RADIUS, SNAPPING_HANDLE_RADIUS); 0267 } 0268 0269 paintToolOutline(&gc, path); 0270 KisToolPaint::paint(gc,converter); 0271 } 0272 0273 void KisToolPolylineBase::updateArea() 0274 { 0275 updateCanvasPixelRect(image()->bounds()); 0276 } 0277 0278 void KisToolPolylineBase::endStroke() 0279 { 0280 if (!m_dragging) return; 0281 0282 m_dragging = false; 0283 if(m_points.count() > 1) { 0284 finishPolyline(m_points); 0285 } 0286 m_points.clear(); 0287 m_closeSnappingActivated = false; 0288 updateArea(); 0289 endShape(); 0290 } 0291 0292 void KisToolPolylineBase::cancelStroke() 0293 { 0294 if (!m_dragging) return; 0295 0296 m_dragging = false; 0297 m_points.clear(); 0298 m_closeSnappingActivated = false; 0299 updateArea(); 0300 endShape(); 0301 } 0302 0303 QRectF KisToolPolylineBase::dragBoundingRect() 0304 { 0305 QRectF rect = pixelToView(QRectF(m_dragStart, m_dragEnd).normalized()); 0306 rect.adjust(-PREVIEW_LINE_WIDTH, -PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH); 0307 return rect; 0308 }