/* 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 "element.h" #include "diagram.h" #include "conductor.h" #include "diagramcommands.h" #include #include "elementprovider.h" #include "diagramposition.h" #include "terminal.h" #include "terminaldata.h" #include "PropertiesEditor/propertieseditordialog.h" #include "elementpropertieswidget.h" #include "numerotationcontextcommands.h" #include "diagramcontext.h" #include "changeelementinformationcommand.h" #include "dynamicelementtextitem.h" #include "elementtextitemgroup.h" #include "elementpicturefactory.h" #include "iostream" class ElementXmlRetroCompatibility { friend class Element; static void loadSequential(const QDomElement &dom_element, const QString& seq, QStringList* list) { int i = 0; while (!dom_element.attribute(seq + QString::number(i+1)).isEmpty()) { list->append(dom_element.attribute(seq + QString::number(i+1))); i++; } } static void loadSequential(const QDomElement &dom_element, Element *element) { autonum::sequentialNumbers sn; loadSequential(dom_element,"sequ_",&sn.unit); loadSequential(dom_element,"sequf_",&sn.unit_folio); loadSequential(dom_element,"seqt_",&sn.ten); loadSequential(dom_element,"seqtf_",&sn.ten_folio); loadSequential(dom_element,"seqh_",&sn.hundred); loadSequential(dom_element,"seqhf_",&sn.hundred_folio); element->rSequenceStruct() = sn; } }; /** * @brief Element::Element * New element from xml * @param location, location of this element * @param parent, parent graphics item * @param state, state of the instanciation */ Element::Element(const ElementsLocation &location, QGraphicsItem *parent, int *state, kind link_type) : QetGraphicsItem(parent), m_link_type (link_type), m_location (location) { if(! (location.isElement() && location.exist())) { if (state) { *state = 1; return; } } int elmt_state; qDebug() << "\tCollection Path: " << location.collectionPath(); buildFromXml(location.xml(), &elmt_state); if (state) { *state = elmt_state; } if (elmt_state) { return; } if (state) { *state = 0; } setPrefix(autonum::elementPrefixForLocation(location)); m_uuid = QUuid::createUuid(); setZValue(10); setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); connect(this, &Element::rotationChanged, [this]() { for(QGraphicsItem *qgi : childItems()) { if (Terminal *t = qgraphicsitem_cast(qgi)) t->updateConductor(); } }); } /** * @brief Element::~Element */ Element::~Element() { qDeleteAll (m_dynamic_text_list); qDeleteAll (m_terminals); } /** * @brief Element::terminals * @return the list of terminals of this element. */ QList Element::terminals() const { return m_terminals; } /** * @brief Element::conductors * @return The list of conductors docked to this element * the list is sorted according to the position of the terminal where the conductor is docked * from top to bottom, and left to right. */ QList Element::conductors() const { QList conductors; for (Terminal *t : m_terminals) { conductors << t -> conductors(); } return(conductors); } void Element::editProperty() { if (diagram() && !diagram()->isReadOnly()) { ElementPropertiesWidget *epw = new ElementPropertiesWidget(this); PropertiesEditorDialog dialog(epw, QApplication::activeWindow()); connect(epw, &ElementPropertiesWidget::findEditClicked, &dialog, &QDialog::reject); //Must be windowModal, else when user do a drag and drop //with the "text" tab of ElementPropertiesWidget, the ui freeze, until user press escape key dialog.setWindowModality(Qt::WindowModal); dialog.exec(); } } /** @param hl true pour mettre l'element en evidence, false sinon */ void Element::setHighlighted(bool hl) { m_must_highlight = hl; update(); } /** * @brief Element::displayHelpLine * Display the help line of each terminal if b is true * @param b */ void Element::displayHelpLine(bool b) { foreach (Terminal *t, terminals()) t->drawHelpLine(b); } /** * @brief Element::paint * @param painter * @param options * @param widget */ void Element::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *) { if (m_must_highlight) { drawHighlight(painter, options); } if (options && options -> levelOfDetail < 1.0) { painter->drawPicture(0, 0, m_low_zoom_picture); } else { painter->drawPicture(0, 0, m_picture); } //Draw the selection rectangle if ( isSelected() || m_mouse_over ) { drawSelection(painter, options); } } /** @return Le rectangle delimitant le contour de l'element */ QRectF Element::boundingRect() const { return(QRectF(QPointF(-hotspot_coord.x(), -hotspot_coord.y()), dimensions)); } /** * @brief Element::setSize * Define the size of the element. * The size must be a multiple of 10. * If not, the dimensions indicated will be arrrondies to higher tens. * @param wid * @param hei */ void Element::setSize(int wid, int hei) { prepareGeometryChange(); while (wid % 10) ++ wid; while (hei % 10) ++ hei; dimensions = QSize(wid, hei); } /** @return la taille de l'element sur le schema */ QSize Element::size() const { return(dimensions); } /** Definit le hotspot de l'element par rapport au coin superieur gauche de son rectangle delimitant. Necessite que la taille ait deja ete definie @param hs Coordonnees du hotspot */ QPoint Element::setHotspot(QPoint hs) { // la taille doit avoir ete definie prepareGeometryChange(); if (dimensions.isNull()) hotspot_coord = QPoint(0, 0); else { // les coordonnees indiquees ne doivent pas depasser les dimensions de l'element int hsx = qMin(hs.x(), dimensions.width()); int hsy = qMin(hs.y(), dimensions.height()); hotspot_coord = QPoint(hsx, hsy); } return(hotspot_coord); } /** @return Le hotspot courant de l'element */ QPoint Element::hotspot() const { return(hotspot_coord); } /** * @brief Element::pixmap * @return the pixmap of this element */ QPixmap Element::pixmap() { return ElementPictureFactory::instance()->pixmap(m_location); } /*** Methodes protegees ***/ /** Dessine un petit repere (axes x et y) relatif a l'element @param painter Le QPainter a utiliser pour dessiner les axes @param options Les options de style a prendre en compte */ void Element::drawAxes(QPainter *painter, const QStyleOptionGraphicsItem *options) { Q_UNUSED(options); painter -> setPen(Qt::blue); painter -> drawLine(0, 0, 10, 0); painter -> drawLine(7,-3, 10, 0); painter -> drawLine(7, 3, 10, 0); painter -> setPen(Qt::red); painter -> drawLine(0, 0, 0, 10); painter -> drawLine(0, 10,-3, 7); painter -> drawLine(0, 10, 3, 7); } /*** Methodes privees ***/ /** Dessine le cadre de selection de l'element de maniere systematiquement non antialiasee. @param painter Le QPainter a utiliser pour dessiner les bornes. @param options Les options de style a prendre en compte */ void Element::drawSelection(QPainter *painter, const QStyleOptionGraphicsItem *options) { Q_UNUSED(options); painter -> save(); // Annulation des renderhints painter -> setRenderHint(QPainter::Antialiasing, false); painter -> setRenderHint(QPainter::TextAntialiasing, false); painter -> setRenderHint(QPainter::SmoothPixmapTransform, false); // Dessin du cadre de selection en gris QPen t; t.setColor(Qt::gray); t.setStyle(Qt::DashDotLine); t.setCosmetic(true); painter -> setPen(t); // Le dessin se fait a partir du rectangle delimitant painter -> drawRoundedRect(boundingRect().adjusted(1, 1, -1, -1), 10, 10); painter -> restore(); } /** Dessine le cadre de selection de l'element de maniere systematiquement non antialiasee. @param painter Le QPainter a utiliser pour dessiner les bornes. @param options Les options de style a prendre en compte */ void Element::drawHighlight(QPainter *painter, const QStyleOptionGraphicsItem *options) { Q_UNUSED(options); painter -> save(); qreal gradient_radius = qMin(boundingRect().width(), boundingRect().height()) / 2.0; QRadialGradient gradient( boundingRect().center(), gradient_radius, boundingRect().center() ); gradient.setColorAt(0.0, QColor::fromRgb(69, 137, 255, 255)); gradient.setColorAt(1.0, QColor::fromRgb(69, 137, 255, 0)); QBrush brush(gradient); painter -> setPen(Qt::NoPen); painter -> setBrush(brush); // Le dessin se fait a partir du rectangle delimitant painter -> drawRoundedRect(boundingRect().adjusted(1, 1, -1, -1), 10, 10); painter -> restore(); } /** * @brief Element::buildFromXml * Build this element from an xml description * @param xml_def_elmt * @param state * Optional pointer which define the status of build * 0 - evreything all right * 4 - xml isn't a "definition" * 5 - attribute of the definition isn't present or valid * 6 - the definition is empty * 7 - parsing of a xml node who describe a graphical part failed. * 8 - No part of the drawing could be loaded * @return */ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state) { m_state = QET::GIBuildingFromXml; if (xml_def_elmt.tagName() != "definition" || xml_def_elmt.attribute("type") != "element") { if (state) *state = 4; m_state = QET::GIOK; return(false); } //Check if the curent version can read the xml description if (xml_def_elmt.hasAttribute("version")) { bool conv_ok; qreal element_version = xml_def_elmt.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 attribute must be present and valid int w, h, hot_x, hot_y; if ( !QET::attributeIsAnInteger(xml_def_elmt, QString("width"), &w) ||\ !QET::attributeIsAnInteger(xml_def_elmt, QString("height"), &h) ||\ !QET::attributeIsAnInteger(xml_def_elmt, QString("hotspot_x"), &hot_x) ||\ !QET::attributeIsAnInteger(xml_def_elmt, QString("hotspot_y"), &hot_y) ) { if (state) *state = 5; m_state = QET::GIOK; return(false); } setSize(w, h); setHotspot(QPoint(hot_x, hot_y)); //the definition must have childs if (xml_def_elmt.firstChild().isNull()) { if (state) *state = 6; m_state = QET::GIOK; return(false); } //Extract the names m_names.fromXml(xml_def_elmt); setToolTip(name()); //load kind informations m_kind_informations.fromXml(xml_def_elmt.firstChildElement("kindInformations"), "kindInformation"); //load element information m_element_informations.fromXml(xml_def_elmt.firstChildElement("elementInformations"), "elementInformation"); //scroll of the Children of the Definition: Parts of the Drawing int parsed_elements_count = 0; for (QDomNode node = xml_def_elmt.firstChild() ; !node.isNull() ; node = node.nextSibling()) { QDomElement elmts = node.toElement(); if (elmts.isNull()) continue; if (elmts.tagName() == "description") { //Minor workaround to find if there is a "input" tagg as label. //If not, we set the tagg "label" to the first "input. QList input_field; bool have_label = false; for (QDomElement input_node = node.firstChildElement("input") ; !input_node.isNull() ; input_node = input_node.nextSiblingElement("input")) { if (!input_node.isNull()) { input_field << input_node; if (input_node.attribute("tagg", "none") == "label") have_label = true; } } if(!have_label && !input_field.isEmpty()) input_field.first().setAttribute("tagg", "label"); //Parse the definition for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) { QDomElement qde = n.toElement(); if (qde.isNull()) continue; qDebug() << "\t\tElement.cpp:buildFromXml;parseElement: " << qde.tagName(); if (parseElement(qde)) { qDebug() << "\t\t\tParsing Element success"; ++ parsed_elements_count; } else { qDebug() << "\t\t\tParsing Element no success"; if (state) *state = 7; m_state = QET::GIOK; return(false); } } } } ElementPictureFactory *epf = ElementPictureFactory::instance(); epf->getPictures(m_location, const_cast(m_picture), const_cast(m_low_zoom_picture)); if(!m_picture.isNull()) ++ parsed_elements_count; //They must be at least one parsed graphics part if (!parsed_elements_count) { if (state) *state = 8; m_state = QET::GIOK; return(false); } if (state) *state = 0; m_state = QET::GIOK; return(true); } /** * @brief Element::parseElement * Parse the element of the xml description of this element * @param dom * @return */ bool Element::parseElement(const QDomElement &dom) { if (dom.tagName() == "terminal") return(parseTerminal(dom)); else if (dom.tagName() == "input") return(parseInput(dom)); else if (dom.tagName() == "dynamic_text") return(parseDynamicText(dom)); else return(true); } /** * @brief Element::parseInput * Parse the input (old text field) * the parsed input are converted to dynamic text field, this function * is only here to keep compatibility with old text. * @param dom_element * @return */ bool Element::parseInput(const QDomElement &dom_element) { qreal pos_x, pos_y; int size; if ( !QET::attributeIsAReal(dom_element, "x", &pos_x) ||\ !QET::attributeIsAReal(dom_element, "y", &pos_y) ||\ !QET::attributeIsAnInteger(dom_element, "size", &size) ) return(false); else { DynamicElementTextItem *deti = new DynamicElementTextItem(this); deti->setText(dom_element.attribute("text", "_")); QFont font = deti->font(); font.setPointSize(dom_element.attribute("size", QString::number(9)).toInt()); deti->setFont(font); deti->setRotation(dom_element.attribute("rotation", QString::number(0)).toDouble()); if(dom_element.attribute("tagg", "none") != "none") { deti->setTextFrom(DynamicElementTextItem::ElementInfo); deti->setInfoName(dom_element.attribute("tagg")); } //the origin transformation point of PartDynamicTextField is the top left corner, no matter the font size //The origin transformation point of ElementTextItem is the middle of left edge, and so by definition, change with the size of the font //We need to use a QTransform to find the pos of this text from the saved pos of text item QTransform transform; //First make the rotation transform.rotate(dom_element.attribute("rotation", "0").toDouble()); QPointF pos = transform.map(QPointF(0, -deti->boundingRect().height()/2)); transform.reset(); //Second translate to the pos QPointF p(dom_element.attribute("x", QString::number(0)).toDouble(), dom_element.attribute("y", QString::number(0)).toDouble()); transform.translate(p.x(), p.y()); deti->setPos(transform.map(pos)); m_converted_text_from_xml_description.insert(deti, p); return true; } } /** * @brief Element::parseDynamicText * Create the dynamic text field describ in @dom_element * @param dom_element * @return */ DynamicElementTextItem *Element::parseDynamicText(const QDomElement &dom_element) { DynamicElementTextItem *deti = new DynamicElementTextItem(this); //Because the xml description of a .elmt file is the same as how a dynamic text field is save to xml in a .qet file //wa call fromXml, we just change the tagg name (.elmt = dynamic_text, .qet = dynamic_elmt_text) //and the uuid (because the uuid, is the uuid of the descritpion and not the uuid of instantiated dynamic text field) QDomElement dom(dom_element.cloneNode(true).toElement()); dom.setTagName(DynamicElementTextItem::xmlTagName()); deti->fromXml(dom); deti->m_uuid = QUuid::createUuid(); this->addDynamicTextItem(deti); return deti; } /*! * \brief Element::parseTerminal * Parse partTerminal from xml structure * \param dom_element * \return */ Terminal *Element::parseTerminal(const QDomElement &dom_element) { if (!Terminal::valideXml(dom_element)) return nullptr; Terminal *new_terminal = new Terminal(0, 0, Qet::Orientation::North, this); new_terminal->fromXml(dom_element); m_terminals << new_terminal; //Sort from top to bottom and left to rigth std::sort(m_terminals.begin(), m_terminals.end(), [](Terminal *a, Terminal *b) { if(a->dockConductor().y() == b->dockConductor().y()) return (a->dockConductor().x() < b->dockConductor().x()); else return (a->dockConductor().y() < b->dockConductor().y()); }); return(new_terminal); } /** Permet de savoir si un element XML (QDomElement) represente bien un element @param e Le QDomElement a valide @return true si l'element XML est un Element, false sinon */ bool Element::valideXml(QDomElement &e) { // verifie le nom du tag if (e.tagName() != "element") return(false); // verifie la presence des attributs minimaux if (!e.hasAttribute("type")) return(false); if (!e.hasAttribute("x")) return(false); if (!e.hasAttribute("y")) return(false); bool conv_ok; // parse l'abscisse e.attribute("x").toDouble(&conv_ok); if (!conv_ok) return(false); // parse l'ordonnee e.attribute("y").toDouble(&conv_ok); if (!conv_ok) return(false); return(true); } /** * @brief Element::fromXml * Import the parameters of this element from a xml document. * When call this function ensure this element is already in a scene, because * the dynamic text item and element text item group (in the xml file) are created in this function * and need a diagram for create their Xref, when this element is linked to another. * If not the Xref can be not displayed, until the next call of update Xref of the group or text item. * @param e : the dom element where the parameter is stored * @param table_id_adr : Reference to the mapping table between IDs of the XML file * and the addresses in memory. If the import succeeds, it must be add the right couples (id, address). * @param handle_inputs_rotation : apply the rotation of this element to his child text * @return */ bool Element::fromXml(QDomElement &e, QHash &table_id_adr, bool handle_inputs_rotation) { m_state = QET::GILoadingFromXml; /* les bornes vont maintenant etre recensees pour associer leurs id a leur adresse reelle ce recensement servira lors de la mise en place des fils */ QList liste_terminals; foreach(QDomElement qde, QET::findInDomElement(e, "terminals", "terminal")) { if (Terminal::valideXml(qde)) liste_terminals << qde; } QHash priv_id_adr; int terminals_non_trouvees = 0; // The added childs from the collection now must match with the terminals from the diagram. Iterate through // all Terminals in the collection and in the diagram to link them together foreach(QGraphicsItem *qgi, childItems()) { // Where the Terminals are added as childs? if (Terminal *p = qgraphicsitem_cast(qgi)) { bool terminal_trouvee = false; foreach(QDomElement qde, liste_terminals) { if (p -> fromXml(qde)) { // TODO: is there validXML enough? Because the Terminal was already read in the ElementCreation function qDebug() << "Matching Terminal found."; priv_id_adr.insert(qde.attribute("id").toInt(), p); terminal_trouvee = true; // We used to break here, because we did not expect // several terminals to share the same position. // Of course, it finally happened. } } if (!terminal_trouvee) ++ terminals_non_trouvees; } } if (terminals_non_trouvees > 0) { qDebug() << "element.cpp: Element::fromXML; Elements not found: " << terminals_non_trouvees; m_state = QET::GIOK; return(false); } else { // verifie que les associations id / adr n'entrent pas en conflit avec table_id_adr foreach(int id_trouve, priv_id_adr.keys()) { if (table_id_adr.contains(id_trouve)) { // cet element possede un id qui est deja reference (= conflit) m_state = QET::GIOK; return(false); } } // copie des associations id / adr foreach(int id_trouve, priv_id_adr.keys()) { table_id_adr.insert(id_trouve, priv_id_adr.value(id_trouve)); } } //load uuid of connected elements QList uuid_list = QET::findInDomElement(e, "links_uuids", "link_uuid"); foreach (QDomElement qdo, uuid_list) tmp_uuids_link << qdo.attribute("uuid"); //uuid of this element m_uuid= QUuid(e.attribute("uuid", QUuid::createUuid().toString())); //load prefix m_prefix = e.attribute("prefix"); QString fl = e.attribute("freezeLabel", "false"); m_freeze_label = fl == "false"? false : true; //Load Sequential Values if (e.hasAttribute("sequ_1") || e.hasAttribute("sequf_1") || e.hasAttribute("seqt_1") || e.hasAttribute("seqtf_1") || e.hasAttribute("seqh_1") || e.hasAttribute("sequf_1")) ElementXmlRetroCompatibility::loadSequential(e, this); else m_autoNum_seq.fromXml(e.firstChildElement("sequentialNumbers")); //Position and selection. //We directly call setPos from QGraphicsObject, because QetGraphicsItem will snap to grid QGraphicsObject::setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble()); setZValue(e.attribute("z", QString::number(this->zValue())).toDouble()); setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); // orientation bool conv_ok; int read_ori = e.attribute("orientation").toInt(&conv_ok); if (!conv_ok || read_ori < 0 || read_ori > 3) read_ori = 0; if (handle_inputs_rotation) { setRotation(rotation() + (90*read_ori)); } else { setRotation(90*read_ori); } //Befor load the dynamic text field, //we remove the dynamic text field created from the description of this element, to avoid doublons. for(DynamicElementTextItem *deti : m_dynamic_text_list) delete deti; m_dynamic_text_list.clear(); //************************// //***Dynamic texts item***// //************************// for (const QDomElement& qde : QET::findInDomElement(e, "dynamic_texts", DynamicElementTextItem::xmlTagName())) { DynamicElementTextItem *deti = new DynamicElementTextItem(this); addDynamicTextItem(deti); deti->fromXml(qde); } //************************// //***Element texts item***// //************************// QList inputs = QET::findInDomElement(e, "inputs", "input"); //First case, we check for the text item converted to dynamic text item const QList conv_deti_list = m_converted_text_from_xml_description.keys(); QList successfully_converted; const QList dom_inputs = inputs; for (DynamicElementTextItem *deti : conv_deti_list) { for(const QDomElement& dom_input : dom_inputs) { //we use the same method used in ElementTextItem::fromXml to compar and know if the input dom element is for one of the text stored. //The comparaison is made from the text position : if the position of the text is the same as the position stored in 'input' dom element //that mean this is the good text if (qFuzzyCompare(qreal(dom_input.attribute("x").toDouble()), m_converted_text_from_xml_description.value(deti).x()) && qFuzzyCompare(qreal(dom_input.attribute("y").toDouble()), m_converted_text_from_xml_description.value(deti).y())) { //Once again this 'if', is only for retrocompatibility with old old old project //when element text with tagg "label" is not null, but the element information "label" is. if((deti->textFrom() == DynamicElementTextItem::ElementInfo) && (deti->infoName() == "label")) m_element_informations.addValue("label", dom_input.attribute("text")); deti->setText(dom_input.attribute("text")); qreal rotation = deti->rotation(); QPointF xml_pos = m_converted_text_from_xml_description.value(deti); if (dom_input.attribute("userrotation").toDouble()) rotation = dom_input.attribute("userrotation").toDouble(); if (dom_input.hasAttribute("userx")) xml_pos.setX(dom_input.attribute("userx").toDouble()); if(dom_input.hasAttribute("usery")) xml_pos.setY(dom_input.attribute("usery", "0").toDouble()); //the origin transformation point of PartDynamicTextField is the top left corner, no matter the font size //The origin transformation point of PartTextField is the middle of left edge, and so by definition, change with the size of the font //We need to use a QTransform to find the pos of this text from the saved pos of text item deti->setPos(xml_pos); deti->setRotation(rotation); QTransform transform; //First make the rotation transform.rotate(rotation); QPointF pos = transform.map(QPointF(0, -deti->boundingRect().height()/2)); transform.reset(); //Second translate to the pos transform.translate(xml_pos.x(), xml_pos.y()); deti->setPos(transform.map(pos)); //dom_input and deti matched we remove the dom_input from @inputs list, //to avoid unnecessary checking made below //we also move deti from the m_converted_text_from_xml_description to m_dynamic_text_list inputs.removeAll(dom_input); m_dynamic_text_list.append(deti); m_converted_text_from_xml_description.remove(deti); successfully_converted << deti; } } } //###Firts case : if this is the first time the user open the project since text item are converted to dynamic text, //in the previous opening of the project, every texts field present in the element description was created. //At save time, the values of each of them was save in the 'input' dom element. //The loop upper is made for the first case, to import the values in 'input' to the new converted dynamic texts field. //###Second case : this is not the first time the user open the project since text item are converted to dynamic text. //That mean, in a previous opening of the project, the text item was already converted and save as a dynamic text field. //So there isn't 'input' dom element in the project, and every dynamic text item present in m_converted_text_from_xml_description //need to be deleted (because already exist in m_dynamic_text_list, from a previous save) for (DynamicElementTextItem *deti : m_converted_text_from_xml_description.keys()) delete deti; m_converted_text_from_xml_description.clear(); for (QDomElement qde : QET::findInDomElement(e, "texts_groups", ElementTextItemGroup::xmlTaggName())) { ElementTextItemGroup *group = addTextGroup("loaded_from_xml_group"); group->fromXml(qde); } //load informations DiagramContext dc; dc.fromXml(e.firstChildElement("elementInformations"), "elementInformation"); /** * Since the commit 4791, the value used as "label" and "formula" is stored in differents keys (instead of the same key, "label" in previous version), * so, if "label" contain "%" (Use variable value), and "formula" does not exist, * this mean the label was made before commit 4791 (0.51 dev). So we swap the value stored in "label" to "formula" as expected. * @TODO remove this code at version 0.7 or more (probably useless). */ if (dc["label"].toString().contains("%") && dc["formula"].toString().isNull()) { dc.addValue("formula", dc["label"]); } //retrocompatibility with older version if(dc.value("label").toString().isEmpty() && !m_element_informations.value("label").toString().isEmpty()) dc.addValue("label", m_element_informations.value("label")); //We must to block the update of the alignment when load the information //otherwise the pos of the text will not be the same as it was at save time. for(DynamicElementTextItem *deti : m_dynamic_text_list) deti->m_block_alignment = true; setElementInformations(dc); for(DynamicElementTextItem *deti : m_dynamic_text_list) deti->m_block_alignment = false; /** During the devel of the version 0.7, the "old text" was replaced by the dynamic element text item. When open a project made befor the 0.7, we must to reproduce the same visual when the label are not empty and visible, and comment are not empty and visible and/or location are not empty and visible. we create a text group with inside the needed texts, label and comment and/or location. */ //#1 There must be old text converted to dynamic text if(!successfully_converted.isEmpty()) { //#2 the element information must have label not empty and visible //and a least comment or location not empty and visible QString label = m_element_informations.value("label").toString(); QString comment = m_element_informations.value("comment").toString(); QString location = m_element_informations.value("location").toString(); bool la = m_element_informations.keyMustShow("label"); bool c = m_element_informations.keyMustShow("comment"); bool lo = m_element_informations.keyMustShow("location"); if((m_link_type != Master) || ((m_link_type == Master) && (diagram()->project()->defaultXRefProperties(m_kind_informations["type"].toString()).snapTo() == XRefProperties::Label)) ) { if(!label.isEmpty() && la && ((!comment.isEmpty() && c) || (!location.isEmpty() && lo))) { //#2 in the converted list one text must have text from = element info and info name = label for(DynamicElementTextItem *deti : successfully_converted) { if(deti->textFrom() == DynamicElementTextItem::ElementInfo && deti->infoName() == "label") { qreal rotation = deti->rotation(); //Create the comment item DynamicElementTextItem *comment_text = nullptr; if (m_link_type !=PreviousReport || m_link_type !=NextReport) { m_state = QET::GIOK; return(true); } if(!comment.isEmpty() && c) { comment_text = new DynamicElementTextItem(this); comment_text->setTextFrom(DynamicElementTextItem::ElementInfo); comment_text->setInfoName("comment"); QFont font = comment_text->font(); font.setPointSize(6); comment_text->setFont(font); comment_text->setFrame(true); if(comment_text->toPlainText().count() > 17) comment_text->setTextWidth(80); comment_text->setPos(deti->x(), deti->y()+10); //+10 is arbitrary, comment_text must be below deti addDynamicTextItem(comment_text); } //create the location item DynamicElementTextItem *location_text = nullptr; if (m_link_type !=PreviousReport || m_link_type !=NextReport) { m_state = QET::GIOK; return(true); } if(!location.isEmpty() && lo) { location_text = new DynamicElementTextItem(this); location_text->setTextFrom(DynamicElementTextItem::ElementInfo); location_text->setInfoName("location"); QFont font = location_text->font(); font.setPointSize(6); location_text->setFont(font); if(location_text->toPlainText().count() > 17) location_text->setTextWidth(80); location_text->setPos(deti->x(), deti->y()+20); //+20 is arbitrary, location_text must be below deti and comment addDynamicTextItem(location_text); } QPointF pos = deti->pos(); if (m_link_type !=PreviousReport || m_link_type !=NextReport) { m_state = QET::GIOK; return(true); } //Create the group ElementTextItemGroup *group = addTextGroup(tr("Label + commentaire")); addTextToGroup(deti, group); if(comment_text) addTextToGroup(comment_text, group); if(location_text) addTextToGroup(location_text, group); group->setAlignment(Qt::AlignVCenter); group->setVerticalAdjustment(-4); group->setRotation(rotation); //Change the position of the group, so that the text "label" stay in the same //position in scene coordinate group->setPos(pos - deti->pos()); break; } } } } else { //This element is supposed to be a master and Xref property snap to bottom if((!comment.isEmpty() && c) || (!location.isEmpty() && lo)) { //Create the comment item DynamicElementTextItem *comment_text = nullptr; if(!comment.isEmpty() && c) { comment_text = new DynamicElementTextItem(this); comment_text->setTextFrom(DynamicElementTextItem::ElementInfo); comment_text->setInfoName("comment"); QFont font = comment_text->font(); font.setPointSize(6); comment_text->setFont(font); comment_text->setFrame(true); comment_text->setTextWidth(80); addDynamicTextItem(comment_text); } //create the location item DynamicElementTextItem *location_text = nullptr; if(!location.isEmpty() && lo) { location_text = new DynamicElementTextItem(this); location_text->setTextFrom(DynamicElementTextItem::ElementInfo); location_text->setInfoName("location"); QFont font = location_text->font(); font.setPointSize(6); location_text->setFont(font); location_text->setTextWidth(80); if(comment_text) location_text->setPos(comment_text->x(), comment_text->y()+10); //+10 is arbitrary, location_text must be below the comment addDynamicTextItem(location_text); } //Create the group ElementTextItemGroup *group = addTextGroup(tr("Label + commentaire")); if(comment_text) addTextToGroup(comment_text, group); if(location_text) addTextToGroup(location_text, group); group->setAlignment(Qt::AlignVCenter); group->setVerticalAdjustment(-4); group->setHoldToBottomPage(true); } } } m_state = QET::GIOK; return(true); } /** Permet d'exporter l'element en XML @param document Document XML a utiliser @param table_adr_id Table de correspondance entre les adresses des bornes et leur id dans la representation XML ; cette table completee par cette methode @return L'element XML representant cet element electrique */ QDomElement Element::toXml(QDomDocument &document, QHash &table_adr_id) const { QDomElement element = document.createElement("element"); // type element.setAttribute("type", m_location.path()); // uuid element.setAttribute("uuid", uuid().toString()); // prefix element.setAttribute("prefix", m_prefix); //frozen label element.setAttribute("freezeLabel", m_freeze_label? "true" : "false"); // sequential num QDomElement seq = m_autoNum_seq.toXml(document); if (seq.hasChildNodes()) element.appendChild(seq); // position, selection et orientation element.setAttribute("x", QString::number(pos().x())); element.setAttribute("y", QString::number(pos().y())); element.setAttribute("z", QString::number(this->zValue())); element.setAttribute("orientation", QString::number(orientation())); /* recupere le premier id a utiliser pour les bornes de cet element */ int id_terminal = 0; if (!table_adr_id.isEmpty()) { // trouve le plus grand id int max_id_t = -1; foreach (int id_t, table_adr_id.values()) { if (id_t > max_id_t) max_id_t = id_t; } id_terminal = max_id_t + 1; } // enregistrement des bornes de l'appareil QDomElement xml_terminals = document.createElement("terminals"); // pour chaque enfant de l'element foreach(Terminal *t, terminals()) { // alors on enregistre la borne QDomElement terminal = t -> toXml(document); terminal.setAttribute("id", id_terminal); // for backward compatibility table_adr_id.insert(t, id_terminal ++); xml_terminals.appendChild(terminal); } element.appendChild(xml_terminals); // enregistrement des champ de texte de l'appareil QDomElement inputs = document.createElement("inputs"); element.appendChild(inputs); //if this element is linked to other elements, //save the uuid of each other elements if (! isFree()) { QDomElement links_uuids = document.createElement("links_uuids"); foreach (Element *elmt, connected_elements) { QDomElement link_uuid = document.createElement("link_uuid"); link_uuid.setAttribute("uuid", elmt->uuid().toString()); links_uuids.appendChild(link_uuid); } element.appendChild(links_uuids); } //save information of this element if (! m_element_informations.keys().isEmpty()) { QDomElement infos = document.createElement("elementInformations"); m_element_informations.toXml(infos, "elementInformation"); element.appendChild(infos); } //Dynamic texts QDomElement dyn_text = document.createElement("dynamic_texts"); for (DynamicElementTextItem *deti : m_dynamic_text_list) dyn_text.appendChild(deti->toXml(document)); QDomElement texts_group = document.createElement("texts_groups"); //Dynamic texts owned by groups for(ElementTextItemGroup *group : m_texts_group) { group->blockAlignmentUpdate(true); //temporarily remove the texts from group to get the pos relative to element and not group. //Set the alignment to top, because top is not used by groupand so, //each time a text is removed from the group, the alignement is not updated Qt::Alignment al = group->alignment(); group->setAlignment(Qt::AlignTop); //Remove the texts from group QList deti_list = group->texts(); for(DynamicElementTextItem *deti : deti_list) group->removeFromGroup(deti); //Save the texts to xml for (DynamicElementTextItem *deti : deti_list) dyn_text.appendChild(deti->toXml(document)); //Re add texts to group for(DynamicElementTextItem *deti : deti_list) group->addToGroup(deti); //Restor the alignement group->setAlignment(al); //Save the group to xml texts_group.appendChild(group->toXml(document)); group->blockAlignmentUpdate(false); } //Append the dynamic texts to element element.appendChild(dyn_text); //Append the texts group to element element.appendChild(texts_group); return(element); } /** * @brief Element::addDynamiqueTextItem * Add @deti as a dynamic text item of this element, @deti is reparented to this * If @deti is null, a new DynamicElementTextItem is created and added to this element. * @param deti */ void Element::addDynamicTextItem(DynamicElementTextItem *deti) { if (deti && !m_dynamic_text_list.contains(deti)) { m_dynamic_text_list.append(deti); deti->setParentItem(this); emit textAdded(deti); } else { DynamicElementTextItem *text = new DynamicElementTextItem(this); m_dynamic_text_list.append(text); emit textAdded(text); } } /** * @brief Element::removeDynamicTextItem * Remove @deti, no matter if is a child of this element or a child of a group of this element. * Set he parent item of @deti to 0, @deti is not deleted. * @param deti */ void Element::removeDynamicTextItem(DynamicElementTextItem *deti) { if (m_dynamic_text_list.contains(deti)) { m_dynamic_text_list.removeOne(deti); deti->setParentItem(nullptr); emit textRemoved(deti); return; } for(ElementTextItemGroup *group : m_texts_group) { if(group->texts().contains(deti)) { removeTextFromGroup(deti, group); m_dynamic_text_list.removeOne(deti); deti->setParentItem(nullptr); emit textRemoved(deti); return; } } } /** * @brief Element::dynamicTextItems * @return all dynamic text items of this element directly child of this element. * Texts in text-groups belonging to this element are not returned by this function. * @see ElementTextItemGroup::texts */ QList Element::dynamicTextItems() const { return m_dynamic_text_list; } /** * @brief Element::addTextGroup * Create and add an element text item group to this element. * If this element already have a group with the same name, * then @name will renamed to name1 or name2 etc.... * @param name : the name of the group * @return the created group. */ ElementTextItemGroup *Element::addTextGroup(const QString &name) { if(m_texts_group.isEmpty()) { ElementTextItemGroup *group = new ElementTextItemGroup(name, this); m_texts_group << group; emit textsGroupAdded(group); return group; } //Set a new name if name already exist QString rename = name; int i=1; while (textGroup(rename)) { rename = name+QString::number(i); i++; } //Create the group ElementTextItemGroup *group = new ElementTextItemGroup(rename, this); m_texts_group << group; emit textsGroupAdded(group); return group; } /** * @brief Element::addTextGroup * @param group add group @group to the group of this element. * the group must not be owned by an element. */ void Element::addTextGroup(ElementTextItemGroup *group) { if(group->parentElement()) return; m_texts_group << group; group->setParentItem(this); emit textsGroupAdded(group); } /** * @brief Element::removeTextGroup * Remove the text group @group from this element, and set the parent of group to 0. * group is not deleted. * All texts owned by the group will be reparented to this element * @param name */ void Element::removeTextGroup(ElementTextItemGroup *group) { if(!m_texts_group.contains(group)) return; const QList items_list = group->childItems(); for(QGraphicsItem *qgi : items_list) { if(qgi->type() == DynamicElementTextItem::Type) { DynamicElementTextItem *deti = static_cast(qgi); removeTextFromGroup(deti, group); } } emit textsGroupAboutToBeRemoved(group); m_texts_group.removeOne(group); group->setParentItem(nullptr); } /** * @brief Element::textGroup * @param name * @return the text group named @name or nullptr if this element * haven't got a group with this name */ ElementTextItemGroup *Element::textGroup(const QString &name) const { for (ElementTextItemGroup *group : m_texts_group) if(group->name() == name) return group; return nullptr; } /** * @brief Element::textGroups * @return All texts groups of this element */ QList Element::textGroups() const { return m_texts_group; } /** * @brief Element::addTextToGroup * Add the text @text to the group @group; * If @group isn't owned by this element return false. * The text must be a text of this element. * @return : true if the text was succesfully added to the group. */ bool Element::addTextToGroup(DynamicElementTextItem *text, ElementTextItemGroup *group) { if(!m_dynamic_text_list.contains(text)) return false; if(!m_texts_group.contains(group)) return false; m_dynamic_text_list.removeOne(text); emit textRemoved(text); group->addToGroup(text); emit textAddedToGroup(text, group); return true; } /** * @brief Element::removeTextFromGroup * Remove the text @text from the group @group, en reparent @text to this element * @return true if text was succesfully removed */ bool Element::removeTextFromGroup(DynamicElementTextItem *text, ElementTextItemGroup *group) { if(!m_texts_group.contains(group)) return false; if(group->texts().contains(text)) { group->removeFromGroup(text); emit textRemovedFromGroup(text, group); addDynamicTextItem(text); return true; } return false; } /** * @brief Element::AlignedFreeTerminals * @return a list of terminal (owned by this element) aligned to other terminal (from other element) * The first Terminal of QPair is a Terminal owned by this element, * this terminal haven't got any conductor docked. * The second Terminal of QPair is a Terminal owned by an other element, * which is aligned with the first Terminal. The second Terminal can have or not docked conductors. */ QList > Element::AlignedFreeTerminals() const { QList > list; foreach (Terminal *terminal, terminals()) { if (terminal->conductors().isEmpty()) { Terminal *other_terminal = terminal -> alignedWithTerminal(); if (other_terminal) list << qMakePair(terminal, other_terminal); } } return list; } /** * @brief Element::initLink * Initialise the link between this element and other elements. * This method can be call once because init the link according to * uuid store in a private list, after link, the list is clear, so * call another time do nothing. * * @param prj, ownership project of this element and other element to be linked */ void Element::initLink(QETProject *prj) { // if nothing to link return now if (tmp_uuids_link.isEmpty()) return; ElementProvider ep(prj); foreach (Element *elmt, ep.fromUuids(tmp_uuids_link)) { elmt->linkToElement(this); } tmp_uuids_link.clear(); } QString Element::linkTypeToString() const { switch (m_link_type) { case Simple: return "Simple"; case NextReport : return "NextReport"; case PreviousReport: return "PreviousReport"; case Master: return "Master"; case Slave: return "Slave"; case Terminale: return "Terminale"; default: return "Unknown"; } } /** * @brief Element::setElementInformations * Set new information for this element. * If new information is different of current infotmation emit @elementInfoChange * @param dc */ void Element::setElementInformations(DiagramContext dc) { if (m_element_informations == dc) { return; } DiagramContext old_info = m_element_informations; m_element_informations = dc; m_element_informations.addValue("label", actualLabel()); //Update the label if there is a formula emit elementInfoChange(old_info, m_element_informations); } /** * @brief comparPos * Compare position of the two elements. Compare 3 points: * 1 folio - 2 row - 3 line * returns a response when a comparison is found. * @return true if elmt1 is at lower position than elmt 2, else false */ bool comparPos(const Element *elmt1, const Element *elmt2) { //Compare folio first if (elmt1->diagram()->folioIndex() != elmt2->diagram()->folioIndex()) return elmt1->diagram()->folioIndex() < elmt2->diagram()->folioIndex(); //Compare the row(in letter pos) in second QString a = elmt1->diagram()->convertPosition(elmt1->scenePos()).letter(); QString b = elmt2->diagram()->convertPosition(elmt2->scenePos()).letter(); if (a != b) return apos().x() == elmt2->pos().x()) return elmt1->y() <= elmt2->pos().y(); return elmt1->pos().x() <= elmt2->pos().x(); } /** * @brief Element::mouseMoveEvent * @param event */ void Element::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QetGraphicsItem::mouseMoveEvent(event); foreach (Terminal *t, terminals()) { t -> drawHelpLine(true); } } /** * @brief Element::mouseReleaseEvent * @param event */ void Element::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QetGraphicsItem::mouseReleaseEvent(event); foreach (Terminal *t, terminals()) { t -> drawHelpLine(false); } } /** * When mouse over element * change m_mouse_over to true (used in paint() function ) * Also highlight linked elements * @param e QGraphicsSceneHoverEvent */ void Element::hoverEnterEvent(QGraphicsSceneHoverEvent *e) { Q_UNUSED(e) foreach (Element *elmt, linkedElements()) elmt -> setHighlighted(true); m_mouse_over = true; setToolTip( name() ); update(); } /** * When mouse over element leave the position * change m_mouse_over to false(used in paint() function ) * Also un-highlight linked elements * @param e QGraphicsSceneHoverEvent */ void Element::hoverLeaveEvent(QGraphicsSceneHoverEvent *e) { Q_UNUSED(e) foreach (Element *elmt, linkedElements()) elmt -> setHighlighted(false); m_mouse_over = false; update(); } /** * @brief Element::setUpFormula * Set up the formula used to create the label of this element * @param : if true set tagged text to code letter (ex K for coil) with condition : * formula is empty, text tagged "label" is emptty or "_"; */ void Element::setUpFormula(bool code_letter) { Q_UNUSED(code_letter) if (linkType() == Element::Slave || linkType() & Element::AllReport) return; if (diagram()) { QString formula = diagram()->project()->elementAutoNumCurrentFormula(); m_element_informations.addValue("formula", formula); QString element_currentAutoNum = diagram()->project()->elementCurrentAutoNum(); NumerotationContext nc = diagram()->project()->elementAutoNum(element_currentAutoNum); NumerotationContextCommands ncc (nc); m_autoNum_seq.clear(); autonum::setSequential(formula, m_autoNum_seq, nc, diagram(), element_currentAutoNum); diagram()->project()->addElementAutoNum(element_currentAutoNum, ncc.next()); if(!m_freeze_label && !formula.isEmpty()) { DiagramContext dc = m_element_informations; m_element_informations.addValue("label", actualLabel()); emit elementInfoChange(dc, m_element_informations); } } } /** * @brief Element::getPrefix * get Element Prefix */ QString Element::getPrefix() const{ return m_prefix; } /** * @brief Element::setPrefix * set Element Prefix */ void Element::setPrefix(QString prefix) { m_prefix = std::move(prefix); } /** * @brief Element::freezeLabel * Freeze this element label */ void Element::freezeLabel(bool freeze) { m_freeze_label = freeze; } /** * @brief Element::freezeNewAddedElement * Freeze this label if needed */ void Element::freezeNewAddedElement() { if (this->diagram()->freezeNewElements() || this->diagram()->project()->isFreezeNewElements()) { freezeLabel(true); } else return; } /** * @brief Element::actualLabel * Always return the current label to be displayed. * This function is usefull when label is based on formula, because label can change at any time. * @return */ QString Element::actualLabel() { if (m_element_informations.value("formula").toString().isEmpty()) { return m_element_informations.value("label").toString(); } else { return autonum::AssignVariables::formulaToLabel(m_element_informations.value("formula").toString(), m_autoNum_seq, diagram(), this); } } /** * @brief Element::name * @return the human name of this element */ QString Element::name() const { return m_names.name(m_location.baseName()); } ElementsLocation Element::location() const { return m_location; }