Files
qelectrotech-source-mirror/sources/qetgraphicsitem/elementtextitemgroup.cpp
T
ispyisail a5b5d49b98 Add horizontal and vertical mirror for symbols on a folio (#1335)
Edit > "Miroir horizontal" (M) and "Miroir vertical" (F), also in the
folio's right-click menu, mirror the selected symbols in place. The keys
are the ones the element editor uses for the same two actions.

An element keeps two mirrors about its own axes, applied before its
rotation, and saves them as mirror="horizontal|vertical|both" on its
<element> (written only when set, so other projects save byte for byte
as before). On a symbol turned by 90 or 270 degrees, a mirror of the
folio is the other mirror of the symbol itself, so the rotation never
changes: a label kept upright does not swing round, and "Pivoter" still
turns a mirrored symbol clockwise.

- Terminals face the mirrored way (Terminal::orientation()), so wires
  follow.
- The symbol stays where it was: its centre is kept, on the grid, since
  the hotspot is often a corner.
- Texts read normally. The element's texts, text groups and cross
  reference are mirrored a second time about the centre of their own box
  (Element::keepReadable()), and ElementPictureFactory draws the texts
  of the symbol itself the same way, in a cached picture per mirror.
  Groups and cross references held at the bottom of the folio stay
  centred under their element.
- DXF export mirrors the symbol's lines, arcs and texts.
- Scripting: qet.mirrorElement(folio, uuid, vertical) and
  qet.elementMirror(folio, uuid); live mode may run both menu commands;
  the MCP server gets a mirror_element op and qet_diff reports mirrors.

Not done: the parts of a PLC table drawn at run time
(Element::drawPlcTable()) are not kept readable on a mirrored PLC; the
project database has no column for it, as it has none for the rotation.

Test: tst_scriptmirror mirrors a symbol through --run, checks every
terminal's side and facing, the round trip, undo, a save and reload,
and a symbol turned by 90 degrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:05:17 +13:00

1025 lines
30 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 "elementtextitemgroup.h"
#include "../shownkinds.h"
#include "../qetproject.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../diagram.h"
#include "../qetapp.h"
#include "../undocommand/addelementtextcommand.h"
#include "crossrefitem.h"
#include "dynamicelementtextitem.h"
#include "element.h"
#include "masterelement.h"
#include "qgraphicsitemutility.h"
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <utility>
/**
@brief sorting
@param qgia
@param qgib
@return
*/
bool sorting(QGraphicsItem *qgia, QGraphicsItem *qgib)
{
return qgia->pos().y() < qgib->pos().y();
}
/**
@brief ElementTextItemGroup::ElementTextItemGroup
@param name :
@param parent :
*/
ElementTextItemGroup::ElementTextItemGroup(const QString &name,
Element *parent) :
QGraphicsItemGroup(parent),
m_name(name),
m_parent_element(parent)
{
ShownKinds::tag(this, ShownKinds::SymbolTexts);
setFlags(QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemIsMovable);
connect(parent,
&Element::linkedElementChanged,
this,
&ElementTextItemGroup::updateXref);
if(parent->diagram())
m_project_xref_connection = connect(
parent->diagram()->project(),
&QETProject::XRefPropertiesChanged,
this,
&ElementTextItemGroup::updateXref);
}
/**
@brief ElementTextItemGroup::itemChange
The group is very often built while its element is not on a scene yet
(project or element loading): the connection to the project was then
impossible and the first updateXref() ran without a diagram, so the
cross ref of a master waiting for its slaves stayed invisible until an
unrelated settings change happened to refresh it. Do both here, the
moment the group really reaches the scene.
@param change
@param value
@return
*/
QVariant ElementTextItemGroup::itemChange(
QGraphicsItem::GraphicsItemChange change,
const QVariant &value)
{
if (change == QGraphicsItem::ItemSceneHasChanged)
{
if (m_parent_element
&& m_parent_element->diagram()
&& m_parent_element->diagram()->project())
{
QETProject *project = m_parent_element->diagram()->project();
if (!m_project_xref_connection)
m_project_xref_connection = connect(
project,
&QETProject::XRefPropertiesChanged,
this,
&ElementTextItemGroup::updateXref);
updateXref();
}
}
return QGraphicsItemGroup::itemChange(change, value);
}
ElementTextItemGroup::~ElementTextItemGroup()
{}
/**
@brief ElementTextItemGroup::addToGroup
@param item
*/
void ElementTextItemGroup::addToGroup(QGraphicsItem *item)
{
if(item->type() == DynamicElementTextItem::Type)
{
//Before adding text to this group we must set the text at the same rotation of this group
if((item->rotation() != rotation()) && !m_block_alignment_update)
item->setRotation(rotation());
//On a mirrored element, QGraphicsItemGroup::addToGroup() would
//fold the mirror that keeps the text and this group readable
//(Element::keepReadable()) into the text's transform. The group
//gets its own back from updateAlignment().
item->resetTransform();
resetTransform();
QGraphicsItemGroup::addToGroup(item);
updateAlignment();
DynamicElementTextItem *deti = qgraphicsitem_cast<DynamicElementTextItem *>(item);
connect(deti, &DynamicElementTextItem::fontChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::textChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::textFromChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::infoNameChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::compositeTextChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::plainTextChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::textWidthChanged,
this, &ElementTextItemGroup::updateAlignment);
connect(deti, &DynamicElementTextItem::textFromChanged,
this, &ElementTextItemGroup::updateXref);
connect(deti, &DynamicElementTextItem::infoNameChanged,
this, &ElementTextItemGroup::updateXref);
updateXref();
}
}
/**
@brief ElementTextItemGroup::removeFromGroup
@param item
*/
void ElementTextItemGroup::removeFromGroup(QGraphicsItem *item)
{
resetTransform(); //See addToGroup(), given back by updateAlignment()
QGraphicsItemGroup::removeFromGroup(item);
//the item transformation is not reset, we must do it ourselves,
// because for example if the group rotation is 45°
//When item is removed from group,
// visually the item is unchanged (so 45°)
// but if we call item->rotation() the returned value is 0.
item->resetTransform();
item->setRotation(this->rotation());
item->setFlag(QGraphicsItem::ItemIsSelectable, true);
if (m_parent_element)
m_parent_element->keepReadable(item);
updateAlignment();
if(DynamicElementTextItem *deti = qgraphicsitem_cast<DynamicElementTextItem *>(item))
{
disconnect(deti, &DynamicElementTextItem::fontChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::textChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::textFromChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::infoNameChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::compositeTextChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::plainTextChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::textWidthChanged,
this, &ElementTextItemGroup::updateAlignment);
disconnect(deti, &DynamicElementTextItem::textFromChanged,
this, &ElementTextItemGroup::updateXref);
disconnect(deti, &DynamicElementTextItem::infoNameChanged,
this, &ElementTextItemGroup::updateXref);
updateXref();
}
}
/**
@brief ElementTextItemGroup::blockAlignmentUpdate
If true, the texts in this group are never aligned, moved, rotated etc...
the texts stay as it was, until blockAlignmentUpdate is set to false.
@param block
*/
void ElementTextItemGroup::blockAlignmentUpdate(bool block)
{
m_block_alignment_update = block;
}
/**
@brief ElementTextItemGroup::setAlignement
Set the alignement of this group
@param alignement
*/
void ElementTextItemGroup::setAlignment(Qt::Alignment alignement)
{
m_alignment = alignement;
updateAlignment();
emit alignmentChanged(alignement);
}
Qt::Alignment ElementTextItemGroup::alignment() const
{
return m_alignment;
}
/**
@brief ElementTextItemGroup::setAlignment
Update the alignement of the items in this group, according
to the current alignement.
@param alignement
*/
void ElementTextItemGroup::updateAlignment()
{
if(m_block_alignment_update)
return;
prepareGeometryChange();
QList <DynamicElementTextItem *> texts = this->texts();
//The mirror of a mirrored element's group depends on the size of
//the group: drop it while the texts are laid out, set it back after
resetTransform();
qreal rotation_ = rotation();
//Set the rotation of this group to 0° relative to the scene
qreal rot = rotation();
QGraphicsItem *parent = parentItem();
while (parent) {
rot += parent->rotation();
parent = parent->parentItem();
}
if(rot != 0)
setRotation(rotation() - rot);
if(texts.size() == 1)
{
prepareGeometryChange();
QGraphicsItem *first = texts.first();
setPos(mapFromScene(first->mapToScene(pos())));
first->setPos(0,0);
}
else if (texts.size() > 1)
{
qreal width = 0;
for(QGraphicsItem *item : texts)
if(item->boundingRect().width() > width)
width = item->boundingRect().width();
prepareGeometryChange();
std::sort(texts.begin(), texts.end(), sorting);
qreal y_offset = 0;
if(m_alignment == Qt::AlignLeft)
{
QPointF ref = texts.first()->pos();
for(QGraphicsItem *item : texts)
{
item->setPos(0, ref.y()+y_offset);
y_offset+=item->boundingRect().height() + m_vertical_adjustment;
}
}
else if(m_alignment == Qt::AlignVCenter)
{
QPointF ref(width/2,0);
for(QGraphicsItem *item : texts)
{
item->setPos(ref.x() - item->boundingRect().width()/2,
ref.y() + y_offset);
y_offset+=item->boundingRect().height() + m_vertical_adjustment;
}
}
else if (m_alignment == Qt::AlignRight)
{
QPointF ref(width,0);
for(QGraphicsItem *item : texts)
{
item->setPos(ref.x() - item->boundingRect().width(),
ref.y() + y_offset);
y_offset+=item->boundingRect().height() + m_vertical_adjustment;
}
}
}
//Restore the rotation
setRotation(rotation_);
if (m_parent_element)
m_parent_element->keepReadable(this);
if(m_Xref_item)
m_Xref_item->autoPos();
if(m_slave_Xref_item)
{
int slave_offset = 0;
Element *master_elmt = m_parent_element->linkedElements().isEmpty()
? nullptr : m_parent_element->linkedElements().first();
if (master_elmt && m_parent_element->diagram()) {
XRefProperties xrp = m_parent_element->diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
slave_offset = xrp.slaveOffset();
}
adjustSlaveXrefPos(slave_offset);
}
if(m_hold_to_bottom_of_page)
autoPos();
}
/**
@brief ElementTextItemGroup::setVerticalAdjustment
Set the value of the vertical adjustment to v.
The vertical adjutment is use to adjust
the space between the texts of this group.
@param v
*/
void ElementTextItemGroup::setVerticalAdjustment(int v)
{
prepareGeometryChange();
m_vertical_adjustment = v;
updateAlignment();
emit verticalAdjustmentChanged(v);
}
/**
@brief ElementTextItemGroup::setName
@param name Set the name of this group
*/
void ElementTextItemGroup::setName(QString name)
{
m_name = std::move(name);
emit nameChanged(m_name);
}
void ElementTextItemGroup::setHoldToBottomPage(bool hold)
{
if(m_hold_to_bottom_of_page == hold)
return;
m_hold_to_bottom_of_page = hold;
if(m_hold_to_bottom_of_page)
{
setFlag(QGraphicsItem::ItemIsSelectable, false);
setFlag(QGraphicsItem::ItemIsMovable, false);
connect(m_parent_element, &Element::yChanged, this, &ElementTextItemGroup::autoPos);
connect(m_parent_element, &Element::rotationChanged, this, &ElementTextItemGroup::autoPos);
connect(m_parent_element, &Element::mirrorChanged, this, &ElementTextItemGroup::autoPos);
if(m_parent_element->linkType() == Element::Master)
{
//We use timer to let the time of the parent element
// xref to be updated,
// before updating the position of this group
//because the position of this group is related
// to the size of the parent element Xref
//this is the context of both connections, so they go
//when the group is deleted: the project outlives it.
m_linked_changed_timer = connect(
m_parent_element,
&Element::linkedElementChanged,
this,
[this]()
{QTimer::singleShot(200,
this,
&ElementTextItemGroup::autoPos);}
);
if(m_parent_element->diagram())
m_XrefChanged_timer = connect(
m_parent_element->diagram()->project(),
&QETProject::XRefPropertiesChanged,
this,
[this]()
{QTimer::singleShot(200,
this,
&ElementTextItemGroup::autoPos);}
);
}
autoPos();
}
else
{
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemIsMovable, true);
disconnect(m_parent_element, &Element::yChanged,
this, &ElementTextItemGroup::autoPos);
disconnect(m_parent_element, &Element::rotationChanged,
this, &ElementTextItemGroup::autoPos);
disconnect(m_parent_element, &Element::mirrorChanged,
this, &ElementTextItemGroup::autoPos);
if(m_parent_element->linkType() == Element::Master)
{
disconnect(m_linked_changed_timer);
if(m_XrefChanged_timer)
disconnect(m_XrefChanged_timer);
}
}
emit holdToBottomPageChanged(hold);
}
void ElementTextItemGroup::setFrame(const bool frame)
{
m_frame = frame;
update();
emit frameChanged(m_frame);
}
bool ElementTextItemGroup::frame() const
{
return m_frame;
}
/**
@brief ElementTextItemGroup::texts
@return Every texts in this group
*/
QList<DynamicElementTextItem *> ElementTextItemGroup::texts() const
{
QList<DynamicElementTextItem *> list;
for(QGraphicsItem *qgi : childItems())
{
if(qgi->type() == DynamicElementTextItem::Type)
list << static_cast<DynamicElementTextItem *>(qgi);
}
return list;
}
/**
@brief ElementTextItemGroup::diagram
@return The diagram of this group, or nullptr if this group is not in a diagram
*/
Diagram *ElementTextItemGroup::diagram() const
{
if(scene())
return static_cast<Diagram *>(scene());
else
return nullptr;
}
/**
@brief ElementTextItemGroup::parentElement
@return The parent element of this group or nullptr
*/
Element *ElementTextItemGroup::parentElement() const
{
if(parentItem() && parentItem()->type() == Element::Type)
return static_cast<Element *>(parentItem());
else
return nullptr;
}
/**
@brief ElementTextItemGroup::toXml
Export data of this group to xml
@param dom_document
@return
*/
QDomElement ElementTextItemGroup::toXml(QDomDocument &dom_document) const
{
QDomElement dom_element = dom_document.createElement(this->xmlTaggName());
dom_element.setAttribute("name", m_name);
dom_element.setAttribute("x", QString::number(pos().x()));
dom_element.setAttribute("y", QString::number(pos().y()));
QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
dom_element.setAttribute("alignment", me.valueToKey(m_alignment));
dom_element.setAttribute("rotation", this->rotation());
dom_element.setAttribute("vertical_adjustment", m_vertical_adjustment);
dom_element.setAttribute("frame", m_frame? "true" : "false");
dom_element.setAttribute("hold_to_bottom_page",
m_hold_to_bottom_of_page == true ? "true" : "false");
QDomElement dom_texts = dom_document.createElement("texts");
for(DynamicElementTextItem *deti : texts())
{
QDomElement text = dom_document.createElement("text");
text.setAttribute("uuid", deti->uuid().toString());
dom_texts.appendChild(text);
}
dom_element.appendChild(dom_texts);
return dom_element;
}
/**
@brief ElementTextItemGroup::fromXml
Import data of this group from xml
@param dom_element
*/
void ElementTextItemGroup::fromXml(QDomElement &dom_element)
{
if (dom_element.tagName() != xmlTaggName()) {
qDebug() << "ElementTextItemGroup::fromXml : Wrong tagg name";
return;
}
setName(dom_element.attribute("name", "no name"));
QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
setAlignment(
Qt::Alignment(
me.keyToValue(
dom_element.attribute(
"alignment")
.toStdString()
.data()
)
)
);
setPos(dom_element.attribute("x", QString::number(0)).toDouble(),
dom_element.attribute("y", QString::number(0)).toDouble());
setRotation(dom_element.attribute("rotation", QString::number(0)).toDouble());
setVerticalAdjustment(dom_element.attribute("vertical_adjustment").toInt());
setFrame(dom_element.attribute("frame", "false") == "true"? true : false);
QString hold = dom_element.attribute("hold_to_bottom_page", "false");
setHoldToBottomPage(hold == "true" ? true : false);
if(parentElement())
{
m_block_alignment_update = true;
for(const QDomElement& text : QET::findInDomElement(dom_element, "texts", "text"))
{
DynamicElementTextItem *deti = nullptr;
QUuid uuid(text.attribute("uuid"));
for(DynamicElementTextItem *txt : parentElement()->dynamicTextItems())
if(txt->uuid() == uuid)
deti = txt;
if (deti)
parentElement()->addTextToGroup(deti, this);
}
m_block_alignment_update = false;
}
}
/**
@brief ElementTextItemGroup::paint
@param painter
@param option
@param widget
*/
void ElementTextItemGroup::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
if(isSelected())
{
painter->save();
QPen t;
t.setColor(Qt::gray);
t.setStyle(Qt::DashDotLine);
t.setCosmetic(true);
painter->setPen(t);
painter->drawRoundedRect(boundingRect().adjusted(1, 1, -1, -1),
10, 10);
painter->restore();
}
if(m_frame)
{
qreal font_size = 1;
QRectF rect;
for(DynamicElementTextItem *deti : this->texts())
{
font_size = std::max(font_size, deti->font().pointSizeF());
rect = rect.united(mapFromItem(deti, deti->frameRect()).boundingRect());
}
//Adjust the thickness according to the font size
qreal w=0.3;
if (font_size >= 5)
{
w = font_size*0.1;
if(w > 2.5)
w = 2.5;
}
painter->save();
QPen pen;
pen.setWidthF(w);
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing);
//Adjust the rounding of the rectangle according to the size of the font
qreal ro = font_size/3;
painter->drawRoundedRect(rect, ro, ro);
painter->restore();
}
}
/**
@brief ElementTextItemGroup::boundingRect
@return
*/
QRectF ElementTextItemGroup::boundingRect() const
{
//If we refer to the Qt doc, the bounding rect of a QGraphicsItemGroup,
//is the bounding of all childrens in the group
//When add an item in the group, the bounding rect is good, but
//if we move an item already in the group, the bounding rect of the group stay unchanged.
//We reimplement this function to avoid this behavior.
QRectF rect;
for(QGraphicsItem *qgi : texts())
{
QRectF r(qgi->pos(), QSize(qgi->boundingRect().width(),
qgi->boundingRect().height()));
rect = rect.united(r);
}
return rect;
}
void ElementTextItemGroup::setRotation(qreal angle)
{
QGraphicsItemGroup::setRotation(angle);
emit rotationChanged(angle);
}
void ElementTextItemGroup::setPos(const QPointF &pos)
{
QPointF old_pos = this->pos();
QGraphicsItemGroup::setPos(pos);
if (old_pos.x() != this->pos().x())
emit xChanged();
if (old_pos.y() != this->pos().y())
emit yChanged();
}
void ElementTextItemGroup::setPos(qreal x, qreal y)
{
QPointF old_pos = this->pos();
QGraphicsItemGroup::setPos(x,y);
if (old_pos.x() != this->pos().x())
emit xChanged();
if (old_pos.y() != this->pos().y())
emit yChanged();
}
/**
@brief ElementTextItemGroup::mousePressEvent
@param event
*/
void ElementTextItemGroup::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
m_first_move = true;
if(event->modifiers() & Qt::ControlModifier)
setSelected(!isSelected());
}
QGraphicsItemGroup::mousePressEvent(event);
}
/**
@brief ElementTextItemGroup::mouseMoveEvent
@param event
*/
void ElementTextItemGroup::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if((event->buttons() & Qt::LeftButton) && (flags() & ItemIsMovable))
{
if(diagram() && m_first_move)
diagram()->elementTextsMover().beginMovement(diagram(),
this);
if(m_first_move)
{
m_initial_position = pos();
if(parentElement())
parentElement()->setHighlighted(true);
}
QPointF current_parent_pos;
QPointF button_down_parent_pos;
current_parent_pos = mapToParent(mapFromScene(event->scenePos()));
button_down_parent_pos = mapToParent(mapFromScene(event->buttonDownScenePos(Qt::LeftButton)));
QPointF new_pos = m_initial_position + current_parent_pos - button_down_parent_pos;
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos));
if(diagram())
diagram()->elementTextsMover().continueMovement(event);
} else {
event->ignore();
}
if(m_first_move)
m_first_move = false;
}
/**
@brief ElementTextItemGroup::mouseReleaseEvent
@param event
*/
void ElementTextItemGroup::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(diagram())
{
diagram()->elementTextsMover().endMovement();
if(parentElement())
parentElement()->setHighlighted(false);
}
if(!(event->modifiers() & Qt::ControlModifier))
QGraphicsItemGroup::mouseReleaseEvent(event);
}
void ElementTextItemGroup::mouseDoubleClickEvent(
QGraphicsSceneMouseEvent *event)
{
if(m_slave_Xref_item)
{
if(m_slave_Xref_item->boundingRect().contains(mapToItem(m_slave_Xref_item, event->pos())))
{
if(parentElement()->linkType() == Element::Slave && !parentElement()->linkedElements().isEmpty())
{
m_slave_Xref_item->setDefaultTextColor(Qt::black);
Element *elmt = parentElement()->linkedElements().first();
//Unselect and ungrab mouse to prevent unwanted
//move when linked element is in the same scene of this.
setSelected(false);
ungrabMouse();
if(scene() != elmt->scene())
elmt->diagram()->showMe();
elmt->setSelected(true);
//Zoom to the element
for(QGraphicsView *view : elmt->scene()->views())
{
QRectF fit = elmt->sceneBoundingRect();
fit.adjust(-200, -200, 200, 200);
view->fitInView(fit, Qt::KeepAspectRatioByExpanding);
}
}
}
}
}
/**
@brief ElementTextItemGroup::keyPressEvent
@param event
*/
void ElementTextItemGroup::keyPressEvent(QKeyEvent *event)
{
if(event->modifiers() == Qt::ControlModifier)
{
if(event->key() == Qt::Key_Left && m_alignment != Qt::AlignLeft)
{
if(diagram())
diagram()->undoStack().push(new AlignmentTextsGroupCommand(this, Qt::AlignLeft));
else
setAlignment(Qt::AlignLeft);
}
else if (event->key() == Qt::Key_Up && m_alignment != Qt::AlignVCenter)
{
if(diagram())
diagram()->undoStack().push(new AlignmentTextsGroupCommand(this, Qt::AlignVCenter));
else
setAlignment(Qt::AlignVCenter);
}
else if (event->key() == Qt::Key_Right && m_alignment != Qt::AlignRight)
{
if(diagram())
diagram()->undoStack().push(new AlignmentTextsGroupCommand(this, Qt::AlignRight));
else
setAlignment(Qt::AlignRight);
}
}
event->ignore();
}
void ElementTextItemGroup::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
//The pos of the event is not in this item coordinate,
//but in child item hovered by the mouse, so we use the scene pos.
if(m_slave_Xref_item &&
m_slave_Xref_item->boundingRect().contains(m_slave_Xref_item->mapFromScene(event->scenePos())))
m_slave_Xref_item->setDefaultTextColor(Qt::blue);
QGraphicsItemGroup::hoverEnterEvent(event);
}
void ElementTextItemGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
if(m_slave_Xref_item)
m_slave_Xref_item->setDefaultTextColor(Qt::black);
QGraphicsItemGroup::hoverLeaveEvent(event);
}
void ElementTextItemGroup::updateXref()
{
if(m_parent_element->diagram())
{
QETProject *project = m_parent_element->diagram()->project();
if(m_parent_element->linkType() == Element::Master)
{
XRefProperties xrp = project->defaultXRefProperties(m_parent_element->kindInformations()["type"].toString());
if(xrp.snapTo() == XRefProperties::Label &&
(!m_parent_element->linkedElements().isEmpty()
|| CrossRefItem::showAllConfiguredSlaves(m_parent_element, xrp)))
{
//At least one text owned by this group must be set with
//textFrom -> element info and element info name -> label
//for display a xref
for(DynamicElementTextItem *deti : texts())
{
if(deti->textFrom() == DynamicElementTextItem::ElementInfo &&
deti->infoName() == "label")
{
if(!m_Xref_item)
m_Xref_item = new CrossRefItem(m_parent_element, this);
m_Xref_item->autoPos();
return;
}
}
}
}
else if(m_parent_element->linkType() == Element::Slave &&
!m_parent_element->linkedElements().isEmpty())
{
Element *master_elmt = m_parent_element->linkedElements().first();
XRefProperties xrp = project->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
//Champ de texte: store xref in element informations
if(xrp.getXrefPos() == Qt::AlignHCenter)
{
for(DynamicElementTextItem *deti : texts())
{
if(deti->textFrom() == DynamicElementTextItem::ElementInfo && deti->infoName() == "xref")
{
QString xref_label = xrp.slaveLabel();
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
DiagramContext dc = m_parent_element->elementInformations();
if(dc.value("xref").toString() != xref_label)
{
dc.addValue("xref", xref_label);
m_parent_element->setElementInformations(dc);
}
//Set up connections for future updates
if(m_update_slave_Xref_connection.isEmpty())
{
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(project, &QETProject::projectDiagramsOrderChanged, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(project, &QETProject::diagramRemoved, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(project, &QETProject::XRefPropertiesChanged, this, &ElementTextItemGroup::updateXref);
}
return;
}
}
//No "xref" text found: fall through to cleanup
}
for(DynamicElementTextItem *deti : texts())
{
if((deti->textFrom() == DynamicElementTextItem::ElementInfo && deti->infoName() == "label") ||
(deti->textFrom() == DynamicElementTextItem::CompositeText && deti->compositeText().contains("%{label")))
{
QString xref_label = xrp.slaveLabel();
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
if(!m_slave_Xref_item)
{
m_slave_Xref_item = new QGraphicsTextItem(xref_label, this);
ShownKinds::tag(m_slave_Xref_item, ShownKinds::CrossReferences);
m_slave_Xref_item->setFont(QETApp::diagramTextsFont(5));
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(project, &QETProject::projectDiagramsOrderChanged, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(project, &QETProject::diagramRemoved, this, &ElementTextItemGroup::updateXref);
m_update_slave_Xref_connection << connect(project, &QETProject::XRefPropertiesChanged, this, &ElementTextItemGroup::updateXref);
}
else
m_slave_Xref_item->setPlainText(xref_label);
adjustSlaveXrefPos(xrp.slaveOffset());
return;
}
}
}
}
//There is no reason to have a xref, we delete it if exist
if(m_Xref_item)
{
delete m_Xref_item;
m_Xref_item = nullptr;
}
m_update_slave_Xref_connection.clear();
if(m_slave_Xref_item)
{
delete m_slave_Xref_item;
m_slave_Xref_item = nullptr;
//If position changed to Champ de texte, store xref in element info
if(m_parent_element->linkType() == Element::Slave &&
!m_parent_element->linkedElements().isEmpty() &&
m_parent_element->diagram())
{
Element *master_elmt = m_parent_element->linkedElements().first();
if(master_elmt)
{
XRefProperties xrp = m_parent_element->diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
if(xrp.getXrefPos() == Qt::AlignHCenter)
{
QString xref_label = xrp.slaveLabel();
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
DiagramContext dc = m_parent_element->elementInformations();
if(dc.value("xref").toString() != xref_label)
{
dc.addValue("xref", xref_label);
m_parent_element->setElementInformations(dc);
}
}
}
}
}
//Remove stale "xref" from elementInformations when no longer needed
if(m_parent_element->linkType() == Element::Slave &&
m_parent_element->diagram())
{
for(DynamicElementTextItem *deti : texts())
{
if(deti->textFrom() == DynamicElementTextItem::ElementInfo && deti->infoName() == "xref")
{
DiagramContext dc = m_parent_element->elementInformations();
if(!dc.value("xref").toString().isEmpty())
{
dc.remove("xref");
m_parent_element->setElementInformations(dc);
}
break;
}
}
}
}
void ElementTextItemGroup::adjustSlaveXrefPos(int slave_offset)
{
QRectF r = boundingRect();
QPointF pos(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,
r.bottom() + slave_offset);
m_slave_Xref_item->setPos(pos);
}
void ElementTextItemGroup::autoPos()
{
int offset = 5;
if(m_parent_element->linkType() == Element::Master)
{
if(!diagram())
return;
MasterElement *master = static_cast<MasterElement *>(m_parent_element);
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master->kindInformations()["type"].toString());
if(xrp.snapTo() == XRefProperties::Bottom)
{
QRectF rectXref = master->XrefBoundingRect();
offset = xrp.offset() <= 40 ? 5 : xrp.offset();
offset += (int)rectXref.height();
}
}
qreal r = rotation();
QGIUtility::centerToBottomDiagram(this, m_parent_element, offset);
//centerToBottomDiagram change the rotation of this group if needed,
//but setRotation is not a virtual function of QGraphicsItem, and the function centerToBottomDiagram
//work with a QGraphicsItem. So we emit the signal if rotation changed
if(rotation() != r)
emit rotationChanged(rotation());
}