Files
qelectrotech-source-mirror/sources/qetgraphicsitem/qgraphicsitemutility.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

129 lines
4.2 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 "qgraphicsitemutility.h"
#include "../diagram.h"
#include "element.h"
#include <QDebug>
#include <QGraphicsItem>
namespace QGIUtility
{
/**
@brief centerToParentBottom
Center the item at the bottom of is parent.
@param item item to center
@return true if centered else false (item have not parent)
*/
bool centerToParentBottom(QGraphicsItem *item) {
if (! item->parentItem()) {
qDebug() << "Qet::centerToParentBottom : item have not parent";
return false;
}
QPointF p = item -> parentItem() -> boundingRect().center();
p.ry() += item -> parentItem() -> boundingRect().height()/2;
p.rx() -= (item -> boundingRect().width()/2 + item->boundingRect().left()); //< we add boundingrect.left because this value can be négative
item -> setPos(p);
return true;
}
/**
@brief centerToBottomDiagram
Set item pos to the bottom of diagram and centered vertically to element_to_follow, and add offset.
@param item_to_center
@param element_to_follow
@param offset
@return true if element is centered else false (element_to_follow have not diagram)
*/
bool centerToBottomDiagram (QGraphicsItem *item_to_center, Element *element_to_follow, qreal offset)
{
if (! element_to_follow -> diagram())
{
qDebug() << "qgraphicsitemutility centerAtBottomDiagram : Element_to_follow have not diagram";
return false;
}
QRectF border = element_to_follow -> diagram() -> border_and_titleblock.insideBorderRect();
QPointF point = element_to_follow -> sceneBoundingRect().center();
point.setY(border.bottom() - item_to_center -> boundingRect().height() - offset );
point.rx() -= (item_to_center -> boundingRect().width()/2);
//Apply the difference between the pos() of item and its bounding rect
QPointF tl = item_to_center->boundingRect().topLeft();
point.rx() -= tl.x();
point.ry() -= tl.y();
item_to_center -> setPos(0,0); //Due to a weird behavior or bug, before setting the new position and rotation,
item_to_center -> setRotation(0); //we must set the position and rotation to 0.
//Same for the mirror that keeps it readable on a mirrored element,
//given back below
item_to_center -> resetTransform();
item_to_center->setPos(item_to_center->mapFromScene(point));
qreal rot = item_to_center->rotation();
QGraphicsItem *parent = item_to_center->parentItem();
while (parent) {
rot += parent->rotation();
parent = parent->parentItem();
}
if(rot != 0) {
item_to_center->setRotation(item_to_center->rotation() - rot);
}
element_to_follow->keepReadable(item_to_center);
//On a mirrored element the item's box extends from its pos() the
//mirrored way, so it is not where the lines above meant. Put it
//there: centred on the element, its top at the same height.
if (element_to_follow->isMirrored() && item_to_center->parentItem())
{
const QRectF placed = item_to_center->sceneBoundingRect();
const QPointF wanted(
element_to_follow->sceneBoundingRect().center().x()
- placed.width() / 2,
border.bottom() - item_to_center->boundingRect().height() - offset);
QGraphicsItem *parent = item_to_center->parentItem();
item_to_center->setPos(parent->mapFromScene(
parent->mapToScene(item_to_center->pos())
+ wanted - placed.topLeft()));
}
return true;
}
void drawBoundingRectSelection(QGraphicsItem *item, QPainter *painter)
{
painter->save();
QPen t;
t.setColor(Qt::gray);
t.setStyle(Qt::DashDotLine);
t.setCosmetic(true);
painter->setPen(t);
painter->drawRoundedRect(item->boundingRect(),10,10);
painter->restore();
}
}