mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-08 04:44: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:
+42
-7
@@ -37,6 +37,7 @@
|
||||
#include "qetgraphicsitem/terminal.h"
|
||||
#include "textlines.h"
|
||||
|
||||
#include <QFontMetricsF>
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QSet>
|
||||
#include <cmath>
|
||||
@@ -178,6 +179,10 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
foreach(Element *elmt, list_elements)
|
||||
{
|
||||
double rotation_angle = elmt -> orientation() * 90;
|
||||
//The mirrors of an element are about its own axes, before it is
|
||||
//rotated, see Element::setMirror()
|
||||
const qreal mirror_x = elmt -> hasHorizontalMirror() ? -1 : 1;
|
||||
const qreal mirror_y = elmt -> hasVerticalMirror() ? -1 : 1;
|
||||
|
||||
qreal elem_pos_x = elmt -> pos().x();
|
||||
qreal elem_pos_y = elmt -> pos().y();// - (diagram -> margin / 2);
|
||||
@@ -191,8 +196,26 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
if (fontSize < 0)
|
||||
fontSize = text->font().pixelSize();
|
||||
|
||||
qreal x = elem_pos_x + text->pos().x();
|
||||
qreal y = elem_pos_y + text->pos().y();
|
||||
QPointF text_pos = text->pos();
|
||||
if (elmt -> isMirrored())
|
||||
{
|
||||
//The text keeps reading normally: only its box is
|
||||
//mirrored, about its own centre, as ElementPictureFactory
|
||||
//draws it on the folio
|
||||
QTransform box_transform;
|
||||
box_transform.translate(text->pos().x(), text->pos().y());
|
||||
box_transform.rotate(text->rotation());
|
||||
const QFontMetricsF metrics(text->font());
|
||||
const QRectF box(0, -metrics.ascent(),
|
||||
text->boundingRect().width(),
|
||||
text->boundingRect().height());
|
||||
const QPointF centre = box_transform.mapRect(box).center();
|
||||
text_pos += QPointF(centre.x() * (mirror_x - 1),
|
||||
centre.y() * (mirror_y - 1));
|
||||
}
|
||||
|
||||
qreal x = elem_pos_x + text_pos.x();
|
||||
qreal y = elem_pos_y + text_pos.y();
|
||||
|
||||
qreal angle = text -> rotation() + rotation_angle;
|
||||
qreal angler = angle * M_PI/180;
|
||||
@@ -217,21 +240,21 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
Createdxf::layer = Layer::Symbols;
|
||||
for (QLineF line : primitives.m_lines)
|
||||
{
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle).scale(mirror_x, mirror_y);
|
||||
QLineF l = t.map(line);
|
||||
Createdxf::drawLine(file_path, l, 0);
|
||||
}
|
||||
|
||||
for (QRectF rect : primitives.m_rectangles)
|
||||
{
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle).scale(mirror_x, mirror_y);
|
||||
QRectF r = t.mapRect(rect);
|
||||
Createdxf::drawRectangle(file_path,r,0);
|
||||
}
|
||||
|
||||
for (QRectF circle_rect : primitives.m_circles)
|
||||
{
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle).scale(mirror_x, mirror_y);
|
||||
QPointF c = t.map(QPointF(circle_rect.center().x(),circle_rect.center().y()));
|
||||
Createdxf::drawCircle(file_path,c,circle_rect.width()/2,0);
|
||||
}
|
||||
@@ -240,7 +263,7 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
{
|
||||
if (polygon.size() == 0)
|
||||
continue;
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle).scale(mirror_x, mirror_y);
|
||||
QPolygonF poly = t.map(polygon);
|
||||
if(poly.isClosed())
|
||||
Createdxf::drawPolygon(file_path,poly,0);
|
||||
@@ -259,6 +282,18 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
qreal h = arc.at(3);
|
||||
qreal startAngle = arc.at(4);
|
||||
qreal spanAngle = arc .at(5);
|
||||
//In a mirror the arc starts where it ended: an angle a
|
||||
//becomes 180 - a in a horizontal one, -a in a vertical one
|
||||
if (elmt -> hasHorizontalMirror())
|
||||
{
|
||||
x = elem_pos_x - arc.at(0) - w;
|
||||
startAngle = 180 - startAngle - spanAngle;
|
||||
}
|
||||
if (elmt -> hasVerticalMirror())
|
||||
{
|
||||
y = elem_pos_y - arc.at(1) - h;
|
||||
startAngle = -startAngle - spanAngle;
|
||||
}
|
||||
QRectF r(x,y,w,h);
|
||||
QPointF hotspot(elem_pos_x,elem_pos_y);
|
||||
Createdxf::drawArcEllipse(file_path, r, startAngle, spanAngle, hotspot, rotation_angle, 0);
|
||||
@@ -268,7 +303,7 @@ void DxfExport::write(Diagram *diagram, int width, int height,
|
||||
// Draw terminals
|
||||
QList<Terminal *> list_terminals = elmt->terminals();
|
||||
QColor col("red");
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
|
||||
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle).scale(mirror_x, mirror_y);
|
||||
foreach(Terminal *tp, list_terminals) {
|
||||
QPointF c = t.map(QPointF(tp->dock_elmt_.x(),tp->dock_elmt_.y()));
|
||||
Createdxf::drawCircle(file_path,c,3.0,Createdxf::dxfColor(col));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
#include "utils/qetutils.h"
|
||||
#include "undocommand/groupitemscommand.h"
|
||||
#include "undocommand/alignselectioncommand.h"
|
||||
#include "undocommand/mirrorselectioncommand.h"
|
||||
#include "undocommand/rotateselectioncommand.h"
|
||||
#include "undocommand/rotatetextscommand.h"
|
||||
#include "diagram.h"
|
||||
@@ -978,6 +979,8 @@ void QETDiagramEditor::setUpActions()
|
||||
m_rotate_selection = m_selection_actions_group.addAction( QET::Icons::TransformRotate, tr("Pivoter") );
|
||||
m_rotate_group_selection = m_selection_actions_group.addAction( QET::Icons::TransformRotate, tr("Pivoter le groupe") );
|
||||
m_rotate_texts = m_selection_actions_group.addAction( QET::Icons::ObjectRotateRight, tr("Orienter les textes") );
|
||||
m_mirror_horizontal = m_selection_actions_group.addAction( QET::Icons::ImageFlipHorizontal, tr("Miroir horizontal") );
|
||||
m_mirror_vertical = m_selection_actions_group.addAction( QET::Icons::ImageFlipVertical, tr("Miroir vertical") );
|
||||
m_find_element = m_selection_actions_group.addAction( QET::Icons::ZoomDraw, tr("Retrouver dans le panel") );
|
||||
m_edit_selection = m_selection_actions_group.addAction( QET::Icons::ElementEdit, tr("Éditer l'item sélectionné") );
|
||||
m_group_selected_texts = m_selection_actions_group.addAction( QET::Icons::textGroup, tr("Grouper les textes sélectionnés"));
|
||||
@@ -988,6 +991,9 @@ void QETDiagramEditor::setUpActions()
|
||||
ShortcutManager::instance().registerAction(m_rotate_selection, "diagrameditor.rotate_selection", tr("Éditeur de schémas"), Qt::Key_Space);
|
||||
ShortcutManager::instance().registerAction(m_rotate_group_selection, "diagrameditor.rotate_group_selection", tr("Éditeur de schémas"), Qt::SHIFT | Qt::Key_Space);
|
||||
ShortcutManager::instance().registerAction(m_rotate_texts, "diagrameditor.rotate_texts", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_Space);
|
||||
//M and F, the keys of the same two actions in the element editor
|
||||
ShortcutManager::instance().registerAction(m_mirror_horizontal, "diagrameditor.mirror_horizontal", tr("Éditeur de schémas"), Qt::Key_M);
|
||||
ShortcutManager::instance().registerAction(m_mirror_vertical, "diagrameditor.mirror_vertical", tr("Éditeur de schémas"), Qt::Key_F);
|
||||
ShortcutManager::instance().registerAction(m_find_element, "diagrameditor.find_element", tr("Éditeur de schémas"), QKeySequence());
|
||||
ShortcutManager::instance().registerAction(m_group_selected_texts, "diagrameditor.group_selected_texts", tr("Éditeur de schémas"), QKeySequence());
|
||||
ShortcutManager::instance().registerAction(m_edit_selection, "diagrameditor.edit_selection", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_E);
|
||||
@@ -1081,12 +1087,16 @@ void QETDiagramEditor::setUpActions()
|
||||
m_rotate_selection->setStatusTip( tr("Pivote les éléments et textes sélectionnés", "status bar tip"));
|
||||
m_rotate_group_selection->setStatusTip( tr("Pivote la sélection comme un groupe autour de son centre, au lieu de chaque élément sur place", "status bar tip"));
|
||||
m_rotate_texts ->setStatusTip( tr("Pivote les textes sélectionnés à un angle précis", "status bar tip"));
|
||||
m_mirror_horizontal->setStatusTip( tr("Retourne les éléments sélectionnés de gauche à droite", "status bar tip"));
|
||||
m_mirror_vertical ->setStatusTip( tr("Retourne les éléments sélectionnés de haut en bas", "status bar tip"));
|
||||
m_find_element ->setStatusTip( tr("Retrouve l'élément sélectionné dans le panel", "status bar tip"));
|
||||
|
||||
m_delete_selection ->setData("delete_selection");
|
||||
m_rotate_selection ->setData("rotate_selection");
|
||||
m_rotate_group_selection->setData("rotate_group_selection");
|
||||
m_rotate_texts ->setData("rotate_selected_text");
|
||||
m_mirror_horizontal ->setData("mirror_horizontal");
|
||||
m_mirror_vertical ->setData("mirror_vertical");
|
||||
m_find_element ->setData("find_selected_element");
|
||||
m_edit_selection ->setData("edit_selected_element");
|
||||
m_group_selected_texts->setData("group_selected_texts");
|
||||
@@ -2333,6 +2343,16 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
|
||||
if(c->isValid())
|
||||
diagram->undoStack().push(c);
|
||||
}
|
||||
else if (value == "mirror_horizontal" || value == "mirror_vertical")
|
||||
{
|
||||
auto *c = new MirrorSelectionCommand(
|
||||
diagram,
|
||||
value == "mirror_horizontal" ? Qt::Horizontal : Qt::Vertical);
|
||||
if (c->isValid())
|
||||
diagram->undoStack().push(c);
|
||||
else
|
||||
delete c;
|
||||
}
|
||||
else if (value == "rotate_group_selection")
|
||||
{
|
||||
RotateSelectionCommand *c = new RotateSelectionCommand(diagram, 90, nullptr, true);
|
||||
@@ -2570,6 +2590,8 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
<< m_delete_selection
|
||||
<< m_rotate_selection
|
||||
<< m_rotate_group_selection
|
||||
<< m_mirror_horizontal
|
||||
<< m_mirror_vertical
|
||||
<< m_edit_selection
|
||||
<< m_group_selected_texts
|
||||
<< m_group_selection
|
||||
@@ -2593,6 +2615,8 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
// number of selected elements
|
||||
int selected_elements_count = dc.count(DiagramContent::Elements);
|
||||
m_find_element->setEnabled(selected_elements_count == 1);
|
||||
m_mirror_horizontal->setEnabled(!ro && selected_elements_count);
|
||||
m_mirror_vertical->setEnabled(!ro && selected_elements_count);
|
||||
|
||||
//Actions that need items (elements, conductors, texts...) selected, to be enabled
|
||||
bool copiable_items = dc.hasCopiableItems();
|
||||
|
||||
@@ -270,6 +270,8 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_rotate_selection, ///< Rotate selected elements and text items by 90 degrees
|
||||
*m_rotate_group_selection = nullptr, ///< Rotate the selection as a whole around its shared center, instead of each item in place
|
||||
*m_rotate_texts, ///< Direct selected text items to a specific angle
|
||||
*m_mirror_horizontal = nullptr, ///< Mirror the selected elements, left and right swap (#1335)
|
||||
*m_mirror_vertical = nullptr, ///< Mirror the selected elements, top and bottom swap (#1335)
|
||||
*m_find_element, ///< Find the selected element in the panel
|
||||
*m_group_selected_texts = nullptr,
|
||||
*m_group_selection = nullptr, ///< Group the selected items (#1070)
|
||||
|
||||
@@ -124,6 +124,7 @@ void CrossRefItem::setUpConnection()
|
||||
m_update_connection << connect(m_element, &Element::xChanged, this, &CrossRefItem::autoPos);
|
||||
m_update_connection << connect(m_element, &Element::yChanged, this, &CrossRefItem::autoPos);
|
||||
m_update_connection << connect(m_element, &Element::rotationChanged, this, &CrossRefItem::autoPos);
|
||||
m_update_connection << connect(m_element, &Element::mirrorChanged, this, &CrossRefItem::autoPos);
|
||||
set=true;
|
||||
}
|
||||
// For PLC masters, always set up connections for update notifications
|
||||
@@ -288,6 +289,7 @@ void CrossRefItem::updateLabel()
|
||||
QList<QPointF> positions = m_element->plcTablePositions();
|
||||
QPointF pos = positions.isEmpty() ? QPointF(0, 0) : positions.first();
|
||||
setPos(pos);
|
||||
m_element->keepReadable(this);
|
||||
|
||||
// Populate m_hovered_contacts_map using drawAsPlcTable on a
|
||||
// dummy painter (m_update_map=true).
|
||||
@@ -338,6 +340,7 @@ void CrossRefItem::autoPos()
|
||||
stackAtBottom();
|
||||
else
|
||||
QGIUtility::centerToParentBottom(this);
|
||||
m_element->keepReadable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,9 +412,12 @@ void CrossRefItem::stackAtBottom()
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
//From pos(), not scenePos(): on a mirrored element scenePos()
|
||||
//also holds the mirror that keeps the cross readable
|
||||
if (self.rect.bottom() != bottom)
|
||||
xref->setPos(xref->parentItem()->mapFromScene(
|
||||
xref->scenePos() + QPointF(0, self.rect.bottom() - bottom)));
|
||||
xref->parentItem()->mapToScene(xref->pos())
|
||||
+ QPointF(0, self.rect.bottom() - bottom)));
|
||||
placed << self;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,12 @@
|
||||
#include "../ui/elementpropertieswidget.h"
|
||||
#include "../undocommand/changeelementinformationcommand.h"
|
||||
#include "../undocommand/setautonumcontextcommand.h"
|
||||
#include "crossrefitem.h"
|
||||
#include "dynamicelementtextitem.h"
|
||||
#include "elementtextitemgroup.h"
|
||||
#include "iostream"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QApplication>
|
||||
#include <QCollator>
|
||||
#include <QScreen>
|
||||
@@ -161,6 +163,10 @@ Element::Element(
|
||||
connect(this, &Element::rotationChanged, update_docked_conductors);
|
||||
connect(this, &Element::xChanged, update_docked_conductors);
|
||||
connect(this, &Element::yChanged, update_docked_conductors);
|
||||
connect(this, &Element::mirrorChanged, update_docked_conductors);
|
||||
//The mirror is about the element's own axis, so it follows the
|
||||
//rotation, see applyMirrorTransform()
|
||||
connect(this, &Element::rotationChanged, this, &Element::applyMirrorTransform);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,6 +257,152 @@ void Element::displayHelpLine(bool b)
|
||||
t->drawHelpLine(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::setHorizontalMirror
|
||||
Mirror this element about its own vertical axis (its left and right
|
||||
swap), or put it back. See setMirror().
|
||||
@param mirror
|
||||
*/
|
||||
void Element::setHorizontalMirror(bool mirror)
|
||||
{
|
||||
setMirror(mirror, m_vertical_mirror);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::setVerticalMirror
|
||||
Mirror this element about its own horizontal axis (its top and bottom
|
||||
swap), or put it back. See setMirror().
|
||||
@param mirror
|
||||
*/
|
||||
void Element::setVerticalMirror(bool mirror)
|
||||
{
|
||||
setMirror(m_horizontal_mirror, mirror);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::setMirror
|
||||
The mirrors are about the element's own axes, through its hotspot, and
|
||||
are applied before the rotation: a mirrored element still turns
|
||||
clockwise with "Pivoter", and orientation() keeps its meaning. On the
|
||||
folio, a mirror of a turned element is the other mirror of the element
|
||||
itself, see MirrorSelectionCommand.
|
||||
The texts of the element, its cross reference and the texts drawn in
|
||||
its symbol are moved to their mirrored place but keep reading normally.
|
||||
@param horizontal : the left and right of the element swap
|
||||
@param vertical : the top and bottom of the element swap
|
||||
*/
|
||||
void Element::setMirror(bool horizontal, bool vertical)
|
||||
{
|
||||
if (horizontal == m_horizontal_mirror && vertical == m_vertical_mirror)
|
||||
return;
|
||||
|
||||
m_horizontal_mirror = horizontal;
|
||||
m_vertical_mirror = vertical;
|
||||
|
||||
m_mirrored_picture = QPicture();
|
||||
m_mirrored_low_zoom_picture = QPicture();
|
||||
if (isMirrored())
|
||||
ElementPictureFactory::instance()->getMirroredPictures(
|
||||
m_location,
|
||||
horizontal,
|
||||
vertical,
|
||||
m_mirrored_picture,
|
||||
m_mirrored_low_zoom_picture);
|
||||
|
||||
applyMirrorTransform();
|
||||
keepChildrenReadable();
|
||||
update();
|
||||
emit mirrorChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::mirrorTransform
|
||||
@return the mirrors of this element, about its own axes
|
||||
*/
|
||||
QTransform Element::mirrorTransform() const
|
||||
{
|
||||
return QTransform::fromScale(m_horizontal_mirror ? -1 : 1,
|
||||
m_vertical_mirror ? -1 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::applyMirrorTransform
|
||||
QGraphicsItem applies transform() after the rotation, so the mirrors of
|
||||
the element's own axes (S, before rotating) are given here as the
|
||||
mirrors about the rotated axes: R . S . R^-1, which is then applied
|
||||
after R.
|
||||
*/
|
||||
void Element::applyMirrorTransform()
|
||||
{
|
||||
if (!isMirrored())
|
||||
{
|
||||
if (!transform().isIdentity())
|
||||
setTransform(QTransform());
|
||||
return;
|
||||
}
|
||||
|
||||
//QTransform's product applies its left operand first
|
||||
setTransform(QTransform().rotate(-rotation())
|
||||
* mirrorTransform()
|
||||
* QTransform().rotate(rotation()));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::keepReadable
|
||||
Keep @p child, a text, a group of texts or a cross reference of this
|
||||
element, reading normally when this element is mirrored.
|
||||
The child is mirrored a second time, the same way, about the centre of
|
||||
its own box: the box keeps its place in the element, so it lands
|
||||
mirrored with the element, but the two mirrors cancel on what is drawn
|
||||
inside it.
|
||||
Does nothing for an item that is not a direct child of this element.
|
||||
@param child
|
||||
*/
|
||||
void Element::keepReadable(QGraphicsItem *child) const
|
||||
{
|
||||
if (!child || child->parentItem() != this)
|
||||
return;
|
||||
|
||||
if (!isMirrored())
|
||||
{
|
||||
if (!child->transform().isIdentity())
|
||||
child->resetTransform();
|
||||
return;
|
||||
}
|
||||
|
||||
//The rotation part of the child's own transformation, which
|
||||
//QGraphicsItem applies before transform()
|
||||
const QPointF origin = child->transformOriginPoint();
|
||||
QTransform rotation;
|
||||
rotation.translate(origin.x(), origin.y());
|
||||
rotation.rotate(child->rotation());
|
||||
rotation.translate(-origin.x(), -origin.y());
|
||||
const QPointF centre = rotation.mapRect(child->boundingRect()).center();
|
||||
|
||||
const QTransform mirror = QTransform::fromTranslate(-centre.x(), -centre.y())
|
||||
* mirrorTransform()
|
||||
* QTransform::fromTranslate(centre.x(), centre.y());
|
||||
if (child->transform() != mirror)
|
||||
child->setTransform(mirror);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::keepChildrenReadable
|
||||
keepReadable() for every text, group of texts and cross reference
|
||||
which is a direct child of this element.
|
||||
*/
|
||||
void Element::keepChildrenReadable() const
|
||||
{
|
||||
const QList<QGraphicsItem *> children = childItems();
|
||||
for (QGraphicsItem *child : children)
|
||||
{
|
||||
if (child->type() == DynamicElementTextItem::Type
|
||||
|| child->type() == CrossRefItem::Type
|
||||
|| dynamic_cast<ElementTextItemGroup *>(child))
|
||||
keepReadable(child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::paint
|
||||
@param painter
|
||||
@@ -275,13 +427,16 @@ void Element::paint(
|
||||
QBrush brush;
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(brush);
|
||||
const QPicture &picture = isMirrored() ? m_mirrored_picture : m_picture;
|
||||
const QPicture &low_zoom_picture = isMirrored() ? m_mirrored_low_zoom_picture
|
||||
: m_low_zoom_picture;
|
||||
if (options && options->levelOfDetailFromTransform(painter->worldTransform()) < 0.5)
|
||||
{
|
||||
if (!m_low_zoom_picture.isNull())
|
||||
painter->drawPicture(0, 0, m_low_zoom_picture);
|
||||
if (!low_zoom_picture.isNull())
|
||||
painter->drawPicture(0, 0, low_zoom_picture);
|
||||
} else {
|
||||
if (!m_picture.isNull())
|
||||
painter->drawPicture(0, 0, m_picture);
|
||||
if (!picture.isNull())
|
||||
painter->drawPicture(0, 0, picture);
|
||||
}
|
||||
|
||||
painter->restore(); //Restore the QPainter after use drawPicture
|
||||
@@ -938,6 +1093,11 @@ bool Element::fromXml(QDomElement &e,
|
||||
for(DynamicElementTextItem *deti : m_dynamic_text_list)
|
||||
deti->m_block_alignment = false;
|
||||
|
||||
//Last, so that the texts and groups loaded above are kept readable
|
||||
const QString mirror = e.attribute(QStringLiteral("mirror"));
|
||||
setMirror(mirror == QLatin1String("horizontal") || mirror == QLatin1String("both"),
|
||||
mirror == QLatin1String("vertical") || mirror == QLatin1String("both"));
|
||||
|
||||
m_state = QET::GIOK;
|
||||
return(true);
|
||||
}
|
||||
@@ -987,6 +1147,13 @@ QDomElement Element::toXml(
|
||||
element.setAttribute(QStringLiteral("y"), QString::number(pos().y()));
|
||||
element.setAttribute(QStringLiteral("z"), QString::number(this->zValue()));
|
||||
element.setAttribute(QStringLiteral("orientation"), QString::number(orientation()));
|
||||
//Written only when set, so a project without a mirrored element
|
||||
//saves exactly as before
|
||||
if (isMirrored())
|
||||
element.setAttribute(QStringLiteral("mirror"),
|
||||
!m_vertical_mirror ? QStringLiteral("horizontal")
|
||||
: !m_horizontal_mirror ? QStringLiteral("vertical")
|
||||
: QStringLiteral("both"));
|
||||
element.setAttribute(QStringLiteral("is_movable"), bool(is_movable_));
|
||||
|
||||
/* get the first id to use for the bounds of this element
|
||||
@@ -1133,18 +1300,25 @@ QDomElement Element::toXml(
|
||||
*/
|
||||
void Element::addDynamicTextItem(DynamicElementTextItem *deti)
|
||||
{
|
||||
if (deti && !m_dynamic_text_list.contains(deti))
|
||||
{
|
||||
m_dynamic_text_list.append(deti);
|
||||
deti->setParentItem(this);
|
||||
emit textAdded(deti);
|
||||
}
|
||||
if (!deti || m_dynamic_text_list.contains(deti))
|
||||
deti = new DynamicElementTextItem(this);
|
||||
else
|
||||
{
|
||||
DynamicElementTextItem *text = new DynamicElementTextItem(this);
|
||||
m_dynamic_text_list.append(text);
|
||||
emit textAdded(text);
|
||||
}
|
||||
deti->setParentItem(this);
|
||||
|
||||
m_dynamic_text_list.append(deti);
|
||||
|
||||
//On a mirrored element, the mirror of the text depends on the size
|
||||
//and the rotation of the text, see keepReadable()
|
||||
connect(deti->document()->documentLayout(),
|
||||
&QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
this, [this, deti]() {keepReadable(deti);});
|
||||
connect(deti, &DynamicElementTextItem::rotationChanged,
|
||||
this, [this, deti]() {keepReadable(deti);});
|
||||
connect(deti, &DynamicElementTextItem::rotationPointCenterChanged,
|
||||
this, [this, deti]() {keepReadable(deti);});
|
||||
keepReadable(deti);
|
||||
|
||||
emit textAdded(deti);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1156,10 +1330,18 @@ void Element::addDynamicTextItem(DynamicElementTextItem *deti)
|
||||
*/
|
||||
void Element::removeDynamicTextItem(DynamicElementTextItem *deti)
|
||||
{
|
||||
//Undo the connections and the mirror of addDynamicTextItem()
|
||||
auto forget = [this, deti]() {
|
||||
disconnect(deti->document()->documentLayout(), nullptr, this, nullptr);
|
||||
disconnect(deti, nullptr, this, nullptr);
|
||||
deti->resetTransform();
|
||||
};
|
||||
|
||||
if (m_dynamic_text_list.contains(deti))
|
||||
{
|
||||
m_dynamic_text_list.removeOne(deti);
|
||||
deti->setParentItem(nullptr);
|
||||
forget();
|
||||
emit textRemoved(deti);
|
||||
return;
|
||||
}
|
||||
@@ -1171,6 +1353,7 @@ void Element::removeDynamicTextItem(DynamicElementTextItem *deti)
|
||||
removeTextFromGroup(deti, group);
|
||||
m_dynamic_text_list.removeOne(deti);
|
||||
deti->setParentItem(nullptr);
|
||||
forget();
|
||||
emit textRemoved(deti);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ class Element : public QetGraphicsItem
|
||||
friend class DynamicElementTextItem;
|
||||
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool horizontalMirror READ hasHorizontalMirror WRITE setHorizontalMirror NOTIFY mirrorChanged)
|
||||
Q_PROPERTY(bool verticalMirror READ hasVerticalMirror WRITE setVerticalMirror NOTIFY mirrorChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
@brief The kind enum
|
||||
@@ -111,6 +114,7 @@ class Element : public QetGraphicsItem
|
||||
void textRemovedFromGroup(
|
||||
DynamicElementTextItem *text,
|
||||
ElementTextItemGroup *group);
|
||||
void mirrorChanged();
|
||||
|
||||
public slots:
|
||||
void updateConductorTexts();
|
||||
@@ -185,6 +189,13 @@ class Element : public QetGraphicsItem
|
||||
int> &) const;
|
||||
QUuid uuid() const;
|
||||
int orientation() const;
|
||||
bool hasHorizontalMirror() const {return m_horizontal_mirror;}
|
||||
bool hasVerticalMirror() const {return m_vertical_mirror;}
|
||||
bool isMirrored() const {return m_horizontal_mirror || m_vertical_mirror;}
|
||||
void setHorizontalMirror(bool mirror);
|
||||
void setVerticalMirror(bool mirror);
|
||||
QTransform mirrorTransform() const;
|
||||
void keepReadable(QGraphicsItem *child) const;
|
||||
|
||||
//METHODS related to texts
|
||||
void addDynamicTextItem(DynamicElementTextItem *deti = nullptr);
|
||||
@@ -296,10 +307,15 @@ class Element : public QetGraphicsItem
|
||||
QList <Terminal *> m_terminals;
|
||||
QPicture m_picture;
|
||||
QPicture m_low_zoom_picture;
|
||||
QPicture m_mirrored_picture;
|
||||
QPicture m_mirrored_low_zoom_picture;
|
||||
ElementData m_data;
|
||||
QList<QPointF> m_plc_table_positions; // Positions of plc_table parts in the element definition
|
||||
|
||||
void drawPlcTable(QPainter *painter);
|
||||
void setMirror(bool horizontal, bool vertical);
|
||||
void applyMirrorTransform();
|
||||
void keepChildrenReadable() const;
|
||||
|
||||
public:
|
||||
/// Positions where the PLC IO table is drawn (from the .elmt file).
|
||||
@@ -310,6 +326,8 @@ class Element : public QetGraphicsItem
|
||||
QSize dimensions;
|
||||
QPoint hotspot_coord;
|
||||
bool m_mouse_over = false;
|
||||
bool m_horizontal_mirror = false;
|
||||
bool m_vertical_mirror = false;
|
||||
QString m_prefix;
|
||||
QList <DynamicElementTextItem *> m_dynamic_text_list;
|
||||
QList <ElementTextItemGroup *> m_texts_group;
|
||||
|
||||
@@ -119,6 +119,12 @@ void ElementTextItemGroup::addToGroup(QGraphicsItem *item)
|
||||
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();
|
||||
|
||||
@@ -153,6 +159,7 @@ void ElementTextItemGroup::addToGroup(QGraphicsItem *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°
|
||||
@@ -162,6 +169,8 @@ void ElementTextItemGroup::removeFromGroup(QGraphicsItem *item)
|
||||
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))
|
||||
@@ -232,6 +241,10 @@ void ElementTextItemGroup::updateAlignment()
|
||||
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();
|
||||
|
||||
@@ -302,6 +315,8 @@ void ElementTextItemGroup::updateAlignment()
|
||||
|
||||
//Restore the rotation
|
||||
setRotation(rotation_);
|
||||
if (m_parent_element)
|
||||
m_parent_element->keepReadable(this);
|
||||
|
||||
if(m_Xref_item)
|
||||
m_Xref_item->autoPos();
|
||||
@@ -357,6 +372,7 @@ void ElementTextItemGroup::setHoldToBottomPage(bool hold)
|
||||
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
|
||||
@@ -396,6 +412,8 @@ void ElementTextItemGroup::setHoldToBottomPage(bool hold)
|
||||
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);
|
||||
|
||||
@@ -75,6 +75,9 @@ bool centerToBottomDiagram (QGraphicsItem *item_to_center, Element *element_to_f
|
||||
|
||||
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));
|
||||
|
||||
@@ -87,6 +90,23 @@ bool centerToBottomDiagram (QGraphicsItem *item_to_center, Element *element_to_f
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -118,13 +118,20 @@ Qet::Orientation Terminal::orientation() const
|
||||
// orientations actuelle et par defaut de l'element
|
||||
// current and default element orientations
|
||||
int ori_cur = elt -> orientation();
|
||||
if (ori_cur == 0) return(d->m_orientation);
|
||||
// The mirrors of an element are about its own axes, before it is
|
||||
// rotated (Element::setMirror()): a horizontal one swaps east and
|
||||
// west, a vertical one north and south
|
||||
Qet::Orientation ori_elmt = d->m_orientation;
|
||||
if (Qet::isHorizontal(ori_elmt) ? elt -> hasHorizontalMirror()
|
||||
: elt -> hasVerticalMirror())
|
||||
ori_elmt = (Qet::Orientation)((ori_elmt + 2) % 4);
|
||||
if (ori_cur == 0) return(ori_elmt);
|
||||
else {
|
||||
// calcul l'angle de rotation implique par l'orientation de l'element parent
|
||||
// angle de rotation de la borne sur la scene, divise par 90
|
||||
// calculates the angle of rotation implied by the orientation of the parent
|
||||
// element angle of rotation of the terminal on the scene, divided by 90
|
||||
int angle = ori_cur + d->m_orientation;
|
||||
int angle = ori_cur + ori_elmt;
|
||||
while (angle >= 4) angle -= 4;
|
||||
return((Qet::Orientation)angle);
|
||||
}
|
||||
|
||||
@@ -550,6 +550,8 @@ QStringList LiveServer::allowedCommands()
|
||||
QStringLiteral("diagrameditor.zoom_fit"),
|
||||
QStringLiteral("diagrameditor.zoom_reset"),
|
||||
QStringLiteral("diagrameditor.rotate_selection"),
|
||||
QStringLiteral("diagrameditor.mirror_horizontal"),
|
||||
QStringLiteral("diagrameditor.mirror_vertical"),
|
||||
QStringLiteral("diagrameditor.rotate_texts"),
|
||||
QStringLiteral("diagrameditor.snap_selection_to_grid"),
|
||||
QStringLiteral("diagrameditor.group_selection"),
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#include "../qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "../qetgraphicsitem/independenttextitem.h"
|
||||
#include "../qetgraphicsitem/qetshapeitem.h"
|
||||
#include "../undocommand/mirrorselectioncommand.h"
|
||||
#include "../undocommand/promoteshapecommand.h"
|
||||
#include "../TerminalStrip/UndoCommand/addterminalstripcommand.h"
|
||||
#include "../TerminalStrip/UndoCommand/addterminaltostripcommand.h"
|
||||
@@ -833,6 +834,43 @@ bool QetScriptApi::rotateElement(int folioIndex, const QString &elementUuid, dou
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QetScriptApi::mirrorElement
|
||||
Mirror an element in place, as "Miroir horizontal" (left and right
|
||||
swap) or, with @p vertical, "Miroir vertical" (top and bottom swap)
|
||||
do on a selection. Mirroring twice the same way puts it back.
|
||||
@return false if the element is not found or the project is read only
|
||||
*/
|
||||
bool QetScriptApi::mirrorElement(int folioIndex, const QString &elementUuid, bool vertical)
|
||||
{
|
||||
if (m_project && m_project->isReadOnly()) {
|
||||
log(QStringLiteral("qet.mirrorElement: project is read-only"));
|
||||
return false;
|
||||
}
|
||||
Element *element = findElement(folioIndex, elementUuid);
|
||||
if (!element) return false;
|
||||
|
||||
m_project->undoStack()->push(new MirrorSelectionCommand(
|
||||
{element}, vertical ? Qt::Vertical : Qt::Horizontal));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QetScriptApi::elementMirror
|
||||
@return the mirrors of the element about its own axes, as saved in the
|
||||
project (Element::setMirror()): "horizontal", "vertical", "both", or ""
|
||||
if it is not mirrored or not found. On a turned element, a horizontal
|
||||
mirror of the folio is a vertical mirror of the element.
|
||||
*/
|
||||
QString QetScriptApi::elementMirror(int folioIndex, const QString &elementUuid) const
|
||||
{
|
||||
const Element *element = findElement(folioIndex, elementUuid);
|
||||
if (!element || !element->isMirrored()) return QString();
|
||||
if (!element->hasVerticalMirror()) return QStringLiteral("horizontal");
|
||||
if (!element->hasHorizontalMirror()) return QStringLiteral("vertical");
|
||||
return QStringLiteral("both");
|
||||
}
|
||||
|
||||
QStringList QetScriptApi::elementUuids(int folioIndex) const
|
||||
{
|
||||
QStringList uuids;
|
||||
|
||||
@@ -398,6 +398,8 @@ class QetScriptApi : public QObject
|
||||
Q_INVOKABLE bool setElementPosition(int folioIndex, const QString &elementUuid, double x, double y);
|
||||
Q_INVOKABLE bool moveElement(int folioIndex, const QString &elementUuid, double dx, double dy);
|
||||
Q_INVOKABLE bool rotateElement(int folioIndex, const QString &elementUuid, double angle);
|
||||
Q_INVOKABLE bool mirrorElement(int folioIndex, const QString &elementUuid, bool vertical = false);
|
||||
Q_INVOKABLE QString elementMirror(int folioIndex, const QString &elementUuid) const;
|
||||
Q_INVOKABLE bool deleteElement(int folioIndex, const QString &elementUuid);
|
||||
|
||||
// -- address what is already there --
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 "mirrorselectioncommand.h"
|
||||
|
||||
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
|
||||
/**
|
||||
@brief MirrorSelectionCommand::MirrorSelectionCommand
|
||||
Mirror the elements selected in @p diagram; the other selected items
|
||||
are left as they are. See the other constructor.
|
||||
@param diagram : the diagram whose selection is mirrored
|
||||
@param orientation : Qt::Horizontal swaps left and right,
|
||||
Qt::Vertical swaps top and bottom
|
||||
@param parent : parent undo command
|
||||
*/
|
||||
MirrorSelectionCommand::MirrorSelectionCommand(Diagram *diagram,
|
||||
Qt::Orientation orientation,
|
||||
QUndoCommand *parent) :
|
||||
MirrorSelectionCommand(selectedElements(diagram), orientation, parent)
|
||||
{}
|
||||
|
||||
/**
|
||||
@brief MirrorSelectionCommand::MirrorSelectionCommand
|
||||
The mirrors of an element are about its own axes, before it is rotated
|
||||
(Element::setMirror()). Turned by 0 or 180 degrees, the element's axes
|
||||
are the folio's, so a horizontal mirror on the folio is the element's
|
||||
own horizontal mirror; turned by 90 or 270 degrees, they are swapped,
|
||||
so it is the element's vertical mirror. The rotation never changes.
|
||||
Each element is mirrored in place: its centre stays where it was.
|
||||
@param elements : the elements to mirror
|
||||
@param orientation : Qt::Horizontal swaps left and right on the folio,
|
||||
Qt::Vertical swaps top and bottom
|
||||
@param parent : parent undo command
|
||||
*/
|
||||
MirrorSelectionCommand::MirrorSelectionCommand(const QList<Element *> &elements,
|
||||
Qt::Orientation orientation,
|
||||
QUndoCommand *parent) :
|
||||
QUndoCommand(parent)
|
||||
{
|
||||
setText(orientation == Qt::Horizontal
|
||||
? QObject::tr("Miroir horizontal")
|
||||
: QObject::tr("Miroir vertical"));
|
||||
|
||||
for (Element *element : elements)
|
||||
{
|
||||
if (!element)
|
||||
continue;
|
||||
|
||||
const bool turned = element->orientation() % 2;
|
||||
const bool horizontal = (orientation == Qt::Horizontal) != turned;
|
||||
|
||||
bool new_horizontal = element->hasHorizontalMirror();
|
||||
bool new_vertical = element->hasVerticalMirror();
|
||||
if (horizontal)
|
||||
{
|
||||
new_horizontal = !new_horizontal;
|
||||
new QPropertyUndoCommand(element, "horizontalMirror",
|
||||
!new_horizontal, new_horizontal, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
new_vertical = !new_vertical;
|
||||
new QPropertyUndoCommand(element, "verticalMirror",
|
||||
!new_vertical, new_vertical, this);
|
||||
}
|
||||
|
||||
//The mirror is about the element's hotspot, which is often a
|
||||
//corner of the symbol: move it so that it stays where it was,
|
||||
//centre on centre, and on the grid
|
||||
QPointF centre = element->boundingRect().center();
|
||||
centre = QTransform::fromScale(new_horizontal ? -1 : 1, new_vertical ? -1 : 1)
|
||||
.map(centre);
|
||||
centre = QTransform().rotate(element->rotation()).map(centre);
|
||||
|
||||
const QPointF old_pos = element->pos();
|
||||
const QPointF new_pos = Diagram::snapToGrid(
|
||||
element->mapToScene(element->boundingRect().center()) - centre);
|
||||
if (new_pos != old_pos)
|
||||
new QPropertyUndoCommand(element, "pos", old_pos, new_pos, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MirrorSelectionCommand::selectedElements
|
||||
@param diagram
|
||||
@return the elements selected in @p diagram, none if it is read only
|
||||
*/
|
||||
QList<Element *> MirrorSelectionCommand::selectedElements(const Diagram *diagram)
|
||||
{
|
||||
QList<Element *> elements;
|
||||
if (!diagram || diagram->isReadOnly())
|
||||
return elements;
|
||||
|
||||
const QList<QGraphicsItem *> items = diagram->selectedItems();
|
||||
for (QGraphicsItem *item : items)
|
||||
if (Element *element = qgraphicsitem_cast<Element *>(item))
|
||||
elements << element;
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MirrorSelectionCommand::isValid
|
||||
@return true if at least one element is mirrored by this command
|
||||
*/
|
||||
bool MirrorSelectionCommand::isValid() const
|
||||
{
|
||||
return childCount() > 0;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#ifndef MIRRORSELECTIONCOMMAND_H
|
||||
#define MIRRORSELECTIONCOMMAND_H
|
||||
|
||||
#include <QList>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class Diagram;
|
||||
class Element;
|
||||
|
||||
/**
|
||||
@brief The MirrorSelectionCommand class
|
||||
Mirror elements in place, horizontally (left and right swap) or
|
||||
vertically (top and bottom swap), on the folio. See Element::setMirror()
|
||||
*/
|
||||
class MirrorSelectionCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
MirrorSelectionCommand(Diagram *diagram,
|
||||
Qt::Orientation orientation,
|
||||
QUndoCommand *parent = nullptr);
|
||||
MirrorSelectionCommand(const QList<Element *> &elements,
|
||||
Qt::Orientation orientation,
|
||||
QUndoCommand *parent = nullptr);
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
static QList<Element *> selectedElements(const Diagram *diagram);
|
||||
};
|
||||
|
||||
#endif // MIRRORSELECTIONCOMMAND_H
|
||||
Reference in New Issue
Block a user