File indexing completed on 2024-06-23 04:27:04
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2006-2009 Jan Hambrecht <jaham@gmx.net> 0003 SPDX-FileCopyrightText: 2009 Thomas Zander <zander@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "StarShape.h" 0009 0010 #include <KoParameterShape_p.h> 0011 #include <KoPathPoint.h> 0012 #include <KoShapeLoadingContext.h> 0013 #include <KoShapeSavingContext.h> 0014 #include <KoXmlNS.h> 0015 #include <KoXmlWriter.h> 0016 #include <QStringList> 0017 0018 #include <math.h> 0019 0020 StarShape::StarShape() 0021 : m_cornerCount(5) 0022 , m_zoomX(1.0) 0023 , m_zoomY(1.0) 0024 , m_convex(false) 0025 { 0026 m_radius[base] = 25.0; 0027 m_radius[tip] = 50.0; 0028 m_angles[base] = m_angles[tip] = defaultAngleRadian(); 0029 m_roundness[base] = m_roundness[tip] = 0.0f; 0030 0031 m_center = QPointF(50, 50); 0032 updatePath(QSize(100, 100)); 0033 } 0034 0035 StarShape::StarShape(const StarShape &rhs) 0036 : KoParameterShape(rhs), 0037 m_cornerCount(rhs.m_cornerCount), 0038 m_radius(rhs.m_radius), 0039 m_angles(rhs.m_angles), 0040 m_zoomX(rhs.m_zoomX), 0041 m_zoomY(rhs.m_zoomY), 0042 m_roundness(rhs.m_roundness), 0043 m_center(rhs.m_center), 0044 m_convex(rhs.m_convex) 0045 { 0046 } 0047 0048 StarShape::~StarShape() 0049 { 0050 } 0051 0052 KoShape *StarShape::cloneShape() const 0053 { 0054 return new StarShape(*this); 0055 } 0056 0057 0058 void StarShape::setCornerCount(uint cornerCount) 0059 { 0060 if (cornerCount >= 3) { 0061 double oldDefaultAngle = defaultAngleRadian(); 0062 m_cornerCount = cornerCount; 0063 double newDefaultAngle = defaultAngleRadian(); 0064 m_angles[base] += newDefaultAngle - oldDefaultAngle; 0065 m_angles[tip] += newDefaultAngle - oldDefaultAngle; 0066 0067 updatePath(QSize()); 0068 } 0069 } 0070 0071 uint StarShape::cornerCount() const 0072 { 0073 return m_cornerCount; 0074 } 0075 0076 void StarShape::setBaseRadius(qreal baseRadius) 0077 { 0078 m_radius[base] = fabs(baseRadius); 0079 updatePath(QSize()); 0080 } 0081 0082 qreal StarShape::baseRadius() const 0083 { 0084 return m_radius[base]; 0085 } 0086 0087 void StarShape::setTipRadius(qreal tipRadius) 0088 { 0089 m_radius[tip] = fabs(tipRadius); 0090 updatePath(QSize()); 0091 } 0092 0093 qreal StarShape::tipRadius() const 0094 { 0095 return m_radius[tip]; 0096 } 0097 0098 void StarShape::setBaseRoundness(qreal baseRoundness) 0099 { 0100 m_roundness[base] = baseRoundness; 0101 updatePath(QSize()); 0102 } 0103 0104 void StarShape::setTipRoundness(qreal tipRoundness) 0105 { 0106 m_roundness[tip] = tipRoundness; 0107 updatePath(QSize()); 0108 } 0109 0110 void StarShape::setConvex(bool convex) 0111 { 0112 m_convex = convex; 0113 updatePath(QSize()); 0114 } 0115 0116 bool StarShape::convex() const 0117 { 0118 return m_convex; 0119 } 0120 0121 QPointF StarShape::starCenter() const 0122 { 0123 return m_center; 0124 } 0125 0126 void StarShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) 0127 { 0128 if (modifiers & Qt::ShiftModifier) { 0129 QPointF handle = handles()[handleId]; 0130 QPointF tangentVector = point - handle; 0131 qreal distance = sqrt(tangentVector.x() * tangentVector.x() + tangentVector.y() * tangentVector.y()); 0132 QPointF radialVector = handle - m_center; 0133 // cross product to determine in which direction the user is dragging 0134 qreal moveDirection = radialVector.x() * tangentVector.y() - radialVector.y() * tangentVector.x(); 0135 // make the roundness stick to zero if distance is under a certain value 0136 float snapDistance = 3.0; 0137 if (distance >= 0.0) { 0138 distance = distance < snapDistance ? 0.0 : distance - snapDistance; 0139 } else { 0140 distance = distance > -snapDistance ? 0.0 : distance + snapDistance; 0141 } 0142 // control changes roundness on both handles, else only the actual handle roundness is changed 0143 if (modifiers & Qt::ControlModifier) { 0144 m_roundness[handleId] = moveDirection < 0.0f ? distance : -distance; 0145 } else { 0146 m_roundness[base] = m_roundness[tip] = moveDirection < 0.0f ? distance : -distance; 0147 } 0148 } else { 0149 QPointF distVector = point - m_center; 0150 // unapply scaling 0151 distVector.rx() /= m_zoomX; 0152 distVector.ry() /= m_zoomY; 0153 m_radius[handleId] = sqrt(distVector.x() * distVector.x() + distVector.y() * distVector.y()); 0154 0155 qreal angle = atan2(distVector.y(), distVector.x()); 0156 if (angle < 0.0) { 0157 angle += 2.0 * M_PI; 0158 } 0159 qreal diffAngle = angle - m_angles[handleId]; 0160 qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount); 0161 if (handleId == tip) { 0162 m_angles[tip] += diffAngle - radianStep; 0163 m_angles[base] += diffAngle - radianStep; 0164 } else { 0165 // control make the base point move freely 0166 if (modifiers & Qt::ControlModifier) { 0167 m_angles[base] += diffAngle - 2 * radianStep; 0168 } else { 0169 m_angles[base] = m_angles[tip]; 0170 } 0171 } 0172 } 0173 } 0174 0175 void StarShape::updatePath(const QSizeF &size) 0176 { 0177 Q_UNUSED(size); 0178 qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount); 0179 0180 createPoints(m_convex ? m_cornerCount : 2 * m_cornerCount); 0181 0182 KoSubpath &points = *subpaths()[0]; 0183 0184 uint index = 0; 0185 for (uint i = 0; i < 2 * m_cornerCount; ++i) { 0186 uint cornerType = i % 2; 0187 if (cornerType == base && m_convex) { 0188 continue; 0189 } 0190 qreal radian = static_cast<qreal>((i + 1) * radianStep) + m_angles[cornerType]; 0191 QPointF cornerPoint = QPointF(m_zoomX * m_radius[cornerType] * cos(radian), m_zoomY * m_radius[cornerType] * sin(radian)); 0192 0193 points[index]->setPoint(m_center + cornerPoint); 0194 points[index]->unsetProperty(KoPathPoint::StopSubpath); 0195 points[index]->unsetProperty(KoPathPoint::CloseSubpath); 0196 if (m_roundness[cornerType] > 1e-10 || m_roundness[cornerType] < -1e-10) { 0197 // normalized cross product to compute tangential vector for handle point 0198 QPointF tangentVector(cornerPoint.y() / m_radius[cornerType], -cornerPoint.x() / m_radius[cornerType]); 0199 points[index]->setControlPoint2(points[index]->point() - m_roundness[cornerType] * tangentVector); 0200 points[index]->setControlPoint1(points[index]->point() + m_roundness[cornerType] * tangentVector); 0201 } else { 0202 points[index]->removeControlPoint1(); 0203 points[index]->removeControlPoint2(); 0204 } 0205 index++; 0206 } 0207 0208 // first path starts and closes path 0209 points[0]->setProperty(KoPathPoint::StartSubpath); 0210 points[0]->setProperty(KoPathPoint::CloseSubpath); 0211 // last point stops and closes path 0212 points.last()->setProperty(KoPathPoint::StopSubpath); 0213 points.last()->setProperty(KoPathPoint::CloseSubpath); 0214 0215 normalize(); 0216 0217 QList<QPointF> handles; 0218 handles.push_back(points.at(tip)->point()); 0219 if (!m_convex) { 0220 handles.push_back(points.at(base)->point()); 0221 } 0222 setHandles(handles); 0223 0224 m_center = computeCenter(); 0225 } 0226 0227 void StarShape::createPoints(int requiredPointCount) 0228 { 0229 if (subpaths().count() != 1) { 0230 clear(); 0231 subpaths().append(new KoSubpath()); 0232 } 0233 int currentPointCount = subpaths()[0]->count(); 0234 if (currentPointCount > requiredPointCount) { 0235 for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { 0236 delete subpaths()[0]->front(); 0237 subpaths()[0]->pop_front(); 0238 } 0239 } else if (requiredPointCount > currentPointCount) { 0240 for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { 0241 subpaths()[0]->append(new KoPathPoint(this, QPointF())); 0242 } 0243 } 0244 0245 notifyPointsChanged(); 0246 } 0247 0248 void StarShape::setSize(const QSizeF &newSize) 0249 { 0250 QTransform matrix(resizeMatrix(newSize)); 0251 m_zoomX *= matrix.m11(); 0252 m_zoomY *= matrix.m22(); 0253 0254 // this transforms the handles 0255 KoParameterShape::setSize(newSize); 0256 0257 m_center = computeCenter(); 0258 } 0259 0260 QPointF StarShape::computeCenter() const 0261 { 0262 KoSubpath &points = *subpaths()[0]; 0263 0264 QPointF center(0, 0); 0265 for (uint i = 0; i < m_cornerCount; ++i) { 0266 if (m_convex) { 0267 center += points[i]->point(); 0268 } else { 0269 center += points[2 * i]->point(); 0270 } 0271 } 0272 if (m_cornerCount > 0) { 0273 return center / static_cast<qreal>(m_cornerCount); 0274 } 0275 return center; 0276 0277 } 0278 0279 QString StarShape::pathShapeId() const 0280 { 0281 return StarShapeId; 0282 } 0283 0284 double StarShape::defaultAngleRadian() const 0285 { 0286 qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount); 0287 0288 return M_PI_2 - 2 * radianStep; 0289 }