File indexing completed on 2025-01-26 03:34:15
0001 /* 0002 File : Symbol.cpp 0003 Project : LabPlot 0004 Description : Symbol 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2015-2023 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 /*! 0012 \class Symbol 0013 \brief 0014 0015 \ingroup worksheet 0016 */ 0017 0018 #include "Symbol.h" 0019 #include "SymbolPrivate.h" 0020 #include "backend/lib/XmlStreamReader.h" 0021 #include "backend/lib/commandtemplates.h" 0022 #include "backend/worksheet/Worksheet.h" 0023 0024 #include <KConfigGroup> 0025 #include <KLocalizedString> 0026 0027 #include <QFont> 0028 #include <QPainter> 0029 0030 #include <gsl/gsl_math.h> 0031 0032 // order of styles in UI comboboxes (defined in Symbol.h, order can be changed without breaking projects) 0033 static QVector<Symbol::Style> StyleOrder = {Symbol::Style::NoSymbols, 0034 Symbol::Style::Circle, 0035 Symbol::Style::Square, 0036 Symbol::Style::EquilateralTriangle, 0037 Symbol::Style::Line, 0038 Symbol::Style::Cross, 0039 Symbol::Style::Tri, 0040 Symbol::Style::X, 0041 Symbol::Style::Asterisk, 0042 Symbol::Style::XPlus, 0043 Symbol::Style::TallPlus, 0044 Symbol::Style::LatinCross, 0045 Symbol::Style::DotPlus, 0046 Symbol::Style::Pin, 0047 Symbol::Style::Hash, 0048 Symbol::Style::SquareX, 0049 Symbol::Style::SquarePlus, 0050 Symbol::Style::SquareHalf, 0051 Symbol::Style::SquareDot, 0052 Symbol::Style::SquareDiag, 0053 Symbol::Style::SquareTriangle, 0054 Symbol::Style::CircleHalf, 0055 Symbol::Style::CircleDot, 0056 Symbol::Style::CircleX, 0057 Symbol::Style::CircleTri, 0058 Symbol::Style::Peace, 0059 Symbol::Style::TriangleDot, 0060 Symbol::Style::TriangleLine, 0061 Symbol::Style::TriangleHalf, 0062 Symbol::Style::RightTriangle, 0063 Symbol::Style::Bar, 0064 Symbol::Style::PeakedBar, 0065 Symbol::Style::SkewedBar, 0066 Symbol::Style::Diamond, 0067 Symbol::Style::Lozenge, 0068 Symbol::Style::Tie, 0069 Symbol::Style::TinyTie, 0070 Symbol::Style::Boomerang, 0071 Symbol::Style::SmallBoomerang, 0072 Symbol::Style::Star, 0073 Symbol::Style::Star3, 0074 Symbol::Style::Star4, 0075 Symbol::Style::Star5, 0076 Symbol::Style::Star6, 0077 Symbol::Style::Plus, 0078 Symbol::Style::Latin, 0079 Symbol::Style::David, 0080 Symbol::Style::Home, 0081 Symbol::Style::Pentagon, 0082 Symbol::Style::Hexagon, 0083 Symbol::Style::Female, 0084 Symbol::Style::Male, 0085 Symbol::Style::Flower, 0086 Symbol::Style::Flower2, 0087 Symbol::Style::Flower3, 0088 Symbol::Style::Flower5, 0089 Symbol::Style::Flower6, 0090 Symbol::Style::Heart, 0091 Symbol::Style::Spade, 0092 Symbol::Style::Club, 0093 Symbol::Style::Lightning}; 0094 0095 Symbol::Symbol(const QString& name) 0096 : AbstractAspect(name, AspectType::AbstractAspect) 0097 , d_ptr(new SymbolPrivate(this)) { 0098 } 0099 0100 Symbol::~Symbol() { 0101 delete d_ptr; 0102 } 0103 0104 void Symbol::init(const KConfigGroup& group) { 0105 Q_D(Symbol); 0106 0107 Symbol::Style defaultStyle = Symbol::Style::NoSymbols; 0108 double defaultSize = Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point); 0109 QColor defaultBorderColor(Qt::black); 0110 double defaultBorderWidth = Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point); 0111 0112 auto type = parentAspect()->type(); 0113 if (type == AspectType::CustomPoint || type == AspectType::LollipopPlot) 0114 defaultStyle = Symbol::Style::Circle; 0115 else if (type == AspectType::DatapickerImage || type == AspectType::DatapickerCurve) { 0116 defaultStyle = Symbol::Style::Cross; 0117 defaultSize = Worksheet::convertToSceneUnits(7, Worksheet::Unit::Point); 0118 defaultBorderColor = Qt::red; 0119 defaultBorderWidth = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point); 0120 } 0121 0122 d->style = (Symbol::Style)group.readEntry("SymbolStyle", (int)defaultStyle); 0123 d->size = group.readEntry("SymbolSize", defaultSize); 0124 d->rotationAngle = group.readEntry("SymbolRotation", 0.0); 0125 d->opacity = group.readEntry("SymbolOpacity", 1.0); 0126 d->brush.setStyle((Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern)); 0127 d->brush.setColor(group.readEntry("SymbolFillingColor", QColor(Qt::red))); 0128 d->pen.setStyle((Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine)); 0129 d->pen.setColor(group.readEntry("SymbolBorderColor", defaultBorderColor)); 0130 d->pen.setWidthF(group.readEntry("SymbolBorderWidth", defaultBorderWidth)); 0131 } 0132 0133 // ############################################################################## 0134 // ########################## getter methods ################################## 0135 // ############################################################################## 0136 BASIC_SHARED_D_READER_IMPL(Symbol, Symbol::Style, style, style) 0137 BASIC_SHARED_D_READER_IMPL(Symbol, qreal, opacity, opacity) 0138 BASIC_SHARED_D_READER_IMPL(Symbol, qreal, rotationAngle, rotationAngle) 0139 BASIC_SHARED_D_READER_IMPL(Symbol, qreal, size, size) 0140 BASIC_SHARED_D_READER_IMPL(Symbol, QBrush, brush, brush) 0141 BASIC_SHARED_D_READER_IMPL(Symbol, QPen, pen, pen) 0142 0143 // ############################################################################## 0144 // ################# setter methods and undo commands ########################## 0145 // ############################################################################## 0146 STD_SETTER_CMD_IMPL_F_S(Symbol, SetStyle, Symbol::Style, style, updateSymbols) 0147 void Symbol::setStyle(Symbol::Style style) { 0148 Q_D(Symbol); 0149 if (style != d->style) 0150 exec(new SymbolSetStyleCmd(d, style, ki18n("%1: set symbol style"))); 0151 } 0152 0153 STD_SETTER_CMD_IMPL_F_S(Symbol, SetSize, qreal, size, updateSymbols) 0154 void Symbol::setSize(qreal size) { 0155 Q_D(Symbol); 0156 if (!qFuzzyCompare(1 + size, 1 + d->size)) 0157 exec(new SymbolSetSizeCmd(d, size, ki18n("%1: set symbol size"))); 0158 } 0159 0160 STD_SETTER_CMD_IMPL_F_S(Symbol, SetColor, QColor, color, update) 0161 void Symbol::setColor(const QColor& color) { 0162 Q_D(Symbol); 0163 if (color != d->color) 0164 exec(new SymbolSetColorCmd(d, color, ki18n("%1: set symbol color"))); 0165 } 0166 0167 STD_SETTER_CMD_IMPL_F_S(Symbol, SetRotationAngle, qreal, rotationAngle, updateSymbols) 0168 void Symbol::setRotationAngle(qreal angle) { 0169 Q_D(Symbol); 0170 if (!qFuzzyCompare(1 + angle, 1 + d->rotationAngle)) 0171 exec(new SymbolSetRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); 0172 } 0173 0174 STD_SETTER_CMD_IMPL_F_S(Symbol, SetBrush, QBrush, brush, updatePixmap) 0175 void Symbol::setBrush(const QBrush& brush) { 0176 Q_D(Symbol); 0177 if (brush != d->brush) 0178 exec(new SymbolSetBrushCmd(d, brush, ki18n("%1: set symbol filling"))); 0179 } 0180 0181 STD_SETTER_CMD_IMPL_F_S(Symbol, SetPen, QPen, pen, updateSymbols) 0182 void Symbol::setPen(const QPen& pen) { 0183 Q_D(Symbol); 0184 if (pen != d->pen) 0185 exec(new SymbolSetPenCmd(d, pen, ki18n("%1: set symbol outline style"))); 0186 } 0187 0188 STD_SETTER_CMD_IMPL_F_S(Symbol, SetOpacity, qreal, opacity, updatePixmap) 0189 void Symbol::setOpacity(qreal opacity) { 0190 Q_D(Symbol); 0191 if (opacity != d->opacity) 0192 exec(new SymbolSetOpacityCmd(d, opacity, ki18n("%1: set symbols opacity"))); 0193 } 0194 0195 // ############################################################################## 0196 // ####################### Private implementation ############################### 0197 // ############################################################################## 0198 SymbolPrivate::SymbolPrivate(Symbol* owner) 0199 : q(owner) { 0200 } 0201 0202 QString SymbolPrivate::name() const { 0203 return q->parentAspect()->name(); 0204 } 0205 0206 void SymbolPrivate::update() { 0207 pen.setColor(color); 0208 brush.setColor(color); 0209 Q_EMIT q->updateRequested(); 0210 } 0211 0212 void SymbolPrivate::updateSymbols() { 0213 Q_EMIT q->updateRequested(); 0214 } 0215 0216 void SymbolPrivate::updatePixmap() { 0217 Q_EMIT q->updatePixmapRequested(); 0218 } 0219 0220 // ############################################################################## 0221 // ################## Serialization/Deserialization ########################### 0222 // ############################################################################## 0223 //! Save as XML 0224 void Symbol::save(QXmlStreamWriter* writer) const { 0225 Q_D(const Symbol); 0226 0227 if (parentAspect()->type() == AspectType::CustomPoint || parentAspect()->type() == AspectType::LollipopPlot) 0228 writer->writeStartElement(QStringLiteral("symbol")); 0229 else if (parentAspect()->type() == AspectType::BoxPlot) 0230 writer->writeStartElement(name()); // BoxPlot has multiple symbols, differentiated by their names 0231 else 0232 writer->writeStartElement(QStringLiteral("symbols")); // keep the backward compatibility for "symbols" used in XYCurve and Histogram 0233 0234 writer->writeAttribute(QStringLiteral("symbolsStyle"), QString::number(static_cast<int>(d->style))); 0235 writer->writeAttribute(QStringLiteral("opacity"), QString::number(d->opacity)); 0236 writer->writeAttribute(QStringLiteral("rotation"), QString::number(d->rotationAngle)); 0237 writer->writeAttribute(QStringLiteral("size"), QString::number(d->size)); 0238 WRITE_QBRUSH(d->brush); 0239 WRITE_QPEN(d->pen); 0240 writer->writeEndElement(); // close "Symbol" section 0241 } 0242 0243 //! Load from XML 0244 bool Symbol::load(XmlStreamReader* reader, bool preview) { 0245 if (preview) 0246 return true; 0247 0248 Q_D(Symbol); 0249 QString str; 0250 const auto& attribs = reader->attributes(); 0251 READ_INT_VALUE("symbolsStyle", style, Symbol::Style); 0252 READ_DOUBLE_VALUE("opacity", opacity); 0253 READ_DOUBLE_VALUE("rotation", rotationAngle); 0254 READ_DOUBLE_VALUE("size", size); 0255 READ_QBRUSH(d->brush); 0256 READ_QPEN(d->pen); 0257 0258 return true; 0259 } 0260 0261 // ############################################################################## 0262 // ######################### Theme management ################################## 0263 // ############################################################################## 0264 void Symbol::loadThemeConfig(const KConfigGroup& group, const QColor& themeColor) { 0265 setOpacity(group.readEntry("SymbolOpacity", 1.0)); 0266 QBrush brush; 0267 brush.setStyle((Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern)); 0268 brush.setColor(themeColor); 0269 setBrush(brush); 0270 0271 QPen p; 0272 p.setStyle((Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine)); 0273 p.setColor(themeColor); 0274 p.setWidthF(group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point))); 0275 setPen(p); 0276 } 0277 0278 void Symbol::saveThemeConfig(const KConfigGroup& /*group*/) const { 0279 // TODO: 0280 // group.writeEntry("SymbolOpacity", opacity()); 0281 } 0282 0283 //************************************************************* 0284 //********************* static functions ********************** 0285 //************************************************************* 0286 0287 int Symbol::stylesCount() { 0288 return ENUM_COUNT(Symbol, Style); 0289 } 0290 0291 QString Symbol::styleName(Symbol::Style style) { 0292 switch (style) { 0293 case Style::NoSymbols: 0294 return i18n("none"); 0295 case Style::Circle: 0296 return i18n("circle"); 0297 case Style::Square: 0298 return i18n("square"); 0299 case Style::EquilateralTriangle: 0300 return i18n("equilateral triangle"); 0301 case Style::RightTriangle: 0302 return i18n("right triangle"); 0303 case Style::Bar: 0304 return i18n("bar"); 0305 case Style::PeakedBar: 0306 return i18n("peaked bar"); 0307 case Style::SkewedBar: 0308 return i18n("skewed bar"); 0309 case Style::Diamond: 0310 return i18n("diamond"); 0311 case Style::Lozenge: 0312 return i18n("lozenge"); 0313 case Style::Tie: 0314 return i18n("tie"); 0315 case Style::TinyTie: 0316 return i18n("tiny tie"); 0317 case Style::Plus: 0318 return i18n("plus"); 0319 case Style::Boomerang: 0320 return i18n("boomerang"); 0321 case Style::SmallBoomerang: 0322 return i18n("small boomerang"); 0323 case Style::Star4: 0324 return i18n("star4"); 0325 case Style::Star5: 0326 return i18n("star5"); 0327 case Style::Line: 0328 return i18n("line"); 0329 case Style::Cross: 0330 return i18n("cross"); 0331 case Style::Heart: 0332 return i18n("heart"); 0333 case Style::Lightning: 0334 return i18n("lightning"); 0335 case Style::X: 0336 return i18n("character 'X'"); 0337 case Style::Asterisk: 0338 return i18n("asterisk"); 0339 case Style::Tri: 0340 return i18n("tri"); 0341 case Style::XPlus: 0342 return i18n("x plus"); 0343 case Style::TallPlus: 0344 return i18n("tall plus"); 0345 case Style::LatinCross: 0346 return i18n("latin cross"); 0347 case Style::DotPlus: 0348 return i18n("dot plus"); 0349 case Style::Hash: 0350 return i18n("hash"); 0351 case Style::SquareX: 0352 return i18n("square x"); 0353 case Style::SquarePlus: 0354 return i18n("square plus"); 0355 case Style::SquareHalf: 0356 return i18n("half square"); 0357 case Style::SquareDot: 0358 return i18n("square dot"); 0359 case Style::SquareDiag: 0360 return i18n("diag square"); 0361 case Style::SquareTriangle: 0362 return i18n("square triangle"); 0363 case Style::CircleHalf: 0364 return i18n("circle half"); 0365 case Style::CircleDot: 0366 return i18n("circle dot"); 0367 case Style::CircleX: 0368 return i18n("circle x"); 0369 case Style::CircleTri: 0370 return i18n("circle tri"); 0371 case Style::Peace: 0372 return i18n("peace"); 0373 case Style::TriangleDot: 0374 return i18n("triangle dot"); 0375 case Style::TriangleLine: 0376 return i18n("triangle line"); 0377 case Style::TriangleHalf: 0378 return i18n("half triangle"); 0379 case Style::Flower: 0380 return i18n("flower"); 0381 case Style::Flower2: 0382 return i18n("flower2"); 0383 case Style::Flower3: 0384 return i18n("flower3"); 0385 case Style::Flower5: 0386 return i18n("flower5"); 0387 case Style::Flower6: 0388 return i18n("flower6"); 0389 case Style::Star: 0390 return i18n("star"); 0391 case Style::Star3: 0392 return i18n("star3"); 0393 case Style::Star6: 0394 return i18n("star6"); 0395 case Style::Pentagon: 0396 return i18n("pentagon"); 0397 case Style::Hexagon: 0398 return i18n("hexagon"); 0399 case Style::Latin: 0400 return i18n("latin"); 0401 case Style::David: 0402 return i18n("david"); 0403 case Style::Home: 0404 return i18n("home"); 0405 case Style::Pin: 0406 return i18n("pin"); 0407 case Style::Female: 0408 return i18n("female"); 0409 case Style::Male: 0410 return i18n("male"); 0411 case Style::Spade: 0412 return i18n("spade"); 0413 case Style::Club: 0414 return i18n("club"); 0415 } 0416 0417 return {}; 0418 } 0419 0420 Symbol::Style Symbol::indexToStyle(const int index) { 0421 return StyleOrder.at(index); 0422 } 0423 0424 QPainterPath Symbol::stylePath(Symbol::Style style) { 0425 QPainterPath path; 0426 QPolygonF polygon; 0427 0428 switch (style) { 0429 case Style::NoSymbols: 0430 break; 0431 case Style::Circle: 0432 path.addEllipse(QPoint(0, 0), 0.5, 0.5); 0433 break; 0434 case Style::Square: 0435 path.addRect(QRectF(-0.5, -0.5, 1.0, 1.0)); 0436 break; 0437 case Style::EquilateralTriangle: 0438 polygon << QPointF(-0.5, 0.5) << QPointF(0, -0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, 0.5); 0439 path.addPolygon(polygon); 0440 break; 0441 case Style::RightTriangle: 0442 polygon << QPointF(-0.5, -0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, 0.5) << QPointF(-0.5, -0.5); 0443 path.addPolygon(polygon); 0444 break; 0445 case Style::Bar: 0446 path.addRect(QRectF(-0.5, -0.2, 1.0, 0.4)); 0447 break; 0448 case Style::PeakedBar: 0449 polygon << QPointF(-0.5, 0) << QPointF(-0.3, -0.2) << QPointF(0.3, -0.2) << QPointF(0.5, 0) << QPointF(0.3, 0.2) << QPointF(-0.3, 0.2) 0450 << QPointF(-0.5, 0); 0451 path.addPolygon(polygon); 0452 break; 0453 case Style::SkewedBar: 0454 polygon << QPointF(-0.5, 0.2) << QPointF(-0.2, -0.2) << QPointF(0.5, -0.2) << QPointF(0.2, 0.2) << QPointF(-0.5, 0.2); 0455 path.addPolygon(polygon); 0456 break; 0457 case Style::Diamond: 0458 polygon << QPointF(-0.5, 0) << QPointF(0, -0.5) << QPointF(0.5, 0) << QPointF(0, 0.5) << QPointF(-0.5, 0); 0459 path.addPolygon(polygon); 0460 break; 0461 case Style::Lozenge: 0462 polygon << QPointF(-0.25, 0) << QPointF(0, -0.5) << QPointF(0.25, 0) << QPointF(0, 0.5) << QPointF(-0.25, 0); 0463 path.addPolygon(polygon); 0464 break; 0465 case Style::Tie: 0466 polygon << QPointF(-0.5, -0.5) << QPointF(0.5, -0.5) << QPointF(-0.5, 0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, -0.5); 0467 path.addPolygon(polygon); 0468 break; 0469 case Style::TinyTie: 0470 polygon << QPointF(-0.2, -0.5) << QPointF(0.2, -0.5) << QPointF(-0.2, 0.5) << QPointF(0.2, 0.5) << QPointF(-0.2, -0.5); 0471 path.addPolygon(polygon); 0472 break; 0473 case Style::Plus: 0474 polygon << QPointF(-0.2, -0.5) << QPointF(0.2, -0.5) << QPointF(0.2, -0.2) << QPointF(0.5, -0.2) << QPointF(0.5, 0.2) << QPointF(0.2, 0.2) 0475 << QPointF(0.2, 0.5) << QPointF(-0.2, 0.5) << QPointF(-0.2, 0.2) << QPointF(-0.5, 0.2) << QPointF(-0.5, -0.2) << QPointF(-0.2, -0.2) 0476 << QPointF(-0.2, -0.5); 0477 path.addPolygon(polygon); 0478 break; 0479 case Style::Boomerang: 0480 polygon << QPointF(-0.5, 0.5) << QPointF(0, -0.5) << QPointF(0.5, 0.5) << QPointF(0, 0) << QPointF(-0.5, 0.5); 0481 path.addPolygon(polygon); 0482 break; 0483 case Style::SmallBoomerang: 0484 polygon << QPointF(-0.3, 0.5) << QPointF(0, -0.5) << QPointF(0.3, 0.5) << QPointF(0, 0) << QPointF(-0.3, 0.5); 0485 path.addPolygon(polygon); 0486 break; 0487 case Style::Star4: 0488 polygon << QPointF(-0.5, 0) << QPointF(-0.1, -0.1) << QPointF(0, -0.5) << QPointF(0.1, -0.1) << QPointF(0.5, 0) << QPointF(0.1, 0.1) << QPointF(0, 0.5) 0489 << QPointF(-0.1, 0.1) << QPointF(-0.5, 0); 0490 path.addPolygon(polygon); 0491 break; 0492 case Style::Star5: 0493 polygon << QPointF(-0.5, 0) << QPointF(-0.1, -0.1) << QPointF(0, -0.5) << QPointF(0.1, -0.1) << QPointF(0.5, 0) << QPointF(0.1, 0.1) 0494 << QPointF(0.5, 0.5) << QPointF(0, 0.2) << QPointF(-0.5, 0.5) << QPointF(-0.1, 0.1) << QPointF(-0.5, 0); 0495 path.addPolygon(polygon); 0496 break; 0497 case Style::Line: 0498 path = QPainterPath(QPointF(0, -0.5)); 0499 path.lineTo(0, 0.5); 0500 break; 0501 case Style::Cross: 0502 path = QPainterPath(QPointF(0, -0.5)); 0503 path.lineTo(0, 0.5); 0504 path.moveTo(-0.5, 0); 0505 path.lineTo(0.5, 0); 0506 break; 0507 case Style::Heart: { 0508 // https://mathworld.wolfram.com/HeartCurve.html with additional 0509 // normalization to fit into a 1.0x1.0 rectangular 0510 int steps = 100; 0511 double range = 2. * M_PI / (steps - 1); 0512 for (int i = 0; i < steps; ++i) { 0513 double t = i * range + M_PI / 2; 0514 double x = gsl_pow_3(sin(t)); 0515 double y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)) / 17; 0516 polygon << QPointF(x / 2, y / 2); 0517 } 0518 double t = M_PI / 2.; 0519 double x = gsl_pow_3(sin(t)); 0520 double y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)) / 17; 0521 polygon << QPointF(x / 2, y / 2); 0522 path.addPolygon(polygon); 0523 break; 0524 } 0525 case Style::Lightning: 0526 polygon << QPointF(0, 0.5) << QPointF(0.4, -0.03) << QPointF(0, -0.03) << QPointF(0.2, -0.5) << QPointF(-0.4, 0.1) << QPointF(0.06, 0.1) 0527 << QPointF(0, 0.5); 0528 path.addPolygon(polygon); 0529 break; 0530 case Style::X: 0531 path = QPainterPath(QPointF(-0.4, -0.5)); 0532 path.lineTo(0.4, 0.5); 0533 path.moveTo(0.4, -0.5); 0534 path.lineTo(-0.4, 0.5); 0535 break; 0536 case Style::Asterisk: 0537 path = QPainterPath(QPointF(0., .5)); 0538 path.lineTo(0., -.5); 0539 path.moveTo(M_SQRT3 / 4., -.25); 0540 path.lineTo(-M_SQRT3 / 4., .25); 0541 path.moveTo(M_SQRT3 / 4., .25); 0542 path.lineTo(-M_SQRT3 / 4., -.25); 0543 break; 0544 case Style::Tri: 0545 path = QPainterPath(QPointF(0., 0.)); 0546 path.lineTo(0., -1.); 0547 path.moveTo(0., 0.); 0548 path.lineTo(-M_SQRT3 / 2., 1. / 2.); 0549 path.moveTo(0., 0.); 0550 path.lineTo(M_SQRT3 / 2., 1. / 2.); 0551 break; 0552 case Style::XPlus: 0553 path = QPainterPath(QPointF(.5, 0.)); 0554 path.lineTo(-.5, 0.); 0555 path.moveTo(0., .5); 0556 path.lineTo(0., -.5); 0557 path.moveTo(.5 / M_SQRT2, .5 / M_SQRT2); 0558 path.lineTo(-.5 / M_SQRT2, -.5 / M_SQRT2); 0559 path.moveTo(.5 / M_SQRT2, -.5 / M_SQRT2); 0560 path.lineTo(-.5 / M_SQRT2, .5 / M_SQRT2); 0561 break; 0562 case Style::TallPlus: 0563 path = QPainterPath(QPointF(.25, 0.)); 0564 path.lineTo(-.25, 0.); 0565 path.moveTo(0., .5); 0566 path.lineTo(0., -.5); 0567 break; 0568 case Style::LatinCross: 0569 path = QPainterPath(QPointF(0., .5)); 0570 path.lineTo(0., -.5); 0571 path.moveTo(-1. / 3., -1. / 6.); 0572 path.lineTo(1. / 3., -1. / 6.); 0573 break; 0574 case Style::DotPlus: 0575 path = QPainterPath(QPointF(0., .5)); 0576 path.lineTo(0., .25); 0577 path.moveTo(0., -.5); 0578 path.lineTo(0., -.25); 0579 path.moveTo(.5, 0.); 0580 path.lineTo(.25, 0.); 0581 path.moveTo(-.5, 0.); 0582 path.lineTo(-.25, 0.); 0583 path.addEllipse(-.05, -.05, .1, .1); 0584 break; 0585 case Style::Hash: 0586 path = QPainterPath(QPointF(-.25, .5)); 0587 path.lineTo(-.25, -.5); 0588 path.moveTo(.25, .5); 0589 path.lineTo(.25, -.5); 0590 path.moveTo(.5, .25); 0591 path.lineTo(-.5, .25); 0592 path.moveTo(.5, -.25); 0593 path.lineTo(-.5, -.25); 0594 break; 0595 case Style::SquareX: 0596 path = QPainterPath(QPointF(-.5, .5)); 0597 path.lineTo(.5, -.5); 0598 path.lineTo(-.5, -.5); 0599 path.lineTo(.5, .5); 0600 path.lineTo(-.5, .5); 0601 path.moveTo(-.5, .5); 0602 path.lineTo(-.5, -.5); 0603 path.moveTo(.5, .5); 0604 path.lineTo(.5, -.5); 0605 break; 0606 case Style::SquarePlus: 0607 path = QPainterPath(QPointF(-.5, .5)); 0608 path.lineTo(-.5, 0.); 0609 path.lineTo(.5, 0.); 0610 path.lineTo(.5, -.5); 0611 path.lineTo(0., -.5); 0612 path.lineTo(0., .5); 0613 path.lineTo(-.5, .5); 0614 path.moveTo(.5, .5); 0615 path.lineTo(0., .5); 0616 path.moveTo(.5, .5); 0617 path.lineTo(.5, 0); 0618 path.moveTo(-.5, -.5); 0619 path.lineTo(0., -.5); 0620 path.moveTo(-.5, -.5); 0621 path.lineTo(-.5, 0); 0622 break; 0623 case Style::SquareHalf: 0624 path = QPainterPath(QPointF(-.5, .5)); 0625 path.lineTo(.5, .5); 0626 path.lineTo(.5, 0.); 0627 path.lineTo(-.5, 0.); 0628 path.lineTo(-.5, .5); 0629 path.moveTo(-.5, -.5); 0630 path.lineTo(-.5, 0); 0631 path.moveTo(-.5, -.5); 0632 path.lineTo(.5, -.5); 0633 path.moveTo(.5, -.5); 0634 path.lineTo(.5, 0.); 0635 break; 0636 case Style::SquareDot: 0637 path.addEllipse(-.1, -.1, .2, .2); 0638 path.addRect(QRectF(-0.5, -0.5, 1.0, 1.0)); 0639 break; 0640 case Style::SquareDiag: 0641 path = QPainterPath(QPointF(-.5, .5)); 0642 path.lineTo(.5, .5); 0643 path.lineTo(-.5, -.5); 0644 path.lineTo(-.5, .5); 0645 path.moveTo(.5, -.5); 0646 path.lineTo(.5, .5); 0647 path.moveTo(.5, -.5); 0648 path.lineTo(-.5, -.5); 0649 break; 0650 case Style::SquareTriangle: 0651 path = QPainterPath(QPointF(-.5, .5)); 0652 path.lineTo(0, -.5); 0653 path.lineTo(.5, .5); 0654 path.lineTo(-.5, .5); 0655 path.moveTo(.5, -.5); 0656 path.lineTo(-.5, -.5); 0657 path.moveTo(.5, -.5); 0658 path.lineTo(.5, .5); 0659 path.moveTo(-.5, -.5); 0660 path.lineTo(-.5, .5); 0661 break; 0662 case Style::CircleHalf: 0663 path = QPainterPath(QPointF(0., .5)); 0664 path.arcTo(-.5, -.5, 1., 1., -90., 180.); 0665 path.closeSubpath(); 0666 path.addEllipse(QPoint(0, 0), 0.5, 0.5); 0667 break; 0668 case Style::CircleDot: 0669 path.addEllipse(-.1, -.1, .2, .2); 0670 path.addEllipse(QPoint(0, 0), 0.5, 0.5); 0671 break; 0672 case Style::CircleX: 0673 path = QPainterPath(QPointF(.5 / M_SQRT2, .5 / M_SQRT2)); 0674 path.lineTo(-.5 / M_SQRT2, -.5 / M_SQRT2); 0675 path.arcTo(-.5, -.5, 1., 1., -45., 90.); 0676 path.lineTo(-.5 / M_SQRT2, .5 / M_SQRT2); 0677 path.arcTo(-.5, -.5, 1., 1., 225., -90.); 0678 path.closeSubpath(); 0679 path.addEllipse(QPoint(0, 0), 0.5, 0.5); 0680 break; 0681 case Style::CircleTri: 0682 path.addEllipse(QPoint(0, 0), 0.5, 0.5); 0683 path.moveTo(0., 0.); 0684 path.lineTo(0., -.5); 0685 path.moveTo(0., 0.); 0686 path.lineTo(-M_SQRT3 / 4., 1. / 4.); 0687 path.moveTo(0., 0.); 0688 path.lineTo(M_SQRT3 / 4., 1. / 4.); 0689 break; 0690 case Style::Peace: 0691 path = QPainterPath(QPointF(0, .5)); 0692 path.lineTo(0, -.5); 0693 path.moveTo(0., 0.); 0694 path.lineTo(-.5 / M_SQRT2, .5 / M_SQRT2); 0695 path.moveTo(0., 0.); 0696 path.lineTo(.5 / M_SQRT2, .5 / M_SQRT2); 0697 path.closeSubpath(); 0698 path.addEllipse(QPoint(0, 0), 0.5, 0.5); 0699 break; 0700 case Style::TriangleDot: 0701 path.addEllipse(-.1, -.1, .2, .2); 0702 polygon << QPointF(-0.5, 0.5) << QPointF(0, -0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, 0.5); 0703 path.addPolygon(polygon); 0704 break; 0705 case Style::TriangleHalf: 0706 path = QPainterPath(QPointF(-.25, 0)); 0707 path.lineTo(0, -.5); 0708 path.lineTo(.25, 0); 0709 path.closeSubpath(); 0710 path.moveTo(.5, .5); 0711 path.lineTo(.25, 0); 0712 path.moveTo(.5, .5); 0713 path.lineTo(-.5, .5); 0714 path.moveTo(-.5, .5); 0715 path.lineTo(-.25, 0); 0716 break; 0717 case Style::TriangleLine: 0718 path = QPainterPath(QPointF(-.5, .5)); 0719 path.lineTo(0, -.5); 0720 path.lineTo(0, .5); 0721 path.closeSubpath(); 0722 path.moveTo(.5, .5); 0723 path.lineTo(0, .5); 0724 path.moveTo(.5, .5); 0725 path.lineTo(0, -.5); 0726 break; 0727 case Style::Flower: { 0728 int steps = 100; 0729 double range = 2. * M_PI / (steps - 1); 0730 for (int i = 0; i < steps; ++i) { 0731 double t = i * range; 0732 double r = sin(2 * t) * sin(2 * t); 0733 double x = r * sin(t); 0734 double y = r * cos(t); 0735 polygon << QPointF(x / 2, y / 2); 0736 } 0737 path.addPolygon(polygon); 0738 break; 0739 } 0740 case Style::Flower2: { 0741 int steps = 100; 0742 double range = 2. * M_PI / (steps - 1); 0743 for (int i = 0; i < steps; ++i) { 0744 double t = i * range; 0745 double r = sin(t) * sin(t); 0746 double x = r * sin(t); 0747 double y = r * cos(t); 0748 polygon << QPointF(x / 2, y / 2); 0749 } 0750 path.addPolygon(polygon); 0751 break; 0752 } 0753 case Style::Flower3: { 0754 int steps = 100; 0755 double range = 2. * M_PI / (steps - 1); 0756 for (int i = 0; i < steps; ++i) { 0757 double t = i * range; 0758 double r = sin(3. * t / 2.) * sin(3. * t / 2.); 0759 double x = r * sin(t); 0760 double y = r * cos(t); 0761 polygon << QPointF(x / 2, y / 2); 0762 } 0763 path.addPolygon(polygon); 0764 break; 0765 } 0766 case Style::Flower5: { 0767 int steps = 100; 0768 double range = 2. * M_PI / (steps - 1); 0769 for (int i = 0; i < steps; ++i) { 0770 double t = i * range; 0771 double r = sin(5. * t / 2.) * sin(5. * t / 2.); 0772 double x = r * sin(t); 0773 double y = r * cos(t); 0774 polygon << QPointF(x / 2, y / 2); 0775 } 0776 path.addPolygon(polygon); 0777 break; 0778 } 0779 case Style::Flower6: { 0780 int steps = 100; 0781 double range = 2. * M_PI / (steps - 1); 0782 for (int i = 0; i < steps; ++i) { 0783 double t = i * range; 0784 double r = sin(3. * t) * sin(3. * t); 0785 double x = r * sin(t); 0786 double y = r * cos(t); 0787 polygon << QPointF(x / 2, y / 2); 0788 } 0789 path.addPolygon(polygon); 0790 break; 0791 } 0792 case Style::Star: 0793 for (int i = 0; i < 5; i++) { 0794 double angle = 2. * M_PI * i / 5. - M_PI / 10.; 0795 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0796 polygon << QPointF(.2 * cos(angle + M_PI / 5.), .2 * sin(angle + M_PI / 5.)); 0797 } 0798 path.addPolygon(polygon); 0799 path.closeSubpath(); 0800 break; 0801 case Style::Star3: 0802 for (int i = 0; i < 3; i++) { 0803 double angle = 2. * M_PI * i / 3. + M_PI / 6.; 0804 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0805 polygon << QPointF(.1 * cos(angle + M_PI / 3.), .1 * sin(angle + M_PI / 3.)); 0806 } 0807 path.addPolygon(polygon); 0808 path.closeSubpath(); 0809 break; 0810 case Style::Star6: 0811 for (int i = 0; i < 6; i++) { 0812 double angle = 2. * M_PI * i / 6.; 0813 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0814 polygon << QPointF(.1 * cos(angle + M_PI / 6.), .1 * sin(angle + M_PI / 6.)); 0815 } 0816 path.addPolygon(polygon); 0817 path.closeSubpath(); 0818 break; 0819 case Style::Pentagon: 0820 for (int i = 0; i < 5; i++) { 0821 double angle = 2. * M_PI * i / 5. - M_PI / 10.; 0822 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0823 } 0824 path.addPolygon(polygon); 0825 path.closeSubpath(); 0826 break; 0827 case Style::Hexagon: 0828 for (int i = 0; i < 6; i++) { 0829 double angle = 2. * M_PI * i / 6.; 0830 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0831 } 0832 path.addPolygon(polygon); 0833 path.closeSubpath(); 0834 break; 0835 case Style::Latin: 0836 polygon << QPointF(-0.1, -0.5) << QPointF(0.1, -0.5) << QPointF(0.1, -0.3) << QPointF(0.5, -0.3) << QPointF(0.5, -0.1) << QPointF(0.1, -0.1) 0837 << QPointF(0.1, 0.5) << QPointF(-0.1, 0.5) << QPointF(-0.1, -0.1) << QPointF(-0.5, -0.1) << QPointF(-0.5, -0.3) << QPointF(-0.1, -0.3) 0838 << QPointF(-0.1, -0.5); 0839 path.addPolygon(polygon); 0840 break; 0841 case Style::David: 0842 for (int i = 0; i < 4; i++) { 0843 double angle = 2. * M_PI * i / 3. + M_PI / 6.; 0844 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0845 } 0846 path.setFillRule(Qt::FillRule::WindingFill); 0847 path.addPolygon(polygon); 0848 polygon.clear(); 0849 for (int i = 0; i < 4; i++) { 0850 double angle = 2. * M_PI * i / 3. - M_PI / 6.; 0851 polygon << QPointF(.5 * cos(angle), .5 * sin(angle)); 0852 } 0853 0854 path.addPolygon(polygon); 0855 break; 0856 case Style::Home: 0857 path = QPainterPath(QPointF(-.25, .25)); 0858 path.lineTo(.25, .25); 0859 path.lineTo(.25, -.25); 0860 path.lineTo(0., -.5); 0861 path.lineTo(-.25, -.25); 0862 path.lineTo(-.25, .25); 0863 break; 0864 case Style::Pin: 0865 path = QPainterPath(QPointF(0., 0.)); 0866 path.lineTo(0., -.3); 0867 path.closeSubpath(); 0868 path.addEllipse(QPointF(0., -.4), 0.2, 0.2); 0869 break; 0870 case Style::Female: 0871 path.addEllipse(QPointF(0., 0.), .25, .25); 0872 path.closeSubpath(); 0873 path.moveTo(0., .25); 0874 path.lineTo(0., .5); 0875 path.moveTo(-.15, .375); 0876 path.lineTo(.15, .375); 0877 break; 0878 case Style::Male: 0879 path.addEllipse(QPointF(0., 0.), .3, .3); 0880 path.closeSubpath(); 0881 path.moveTo(.3 / M_SQRT2, -.3 / M_SQRT2); 0882 path.lineTo(.5, -.5); 0883 path.moveTo(.5, -.5); 0884 path.lineTo(.35, -.5); 0885 path.moveTo(.5, -.5); 0886 path.lineTo(.5, -.35); 0887 break; 0888 case Style::Spade: { 0889 QFont font(QStringLiteral("Times"), 1); 0890 path.addText(-.3, .3, font, UTF8_QSTRING("♠")); 0891 break; 0892 } 0893 case Style::Club: 0894 QFont font(QStringLiteral("Times"), 1); 0895 path.addText(-.3, .3, font, UTF8_QSTRING("♣")); 0896 break; 0897 } 0898 0899 return path; 0900 } 0901 0902 void Symbol::draw(QPainter* painter, QPointF point) { 0903 Q_D(const Symbol); 0904 if (d->style == Symbol::Style::NoSymbols) 0905 return; 0906 0907 painter->setOpacity(d->opacity); 0908 painter->setPen(d->pen); 0909 painter->setBrush(d->brush); 0910 QTransform trafo; 0911 trafo.scale(d->size, d->size); 0912 QPainterPath path = Symbol::stylePath(d->style); 0913 if (d->rotationAngle != 0) 0914 trafo.rotate(-d->rotationAngle); 0915 0916 path = trafo.map(path); 0917 0918 trafo.reset(); 0919 trafo.translate(point.x(), point.y()); 0920 painter->drawPath(trafo.map(path)); 0921 } 0922 0923 void Symbol::draw(QPainter* painter, const QVector<QPointF>& points) { 0924 Q_D(const Symbol); 0925 if (d->style == Symbol::Style::NoSymbols || points.isEmpty()) 0926 return; 0927 0928 painter->setOpacity(d->opacity); 0929 painter->setPen(d->pen); 0930 painter->setBrush(d->brush); 0931 QPainterPath path = Symbol::stylePath(d->style); 0932 QTransform trafo; 0933 trafo.scale(d->size, d->size); 0934 if (d->rotationAngle != 0) 0935 trafo.rotate(-d->rotationAngle); 0936 0937 path = trafo.map(path); 0938 0939 for (const auto& point : points) { 0940 trafo.reset(); 0941 trafo.translate(point.x(), point.y()); 0942 painter->drawPath(trafo.map(path)); 0943 } 0944 }