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 }