mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-08 04:44:13 +02:00
a5b5d49b98
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>
1025 lines
30 KiB
C++
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());
|
|
}
|
|
|