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 }