/* Copyright 2006-2017 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 "elementscene.h" #include "qetelementeditor.h" #include "elementprimitivedecorator.h" #include #include "partline.h" #include "partrectangle.h" #include "partellipse.h" #include "partpolygon.h" #include "partterminal.h" #include "parttext.h" #include "parttextfield.h" #include "partarc.h" #include "editorcommands.h" #include "elementcontent.h" #include "nameslist.h" #include "ui/elementpropertieseditorwidget.h" #include "eseventinterface.h" #include "QetGraphicsItemModeler/qetgraphicshandleritem.h" #include "partdynamictextfield.h" #include #include /** Constructeur @param editor L'editeur d'element concerne @param parent le Widget parent */ ElementScene::ElementScene(QETElementEditor *editor, QObject *parent) : QGraphicsScene(parent), m_elmt_type("simple"), m_qgi_manager(this), m_element_editor(editor) { m_behavior = Normal; setItemIndexMethod(NoIndex); setGrid(1, 1); initPasteArea(); m_undo_stack.setClean(); m_decorator_lock = new QMutex(QMutex::NonRecursive); connect(&m_undo_stack, SIGNAL(indexChanged(int)), this, SLOT(managePrimitivesGroups())); connect(this, SIGNAL(selectionChanged()), this, SLOT(managePrimitivesGroups())); } /** * @brief ElementScene::~ElementScene */ ElementScene::~ElementScene() { //Disconnect to avoid crash, see bug report N° 122. disconnect(&m_undo_stack, SIGNAL(indexChanged(int)), this, SLOT(managePrimitivesGroups())); delete m_decorator_lock; if (m_event_interface) delete m_event_interface; if (m_decorator) delete m_decorator; } /** * @brief ElementScene::mouseMoveEvent * @param e */ void ElementScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { if (m_event_interface) { if (m_event_interface -> mouseMoveEvent(e)) { if (m_event_interface->isFinish()) { delete m_event_interface; m_event_interface = nullptr; emit(partsAdded()); } return; } } QPointF event_pos = e -> scenePos(); if (!(e -> modifiers() & Qt::ControlModifier)) event_pos = snapToGrid(event_pos); if (m_behavior == PasteArea) { QRectF current_rect(m_paste_area -> rect()); current_rect.moveCenter(event_pos); m_paste_area -> setRect(current_rect); return; } QGraphicsScene::mouseMoveEvent(e); } /** * @brief ElementScene::mousePressEvent * @param e */ void ElementScene::mousePressEvent(QGraphicsSceneMouseEvent *e) { if (m_event_interface) { if (m_event_interface -> mousePressEvent(e)) { if (m_event_interface->isFinish()) { delete m_event_interface; m_event_interface = nullptr; emit(partsAdded()); } return; } } QGraphicsScene::mousePressEvent(e); } /** * @brief ElementScene::mouseReleaseEvent * @param e */ void ElementScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { if (m_event_interface) { if (m_event_interface -> mouseReleaseEvent(e)) { if (m_event_interface->isFinish()) { delete m_event_interface; m_event_interface = nullptr; emit(partsAdded()); } return; } } if (m_behavior == PasteArea) { m_defined_paste_area = m_paste_area -> rect(); removeItem(m_paste_area); emit(pasteAreaDefined(m_defined_paste_area)); m_behavior = Normal; return; } QGraphicsScene::mouseReleaseEvent(e); } /** * @brief ElementScene::mouseDoubleClickEvent * @param event */ void ElementScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if (m_event_interface) { if (m_event_interface -> mouseDoubleClickEvent(event)) { if (m_event_interface->isFinish()) { delete m_event_interface; m_event_interface = nullptr; emit(partsAdded()); } return; } } QGraphicsScene::mouseDoubleClickEvent(event); } /** * @brief ElementScene::keyPressEvent * manage key press event * @param event */ void ElementScene::keyPressEvent(QKeyEvent *event) { if (m_event_interface) { if (m_event_interface -> keyPressEvent(event)) { if (m_event_interface->isFinish()) { delete m_event_interface; m_event_interface = nullptr; emit(partsAdded()); } return; } } QGraphicsScene::keyPressEvent(event); } /** * @brief ElementScene::contextMenuEvent * Display the context menu event, only if behavior are Normal * @param event */ void ElementScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { if (m_behavior == ElementScene::Normal) m_element_editor -> contextMenu(event->screenPos()); } /** Dessine l'arriere-plan de l'editeur, cad l'indicateur de hotspot. @param p Le QPainter a utiliser pour dessiner @param rect Le rectangle de la zone a dessiner */ void ElementScene::drawForeground(QPainter *p, const QRectF &rect) { Q_UNUSED(rect); p -> save(); // desactive tout antialiasing, sauf pour le texte p -> setRenderHint(QPainter::Antialiasing, false); p -> setRenderHint(QPainter::TextAntialiasing, true); p -> setRenderHint(QPainter::SmoothPixmapTransform, false); QPen pen(Qt::red); pen.setCosmetic(true); p -> setPen(pen); p -> setBrush(Qt::NoBrush); p -> drawLine(-20, 0, 20, 0); p -> drawLine(0, -20, 0, 20); p -> restore(); } /** * @brief ElementScene::setEventInterface * Set a new event interface * @param interface */ void ElementScene::setEventInterface(ESEventInterface *event_interface) { if (m_event_interface) { delete m_event_interface; //We must to re-init because previous interface //Reset his own init when deleted event_interface->init(); } m_event_interface = event_interface; } /** * @brief ElementScene::clearEventInterface * Clear the current event interface */ void ElementScene::clearEventInterface() { if(m_event_interface) { delete m_event_interface; m_event_interface = nullptr; } } /** * @brief ElementScene::setBehavior * Modifie the current behavior of this scene * @param b */ void ElementScene::setBehavior(ElementScene::Behavior b) { m_behavior = b; } /** @return la taille horizontale de la grille */ int ElementScene::xGrid() const { return(m_x_grid); } /** @return la taille verticale de la grille */ int ElementScene::yGrid() const { return(m_y_grid); } /** @param x_g Taille horizontale de la grille @param y_g Taille verticale de la grille */ void ElementScene::setGrid(int x_g, int y_g) { m_x_grid = x_g ? x_g : 1; m_y_grid = y_g ? y_g : 1; } /** * @brief ElementScene::toXml * Export this element as a xml file * @param all_parts (true by default) if true, export the entire element in xml, * if false, only export the selected parts. * @return an xml document that describe the element. */ const QDomDocument ElementScene::toXml(bool all_parts) { QRectF size= elementSceneGeometricRect(); //if the element doesn't contains the origin point of the scene //we move the element to the origin for solve this default before saving if (!size.contains(0,0) && all_parts) { centerElementToOrigine(); //recalcul the size after movement size= elementSceneGeometricRect(); } //define the size of the element by the upper multiple of 10 int upwidth = ((qRound(size.width())/10)*10)+10; if ((qRound(size.width())%10) > 6) upwidth+=10; int upheight = ((qRound(size.height())/10)*10)+10; if ((qRound(size.height())%10) > 6) upheight+=10; //the margin between the real size of the element and the rectangle that delimits int xmargin = qRound(upwidth - size.width()); int ymargin = qRound(upheight - size.height()); // document XML QDomDocument xml_document; //Root of xml document QDomElement root = xml_document.createElement("definition"); root.setAttribute("type", "element"); root.setAttribute("width", QString("%1").arg(upwidth)); root.setAttribute("height", QString("%1").arg(upheight)); root.setAttribute("hotspot_x", QString("%1").arg(-(qRound(size.x() - (xmargin/2))))); root.setAttribute("hotspot_y", QString("%1").arg(-(qRound(size.y() - (ymargin/2))))); root.setAttribute("orientation", "dyyy"); //we keep the orientation for compatibility with previous version of qet root.setAttribute("version", QET::version); root.setAttribute("link_type", m_elmt_type); //Uuid used to compare two elements QDomElement uuid = xml_document.createElement("uuid"); uuid.setAttribute("uuid", QUuid::createUuid().toString()); root.appendChild(uuid); //names of element root.appendChild(m_names_list.toXml(xml_document)); if (m_elmt_type == "slave" || m_elmt_type == "master") { QDomElement kindInfo = xml_document.createElement("kindInformations"); m_elmt_kindInfo.toXml(kindInfo, "kindInformation"); root.appendChild(kindInfo); } if(m_elmt_type == "simple" || m_elmt_type == "master" || m_elmt_type == "terminal") { QDomElement element_info = xml_document.createElement("elementInformations"); m_elmt_information.toXml(element_info, "elementInformation"); root.appendChild(element_info); } //complementary information about the element QDomElement informations_element = xml_document.createElement("informations"); root.appendChild(informations_element); informations_element.appendChild(xml_document.createTextNode(informations())); QDomElement description = xml_document.createElement("description"); //the graphic description of the element foreach(QGraphicsItem *qgi, zItems()) { //If the export concerns only the selection, the not selected part is ignored if (!all_parts && !qgi -> isSelected()) continue; if (CustomElementPart *ce = dynamic_cast(qgi)) { if (ce -> isUseless()) continue; description.appendChild(ce -> toXml(xml_document)); } } root.appendChild(description); xml_document.appendChild(root); return(xml_document); } /** @param xml_document un document XML decrivant un element @return le boundingRect du contenu de l'element */ QRectF ElementScene::boundingRectFromXml(const QDomDocument &xml_document) { // charge les parties depuis le document XML ElementContent loaded_content = loadContent(xml_document); if (loaded_content.isEmpty()) return(QRectF()); // calcule le boundingRect QRectF bounding_rect = elementContentBoundingRect(loaded_content); // detruit les parties chargees qDeleteAll(loaded_content); return(bounding_rect); } /** Importe l'element decrit dans un document XML. Si une position est precisee, les elements importes sont positionnes de maniere a ce que le coin superieur gauche du plus petit rectangle pouvant les entourant tous (le bounding rect) soit a cette position. @param xml_document un document XML decrivant l'element @param position La position des parties importees @param consider_informations Si vrai, les informations complementaires (dimensions, hotspot, etc.) seront prises en compte @param content_ptr si ce pointeur vers un ElementContent est different de 0, il sera rempli avec le contenu ajoute a l'element par le fromXml @return true si l'import a reussi, false sinon */ void ElementScene::fromXml(const QDomDocument &xml_document, const QPointF &position, bool consider_informations, ElementContent *content_ptr) { bool state = true; //Consider the informations of the element if (consider_informations) { state = applyInformations(xml_document); } if (state) { ElementContent loaded_content = loadContent(xml_document); if (position != QPointF()) addContentAtPos(loaded_content, position); else addContent(loaded_content); if (content_ptr) *content_ptr = loaded_content; } } /** @return the minimum, margin-less rectangle the element can fit into, in scene coordinates. It is different from itemsBoundingRect() because it is not supposed to imply any margin. */ QRectF ElementScene::elementSceneGeometricRect() const{ QRectF esgr; foreach (QGraphicsItem *qgi, items()) { if (qgi->type() == ElementPrimitiveDecorator::Type) continue; if (qgi->type() == QGraphicsRectItem::Type) continue; if (qgi->type() == PartTextField::Type) continue; if (qgi->type() == PartDynamicTextField::Type) continue; if (CustomElementPart *cep = dynamic_cast (qgi)) { esgr |= cep -> sceneGeometricRect(); } } return (esgr); } /** @return true si l'element comporte au moins une borne, false s'il n'en a aucune. */ bool ElementScene::containsTerminals() const { foreach(QGraphicsItem *qgi,items()) { if (qgraphicsitem_cast(qgi)) { return(true); } } return(false); } /** @return la pile d'annulations de cet editeur d'element */ QUndoStack &ElementScene::undoStack() { return(m_undo_stack); } /** @return le gestionnaire de QGraphicsItem de cet editeur d'element */ QGIManager &ElementScene::qgiManager() { return(m_qgi_manager); } /** @return true si le presse-papier semble contenir un element */ bool ElementScene::clipboardMayContainElement() { QString clipboard_text = QApplication::clipboard() -> text().trimmed(); bool may_be_element = clipboard_text.startsWith(""); return(may_be_element); } /** @param clipboard_content chaine de caractere, provenant vraisemblablement du presse-papier. @return true si clipboard_content a ete copie depuis cet element. */ bool ElementScene::wasCopiedFromThisElement(const QString &clipboard_content) { return(clipboard_content == m_last_copied); } /** Gere le fait de couper la selection = l'exporter en XML dans le presse-papier puis la supprimer. */ void ElementScene::cut() { copy(); QList cut_content = selectedItems(); clearSelection(); undoStack().push(new CutPartsCommand(this, cut_content)); } /** Gere le fait de copier la selection = l'exporter en XML dans le presse-papier. */ void ElementScene::copy() { // accede au presse-papier QClipboard *clipboard = QApplication::clipboard(); // genere la description XML de la selection QString clipboard_content = toXml(false).toString(4); // met la description XML dans le presse-papier if (clipboard -> supportsSelection()) { clipboard -> setText(clipboard_content, QClipboard::Selection); } clipboard -> setText(clipboard_content); // retient le dernier contenu copie m_last_copied = clipboard_content; } QETElementEditor* ElementScene::editor() const { return m_element_editor; } /** * @brief ElementScene::slot_select * Select the item in content, every others items in the scene are deselected * @param content */ void ElementScene::slot_select(const ElementContent &content) { blockSignals(true); //Befor clear selection, we must to remove the handlers items in @content, //because if in @content there are a selected item, but also its handlers items, //When item is deselected, the item delete its handlers items, //then handlers in content doesn't exist anymore and cause segfault QList items_list; for (QGraphicsItem *qgi : content) { if(qgi->type() != QetGraphicsHandlerItem::Type) items_list << qgi; } clearSelection(); foreach(QGraphicsItem *qgi, items_list) qgi -> setSelected(true); blockSignals(false); emit(selectionChanged()); } /** * @brief ElementScene::slot_selectAll * Select all items */ void ElementScene::slot_selectAll() { slot_select(items()); } /** * @brief ElementScene::slot_deselectAll * deselect all item */ void ElementScene::slot_deselectAll() { slot_select(ElementContent()); } /** Inverse la selection */ void ElementScene::slot_invertSelection() { blockSignals(true); foreach(QGraphicsItem *qgi, items()) qgi -> setSelected(!qgi -> isSelected()); blockSignals(false); emit(selectionChanged()); } /** Supprime les elements selectionnes */ void ElementScene::slot_delete() { // verifie qu'il y a qqc de selectionne QList selected_items = selectedItems(); if (selected_items.isEmpty()) return; // efface tout ce qui est selectionne m_undo_stack.push(new DeletePartsCommand(this, selected_items)); // removing items does not trigger QGraphicsScene::selectionChanged() emit(partsRemoved()); emit(selectionChanged()); } /** Lance un dialogue pour editer les informations complementaires de cet element. Concretement, ce champ libre est destine a accueillir des informations sur l'auteur de l'element, sa licence, etc. */ void ElementScene::slot_editAuthorInformations() { bool is_read_only = m_element_editor && m_element_editor -> isReadOnly(); // cree un dialogue QDialog dialog_author(m_element_editor); dialog_author.setModal(true); #ifdef Q_OS_MAC dialog_author.setWindowFlags(Qt::Sheet); #endif dialog_author.setMinimumSize(400, 260); dialog_author.setWindowTitle(tr("Éditer les informations sur l'auteur", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog_author); // ajoute un champ explicatif au dialogue QLabel *information_label = new QLabel(tr("Vous pouvez utiliser ce champ libre pour mentionner les auteurs de l'élément, sa licence, ou tout autre renseignement que vous jugerez utile.")); information_label -> setAlignment(Qt::AlignJustify | Qt::AlignVCenter); information_label -> setWordWrap(true); dialog_layout -> addWidget(information_label); // ajoute un QTextEdit au dialogue QTextEdit *text_field = new QTextEdit(); text_field -> setAcceptRichText(false); text_field -> setPlainText(informations()); text_field -> setReadOnly(is_read_only); dialog_layout -> addWidget(text_field); // ajoute deux boutons au dialogue QDialogButtonBox *dialog_buttons = new QDialogButtonBox(is_read_only ? QDialogButtonBox::Ok : QDialogButtonBox::Ok | QDialogButtonBox::Cancel); dialog_layout -> addWidget(dialog_buttons); connect(dialog_buttons, SIGNAL(accepted()), &dialog_author, SLOT(accept())); connect(dialog_buttons, SIGNAL(rejected()), &dialog_author, SLOT(reject())); // lance le dialogue if (dialog_author.exec() == QDialog::Accepted && !is_read_only) { QString new_infos = text_field -> toPlainText().remove(QChar(13)); // CR-less text if (new_infos != informations()) { undoStack().push(new ChangeInformationsCommand(this, informations(), new_infos)); } } } /** * @brief ElementScene::slot_editProperties * Open dialog to edit the element properties */ void ElementScene::slot_editProperties() { QString type = m_elmt_type; DiagramContext kind_info = m_elmt_kindInfo; DiagramContext elmt_info = m_elmt_information; ElementPropertiesEditorWidget epew(type, kind_info, elmt_info); epew.exec(); if (type != m_elmt_type || kind_info != m_elmt_kindInfo || elmt_info != m_elmt_information) undoStack().push(new ChangePropertiesCommand(this, type, kind_info, elmt_info)); } /** Lance un dialogue pour editer les noms de cet element */ void ElementScene::slot_editNames() { bool is_read_only = m_element_editor && m_element_editor -> isReadOnly(); // cree un dialogue QDialog dialog(m_element_editor); #ifdef Q_OS_MAC dialog.setWindowFlags(Qt::Sheet); #endif dialog.setModal(true); dialog.setMinimumSize(400, 330); dialog.setWindowTitle(tr("Éditer les noms", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog); // ajoute un champ explicatif au dialogue QLabel *information_label = new QLabel(tr("Vous pouvez spécifier le nom de l'élément dans plusieurs langues.")); information_label -> setAlignment(Qt::AlignJustify | Qt::AlignVCenter); information_label -> setWordWrap(true); dialog_layout -> addWidget(information_label); // ajoute un NamesListWidget au dialogue NamesListWidget *names_widget = new NamesListWidget(); names_widget -> setNames(m_names_list); names_widget -> setReadOnly(is_read_only); dialog_layout -> addWidget(names_widget); // ajoute deux boutons au dialogue QDialogButtonBox *dialog_buttons = new QDialogButtonBox(is_read_only ? QDialogButtonBox::Ok : QDialogButtonBox::Ok | QDialogButtonBox::Cancel); dialog_layout -> addWidget(dialog_buttons); connect(dialog_buttons, SIGNAL(accepted()), names_widget, SLOT(check())); connect(names_widget, SIGNAL(inputChecked()), &dialog, SLOT(accept())); connect(dialog_buttons, SIGNAL(rejected()), &dialog, SLOT(reject())); // lance le dialogue if (dialog.exec() == QDialog::Accepted && !is_read_only) { NamesList new_names(names_widget -> names()); if (new_names != m_names_list) undoStack().push(new ChangeNamesCommand(this, m_names_list, new_names)); } } /** Amene les elements selectionnes au premier plan */ void ElementScene::slot_bringForward() { undoStack().push(new ChangeZValueCommand(this, ChangeZValueCommand::BringForward)); emit(partsZValueChanged()); } /** Remonte les elements selectionnes d'un plan */ void ElementScene::slot_raise() { undoStack().push(new ChangeZValueCommand(this, ChangeZValueCommand::Raise)); emit(partsZValueChanged()); } /** Descend les elements selectionnes d'un plan */ void ElementScene::slot_lower() { undoStack().push(new ChangeZValueCommand(this, ChangeZValueCommand::Lower)); emit(partsZValueChanged()); } /** Envoie les elements selectionnes au fond */ void ElementScene::slot_sendBackward() { undoStack().push(new ChangeZValueCommand(this, ChangeZValueCommand::SendBackward)); emit(partsZValueChanged()); } /** @return the list of primitives currently present on the scene. */ QList ElementScene::primitives() const { QList primitives_list; foreach (QGraphicsItem *item, items()) { if (CustomElementPart *primitive = dynamic_cast(item)) { primitives_list << primitive; } } return(primitives_list); } /** @param include_terminals true pour inclure les bornes, false sinon @return les parties de l'element ordonnes par zValue croissante */ QList ElementScene::zItems(ItemOptions options) const { // handle dummy request, i.e. when neither Selected nor NonSelected are set if (!(options & ElementScene::Selected) && !(options & ElementScene::NonSelected)) { return(QList()); } // retrieve all items QList all_items_list(items()); QMutableListIterator i(all_items_list); // remove unrequired items if ((options & ElementScene::SelectedOrNot) != ElementScene::SelectedOrNot) { bool keep_selected = options & ElementScene::Selected; while (i.hasNext()) { if (i.next() -> isSelected() != keep_selected) { i.remove(); } } } QList terminals; QList helpers; for (i.toFront(); i.hasNext(); ) { i.next(); QGraphicsItem *qgi = i.value(); if ( qgi -> type() == ElementPrimitiveDecorator::Type || qgi -> type() == QGraphicsRectItem::Type || qgi->type() == QetGraphicsHandlerItem::Type ) { i.remove(); helpers << qgi; } else if (qgraphicsitem_cast(qgi)) { i.remove(); terminals << qgi; } } // ordonne les parties par leur zValue if (options & SortByZValue) { std::sort (all_items_list.begin(), all_items_list.end(), ElementScene::zValueLessThan); } // rajoute eventuellement les bornes if (options & ElementScene::IncludeTerminals) { all_items_list += terminals; } if (options & ElementScene::IncludeHelperItems) { all_items_list += helpers; } return(all_items_list); } /** @return les parties graphiques selectionnees */ ElementContent ElementScene::selectedContent() const { ElementContent content; foreach(QGraphicsItem *qgi, zItems()) { if (qgi -> isSelected()) content << qgi; } return(content); } /** @param to_paste Rectangle englobant les parties a coller @return le rectangle ou il faudra coller ces parties */ void ElementScene::getPasteArea(const QRectF &to_paste) { // on le dessine sur la scene m_paste_area -> setRect(to_paste); addItem(m_paste_area); // on passe la scene en mode "recherche de zone pour copier/coller" m_behavior = PasteArea; } /** * @brief ElementScene::reset * Remove all QGraphicsItems in the scene and clear the undo stack. */ void ElementScene::reset() { clearSelection(); undoStack().clear(); //We don't add handlers, because it's the role of the primitive or decorator to remove it. QList items_list; for (QGraphicsItem *qgi : items()) { if(qgi->type() != QetGraphicsHandlerItem::Type) items_list << qgi; } for (QGraphicsItem *qgi : items_list) { removeItem(qgi); qgiManager().release(qgi); } delete m_decorator; m_decorator = nullptr; } /** @param content Contenu ( = parties) d'un element @return le boundingRect de ces parties, exprime dans les coordonnes de la scene */ QRectF ElementScene::elementContentBoundingRect(const ElementContent &content) const { QRectF bounding_rect; foreach(QGraphicsItem *qgi, content) { // skip non-primitives QGraphicsItems (paste area, selection decorator) if (qgi -> type() == ElementPrimitiveDecorator::Type) continue; if (qgi -> type() == QGraphicsRectItem::Type) continue; bounding_rect |= qgi -> sceneBoundingRect(); } return(bounding_rect); } /** Applique les informations (dimensions, hostpot, orientations, connexions internes, noms et informations complementaires) contenu dans un document XML. @param xml_document Document XML a analyser @return true si la lecture et l'application des informations s'est bien passee, false sinon. */ bool ElementScene::applyInformations(const QDomDocument &xml_document) { // Root must be an element definition QDomElement root = xml_document.documentElement(); if (root.tagName() != "definition" || root.attribute("type") != "element") return(false); //Extract info about element type m_elmt_type = root.attribute("link_type", "simple"); m_elmt_kindInfo.fromXml(root.firstChildElement("kindInformations"), "kindInformation"); //Extract info of element m_elmt_information.fromXml(root.firstChildElement("elementInformations"), "elementInformation"); //Extract names of xml definition m_names_list.fromXml(root); //extract additional informations setInformations(QString()); for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling()) { QDomElement elmt = node.toElement(); if (elmt.isNull()) continue; if (elmt.tagName() == "informations") { setInformations(elmt.text()); break; } } return(true); } /** Par le document XML xml_document et retourne le contenu ( = liste de parties) correspondant. @param xml_document Document XML a analyser @param error_message pointeur vers une QString ; si error_message est different de 0, un message d'erreur sera stocke dedans si necessaire */ /** * @brief ElementScene::loadContent * @param xml_document : xml dom document to analyze * @return */ /** * @brief ElementScene::loadContent * Create and load the content describe in the xml document. * @param xml_document * @return the loaded content */ ElementContent ElementScene::loadContent(const QDomDocument &xml_document) { ElementContent loaded_parts; //The root is supposed to be an element definition QDomElement root = xml_document.documentElement(); if (root.tagName() != "definition" || root.attribute("type") != "element") return(loaded_parts); //Load the graphic description of the element for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling()) { QDomElement elmts = node.toElement(); if (elmts.isNull()) continue; if (elmts.tagName() == "description") { int z = 1; for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) { QDomElement qde = n.toElement(); if (qde.isNull()) continue; CustomElementPart *cep = nullptr; PartDynamicTextField *pdtf = nullptr; bool convertibleTextField = false; if (qde.tagName() == "line") cep = new PartLine (m_element_editor); else if (qde.tagName() == "rect") cep = new PartRectangle(m_element_editor); else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor); else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor); else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor); else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor); else if (qde.tagName() == "text") cep = new PartText (m_element_editor); else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor); else if (qde.tagName() == "dynamic_text") cep = new PartDynamicTextField (m_element_editor); //For the input (aka the old text field) we try to convert it to the new partDynamicTextField else if (qde.tagName() == "input") { convertibleTextField = PartDynamicTextField::canImportFromTextField(qde); if(convertibleTextField) cep = pdtf = new PartDynamicTextField(m_element_editor); else cep = new PartTextField(m_element_editor); } else continue; if (QGraphicsItem *qgi = dynamic_cast(cep)) { if (!qgi->zValue()) qgi->setZValue(z++); loaded_parts<fromTextFieldXml(qde); else cep->fromXml(qde); } else delete cep; } } } return(loaded_parts); } /** Ajoute le contenu content a cet element @param content contenu ( = liste de parties) a charger @return Le contenu ajoute */ ElementContent ElementScene::addContent(const ElementContent &content) { foreach(QGraphicsItem *part, content) { addPrimitive(part); } return(content); } /** Ajoute le contenu content a cet element @param content contenu ( = liste de parties) a charger @param pos Position du coin superieur gauche du contenu apres avoir ete ajoute @return Le contenu ajoute */ ElementContent ElementScene::addContentAtPos(const ElementContent &content, const QPointF &pos) { // calcule le boundingRect du contenu a ajouter QRectF bounding_rect = elementContentBoundingRect(content); // en deduit le decalage a appliquer aux parties pour les poser au point demander QPointF offset = pos - bounding_rect.topLeft(); // ajoute les parties avec le decalage adequat foreach(QGraphicsItem *part, content) { part -> setPos(part -> pos() + offset); addPrimitive(part); } return(content); } /** Add a primitive to the scene by wrapping it within an ElementPrimitiveDecorator group. */ void ElementScene::addPrimitive(QGraphicsItem *primitive) { if (!primitive) return; addItem(primitive); } /** Initialise la zone de collage */ void ElementScene::initPasteArea() { m_paste_area = new QGraphicsRectItem(); m_paste_area -> setZValue(1000000); QPen paste_area_pen; paste_area_pen.setStyle(Qt::DashDotLine); paste_area_pen.setColor(QColor(30, 56, 86, 255)); QBrush paste_area_brush; paste_area_brush.setStyle(Qt::SolidPattern); paste_area_brush.setColor(QColor(90, 167, 255, 64)); m_paste_area -> setPen(paste_area_pen); m_paste_area -> setBrush(paste_area_brush); } /** Arrondit les coordonnees du point passees en parametre de facon a ce que ce point soit aligne sur la grille. @param point une reference vers un QPointF. Cet objet sera modifie. */ QPointF ElementScene::snapToGrid(QPointF point) { point.rx() = qRound(point.x() / m_x_grid) * m_x_grid; point.ry() = qRound(point.y() / m_y_grid) * m_y_grid; return point; } /** @return true if \a item1's zValue() is less than \a item2's. */ bool ElementScene::zValueLessThan(QGraphicsItem *item1, QGraphicsItem *item2) { return(item1-> zValue() < item2 -> zValue()); } /** * @brief ElementScene::centerElementToOrigine * try to center better is possible the element to the scene * (the calcul isn't optimal but work good) */ void ElementScene::centerElementToOrigine() { QRectF size= elementSceneGeometricRect(); int center_x = qRound(size.center().x()); int center_y = qRound(size.center().y()); //define the movement of translation int move_x = center_x - (center_x %10); if (center_x < 0) move_x -= 10; int move_y = center_y - (center_y %10); if (center_y < 0) move_y -= 10; //move each primitive by @move foreach (QGraphicsItem *qgi, items()) { if (qgi -> type() == ElementPrimitiveDecorator::Type) continue; if (qgi -> type() == QGraphicsRectItem::Type) continue; //deselect item for disable decorator qgi -> setSelected(false); qgi -> moveBy(-(move_x), -(move_y)); } emit (needZoomFit()); } /** * @brief ElementScene::managePrimitivesGroups * Ensure the decorator is adequately shown, hidden or updated so it always * represents the current selection. */ void ElementScene::managePrimitivesGroups() { //this function is not supposed to be reentrant if (!m_decorator_lock->tryLock()) return; if (!m_decorator) { m_decorator = new ElementPrimitiveDecorator(); connect(m_decorator, SIGNAL(actionFinished(ElementEditionCommand*)), this, SLOT(stackAction(ElementEditionCommand *))); addItem(m_decorator); m_decorator -> hide(); } // should we hide the decorator? QList selected_items = zItems(ElementScene::Selected | ElementScene::IncludeTerminals); if (selected_items.size() <= 1) { m_decorator -> hide(); } else { for(QGraphicsItem *qgi : selected_items) { //We recall set selected, then every primitive will remove there handler because there are several item selected qgi->setSelected(true); } m_decorator -> setZValue(1000000); m_decorator -> setPos(0, 0); m_decorator -> setItems(selected_items); } m_decorator_lock -> unlock(); } /** Push the provided \a command on the undo stack. */ void ElementScene::stackAction(ElementEditionCommand *command) { if (command -> elementScene()) { if (command -> elementScene() != this) return; } else { command -> setElementScene(this); } if (!command -> elementView()) { foreach (QGraphicsView *view, views()) { if (ElementView *element_view = dynamic_cast(view)) { command -> setElementView(element_view); break; } } } undoStack().push(command); }