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 }