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 }