mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-28 04:54:13 +02:00
29d16c3337
Two bugs reported by @arummler on #591 after merge: "It works but to select the text field one has to right click on it...I think there are competing handlers or something." "the drag elements should be on the border of the box. In the moment they appear directly left and right from the text." Both reproduced headlessly (scripts/qet-gui-dialog.sh) against a fresh build of current master and root-caused before touching anything. Selection: DynamicElementTextItem::mousePressEvent() forwards a plain click (no Shift) straight to parentElement()->mousePressEvent(), by design and pre-existing -- it's what lets dragging a symbol by its own label move the whole symbol rather than just the label. That's correct and untouched here. But it means a plain click leaves the *parent* selected, not the text, and #591's handles were wired only to the text's own ItemSelectedHasChanged -- so they were only reachable via Shift+click or a right-click's context menu (which happens to select the item under the cursor for its own context menu, unrelated to the Shift path), neither of which anyone reaches for to resize a text. Confirmed with screenshots at each step, including that Shift+click already reached the existing (if misplaced) handles correctly. Fix: DynamicElementTextItem::refreshResizeHandlesVisibility() shows the handles when either the text itself or its parent element is selected, and Element gets an itemChange() override (it had none) that calls it on each of its own texts when the element's own selection changes. Both sides driven from itemChange(), Qt's own hook for exactly this and the same one already used for the text's own selection. First attempt drove this from paint() instead, since the PR's own updateResizeHandlesPos() already runs there. That crashed reproducibly (SIGABRT) on deselecting a text: paint() runs while QGraphicsScene iterates its item list to draw it, and addResizeHandles()/ removeResizeHandles() mutate that list via QGraphicsScene::addItem()/ removeItem(), which cannot safely happen mid-iteration. Caught it with the same headless repro before it went anywhere near a PR, moved the logic to itemChange(), and re-ran the full sequence -- select, resize, undo, deselect, twice through -- clean. Position: updateResizeHandlesPos() placed the handles on frameRect(), which is a box sized to the text's natural (idealWidth()) content and then re-centred inside boundingRect() -- it does not grow with textWidth(). Once a text has been widened, frameRect() stays tight around the glyphs while boundingRect() -- the box QGraphicsView actually outlines as the selection, and the box a user drags relative to -- grows around it, leaving the handles stranded well inside the visible selection border. Fix: position them on boundingRect() instead, which does track textWidth(); confirmed by widening a text and checking the handle lands exactly on the new edge rather than partway across it. Verified headlessly end to end on the original report's own element ("motor off" on grafcet.qet, folio 1): a single plain left-click (no Shift, no right-click) now shows both handles at the true box border; dragging resizes correctly and the handle tracks the growing edge; Ctrl+Z restores the -1 auto-width sentinel and the handles stay at the reverted position; clicking away removes them; repeated twice with no crash. Qt 6.10.2, ctest 12/12, no new warnings in either changed file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
339 lines
9.8 KiB
C++
339 lines
9.8 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
|
|
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);
|
|
|
|
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 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;
|
|
|
|
//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 *);
|
|
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
|
|
|
|
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;
|
|
ElementData m_data;
|
|
QList<QPointF> m_plc_table_positions; // Positions of plc_table parts in the element definition
|
|
|
|
void drawPlcTable(QPainter *painter);
|
|
|
|
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;
|
|
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
|