1 /*
2 Copyright 2006-2019 The QElectroTech Team
3 This file is part of QElectroTech.
4
5 QElectroTech is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 2 of the License, or
8 (at your option) any later version.
9
10 QElectroTech is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <math.h>
19 #include "qetgraphicsitem/conductor.h"
20 #include "qetgraphicsitem/conductortextitem.h"
21 #include "factory/elementfactory.h"
22 #include "diagram.h"
23 #include "diagramcommands.h"
24 #include "diagramcontent.h"
25 #include "diagramposition.h"
26 #include "exportdialog.h"
27 #include "qetgraphicsitem/independenttextitem.h"
28 #include "qetgraphicsitem/diagramimageitem.h"
29 #include "qetgraphicsitem/qetshapeitem.h"
30 #include "terminal.h"
31 #include "diagrameventinterface.h"
32 #include "qetapp.h"
33 #include "elementcollectionhandler.h"
34 #include "element.h"
35 #include "diagramview.h"
36 #include "dynamicelementtextitem.h"
37 #include "elementtextitemgroup.h"
38 #include "undocommand/addelementtextcommand.h"
39 #include "QPropertyUndoCommand/qpropertyundocommand.h"
40
41 int Diagram::xGrid = 10;
42 int Diagram::yGrid = 10;
43 int Diagram::xKeyGrid = 10;
44 int Diagram::yKeyGrid = 10;
45 int Diagram::xKeyGridFine = 1;
46 int Diagram::yKeyGridFine = 1;
47 const qreal Diagram::margin = 5.0;
48
49 // static variable to keep track of present background color of the diagram.
50 QColor Diagram::background_color = Qt::white;
51
52 /**
53 * @brief Diagram::Diagram
54 * Constructor
55 * @param project : The project of this diagram and also parent QObject
56 */
Diagram(QETProject * project)57 Diagram::Diagram(QETProject *project) :
58 QGraphicsScene (project),
59 m_project (nullptr),
60 diagram_qet_version_ (-1),
61 draw_grid_ (true),
62 use_border_ (true),
63 draw_terminals_ (true),
64 draw_colored_conductors_ (true),
65 m_event_interface (nullptr),
66 m_freeze_new_elements (false),
67 m_freeze_new_conductors_ (false)
68 {
69 setItemIndexMethod(QGraphicsScene::NoIndex);
70 //Set to no index, because they can be the source of the crash with conductor and shape ghost.
71 //https://forum.qt.io/topic/71316/qgraphicsscenefinditembsptreevisitor-visit-crashes-due-to-an-obsolete-paintevent-after-qgraphicsscene-removeitem
72 //https://stackoverflow.com/questions/38458830/crash-after-qgraphicssceneremoveitem-with-custom-item-class
73 //http://www.qtcentre.org/archive/index.php/t-33730.html
74 //http://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063
75
76 setProject(project);
77 qgi_manager_ = new QGIManager(this);
78 setBackgroundBrush(Qt::white);
79 conductor_setter_ = new QGraphicsLineItem(nullptr);
80 conductor_setter_ -> setZValue(1000000);
81
82 QPen pen(Qt::NoBrush, 1.5, Qt::DashLine);
83 pen.setColor(Qt::black);
84 conductor_setter_ -> setPen(pen);
85
86 connect(&border_and_titleblock, SIGNAL(needTitleBlockTemplate(const QString &)), this, SLOT(setTitleBlockTemplate(const QString &)));
87 connect(&border_and_titleblock, SIGNAL(diagramTitleChanged(const QString &)), this, SLOT(titleChanged(const QString &)));
88 connect(&border_and_titleblock, SIGNAL(titleBlockFolioChanged(const QString &)), this, SLOT(titleChanged(const QString &)));
89 connect(&border_and_titleblock, SIGNAL(borderChanged(QRectF,QRectF)), this, SLOT(adjustSceneRect()));
90 connect(&border_and_titleblock, SIGNAL(titleBlockFolioChanged(const QString &)), this, SLOT(updateLabels()));
91 connect(this, SIGNAL (diagramActivated()), this, SLOT(loadElmtFolioSeq()));
92 connect(this, SIGNAL (diagramActivated()), this, SLOT(loadCndFolioSeq()));
93 adjustSceneRect();
94 }
95
96 /**
97 * @brief Diagram::~Diagram
98 * Destructor
99 */
~Diagram()100 Diagram::~Diagram()
101 {
102 //First clear every selection to close an hypothetical editor
103 clearSelection();
104 // clear undo stack to prevent errors, because contains pointers to this diagram and is elements.
105 undoStack().clear();
106 //delete of QGIManager, every elements he knows are removed
107 delete qgi_manager_;
108 // remove of conductor setter
109 delete conductor_setter_;
110
111 if (m_event_interface)
112 delete m_event_interface;
113
114 // list removable items
115 QList<QGraphicsItem *> deletable_items;
116 for(QGraphicsItem *qgi : items())
117 {
118 if (qgi -> parentItem()) continue;
119 if (qgraphicsitem_cast<Conductor *>(qgi)) continue;
120 deletable_items << qgi;
121 }
122
123 qDeleteAll (deletable_items);
124 }
125
126 /**
127 Dessine l'arriere-plan du schema, cad la grille.
128 @param p Le QPainter a utiliser pour dessiner
129 @param r Le rectangle de la zone a dessiner
130 */
drawBackground(QPainter * p,const QRectF & r)131 void Diagram::drawBackground(QPainter *p, const QRectF &r) {
132 p -> save();
133
134 // desactive tout antialiasing, sauf pour le texte
135 p -> setRenderHint(QPainter::Antialiasing, false);
136 p -> setRenderHint(QPainter::TextAntialiasing, true);
137 p -> setRenderHint(QPainter::SmoothPixmapTransform, false);
138
139 // dessine un fond blanc
140 p -> setPen(Qt::NoPen);
141 //set brush color to present background color.
142 p -> setBrush(Diagram::background_color);
143 p -> drawRect(r);
144
145 if (draw_grid_) {
146 //Draw the point of the grid
147 // if background color is black, then grid spots shall be white, else they shall be black in color.
148 QPen pen;
149 Diagram::background_color == Qt::black? pen.setColor(Qt::white) : pen.setColor(Qt::black);
150 pen.setCosmetic(true);
151 p->setPen(pen);
152
153 p -> setBrush(Qt::NoBrush);
154
155 //If user allow zoom out beyond of folio, we draw grid outside of border.
156 QSettings settings;
157 int xGrid = settings.value("diagrameditor/Xgrid", Diagram::xGrid).toInt();
158 int yGrid = settings.value("diagrameditor/Ygrid", Diagram::yGrid).toInt();
159 QRectF rect = settings.value("diagrameditor/zoom-out-beyond-of-folio", false).toBool() ?
160 r :
161 border_and_titleblock.insideBorderRect().intersected(r);
162
163 qreal limite_x = rect.x() + rect.width();
164 qreal limite_y = rect.y() + rect.height();
165
166 int g_x = (int)ceil(rect.x());
167 while (g_x % xGrid) ++ g_x;
168 int g_y = (int)ceil(rect.y());
169 while (g_y % yGrid) ++ g_y;
170
171 QPolygon points;
172 for (int gx = g_x ; gx < limite_x ; gx += xGrid) {
173 for (int gy = g_y ; gy < limite_y ; gy += yGrid) {
174 points << QPoint(gx, gy);
175 }
176 }
177 p -> drawPoints(points);
178 }
179
180 if (use_border_) border_and_titleblock.draw(p);
181 p -> restore();
182 }
183
184 /**
185 * @brief Diagram::mouseDoubleClickEvent
186 * This event is managed by diagram event interface if any.
187 * @param event :
188 */
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)189 void Diagram::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
190 {
191 event->setAccepted(false);
192
193 if (m_event_interface) {
194 m_event_interface->mouseDoubleClickEvent(event);
195 if (event->isAccepted()) {
196 return;
197 }
198 }
199
200 QGraphicsScene::mouseDoubleClickEvent(event);
201 }
202
203 /**
204 * @brief Diagram::mousePressEvent
205 * This event is managed by diagram event interface if any.
206 * @param event
207 */
mousePressEvent(QGraphicsSceneMouseEvent * event)208 void Diagram::mousePressEvent(QGraphicsSceneMouseEvent *event)
209 {
210 event->setAccepted(false);
211
212 if (m_event_interface) {
213 m_event_interface->mousePressEvent(event);
214 if (event->isAccepted()) {
215 return;
216 }
217 }
218
219 QGraphicsScene::mousePressEvent(event);
220 }
221
222 /**
223 * @brief Diagram::mouseMoveEvent
224 * This event is managed by diagram event interface if any.
225 * @param event
226 */
mouseMoveEvent(QGraphicsSceneMouseEvent * event)227 void Diagram::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
228 {
229 event->setAccepted(false);
230
231 if (m_event_interface) {
232 m_event_interface->mouseMoveEvent(event);
233 if (event->isAccepted()) {
234 return;
235 }
236 }
237
238 QGraphicsScene::mouseMoveEvent(event);
239 }
240
241 /**
242 * @brief Diagram::mouseReleaseEvent
243 * This event is managed by diagram event interface if any.
244 * @param event
245 */
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)246 void Diagram::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
247 {
248 event->setAccepted(false);
249
250 if (m_event_interface) {
251 m_event_interface->mouseReleaseEvent(event);
252 if (event->isAccepted()) {
253 return;
254 }
255 }
256
257 QGraphicsScene::mouseReleaseEvent(event);
258 }
259
260 /**
261 * @brief Diagram::wheelEvent
262 * This event is managed by diagram event interface if any.
263 * @param event
264 */
wheelEvent(QGraphicsSceneWheelEvent * event)265 void Diagram::wheelEvent(QGraphicsSceneWheelEvent *event)
266 {
267 event->setAccepted(false);
268
269 if (m_event_interface) {
270 m_event_interface->wheelEvent(event);
271 if (event->isAccepted()) {
272 return;
273 }
274 }
275
276 QGraphicsScene::wheelEvent(event);
277 }
278
279 /**
280 * @brief Diagram::keyPressEvent
281 * This event is managed by diagram event interface if any.
282 * Else move selected elements
283 * @param e
284 */
keyPressEvent(QKeyEvent * event)285 void Diagram::keyPressEvent(QKeyEvent *event)
286 {
287 QSettings settings;
288 int xKeyGrid = settings.value("diagrameditor/key_Xgrid", Diagram::xKeyGrid).toInt();
289 int yKeyGrid = settings.value("diagrameditor/key_Ygrid", Diagram::yKeyGrid).toInt();
290 int xKeyGridFine = settings.value("diagrameditor/key_fine_Xgrid", Diagram::xKeyGridFine).toInt();
291 int yKeyGridFine = settings.value("diagrameditor/key_fine_Ygrid", Diagram::yKeyGridFine).toInt();
292 event->setAccepted(false);
293
294 if (m_event_interface) {
295 m_event_interface->keyPressEvent(event);
296 if (event->isAccepted()) {
297 return;
298 }
299 }
300
301 if (!isReadOnly())
302 {
303 QPointF movement;
304 qreal top_position = 0;
305 qreal left_position = 0;
306 DiagramContent dc(this);
307 if (!dc.items(DiagramContent::All).isEmpty())
308 {
309 //Move item with the keyboard arrow
310 if(event->modifiers() == Qt::NoModifier)
311 {
312 switch(event->key())
313 {
314 case Qt::Key_Left:
315 for (Element *item : dc.m_elements)
316 {
317 left_position = item->sceneBoundingRect().x();
318 if(left_position <= 5)
319 return;
320 }
321 movement = QPointF(-xKeyGrid, 0.0);
322 break;
323 case Qt::Key_Right:
324 movement = QPointF(+xKeyGrid, 0.0);
325 break;
326 case Qt::Key_Up:
327 for(Element *item : dc.m_elements)
328 {
329 top_position = item->sceneBoundingRect().y();
330 if(top_position <= 5)
331 return;
332 }
333 movement = QPointF(0.0, -yKeyGrid);
334 break;
335 case Qt::Key_Down:
336 movement = QPointF(0.0, +yKeyGrid);
337 break;
338 }
339
340 if (!movement.isNull() && !focusItem())
341 {
342 m_elements_mover.beginMovement(this);
343 m_elements_mover.continueMovement(movement);
344 event->accept();
345 return;
346 }
347 }
348 else if(event->modifiers() == Qt::AltModifier)
349
350 {
351 switch(event->key())
352 {
353 case Qt::Key_Left:
354 for (Element *item : dc.m_elements)
355 {
356 left_position = item->sceneBoundingRect().x();
357 if(left_position <= 5)
358 return;
359 }
360 movement = QPointF(-xKeyGridFine, 0.0);
361 break;
362 case Qt::Key_Right:
363 movement = QPointF(+xKeyGridFine, 0.0);
364 break;
365 case Qt::Key_Up:
366 for(Element *item : dc.m_elements)
367 {
368 top_position = item->sceneBoundingRect().y();
369 if(top_position <= 5)
370 return;
371 }
372 movement = QPointF(0.0, -yKeyGridFine);
373 break;
374 case Qt::Key_Down:
375 movement = QPointF(0.0, +yKeyGridFine);
376 break;
377 }
378
379 if (!movement.isNull() && !focusItem())
380 {
381 m_elements_mover.beginMovement(this);
382 m_elements_mover.continueMovement(movement);
383 event->accept();
384 return;
385 }
386 }
387 else if(event->modifiers() == Qt::ControlModifier)
388 {
389 //Adjust the alignment of a texts group
390 if(selectedItems().size() == 1 && selectedItems().first()->type() == QGraphicsItemGroup::Type)
391 {
392 if(ElementTextItemGroup *etig = dynamic_cast<ElementTextItemGroup *>(selectedItems().first()))
393 {
394 if(event->key() == Qt::Key_Left && etig->alignment() != Qt::AlignLeft)
395 undoStack().push(new AlignmentTextsGroupCommand(etig, Qt::AlignLeft));
396
397 else if (event->key() == Qt::Key_Up && etig->alignment() != Qt::AlignVCenter)
398 undoStack().push(new AlignmentTextsGroupCommand(etig, Qt::AlignVCenter));
399
400 else if (event->key() == Qt::Key_Right && etig->alignment() != Qt::AlignRight)
401 undoStack().push(new AlignmentTextsGroupCommand(etig, Qt::AlignRight));
402 }
403 }
404 }
405 }
406
407 event->ignore();
408 QGraphicsScene::keyPressEvent(event);
409 }
410 }
411
412 /**
413 * @brief Diagram::keyReleaseEvent
414 * This event is managed by diagram event interface if any.
415 * Else move selected element
416 * @param e
417 */
keyReleaseEvent(QKeyEvent * e)418 void Diagram::keyReleaseEvent(QKeyEvent *e)
419 {
420 e->setAccepted(false);
421
422 if (m_event_interface) {
423 m_event_interface->keyReleaseEvent(e);
424 if (e->isAccepted()) {
425 return;
426 }
427 }
428
429 bool transmit_event = true;
430 if (!isReadOnly()) {
431 // detecte le relachement d'une touche de direction ( = deplacement d'elements)
432 if (
433 (e -> key() == Qt::Key_Left || e -> key() == Qt::Key_Right ||
434 e -> key() == Qt::Key_Up || e -> key() == Qt::Key_Down) &&
435 !e -> isAutoRepeat()
436 ) {
437 m_elements_mover.endMovement();
438 e -> accept();
439 transmit_event = false;
440 }
441 }
442 if (transmit_event) {
443 QGraphicsScene::keyReleaseEvent(e);
444 }
445 }
446
447 /**
448 * @brief Diagram::setEventInterface
449 * Set event_interface has current interface.
450 * Diagram become the ownership of event_interface
451 * If there is a previous interface, they will be delete before
452 * and call init() to the new interface.
453 * @param event_interface
454 */
setEventInterface(DiagramEventInterface * event_interface)455 void Diagram::setEventInterface(DiagramEventInterface *event_interface)
456 {
457 if (m_event_interface)
458 {
459 delete m_event_interface;
460 event_interface -> init();
461 }
462 m_event_interface = event_interface;
463
464 connect(m_event_interface, &DiagramEventInterface::finish, [this]()
465 {
466 delete this->m_event_interface;
467 this->m_event_interface = nullptr;
468 });
469 }
470
471 /**
472 * @brief Diagram::clearEventInterface
473 * Clear the current event interface.
474 */
clearEventInterface()475 void Diagram::clearEventInterface()
476 {
477 if(m_event_interface)
478 {
479 delete m_event_interface;
480 m_event_interface = nullptr;
481 }
482 }
483
484 /**
485 * @brief Diagram::conductorsAutonumName
486 * @return the name of autonum to use.
487 */
conductorsAutonumName() const488 QString Diagram::conductorsAutonumName() const {
489 return m_conductors_autonum_name;
490 }
491
492 /**
493 * @brief Diagram::setConductorsAutonumName
494 * @param name, name of autonum to use.
495 */
setConductorsAutonumName(const QString & name)496 void Diagram::setConductorsAutonumName(const QString &name) {
497 m_conductors_autonum_name= name;
498 }
499
500 /**
501 Exporte le schema vers une image
502 @return Une QImage representant le schema
503 */
toPaintDevice(QPaintDevice & pix,int width,int height,Qt::AspectRatioMode aspectRatioMode)504 bool Diagram::toPaintDevice(QPaintDevice &pix, int width, int height, Qt::AspectRatioMode aspectRatioMode) {
505 // determine la zone source = contenu du schema + marges
506 QRectF source_area;
507 if (!use_border_) {
508 source_area = itemsBoundingRect();
509 source_area.translate(-margin, -margin);
510 source_area.setWidth (source_area.width () + 2.0 * margin);
511 source_area.setHeight(source_area.height() + 2.0 * margin);
512 } else {
513 source_area = QRectF(
514 0.0,
515 0.0,
516 border_and_titleblock.borderAndTitleBlockRect().width() + 2.0 * margin,
517 border_and_titleblock.borderAndTitleBlockRect().height() + 2.0 * margin
518 );
519 }
520
521 // si les dimensions ne sont pas precisees, l'image est exportee a l'echelle 1:1
522 QSize image_size = (width == -1 && height == -1) ? source_area.size().toSize() : QSize(width, height);
523
524 // prepare le rendu
525 QPainter p;
526 if (!p.begin(&pix)) return(false);
527
528 // rendu antialiase
529 p.setRenderHint(QPainter::Antialiasing, true);
530 p.setRenderHint(QPainter::TextAntialiasing, true);
531 p.setRenderHint(QPainter::SmoothPixmapTransform, true);
532
533 // deselectionne tous les elements
534 QList<QGraphicsItem *> selected_elmts = selectedItems();
535 foreach (QGraphicsItem *qgi, selected_elmts) qgi -> setSelected(false);
536
537 // effectue le rendu lui-meme
538 render(&p, QRect(QPoint(0, 0), image_size), source_area, aspectRatioMode);
539 p.end();
540
541 // restaure les elements selectionnes
542 foreach (QGraphicsItem *qgi, selected_elmts) qgi -> setSelected(true);
543
544 return(true);
545 }
546
547 /**
548 Permet de connaitre les dimensions qu'aura l'image generee par la methode toImage()
549 @return La taille de l'image generee par toImage()
550 */
imageSize() const551 QSize Diagram::imageSize() const {
552 // determine la zone source = contenu du schema + marges
553 qreal image_width, image_height;
554 if (!use_border_) {
555 QRectF items_rect = itemsBoundingRect();
556 image_width = items_rect.width();
557 image_height = items_rect.height();
558 } else {
559 image_width = border_and_titleblock.borderAndTitleBlockRect().width();
560 image_height = border_and_titleblock.borderAndTitleBlockRect().height();
561 }
562
563 image_width += 2.0 * margin;
564 image_height += 2.0 * margin;
565
566 // renvoie la taille de la zone source
567 return(QSizeF(image_width, image_height).toSize());
568 }
569
570 /**
571 @return true si le schema est considere comme vide, false sinon.
572 Un schema vide ne contient ni element, ni conducteur, ni champ de texte
573 */
isEmpty() const574 bool Diagram::isEmpty() const {
575 return(!items().count());
576 }
577
578 /**
579 * @brief Diagram::potential
580 * @return all potential in the diagram
581 *each potential are in the QList and each conductors of one potential are in the QSet
582 */
potentials()583 QList < QSet <Conductor *> > Diagram::potentials() {
584 QList < QSet <Conductor *> > potential_List;
585 if (content().conductors().size() == 0) return (potential_List); //return an empty potential
586 QList <Conductor *> conductors_list = content().conductors();
587
588 do {
589 QSet <Conductor *> one_potential = conductors_list.first() -> relatedPotentialConductors();
590 one_potential << conductors_list.takeFirst();
591 foreach (Conductor *c, one_potential) conductors_list.removeOne(c);
592 potential_List << one_potential;
593 } while (!conductors_list.empty());
594
595 return (potential_List);
596 }
597
598 /**
599 Exporte tout ou partie du schema
600 @param whole_content Booleen (a vrai par defaut) indiquant si le XML genere doit
601 representer l'integralite du schema ou seulement le contenu selectionne
602 @return Un Document XML (QDomDocument)
603 */
toXml(bool whole_content)604 QDomDocument Diagram::toXml(bool whole_content) {
605 // document
606 QDomDocument document;
607
608 // racine de l'arbre XML
609 QDomElement racine = document.createElement("diagram");
610
611 // add the application version number
612 racine.setAttribute("version", QET::version);
613
614 // proprietes du schema
615 if (whole_content) {
616 border_and_titleblock.titleBlockToXml(racine);
617 border_and_titleblock.borderToXml(racine);
618
619 // Default conductor properties
620 QDomElement default_conductor = document.createElement("defaultconductor");
621 defaultConductorProperties.toXml(default_conductor);
622 racine.appendChild(default_conductor);
623
624 // Conductor autonum
625 if (!m_conductors_autonum_name.isEmpty()) {
626 racine.setAttribute("conductorAutonum", m_conductors_autonum_name);
627 }
628
629 //Default New Element
630 racine.setAttribute("freezeNewElement", m_freeze_new_elements ? "true" : "false");
631
632 //Default New Conductor
633 racine.setAttribute("freezeNewConductor", m_freeze_new_conductors_ ? "true" : "false");
634
635 //Element Folio Sequential Variables
636 if (!m_elmt_unitfolio_max.isEmpty() || !m_elmt_tenfolio_max.isEmpty() || !m_elmt_hundredfolio_max.isEmpty()) {
637 QDomElement elmtfoliosequential = document.createElement("elementautonumfoliosequentials");
638 if (!m_elmt_unitfolio_max.isEmpty()) {
639 QDomElement elmtfolioseq = document.createElement("elementunitfolioseq");
640 folioSequentialsToXml(&m_elmt_unitfolio_max, &elmtfolioseq, "sequf_", "unitfolioseq", &document);
641 elmtfoliosequential.appendChild(elmtfolioseq);
642 }
643 if (!m_elmt_tenfolio_max.isEmpty()) {
644 QDomElement elmtfolioseq = document.createElement("elementtenfolioseq");
645 folioSequentialsToXml(&m_elmt_tenfolio_max, &elmtfolioseq, "seqtf_", "tenfolioseq", &document);
646 elmtfoliosequential.appendChild(elmtfolioseq);
647 }
648 if (!m_elmt_hundredfolio_max.isEmpty()) {
649 QDomElement elmtfolioseq = document.createElement("elementhundredfolioseq");
650 folioSequentialsToXml(&m_elmt_hundredfolio_max, &elmtfolioseq, "seqhf_", "hundredfolioseq", &document);
651 elmtfoliosequential.appendChild(elmtfolioseq);
652 }
653 racine.appendChild(elmtfoliosequential);
654 }
655 //Conductor Folio Sequential Variables
656 if (!m_cnd_unitfolio_max.isEmpty() || !m_cnd_tenfolio_max.isEmpty() || !m_cnd_hundredfolio_max.isEmpty()) {
657 QDomElement cndfoliosequential = document.createElement("conductorautonumfoliosequentials");
658 QHash<QString, QStringList>::iterator i;
659 if (!m_cnd_unitfolio_max.isEmpty()) {
660 QDomElement cndfolioseq = document.createElement("conductorunitfolioseq");
661 folioSequentialsToXml(&m_cnd_unitfolio_max, &cndfolioseq, "sequf_", "unitfolioseq", &document);
662 cndfoliosequential.appendChild(cndfolioseq);
663 }
664 if (!m_cnd_tenfolio_max.isEmpty()) {
665 QDomElement cndfolioseq = document.createElement("conductortenfolioseq");
666 folioSequentialsToXml(&m_cnd_tenfolio_max, &cndfolioseq, "seqtf_", "tenfolioseq", &document);
667 cndfoliosequential.appendChild(cndfolioseq);
668 }
669 if (!m_cnd_hundredfolio_max.isEmpty()) {
670 QDomElement cndfolioseq = document.createElement("conductorhundredfolioseq");
671 folioSequentialsToXml(&m_cnd_hundredfolio_max, &cndfolioseq, "seqhf_", "hundredfolioseq", &document);
672 cndfoliosequential.appendChild(cndfolioseq);
673 }
674 racine.appendChild(cndfoliosequential);
675 }
676 }
677 else {
678 //this method with whole_content to false,
679 //is often use to copy and paste the current selection
680 //so we add the id of the project where copy occur.
681 racine.setAttribute("projectId", QETApp::projectId(m_project));
682 }
683 document.appendChild(racine);
684
685 // si le schema ne contient pas d'element (et donc pas de conducteurs), on retourne de suite le document XML
686 if (items().isEmpty()) return(document);
687
688 // creation de trois listes : une qui contient les elements, une qui contient les conducteurs, une qui contient les champs de texte
689 QList<Element *> list_elements;
690 QList<Conductor *> list_conductors;
691 QList<DiagramTextItem *> list_texts;
692 QList<DiagramImageItem *> list_images;
693 QList<QetShapeItem *> list_shapes;
694
695 QList<QGraphicsItem *> list_items = items();
696 ;
697 // Determine les elements a "XMLiser"
698 foreach(QGraphicsItem *qgi, list_items) {
699 if (Element *elmt = qgraphicsitem_cast<Element *>(qgi)) {
700 if (whole_content) list_elements << elmt;
701 else if (elmt -> isSelected()) list_elements << elmt;
702 } else if (Conductor *f = qgraphicsitem_cast<Conductor *>(qgi)) {
703 if (whole_content) list_conductors << f;
704 // lorsqu'on n'exporte pas tout le diagram, il faut retirer les conducteurs non selectionnes
705 // et pour l'instant, les conducteurs non selectionnes sont les conducteurs dont un des elements n'est pas selectionne
706 else if (f -> terminal1 -> parentItem() -> isSelected() && f -> terminal2 -> parentItem() -> isSelected()) {
707 list_conductors << f;
708 }
709 } else if (IndependentTextItem *iti = qgraphicsitem_cast<IndependentTextItem *>(qgi)) {
710 if (whole_content) list_texts << iti;
711 else if (iti -> isSelected()) list_texts << iti;
712 } else if (DiagramImageItem *dii = qgraphicsitem_cast<DiagramImageItem *>(qgi)) {
713 if (whole_content) list_images << dii;
714 else if (dii -> isSelected()) list_images << dii;
715 } else if (QetShapeItem *dsi = qgraphicsitem_cast<QetShapeItem *>(qgi)) {
716 if (whole_content) list_shapes << dsi;
717 else if (dsi -> isSelected()) list_shapes << dsi;
718 }
719 }
720
721 // table de correspondance entre les adresses des bornes et leurs ids
722 QHash<Terminal *, int> table_adr_id;
723
724 // enregistrement des elements
725 if (!list_elements.isEmpty()) {
726 QDomElement elements = document.createElement("elements");
727 foreach(Element *elmt, list_elements) {
728 elements.appendChild(elmt -> toXml(document, table_adr_id));
729 }
730 racine.appendChild(elements);
731 }
732
733 // enregistrement des conducteurs
734 if (!list_conductors.isEmpty()) {
735 QDomElement conductors = document.createElement("conductors");
736 foreach(Conductor *cond, list_conductors) {
737 conductors.appendChild(cond -> toXml(document, table_adr_id));
738 }
739 racine.appendChild(conductors);
740 }
741
742 // enregistrement des champs de texte
743 if (!list_texts.isEmpty()) {
744 QDomElement inputs = document.createElement("inputs");
745 foreach(DiagramTextItem *dti, list_texts) {
746 inputs.appendChild(dti -> toXml(document));
747 }
748 racine.appendChild(inputs);
749 }
750
751 // save of images
752 if (!list_images.isEmpty()) {
753 QDomElement images = document.createElement("images");
754 foreach (DiagramImageItem *dii, list_images) {
755 images.appendChild(dii -> toXml(document));
756 }
757 racine.appendChild(images);
758 }
759
760 // save of basic shapes
761 if (!list_shapes.isEmpty()) {
762 QDomElement shapes = document.createElement("shapes");
763 foreach (QetShapeItem *dii, list_shapes) {
764 shapes.appendChild(dii -> toXml(document));
765 }
766 racine.appendChild(shapes);
767 }
768 // on retourne le document XML ainsi genere
769 return(document);
770 }
771
772 /**
773 + * @brief Diagram::folioSequentialsToXml
774 + * Add folio sequential to QDomElement
775 + * @param domElement to add attributes
776 + * @param hash to retrieve content with content
777 + * @param sequential type
778 + */
folioSequentialsToXml(QHash<QString,QStringList> * hash,QDomElement * domElement,const QString & seq_type,const QString & type,QDomDocument * doc)779 void Diagram::folioSequentialsToXml(QHash<QString, QStringList> *hash, QDomElement *domElement, const QString& seq_type, const QString& type, QDomDocument *doc) {
780 QHash<QString, QStringList>::iterator i;
781 for (i = hash->begin(); i != hash->end(); i++) {
782 QDomElement folioseq = doc->createElement(type);
783 folioseq.setAttribute("title", i.key());
784 for (int j = 0; j < i.value().size(); j++) {
785 folioseq.setAttribute(seq_type + QString::number(j+1), i.value().at(j));
786 }
787 domElement->appendChild(folioseq);
788 }
789 }
790
791 /**
792 Importe le schema decrit dans un document XML. Si une position est
793 precisee, les elements importes sont positionnes de maniere a ce que le
794 coin superieur gauche du plus petit rectangle pouvant les entourant tous
795 (le bounding rect) soit a cette position.
796 @param document Le document XML a analyser
797 @param position La position du schema importe
798 @param consider_informations Si vrai, les informations complementaires
799 (auteur, titre, ...) seront prises en compte
800 @param content_ptr si ce pointeur vers un DiagramContent est different de 0,
801 il sera rempli avec le contenu ajoute au schema par le fromXml
802 @return true si l'import a reussi, false sinon
803 */
fromXml(QDomDocument & document,QPointF position,bool consider_informations,DiagramContent * content_ptr)804 bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) {
805 QDomElement root = document.documentElement();
806 return(fromXml(root, position, consider_informations, content_ptr));
807 }
808
809 /**
810 Importe le schema decrit dans un element XML. Cette methode delegue son travail a Diagram::fromXml
811 Si l'import reussit, cette methode initialise egalement le document XML
812 interne permettant de bien gerer l'enregistrement de ce schema dans le
813 projet auquel il appartient.
814 @see Diagram::fromXml
815 @param document Le document XML a analyser
816 @param position La position du schema importe
817 @param consider_informations Si vrai, les informations complementaires
818 (auteur, titre, ...) seront prises en compte
819 @param content_ptr si ce pointeur vers un DiagramContent est different de 0,
820 il sera rempli avec le contenu ajoute au schema par le fromXml
821 @return true si l'import a reussi, false sinon
822
823 */
initFromXml(QDomElement & document,QPointF position,bool consider_informations,DiagramContent * content_ptr)824 bool Diagram::initFromXml(QDomElement &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) {
825 // import le contenu et les proprietes du schema depuis l'element XML fourni en parametre
826 bool from_xml = fromXml(document, position, consider_informations, content_ptr);
827
828 return(from_xml);
829 }
830
831 /**
832 Importe le schema decrit dans un element XML. Si une position est
833 precisee, les elements importes sont positionnes de maniere a ce que le
834 coin superieur gauche du plus petit rectangle pouvant les entourant tous
835 (le bounding rect) soit a cette position.
836 @param document Le document XML a analyser
837 @param position La position du schema importe
838 @param consider_informations Si vrai, les informations complementaires
839 (auteur, titre, ...) seront prises en compte
840 @param content_ptr si ce pointeur vers un DiagramContent est different de 0,
841 il sera rempli avec le contenu ajoute au schema par le fromXml
842 @return true si l'import a reussi, false sinon
843 */
fromXml(QDomElement & document,QPointF position,bool consider_informations,DiagramContent * content_ptr)844 bool Diagram::fromXml(QDomElement &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) {
845 const QDomElement& root = document;
846 // The first element must be a diagram
847 if (root.tagName() != "diagram") return(false);
848
849 // Read attributes of this diagram
850 if (consider_informations) {
851 // Version of diagram
852 bool conv_ok;
853 qreal version_value = root.attribute("version").toDouble(&conv_ok);
854 if (conv_ok) {
855 diagram_qet_version_ = version_value;
856 }
857
858 // Load border and titleblock
859 border_and_titleblock.titleBlockFromXml(root);
860 border_and_titleblock.borderFromXml(root);
861
862 // Find the element "defaultconductor".
863 // If found, load default conductor properties.
864 QDomElement default_conductor_elmt = root.firstChildElement("defaultconductor");
865 if (!default_conductor_elmt.isNull()) {
866 defaultConductorProperties.fromXml(default_conductor_elmt);
867 }
868
869 // Load the autonum
870 m_conductors_autonum_name = root.attribute("conductorAutonum");
871
872 // Load Freeze New Element
873 m_freeze_new_elements = root.attribute("freezeNewElement").toInt();
874
875 // Load Freeze New Conductor
876 m_freeze_new_conductors_ = root.attribute("freezeNewConductor").toInt();
877
878 //Load Element Folio Sequential
879 folioSequentialsFromXml(root, &m_elmt_unitfolio_max, "elementunitfolioseq","sequf_","unitfolioseq", "elementautonumfoliosequentials");
880 folioSequentialsFromXml(root, &m_elmt_tenfolio_max, "elementtenfolioseq","seqtf_", "tenfolioseq", "elementautonumfoliosequentials");
881 folioSequentialsFromXml(root, &m_elmt_hundredfolio_max, "elementhundredfolioseq","seqhf_", "hundredfolioseq", "elementautonumfoliosequentials");
882
883 //Load Conductor Folio Sequential
884 folioSequentialsFromXml(root, &m_cnd_unitfolio_max, "conductorunitfolioseq","sequf_","unitfolioseq", "conductorautonumfoliosequentials");
885 folioSequentialsFromXml(root, &m_cnd_tenfolio_max, "conductortenfolioseq","seqtf_","tenfolioseq", "conductorautonumfoliosequentials");
886 folioSequentialsFromXml(root, &m_cnd_hundredfolio_max, "conductorhundredfolioseq","seqhf_","hundredfolioseq", "conductorautonumfoliosequentials");
887 }
888
889 // if child haven't got a child, loading is finish (diagram is empty)
890 if (root.firstChild().isNull()) {
891 return(true);
892 }
893
894 // Backward compatibility: prior to version 0.3, we need to compensate, at
895 // diagram-opening time, the rotation of the element for each of its
896 // textfields having the "FollowParentRotation" option disabled.
897 // After 0.3, elements textfields get userx, usery and userrotation attributes
898 // that explicitly specify their position and orientation.
899 qreal project_qet_version = declaredQElectroTechVersion(true);
900 bool handle_inputs_rotation = (
901 project_qet_version != -1 && project_qet_version < 0.3 &&
902 m_project -> state() == QETProject::ProjectParsingRunning
903 );
904
905 //If paste from another project
906 if (root.hasAttribute("projectId")) {
907 QETProject *other_project = QETApp::project(root.attribute("projectId", "-1").toInt());
908
909 //We try to paste from another project, then befor paste elements,
910 //we must to import the definition of the pasted elements (owned by other project)
911 //in the embedded collection of this project
912 if (other_project && other_project != m_project) {
913 ElementCollectionHandler ech;
914 foreach (QDomElement element_xml, QET::findInDomElement(root, "elements", "element")) {
915 if (!Element::valideXml(element_xml)) continue;
916
917 QString type_id = element_xml.attribute("type");
918
919 if (type_id.startsWith("embed://")) {
920 ElementsLocation location(type_id, other_project);
921 ech.importFromProject(m_project, location);
922 }
923 }
924 }
925 }
926 //Load all elements from the XML
927 QList<Element *> added_elements;
928 QHash<int, Terminal *> table_adr_id;
929 foreach (QDomElement element_xml, QET::findInDomElement(root, "elements", "element"))
930 {
931 if (!Element::valideXml(element_xml)) continue;
932
933 // cree un element dont le type correspond a l'id type
934 QString type_id = element_xml.attribute("type");
935 ElementsLocation element_location;
936 if (type_id.startsWith("embed://")) {
937 element_location = ElementsLocation(type_id, m_project);
938 }
939 else {
940 element_location = ElementsLocation(type_id);
941 }
942
943 int state = 0;
944 Element *nvel_elmt = ElementFactory::Instance() -> createElement(element_location, nullptr, &state);
945 if (state)
946 {
947 QString debug_message = QString("Diagram::fromXml() : Le chargement de la description de l'element %1 a echoue avec le code d'erreur %2").arg(element_location.path()).arg(state);
948 qDebug() << qPrintable(debug_message);
949 delete nvel_elmt;
950 continue;
951 }
952
953 addItem(nvel_elmt);
954 //Loading fail, remove item from the diagram
955 if (!nvel_elmt->fromXml(element_xml, table_adr_id, handle_inputs_rotation))
956 {
957 removeItem(nvel_elmt);
958 delete nvel_elmt;
959 qDebug() << "Diagram::fromXml() : Le chargement des parametres d'un element a echoue";
960 } else {
961 added_elements << nvel_elmt;
962 }
963 }
964
965 // Load text
966 QList<IndependentTextItem *> added_texts;
967 foreach (QDomElement text_xml, QET::findInDomElement(root, "inputs", "input")) {
968 IndependentTextItem *iti = new IndependentTextItem();
969 iti -> fromXml(text_xml);
970 addItem(iti);
971 added_texts << iti;
972 }
973
974 // Load image
975 QList<DiagramImageItem *> added_images;
976 foreach (QDomElement image_xml, QET::findInDomElement(root, "images", "image")) {
977 DiagramImageItem *dii = new DiagramImageItem ();
978 dii -> fromXml(image_xml);
979 addItem(dii);
980 added_images << dii;
981 }
982
983 // Load shape
984 QList<QetShapeItem *> added_shapes;
985 foreach (QDomElement shape_xml, QET::findInDomElement(root, "shapes", "shape")) {
986 QetShapeItem *dii = new QetShapeItem (QPointF(0,0));
987 dii -> fromXml(shape_xml);
988 addItem(dii);
989 added_shapes << dii;
990 }
991
992 // Load conductor
993 QList<Conductor *> added_conductors;
994 foreach (QDomElement f, QET::findInDomElement(root, "conductors", "conductor"))
995 {
996 if (!Conductor::valideXml(f)) continue;
997
998 //Check if terminal that conductor must be linked is know
999 int id_p1 = f.attribute("terminal1").toInt();
1000 int id_p2 = f.attribute("terminal2").toInt();
1001 if (table_adr_id.contains(id_p1) && table_adr_id.contains(id_p2))
1002 {
1003 Terminal *p1 = table_adr_id.value(id_p1);
1004 Terminal *p2 = table_adr_id.value(id_p2);
1005 if (p1 != p2)
1006 {
1007 Conductor *c = new Conductor(p1, p2);
1008 if (c->isValid())
1009 {
1010 addItem(c);
1011 c -> fromXml(f);
1012 added_conductors << c;
1013 }
1014 else
1015 delete c;
1016 }
1017 }
1018 else qDebug() << "Diagram::fromXml() : Le chargement du conducteur" << id_p1 << id_p2 << "a echoue";
1019 }
1020
1021 //Translate items if a new position was given in parameter
1022 if (position != QPointF()) {
1023
1024 QList<QGraphicsItem *> added_items;
1025 foreach (Element *added_element, added_elements ) added_items << added_element;
1026 foreach (Conductor *added_cond, added_conductors) added_items << added_cond;
1027 foreach (QetShapeItem *added_shape, added_shapes ) added_items << added_shape;
1028 foreach (DiagramTextItem *added_text, added_texts ) added_items << added_text;
1029 foreach (DiagramImageItem *added_image, added_images ) added_items << added_image;
1030
1031 //Get the top left corner of the rectangle that contain all added items
1032 QRectF items_rect;
1033 foreach (QGraphicsItem *item, added_items) {
1034 items_rect = items_rect.united(item -> mapToScene(item -> boundingRect()).boundingRect());
1035 }
1036
1037 QPointF point_ = items_rect.topLeft();
1038 QPointF pos_ = Diagram::snapToGrid(QPointF (position.x() - point_.x(),
1039 position.y() - point_.y()));
1040
1041 //Translate all added items
1042 foreach (QGraphicsItem *qgi, added_items)
1043 qgi -> setPos( qgi -> pos() += pos_);
1044 }
1045
1046 // remplissage des listes facultatives
1047 if (content_ptr) {
1048 content_ptr -> m_elements = added_elements;
1049 content_ptr -> m_conductors_to_move = added_conductors;
1050 content_ptr -> m_text_fields = added_texts.toSet();
1051 content_ptr -> m_images = added_images.toSet();
1052 content_ptr -> m_shapes = added_shapes.toSet();
1053 }
1054 adjustSceneRect();
1055 return(true);
1056 }
1057
1058 /**
1059 * @brief Diagram::folioSequentialsFromXml
1060 * Load folio sequential from QDomElement
1061 * @param root containing all folio sequentials
1062 * @param hash to be loaded with content
1063 * @param folioSeq type
1064 * @param seq type
1065 * @param type of sequential
1066 */
folioSequentialsFromXml(const QDomElement & root,QHash<QString,QStringList> * hash,const QString & folioSeq,const QString & seq,const QString & type,const QString & autonumFolioSeqType)1067 void Diagram::folioSequentialsFromXml(const QDomElement &root, QHash<QString, QStringList>* hash, const QString& folioSeq, const QString& seq, const QString& type, const QString& autonumFolioSeqType) {
1068 foreach (QDomElement folioSeqAutoNum, QET::findInDomElement(root, autonumFolioSeqType, folioSeq)) {
1069 for(QDomElement folioseq = folioSeqAutoNum.firstChildElement(type); !folioseq.isNull(); folioseq = folioseq.nextSiblingElement(type)) {
1070 QString title = folioseq.attribute("title");
1071 QStringList list;
1072 int i = 1;
1073 while (folioseq.hasAttribute(seq + QString::number(i))) {
1074 list << folioseq.attribute(seq + QString::number(i));
1075 i++;
1076 }
1077 hash->insert(title,list);
1078 }
1079 }
1080 }
1081
1082 /**
1083 * @brief Diagram::refreshContents
1084 * refresh all content of diagram.
1085 * - refresh conductor text.
1086 * - linking the elements waiting to be linked
1087 * - Refresh the connection of the dynamic element text item (use for text with source of text label)
1088 */
refreshContents()1089 void Diagram::refreshContents()
1090 {
1091 for (Element *elmt : elements())
1092 {
1093 elmt->initLink(project());
1094 for (DynamicElementTextItem *deti : elmt->dynamicTextItems())
1095 deti->refreshLabelConnection();
1096 }
1097
1098 for (Conductor *conductor : conductors())
1099 conductor->refreshText();
1100 }
1101
1102 /**
1103 * @brief Diagram::addItem
1104 * Réimplemented from QGraphicsScene::addItem(QGraphicsItem *item)
1105 * Do some specific operation if item need it (for exemple an element)
1106 * @param item
1107 */
addItem(QGraphicsItem * item)1108 void Diagram::addItem(QGraphicsItem *item)
1109 {
1110 if (!item || isReadOnly() || item->scene() == this) return;
1111 QGraphicsScene::addItem(item);
1112
1113 switch (item->type())
1114 {
1115 case Conductor::Type:
1116 {
1117 Conductor *conductor = static_cast<Conductor *>(item);
1118 conductor->terminal1->addConductor(conductor);
1119 conductor->terminal2->addConductor(conductor);
1120 conductor->calculateTextItemPosition();
1121 break;
1122 }
1123 default: {break;}
1124 }
1125 }
1126
1127 /**
1128 * @brief Diagram::removeItem
1129 * Réimplemented from QGraphicsScene::removeItem(QGraphicsItem *item)
1130 * Do some specific operation if item need it (for exemple an element)
1131 * @param item
1132 */
removeItem(QGraphicsItem * item)1133 void Diagram::removeItem(QGraphicsItem *item)
1134 {
1135 if (!item || isReadOnly()) return;
1136
1137 switch (item->type())
1138 {
1139 case Element::Type:
1140 {
1141 Element *elmt = static_cast<Element*>(item);
1142 elmt->unlinkAllElements();
1143 break;
1144 }
1145 case Conductor::Type:
1146 {
1147 Conductor *conductor = static_cast<Conductor *>(item);
1148 conductor->terminal1->removeConductor(conductor);
1149 conductor->terminal2->removeConductor(conductor);
1150 break;
1151 }
1152 default: {break;}
1153 }
1154
1155 QGraphicsScene::removeItem(item);
1156 }
1157
titleChanged(const QString & title)1158 void Diagram::titleChanged(const QString &title) {
1159 emit(diagramTitleChanged(this, title));
1160 }
1161
1162 /**
1163 This slot may be used to inform the diagram object that the given title
1164 block template has changed. The diagram will thus flush its title
1165 block-dedicated rendering cache.
1166 @param template_name Name of the title block template that has changed
1167 */
titleBlockTemplateChanged(const QString & template_name)1168 void Diagram::titleBlockTemplateChanged(const QString &template_name) {
1169 if (border_and_titleblock.titleBlockTemplateName() != template_name) return;
1170
1171 border_and_titleblock.titleBlockTemplateChanged(template_name);
1172 update();
1173 }
1174
1175 /**
1176 This slot has to be be used to inform this class that the given title block
1177 template is about to be removed and is no longer accessible. This class
1178 will either use the provided optional TitleBlockTemplate or the default
1179 title block provided by QETApp::defaultTitleBlockTemplate()
1180 @param template_name Name of the title block template that has changed
1181 @param new_template (Optional) Name of the title block template to use instead
1182 */
titleBlockTemplateRemoved(const QString & template_name,const QString & new_template)1183 void Diagram::titleBlockTemplateRemoved(const QString &template_name, const QString &new_template)
1184 {
1185 if (border_and_titleblock.titleBlockTemplateName() != template_name) return;
1186 const TitleBlockTemplate *final_template = m_project->embeddedTitleBlockTemplatesCollection()->getTemplate(new_template);
1187 border_and_titleblock.titleBlockTemplateRemoved(template_name, final_template);
1188 update();
1189 }
1190
1191 /**
1192 Set the template to use to render the title block of this diagram.
1193 @param template_name Name of the title block template.
1194 */
setTitleBlockTemplate(const QString & template_name)1195 void Diagram::setTitleBlockTemplate(const QString &template_name)
1196 {
1197 if (!m_project) return;
1198
1199 QString current_name = border_and_titleblock.titleBlockTemplateName();
1200 const TitleBlockTemplate *titleblock_template = m_project->embeddedTitleBlockTemplatesCollection()->getTemplate(template_name);
1201 border_and_titleblock.titleBlockTemplateRemoved(current_name, titleblock_template);
1202
1203 if (template_name != current_name)
1204 emit(usedTitleBlockTemplateChanged(template_name));
1205 }
1206
1207 /**
1208 Selectionne tous les objets du schema
1209 */
selectAll()1210 void Diagram::selectAll() {
1211 if (items().isEmpty()) return;
1212
1213 blockSignals(true);
1214 foreach(QGraphicsItem *qgi, items()) qgi -> setSelected(true);
1215 blockSignals(false);
1216 emit(selectionChanged());
1217 }
1218
1219 /**
1220 Deslectionne tous les objets selectionnes
1221 */
deselectAll()1222 void Diagram::deselectAll() {
1223 if (items().isEmpty()) return;
1224
1225 clearSelection();
1226 }
1227
1228 /**
1229 Inverse l'etat de selection de tous les objets du schema
1230 */
invertSelection()1231 void Diagram::invertSelection() {
1232 if (items().isEmpty()) return;
1233
1234 blockSignals(true);
1235 foreach (QGraphicsItem *item, items()) item -> setSelected(!item -> isSelected());
1236 blockSignals(false);
1237 emit(selectionChanged());
1238 }
1239
1240 /**
1241 * @brief Diagram::updateLabels
1242 * Update elements and conductors that reference folio field
1243 * in their labels.
1244 */
updateLabels()1245 void Diagram::updateLabels()
1246 {
1247 for (Conductor *cnd : content().conductors())
1248 {
1249 cnd->refreshText();
1250 }
1251 }
1252
1253 /**
1254 * @brief Diagram::insertFolioSeqHash
1255 * This class inserts a stringlist containing all
1256 * sequential variables related to an autonum in a QHash
1257 * @param Hash to be accessed
1258 * @param autonum title
1259 * @param sequential to be treated
1260 * @param type to be treated
1261 * @param Numerotation Context to be manipulated
1262 */
insertFolioSeqHash(QHash<QString,QStringList> * hash,const QString & title,const QString & type,NumerotationContext * nc)1263 void Diagram::insertFolioSeqHash(QHash<QString, QStringList> *hash, const QString& title, const QString& type, NumerotationContext *nc) {
1264 QStringList max;
1265 for (int i = 0; i < nc->size(); i++) {
1266 if (nc->itemAt(i).at(0) == type) {
1267 nc->replaceValue(i, QString::number(nc->itemAt(i).at(3).toInt()));
1268 max.append(QString::number(nc->itemAt(i).at(3).toInt() - nc->itemAt(i).at(2).toInt()));
1269 }
1270 }
1271 hash->insert(title,max);
1272 }
1273
1274 /**
1275 * @brief Diagram::loadFolioSeqHash
1276 * This class loads all folio sequential variables
1277 * related to the current autonum
1278 * @param Hash to be accessed
1279 * @param autonum title
1280 * @param sequential to be treated
1281 * @param type to be treated
1282 * @param Numerotation Context to be manipulated
1283 */
loadFolioSeqHash(QHash<QString,QStringList> * hash,const QString & title,const QString & type,NumerotationContext * nc)1284 void Diagram::loadFolioSeqHash(QHash<QString, QStringList> *hash, const QString& title, const QString& type, NumerotationContext *nc) {
1285 int j = 0;
1286 for (int i = 0; i < nc->size(); i++) {
1287 if (nc->itemAt(i).at(0) == type) {
1288 QString new_value;
1289 new_value = QString::number(hash->value(title).at(j).toInt() + nc->itemAt(i).at(2).toInt());
1290 nc->replaceValue(i,new_value);
1291 j++;
1292 }
1293 }
1294 }
1295
1296 /**
1297 * @brief Diagram::changeZValue
1298 * Change the Z value of the current selected item, according to @option
1299 */
changeZValue(QET::DepthOption option)1300 void Diagram::changeZValue(QET::DepthOption option)
1301 {
1302 DiagramContent dc(this);
1303 QUndoCommand *undo = new QUndoCommand(tr("Modifier la profondeur"));
1304 QList<QGraphicsItem *> l = dc.items(DiagramContent::SelectedOnly | \
1305 DiagramContent::Elements | \
1306 DiagramContent::Shapes | \
1307 DiagramContent::Images);
1308 QList<QGraphicsObject *> list;
1309 for(QGraphicsItem *item : l)
1310 list << item->toGraphicsObject();
1311
1312 qreal maxz=0,
1313 minz=0;
1314 for(QGraphicsItem *item : this->items())
1315 {
1316 qreal z = item->zValue();
1317 if(z >= Terminal::Z-2)
1318 continue;
1319 maxz = std::max(maxz,z);
1320 minz = std::min(minz,z);
1321 }
1322
1323 if(option == QET::Raise)
1324 {
1325 for(QGraphicsObject *qgo : list)
1326 if(qgo->zValue() < (Terminal::Z-2)) //Ensure item is always below terminal
1327 new QPropertyUndoCommand(qgo, "z", qgo->zValue(), qgo->zValue()+1, undo);
1328 }
1329 else if(option == QET::Lower)
1330 {
1331 for(QGraphicsObject *qgo : list)
1332 if(qgo->zValue() < (Terminal::Z-2)) //Ensure item is always below terminal
1333 new QPropertyUndoCommand(qgo, "z", qgo->zValue(), qgo->zValue()-1, undo);
1334 }
1335 else if (option == QET::BringForward)
1336 {
1337 for(QGraphicsObject *qgo : list)
1338 new QPropertyUndoCommand(qgo, "z", qgo->zValue(), maxz+1, undo);
1339 }
1340 else if(option == QET::SendBackward)
1341 {
1342 for(QGraphicsObject *qgo : list)
1343 new QPropertyUndoCommand(qgo, "z", qgo->zValue(), minz-1, undo);
1344 }
1345
1346 if(undo->childCount())
1347 this->undoStack().push(undo);
1348 else
1349 delete undo;
1350 }
1351
1352 /**
1353 * @brief Diagram::loadElmtFolioSeq
1354 * This class loads all folio sequential variables related
1355 * to the current autonum
1356 */
loadElmtFolioSeq()1357 void Diagram::loadElmtFolioSeq() {
1358 QString title = project()->elementCurrentAutoNum();
1359 NumerotationContext nc = project()->elementAutoNum(title);
1360
1361 //Unit Folio
1362 if (m_elmt_unitfolio_max.isEmpty() || !m_elmt_unitfolio_max.contains(title)) {
1363 //Insert Initial Value
1364 if (project()->elementAutoNumCurrentFormula().contains("%sequf_")) {
1365 insertFolioSeqHash(&m_elmt_unitfolio_max,title,"unitfolio",&nc);
1366 project()->addElementAutoNum(title,nc);
1367 }
1368 }
1369 else if (m_elmt_unitfolio_max.contains(title)) {
1370 //Load Folio Current Value
1371 if (project()->elementAutoNumCurrentFormula().contains("%sequf_")) {
1372 loadFolioSeqHash(&m_elmt_unitfolio_max,title,"unitfolio",&nc);
1373 project()->addElementAutoNum(title,nc);
1374 }
1375 }
1376
1377 //Ten Folio
1378 if (m_elmt_tenfolio_max.isEmpty() || !m_elmt_tenfolio_max.contains(title)) {
1379 //Insert Initial Value
1380 if (project()->elementAutoNumCurrentFormula().contains("%seqtf_")) {
1381 insertFolioSeqHash(&m_elmt_tenfolio_max,title,"tenfolio",&nc);
1382 project()->addElementAutoNum(title,nc);
1383 }
1384 }
1385 else if (m_elmt_tenfolio_max.contains(title)) {
1386 //Load Folio Current Value
1387 if (project()->elementAutoNumCurrentFormula().contains("%seqtf_")) {
1388 loadFolioSeqHash(&m_elmt_tenfolio_max,title,"tenfolio",&nc);
1389 project()->addElementAutoNum(title,nc);
1390 }
1391 }
1392
1393 //Hundred Folio
1394 if (m_elmt_hundredfolio_max.isEmpty() || !m_elmt_hundredfolio_max.contains(title)) {
1395 //Insert Initial Value
1396 if (project()->elementAutoNumCurrentFormula().contains("%seqhf_")) {
1397 insertFolioSeqHash(&m_elmt_hundredfolio_max,title,"hundredfolio",&nc);
1398 project()->addElementAutoNum(title,nc);
1399 }
1400 }
1401 else if (m_elmt_hundredfolio_max.contains(title)) {
1402 //Load Folio Current Value
1403 if (project()->elementAutoNumCurrentFormula().contains("%seqhf_")) {
1404 loadFolioSeqHash(&m_elmt_hundredfolio_max,title,"hundredfolio",&nc);
1405 project()->addElementAutoNum(title,nc);
1406 }
1407 }
1408 }
1409
1410 /**
1411 * @brief Diagram::loadCndFolioSeq
1412 * This class loads all conductor folio sequential variables related
1413 * to the current autonum
1414 */
loadCndFolioSeq()1415 void Diagram::loadCndFolioSeq() {
1416 //Conductor
1417 QString title = project()->conductorCurrentAutoNum();
1418 NumerotationContext nc = project()->conductorAutoNum(title);
1419 QString formula = autonum::numerotationContextToFormula(nc);
1420
1421 //Unit Folio
1422 if (m_cnd_unitfolio_max.isEmpty() || !m_cnd_unitfolio_max.contains(title)) {
1423 //Insert Initial Value
1424 if (formula.contains("%sequf_")) {
1425 insertFolioSeqHash(&m_cnd_unitfolio_max,title,"unitfolio",&nc);
1426 project()->addConductorAutoNum(title,nc);
1427 }
1428 }
1429 else if (m_cnd_unitfolio_max.contains(title)) {
1430 //Load Folio Current Value
1431 if (formula.contains("%sequf_")) {
1432 loadFolioSeqHash(&m_cnd_unitfolio_max,title,"unitfolio",&nc);
1433 project()->addConductorAutoNum(title,nc);
1434 }
1435 }
1436
1437 //Ten Folio
1438 if (m_cnd_tenfolio_max.isEmpty() || !m_cnd_tenfolio_max.contains(title)) {
1439 //Insert Initial Value
1440 if (formula.contains("%seqtf_")) {
1441 insertFolioSeqHash(&m_cnd_tenfolio_max,title,"tenfolio",&nc);
1442 project()->addConductorAutoNum(title,nc);
1443 }
1444 }
1445 else if (m_cnd_tenfolio_max.contains(title)) {
1446 //Load Folio Current Value
1447 if (formula.contains("%seqtf_")) {
1448 loadFolioSeqHash(&m_cnd_tenfolio_max,title,"tenfolio",&nc);
1449 project()->addConductorAutoNum(title,nc);
1450 }
1451 }
1452
1453 //Hundred Folio
1454 if (m_cnd_hundredfolio_max.isEmpty() || !m_cnd_hundredfolio_max.contains(title)) {
1455 //Insert Initial Value
1456 if (formula.contains("%seqhf_")) {
1457 insertFolioSeqHash(&m_cnd_hundredfolio_max,title,"hundredfolio",&nc);
1458 project()->addConductorAutoNum(title,nc);
1459 }
1460 }
1461 else if (m_cnd_hundredfolio_max.contains(title)) {
1462 //Load Folio Current Value
1463 if (formula.contains("%seqhf_")) {
1464 loadFolioSeqHash(&m_cnd_hundredfolio_max,title,"hundredfolio",&nc);
1465 project()->addConductorAutoNum(title,nc);
1466 }
1467 }
1468 }
1469
1470 /**
1471 @return le titre du cartouche
1472 */
title() const1473 QString Diagram::title() const {
1474 return(border_and_titleblock.title());
1475 }
1476
elements() const1477 QList <Element *> Diagram::elements() const {
1478 QList<Element *> element_list;
1479 foreach (QGraphicsItem *qgi, items()) {
1480 if (Element *elmt = qgraphicsitem_cast<Element *>(qgi))
1481 element_list <<elmt;
1482 }
1483 return (element_list);
1484 }
1485
1486 /**
1487 * @brief Diagram::conductors
1488 * Return the list containing all conductors
1489 */
conductors() const1490 QList <Conductor *> Diagram::conductors() const {
1491 QList<Conductor *> cnd_list;
1492 foreach (QGraphicsItem *qgi, items()) {
1493 if (Conductor *cnd = qgraphicsitem_cast<Conductor *>(qgi))
1494 cnd_list <<cnd;
1495 }
1496 return (cnd_list);
1497 }
1498
elementsMover()1499 ElementsMover &Diagram::elementsMover() {
1500 return m_elements_mover;
1501 }
1502
elementTextsMover()1503 ElementTextsMover &Diagram::elementTextsMover() {
1504 return m_element_texts_mover;
1505 }
1506
1507 /**
1508 Permet de savoir si un element est utilise sur un schema
1509 @param location Emplacement d'un element
1510 @return true si l'element location est utilise sur ce schema, false sinon
1511 */
usesElement(const ElementsLocation & location)1512 bool Diagram::usesElement(const ElementsLocation &location)
1513 {
1514 for(Element *element : elements()) {
1515 if (element -> location() == location) {
1516 return(true);
1517 }
1518 }
1519 return(false);
1520 }
1521
1522 /**
1523 @param a title block template name
1524 @return true if the provided template is used by this diagram, false
1525 otherwise.
1526 */
usesTitleBlockTemplate(const QString & name)1527 bool Diagram::usesTitleBlockTemplate(const QString &name) {
1528 return(name == border_and_titleblock.titleBlockTemplateName());
1529 }
1530
1531 /**
1532 * @brief Diagram::freezeElements
1533 * Freeze every existent element label.
1534 */
freezeElements(bool freeze)1535 void Diagram::freezeElements(bool freeze) {
1536 foreach (Element *elmt, elements()) {
1537 elmt->freezeLabel(freeze);
1538 }
1539 }
1540
1541 /**
1542 * @brief Diagram::unfreezeElements
1543 * Unfreeze every existent element label.
1544 */
unfreezeElements()1545 void Diagram::unfreezeElements() {
1546 foreach (Element *elmt, elements()) {
1547 elmt->freezeLabel(false);
1548 }
1549 }
1550
1551 /**
1552 * @brief Diagram::freezeNewElements
1553 * Set new element label to be frozen.
1554 */
setFreezeNewElements(bool b)1555 void Diagram::setFreezeNewElements(bool b) {
1556 m_freeze_new_elements = b;
1557 }
1558
1559 /**
1560 * @brief Diagram::freezeNewElements
1561 * @return current freeze new element status .
1562 */
freezeNewElements()1563 bool Diagram::freezeNewElements() {
1564 return m_freeze_new_elements;
1565 }
1566
1567 /**
1568 * @brief Diagram::freezeConductors
1569 * Freeze every existent conductor label.
1570 */
freezeConductors(bool freeze)1571 void Diagram::freezeConductors(bool freeze) {
1572 foreach (Conductor *cnd, conductors()) {
1573 cnd->setFreezeLabel(freeze);
1574 }
1575 }
1576
1577 /**
1578 * @brief Diagram::setfreezeNewConductors
1579 * Set new conductor label to be frozen.
1580 */
setFreezeNewConductors(bool b)1581 void Diagram::setFreezeNewConductors(bool b) {
1582 m_freeze_new_conductors_ = b;
1583 }
1584
1585 /**
1586 * @brief Diagram::freezeNewConductors
1587 * @return current freeze new conductor status .
1588 */
freezeNewConductors()1589 bool Diagram::freezeNewConductors() {
1590 return m_freeze_new_conductors_;
1591 }
1592
1593 /**
1594 * @brief Diagram::adjustSceneRect
1595 * Recalcul and adjust the size of the scene
1596 */
adjustSceneRect()1597 void Diagram::adjustSceneRect()
1598 {
1599 QRectF old_rect = sceneRect();
1600 setSceneRect(border_and_titleblock.borderAndTitleBlockRect().united(itemsBoundingRect()));
1601 update(old_rect.united(sceneRect()));
1602 }
1603
1604 /**
1605 Cette methode permet d'appliquer de nouvelles options de rendu tout en
1606 accedant aux proprietes de rendu en cours.
1607 @param new_properties Nouvelles options de rendu a appliquer
1608 @return les options de rendu avant l'application de new_properties
1609 */
applyProperties(const ExportProperties & new_properties)1610 ExportProperties Diagram::applyProperties(const ExportProperties &new_properties) {
1611 // exporte les options de rendu en cours
1612 ExportProperties old_properties;
1613 old_properties.draw_grid = displayGrid();
1614 old_properties.draw_border = border_and_titleblock.borderIsDisplayed();
1615 old_properties.draw_titleblock = border_and_titleblock.titleBlockIsDisplayed();
1616 old_properties.draw_terminals = drawTerminals();
1617 old_properties.draw_colored_conductors = drawColoredConductors();
1618 old_properties.exported_area = useBorder() ? QET::BorderArea : QET::ElementsArea;
1619
1620 // applique les nouvelles options de rendu
1621 setUseBorder (new_properties.exported_area == QET::BorderArea);
1622 setDrawTerminals (new_properties.draw_terminals);
1623 setDrawColoredConductors (new_properties.draw_colored_conductors);
1624 setDisplayGrid (new_properties.draw_grid);
1625 border_and_titleblock.displayBorder(new_properties.draw_border);
1626 border_and_titleblock.displayTitleBlock (new_properties.draw_titleblock);
1627
1628 // retourne les anciennes options de rendu
1629 return(old_properties);
1630 }
1631
1632 /**
1633 @param pos Position cartesienne (ex : 10.3, 45.2) a transformer en position
1634 dans la grille (ex : B2)
1635 @return la position dans la grille correspondant a pos
1636 */
convertPosition(const QPointF & pos)1637 DiagramPosition Diagram::convertPosition(const QPointF &pos) {
1638 // delegue le calcul au BorderTitleBlock
1639 DiagramPosition diagram_position = border_and_titleblock.convertPosition(pos);
1640
1641 // embarque la position cartesienne
1642 diagram_position.setPosition(pos);
1643
1644 return(diagram_position);
1645 }
1646
1647 /**
1648 * @brief Diagram::snapToGrid
1649 * Return a nearest snap point of p
1650 * @param p point to find the nearest snaped point
1651 * @return
1652 */
snapToGrid(const QPointF & p)1653 QPointF Diagram::snapToGrid(const QPointF &p)
1654 {
1655 //Return a point rounded to the nearest pixel
1656 if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
1657 {
1658 int p_x = qRound(p.x());
1659 int p_y = qRound(p.y());
1660 return (QPointF(p_x, p_y));
1661 }
1662
1663 //Return a point snapped to the grid
1664 int p_x = qRound(p.x() / Diagram::xGrid) * Diagram::xGrid;
1665 int p_y = qRound(p.y() / Diagram::yGrid) * Diagram::yGrid;
1666 return (QPointF(p_x, p_y));
1667 }
1668
1669
1670
1671 /**
1672 Definit s'il faut afficher ou non les bornes
1673 @param dt true pour afficher les bornes, false sinon
1674 */
setDrawTerminals(bool dt)1675 void Diagram::setDrawTerminals(bool dt) {
1676 foreach(QGraphicsItem *qgi, items()) {
1677 if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
1678 t -> setVisible(dt);
1679 }
1680 }
1681 }
1682
1683 /**
1684 Definit s'il faut respecter ou non les couleurs des conducteurs.
1685 Si non, les conducteurs sont tous dessines en noir.
1686 @param dcc true pour respecter les couleurs, false sinon
1687 */
setDrawColoredConductors(bool dcc)1688 void Diagram::setDrawColoredConductors(bool dcc) {
1689 draw_colored_conductors_ = dcc;
1690 }
1691
1692 /**
1693 @return la liste des conducteurs selectionnes sur le schema
1694 */
selectedConductors() const1695 QSet<Conductor *> Diagram::selectedConductors() const {
1696 QSet<Conductor *> conductors_set;
1697 foreach(QGraphicsItem *qgi, selectedItems()) {
1698 if (Conductor *c = qgraphicsitem_cast<Conductor *>(qgi)) {
1699 conductors_set << c;
1700 }
1701 }
1702 return(conductors_set);
1703 }
1704
1705 /// @return true si le presse-papier semble contenir un schema
clipboardMayContainDiagram()1706 bool Diagram::clipboardMayContainDiagram() {
1707 QString clipboard_text = QApplication::clipboard() -> text().trimmed();
1708 bool may_be_diagram = clipboard_text.startsWith("<diagram") && clipboard_text.endsWith("</diagram>");
1709 return(may_be_diagram);
1710 }
1711
1712 /**
1713 @return le projet auquel ce schema appartient ou 0 s'il s'agit d'un schema
1714 independant.
1715 */
project() const1716 QETProject *Diagram::project() const {
1717 return(m_project);
1718 }
1719
1720 /**
1721 * @brief Diagram::setProject
1722 * Set parent project of this diagram, project also become the parent QObject of this diagram
1723 * @param project new project
1724 */
setProject(QETProject * project)1725 void Diagram::setProject(QETProject *project)
1726 {
1727 if (m_project == project)
1728 return;
1729
1730 m_project = project;
1731 setParent (project);
1732 }
1733
1734 /**
1735 @return the folio number of this diagram within its parent project, or -1
1736 if it is has no parent project
1737 */
folioIndex() const1738 int Diagram::folioIndex() const {
1739 if (!m_project) return(-1);
1740 return(m_project -> folioIndex(this));
1741 }
1742
1743 /**
1744 @param fallback_to_project When a diagram does not have a declared version,
1745 this method will use the one declared by its parent project only if
1746 fallback_to_project is true.
1747 @return the declared QElectroTech version of this diagram
1748 */
declaredQElectroTechVersion(bool fallback_to_project) const1749 qreal Diagram::declaredQElectroTechVersion(bool fallback_to_project) const {
1750 if (diagram_qet_version_ != -1) {
1751 return diagram_qet_version_;
1752 }
1753 if (fallback_to_project && m_project) {
1754 return(m_project -> declaredQElectroTechVersion());
1755 }
1756 return(-1);
1757 }
1758
1759 /**
1760 * @brief Diagram::isReadOnly
1761 * @return true if this diagram is read only.
1762 * This method is same has call Diagram::project() -> isReadOnly()
1763 */
isReadOnly() const1764 bool Diagram::isReadOnly() const
1765 {
1766 return m_project -> isReadOnly();
1767 }
1768
1769 /**
1770 @return Le contenu du schema. Les conducteurs sont tous places dans
1771 conductorsToMove.
1772 */
content() const1773 DiagramContent Diagram::content() const {
1774 DiagramContent dc;
1775 foreach(QGraphicsItem *qgi, items()) {
1776 if (Element *e = qgraphicsitem_cast<Element *>(qgi)) {
1777 dc.m_elements << e;
1778 } else if (IndependentTextItem *iti = qgraphicsitem_cast<IndependentTextItem *>(qgi)) {
1779 dc.m_text_fields << iti;
1780 } else if (Conductor *c = qgraphicsitem_cast<Conductor *>(qgi)) {
1781 dc.m_conductors_to_move << c;
1782 }
1783 }
1784 return(dc);
1785 }
1786
1787 /**
1788 * @brief Diagram::canRotateSelection
1789 * @return True if a least one of selected items can be rotated
1790 */
canRotateSelection() const1791 bool Diagram::canRotateSelection() const
1792 {
1793 for (QGraphicsItem *qgi : selectedItems())
1794 {
1795 if (qgi->type() == IndependentTextItem::Type ||
1796 qgi->type() == ConductorTextItem::Type ||
1797 qgi->type() == DiagramImageItem::Type ||
1798 qgi->type() == Element::Type ||
1799 qgi->type() == DynamicElementTextItem::Type)
1800 return true;
1801
1802 if(qgi->type() == QGraphicsItemGroup::Type)
1803 if(dynamic_cast<ElementTextItemGroup *>(qgi))
1804 return true;
1805 }
1806
1807 return false;
1808 }
1809