Files
qelectrotech-source-mirror/sources/diagram.cpp
T
Jeff Patterson b8c9e670c4 Keep scene updates flowing on the dark canvas and soften its grid
On a dark palette the folio view paints through QGraphicsView::render()
instead of onto its viewport. In that case QGraphicsView never clears
the scene's "update everything" flag, and while the flag is set every
further QGraphicsScene::update() and item update is dropped: from the
second Diagram::update() on, the grid toggle, the white/gray toggle and
even a selection waited for an unrelated repaint. With a receiver on
QGraphicsScene::changed() the scene clears the flag before it emits, so
DiagramView now connects an empty receiver in its constructor.

While the view paints for inversion, Diagram draws the grid dots a
third of the way from the sheet color to black, so they come out as a
soft gray on the dark sheet instead of as bright as the ink. Printing
and export never take that path.

Test in tst_qetpalette: sceneUpdatesReachARenderedView.
2026-09-19 18:26:45 -05:00

2999 lines
88 KiB
C++

/*
Copyright 2006-2026 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 <http://www.gnu.org/licenses/>.
*/
#include "diagram.h"
#include "ElementsCollection/elementcollectionhandler.h"
#include "TerminalStrip/GraphicsItem/terminalstripitem.h"
#include "xml/terminalstripitemxml.h"
#include "QPropertyUndoCommand/qpropertyundocommand.h"
#include "diagramcontent.h"
#include "diagramevent/diagrameventinterface.h"
#include "diagramposition.h"
#include "factory/elementfactory.h"
#include "qetapp.h"
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/dynamicelementtextitem.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/elementtextitemgroup.h"
#include "qetgraphicsitem/independenttextitem.h"
#include "qetgraphicsitem/qetshapeitem.h"
#include "qetgraphicsitem/terminal.h"
#include "qetxml.h"
#include "undocommand/addelementtextcommand.h"
#include "qetinformation.h"
#include "qetproject.h"
#include "diagramsortkeys.h"
#include <algorithm>
#include <cassert>
#include <math.h>
namespace {
using DiagramSortKeys::positionKey;
/// Sort key for Diagram::toXml()'s <elements> block: the element's own
/// diagram-local position, exactly what it's already saved as (x/y),
/// never invented or regenerated. uuid() is deliberately NOT used here:
/// for an element with no persisted uuid attribute, fromXml() invents a
/// fresh random one on every load, so sorting by uuid would still be
/// non-deterministic across process runs for any legacy file.
QString elementSortKey(Element *elmt)
{
//Position alone is not a total order: two elements can sit at the
//same x/y (lmdg.qet has a pair of text elements both at 780,350).
//With equal keys std::stable_sort falls back to the order the
//scene handed us, which varies per run, so those two swapped
//places on every save. The uuid breaks the tie.
//
//For an element with a persisted uuid attribute this is fully
//deterministic. For a legacy element without one, fromXml()
//invents a fresh uuid per load, so a collision between two such
//elements is no better ordered than before -- but no worse
//either, and the tiebreaker is only consulted when the positions
//are equal.
return positionKey(elmt->pos())
+ QLatin1Char(':') + elmt->uuid().toString();
}
/// Sort key for a terminal: its parent element's position, then the
/// terminal's own position local to that element (from the .elmt
/// definition, fixed regardless of where the element is placed).
QString terminalSortKey(Terminal *terminal)
{
if (!terminal)
return QString();
Element *parent = terminal->parentElement();
return (parent ? positionKey(parent->pos()) : QStringLiteral("?"))
+ QLatin1Char(':') + positionKey(terminal->pos());
}
/// Sort key for Diagram::toXml()'s <conductors> block. Built from both
/// endpoints' terminalSortKey(), not Conductor::uuid(): in every example
/// project checked, conductors have no persisted uuid attribute at all,
/// so uuid() is a freshly-minted random value on every load -- exactly
/// as unusable for cross-run determinism as the element case above, just
/// with no persisted fallback to reach for instead. Canonicalised
/// (smaller key first) since a conductor's two ends are unordered for
/// this purpose.
QString conductorSortKey(Conductor *cond)
{
QString a = terminalSortKey(cond->terminal1);
QString b = terminalSortKey(cond->terminal2);
return (a <= b) ? (a + QLatin1Char('>') + b) : (b + QLatin1Char('>') + a);
}
}
int Diagram::xGrid = 10;
int Diagram::yGrid = 10;
int Diagram::xKeyGrid = 10;
int Diagram::yKeyGrid = 10;
int Diagram::xKeyGridFine = 1;
int Diagram::yKeyGridFine = 1;
const qreal Diagram::margin = 5.0;
/**
@brief Diagram::background_color
static variable to keep track of present background color of the diagram.
*/
QColor Diagram::background_color = Qt::white;
/**
@brief Diagram::Diagram
Constructor
@param project : The project of this diagram and also parent QObject
*/
Diagram::Diagram(QETProject *project) :
QGraphicsScene (project),
m_project (project),
use_border_ (true),
draw_terminals_ (true),
draw_terminal_names_ (true),
draw_colored_conductors_ (true),
m_event_interface (nullptr),
m_freeze_new_elements (false),
m_freeze_new_conductors_ (false)
{
QSettings settings;
draw_grid_ = settings.value(QStringLiteral("diagrameditor/grid_display_startup"), true).toBool();
draw_guides_ = settings.value(QStringLiteral("diagrameditor/guides_display_startup"), false).toBool();
setItemIndexMethod(QGraphicsScene::NoIndex);
/* Set to no index,
* because they can be the source of the crash with conductor and shape ghost.
* https://forum.qt.io/topic/71316/qgraphicsscenefinditembsptreevisitor-visit-crashes-due-to-an-obsolete-paintevent-after-qgraphicsscene-removeitem
* https://stackoverflow.com/questions/38458830/crash-after-qgraphicssceneremoveitem-with-custom-item-class
* http://www.qtcentre.org/archive/index.php/t-33730.html
* http://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063
*/
qgi_manager_ = new QGIManager(this);
setBackgroundBrush(Qt::white);
conductor_setter_ = new QGraphicsLineItem(nullptr);
conductor_setter_ -> setZValue(1000000);
QPen pen(Qt::NoBrush, 1.5, Qt::DashLine);
pen.setColor(Qt::black);
conductor_setter_ -> setPen(pen);
connect(&border_and_titleblock, &BorderTitleBlock::informationChanged, this, [this]() {
for (auto conductor : content().conductors()) {
conductor->refreshText();
}
emit diagramInformationChanged();
});
connect(m_project, &QETProject::projectInformationsChanged, this, [this]() {
for (auto conductor : content().conductors()) {
conductor->refreshText();
}
});
connect(&border_and_titleblock,
&BorderTitleBlock::needTitleBlockTemplate,
this, &Diagram::setTitleBlockTemplate);
connect(&border_and_titleblock,
&BorderTitleBlock::informationChanged,
this, &Diagram::titleChanged);
connect(&border_and_titleblock,
&BorderTitleBlock::titleBlockFolioChanged,
this, &Diagram::titleChanged);
connect(&border_and_titleblock,
&BorderTitleBlock::borderChanged,
this, &Diagram::adjustSceneRect);
connect(this, &Diagram::diagramActivated,
this, &Diagram::loadElmtFolioSeq);
connect(this, &Diagram::diagramActivated,
this, &Diagram::loadCndFolioSeq);
adjustSceneRect();
m_guides_list.clear();
if (m_project) {
for (const auto &pg : m_project->defaultGuides()) {
Diagram::Guide g;
g.orientation = static_cast<Diagram::Guide::Orientation>(pg.orientation);
g.position = pg.position;
g.color = pg.color;
m_guides_list.append(g);
}
} else {
QSettings settings;
int size = settings.beginReadArray(QStringLiteral("diagrameditor/defaultguides"));
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
Diagram::Guide g;
g.orientation = static_cast<Diagram::Guide::Orientation>(settings.value(QStringLiteral("orientation"), 0).toInt());
g.position = settings.value(QStringLiteral("position"), 0.0).toReal();
g.color = QColor(settings.value(QStringLiteral("color"), QStringLiteral("#ff0000")).toString());
m_guides_list.append(g);
}
settings.endArray();
}
}
/**
@brief Diagram::~Diagram
Destructor
*/
Diagram::~Diagram()
{
//First clear every selection to close an hypothetical editor
clearSelection();
// clear undo stack to prevent errors,
// because contains pointers to this diagram and is elements.
undoStack().clear();
//delete of QGIManager, every elements he knows are removed
delete qgi_manager_;
// remove of conductor setter
delete conductor_setter_;
if (m_event_interface)
delete m_event_interface;
// list removable items
QVector<QGraphicsItem *> deletable_items;
for(const auto &qgi : items())
{
if (qgi->parentItem())
continue;
if (qgraphicsitem_cast<Conductor *>(qgi))
continue;
deletable_items.append(qgi);
}
for (const auto &item : std::as_const(deletable_items))
{
removeItem(item);
delete item;
}
}
/**
@brief Diagram::drawBackground
Draw the background of the diagram, ie the grid.
\~French Dessine l'arriere-plan du schema, cad la grille.
\~ @param p :
The QPainter to use for drawing
\~French Le QPainter a utiliser pour dessiner
\~ @param r :
The rectangle of the area to be drawn
\~French Le rectangle de la zone a dessiner
*/
void Diagram::drawBackground(QPainter *p, const QRectF &r) {
p -> save();
// disable all antialiasing, except for text
// desactive tout antialiasing, sauf pour le texte
p -> setRenderHint(QPainter::Antialiasing, false);
p -> setRenderHint(QPainter::TextAntialiasing, true);
p -> setRenderHint(QPainter::SmoothPixmapTransform, false);
// draw a white background
// dessine un fond blanc
p -> setPen(Qt::NoPen);
//set brush color to present background color.
p -> setBrush(Diagram::background_color);
p -> drawRect(r);
QSettings settings;
QRectF rect = settings.value(
QStringLiteral("diagrameditor/zoom-out-beyond-of-folio"),
false).toBool() ? r
: border_and_titleblock
.insideBorderRect()
.intersected(r);
if (draw_grid_) {
/* Draw the points of the grid
* if background color is black,
* then grid spots shall be white,
* else they shall be black in color.
* A view that shows the sheet with its lightness inverted
* would turn black dots as bright as the ink, so it gets
* dots a third of the way from the sheet color to black,
* which come out as a soft gray.
*/
QPen pen;
if (Diagram::background_color == Qt::black)
pen.setColor(Qt::white);
else if (m_inverted_lightness)
pen.setColor(QColor(Diagram::background_color.red() * 2 / 3,
Diagram::background_color.green() * 2 / 3,
Diagram::background_color.blue() * 2 / 3));
else
pen.setColor(Qt::black);
pen.setCosmetic(true);
p->setPen(pen);
p -> setBrush(Qt::NoBrush);
int xGrid = settings.value(QStringLiteral("diagrameditor/Xgrid"),
Diagram::xGrid).toInt();
int yGrid = settings.value(QStringLiteral("diagrameditor/Ygrid"),
Diagram::yGrid).toInt();
qreal limit_x = rect.x() + rect.width();
qreal limit_y = rect.y() + rect.height();
int g_x = (int)ceil(rect.x());
while (g_x % xGrid) ++ g_x;
int g_y = (int)ceil(rect.y());
while (g_y % yGrid) ++ g_y;
QPolygon points;
for (int gx = g_x ; gx < limit_x ; gx += xGrid) {
for (int gy = g_y ; gy < limit_y ; gy += yGrid) {
points << QPoint(gx, gy);
}
}
qreal zoom_factor = p->transform().m11();
int minWidthPen = settings.value(QStringLiteral("diagrameditor/grid_pointsize_min"), 1).toInt();
int maxWidthPen = settings.value(QStringLiteral("diagrameditor/grid_pointsize_max"), 1).toInt();
pen.setWidth(minWidthPen);
if (minWidthPen != maxWidthPen) {
qreal stepPen = (maxWidthPen - minWidthPen) / (qreal)maxWidthPen;
qreal stepZoom = (5.0 - 1.0) / maxWidthPen;
for (int n=0; n<maxWidthPen; n++) {
if ((zoom_factor > (1.0 + n * stepZoom)) && (zoom_factor <= (1.0 + (n+1) * stepZoom))) {
int widthPen = minWidthPen + qRound(n * stepPen);
pen.setWidth(widthPen);
}
}
if (zoom_factor <= 1.0)
pen.setWidth(minWidthPen);
else if (zoom_factor > (1.0 + stepZoom * maxWidthPen))
pen.setWidth(maxWidthPen);
}
p -> setPen(pen);
if (zoom_factor > 0.5) // no grid below ... !
p -> drawPoints(points);
}
if (draw_guides_) {
for (const Diagram::Guide &guide : m_guides_list) {
QPen guidePen(guide.color, 1, Qt::DashLine);
guidePen.setCosmetic(true);
p->setPen(guidePen);
if (guide.orientation == Diagram::Guide::Vertical) {
p->drawLine(guide.position, rect.top(), guide.position, rect.bottom());
} else {
p->drawLine(rect.left(), guide.position, rect.right(), guide.position);
}
}
}
if (use_border_) {
border_and_titleblock.draw(p);
}
p -> restore();
}
/**
@brief Diagram::mouseDoubleClickEvent
This event is managed by diagram event interface if any.
@param event :
*/
void Diagram::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
event->setAccepted(false);
if (m_event_interface) {
m_event_interface->mouseDoubleClickEvent(event);
if (event->isAccepted()) {
return;
}
}
QGraphicsScene::mouseDoubleClickEvent(event);
}
/**
@brief Diagram::mousePressEvent
This event is managed by diagram event interface if any.
@param event
*/
void Diagram::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
event->setAccepted(false);
if (m_event_interface) {
m_event_interface->mousePressEvent(event);
if (event->isAccepted()) {
return;
}
}
QGraphicsScene::mousePressEvent(event);
}
/**
@brief Diagram::mouseMoveEvent
This event is managed by diagram event interface if any.
@param event
*/
void Diagram::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->setAccepted(false);
if (m_event_interface) {
m_event_interface->mouseMoveEvent(event);
if (event->isAccepted()) {
return;
}
}
QGraphicsScene::mouseMoveEvent(event);
}
/**
@brief Diagram::mouseReleaseEvent
This event is managed by diagram event interface if any.
@param event
*/
void Diagram::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
event->setAccepted(false);
if (m_event_interface) {
m_event_interface->mouseReleaseEvent(event);
if (event->isAccepted()) {
return;
}
}
QGraphicsScene::mouseReleaseEvent(event);
}
/**
@brief Diagram::wheelEvent
This event is managed by diagram event interface if any.
@param event
*/
void Diagram::wheelEvent(QGraphicsSceneWheelEvent *event)
{
event->setAccepted(false);
if (m_event_interface) {
m_event_interface->wheelEvent(event);
if (event->isAccepted()) {
return;
}
}
QGraphicsScene::wheelEvent(event);
}
/**
@brief Diagram::event
QGraphicsScene has its own Tab/Shift+Tab item-focus-chain traversal
(mirroring QWidget's), checked before keyPressEvent() is ever reached:
by default it would silently consume Tab/Backtab to move focus among
the scene's own focusable items. Intercepting the key press here,
ahead of that, is the only way to reliably override it: unlike
QWidget::focusNextPrevChild(), QGraphicsScene::focusNextPrevChild() is
only virtual starting in Qt 6 (guarded by the QT6_VIRTUAL macro), so a
Diagram:: override of it would silently do nothing on a Qt 5 build.
@param event
*/
bool Diagram::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
auto *key_event = static_cast<QKeyEvent *>(event);
if ((key_event->key() == Qt::Key_Tab || key_event->key() == Qt::Key_Backtab)
&& !isReadOnly() && !focusItem()) {
bool forward = key_event->key() == Qt::Key_Tab
&& !(key_event->modifiers() & Qt::ShiftModifier);
selectNextItem(forward);
event->accept();
return true;
}
}
return QGraphicsScene::event(event);
}
/**
@brief Diagram::keyPressEvent
This event is managed by diagram event interface if any.
Else move selected elements
@param event
*/
void Diagram::keyPressEvent(QKeyEvent *event)
{
event->setAccepted(false);
if (m_event_interface) {
m_event_interface->keyPressEvent(event);
if (event->isAccepted()) {
return;
}
}
if (isReadOnly()) return;
QPointF movement;
qreal top_position = 0;
qreal left_position = 0;
DiagramContent dc(this);
if (dc.items(DiagramContent::All).isEmpty())
{
event->ignore();
QGraphicsScene::keyPressEvent(event);
return;
}
#if TODO_LIST
#pragma message("@TODO move code to new function")
#endif
//Move item with the keyboard arrow
#ifdef Q_OS_MACOS
if(event->modifiers() == Qt::KeypadModifier)
#else
if(event->modifiers() == Qt::NoModifier)
#endif
{
QSettings settings;
int xKeyGrid = settings.value(QStringLiteral("diagrameditor/key_Xgrid"),
Diagram::xKeyGrid).toInt();
int yKeyGrid = settings.value(QStringLiteral("diagrameditor/key_Ygrid"),
Diagram::yKeyGrid).toInt();
switch(event->key())
{
case Qt::Key_Left:
for (Element *item : dc.m_elements)
{
left_position = item->sceneBoundingRect().x();
if(left_position <= 5)
return;
}
movement = QPointF(-xKeyGrid, 0.0);
break;
case Qt::Key_Right:
movement = QPointF(+xKeyGrid, 0.0);
break;
case Qt::Key_Up:
for(Element *item : dc.m_elements)
{
top_position = item->sceneBoundingRect().y();
if(top_position <= 5)
return;
}
movement = QPointF(0.0, -yKeyGrid);
break;
case Qt::Key_Down:
movement = QPointF(0.0, +yKeyGrid);
break;
}
if (!movement.isNull() && !focusItem())
{
m_elements_mover.beginMovement(this);
m_elements_mover.continueMovement(movement);
event->accept();
return;
}
}
else if(event->modifiers() == Qt::AltModifier)
{
QSettings settings;
int xKeyGridFine = settings.value(QStringLiteral("diagrameditor/key_fine_Xgrid"),
Diagram::xKeyGridFine).toInt();
int yKeyGridFine = settings.value(QStringLiteral("diagrameditor/key_fine_Ygrid"),
Diagram::yKeyGridFine).toInt();
switch(event->key())
{
case Qt::Key_Left:
for (Element *item : dc.m_elements)
{
left_position = item->sceneBoundingRect().x();
if(left_position <= 5)
return;
}
movement = QPointF(-xKeyGridFine, 0.0);
break;
case Qt::Key_Right:
movement = QPointF(+xKeyGridFine, 0.0);
break;
case Qt::Key_Up:
for(Element *item : dc.m_elements)
{
top_position = item->sceneBoundingRect().y();
if(top_position <= 5)
return;
}
movement = QPointF(0.0, -yKeyGridFine);
break;
case Qt::Key_Down:
movement = QPointF(0.0, +yKeyGridFine);
break;
}
if (!movement.isNull() && !focusItem())
{
m_elements_mover.beginMovement(this);
m_elements_mover.continueMovement(movement);
event->accept();
return;
}
}
else if(event->modifiers() == Qt::ControlModifier)
{
//Adjust the alignment of a texts group
if(selectedItems().size() == 1
&& selectedItems().first()->type()
== QGraphicsItemGroup::Type)
{
if(ElementTextItemGroup *etig =
dynamic_cast<ElementTextItemGroup *>
(selectedItems().first()))
{
if(event->key() == Qt::Key_Left
&& etig->alignment() != Qt::AlignLeft)
undoStack().push(
new AlignmentTextsGroupCommand(
etig,
Qt::AlignLeft));
else if (event->key() == Qt::Key_Up
&& etig->alignment() != Qt::AlignVCenter)
undoStack().push(
new AlignmentTextsGroupCommand(
etig,
Qt::AlignVCenter));
else if (event->key() == Qt::Key_Right
&& etig->alignment() != Qt::AlignRight)
undoStack().push(
new AlignmentTextsGroupCommand(
etig,
Qt::AlignRight));
}
}
}
event->ignore();
QGraphicsScene::keyPressEvent(event);
}
/**
@brief Diagram::keyReleaseEvent
This event is managed by diagram event interface if any.
Else move selected element
@param e
*/
void Diagram::keyReleaseEvent(QKeyEvent *e)
{
e->setAccepted(false);
if (m_event_interface) {
m_event_interface->keyReleaseEvent(e);
if (e->isAccepted()) {
return;
}
}
bool transmit_event = true;
if (!isReadOnly()) {
/* detects the release of a direction key
* (= movement of elements)
* detecte le relachement d'une touche de direction
* ( = deplacement d'elements)
*/
if (
(
e -> key() == Qt::Key_Left
|| e -> key() == Qt::Key_Right
|| e -> key() == Qt::Key_Up
|| e -> key() == Qt::Key_Down
) && !e -> isAutoRepeat()
) {
m_elements_mover.endMovement();
e -> accept();
transmit_event = false;
}
}
if (transmit_event) {
QGraphicsScene::keyReleaseEvent(e);
}
}
/**
@brief Diagram::uuid
@return the uuid of this diagram
*/
QUuid Diagram::uuid()
{
return m_uuid;
}
/**
@brief Diagram::uuidUsedByOtherDiagram
A hand-edited or merged project file can contain two folios with the same
uuid. The uuid is used as a key (e.g. in the project database), so the
second one must get another one.
@param uuid
@return true if another diagram of the parent project already uses @p uuid
*/
bool Diagram::uuidUsedByOtherDiagram(const QUuid &uuid) const
{
if (!m_project) {
return false;
}
const auto diagrams = m_project->diagrams();
for (const Diagram *diagram : diagrams) {
if (diagram != this && diagram->m_uuid == uuid) {
return true;
}
}
return false;
}
/**
@brief Diagram::derivedUuid
Name-based (version 5) uuid for a folio that has no usable uuid in the
file it is loaded from : either the file predates the uuid attribute, or
the uuid it carries is already taken by another folio of the project.
A random uuid would do as an identity, but it would make saving an
unmodified legacy project non-reproducible : every load would invent a
different one and write it out (see #754). The name is therefore built
only from data read from the file itself -- the project title, the
position of the folio in the file and its title -- so the same input file
always yields the same uuid. It is computed once, when the folio is
loaded, and saved from then on : renaming or moving the folio later does
not change it.
@param root : the <diagram> element being loaded
@param reason : distinguishes the two cases above, so that they can not
produce the same name
@return a uuid not used by any other diagram of the project
*/
QUuid Diagram::derivedUuid(const QDomElement &root, const QString &reason) const
{
//Fixed namespace for QElectroTech folio uuids, never change it :
//doing so would change the uuid given to every legacy folio.
static const QUuid folio_namespace(
QStringLiteral("{d5951240-154d-44d6-8277-0092a31d1920}"));
const int index = m_project
? m_project->diagrams().indexOf(const_cast<Diagram *>(this))
: -1;
const QString project_title = root.ownerDocument()
.documentElement()
.attribute(QStringLiteral("title"));
const QString base = QStringLiteral("qet-folio\n%1\n%2\n%3\n%4")
.arg(reason,
project_title,
QString::number(index),
root.attribute(QStringLiteral("title")));
//A clash is only possible with a hand-crafted file, but the uuid is a
//key : salt the name until it is free. Still deterministic.
QUuid uuid = QUuid::createUuidV5(folio_namespace, base);
for (int salt = 1 ; uuidUsedByOtherDiagram(uuid) ; ++salt) {
uuid = QUuid::createUuidV5(folio_namespace,
base + QStringLiteral("\n")
+ QString::number(salt));
}
return uuid;
}
/**
@brief Diagram::setEventInterface
Set event_interface has current interface.
Diagram become the ownership of event_interface
If there is a previous interface, they will be delete before
and call init() to the new interface.
@param event_interface
*/
void Diagram::setEventInterface(DiagramEventInterface *event_interface)
{
if (m_event_interface)
{
delete m_event_interface;
event_interface -> init();
}
m_event_interface = event_interface;
connect(m_event_interface, &DiagramEventInterface::finish, [this]()
{
delete this->m_event_interface;
this->m_event_interface = nullptr;
});
}
/**
@brief Diagram::clearEventInterface
Clear the current event interface.
*/
void Diagram::clearEventInterface()
{
if(m_event_interface)
{
delete m_event_interface;
m_event_interface = nullptr;
}
}
bool Diagram::eventInterfaceIsRunning() const
{
return m_event_interface && m_event_interface->isRunning();
}
/**
@brief Diagram::conductorsAutonumName
@return the name of autonum to use.
*/
QString Diagram::conductorsAutonumName() const
{
return m_conductors_autonum_name;
}
/**
@brief Diagram::setConductorsAutonumName
@param name : name of autonum to use.
*/
void Diagram::setConductorsAutonumName(const QString &name) {
m_conductors_autonum_name= name;
}
/**
Exporte le schema vers une image
@return Une QImage representant le schema
*/
bool Diagram::toPaintDevice(QPaintDevice &pix,
int width,
int height,
Qt::AspectRatioMode aspectRatioMode) {
// determine the source area = schema content + margins
// determine la zone source = contenu du schema + marges
QRectF source_area;
if (!use_border_) {
source_area = itemsBoundingRect();
source_area.translate(-margin, -margin);
source_area.setWidth (source_area.width () + 2.0 * margin);
source_area.setHeight(source_area.height() + 2.0 * margin);
} else {
source_area = QRectF(
0.0,
0.0,
border_and_titleblock.borderAndTitleBlockRect().width()
+ 2.0 * margin,
border_and_titleblock.borderAndTitleBlockRect().height()
+ 2.0 * margin
);
}
/* if the dimensions are not specified,
* the image is exported at 1: 1 scale
* si les dimensions ne sont pas precisees,
* l'image est exportee a l'echelle 1:1
*/
QSize image_size = (width == -1 && height == -1)
? source_area.size().toSize() : QSize(width, height);
// prepare the rendering
// prepare le rendu
QPainter p;
if (!p.begin(&pix)) return(false);
// anti-aliasis rendering
// rendu antialiase
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::TextAntialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
// deselect all elements
// deselectionne tous les elements
QList<QGraphicsItem *> selected_elmts = selectedItems();
foreach (QGraphicsItem *qgi, selected_elmts) qgi -> setSelected(false);
// renders itself
// effectue le rendu lui-meme
render(&p,
QRect(QPoint(0, 0),image_size),
source_area,
aspectRatioMode);
p.end();
// restore selected items
// restaure les elements selectionnes
foreach (QGraphicsItem *qgi, selected_elmts) qgi -> setSelected(true);
return(true);
}
/**
@brief Diagram::imageSize
Allows you to know the dimensions
that the image generated by the toImage() method will have
\~French Permet de connaitre les dimensions
qu'aura l'image generee par la methode toImage()
\~ @return The size of the image generated by toImage()
\~French La taille de l'image generee par toImage()
*/
QSize Diagram::imageSize() const
{
// determine la zone source = contenu du schema + marges
qreal image_width, image_height;
if (!use_border_) {
QRectF items_rect = itemsBoundingRect();
image_width = items_rect.width();
image_height = items_rect.height();
} else {
image_width = border_and_titleblock
.borderAndTitleBlockRect().width();
image_height = border_and_titleblock
.borderAndTitleBlockRect().height();
}
image_width += 2.0 * margin;
image_height += 2.0 * margin;
// returns the size of the source area
// renvoie la taille de la zone source
return(QSizeF(image_width, image_height).toSize());
}
/**
@brief Diagram::isEmpty
An empty schema contains no element, conductor, or text field
@return true if the schema is considered empty, false otherwise.
\~French true si le schema est considere comme vide, false sinon.
Un schema vide ne contient ni element, ni conducteur, ni champ de texte
*/
bool Diagram::isEmpty() const
{
return(!items().count());
}
/**
@brief Diagram::potential
@return all potential in the diagram
each potential are in the QList
and each conductors of one potential are in the QSet
*/
QList < QSet <Conductor *> > Diagram::potentials()
{
QList < QSet <Conductor *> > potential_List;
if (content().conductors().size() == 0)
return (potential_List); //return an empty potential
QList <Conductor *> conductors_list = content().conductors();
do {
QSet <Conductor *> one_potential =
conductors_list.first()
-> relatedPotentialConductors();
one_potential << conductors_list.takeFirst();
foreach (Conductor *c, one_potential)
conductors_list.removeOne(c);
potential_List << one_potential;
} while (!conductors_list.empty());
return (potential_List);
}
/**
@brief Diagram::toXml
Exports all or part of the schema
\~French Exporte tout ou partie du schema
\~ @param whole_content :
Boolean (to true by default) indicating if the generated XML must
represent the entire schema or only the selected content
\~French Booleen (a vrai par defaut) indiquant si le XML genere doit
representer l'integralite du schema ou seulement le contenu selectionne
\~ @param is_copy_command:
Boolean (false by default) indicating if function is called by an copy command
\~ @return An XML Document (QDomDocument)
\~French Un Document XML (QDomDocument)
*/
QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
// document
QDomDocument document;
// XML tree root
// racine de l'arbre XML
auto dom_root = document.createElement(QStringLiteral("diagram"));
// schema properties
// proprietes du schema
if (whole_content) {
//Persist the folio identity, so that a folio keeps the same uuid
//across save/load. Without it every load invents a new one, and
//nothing outside the running instance (version control, a lock,
//a diff tool...) can tell which folio is which.
dom_root.setAttribute(QStringLiteral("uuid"), m_uuid.toString());
border_and_titleblock.titleBlockToXml(dom_root);
border_and_titleblock.borderToXml(dom_root);
// Default conductor properties
QDomElement default_conductor =
document.createElement(QStringLiteral("defaultconductor"));
defaultConductorProperties.toXml(default_conductor);
dom_root.appendChild(default_conductor);
// Conductor autonum
if (!m_conductors_autonum_name.isEmpty()) {
dom_root.setAttribute(QStringLiteral("conductorAutonum"),
m_conductors_autonum_name);
}
//Default New Element
dom_root.setAttribute(QStringLiteral("freezeNewElement"),
m_freeze_new_elements ? QStringLiteral("true") : QStringLiteral("false"));
//Default New Conductor
dom_root.setAttribute(QStringLiteral("freezeNewConductor"),
m_freeze_new_conductors_
? QStringLiteral("true") : QStringLiteral("false"));
//Element Folio Sequential Variables
if (!m_elmt_unitfolio_max.isEmpty()
|| !m_elmt_tenfolio_max.isEmpty()
|| !m_elmt_hundredfolio_max.isEmpty()) {
QDomElement elmtfoliosequential =
document.createElement(
QStringLiteral("elementautonumfoliosequentials"));
if (!m_elmt_unitfolio_max.isEmpty()) {
QDomElement elmtfolioseq =
document.createElement(
QStringLiteral("elementunitfolioseq"));
folioSequentialsToXml(&m_elmt_unitfolio_max,
&elmtfolioseq,
QStringLiteral("sequf_"),
QStringLiteral("unitfolioseq"),
&document);
elmtfoliosequential.appendChild(elmtfolioseq);
}
if (!m_elmt_tenfolio_max.isEmpty()) {
QDomElement elmtfolioseq =
document.createElement(
QStringLiteral("elementtenfolioseq"));
folioSequentialsToXml(&m_elmt_tenfolio_max,
&elmtfolioseq,
QStringLiteral("seqtf_"),
QStringLiteral("tenfolioseq"),
&document);
elmtfoliosequential.appendChild(elmtfolioseq);
}
if (!m_elmt_hundredfolio_max.isEmpty()) {
QDomElement elmtfolioseq =
document.createElement(
QStringLiteral("elementhundredfolioseq"));
folioSequentialsToXml(&m_elmt_hundredfolio_max,
&elmtfolioseq,
QStringLiteral("seqhf_"),
QStringLiteral("hundredfolioseq"),
&document);
elmtfoliosequential.appendChild(elmtfolioseq);
}
dom_root.appendChild(elmtfoliosequential);
}
// Conductor Folio Sequential Variables
if (!m_cnd_unitfolio_max.isEmpty()
|| !m_cnd_tenfolio_max.isEmpty() ||
!m_cnd_hundredfolio_max.isEmpty()) {
QDomElement cndfoliosequential =
document.createElement(
QStringLiteral("conductorautonumfoliosequentials"));
QHash<QString, QStringList>::iterator i;
if (!m_cnd_unitfolio_max.isEmpty()) {
QDomElement cndfolioseq =
document.createElement(
QStringLiteral("conductorunitfolioseq"));
folioSequentialsToXml(&m_cnd_unitfolio_max,
&cndfolioseq,
QStringLiteral("sequf_"),
QStringLiteral("unitfolioseq"),
&document);
cndfoliosequential.appendChild(cndfolioseq);
}
if (!m_cnd_tenfolio_max.isEmpty()) {
QDomElement cndfolioseq =
document.createElement(
QStringLiteral("conductortenfolioseq"));
folioSequentialsToXml(&m_cnd_tenfolio_max,
&cndfolioseq,
QStringLiteral("seqtf_"),
QStringLiteral("tenfolioseq"),
&document);
cndfoliosequential.appendChild(cndfolioseq);
}
if (!m_cnd_hundredfolio_max.isEmpty()) {
QDomElement cndfolioseq =
document.createElement(
QStringLiteral("conductorhundredfolioseq"));
folioSequentialsToXml(&m_cnd_hundredfolio_max,
&cndfolioseq,
QStringLiteral("seqhf_"),
QStringLiteral("hundredfolioseq"),
&document);
cndfoliosequential.appendChild(cndfolioseq);
}
dom_root.appendChild(cndfoliosequential);
}
}
else {
//this method with whole_content to false,
//is often use to copy and paste the current selection
//so we add the id of the project where copy occur.
dom_root.setAttribute(QStringLiteral("projectId"), QETApp::projectId(m_project));
}
document.appendChild(dom_root);
if (items().isEmpty())
return(document);
QVector<Element *> list_elements;
QVector<Conductor *> list_conductors;
QVector<DiagramTextItem *> list_texts;
QVector<DiagramImageItem *> list_images;
QVector<QetShapeItem *> list_shapes;
QVector<QetGraphicsTableItem *> table_vector;
QVector<TerminalStripItem *> strip_vector;
//Ckeck graphics item to "XMLise"
for(QGraphicsItem *qgi : items())
{
switch (qgi->type())
{
case Element::Type: {
auto elmt = static_cast<Element *>(qgi);
if (whole_content || elmt->isSelected()){
// For a copy/paste command, the text positions must be recalculated for
// correct text alignment, before it is saved to the clipboard
if(is_copy_command && (elmt->linkType() == Element::Slave || elmt->linkType()&Element::AllReport))
correctTextPos(elmt);
list_elements << elmt;
}
break;
}
case Conductor::Type: {
auto cond = static_cast<Conductor *>(qgi);
if (whole_content)
list_conductors << cond;
/* When we did not export the whole diagram,
* we must remove the non-selected conductors.
* At this step that means a conductor which one
* of these two element are not selected
*/
else if (cond->terminal1->parentItem()->isSelected()
&& cond->terminal2->parentItem()->isSelected())
list_conductors << cond;
break;
}
case DiagramImageItem::Type: {
auto image = static_cast<DiagramImageItem *>(qgi);
if (whole_content || image->isSelected())
list_images << image;
break;
}
case IndependentTextItem::Type: {
auto indi_text = static_cast<IndependentTextItem *>(qgi);
if (whole_content || indi_text->isSelected())
list_texts << indi_text;
break;
}
case QetShapeItem::Type: {
auto shape = static_cast<QetShapeItem *>(qgi);
if (whole_content || shape->isSelected())
list_shapes << shape;
break;
}
case QetGraphicsTableItem::Type: {
auto table = static_cast<QetGraphicsTableItem *>(qgi);
if (whole_content || table->isSelected())
table_vector << table;
break;
}
case TerminalStripItem::Type: {
const auto strip = static_cast<TerminalStripItem *>(qgi);
if (whole_content || strip->isSelected()) {
strip_vector << strip;
}
break;
}
}
}
// items() returns items in stacking order, which is not guaranteed
// reproducible across processes (ties between same-Z items follow
// the scene's internal index, not any content-derived order) -- so
// without this, saving an unmodified project produces a different
// byte stream on every run. Elements and conductors are the two
// blocks observed to actually churn across the example corpus;
// sort them into a deterministic, content-derived order before
// serializing. This also fixes the legacy terminal-id churn below,
// since those ids are assigned sequentially in element order.
std::stable_sort(list_elements.begin(), list_elements.end(),
[](Element *a, Element *b) { return elementSortKey(a) < elementSortKey(b); });
std::stable_sort(list_conductors.begin(), list_conductors.end(),
[](Conductor *a, Conductor *b) { return conductorSortKey(a) < conductorSortKey(b); });
// correspondence table between the addresses of the terminals and their ids
// table de correspondance entre les adresses des bornes et leurs ids
QHash<Terminal *, int> table_adr_id;
if (!list_elements.isEmpty()) {
auto dom_elements = document.createElement(QStringLiteral("elements"));
for (auto elmt : list_elements) {
dom_elements.appendChild(elmt->toXml(document,
table_adr_id));
// If copy is active we have to undo the changes we have made during creating(filling) 'list_elements'
if(is_copy_command && (elmt->linkType() == Element::Slave || elmt->linkType()&Element::AllReport))
restoreText(elmt);
}
dom_root.appendChild(dom_elements);
}
if (!list_conductors.isEmpty()) {
auto dom_conductors = document.createElement(QStringLiteral("conductors"));
for (auto cond : list_conductors) {
dom_conductors.appendChild(cond->toXml(document,
table_adr_id));
}
dom_root.appendChild(dom_conductors);
}
if (!list_texts.isEmpty()) {
auto dom_texts = document.createElement(QStringLiteral("inputs"));
for (auto dti : list_texts) {
dom_texts.appendChild(dti->toXml(document));
}
dom_root.appendChild(dom_texts);
}
if (!list_images.isEmpty()) {
auto dom_images = document.createElement(QStringLiteral("images"));
for (auto dii : list_images) {
dom_images.appendChild(dii->toXml(document));
}
dom_root.appendChild(dom_images);
}
if (!list_shapes.isEmpty()) {
auto dom_shapes = document.createElement(QStringLiteral("shapes"));
for (auto dii : list_shapes) {
dom_shapes.appendChild(dii -> toXml(document));
}
dom_root.appendChild(dom_shapes);
}
if (table_vector.size()) {
auto tables = document.createElement(QStringLiteral("tables"));
for (auto table : table_vector) {
tables.appendChild(table->toXml(document));
}
dom_root.appendChild(tables);
}
if (!strip_vector.isEmpty()) {
dom_root.appendChild(TerminalStripItemXml::toXml(strip_vector, document));
}
return(document);
}
/**
@brief Diagram::folioSequentialsToXml
Add folio sequential to QDomElement
@param hash to retrieve content with content
@param domElement to add attributes
@param seq_type type
@param type
@param doc
*/
void Diagram::folioSequentialsToXml(QHash<QString,
QStringList> *hash,
QDomElement *domElement,
const QString& seq_type,
const QString& type,
QDomDocument *doc)
{
QHash<QString, QStringList>::iterator i;
for (i = hash->begin(); i != hash->end(); i++)
{
QDomElement folioseq = doc->createElement(type);
folioseq.setAttribute(QStringLiteral("title"), i.key());
for (int j = 0; j < i.value().size(); j++) {
folioseq.setAttribute(seq_type + QString::number(j+1),
i.value().at(j));
}
domElement->appendChild(folioseq);
}
}
/**
@brief Diagram::fromXml
Imports the described schema into an XML document.
If a position is specified,
the imported elements are positioned so that the upper left corner
of the smallest rectangle that can surround them all
(the bounding rect) is at this position.
\~French Importe le schema decrit dans un document XML.
Si une position estprecisee,
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 document :
The XML document to analyze
\~French Le document XML a analyser
\~ @param position :
The position of the diagram matters
\~French La position du schema importe
\~ @param consider_informations :
If true, additional information
(author, title, ...) will be taken into account
\~French Si vrai, les informations complementaires
(auteur, titre, ...) seront prises en compte
\~ @param content_ptr :
if this pointer to a DiagramContent is different from 0,
it will be filled with the content added to the schema by the fromXml
\~French si ce pointeur vers un DiagramContent est different de 0,
il sera rempli avec le contenu ajoute au schema par le fromXml
\~ @return true if the import was successful, false otherwise
\~French true si l'import a reussi, false sinon
*/
bool Diagram::fromXml(QDomDocument &document,
QPointF position,
bool consider_informations,
DiagramContent *content_ptr) {
QDomElement root = document.documentElement();
return(fromXml(root, position, consider_informations, content_ptr));
}
/**
@brief Diagram::initFromXml
Imports the described schema in an XML element.
This method delegates its work to Diagram :: fromXml
If the import is successful,
this method also initializes the XML document internal allowing to
manage the recording of this diagram in the project to which it belongs.
@see Diagram::fromXml
\~French Importe le schema decrit dans un element XML.
Cette methode delegue son travail a Diagram::fromXml
Si l'import reussit, cette methode initialise egalement le document XML
interne permettant de bien gerer l'enregistrement de ce schema dans le
projet auquel il appartient.
\~ @param document : The XML document to analyze
\~French Le document XML a analyser
\~ @param position : The position of the diagram matters
\~French La position du schema importe
\~ @param consider_informations :
If true, additional information
(author, title, ...) will be taken into account
\~French Si vrai, les informations complementaires
(auteur, titre, ...) seront prises en compte
\~ @param content_ptr :
if this pointer to a DiagramContent is different from 0,
it will be filled with the content added to the schema by the fromXml
\~French si ce pointeur vers un DiagramContent est different de 0,
il sera rempli avec le contenu ajoute au schema par le fromXml
\~ @return true if the import was successful, false otherwise
\~French true si l'import a reussi, false sinon
*/
bool Diagram::initFromXml(QDomElement &document,
QPointF position,
bool consider_informations,
DiagramContent *content_ptr) {
/* import the content and properties of the schema
* from the XML element provided as a parameter
* import le contenu et les proprietes du schema
* depuis l'element XML fourni en parametre
*/
bool from_xml = fromXml(document,
position,
consider_informations,
content_ptr);
return(from_xml);
}
/**
@brief findTerminal
Find terminal to which the conductor should be connected
@param conductor_index 1 or 2 depending on which terminal is searched
@param f Conductor xml element
@param table_adr_id Hash table to all terminal id assignement (legacy)
@param added_elements Elements found in the xml file
@return
*/
Terminal* findTerminal(int conductor_index,
QDomElement& f,
QHash<int,Terminal *>& table_adr_id,
QList<Element *>& added_elements)
{
assert(conductor_index == 1 || conductor_index == 2);
auto str_index = QString::number(conductor_index);
QString element_index = QStringLiteral("element") + str_index;
QString terminal_index = QStringLiteral("terminal") + str_index;
if (f.hasAttribute(element_index)) {
QUuid element_uuid = QUuid(f.attribute(element_index));
// element1 did not exist in the conductor part of the xml until prior 0.7
// It is used as an indicator that uuid's are used to identify terminals
bool element_found = false;
for (auto element: added_elements) {
if (element->uuid() != element_uuid)
continue;
element_found = true;
QUuid terminal_uuid = QUuid(f.attribute(terminal_index));
for (auto terminal: element->terminals()) {
if (terminal->uuid() != terminal_uuid)
continue;
return terminal;
}
qDebug() << "Diagram::fromXml() : "
<< terminal_index
<< ":"
<< terminal_uuid
<< "not found in "
<< element_index
<< ":"
<< element_uuid;
break;
}
if (!element_found)
qDebug() << "Diagram::fromXml() : "
<< element_index
<< ": "
<< element_uuid
<< "not found";
} else {
// Backward compatibility.
// Until version 0.7 a generated id is used to link the terminal.
int id_p1 = f.attribute(terminal_index).toInt();
if (!table_adr_id.contains(id_p1)) {
qDebug() << "Diagram::fromXml() : terminal id "
<< id_p1
<< " not found";
} else
return table_adr_id.value(id_p1);
}
return nullptr;
}
/**
@brief Diagram::fromXml
Imports the described schema in an XML element. If a position is
specified, the imported elements are positioned in such a way that the
upper left corner of the smallest rectangle that can surround them all
(the bounding rect) either at this position.
\~French Importe le schema decrit dans un element 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 document :
The XML document to analyze
\~French Le document XML a analyser
\~ @param position :
The position of the diagram matters
\~French La position du schema importe
\~ @param consider_informations :
If true, additional information
(author, title, ...) will be taken into account
\~French Si vrai, les informations complementaires
(auteur, titre, ...) seront prises en compte
\~ @param content_ptr :
if this pointer to a DiagramContent is different from 0,
it will be filled with the content added to the schema by the fromXml
\~French si ce pointeur vers un DiagramContent est different de 0,
il sera rempli avec le contenu ajoute au schema par le fromXml
\~ @return
true if the import was successful, false otherwise
\~French true si l'import a reussi, false sinon
*/
bool Diagram::fromXml(QDomElement &document,
QPointF position,
bool consider_informations,
DiagramContent *content_ptr)
{
const QDomElement& root = document;
// The first element must be a diagram
if (root.tagName() != QLatin1String("diagram")) {
return(false);
}
// Read attributes of this diagram
if (consider_informations)
{
// Restore the persisted folio uuid. Done first, before any item is
// loaded, so that everything created below sees the final uuid.
// A folio without a usable one (file written before the uuid was
// persisted, or uuid already taken by another folio) gets a
// deterministic one instead, see derivedUuid().
const QUuid persisted_uuid(root.attribute(QStringLiteral("uuid")));
if (persisted_uuid.isNull()) {
m_uuid = derivedUuid(root, QStringLiteral("legacy"));
} else if (uuidUsedByOtherDiagram(persisted_uuid)) {
m_uuid = derivedUuid(root, QStringLiteral("duplicate ")
+ persisted_uuid.toString());
} else {
m_uuid = persisted_uuid;
}
// Load border and titleblock
border_and_titleblock.titleBlockFromXml(root);
border_and_titleblock.borderFromXml(root);
// Find the element "defaultconductor".
// If found, load default conductor properties.
QDomElement default_conductor_elmt =
root.firstChildElement(QStringLiteral("defaultconductor"));
if (!default_conductor_elmt.isNull()) {
defaultConductorProperties.fromXml(default_conductor_elmt);
}
// Load the autonum
m_conductors_autonum_name = root.attribute(QStringLiteral("conductorAutonum"));
// Load Freeze New Element
m_freeze_new_elements = root.attribute(QStringLiteral("freezeNewElement")).toInt();
// Load Freeze New Conductor
m_freeze_new_conductors_ = root.attribute(QStringLiteral("freezeNewConductor")).toInt();
//Load Element Folio Sequential
folioSequentialsFromXml(root,
&m_elmt_unitfolio_max,
QStringLiteral("elementunitfolioseq"),
QStringLiteral("sequf_"),
QStringLiteral("unitfolioseq"),
QStringLiteral("elementautonumfoliosequentials"));
folioSequentialsFromXml(root, &m_elmt_tenfolio_max,
QStringLiteral("elementtenfolioseq"),
QStringLiteral("seqtf_"),
QStringLiteral("tenfolioseq"),
QStringLiteral("elementautonumfoliosequentials"));
folioSequentialsFromXml(root, &m_elmt_hundredfolio_max,
QStringLiteral("elementhundredfolioseq"),
QStringLiteral("seqhf_"),
QStringLiteral("hundredfolioseq"),
QStringLiteral("elementautonumfoliosequentials"));
//Load Conductor Folio Sequential
folioSequentialsFromXml(root, &m_cnd_unitfolio_max,
QStringLiteral("conductorunitfolioseq"),
QStringLiteral("sequf_"),
QStringLiteral("unitfolioseq"),
QStringLiteral("conductorautonumfoliosequentials"));
folioSequentialsFromXml(root, &m_cnd_tenfolio_max,
QStringLiteral("conductortenfolioseq"),
QStringLiteral("seqtf_"),
QStringLiteral("tenfolioseq"),
QStringLiteral("conductorautonumfoliosequentials"));
folioSequentialsFromXml(root, &m_cnd_hundredfolio_max,
QStringLiteral("conductorhundredfolioseq"),
QStringLiteral("seqhf_"),
QStringLiteral("hundredfolioseq"),
QStringLiteral("conductorautonumfoliosequentials"));
}
// if child haven't got a child, loading is finish (diagram is empty)
if (root.firstChild().isNull()) {
return(true);
}
//If paste from another project
if (root.hasAttribute(QStringLiteral("projectId")))
{
QETProject *other_project = QETApp::project(root.attribute(QStringLiteral("projectId"),
QStringLiteral("-1")).toInt());
/* We try to paste from another project,
* then before paste elements,
* we must import the definition of the pasted elements
* (owned by other project)
* in the embedded collection of this project
*/
if (other_project && other_project != m_project)
{
ElementCollectionHandler ech;
for (auto element_xml :
QET::findInDomElement(root,
QStringLiteral("elements"),
QStringLiteral("element"))) {
if (!Element::valideXml(element_xml)) continue;
QString type_id = element_xml.attribute(QStringLiteral("type"));
if (type_id.startsWith(QStringLiteral("embed://"))) {
ElementsLocation location(
type_id,
other_project);
ech.importFromProject(m_project,
location);
}
}
}
}
//Load all elements from the XML
QList<Element *> added_elements;
QHash<int, Terminal *> table_adr_id;
for (auto element_xml :
QET::findInDomElement(root, QStringLiteral("elements"), QStringLiteral("element")))
{
if (!Element::valideXml(element_xml)) continue;
// cree un element dont le type correspond a l'id type
QString type_id = element_xml.attribute(QStringLiteral("type"));
ElementsLocation element_location;
if (type_id.startsWith(QStringLiteral("embed://"))) {
element_location = ElementsLocation(type_id, m_project);
}
else {
element_location = ElementsLocation(type_id);
}
int state = 0;
Element *nvel_elmt =
ElementFactory::Instance() -> createElement(
element_location, nullptr, &state);
if (state)
{
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);
qDebug() << qPrintable(debug_message);
delete nvel_elmt;
continue;
}
addItem(nvel_elmt);
//Loading fail, remove item from the diagram
if (!nvel_elmt->fromXml(element_xml, table_adr_id))
{
removeItem(nvel_elmt);
delete nvel_elmt;
qDebug() << QStringLiteral("Diagram::fromXml() : Le chargement des parametres d'un element a echoue");
} else {
added_elements << nvel_elmt;
}
}
// Load text
QList<IndependentTextItem *> added_texts;
for (auto text_xml : QET::findInDomElement(root,
QStringLiteral("inputs"),
QStringLiteral("input"))) {
IndependentTextItem *iti = new IndependentTextItem();
iti -> fromXml(text_xml);
addItem(iti);
added_texts << iti;
}
// Load image
QList<DiagramImageItem *> added_images;
for (auto image_xml : QET::findInDomElement(root,
QStringLiteral("images"),
QStringLiteral("image"))) {
DiagramImageItem *dii = new DiagramImageItem ();
dii -> fromXml(image_xml);
addItem(dii);
added_images << dii;
}
// Load shape
QList<QetShapeItem *> added_shapes;
for (auto shape_xml : QET::findInDomElement(root,
QStringLiteral("shapes"),
QStringLiteral("shape"))) {
QetShapeItem *dii = new QetShapeItem (QPointF(0,0));
dii -> fromXml(shape_xml);
addItem(dii);
added_shapes << dii;
}
//Load tables
QVector<QetGraphicsTableItem *> added_tables;
for (const auto &dom_table : QETXML::subChild(root,
QStringLiteral("tables"),
QetGraphicsTableItem::xmlTagName()))
{
auto table = new QetGraphicsTableItem();
addItem(table);
table->fromXml(dom_table);
added_tables << table;
}
//Load terminal strip item
QVector<TerminalStripItem *> added_strips { TerminalStripItemXml::fromXml(this, root) };
//Translate items if a new position was given in parameter
if (position != QPointF())
{
QVector <QGraphicsItem *> added_items;
for (auto element : std::as_const(added_elements )) added_items << element;
for (auto shape : std::as_const(added_shapes )) added_items << shape;
for (auto text : std::as_const(added_texts )) added_items << text;
for (auto image : std::as_const(added_images )) added_items << image;
for (auto table : std::as_const(added_tables )) added_items << table;
for (const auto &strip : std::as_const(added_strips)) added_items << strip;
//Get the top left corner of the rectangle that contain all added items
QRectF items_rect;
for (auto item : added_items) {
items_rect = items_rect.united(
item->mapToScene(
item->boundingRect()
).boundingRect());
}
QPointF point_ = items_rect.topLeft();
QPointF pos_ = Diagram::snapToGrid(QPointF (position.x() - point_.x(),
position.y() - point_.y()));
//Translate all added items
for (auto qgi : added_items)
qgi->setPos(qgi->pos() += pos_);
}
// Load conductor
QList<Conductor *> added_conductors;
for (auto f : QET::findInDomElement(root,
QStringLiteral("conductors"),
QStringLiteral("conductor")))
{
if (!Conductor::valideXml(f)) continue;
//Check if terminal that conductor must be linked is know
Terminal* p1 = findTerminal(1, f, table_adr_id, added_elements);
Terminal* p2 = findTerminal(2, f, table_adr_id, added_elements);
if (p1 && p2 && p1 != p2)
{
Conductor *c = new Conductor(p1, p2);
if (c->isValid())
{
addItem(c);
c -> fromXml(f);
added_conductors << c;
}
else
delete c;
}
}
//Filling of falculatory lists
if (content_ptr) {
content_ptr -> m_elements = added_elements;
content_ptr -> m_conductors_to_move = added_conductors;
content_ptr -> m_text_fields = QSet<IndependentTextItem *>(
added_texts.begin(),
added_texts.end());
content_ptr -> m_images = QSet<DiagramImageItem *>(
added_images.begin(),
added_images.end());
content_ptr -> m_shapes = QSet<QetShapeItem *>(
added_shapes.begin(),
added_shapes.end());
content_ptr->m_terminal_strip.swap(added_strips);
content_ptr->m_tables.swap(added_tables);
}
adjustSceneRect();
return(true);
}
/**
@brief Diagram::folioSequentialsFromXml
Load folio sequential from QDomElement
@param root containing all folio sequentials
@param hash : to be loaded with content
@param folioSeq type
@param seq type
@param type of sequential
@param autonumFolioSeqType
*/
void Diagram::folioSequentialsFromXml(const QDomElement &root,
QHash<QString,
QStringList>* hash,
const QString& folioSeq,
const QString& seq,
const QString& type,
const QString& autonumFolioSeqType) {
foreach (QDomElement folioSeqAutoNum,
QET::findInDomElement(root, autonumFolioSeqType, folioSeq)) {
for(QDomElement folioseq
= folioSeqAutoNum.firstChildElement(type);
!folioseq.isNull();
folioseq = folioseq.nextSiblingElement(type)) {
QString title = folioseq.attribute(QStringLiteral("title"));
QStringList list;
int i = 1;
while (folioseq.hasAttribute(seq
+ QString::number(i))) {
list << folioseq.attribute(
seq + QString::number(i));
i++;
}
hash->insert(title,list);
}
}
}
/**
@brief Diagram::refreshContents
refresh all content of diagram.
- refresh conductor text.
- linking the elements waiting to be linked
- Refresh the connection of the dynamic element text item
(use for text with source of text label)
*/
void Diagram::refreshContents()
{
DiagramContent dc_(this, false);
for (auto &elmt : dc_.m_elements) {
elmt->initLink(project());
for (auto &deti : elmt->dynamicTextItems())
deti->refreshLabelConnection();
}
for (auto &conductor : dc_.conductors()) {
conductor->refreshText();
}
for (auto &table : std::as_const(dc_.m_tables)) {
table->initLink();
}
for (auto &strip :std::as_const(dc_.m_terminal_strip)) {
strip->refreshPending();
}
}
/**
@brief Diagram::addItem
Réimplemented from QGraphicsScene::addItem(QGraphicsItem *item)
Do some specific operation if item need it (for example an element)
@param item
*/
void Diagram::addItem(QGraphicsItem *item)
{
if (!item || isReadOnly() || item->scene() == this) return;
QGraphicsScene::addItem(item);
switch (item->type())
{
case Element::Type:
{
m_project->dataBase()->addElement(
static_cast<Element *>(item));
break;
}
case Conductor::Type:
{
Conductor *conductor = static_cast<Conductor *>(item);
conductor->terminal1->addConductor(conductor);
conductor->terminal2->addConductor(conductor);
conductor->calculateTextItemPosition();
m_project->dataBase()->addConductor(conductor);
break;
}
default: {break;}
}
}
/**
@brief Diagram::removeItem
Reimplemented from QGraphicsScene::removeItem(QGraphicsItem *item)
Do some specific operation if item need it (for example an element)
@param item
*/
void Diagram::removeItem(QGraphicsItem *item)
{
if (!item || isReadOnly()) return;
switch (item->type())
{
case Element::Type:
{
auto elmt = static_cast<Element*>(item);
elmt->unlinkAllElements();
m_project->dataBase()->removeElement(elmt);
break;
}
case Conductor::Type:
{
Conductor *conductor = static_cast<Conductor *>(item);
conductor->terminal1->removeConductor(conductor);
conductor->terminal2->removeConductor(conductor);
m_project->dataBase()->removeConductor(conductor);
break;
}
default: {break;}
}
QGraphicsScene::removeItem(item);
}
/**
@brief Diagram::titleChanged
emit(diagramTitleChanged(this, title));
@param title
*/
void Diagram::titleChanged() {
emit(diagramTitleChanged(this));
}
/**
@brief Diagram::titleBlockTemplateChanged
This slot may be used to inform the diagram object that the given title
block template has changed. The diagram will thus flush its title
block-dedicated rendering cache.
@param template_name Name of the title block template that has changed
*/
void Diagram::titleBlockTemplateChanged(const QString &template_name) {
if (border_and_titleblock.titleBlockTemplateName() != template_name)
return;
border_and_titleblock.titleBlockTemplateChanged(template_name);
update();
}
/**
@brief Diagram::titleBlockTemplateRemoved
This slot has to be be used to inform this class that the given title block
template is about to be removed and is no longer accessible. This class
will either use the provided optional TitleBlockTemplate or the default
title block provided by QETApp::defaultTitleBlockTemplate()
@param template_name Name of the title block template that has changed
@param new_template (Optional) Name of the title block template to use instead
*/
void Diagram::titleBlockTemplateRemoved(const QString &template_name,
const QString &new_template)
{
if (border_and_titleblock.titleBlockTemplateName() != template_name) return;
const TitleBlockTemplate *final_template =
m_project->embeddedTitleBlockTemplatesCollection()
->getTemplate(new_template);
border_and_titleblock.titleBlockTemplateRemoved(template_name,
final_template);
update();
}
/**
@brief Diagram::setTitleBlockTemplate
Set the template to use to render the title block of this diagram.
@param template_name Name of the title block template.
*/
void Diagram::setTitleBlockTemplate(const QString &template_name)
{
if (!m_project) return;
QString current_name = border_and_titleblock.titleBlockTemplateName();
const TitleBlockTemplate *titleblock_template =
m_project->embeddedTitleBlockTemplatesCollection()
->getTemplate(template_name);
border_and_titleblock.titleBlockTemplateRemoved(current_name,
titleblock_template);
if (template_name != current_name)
emit(usedTitleBlockTemplateChanged(template_name));
project()->dataBase()->diagramInfoChanged(this);
}
/**
@brief Diagram::selectAll
Select all schema objects
\~French Selectionne tous les objets du schema
*/
void Diagram::selectAll()
{
if (items().isEmpty()) return;
blockSignals(true);
foreach(QGraphicsItem *qgi, items()) qgi -> setSelected(true);
blockSignals(false);
emit(selectionChanged());
}
/**
@brief Diagram::deselectAll
Deselects all selected objects
\~French Deslectionne tous les objets selectionnes
*/
void Diagram::deselectAll()
{
if (items().isEmpty()) return;
clearSelection();
}
/**
@brief Diagram::invertSelection
Reverses the selection state of all schema objects
Inverse l'etat de selection de tous les objets du schema
*/
void Diagram::invertSelection()
{
if (items().isEmpty()) return;
blockSignals(true);
//Get only allowed graphics item
//because some item can be deleted between the
//call of items() and the use of the item in the second 'for' loop
//and crash Qet with a segfault.
QVector<QGraphicsItem *> item_list;
for (auto item : items())
{
if (dynamic_cast<QetGraphicsItem *>(item) ||
dynamic_cast<DiagramTextItem *>(item) ||
dynamic_cast<Conductor *>(item)) {
item_list << item;
}
}
for (auto item : std::as_const(item_list)) {
item -> setSelected(!item -> isSelected());
}
blockSignals(false);
emit selectionChanged();
}
/**
@brief Diagram::selectAllConductors
Select every conductor on this diagram, deselecting anything else.
*/
void Diagram::selectAllConductors()
{
if (items().isEmpty()) return;
blockSignals(true);
for (auto item : items()) {
item -> setSelected(dynamic_cast<Conductor *>(item) != nullptr);
}
blockSignals(false);
emit selectionChanged();
}
/**
@brief Diagram::selectAllTextFields
Select every text field on this diagram (independent/static text,
conductor labels, and dynamic element texts), deselecting anything else.
*/
void Diagram::selectAllTextFields()
{
if (items().isEmpty()) return;
blockSignals(true);
for (auto item : items()) {
item -> setSelected(dynamic_cast<DiagramTextItem *>(item) != nullptr);
}
blockSignals(false);
emit selectionChanged();
}
/**
@brief Diagram::selectNextItem
Select the next (or, if @a forward is false, the previous) selectable
item on this diagram, cycling through items() (z-order) and wrapping
around at either end. If nothing is currently selected, selects the
first (or last) item. Uses the same "what counts as a real selectable
diagram item" filter as invertSelection(), so the candidate list and
its order always match what the user could reach by clicking.
@param forward true to select the next item, false for the previous one
*/
void Diagram::selectNextItem(bool forward)
{
QList<QGraphicsItem *> candidates;
for (auto item : items()) {
if (dynamic_cast<QetGraphicsItem *>(item) ||
dynamic_cast<DiagramTextItem *>(item) ||
dynamic_cast<Conductor *>(item)) {
candidates << item;
}
}
if (candidates.isEmpty())
return;
int current_index = -1;
for (int i = 0; i < candidates.size(); ++i) {
if (candidates.at(i) -> isSelected()) {
current_index = i;
break;
}
}
int next_index;
if (current_index == -1) {
next_index = forward ? 0 : candidates.size() - 1;
} else {
next_index = forward
? (current_index + 1) % candidates.size()
: (current_index - 1 + candidates.size()) % candidates.size();
}
clearSelection();
QGraphicsItem *next_item = candidates.at(next_index);
next_item -> setSelected(true);
next_item -> ensureVisible();
}
/**
@brief Diagram::insertFolioSeqHash
This class inserts a stringlist containing all
sequential variables related to an autonum in a QHash
@param hash : to be accessed
@param title : autonum title
@param type : to be treated
@param nc : Context to be manipulated
*/
void Diagram::insertFolioSeqHash(QHash<QString,
QStringList> *hash,
const QString& title,
const QString& type,
NumerotationContext *nc) {
QStringList max;
for (int i = 0; i < nc->size(); i++) {
if (nc->itemAt(i).at(0) == type) {
nc->replaceValue(i,
QString::number(
nc->itemAt(i).at(3).toInt()));
max.append(QString::number(nc->itemAt(i).at(3).toInt()
- nc->itemAt(i).at(2).toInt()));
}
}
hash->insert(title,max);
}
/**
@brief Diagram::loadFolioSeqHash
This class loads all folio sequential variables
related to the current autonum
@param hash : to be accessed
@param title : autonum title
@param type : to be treated
@param nc : Context to be manipulated
*/
void Diagram::loadFolioSeqHash(QHash<QString,
QStringList> *hash,
const QString& title,
const QString& type,
NumerotationContext *nc) {
int j = 0;
for (int i = 0; i < nc->size(); i++) {
if (nc->itemAt(i).at(0) == type) {
QString new_value;
new_value =
QString::number(
hash->value(title).at(j).toInt()
+ nc->itemAt(i).at(2).toInt());
nc->replaceValue(i,new_value);
j++;
}
}
}
/**
@brief Diagram::changeZValue
Change the Z value of the current selected item, according to option
@param option
*/
void Diagram::changeZValue(QET::DepthOption option)
{
DiagramContent dc(this);
QUndoCommand *undo = new QUndoCommand(tr("Modifier la profondeur"));
QList<QGraphicsItem *> l = dc.items(DiagramContent::SelectedOnly | \
DiagramContent::Elements | \
DiagramContent::Shapes | \
DiagramContent::Images);
QList<QGraphicsObject *> list;
for(QGraphicsItem *item : l)
list << item->toGraphicsObject();
qreal maxz=0,
minz=0;
for(QGraphicsItem *item : this->items())
{
qreal z = item->zValue();
if(z >= Terminal::Z-2)
continue;
maxz = std::max(maxz,z);
minz = std::min(minz,z);
}
if(option == QET::Raise)
{
for(QGraphicsObject *qgo : list)
if(qgo->zValue() < (Terminal::Z-2)) //Ensure item is always below terminal
new QPropertyUndoCommand(qgo,
"z",
qgo->zValue(),
qgo->zValue()+1,
undo);
}
else if(option == QET::Lower)
{
for(QGraphicsObject *qgo : list)
if(qgo->zValue() < (Terminal::Z-2)) //Ensure item is always below terminal
new QPropertyUndoCommand(qgo,
"z",
qgo->zValue(),
qgo->zValue()-1,
undo);
}
else if (option == QET::BringForward)
{
for(QGraphicsObject *qgo : list)
new QPropertyUndoCommand(qgo,
"z",
qgo->zValue(),
maxz+1,
undo);
}
else if(option == QET::SendBackward)
{
for(QGraphicsObject *qgo : list)
new QPropertyUndoCommand(qgo,
"z",
qgo->zValue(),
minz-1,
undo);
}
if(undo->childCount())
this->undoStack().push(undo);
else
delete undo;
}
/**
@brief Diagram::loadElmtFolioSeq
This class loads all folio sequential variables related
to the current autonum
*/
void Diagram::loadElmtFolioSeq()
{
QString title = project()->elementCurrentAutoNum();
NumerotationContext nc = project()->elementAutoNum(title);
//Unit Folio
if (m_elmt_unitfolio_max.isEmpty()
|| !m_elmt_unitfolio_max.contains(title)) {
//Insert Initial Value
if (project()->elementAutoNumCurrentFormula().contains(
QStringLiteral("%sequf_"))) {
insertFolioSeqHash(&m_elmt_unitfolio_max,
title,
QStringLiteral("unitfolio"),
&nc);
project()->addElementAutoNum(title,nc);
}
}
else if (m_elmt_unitfolio_max.contains(title)) {
//Load Folio Current Value
if (project()->elementAutoNumCurrentFormula().contains(
QStringLiteral("%sequf_"))) {
loadFolioSeqHash(&m_elmt_unitfolio_max,
title,
QStringLiteral("unitfolio"),
&nc);
project()->addElementAutoNum(title,nc);
}
}
//Ten Folio
if (m_elmt_tenfolio_max.isEmpty()
|| !m_elmt_tenfolio_max.contains(title)) {
//Insert Initial Value
if (project()->elementAutoNumCurrentFormula().contains(
QStringLiteral("%seqtf_"))) {
insertFolioSeqHash(&m_elmt_tenfolio_max,
title,
QStringLiteral("tenfolio"),
&nc);
project()->addElementAutoNum(title,nc);
}
}
else if (m_elmt_tenfolio_max.contains(title)) {
//Load Folio Current Value
if (project()->elementAutoNumCurrentFormula().contains(
QStringLiteral("%seqtf_"))) {
loadFolioSeqHash(&m_elmt_tenfolio_max,
title,
QStringLiteral("tenfolio"),
&nc);
project()->addElementAutoNum(title,nc);
}
}
//Hundred Folio
if (m_elmt_hundredfolio_max.isEmpty()
|| !m_elmt_hundredfolio_max.contains(title)) {
//Insert Initial Value
if (project()->elementAutoNumCurrentFormula().contains(
QStringLiteral("%seqhf_"))) {
insertFolioSeqHash(&m_elmt_hundredfolio_max,
title,
QStringLiteral("hundredfolio"),
&nc);
project()->addElementAutoNum(title,nc);
}
}
else if (m_elmt_hundredfolio_max.contains(title)) {
//Load Folio Current Value
if (project()->elementAutoNumCurrentFormula().contains(
QStringLiteral("%seqhf_"))) {
loadFolioSeqHash(&m_elmt_hundredfolio_max,
title,
QStringLiteral("hundredfolio"),
&nc);
project()->addElementAutoNum(title,nc);
}
}
}
/**
@brief Diagram::loadCndFolioSeq
This class loads all conductor folio sequential variables related
to the current autonum
*/
void Diagram::loadCndFolioSeq()
{
//Conductor
QString title = project()->conductorCurrentAutoNum();
NumerotationContext nc = project()->conductorAutoNum(title);
QString formula = autonum::numerotationContextToFormula(nc);
//Unit Folio
if (m_cnd_unitfolio_max.isEmpty()
|| !m_cnd_unitfolio_max.contains(title)) {
//Insert Initial Value
if (formula.contains(QStringLiteral("%sequf_"))) {
insertFolioSeqHash(&m_cnd_unitfolio_max,
title,
QStringLiteral("unitfolio"),
&nc);
project()->addConductorAutoNum(title,nc);
}
}
else if (m_cnd_unitfolio_max.contains(title)) {
//Load Folio Current Value
if (formula.contains(QStringLiteral("%sequf_"))) {
loadFolioSeqHash(&m_cnd_unitfolio_max,
title,
QStringLiteral("unitfolio"),
&nc);
project()->addConductorAutoNum(title,nc);
}
}
//Ten Folio
if (m_cnd_tenfolio_max.isEmpty()
|| !m_cnd_tenfolio_max.contains(title)) {
//Insert Initial Value
if (formula.contains(QStringLiteral("%seqtf_"))) {
insertFolioSeqHash(&m_cnd_tenfolio_max,
title,
QStringLiteral("tenfolio"),
&nc);
project()->addConductorAutoNum(title,nc);
}
}
else if (m_cnd_tenfolio_max.contains(title)) {
//Load Folio Current Value
if (formula.contains(QStringLiteral("%seqtf_"))) {
loadFolioSeqHash(&m_cnd_tenfolio_max,
title,
QStringLiteral("tenfolio"),
&nc);
project()->addConductorAutoNum(title,nc);
}
}
//Hundred Folio
if (m_cnd_hundredfolio_max.isEmpty()
|| !m_cnd_hundredfolio_max.contains(title)) {
//Insert Initial Value
if (formula.contains(QStringLiteral("%seqhf_"))) {
insertFolioSeqHash(&m_cnd_hundredfolio_max,
title,
QStringLiteral("hundredfolio"),
&nc);
project()->addConductorAutoNum(title,nc);
}
}
else if (m_cnd_hundredfolio_max.contains(title)) {
//Load Folio Current Value
if (formula.contains(QStringLiteral("%seqhf_"))) {
loadFolioSeqHash(&m_cnd_hundredfolio_max,
title,
QStringLiteral("hundredfolio"),
&nc);
project()->addConductorAutoNum(title,nc);
}
}
}
/**
@brief Diagram::title
@return title of the titleblock
\~Frenchle titre du cartouche
*/
QString Diagram::title() const
{
return(border_and_titleblock.title());
}
/**
@brief Diagram::elements
@return the list containing all elements
*/
QList <Element *> Diagram::elements() const
{
QList<Element *> element_list;
foreach (QGraphicsItem *qgi, items()) {
if (Element *elmt = qgraphicsitem_cast<Element *>(qgi))
element_list <<elmt;
}
return (element_list);
}
/**
@brief Diagram::conductors
@return the list containing all conductors
*/
QList <Conductor *> Diagram::conductors() const
{
QList<Conductor *> cnd_list;
foreach (QGraphicsItem *qgi, items()) {
if (Conductor *cnd = qgraphicsitem_cast<Conductor *>(qgi))
cnd_list <<cnd;
}
return (cnd_list);
}
/**
@brief Diagram::elementsMover
@return
*/
ElementsMover &Diagram::elementsMover()
{
return m_elements_mover;
}
/**
@brief Diagram::elementTextsMover
@return
*/
ElementTextsMover &Diagram::elementTextsMover()
{
return m_element_texts_mover;
}
/**
@brief Diagram::usesElement
Used to find out if an element is used on a schema
\~French Permet de savoir si un element est utilise sur un schema
\~ @param location : Location of an element
\~French Emplacement d'un element
\~ @return true if the location element is used on this schema,
false otherwise
\~French true si l'element location est utilise sur ce schema,
false sinon
*/
bool Diagram::usesElement(const ElementsLocation &location)
{
for(Element *element : elements()) {
if (element -> location() == location) {
return(true);
}
}
return(false);
}
/**
@brief Diagram::usesTitleBlockTemplate
@param name : a title block template name
@return true if the provided template is used by this diagram, false
otherwise.
*/
bool Diagram::usesTitleBlockTemplate(const QString &name) {
return(name == border_and_titleblock.titleBlockTemplateName());
}
/**
@brief Diagram::freezeElements
Freeze every existent element label.
*/
void Diagram::freezeElements(bool freeze) {
foreach (Element *elmt, elements()) {
elmt->freezeLabel(freeze);
}
}
/**
@brief Diagram::unfreezeElements
Unfreeze every existent element label.
*/
void Diagram::unfreezeElements()
{
foreach (Element *elmt, elements()) {
elmt->freezeLabel(false);
}
}
/**
@brief Diagram::freezeNewElements
Set new element label to be frozen.
*/
void Diagram::setFreezeNewElements(bool b) {
m_freeze_new_elements = b;
}
/**
@brief Diagram::freezeNewElements
@return current freeze new element status .
*/
bool Diagram::freezeNewElements()
{
return m_freeze_new_elements;
}
/**
@brief Diagram::freezeConductors
Freeze every existent conductor label.
*/
void Diagram::freezeConductors(bool freeze) {
foreach (Conductor *cnd, conductors()) {
cnd->setFreezeLabel(freeze);
}
}
/**
@brief Diagram::setfreezeNewConductors
Set new conductor label to be frozen.
*/
void Diagram::setFreezeNewConductors(bool b) {
m_freeze_new_conductors_ = b;
}
/**
@brief Diagram::freezeNewConductors
@return current freeze new conductor status .
*/
bool Diagram::freezeNewConductors()
{
return m_freeze_new_conductors_;
}
/**
@brief Diagram::adjustSceneRect
Recalcul and adjust the size of the scene
*/
void Diagram::adjustSceneRect()
{
QRectF old_rect = sceneRect();
setSceneRect(border_and_titleblock.borderAndTitleBlockRect().united(
itemsBoundingRect()));
update(old_rect.united(sceneRect()));
}
/**
@brief Diagram::applyProperties
This method allows you to apply new rendering options while
accessing the current rendering properties.
\~French Cette methode permet d'appliquer de nouvelles options de rendu
tout en accedant aux proprietes de rendu en cours.
\~ @param new_properties :
New rendering options to apply
\~French Nouvelles options de rendu a appliquer
\~ @return rendering options before applying new_properties
\~French les options de rendu avant l'application de new_properties
*/
ExportProperties Diagram::applyProperties(
const ExportProperties &new_properties) {
// exports current rendering options
// exporte les options de rendu en cours
ExportProperties old_properties;
old_properties.draw_grid = displayGrid();
old_properties.draw_guides = displayGuides();
old_properties.draw_border = border_and_titleblock.borderIsDisplayed();
old_properties.draw_titleblock = border_and_titleblock.titleBlockIsDisplayed();
old_properties.draw_terminals = drawTerminals();
old_properties.draw_terminal_names = drawTerminalNames();
old_properties.draw_colored_conductors = drawColoredConductors();
old_properties.exported_area = useBorder() ? QET::BorderArea
: QET::ElementsArea;
// apply the new rendering options
// applique les nouvelles options de rendu
setUseBorder (new_properties.exported_area == QET::BorderArea);
setDrawTerminals (new_properties.draw_terminals);
setDrawTerminalNames (new_properties.draw_terminal_names);
setDrawColoredConductors (new_properties.draw_colored_conductors);
setDisplayGrid (new_properties.draw_grid);
setDisplayGuides (new_properties.draw_guides);
border_and_titleblock.displayBorder(new_properties.draw_border);
border_and_titleblock.displayTitleBlock (new_properties.draw_titleblock);
// return old rendering options
// retourne les anciennes options de rendu
return(old_properties);
}
/**
@brief Diagram::convertPosition
@param pos :
Cartesian position (ex: 10.3, 45.2) to transform into position
in the grid (ex: B2)
\~French Position cartesienne (ex : 10.3, 45.2) a transformer en position
dans la grille (ex : B2)
\~ @return a position in the grid corresponding to pos
\~French la position dans la grille correspondant a pos
*/
DiagramPosition Diagram::convertPosition(const QPointF &pos) {
// delegue le calcul au BorderTitleBlock
DiagramPosition diagram_position =
border_and_titleblock.convertPosition(pos);
// embarque la position cartesienne
diagram_position.setPosition(pos);
return(diagram_position);
}
/**
@brief Diagram::snapToGrid
Return a nearest snap point of p
@param p point to find the nearest snapped point
@return
*/
QPointF Diagram::snapToGrid(const QPointF &p)
{
QSettings settings;
int xGrid = settings.value(QStringLiteral("diagrameditor/Xgrid"),
Diagram::xGrid).toInt();
int yGrid = settings.value(QStringLiteral("diagrameditor/Ygrid"),
Diagram::yGrid).toInt();
//Return a point rounded to the nearest pixel
if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
{
int p_x = qRound(p.x());
int p_y = qRound(p.y());
return (QPointF(p_x, p_y));
}
//Return a point snapped to the grid
int p_x = qRound(p.x() / xGrid) * xGrid;
int p_y = qRound(p.y() / yGrid) * yGrid;
return (QPointF(p_x, p_y));
}
/**
@brief Diagram::setDrawTerminals
Defines whether or not to display the terminals
\~French Definit s'il faut afficher ou non les bornes
\~ @param dt : true to display the bounds, false otherwise
\~French true pour afficher les bornes, false sinon
*/
void Diagram::setDrawTerminals(bool dt) {
draw_terminals_ = dt;
foreach(QGraphicsItem *qgi, items()) {
if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
t -> update();
}
}
}
/**
@brief Diagram::setDrawTerminalNames
Defines whether or not to display the terminal names/labels
@param dt : true to display the terminal names, false otherwise
*/
void Diagram::setDrawTerminalNames(bool dt) {
draw_terminal_names_ = dt;
foreach(QGraphicsItem *qgi, items()) {
if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
t -> update();
}
}
}
/**
@brief Diagram::setDrawColoredConductors
Defines whether or not to respect the colors of the conductors.
If not, the conductors are all drawn in black.
\~French Definit s'il faut respecter ou non les couleurs des conducteurs.
Si non, les conducteurs sont tous dessines en noir.
\~ @param dcc true to respect the colors, false otherwise
\~French dcc true pour respecter les couleurs, false sinon
*/
void Diagram::setDrawColoredConductors(bool dcc) {
draw_colored_conductors_ = dcc;
}
/**
@brief Diagram::selectedConductors
@return the list of conductors selected on the diagram
\~French la liste des conducteurs selectionnes sur le schema
*/
QSet<Conductor *> Diagram::selectedConductors() const
{
QSet<Conductor *> conductors_set;
foreach(QGraphicsItem *qgi, selectedItems()) {
if (Conductor *c = qgraphicsitem_cast<Conductor *>(qgi)) {
conductors_set << c;
}
}
return(conductors_set);
}
/**
@brief Diagram::clipboardMayContainDiagram
@return true if the clipboard appears to contain a schema
\~French true si le presse-papier semble contenir un schema
*/
bool Diagram::clipboardMayContainDiagram()
{
QString clipboard_text = QApplication::clipboard() -> text().trimmed();
bool may_be_diagram = clipboard_text.startsWith(QStringLiteral("<diagram"))
&& clipboard_text.endsWith(QStringLiteral("</diagram>"));
return(may_be_diagram);
}
/**
@brief Diagram::project
@return the project to which this schema belongs
or 0 if it is an independent schema.
\~French le projet auquel ce schema appartient
ou 0 s'il s'agit d'un schema independant.
*/
QETProject *Diagram::project() const
{
return(m_project);
}
/**
@brief Diagram::folioIndex
@return the folio number of this diagram within its parent project,
or -1 if it is has no parent project
*/
int Diagram::folioIndex() const
{
if (!m_project) return(-1);
return(m_project -> folioIndex(this));
}
/**
@brief Diagram::isReadOnly
@return true if this diagram is read only.
This method is same has call Diagram::project() -> isReadOnly()
*/
bool Diagram::isReadOnly() const
{
return m_project -> isReadOnly();
}
/**
@brief Diagram::content
@return The content of the diagram.
The conductors are all seated in conductorsToMove.
\~French Le contenu du schema. Les conducteurs sont tous places dans
conductorsToMove.
*/
DiagramContent Diagram::content() const
{
DiagramContent dc;
foreach(QGraphicsItem *qgi, items()) {
if (Element *e = qgraphicsitem_cast<Element *>(qgi)) {
dc.m_elements << e;
} else if (IndependentTextItem *iti =
qgraphicsitem_cast<IndependentTextItem *>(qgi)) {
dc.m_text_fields << iti;
} else if (Conductor *c =
qgraphicsitem_cast<Conductor *>(qgi)) {
dc.m_conductors_to_move << c;
}
}
return(dc);
}
/**
@brief Diagram::canRotateSelection
@return True if a least one of selected items can be rotated
*/
bool Diagram::canRotateSelection() const
{
for (QGraphicsItem *qgi : selectedItems())
{
if (qgi->type() == IndependentTextItem::Type ||
qgi->type() == ConductorTextItem::Type ||
qgi->type() == DiagramImageItem::Type ||
qgi->type() == Element::Type ||
qgi->type() == DynamicElementTextItem::Type)
return true;
if(qgi->type() == QGraphicsItemGroup::Type)
if(dynamic_cast<ElementTextItemGroup *>(qgi))
return true;
}
return false;
}
/*
* To copy elements with right-aligned or centered elementtext, the text position
of dynamicElementTextItems in report- and slave elements must be reset
to the original insert position bevor writing to clipboard.
It is only necessary for right-aligned and centered texts,
but we do it for all, because it has no influence on other texts.
*/
/**
@brief Diagram::correctTextPos
set insertion position to the original insertion position of the element texts before copying
@param Element
*/
void Diagram::correctTextPos(Element* elmt)
{
for (auto deti : elmt->dynamicTextItems()){
// correct dynamicElementTextItem of type ElementInfo
if( deti->textFrom() == DynamicElementTextItem::ElementInfo){
// save current PlainText
deti->setText(deti->toPlainText());
// writing an empty string to PlainText set
// the insertion point to the original insertion point
deti->setPlainText("");
// block the alignment calculation for the next write action
deti->m_block_alignment = true;
// we need to set the plain text with its value,
// otherewise the text value will not be copy
deti->setPlainText(deti->text());
deti->m_block_alignment = false;
}
// correct dynamicElementTextItem of type CompositeText
else if(deti->textFrom() == DynamicElementTextItem::CompositeText){
QString composite_text = deti->compositeText();
// create compositeText() without variables for slave item
// This string corresponds to the original text with original insertion point
if(elmt->linkType()==Element::Slave){
const QStringList variables_list = QETInformation::elementInfoKeys();
for (auto &variable : variables_list){
if(composite_text.contains(QETInformation::infoToVar(variable)))
composite_text.replace(QETInformation::infoToVar(variable),"");
}
}
// create compositeText() without variables for report item
else if(elmt->linkType()&Element::AllReport){
const QStringList variables_list = QETInformation::folioReportInfoKeys();
for (auto &variable : variables_list){
if(composite_text.contains(QETInformation::infoToVar(variable)))
composite_text.replace(QETInformation::infoToVar(variable),"");
}
}
deti->setText(deti->toPlainText());
deti->setPlainText(composite_text);
deti->m_block_alignment = true;
deti->setPlainText(deti->text());
deti->m_block_alignment = false;
// We need the string for the restoration, so we save it
deti->setText(composite_text);
}
}
// same for textgroups
for (auto group : elmt->textGroups()){
for(DynamicElementTextItem *deti : group->texts()){
if( deti->textFrom() == DynamicElementTextItem::ElementInfo){
deti->setText(deti->toPlainText());
deti->m_block_alignment = true;
deti->setPlainText(deti->text());
deti->m_block_alignment = false;
deti->setPlainText("");
}
else if(deti->textFrom() == DynamicElementTextItem::CompositeText){
QString composite_text = deti->compositeText();
if(elmt->linkType()==Element::Slave){
QStringList variables_list = QETInformation::elementInfoKeys();
for (auto variable : variables_list){
if(composite_text.contains(QETInformation::infoToVar(variable)))
composite_text.replace(QETInformation::infoToVar(variable),"");
}
}
else if(elmt->linkType()&Element::AllReport){
QStringList variables_list = QETInformation::folioReportInfoKeys();
for (auto variable : variables_list){
if(composite_text.contains(QETInformation::infoToVar(variable)))
composite_text.replace(QETInformation::infoToVar(variable),"");
}
}
deti->setText(deti->toPlainText());
deti->setPlainText(composite_text);
deti->m_block_alignment = true;
deti->setPlainText(deti->text());
deti->m_block_alignment = false;
deti->setText(composite_text);
}
}
}
}
/*
* After changing the element texts for copying, the element texts has to be restored.
*/
/**
@brief Diagram::restoreText
After correcting the elementtext position during copying
the Text has to be restored
@param Element
*/
void Diagram::restoreText(Element* elmt)
{
for (auto deti : elmt->dynamicTextItems()){
// restore info text
if( deti->textFrom() == DynamicElementTextItem::ElementInfo){
// delete the actual plainText without alignmant calculation
deti->m_block_alignment = true;
deti->setPlainText("");
deti->m_block_alignment = false;
// restore the plainText
deti->setPlainText(deti->text());
}
// restore composite text
else if (deti->textFrom() == DynamicElementTextItem::CompositeText){
QString composite_text = deti->text();
deti->setText(deti->toPlainText());
// set the PlainText to the origin text without alignment calculation
deti->m_block_alignment = true;
deti->setPlainText(composite_text);
deti->m_block_alignment = false;
deti->setPlainText(deti->text());
}
}
// same for text groups
for (auto group : elmt->textGroups()){
for(DynamicElementTextItem *deti : group->texts()){
if( deti->textFrom() == DynamicElementTextItem::ElementInfo){
deti->m_block_alignment = true;
deti->setPlainText("");
deti->m_block_alignment = false;
deti->setPlainText(deti->text());
}
else if (deti->textFrom() == DynamicElementTextItem::CompositeText){
QString composite_text = deti->text();
deti->setText(deti->toPlainText());
deti->m_block_alignment = true;
deti->setPlainText(composite_text);
deti->m_block_alignment = false;
deti->setPlainText(deti->text());
}
}
}
}
QUndoStack &Diagram::undoStack() {
return *(project()->undoStack());
}
/**
* @brief Diagram::updateProjectGuides
* Aktualisiert die internen Hilfslinien dieses Schaltplans
* basierend auf den Projekt-Einstellungen und erzwingt ein Neuzeichnen.
*/
void Diagram::updateProjectGuides(const QList<GuideProperties> &guides) {
m_guides_list.clear();
for (const GuideProperties &pg : guides) {
Diagram::Guide g;
g.orientation = static_cast<Diagram::Guide::Orientation>(pg.orientation);
g.position = pg.position;
g.color = pg.color;
m_guides_list.append(g);
}
update();
}