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

378 lines
12 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/>.
*/
#ifndef ELEMENT_H
#define ELEMENT_H
#include "../ElementsCollection/elementslocation.h"
#include "../NameList/nameslist.h"
#include "../autoNum/assignvariables.h"
#include "../diagramcontext.h"
#include "../qet.h"
#include "qetgraphicsitem.h"
#include "../properties/elementdata.h"
#include <QHash>
#include <QPicture>
#include <algorithm>
class QETProject;
class Terminal;
class Conductor;
class DynamicElementTextItem;
class ElementTextItemGroup;
class QUndoCommand;
/**
This is the base class for electrical elements.
*/
class Element : public QetGraphicsItem
{
friend class DiagramEventAddElement;
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
Used to know the kind of this element
(master, slave, report ect...)
*/
enum kind {
Simple = 1,
NextReport = 2,
PreviousReport = 4,
AllReport = 6,
Master = 8,
Slave = 16,
Terminale = 32,
Thumbnail = 64,
ConductorDefinition = 128};
/**
* @brief The LinkInfo struct
* Stores link data for element connections.
* For slave elements, group_index indicates which contact group
* of the master this slave is assigned to (-1 = no group assigned).
*/
struct LinkInfo {
QUuid uuid;
int group_index = -1; ///< Index into master's slaveContactGroups, -1 = not assigned
LinkInfo() = default;
LinkInfo(const QUuid &u, int gi = -1) : uuid(u), group_index(gi) {}
};
Element(const ElementsLocation &location,
QGraphicsItem * = nullptr,
int *state = nullptr,
Element::kind link_type = Element::Simple);
~Element() override;
private:
bool definitionGeometryMatches(const QDomElement &definition) const;
Element(const Element &);
// attributes
public:
/**
Enable the use of qgraphicsitem_cast
to safely cast a QGraphicsItem into an Element.
@return the QGraphicsItem type
*/
enum { Type = UserType + 1000 };
int type() const override { return Type; }
signals:
void linkedElementChanged(); //This signal is emitted when the linked elements with this element change
void elementInfoChange(
DiagramContext old_info,
DiagramContext new_info);
void textAdded(DynamicElementTextItem *deti);
void textRemoved(DynamicElementTextItem *deti);
void textsGroupAdded(ElementTextItemGroup *group);
void textsGroupAboutToBeRemoved(ElementTextItemGroup *group);
void textAddedToGroup(
DynamicElementTextItem *text,
ElementTextItemGroup *group);
void textRemovedFromGroup(
DynamicElementTextItem *text,
ElementTextItemGroup *group);
void mirrorChanged();
public slots:
void updateConductorTexts();
public:
QList<Terminal *> terminals() const;
QList<Conductor *> conductors() const;
QList<QPair<Terminal *,Terminal *>> AlignedFreeTerminals() const;
void clearPendingLinks() {
tmp_uuids_link.clear();
m_group_index_map.clear();
}
//METHODS related to information
DiagramContext elementInformations()const
{return m_data.m_informations;}
virtual void setElementInformations(DiagramContext dc);
ElementData elementData() const;
void setElementData (ElementData data);
/**
* @brief kindInformations
* @deprecated
* use elementData function instead
* @return
*/
DiagramContext kindInformations() const
{return m_kind_informations;}
//@kind_information_ is used to store more information
//about the herited class like contactelement for know
// kind of contact (simple tempo) or number of contact show by the element.
autonum::sequentialNumbers sequenceStruct() const
{return m_autoNum_seq;}
autonum::sequentialNumbers& rSequenceStruct()
{return m_autoNum_seq;}
void setUpFormula(bool code_letter = true, QUndoCommand *parent_undo = nullptr);
void setFormulaSchemeId(const QUuid &id);
void setPrefix(QString);
QString getPrefix() const;
void freezeLabel(bool freeze);
bool isFreezeLabel() const {return m_freeze_label;}
void freezeNewAddedElement();
QString actualLabel();
QString name() const override;
ElementsLocation location() const;
/// Result of Element::reloadPicture()
enum class ReloadPictureResult {
Reloaded, ///< drawing replaced by the current definition
Unavailable, ///< definition missing or unreadable, old drawing kept
GeometryChanged ///< size, hotspot or terminals changed, old drawing kept
};
ReloadPictureResult reloadPicture();
virtual void setHighlighted(bool);
void displayHelpLine(bool b = true);
QSize size() const;
QPixmap pixmap();
QPoint setHotspot(QPoint);
QPoint hotspot() const;
void editProperty() override;
static bool valideXml(QDomElement &);
virtual bool fromXml(
QDomElement &,
QHash<int,
Terminal *> &);
virtual QDomElement toXml(
QDomDocument &,
QHash<Terminal *,
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);
void removeDynamicTextItem(DynamicElementTextItem *deti);
QList<DynamicElementTextItem *> dynamicTextItems() const;
ElementTextItemGroup *addTextGroup(const QString &name);
void addTextGroup(ElementTextItemGroup *group);
void removeTextGroup(ElementTextItemGroup *group);
ElementTextItemGroup *textGroup(const QString &name) const;
QList<ElementTextItemGroup *> textGroups() const;
bool addTextToGroup(
DynamicElementTextItem *text,
ElementTextItemGroup *group);
bool removeTextFromGroup(
DynamicElementTextItem *text,
ElementTextItemGroup *group);
//METHODS related to linked element
bool isFree() const;
virtual void linkToElement(Element *) {}
virtual void unlinkAllElements() {}
virtual void unlinkElement(Element *) {}
virtual void initLink(QETProject *);
/**
Resolve tmp_uuids_link against a caller-supplied candidate
list instead of a project-wide search (bugtracker #607).
Used right after an XML round-trip (paste, folio
duplication) and before the pasted/duplicated elements'
uuids are renewed: at that moment a copy's tmp_uuids_link
still holds its source's original partner uuid, which
still matches the not-yet-renewed uuid of that partner's
own copy if it was carried along in the same batch.
Resolving only within @p candidates -- not the whole
project -- is what stops a linked pair pasted together
from matching an original element left elsewhere that
happens to still carry that same soon-to-be-replaced
uuid. If only one half of a linked group is in
@p candidates, its entry finds no match and is dropped,
same as initLink(QETProject *) leaving an unresolvable
link unlinked.
*/
void initLink(const QList<Element *> &candidates);
QList<Element *> linkedElements ();
int groupIndexForElement(Element *elmt) const;
void setGroupIndexForElement(Element *elmt, int index);
/**
* @brief linkType
* use elementData function instead
* @return
*/
virtual kind linkType() const {return m_link_type;} // return the linkable type
/**
* @brief linkTypeToString
* use elementData function instead
* @return
*/
QString linkTypeToString() const;
void newUuid() {m_uuid = QUuid::createUuid();} //create new uuid for this element
void setUuid(const QUuid &uuid) {m_uuid = uuid;}
protected:
void drawAxes(QPainter *, const QStyleOptionGraphicsItem *);
void setSize(int, int);
private:
void drawHighlight(
QPainter *,
const QStyleOptionGraphicsItem *);
bool buildFromXml(const QDomElement &, int * = nullptr);
bool parseElement(const QDomElement &dom);
bool parseInput(const QDomElement &dom_element);
DynamicElementTextItem *parseDynamicText(
const QDomElement &dom_element);
Terminal *parseTerminal(const QDomElement &dom_element);
//Reimplemented from QGraphicsItem
public:
void paint(
QPainter *,
const QStyleOptionGraphicsItem *,
QWidget *) override;
QRectF boundingRect() const override;
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(
QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
protected:
//ATTRIBUTES related to linked element
QList <Element *> connected_elements;
QList <LinkInfo> tmp_uuids_link;
QHash <Element *, int> m_group_index_map; ///< Maps linked elements to their group index (for slave->master links)
QUuid m_uuid;
kind m_link_type = Element::Simple;
//ATTRIBUTES related to informations
DiagramContext m_kind_informations;
autonum::sequentialNumbers m_autoNum_seq;
bool m_freeze_label = false;
QString m_F_str;
ElementsLocation m_location;
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).
QList<QPointF> plcTablePositions() const { return m_plc_table_positions; }
private:
bool m_must_highlight = false;
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;
};
bool comparPos(const Element * elmt1, const Element * elmt2);
inline bool Element::isFree() const
{
return (connected_elements.isEmpty());
}
/**
Indicate the current orientation of this element
O = 0°
1 = 90°
2 = 180°
3 = 270°
@return the current orientation of this element
*/
inline int Element::orientation() const
{
return(QET::correctAngle(rotation())/90);
}
/**
@brief Element::uuid
@return the uuid of this element
*/
inline QUuid Element::uuid() const
{return m_uuid;}
/**
@brief Element::linkedElements
@return the list of linked elements, the list is sorted by position
*/
inline QList <Element *> Element::linkedElements()
{
std::sort(
connected_elements.begin(),
connected_elements.end(),
comparPos);
return connected_elements;
}
#endif