File indexing completed on 2024-06-23 04:28:11

0001 /* This file is part of the KDE project
0002 
0003    SPDX-FileCopyrightText: 2006 Thorsten Zachmann <zachmann@kde.org>
0004    SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org>
0005 
0006    SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "SelectionDecorator.h"
0010 
0011 #include <QPainterPath>
0012 
0013 #include <KoShape.h>
0014 #include <KoSelection.h>
0015 #include <KoResourcePaths.h>
0016 #include "kis_algebra_2d.h"
0017 
0018 #include "kis_debug.h"
0019 #include <KisHandlePainterHelper.h>
0020 #include <KoCanvasResourceProvider.h>
0021 #include <KisQPainterStateSaver.h>
0022 #include "KoShapeGradientHandles.h"
0023 #include <KoCanvasBase.h>
0024 #include <KoSvgTextShape.h>
0025 
0026 #include "kis_painting_tweaks.h"
0027 #include "kis_coordinates_converter.h"
0028 #include "kis_icon_utils.h"
0029 
0030 
0031 
0032 #define HANDLE_DISTANCE 10
0033 
0034 SelectionDecorator::SelectionDecorator(KoCanvasResourceProvider *resourceManager)
0035     : m_hotPosition(KoFlake::Center)
0036     , m_handleRadius(7)
0037     , m_decorationThickness(1)
0038     , m_showFillGradientHandles(false)
0039     , m_showStrokeFillGradientHandles(false)
0040     , m_forceShapeOutlines(false)
0041 {
0042     m_hotPosition =
0043         KoFlake::AnchorPosition(
0044             resourceManager->resource(KoFlake::HotPosition).toInt());
0045 }
0046 
0047 void SelectionDecorator::setSelection(KoSelection *selection)
0048 {
0049     m_selection = selection;
0050 }
0051 
0052 void SelectionDecorator::setHandleRadius(int radius)
0053 {
0054     m_handleRadius = radius;
0055 }
0056 
0057 void SelectionDecorator::setDecorationThickness(int thickness)
0058 {
0059     m_decorationThickness = thickness;
0060 }
0061 
0062 void SelectionDecorator::setShowFillGradientHandles(bool value)
0063 {
0064     m_showFillGradientHandles = value;
0065 }
0066 
0067 void SelectionDecorator::setShowStrokeFillGradientHandles(bool value)
0068 {
0069     m_showStrokeFillGradientHandles = value;
0070 }
0071 
0072 void SelectionDecorator::setShowFillMeshGradientHandles(bool value)
0073 {
0074     m_showFillMeshGradientHandles = value;
0075 }
0076 
0077 void SelectionDecorator::setCurrentMeshGradientHandles(const KoShapeMeshGradientHandles::Handle &selectedHandle,
0078                                                        const KoShapeMeshGradientHandles::Handle &hoveredHandle)
0079 {
0080     m_selectedMeshHandle = selectedHandle;
0081     m_currentHoveredMeshHandle = hoveredHandle;
0082 }
0083 
0084 void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter)
0085 {
0086     QList<KoShape*> selectedShapes = m_selection->selectedVisibleShapes();
0087     if (selectedShapes.isEmpty()) return;
0088 
0089     const bool haveOnlyOneEditableShape =
0090         m_selection->selectedEditableShapes().size() == 1 &&
0091         selectedShapes.size() == 1;
0092 
0093     bool editable = false;
0094     bool forceBoundingRubberLine = false;
0095 
0096     Q_FOREACH (KoShape *shape, KoShape::linearizeSubtree(selectedShapes)) {
0097         if (!haveOnlyOneEditableShape || !m_showStrokeFillGradientHandles) {
0098             KisHandlePainterHelper helper =
0099                 KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius, m_decorationThickness);
0100 
0101             helper.setHandleStyle(KisHandleStyle::secondarySelection());
0102 
0103             if (!m_forceShapeOutlines) {
0104                 helper.drawRubberLine(shape->outlineRect());
0105             } else {
0106                 QList<QPolygonF> polys = shape->outline().toSubpathPolygons();
0107 
0108                 if (polys.size() == 1) {
0109                     const QPolygonF poly1 = polys[0];
0110                     const QPolygonF poly2 = QPolygonF(polys[0].boundingRect());
0111                     const QPolygonF nonoverlap = poly2.subtracted(poly1);
0112 
0113                     forceBoundingRubberLine |= !nonoverlap.isEmpty();
0114                 }
0115 
0116                 Q_FOREACH (const QPolygonF &poly, polys) {
0117                     helper.drawRubberLine(poly);
0118                 }
0119             }
0120         }
0121 
0122         if (shape->isShapeEditable()) {
0123             editable = true;
0124         }
0125     }
0126 
0127     const QRectF handleArea = m_selection->outlineRect();
0128 
0129     // draw extra rubber line around all the shapes
0130     if (selectedShapes.size() > 1 || forceBoundingRubberLine) {
0131         KisHandlePainterHelper helper =
0132             KoShape::createHandlePainterHelperView(&painter, m_selection, converter, m_handleRadius, m_decorationThickness);
0133 
0134         helper.setHandleStyle(KisHandleStyle::primarySelection());
0135         helper.drawRubberLine(handleArea);
0136     }
0137 
0138     // if we have no editable shape selected there
0139     // is no need drawing the selection handles
0140     if (editable) {
0141         KisHandlePainterHelper helper =
0142             KoShape::createHandlePainterHelperView(&painter, m_selection, converter, m_handleRadius, m_decorationThickness);
0143         helper.setHandleStyle(KisHandleStyle::primarySelection());
0144 
0145         QPolygonF outline = handleArea;
0146 
0147         {
0148             helper.drawHandleRect(outline.value(0));
0149             helper.drawHandleRect(outline.value(1));
0150             helper.drawHandleRect(outline.value(2));
0151             helper.drawHandleRect(outline.value(3));
0152             helper.drawHandleRect(0.5 * (outline.value(0) + outline.value(1)));
0153             helper.drawHandleRect(0.5 * (outline.value(1) + outline.value(2)));
0154             helper.drawHandleRect(0.5 * (outline.value(2) + outline.value(3)));
0155             helper.drawHandleRect(0.5 * (outline.value(3) + outline.value(0)));
0156 
0157             QPointF hotPos = KoFlake::anchorToPoint(m_hotPosition, handleArea);
0158             helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
0159             helper.drawHandleRect(hotPos);
0160         }
0161     }
0162 
0163     if (haveOnlyOneEditableShape) {
0164         KoShape *shape = selectedShapes.first();
0165 
0166         if (m_showFillGradientHandles) {
0167             paintGradientHandles(shape, KoFlake::Fill, painter, converter);
0168         } else if (m_showStrokeFillGradientHandles) {
0169             paintGradientHandles(shape, KoFlake::StrokeFill, painter, converter);
0170         }
0171 
0172         // paint meshgradient handles
0173         if(m_showFillMeshGradientHandles) {
0174             paintMeshGradientHandles(shape, KoFlake::Fill, painter, converter);
0175         }
0176     }
0177 
0178 }
0179 
0180 void SelectionDecorator::paintGradientHandles(KoShape *shape, KoFlake::FillVariant fillVariant, QPainter &painter, const KoViewConverter &converter)
0181 {
0182     KoShapeGradientHandles gradientHandles(fillVariant, shape);
0183     QVector<KoShapeGradientHandles::Handle> handles = gradientHandles.handles();
0184 
0185     KisHandlePainterHelper helper =
0186         KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius, m_decorationThickness);
0187 
0188     const QTransform t = shape->absoluteTransformation().inverted();
0189 
0190     if (gradientHandles.type() == QGradient::LinearGradient) {
0191         KIS_SAFE_ASSERT_RECOVER_NOOP(handles.size() == 2);
0192 
0193         if (handles.size() == 2) {
0194             helper.setHandleStyle(KisHandleStyle::gradientArrows());
0195             helper.drawGradientArrow(t.map(handles[0].pos), t.map(handles[1].pos), 1.5 * m_handleRadius);
0196         }
0197     }
0198 
0199     helper.setHandleStyle(KisHandleStyle::gradientHandles());
0200 
0201     Q_FOREACH (const KoShapeGradientHandles::Handle &h, handles) {
0202         if (h.type == KoShapeGradientHandles::Handle::RadialCenter) {
0203             helper.drawGradientCrossHandle(t.map(h.pos), 1.2 * m_handleRadius);
0204         } else {
0205             helper.drawGradientHandle(t.map(h.pos), 1.2 * m_handleRadius);
0206         }
0207     }
0208 }
0209 
0210 void SelectionDecorator::paintMeshGradientHandles(KoShape *shape,
0211                                                   KoFlake::FillVariant fillVariant,
0212                                                   QPainter &painter,
0213                                                   const KoViewConverter &converter)
0214 {
0215     KoShapeMeshGradientHandles gradientHandles(fillVariant, shape);
0216 
0217     KisHandlePainterHelper helper =
0218         KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius, m_decorationThickness);
0219     helper.setHandleStyle(KisHandleStyle::secondarySelection());
0220 
0221     helper.drawPath(gradientHandles.path());
0222 
0223     // invert them, because we draw in logical coordinates.
0224     const QTransform t = shape->absoluteTransformation().inverted();
0225     const QVector<KoShapeMeshGradientHandles::Handle> cornerHandles = gradientHandles.handles();
0226     for (const auto& corner: cornerHandles) {
0227         const QPointF point = t.map(corner.pos);
0228         if (corner.type == KoShapeMeshGradientHandles::Handle::BezierHandle) {
0229             helper.drawConnectionLine(gradientHandles.getAttachedCorner(corner), point);
0230             helper.drawHandleSmallCircle(point);
0231         } else if (corner.type == KoShapeMeshGradientHandles::Handle::Corner) {
0232             helper.drawHandleCircle(point);
0233         }
0234     }
0235 
0236     helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandlesWithSolidOutline());
0237 
0238     // highlight the selected handle (only corner)
0239     if (m_selectedMeshHandle.type == KoShapeMeshGradientHandles::Handle::Corner) {
0240         helper.drawHandleRect(t.map(gradientHandles.getHandle(m_selectedMeshHandle.getPosition()).pos));
0241     }
0242 
0243     // highlight the path which is being hovered/moved
0244     if (m_currentHoveredMeshHandle.type != KoShapeMeshGradientHandles::Handle::None) {
0245         QVector<QPainterPath> paths = gradientHandles.getConnectedPath(m_currentHoveredMeshHandle);
0246         for (const auto &path: paths) {
0247             helper.drawPath(path);
0248         }
0249     }
0250 }
0251 
0252 void SelectionDecorator::setForceShapeOutlines(bool value)
0253 {
0254     m_forceShapeOutlines = value;
0255 }