mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-30 23:04:13 +02:00
2dbaa69186
Grouping two symbols and then locking one of them (Lock position in its properties) left the group in a broken state: dragging the unlocked symbol pulled it away while the locked one stayed, and dragging the locked one did nothing. The move simply dropped locked items, so the rest of the group went without them. A group with a locked member now does not move at all, whichever member is dragged, and the status bar says why. This is the rule the item-groups proposal (discussion #1070) set out for this case. The same rule applies to the arrow keys and to the Align commands, which share DiagramContent::removeNonMovableItems(). Also fixed on the way, for a plain selection with a locked symbol: a wire between the locked symbol and one being dragged kept its user-placed text moving with the dragged end. Such a wire is now redrawn only, as a wire to an unselected symbol already is. Checked in the GUI on two symbols joined by a wire (grafcet example), master against this branch, positions read from the saved file: - drag the unlocked member: master moves it 190 px, this branch moves nothing and shows the message - arrow keys on the selected group (3 runs each): master moves the unlocked member, this branch nothing - the same two symbols ungrouped: both move the unlocked one, as before - user-placed wire text: master shifts it 190 px, this branch keeps it ctest: 34/34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
562 lines
14 KiB
C++
562 lines
14 KiB
C++
/*
|
|
Copyright 2006-2026 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 "diagramtextitem.h"
|
|
|
|
#include "../diagram.h"
|
|
#include "../diagramcommands.h"
|
|
#include "../qetapp.h"
|
|
#include "../richtext/richtexteditor_p.h"
|
|
|
|
/**
|
|
@brief DiagramTextItem::DiagramTextItem
|
|
@param parent : parent item
|
|
*/
|
|
DiagramTextItem::DiagramTextItem(QGraphicsItem *parent) :
|
|
QGraphicsTextItem(parent)
|
|
{ build(); }
|
|
|
|
/**
|
|
@brief DiagramTextItem::DiagramTextItem
|
|
@param text : text to display
|
|
@param parent : parent item
|
|
*/
|
|
DiagramTextItem::DiagramTextItem(const QString &text, QGraphicsItem *parent) :
|
|
QGraphicsTextItem(text, parent),
|
|
m_mouse_hover(false),
|
|
m_previous_html_text(text)
|
|
{ build(); }
|
|
|
|
/**
|
|
@brief DiagramTextItem::build
|
|
Build this item with default value
|
|
*/
|
|
void DiagramTextItem::build()
|
|
{
|
|
//set Zvalue at 10 to be upper than the DiagramImageItem
|
|
setZValue(10);
|
|
setAcceptHoverEvents(true);
|
|
setDefaultTextColor(Qt::black);
|
|
setFont(QETApp::diagramTextsItemFont());
|
|
setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges);
|
|
setNoEditable(false);
|
|
setToolTip(tr("Maintenir ctrl pour un déplacement libre"));
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::diagram
|
|
@return The diagram of this item or 0 if this text isn't in a diagram
|
|
*/
|
|
Diagram *DiagramTextItem::diagram() const
|
|
{
|
|
return(qobject_cast<Diagram *>(scene()));
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::toXml
|
|
This method do nothing and return an empty DomElement
|
|
This is used to be inherited by child class
|
|
@return
|
|
*/
|
|
QDomElement DiagramTextItem::toXml(QDomDocument &) const
|
|
{
|
|
return QDomElement();
|
|
}
|
|
|
|
/**
|
|
Traduit en coordonnees de la scene un mouvement / vecteur initialement
|
|
exprime en coordonnees locales.
|
|
@param movement Vecteur exprime en coordonnees locales
|
|
@return le meme vecteur, exprime en coordonnees de la scene
|
|
*/
|
|
QPointF DiagramTextItem::mapMovementToScene(const QPointF &movement) const
|
|
{
|
|
// on definit deux points en coordonnees locales
|
|
QPointF local_origin(0.0, 0.0);
|
|
QPointF local_movement_point(movement);
|
|
|
|
// on les mappe sur la scene
|
|
QPointF scene_origin(mapToScene(local_origin));
|
|
QPointF scene_movement_point(mapToScene(local_movement_point));
|
|
|
|
// on calcule le vecteur represente par ces deux points
|
|
return(scene_movement_point - scene_origin);
|
|
}
|
|
|
|
/**
|
|
Traduit en coordonnees locales un mouvement / vecteur initialement
|
|
exprime en coordonnees de la scene.
|
|
@param movement Vecteur exprime en coordonnees de la scene
|
|
@return le meme vecteur, exprime en coordonnees locales
|
|
*/
|
|
QPointF DiagramTextItem::mapMovementFromScene(const QPointF &movement) const
|
|
{
|
|
// on definit deux points sur la scene
|
|
QPointF scene_origin(0.0, 0.0);
|
|
QPointF scene_movement_point(movement);
|
|
|
|
// on les mappe sur ce QGraphicsItem
|
|
QPointF local_origin(mapFromScene(scene_origin));
|
|
QPointF local_movement_point(mapFromScene(scene_movement_point));
|
|
|
|
// on calcule le vecteur represente par ces deux points
|
|
return(local_movement_point - local_origin);
|
|
}
|
|
|
|
/**
|
|
Traduit en coordonnees de l'item parent un mouvement / vecteur initialement
|
|
exprime en coordonnees locales.
|
|
@param movement Vecteur exprime en coordonnees locales
|
|
@return le meme vecteur, exprime en coordonnees du parent
|
|
*/
|
|
QPointF DiagramTextItem::mapMovementToParent(const QPointF &movement) const
|
|
{
|
|
// on definit deux points en coordonnees locales
|
|
QPointF local_origin(0.0, 0.0);
|
|
QPointF local_movement_point(movement);
|
|
|
|
// on les mappe sur la scene
|
|
QPointF parent_origin(mapToParent(local_origin));
|
|
QPointF parent_movement_point(mapToParent(local_movement_point));
|
|
|
|
// on calcule le vecteur represente par ces deux points
|
|
return(parent_movement_point - parent_origin);
|
|
}
|
|
|
|
/**
|
|
Traduit en coordonnees locales un mouvement / vecteur initialement
|
|
exprime en coordonnees du parent.
|
|
@param movement Vecteur exprime en coordonnees du parent
|
|
@return le meme vecteur, exprime en coordonnees locales
|
|
*/
|
|
QPointF DiagramTextItem::mapMovementFromParent(const QPointF &movement) const
|
|
{
|
|
// on definit deux points sur le parent
|
|
QPointF parent_origin(0.0, 0.0);
|
|
QPointF parent_movement_point(movement);
|
|
|
|
// on les mappe sur ce QGraphicsItem
|
|
QPointF local_origin(mapFromParent(parent_origin));
|
|
QPointF local_movement_point(mapFromParent(parent_movement_point));
|
|
|
|
// on calcule le vecteur represente par ces deux points
|
|
return(local_movement_point - local_origin);
|
|
}
|
|
|
|
void DiagramTextItem::setFont(const QFont &font)
|
|
{
|
|
if (this->font() == font) {
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
prepareAlignment();
|
|
QGraphicsTextItem::setFont(font);
|
|
finishAlignment();
|
|
emit fontChanged(font);
|
|
}
|
|
}
|
|
|
|
void DiagramTextItem::setColor(const QColor& color)
|
|
{
|
|
setDefaultTextColor(color);
|
|
emit colorChanged(color);
|
|
}
|
|
|
|
QColor DiagramTextItem::color() const
|
|
{
|
|
return defaultTextColor();
|
|
}
|
|
|
|
void DiagramTextItem::setAlignment(const Qt::Alignment &alignment)
|
|
{
|
|
m_alignment = alignment;
|
|
emit alignmentChanged(alignment);
|
|
}
|
|
|
|
Qt::Alignment DiagramTextItem::alignment() const
|
|
{
|
|
return m_alignment;
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::frameRect
|
|
@return the rect used to draw a frame around this text
|
|
*/
|
|
QRectF DiagramTextItem::frameRect() const
|
|
{
|
|
//Get the bounding rectangle of the text
|
|
QSizeF size = document()->size();
|
|
size.setWidth(document()->idealWidth());
|
|
//Remove the margin. Size is exactly the bounding rect of the text
|
|
size.rheight() -= document()->documentMargin()*2;
|
|
size.rwidth() -= document()->documentMargin()*2;
|
|
//Add a little margin only for a better visual;
|
|
size.rheight() += 2;
|
|
size.rwidth() += 2;
|
|
|
|
//The pos of the rect
|
|
QPointF pos = boundingRect().center();
|
|
pos.rx() -= size.width()/2;
|
|
pos.ry() -= size.height()/2;
|
|
|
|
return QRectF(pos, size);
|
|
}
|
|
|
|
void DiagramTextItem::setHtml(const QString &text)
|
|
{
|
|
QGraphicsTextItem::setHtml(text);
|
|
m_is_html = true;
|
|
|
|
m_non_left_alignment = false;
|
|
QTextBlock block = document()->begin();
|
|
while (block.isValid()) {
|
|
Qt::Alignment a = block.blockFormat().alignment();
|
|
if (a & Qt::AlignHCenter || a & Qt::AlignRight || a & Qt::AlignJustify) {
|
|
m_non_left_alignment = true;
|
|
break;
|
|
}
|
|
block = block.next();
|
|
}
|
|
|
|
if (m_non_left_alignment) {
|
|
document()->setTextWidth(document()->idealWidth() + 40.0);
|
|
}
|
|
}
|
|
|
|
void DiagramTextItem::setPlainText(const QString &text)
|
|
{
|
|
QGraphicsTextItem::setPlainText(text);
|
|
m_is_html = false;
|
|
}
|
|
|
|
bool DiagramTextItem::isHtml() const
|
|
{
|
|
return m_is_html;
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::paint
|
|
Draw this text field. This method draws the text by calling QGraphicsTextItem::paint.
|
|
If text is hovered, this method draws the bounding rect in grey
|
|
@param painter : painter to use
|
|
@param option : style option
|
|
@param widget : widget that is drawn to
|
|
*/
|
|
void DiagramTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
|
{
|
|
painter -> setRenderHint(QPainter::Antialiasing, false);
|
|
|
|
QGraphicsTextItem::paint(painter, option, widget);
|
|
|
|
if (m_mouse_hover && !isSelected())
|
|
{
|
|
painter -> save();
|
|
|
|
//Disable renderhints
|
|
painter -> setRenderHint(QPainter::Antialiasing, false);
|
|
painter -> setRenderHint(QPainter::TextAntialiasing, false);
|
|
painter -> setRenderHint(QPainter::SmoothPixmapTransform, false);
|
|
|
|
//Draw the selected rect in grey
|
|
QPen t;
|
|
t.setColor(Qt::gray);
|
|
t.setStyle(Qt::DashDotLine);
|
|
t.setCosmetic(true);
|
|
painter -> setPen(t);
|
|
painter -> drawRoundedRect(boundingRect(), 10, 10);
|
|
|
|
painter -> restore();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::focusInEvent
|
|
@param event
|
|
*/
|
|
void DiagramTextItem::focusInEvent(QFocusEvent *event)
|
|
{
|
|
QGraphicsTextItem::focusInEvent(event);
|
|
|
|
setFlag(QGraphicsItem::ItemIsMovable, false);
|
|
|
|
m_previous_html_text = toHtml();
|
|
m_previous_text = toPlainText();
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::focusOutEvent
|
|
@param event
|
|
*/
|
|
void DiagramTextItem::focusOutEvent(QFocusEvent *event)
|
|
{
|
|
QGraphicsTextItem::focusOutEvent(event);
|
|
|
|
if(toPlainText() != m_previous_text)
|
|
emit textEdited(m_previous_text, toPlainText());
|
|
|
|
QTextCursor cursor = textCursor();
|
|
cursor.clearSelection();
|
|
setTextCursor(cursor);
|
|
|
|
//Bad hack to be re-entrant
|
|
setTextInteractionFlags(Qt::NoTextInteraction);
|
|
|
|
setFlag(QGraphicsItem::ItemIsMovable, true);
|
|
setFlag(QGraphicsTextItem::ItemIsFocusable, false);
|
|
}
|
|
|
|
/**
|
|
Gere les double-clics sur ce champ de texte.
|
|
@param event un QGraphicsSceneMouseEvent decrivant le double-clic
|
|
*/
|
|
void DiagramTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
|
|
if (!(textInteractionFlags() & Qt::TextEditable) && !m_no_editable) {
|
|
// rend le champ de texte editable
|
|
setTextInteractionFlags(Qt::TextEditorInteraction);
|
|
|
|
// edite le champ de texte
|
|
setFocus(Qt::MouseFocusReason);
|
|
} else {
|
|
QGraphicsTextItem::mouseDoubleClickEvent(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::mousePressEvent
|
|
@param event
|
|
*/
|
|
void DiagramTextItem::mousePressEvent (QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton)
|
|
{
|
|
m_first_move = true;
|
|
//Save the pos of item at the beggining of the movement
|
|
m_mouse_to_origin_movement = pos() - event->scenePos();
|
|
}
|
|
QGraphicsTextItem::mousePressEvent(event);
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::mouseMoveEvent
|
|
@param event
|
|
*/
|
|
void DiagramTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
|
if (textInteractionFlags() & Qt::TextEditable) QGraphicsTextItem::mouseMoveEvent(event);
|
|
|
|
else if ((flags() & QGraphicsItem::ItemIsMovable) && (event -> buttons() & Qt::LeftButton)) {
|
|
Diagram *diagram_ = diagram();
|
|
|
|
//This is first move, we signal it to parent diagram
|
|
if(diagram_ && m_first_move)
|
|
diagram_->elementsMover().beginMovement(diagram_, this);
|
|
|
|
if (diagram_ && diagram_->elementsMover().holds(this)) {
|
|
m_first_move = false;
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
QPointF old_pos = pos();
|
|
|
|
//Set the actual pos
|
|
QPointF new_pos = event->scenePos() + m_mouse_to_origin_movement;
|
|
event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos));
|
|
|
|
|
|
//Update the actual movement for other selected item
|
|
if (diagram_)
|
|
diagram_->elementsMover().continueMovement(pos() - old_pos);
|
|
}
|
|
|
|
else event -> ignore();
|
|
|
|
m_first_move = false;
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::mouseReleaseEvent
|
|
@param event
|
|
*/
|
|
void DiagramTextItem::mouseReleaseEvent (QGraphicsSceneMouseEvent *event)
|
|
{
|
|
//Signal to diagram movement is finish
|
|
if (diagram() && (event->button() == Qt::LeftButton))
|
|
{
|
|
diagram()->elementsMover().endMovement();
|
|
event->accept();
|
|
if (event->buttonDownScenePos(Qt::LeftButton) != event->scenePos()) {
|
|
return;
|
|
}
|
|
}
|
|
if (event->modifiers() & Qt::ControlModifier && (event->button() == Qt::LeftButton))
|
|
{
|
|
setSelected(!isSelected());
|
|
event->accept();
|
|
}
|
|
else {
|
|
QGraphicsTextItem::mouseReleaseEvent(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Effectue la rotation du texte en elle-meme
|
|
Pour les DiagramTextItem, la rotation s'effectue autour du point (0, 0).
|
|
Cette methode peut toutefois etre redefinie dans des classes filles
|
|
@param angle Angle de la rotation a effectuer
|
|
*/
|
|
void DiagramTextItem::applyRotation(const qreal &angle) {
|
|
setRotation(QET::correctAngle(rotation()+angle));
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::prepareAlignment
|
|
Call this function before changing the bounding rect of this text.
|
|
*/
|
|
void DiagramTextItem::prepareAlignment()
|
|
{
|
|
m_alignment_rect = boundingRect();
|
|
}
|
|
|
|
/**
|
|
@brief DiagramTextItem::finishAlignment
|
|
Call this function after changing the bounding rect of this text
|
|
to set the position of this text according to the alignment property.
|
|
*/
|
|
void DiagramTextItem::finishAlignment()
|
|
{
|
|
if(m_block_alignment)
|
|
return;
|
|
|
|
QTransform transform;
|
|
transform.rotate(this->rotation());
|
|
qreal x,xa, y,ya;
|
|
x=xa=0;
|
|
y=ya=0;
|
|
|
|
if(m_alignment &Qt::AlignRight)
|
|
{
|
|
x = m_alignment_rect.right();
|
|
xa = boundingRect().right();
|
|
}
|
|
else if(m_alignment &Qt::AlignHCenter)
|
|
{
|
|
x = m_alignment_rect.center().x();
|
|
xa = boundingRect().center().x();
|
|
}
|
|
|
|
if(m_alignment &Qt::AlignBottom)
|
|
{
|
|
y = m_alignment_rect.bottom();
|
|
ya = boundingRect().bottom();
|
|
}
|
|
else if(m_alignment &Qt::AlignVCenter)
|
|
{
|
|
y = m_alignment_rect.center().y();
|
|
ya = boundingRect().center().y();
|
|
}
|
|
|
|
QPointF p = transform.map(QPointF(x,y));
|
|
QPointF pa = transform.map(QPointF(xa,ya));
|
|
QPointF diff = pa-p;
|
|
|
|
setPos(this->pos() - diff);
|
|
}
|
|
|
|
/**
|
|
@brief Edit the text with HtmlEditor
|
|
*/
|
|
void DiagramTextItem::edit()
|
|
{
|
|
QWidget *parent = nullptr;
|
|
if (scene() && scene()->views().size())
|
|
parent = scene()->views().first();
|
|
|
|
qdesigner_internal::RichTextEditorDialog editor(parent);
|
|
editor.setText(toHtml());
|
|
|
|
QVector<Qt::Alignment> alignments;
|
|
connect(&editor, &qdesigner_internal::RichTextEditorDialog::applyEditText,
|
|
this, [this, &editor, &alignments](const QString &text) {
|
|
alignments = editor.blockAlignments();
|
|
this->setHtml(text);
|
|
});
|
|
editor.exec();
|
|
|
|
if (!alignments.isEmpty()) {
|
|
QTextCursor cursor(document());
|
|
cursor.beginEditBlock();
|
|
QTextBlock block = document()->begin();
|
|
int i = 0;
|
|
while (block.isValid() && i < alignments.size()) {
|
|
Qt::Alignment align = alignments.at(i);
|
|
if (align != Qt::AlignLeft && align != Qt::Alignment()) {
|
|
QTextBlockFormat fmt;
|
|
fmt.setAlignment(align);
|
|
cursor.setPosition(block.position());
|
|
cursor.setBlockFormat(fmt);
|
|
}
|
|
block = block.next();
|
|
i++;
|
|
}
|
|
cursor.endEditBlock();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
When mouse over element
|
|
change m_mouse_hover to true (used in paint() function )
|
|
@param e QGraphicsSceneHoverEvent
|
|
*/
|
|
void DiagramTextItem::hoverEnterEvent(QGraphicsSceneHoverEvent *e) {
|
|
Q_UNUSED(e);
|
|
|
|
m_mouse_hover = true;
|
|
QString str_ToolTip = toPlainText();
|
|
|
|
// Add movement instruction for DynamicElementTextItem
|
|
if (inherits("DynamicElementTextItem")) {
|
|
str_ToolTip += tr("\n<Shift> to move");
|
|
}
|
|
|
|
setToolTip(str_ToolTip);
|
|
update();
|
|
}
|
|
|
|
/**
|
|
When mouse over element leave the position
|
|
change m_mouse_hover to false (used in paint() function )
|
|
@param e QGraphicsSceneHoverEvent
|
|
*/
|
|
void DiagramTextItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *e) {
|
|
Q_UNUSED(e);
|
|
m_mouse_hover = false;
|
|
update();
|
|
}
|
|
|
|
/**
|
|
Do nothing default function .
|
|
@param e QGraphicsSceneHoverEvent
|
|
*/
|
|
void DiagramTextItem::hoverMoveEvent(QGraphicsSceneHoverEvent *e) {
|
|
Q_UNUSED(e);
|
|
QGraphicsTextItem::hoverMoveEvent(e);
|
|
}
|