1 /* This file is part of the KDE project
2 
3    Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
4    Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10 
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15 
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20 */
21 
22 #include "SelectionDecorator.h"
23 
24 #include <QPainterPath>
25 
26 #include <KoShape.h>
27 #include <KoSelection.h>
28 #include <KoResourcePaths.h>
29 #include "kis_algebra_2d.h"
30 
31 #include "kis_debug.h"
32 #include <KisHandlePainterHelper.h>
33 #include <KoCanvasResourceProvider.h>
34 #include <KisQPainterStateSaver.h>
35 #include "KoShapeGradientHandles.h"
36 #include <KoCanvasBase.h>
37 #include <KoSvgTextShape.h>
38 
39 #include "kis_painting_tweaks.h"
40 #include "kis_coordinates_converter.h"
41 #include "kis_icon_utils.h"
42 
43 
44 
45 #define HANDLE_DISTANCE 10
46 
SelectionDecorator(KoCanvasResourceProvider * resourceManager)47 SelectionDecorator::SelectionDecorator(KoCanvasResourceProvider *resourceManager)
48     : m_hotPosition(KoFlake::Center)
49     , m_handleRadius(7)
50     , m_lineWidth(2)
51     , m_showFillGradientHandles(false)
52     , m_showStrokeFillGradientHandles(false)
53     , m_forceShapeOutlines(false)
54 {
55     m_hotPosition =
56         KoFlake::AnchorPosition(
57             resourceManager->resource(KoFlake::HotPosition).toInt());
58 }
59 
setSelection(KoSelection * selection)60 void SelectionDecorator::setSelection(KoSelection *selection)
61 {
62     m_selection = selection;
63 }
64 
setHandleRadius(int radius)65 void SelectionDecorator::setHandleRadius(int radius)
66 {
67     m_handleRadius = radius;
68     m_lineWidth = qMax(1, (int)(radius / 2));
69 }
70 
setShowFillGradientHandles(bool value)71 void SelectionDecorator::setShowFillGradientHandles(bool value)
72 {
73     m_showFillGradientHandles = value;
74 }
75 
setShowStrokeFillGradientHandles(bool value)76 void SelectionDecorator::setShowStrokeFillGradientHandles(bool value)
77 {
78     m_showStrokeFillGradientHandles = value;
79 }
80 
setShowFillMeshGradientHandles(bool value)81 void SelectionDecorator::setShowFillMeshGradientHandles(bool value)
82 {
83     m_showFillMeshGradientHandles = value;
84 }
85 
setCurrentMeshGradientHandles(const KoShapeMeshGradientHandles::Handle & selectedHandle,const KoShapeMeshGradientHandles::Handle & hoveredHandle)86 void SelectionDecorator::setCurrentMeshGradientHandles(const KoShapeMeshGradientHandles::Handle &selectedHandle,
87                                                        const KoShapeMeshGradientHandles::Handle &hoveredHandle)
88 {
89     m_selectedMeshHandle = selectedHandle;
90     m_currentHoveredMeshHandle = hoveredHandle;
91 }
92 
paint(QPainter & painter,const KoViewConverter & converter)93 void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter)
94 {
95     QList<KoShape*> selectedShapes = m_selection->selectedVisibleShapes();
96     if (selectedShapes.isEmpty()) return;
97 
98     const bool haveOnlyOneEditableShape =
99         m_selection->selectedEditableShapes().size() == 1 &&
100         selectedShapes.size() == 1;
101 
102     bool editable = false;
103     bool forceBoundngRubberLine = false;
104 
105     Q_FOREACH (KoShape *shape, KoShape::linearizeSubtree(selectedShapes)) {
106         if (!haveOnlyOneEditableShape || !m_showStrokeFillGradientHandles) {
107             KisHandlePainterHelper helper =
108                 KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
109 
110             helper.setHandleStyle(KisHandleStyle::secondarySelection());
111 
112             if (!m_forceShapeOutlines) {
113                 helper.drawRubberLine(shape->outlineRect());
114             } else {
115                 QList<QPolygonF> polys = shape->outline().toSubpathPolygons();
116 
117                 if (polys.size() == 1) {
118                     const QPolygonF poly1 = polys[0];
119                     const QPolygonF poly2 = QPolygonF(polys[0].boundingRect());
120                     const QPolygonF nonoverlap = poly2.subtracted(poly1);
121 
122                     forceBoundngRubberLine |= !nonoverlap.isEmpty();
123                 }
124 
125                 Q_FOREACH (const QPolygonF &poly, polys) {
126                     helper.drawRubberLine(poly);
127                 }
128             }
129         }
130 
131         if (shape->isShapeEditable()) {
132             editable = true;
133         }
134     }
135 
136     const QRectF handleArea = m_selection->outlineRect();
137 
138     // draw extra rubber line around all the shapes
139     if (selectedShapes.size() > 1 || forceBoundngRubberLine) {
140         KisHandlePainterHelper helper =
141             KoShape::createHandlePainterHelperView(&painter, m_selection, converter, m_handleRadius);
142 
143         helper.setHandleStyle(KisHandleStyle::primarySelection());
144         helper.drawRubberLine(handleArea);
145     }
146 
147     // if we have no editable shape selected there
148     // is no need drawing the selection handles
149     if (editable) {
150         KisHandlePainterHelper helper =
151             KoShape::createHandlePainterHelperView(&painter, m_selection, converter, m_handleRadius);
152         helper.setHandleStyle(KisHandleStyle::primarySelection());
153 
154         QPolygonF outline = handleArea;
155 
156         {
157             helper.drawHandleRect(outline.value(0));
158             helper.drawHandleRect(outline.value(1));
159             helper.drawHandleRect(outline.value(2));
160             helper.drawHandleRect(outline.value(3));
161             helper.drawHandleRect(0.5 * (outline.value(0) + outline.value(1)));
162             helper.drawHandleRect(0.5 * (outline.value(1) + outline.value(2)));
163             helper.drawHandleRect(0.5 * (outline.value(2) + outline.value(3)));
164             helper.drawHandleRect(0.5 * (outline.value(3) + outline.value(0)));
165 
166             QPointF hotPos = KoFlake::anchorToPoint(m_hotPosition, handleArea);
167             helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
168             helper.drawHandleRect(hotPos);
169         }
170     }
171 
172     if (haveOnlyOneEditableShape) {
173         KoShape *shape = selectedShapes.first();
174 
175         if (m_showFillGradientHandles) {
176             paintGradientHandles(shape, KoFlake::Fill, painter, converter);
177         } else if (m_showStrokeFillGradientHandles) {
178             paintGradientHandles(shape, KoFlake::StrokeFill, painter, converter);
179         }
180 
181         // paint meshgradient handles
182         if(m_showFillMeshGradientHandles) {
183             paintMeshGradientHandles(shape, KoFlake::Fill, painter, converter);
184         }
185     }
186 
187 }
188 
paintGradientHandles(KoShape * shape,KoFlake::FillVariant fillVariant,QPainter & painter,const KoViewConverter & converter)189 void SelectionDecorator::paintGradientHandles(KoShape *shape, KoFlake::FillVariant fillVariant, QPainter &painter, const KoViewConverter &converter)
190 {
191     KoShapeGradientHandles gradientHandles(fillVariant, shape);
192     QVector<KoShapeGradientHandles::Handle> handles = gradientHandles.handles();
193 
194     KisHandlePainterHelper helper =
195         KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
196 
197     const QTransform t = shape->absoluteTransformation().inverted();
198 
199     if (gradientHandles.type() == QGradient::LinearGradient) {
200         KIS_SAFE_ASSERT_RECOVER_NOOP(handles.size() == 2);
201 
202         if (handles.size() == 2) {
203             helper.setHandleStyle(KisHandleStyle::gradientArrows());
204             helper.drawGradientArrow(t.map(handles[0].pos), t.map(handles[1].pos), 1.5 * m_handleRadius);
205         }
206     }
207 
208     helper.setHandleStyle(KisHandleStyle::gradientHandles());
209 
210     Q_FOREACH (const KoShapeGradientHandles::Handle &h, handles) {
211         if (h.type == KoShapeGradientHandles::Handle::RadialCenter) {
212             helper.drawGradientCrossHandle(t.map(h.pos), 1.2 * m_handleRadius);
213         } else {
214             helper.drawGradientHandle(t.map(h.pos), 1.2 * m_handleRadius);
215         }
216     }
217 }
218 
paintMeshGradientHandles(KoShape * shape,KoFlake::FillVariant fillVariant,QPainter & painter,const KoViewConverter & converter)219 void SelectionDecorator::paintMeshGradientHandles(KoShape *shape,
220                                                   KoFlake::FillVariant fillVariant,
221                                                   QPainter &painter,
222                                                   const KoViewConverter &converter)
223 {
224     KoShapeMeshGradientHandles gradientHandles(fillVariant, shape);
225 
226     KisHandlePainterHelper helper =
227         KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
228     helper.setHandleStyle(KisHandleStyle::secondarySelection());
229 
230     helper.drawPath(gradientHandles.path());
231 
232     // invert them, because we draw in logical coordinates.
233     QTransform t = shape->absoluteTransformation().inverted();
234     QVector<KoShapeMeshGradientHandles::Handle> cornerHandles = gradientHandles.handles();
235     for (const auto& corner: cornerHandles) {
236         if (corner.type == KoShapeMeshGradientHandles::Handle::BezierHandle) {
237             helper.drawHandleSmallCircle(t.map(corner.pos));
238         } else if (corner.type == KoShapeMeshGradientHandles::Handle::Corner) {
239             helper.drawHandleRect(t.map(corner.pos));
240         }
241     }
242 
243     helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandlesWithSolidOutline());
244 
245     // highlight the selected handle (only corner)
246     if (m_selectedMeshHandle.type == KoShapeMeshGradientHandles::Handle::Corner) {
247         helper.drawHandleRect(t.map(gradientHandles.getHandle(m_selectedMeshHandle.getPosition()).pos));
248     }
249 
250     // highlight the path which is being hovered/moved
251     if (m_currentHoveredMeshHandle.type != KoShapeMeshGradientHandles::Handle::None) {
252         QVector<QPainterPath> paths = gradientHandles.getConnectedPath(m_currentHoveredMeshHandle);
253         for (const auto &path: paths) {
254             helper.drawPath(path);
255         }
256     }
257 }
258 
setForceShapeOutlines(bool value)259 void SelectionDecorator::setForceShapeOutlines(bool value)
260 {
261     m_forceShapeOutlines = value;
262 }
263