File indexing completed on 2024-06-23 04:27:04

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2006-2008 Thorsten Zachmann <zachmann@kde.org>
0003    SPDX-FileCopyrightText: 2006-2008 Jan Hambrecht <jaham@gmx.net>
0004    SPDX-FileCopyrightText: 2009 Thomas Zander <zander@kde.org>
0005 
0006    SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "RectangleShape.h"
0010 
0011 #include <KoParameterShape_p.h>
0012 #include <KoPathPoint.h>
0013 #include <KoShapeSavingContext.h>
0014 #include <KoXmlWriter.h>
0015 #include <KoXmlNS.h>
0016 #include <KoUnit.h>
0017 #include <SvgSavingContext.h>
0018 #include <SvgLoadingContext.h>
0019 #include <SvgUtil.h>
0020 #include <SvgStyleWriter.h>
0021 
0022 RectangleShape::RectangleShape()
0023     : KoParameterShape()
0024     , m_cornerRadiusX(0)
0025     , m_cornerRadiusY(0)
0026 {
0027     QList<QPointF> handles;
0028     handles.push_back(QPointF(100, 0));
0029     handles.push_back(QPointF(100, 0));
0030     setHandles(handles);
0031     QSizeF size(100, 100);
0032     updatePath(size);
0033 }
0034 
0035 RectangleShape::RectangleShape(const RectangleShape &rhs)
0036     : KoParameterShape(rhs),
0037       m_cornerRadiusX(rhs.m_cornerRadiusX),
0038       m_cornerRadiusY(rhs.m_cornerRadiusY)
0039 {
0040 }
0041 
0042 RectangleShape::~RectangleShape()
0043 {
0044 }
0045 
0046 KoShape *RectangleShape::cloneShape() const
0047 {
0048     return new RectangleShape(*this);
0049 }
0050 
0051 void RectangleShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
0052 {
0053     Q_UNUSED(modifiers);
0054     QPointF p(point);
0055 
0056     qreal width2 = size().width() / 2.0;
0057     qreal height2 = size().height() / 2.0;
0058     switch (handleId) {
0059     case 0:
0060         if (p.x() < width2) {
0061             p.setX(width2);
0062         } else if (p.x() > size().width()) {
0063             p.setX(size().width());
0064         }
0065         p.setY(0);
0066         m_cornerRadiusX = (size().width() - p.x()) / width2 * 100.0;
0067         if (!(modifiers & Qt::ControlModifier)) {
0068             m_cornerRadiusY = (size().width() - p.x()) / height2 * 100.0;
0069         }
0070         break;
0071     case 1:
0072         if (p.y() < 0) {
0073             p.setY(0);
0074         } else if (p.y() > height2) {
0075             p.setY(height2);
0076         }
0077         p.setX(size().width());
0078         m_cornerRadiusY = p.y() / height2 * 100.0;
0079         if (!(modifiers & Qt::ControlModifier)) {
0080             m_cornerRadiusX = p.y() / width2 * 100.0;
0081         }
0082         break;
0083     }
0084     // this is needed otherwise undo/redo might not end in the same result
0085     if (100 - m_cornerRadiusX < 1e-10) {
0086         m_cornerRadiusX = 100;
0087     }
0088     if (100 - m_cornerRadiusY < 1e-10) {
0089         m_cornerRadiusY = 100;
0090     }
0091 
0092     updateHandles();
0093 }
0094 
0095 void RectangleShape::updateHandles()
0096 {
0097     QList<QPointF> handles;
0098     handles.append(QPointF(size().width() - m_cornerRadiusX / 100.0 * 0.5 * size().width(), 0.0));
0099     handles.append(QPointF(size().width(), m_cornerRadiusY / 100.0 * 0.5 * size().height()));
0100     setHandles(handles);
0101 }
0102 
0103 void RectangleShape::updatePath(const QSizeF &size)
0104 {
0105     qreal rx = 0;
0106     qreal ry = 0;
0107     if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) {
0108         rx = size.width() / 200.0 * m_cornerRadiusX;
0109         ry = size.height() / 200.0 * m_cornerRadiusY;
0110     }
0111 
0112     qreal x2 = size.width() - rx;
0113     qreal y2 = size.height() - ry;
0114 
0115     QPointF curvePoints[12];
0116 
0117     int requiredCurvePointCount = 4;
0118     if (rx && m_cornerRadiusX < 100) {
0119         requiredCurvePointCount += 2;
0120     }
0121     if (ry && m_cornerRadiusY < 100) {
0122         requiredCurvePointCount += 2;
0123     }
0124 
0125     createPoints(requiredCurvePointCount);
0126 
0127     KoSubpath &points = *subpaths()[0];
0128 
0129     int cp = 0;
0130 
0131     // first path starts and closes path
0132     points[cp]->setProperty(KoPathPoint::StartSubpath);
0133     points[cp]->setProperty(KoPathPoint::CloseSubpath);
0134     points[cp]->setPoint(QPointF(rx, 0));
0135     points[cp]->removeControlPoint1();
0136     points[cp]->removeControlPoint2();
0137 
0138     if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) {
0139         // end point of the top edge
0140         points[++cp]->setPoint(QPointF(x2, 0));
0141         points[cp]->removeControlPoint1();
0142         points[cp]->removeControlPoint2();
0143     }
0144 
0145     if (rx) {
0146         // the top right radius
0147         arcToCurve(rx, ry, 90, -90, points[cp]->point(), curvePoints);
0148         points[cp]->setControlPoint2(curvePoints[0]);
0149         points[++cp]->setControlPoint1(curvePoints[1]);
0150         points[cp]->setPoint(curvePoints[2]);
0151         points[cp]->removeControlPoint2();
0152     }
0153 
0154     if (m_cornerRadiusY < 100 || m_cornerRadiusX == 0) {
0155         // the right edge
0156         points[++cp]->setPoint(QPointF(size.width(), y2));
0157         points[cp]->removeControlPoint1();
0158         points[cp]->removeControlPoint2();
0159     }
0160 
0161     if (rx) {
0162         // the bottom right radius
0163         arcToCurve(rx, ry, 0, -90, points[cp]->point(), curvePoints);
0164         points[cp]->setControlPoint2(curvePoints[0]);
0165         points[++cp]->setControlPoint1(curvePoints[1]);
0166         points[cp]->setPoint(curvePoints[2]);
0167         points[cp]->removeControlPoint2();
0168     }
0169 
0170     if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) {
0171         // the bottom edge
0172         points[++cp]->setPoint(QPointF(rx, size.height()));
0173         points[cp]->removeControlPoint1();
0174         points[cp]->removeControlPoint2();
0175     }
0176 
0177     if (rx) {
0178         // the bottom left radius
0179         arcToCurve(rx, ry, 270, -90, points[cp]->point(), curvePoints);
0180         points[cp]->setControlPoint2(curvePoints[0]);
0181         points[++cp]->setControlPoint1(curvePoints[1]);
0182         points[cp]->setPoint(curvePoints[2]);
0183         points[cp]->removeControlPoint2();
0184     }
0185 
0186     if ((m_cornerRadiusY < 100 || m_cornerRadiusX == 0) && ry) {
0187         // the right edge
0188         points[++cp]->setPoint(QPointF(0, ry));
0189         points[cp]->removeControlPoint1();
0190         points[cp]->removeControlPoint2();
0191     }
0192 
0193     if (rx) {
0194         // the top left radius
0195         arcToCurve(rx, ry, 180, -90, points[cp]->point(), curvePoints);
0196         points[cp]->setControlPoint2(curvePoints[0]);
0197         points[0]->setControlPoint1(curvePoints[1]);
0198         points[0]->setPoint(curvePoints[2]);
0199     }
0200 
0201     // unset all stop/close path properties
0202     for (int i = 1; i < cp; ++i) {
0203         points[i]->unsetProperty(KoPathPoint::StopSubpath);
0204         points[i]->unsetProperty(KoPathPoint::CloseSubpath);
0205     }
0206 
0207     // last point stops and closes path
0208     points.last()->setProperty(KoPathPoint::StopSubpath);
0209     points.last()->setProperty(KoPathPoint::CloseSubpath);
0210 
0211     notifyPointsChanged();
0212 }
0213 
0214 void RectangleShape::createPoints(int requiredPointCount)
0215 {
0216     if (subpaths().count() != 1) {
0217         clear();
0218         subpaths().append(new KoSubpath());
0219     }
0220     int currentPointCount = subpaths()[0]->count();
0221     if (currentPointCount > requiredPointCount) {
0222         for (int i = 0; i < currentPointCount - requiredPointCount; ++i) {
0223             delete subpaths()[0]->front();
0224             subpaths()[0]->pop_front();
0225         }
0226     } else if (requiredPointCount > currentPointCount) {
0227         for (int i = 0; i < requiredPointCount - currentPointCount; ++i) {
0228             subpaths()[0]->append(new KoPathPoint(this, QPointF()));
0229         }
0230     }
0231 
0232     notifyPointsChanged();
0233 }
0234 
0235 qreal RectangleShape::cornerRadiusX() const
0236 {
0237     return m_cornerRadiusX;
0238 }
0239 
0240 void RectangleShape::setCornerRadiusX(qreal radius)
0241 {
0242     radius = qBound(0.0, radius, 100.0);
0243     m_cornerRadiusX = radius;
0244     updatePath(size());
0245     updateHandles();
0246 }
0247 
0248 qreal RectangleShape::cornerRadiusY() const
0249 {
0250     return m_cornerRadiusY;
0251 }
0252 
0253 void RectangleShape::setCornerRadiusY(qreal radius)
0254 {
0255     radius = qBound(0.0, radius, 100.0);
0256     m_cornerRadiusY = radius;
0257     updatePath(size());
0258     updateHandles();
0259 }
0260 
0261 QString RectangleShape::pathShapeId() const
0262 {
0263     return RectangleShapeId;
0264 }
0265 
0266 bool RectangleShape::saveSvg(SvgSavingContext &context)
0267 {
0268     // let basic path saiving code handle our saving
0269     if (!isParametricShape()) return false;
0270 
0271     context.shapeWriter().startElement("rect");
0272     context.shapeWriter().addAttribute("id", context.getID(this));
0273     SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter());
0274 
0275     SvgStyleWriter::saveSvgStyle(this, context);
0276 
0277     const QSizeF size = this->size();
0278     context.shapeWriter().addAttribute("width", size.width());
0279     context.shapeWriter().addAttribute("height", size.height());
0280 
0281     double rx = cornerRadiusX();
0282     if (rx > 0.0) {
0283         context.shapeWriter().addAttribute("rx", 0.01 * rx * 0.5 * size.width());
0284     }
0285     double ry = cornerRadiusY();
0286     if (ry > 0.0) {
0287         context.shapeWriter().addAttribute("ry", 0.01 * ry * 0.5 * size.height());
0288     }
0289 
0290     context.shapeWriter().endElement();
0291 
0292     return true;
0293 }
0294 
0295 bool RectangleShape::loadSvg(const QDomElement &element, SvgLoadingContext &context)
0296 {
0297     const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x"));
0298     const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y"));
0299     const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width"));
0300     const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height"));
0301     const QString rxStr = element.attribute("rx");
0302     const QString ryStr = element.attribute("ry");
0303     qreal rx = rxStr.isEmpty() ? 0.0 : SvgUtil::parseUnitX(context.currentGC(), rxStr);
0304     qreal ry = ryStr.isEmpty() ? 0.0 : SvgUtil::parseUnitY(context.currentGC(), ryStr);
0305     // if one radius is given but not the other, use the same value for both
0306     if (!rxStr.isEmpty() && ryStr.isEmpty()) {
0307         ry = rx;
0308     }
0309     if (rxStr.isEmpty() && !ryStr.isEmpty()) {
0310         rx = ry;
0311     }
0312 
0313     setSize(QSizeF(w, h));
0314     setPosition(QPointF(x, y));
0315     if (rx >= 0.0) {
0316         setCornerRadiusX(qMin(qreal(100.0), qreal(rx / (0.5 * w) * 100.0)));
0317     }
0318     if (ry >= 0.0) {
0319         setCornerRadiusY(qMin(qreal(100.0), qreal(ry / (0.5 * h) * 100.0)));
0320     }
0321     if (w == 0.0 || h == 0.0) {
0322         setVisible(false);
0323     }
0324 
0325     return true;
0326 }