mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-08 13:04:13 +02:00
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>
This commit is contained in:
@@ -94,6 +94,40 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementPictureFactory::getMirroredPictures
|
||||
Same as getPictures(), for an element mirrored on its folio
|
||||
(Element::setMirror()). The drawing is the same; only the texts differ:
|
||||
each one is drawn so that, once the element's mirror is applied, its box
|
||||
lands mirrored but its letters still read normally.
|
||||
@param location
|
||||
@param horizontal : the element's left and right swap
|
||||
@param vertical : the element's top and bottom swap
|
||||
@param picture
|
||||
@param low_picture
|
||||
*/
|
||||
void ElementPictureFactory::getMirroredPictures(const ElementsLocation &location,
|
||||
bool horizontal,
|
||||
bool vertical,
|
||||
QPicture &picture,
|
||||
QPicture &low_picture)
|
||||
{
|
||||
if(!location.exist() || !(horizontal || vertical)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPair<QUuid, int> key(cacheKey(location),
|
||||
(horizontal ? 1 : 0) | (vertical ? 2 : 0));
|
||||
|
||||
if(!m_mirrored_pictures_H.contains(key)
|
||||
&& !build(location, nullptr, nullptr,
|
||||
QTransform::fromScale(horizontal ? -1 : 1, vertical ? -1 : 1))) {
|
||||
return;
|
||||
}
|
||||
picture = m_mirrored_pictures_H.value(key);
|
||||
low_picture = m_mirrored_low_pictures_H.value(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementPictureFactory::dropCache
|
||||
Forget the cached drawing of the element at @p location, so the next
|
||||
@@ -113,6 +147,10 @@ void ElementPictureFactory::dropCache(const ElementsLocation &location)
|
||||
const QUuid uuid = cacheKey(location);
|
||||
m_pictures_H.remove(uuid);
|
||||
m_low_pictures_H.remove(uuid);
|
||||
for (int mirror = 1 ; mirror <= 3 ; ++mirror) {
|
||||
m_mirrored_pictures_H.remove(qMakePair(uuid, mirror));
|
||||
m_mirrored_low_pictures_H.remove(qMakePair(uuid, mirror));
|
||||
}
|
||||
m_pixmap_H.remove(uuid);
|
||||
m_primitives_H.remove(uuid);
|
||||
}
|
||||
@@ -216,11 +254,16 @@ ElementPictureFactory::~ElementPictureFactory()
|
||||
this function draw on it and don't store it.
|
||||
if null, this function create a QPicture for normal and low zoom,
|
||||
draw on it and store it in m_pictures_H and m_low_pictures_H
|
||||
(m_mirrored_pictures_H and m_mirrored_low_pictures_H if @p mirror
|
||||
is not the identity)
|
||||
@param mirror : the mirror of an element to draw the drawing for, see
|
||||
getMirroredPictures()
|
||||
@return
|
||||
*/
|
||||
bool ElementPictureFactory::build(const ElementsLocation &location,
|
||||
QPicture *picture,
|
||||
QPicture *low_picture)
|
||||
QPicture *low_picture,
|
||||
const QTransform &mirror)
|
||||
{
|
||||
QDomElement dom = location.xml();
|
||||
|
||||
@@ -277,6 +320,8 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
||||
tmp.setCosmetic(true);
|
||||
low_painter.setPen(tmp);
|
||||
|
||||
m_build_mirror = mirror;
|
||||
|
||||
//scroll of the Children of the Definition: Parts of the Drawing
|
||||
// Extract PLC master data for rendering plc_table parts
|
||||
QDomElement plc_master_data;
|
||||
@@ -330,8 +375,20 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
||||
//End of the drawing
|
||||
painter.end();
|
||||
low_painter.end();
|
||||
m_build_mirror = QTransform();
|
||||
|
||||
const auto uuid_ = cacheKey(location);
|
||||
if (!mirror.isIdentity()) {
|
||||
const QPair<QUuid, int> key(uuid_, (mirror.m11() < 0 ? 1 : 0)
|
||||
| (mirror.m22() < 0 ? 2 : 0));
|
||||
if (!picture) {
|
||||
m_mirrored_pictures_H.insert(key, pic);
|
||||
}
|
||||
if (!low_picture) {
|
||||
m_mirrored_low_pictures_H.insert(key, low_pic);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!picture) {
|
||||
m_pictures_H.insert(uuid_, pic);
|
||||
m_primitives_H.insert(uuid_, primitives_);
|
||||
@@ -612,8 +669,6 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter,
|
||||
text_document.setPlainText(dom.attribute("text"));
|
||||
|
||||
painter.setTransform(QTransform(), false);
|
||||
painter.translate(dom.attribute("x").toDouble(), dom.attribute("y").toDouble());
|
||||
painter.rotate(dom.attribute("rotation", "0").toDouble());
|
||||
|
||||
/*
|
||||
Moves the QPainter's coordinate system to render in the right place;
|
||||
@@ -647,7 +702,25 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter,
|
||||
}
|
||||
}
|
||||
|
||||
painter.translate(qpainter_offset);
|
||||
QTransform text_transform;
|
||||
text_transform.translate(dom.attribute("x").toDouble(), dom.attribute("y").toDouble());
|
||||
text_transform.rotate(dom.attribute("rotation", "0").toDouble());
|
||||
text_transform.translate(qpainter_offset.x(), qpainter_offset.y());
|
||||
|
||||
if (!m_build_mirror.isIdentity())
|
||||
{
|
||||
//The element is mirrored about its own axes when it is drawn.
|
||||
//Mirror the text the same way first, about the centre of its
|
||||
//box: the two mirrors cancel on the letters, which read
|
||||
//normally, and the box still ends up where the element's
|
||||
//mirror puts it.
|
||||
const QRectF box(QPointF(0, 0), text_document.size());
|
||||
const QPointF centre = text_transform.mapRect(box).center();
|
||||
painter.setTransform(QTransform::fromTranslate(-centre.x(), -centre.y())
|
||||
* m_build_mirror
|
||||
* QTransform::fromTranslate(centre.x(), centre.y()));
|
||||
}
|
||||
painter.setTransform(text_transform, true);
|
||||
|
||||
// force the palette used to render the QTextDocument
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <QMutex>
|
||||
#include <QSharedPointer>
|
||||
#include <QHash>
|
||||
#include <QPair>
|
||||
#include <QTransform>
|
||||
|
||||
class ElementsLocation;
|
||||
class QPicture;
|
||||
@@ -83,6 +85,8 @@ class ElementPictureFactory
|
||||
}
|
||||
|
||||
void getPictures(const ElementsLocation &location, QPicture &picture, QPicture &low_picture);
|
||||
void getMirroredPictures(const ElementsLocation &location, bool horizontal, bool vertical,
|
||||
QPicture &picture, QPicture &low_picture);
|
||||
QPixmap pixmap(const ElementsLocation &location);
|
||||
ElementPictureFactory::primitives getPrimitives(const ElementsLocation &location);
|
||||
void dropCache(const ElementsLocation &location);
|
||||
@@ -94,7 +98,8 @@ class ElementPictureFactory
|
||||
~ElementPictureFactory();
|
||||
|
||||
static QUuid cacheKey(const ElementsLocation &location);
|
||||
bool build(const ElementsLocation &location, QPicture *picture=nullptr, QPicture *low_picture=nullptr);
|
||||
bool build(const ElementsLocation &location, QPicture *picture=nullptr, QPicture *low_picture=nullptr,
|
||||
const QTransform &mirror=QTransform());
|
||||
void parseElement(const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||
void parseLine (const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||
void parseRect (const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||
@@ -108,6 +113,12 @@ class ElementPictureFactory
|
||||
|
||||
QHash<QUuid, QPicture> m_pictures_H;
|
||||
QHash<QUuid, QPicture> m_low_pictures_H;
|
||||
/// Drawings for mirrored elements, by element and mirror
|
||||
/// (horizontal, vertical, both), see getMirroredPictures()
|
||||
QHash<QPair<QUuid, int>, QPicture> m_mirrored_pictures_H;
|
||||
QHash<QPair<QUuid, int>, QPicture> m_mirrored_low_pictures_H;
|
||||
/// The mirror build() draws for, read by parseText()
|
||||
QTransform m_build_mirror;
|
||||
QHash<QUuid, QPixmap> m_pixmap_H;
|
||||
QHash<QUuid, primitives> m_primitives_H;
|
||||
static ElementPictureFactory* m_factory;
|
||||
|
||||
Reference in New Issue
Block a user