/* Copyright 2006-2020 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #include "elementpicturefactory.h" #include "elementslocation.h" #include "qet.h" #include "qetapp.h" #include "partline.h" #include #include #include #include #include #include #include #include ElementPictureFactory* ElementPictureFactory::m_factory = nullptr; /** @brief ElementPictureFactory::getPictures Set the picture of the element at location. Note, picture can be null @param location @param picture @param low_picture */ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPicture &picture, QPicture &low_picture) { if(!location.exist()) { return; } QUuid uuid = location.uuid(); if(Q_UNLIKELY(uuid.isNull())) { build(location, &picture, &low_picture); return; } if(m_pictures_H.keys().contains(uuid)) { picture = m_pictures_H.value(uuid); low_picture = m_low_pictures_H.value(uuid); } else { if (build(location)) { picture = m_pictures_H.value(uuid); low_picture = m_low_pictures_H.value(uuid); } } } /** @brief ElementPictureFactory::pixmap @param location @return the pixmap of the element at location Note pixmap can be null */ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location) { QUuid uuid = location.uuid(); if (m_pixmap_H.contains(uuid)) { return m_pixmap_H.value(uuid); } if(build(location)) { auto doc = location.pugiXml(); //size int w = doc.document_element().attribute("width").as_int(); int h = doc.document_element().attribute("height").as_int(); while (w % 10) ++ w; while (h % 10) ++ h; //hotspot int hsx = qMin(doc.document_element().attribute("hotspot_x").as_int(), w); int hsy = qMin(doc.document_element().attribute("hotspot_y").as_int(), h); QPixmap pix(w, h); pix.fill(QColor(255, 255, 255, 0)); QPainter painter(&pix); painter.setRenderHint(QPainter::Antialiasing, true); painter.setRenderHint(QPainter::SmoothPixmapTransform, true); painter.translate(hsx, hsy); painter.drawPicture(0, 0, m_pictures_H.value(uuid)); if (!uuid.isNull()) { m_pixmap_H.insert(uuid, pix); } return pix; } return QPixmap(); } /** @brief ElementPictureFactory::getPrimitives @param location @return The primtive used to draw the element at location */ ElementPictureFactory::primitives ElementPictureFactory::getPrimitives( const ElementsLocation &location) { if(!m_primitives_H.contains(location.uuid())) build(location); return m_primitives_H.value(location.uuid()); } ElementPictureFactory::~ElementPictureFactory() { for (primitives p : m_primitives_H.values()) { qDeleteAll(p.m_texts); } } /** @brief ElementPictureFactory::build Build the picture from location. @param location @param picture @param low_picture if picture and/or low_picture are not null this function draw on it and don't store it. if null, this function create a QPicture for normal and low zoom, draw on it and store it in m_pictures_H and m_low_pictures_H @return */ bool ElementPictureFactory::build(const ElementsLocation &location, QPicture *picture, QPicture *low_picture) { QDomElement dom = location.xml(); //Check if the curent version can read the xml description if (dom.hasAttribute("version")) { bool conv_ok; qreal element_version = dom.attribute("version").toDouble(&conv_ok); if (conv_ok && QET::version.toDouble() < element_version) { std::cerr << qPrintable( QObject::tr("Avertissement : l'élément " " a été enregistré avec une version" " ultérieure de QElectroTech.") ) << std::endl; } } //This attributes must be present and valid int w, h, hot_x, hot_y; if (!QET::attributeIsAnInteger(dom, QString("width"), &w) ||\ !QET::attributeIsAnInteger(dom, QString("height"), &h) ||\ !QET::attributeIsAnInteger(dom, QString("hotspot_x"), &hot_x) ||\ !QET::attributeIsAnInteger(dom, QString("hotspot_y"), &hot_y)) { return(false); } QPainter painter; QPicture pic; primitives primitives_; if (picture) { painter.begin(picture); } else { painter.begin(&pic); } painter.setRenderHint(QPainter::Antialiasing, true); painter.setRenderHint(QPainter::TextAntialiasing, true); painter.setRenderHint(QPainter::SmoothPixmapTransform,true); QPainter low_painter; QPicture low_pic; if (low_picture) { low_painter.begin(low_picture); } else { low_painter.begin(&low_pic); } low_painter.setRenderHint(QPainter::Antialiasing, true); low_painter.setRenderHint(QPainter::TextAntialiasing, true); low_painter.setRenderHint(QPainter::SmoothPixmapTransform,true); QPen tmp; tmp.setWidthF(1.0); //Vaudoo line to take into account the setCosmetic - don't remove tmp.setCosmetic(true); low_painter.setPen(tmp); //scroll of the Children of the Definition: Parts of the Drawing for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling()) { QDomElement elmts = node.toElement(); if (elmts.isNull()) { continue; } if (elmts.tagName() == "description") { //Manage the graphic description = part of drawing for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) { QDomElement qde = n.toElement(); if (qde.isNull()) { continue; } parseElement(qde, painter, primitives_); primitives fake_prim; parseElement(qde, low_painter, fake_prim); } } } //End of the drawing painter.end(); low_painter.end(); if (!picture) { m_pictures_H.insert(location.uuid(), pic); m_primitives_H.insert(location.uuid(), primitives_); } if (!low_picture) { m_low_pictures_H.insert(location.uuid(), low_pic); m_primitives_H.insert(location.uuid(), primitives_); } return true; } void ElementPictureFactory::parseElement(const QDomElement &dom, QPainter &painter, primitives &prim) const { if (dom.tagName() == "line") (parseLine (dom, painter, prim)); else if (dom.tagName() == "rect") (parseRect (dom, painter, prim)); else if (dom.tagName() == "ellipse") (parseEllipse(dom, painter, prim)); else if (dom.tagName() == "circle") (parseCircle (dom, painter, prim)); else if (dom.tagName() == "arc") (parseArc (dom, painter, prim)); else if (dom.tagName() == "polygon") (parsePolygon(dom, painter, prim)); else if (dom.tagName() == "text") (parseText (dom, painter, prim)); } void ElementPictureFactory::parseLine(const QDomElement &dom, QPainter &painter, primitives &prim) const { //This attributes must be present and valid qreal x1, y1, x2, y2; if (!QET::attributeIsAReal(dom, QString("x1"), &x1)) return; if (!QET::attributeIsAReal(dom, QString("y1"), &y1)) return; if (!QET::attributeIsAReal(dom, QString("x2"), &x2)) return; if (!QET::attributeIsAReal(dom, QString("y2"), &y2)) return; Qet::EndType first_end = Qet::endTypeFromString(dom.attribute("end1")); Qet::EndType second_end = Qet::endTypeFromString(dom.attribute("end2")); qreal length1, length2; if (!QET::attributeIsAReal(dom, QString("length1"), &length1)) length1 = 1.5; if (!QET::attributeIsAReal(dom, QString("length2"), &length2)) length2 = 1.5; painter.save(); setPainterStyle(dom, painter); QPen t = painter.pen(); t.setJoinStyle(Qt::MiterJoin); painter.setPen(t); QLineF line(x1, y1, x2, y2); prim.m_lines << line; QPointF point1(line.p1()); QPointF point2(line.p2()); qreal line_length(line.length()); qreal pen_width = painter.pen().widthF(); //Check if we must to draw extremity bool draw_1st_end, draw_2nd_end; qreal reduced_line_length = line_length - (length1 * PartLine::requiredLengthForEndType(first_end)); draw_1st_end = first_end && reduced_line_length >= 0; if (draw_1st_end) { reduced_line_length -= (length2 * PartLine::requiredLengthForEndType(second_end)); } else { reduced_line_length = line_length - (length2 * PartLine::requiredLengthForEndType(second_end)); } draw_2nd_end = second_end && reduced_line_length >= 0; //Draw first extremity QPointF start_point, stop_point; if (draw_1st_end) { QList four_points1(PartLine::fourEndPoints(point1, point2, length1)); if (first_end == Qet::Circle) { painter.drawEllipse(QRectF(four_points1[0] - QPointF(length1, length1), QSizeF(length1 * 2.0, length1 * 2.0))); start_point = four_points1[1]; } else if (first_end == Qet::Diamond) { painter.drawPolygon(QPolygonF() << four_points1[1] << four_points1[2] << point1 << four_points1[3]); start_point = four_points1[1]; } else if (first_end == Qet::Simple) { painter.drawPolyline(QPolygonF() << four_points1[3] << point1 << four_points1[2]); start_point = point1; } else if (first_end == Qet::Triangle) { painter.drawPolygon(QPolygonF() << four_points1[0] << four_points1[2] << point1 << four_points1[3]); start_point = four_points1[0]; } //Adjust the begining according to the width of the pen if (pen_width && (first_end == Qet::Simple || first_end == Qet::Circle)) { start_point = QLineF(start_point, point2).pointAt(pen_width / 2.0 / line_length); } } else { start_point = point1; } //Draw second extremity if (draw_2nd_end) { QList four_points2(PartLine::fourEndPoints(point2, point1, length2)); if (second_end == Qet::Circle) { painter.drawEllipse(QRectF(four_points2[0] - QPointF(length2, length2), QSizeF(length2 * 2.0, length2 * 2.0))); stop_point = four_points2[1]; } else if (second_end == Qet::Diamond) { painter.drawPolygon(QPolygonF() << four_points2[2] << point2 << four_points2[3] << four_points2[1]); stop_point = four_points2[1]; } else if (second_end == Qet::Simple) { painter.drawPolyline(QPolygonF() << four_points2[3] << point2 << four_points2[2]); stop_point = point2; } else if (second_end == Qet::Triangle) { painter.drawPolygon(QPolygonF() << four_points2[0] << four_points2[2] << point2 << four_points2[3] << four_points2[0]); stop_point = four_points2[0]; } //Adjust the end according to the width of the pen if (pen_width && (second_end == Qet::Simple || second_end == Qet::Circle)) { stop_point = QLineF(point1, stop_point).pointAt((line_length - (pen_width / 2.0)) / line_length); } } else { stop_point = point2; } painter.drawLine(start_point, stop_point); painter.restore(); } void ElementPictureFactory::parseRect(const QDomElement &dom, QPainter &painter, ElementPictureFactory::primitives &prim) const { //This attributes must be present and valid qreal rect_x, rect_y, rect_w, rect_h, rect_rx, rect_ry; if (!QET::attributeIsAReal(dom, QString("x"), &rect_x)) return; if (!QET::attributeIsAReal(dom, QString("y"), &rect_y)) return; if (!QET::attributeIsAReal(dom, QString("width"), &rect_w)) return; if (!QET::attributeIsAReal(dom, QString("height"), &rect_h)) return; rect_rx = dom.attribute("rx", "0").toDouble(); rect_ry = dom.attribute("ry", "0").toDouble(); prim.m_rectangles << QRectF(rect_x, rect_y, rect_w, rect_h); painter.save(); setPainterStyle(dom, painter); QPen p = painter.pen(); p.setJoinStyle(Qt::MiterJoin); painter.setPen(p); painter.drawRoundedRect(QRectF(rect_x, rect_y, rect_w, rect_h), rect_rx, rect_ry); painter.restore(); } void ElementPictureFactory::parseEllipse(const QDomElement &dom, QPainter &painter, ElementPictureFactory::primitives &prim) const { //This attributes must be present and valid qreal ellipse_x, ellipse_y, ellipse_l, ellipse_h; if (!QET::attributeIsAReal(dom, QString("x"), &ellipse_x)) return; if (!QET::attributeIsAReal(dom, QString("y"), &ellipse_y)) return; if (!QET::attributeIsAReal(dom, QString("width"), &ellipse_l)) return; if (!QET::attributeIsAReal(dom, QString("height"), &ellipse_h)) return; painter.save(); setPainterStyle(dom, painter); QVector arc; arc.push_back(ellipse_x); arc.push_back(ellipse_y); arc.push_back(ellipse_l); arc.push_back(ellipse_h); arc.push_back(0); arc.push_back(360); prim.m_arcs << arc; painter.drawEllipse(QRectF(ellipse_x, ellipse_y, ellipse_l, ellipse_h)); painter.restore(); } void ElementPictureFactory::parseCircle(const QDomElement &dom, QPainter &painter, ElementPictureFactory::primitives &prim) const { //This attributes must be present and valid qreal cercle_x, cercle_y, cercle_r; if (!QET::attributeIsAReal(dom, QString("x"), &cercle_x)) return; if (!QET::attributeIsAReal(dom, QString("y"), &cercle_y)) return; if (!QET::attributeIsAReal(dom, QString("diameter"), &cercle_r)) return; painter.save(); setPainterStyle(dom, painter); QRectF circle_bounding_rect(cercle_x, cercle_y, cercle_r, cercle_r); prim.m_circles << circle_bounding_rect; painter.drawEllipse(circle_bounding_rect); painter.restore(); } void ElementPictureFactory::parseArc(const QDomElement &dom, QPainter &painter, ElementPictureFactory::primitives &prim) const { //This attributes must be present and valid qreal arc_x, arc_y, arc_l, arc_h, arc_s, arc_a; if (!QET::attributeIsAReal(dom, QString("x"), &arc_x)) return; if (!QET::attributeIsAReal(dom, QString("y"), &arc_y)) return; if (!QET::attributeIsAReal(dom, QString("width"), &arc_l)) return; if (!QET::attributeIsAReal(dom, QString("height"), &arc_h)) return; if (!QET::attributeIsAReal(dom, QString("start"), &arc_s)) return; if (!QET::attributeIsAReal(dom, QString("angle"), &arc_a)) return; painter.save(); setPainterStyle(dom, painter); QVector arc; arc.push_back(arc_x); arc.push_back(arc_y); arc.push_back(arc_l); arc.push_back(arc_h); arc.push_back(arc_s); arc.push_back(arc_a); prim.m_arcs << arc; painter.drawArc(QRectF(arc_x, arc_y, arc_l, arc_h), (int)(arc_s * 16), (int)(arc_a * 16)); painter.restore(); } void ElementPictureFactory::parsePolygon(const QDomElement &dom, QPainter &painter, ElementPictureFactory::primitives &prim) const { int i = 1; while(true) { if (QET::attributeIsAReal(dom, QString("x%1").arg(i)) && QET::attributeIsAReal(dom, QString("y%1").arg(i))) ++ i; else break; } if (i < 3) { return; } QVector points; // empty vector created instead of default initialized vector with i-1 elements. for (int j = 1 ; j < i ; ++ j) { points.insert( j - 1, QPointF( dom.attribute(QString("x%1").arg(j)).toDouble(), dom.attribute(QString("y%1").arg(j)).toDouble() ) ); } painter.save(); setPainterStyle(dom, painter); if (dom.attribute("closed") == "false") painter.drawPolyline(points.data(), i-1); else { painter.drawPolygon(points.data(), i-1); // insert first point at the end again for DXF export. points.push_back(points[0]); } prim.m_polygons << points; painter.restore(); } void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter, ElementPictureFactory::primitives &prim) const { Q_UNUSED(prim); if (dom.tagName() != "text") { return; } painter.save(); setPainterStyle(dom, painter); //Get the font and metric QFont font_; if (dom.hasAttribute("size")) { font_ = QETApp::diagramTextsFont(dom.attribute("size").toDouble()); } else if (dom.hasAttribute("font")) { font_.fromString(dom.attribute("font")); } QColor text_color(dom.attribute("color", "#000000")); //Instanciate a QTextDocument (like the QGraphicsTextItem class) //for generate the graphics rendering of the text QTextDocument text_document; text_document.setDefaultFont(font_); text_document.setPlainText(dom.attribute("text")); painter.setTransform(QTransform(), false); painter.translate(dom.attribute("x").toDouble(), dom.attribute("y").toDouble()); painter.rotate(dom.attribute("rotation", "0").toDouble()); /* Deplace le systeme de coordonnees du QPainter pour effectuer le rendu au bon endroit ; note : on soustrait l'ascent() de la police pour determiner le coin superieur gauche du texte alors que la position indiquee correspond a la baseline. */ QFontMetrics qfm(font_); QPointF qpainter_offset(0.0, -qfm.ascent()); //adjusts the offset by the margin of the text document text_document.setDocumentMargin(0.0); painter.translate(qpainter_offset); // force the palette used to render the QTextDocument QAbstractTextDocumentLayout::PaintContext ctx; ctx.palette.setColor(QPalette::Text, text_color); text_document.documentLayout() -> draw(&painter, ctx); //A very dirty workaround for export this text to dxf QGraphicsSimpleTextItem *qgsti = new QGraphicsSimpleTextItem(); qgsti->setText(dom.attribute("text")); qgsti->setFont(font_); qgsti->setPos(dom.attribute("x").toDouble(), dom.attribute("y").toDouble()); qgsti->setRotation(dom.attribute("rotation", "0").toDouble()); prim.m_texts << qgsti; painter.restore(); } /** @brief ElementPictureFactory::setPainterStyle apply the style store in dom to painter. @param dom @param painter */ void ElementPictureFactory::setPainterStyle(const QDomElement &dom, QPainter &painter) const { QPen pen = painter.pen(); QBrush brush = painter.brush(); pen.setJoinStyle(Qt::BevelJoin); pen.setCapStyle(Qt::SquareCap); //Get the couples style/value #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove const QStringList styles = dom.attribute("style").split(";", QString::SkipEmptyParts); #else #if TODO_LIST #pragma message("@TODO remove code for QT 5.14 or later") #endif const QStringList styles = dom.attribute("style").split(";", Qt::SkipEmptyParts); #endif QRegularExpression rx("^(?[a-z-]+):(?[a-z-]+)$"); if (!rx.isValid()) { qWarning() <