From 2989f6cb4b74b0e5cb525e2dfb008007262d801a Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Mon, 5 Oct 2026 17:56:22 +0200 Subject: [PATCH 1/2] Resize free texts by dragging a corner A selected free text gets the corner handles of the previous commit: dragging one sets the width the text wraps to, keeping the opposite corner in place; double-clicking a handle goes back to the automatic width. One undo step per drag, on the folio's undo stack. The handles are hidden while the text is typed in. DiagramTextItem shows the handles for a subclass that asks for them (textResizeHandlesWanted()); only free texts do for now. Co-Authored-By: Claude Opus 5.5 --- sources/qetgraphicsitem/diagramtextitem.cpp | 84 +++++++++++++++++++ sources/qetgraphicsitem/diagramtextitem.h | 12 +++ .../qetgraphicsitem/independenttextitem.cpp | 10 +++ sources/qetgraphicsitem/independenttextitem.h | 1 + 4 files changed, 107 insertions(+) diff --git a/sources/qetgraphicsitem/diagramtextitem.cpp b/sources/qetgraphicsitem/diagramtextitem.cpp index cc44c6b0c..01e4829d9 100644 --- a/sources/qetgraphicsitem/diagramtextitem.cpp +++ b/sources/qetgraphicsitem/diagramtextitem.cpp @@ -22,6 +22,8 @@ #include "../qetapp.h" #include "../richtext/richtexteditor_p.h" #include "../textanchor.h" +#include "../utils/qetutils.h" +#include "../QetGraphicsItemModeler/textresizehandles.h" /** @brief DiagramTextItem::DiagramTextItem @@ -335,6 +337,7 @@ void DiagramTextItem::focusInEvent(QFocusEvent *event) m_previous_html_text = toHtml(); m_previous_text = toPlainText(); + refreshTextResizeHandles(); } /** @@ -357,6 +360,87 @@ void DiagramTextItem::focusOutEvent(QFocusEvent *event) setFlag(QGraphicsItem::ItemIsMovable, true); setFlag(QGraphicsTextItem::ItemIsFocusable, false); + refreshTextResizeHandles(); +} + +/** + @brief DiagramTextItem::itemChange + Show or hide the resize handles with the selection. + @param change + @param value + @return +*/ +QVariant DiagramTextItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == QGraphicsItem::ItemSelectedHasChanged) + refreshTextResizeHandles(); + else if (change == QGraphicsItem::ItemSceneHasChanged && !scene()) + removeTextResizeHandles(); + + return QGraphicsTextItem::itemChange(change, value); +} + +/** + @brief DiagramTextItem::textResizeHandlesWanted + @return true when the corner handles to change the width of this text + should be shown. A text without a "textWidth" property never has them. +*/ +bool DiagramTextItem::textResizeHandlesWanted() const +{ + return false; +} + +/** + @brief DiagramTextItem::isEditing + @return true while the text itself is being typed in +*/ +bool DiagramTextItem::isEditing() const +{ + return textInteractionFlags() & Qt::TextEditable; +} + +/** + @brief DiagramTextItem::refreshTextResizeHandles + Create or remove the corner handles to change the width of this text, + according to textResizeHandlesWanted(). + Called from itemChange() and when the edition starts or ends, not from + paint(): moving items from paint() left fragments behind + (qelectrotech#1002). +*/ +void DiagramTextItem::refreshTextResizeHandles() +{ + const bool wanted = scene() && textResizeHandlesWanted(); + if (wanted && !m_resize_handles) + { + m_resize_handles = new TextResizeHandles(this, QETUtils::graphicsHandlerSize(this)); + connect(m_resize_handles, &TextResizeHandles::resizeFinished, + this, &DiagramTextItem::pushResizeCommand); + } + else if (!wanted && m_resize_handles) { + removeTextResizeHandles(); + } +} + +/** + @brief DiagramTextItem::removeTextResizeHandles +*/ +void DiagramTextItem::removeTextResizeHandles() +{ + delete m_resize_handles; + m_resize_handles = nullptr; +} + +/** + @brief DiagramTextItem::pushResizeCommand + Make the width change done with the resize handles undoable. The change + is already applied, live during the drag. +*/ +void DiagramTextItem::pushResizeCommand(qreal old_width, qreal new_width, + QPointF old_pos, QPointF new_pos) +{ + if (Diagram *diagram_ = diagram()) + diagram_->undoStack().push(new TextResizeCommand(this, old_width, new_width, + old_pos, new_pos)); } /** diff --git a/sources/qetgraphicsitem/diagramtextitem.h b/sources/qetgraphicsitem/diagramtextitem.h index 323c0db56..e43f1f1de 100644 --- a/sources/qetgraphicsitem/diagramtextitem.h +++ b/sources/qetgraphicsitem/diagramtextitem.h @@ -24,6 +24,7 @@ class Diagram; class QDomElement; class QDomDocument; +class TextResizeHandles; /** This class represents a selectable, movable and editable text field on a @@ -90,12 +91,16 @@ class DiagramTextItem : public QGraphicsTextItem void setPlainText(const QString &text); bool isHtml() const; + void refreshTextResizeHandles(); + TextResizeHandles *textResizeHandles() const {return m_resize_handles;} + protected: void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; void focusInEvent(QFocusEvent *) override; void focusOutEvent(QFocusEvent *) override; + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; void mouseDoubleClickEvent (QGraphicsSceneMouseEvent *event) override; void mousePressEvent (QGraphicsSceneMouseEvent *event) override; @@ -110,7 +115,10 @@ class DiagramTextItem : public QGraphicsTextItem void prepareAlignment(); void finishAlignment(); + virtual bool textResizeHandlesWanted() const; virtual bool hasUserTextWidth() const {return false;} + bool isEditing() const; + void removeTextResizeHandles(); protected: @@ -128,7 +136,11 @@ class DiagramTextItem : public QGraphicsTextItem QPointF m_mouse_to_origin_movement; private: + void pushResizeCommand(qreal old_width, qreal new_width, + QPointF old_pos, QPointF new_pos); + QRectF m_alignment_rect; + TextResizeHandles *m_resize_handles = nullptr; Qt::Alignment m_alignment = (Qt::AlignTop | Qt::AlignLeft); }; #endif diff --git a/sources/qetgraphicsitem/independenttextitem.cpp b/sources/qetgraphicsitem/independenttextitem.cpp index 8e5d03ef4..833b8dd48 100644 --- a/sources/qetgraphicsitem/independenttextitem.cpp +++ b/sources/qetgraphicsitem/independenttextitem.cpp @@ -148,6 +148,16 @@ void IndependentTextItem::setTextWidth(qreal width) emit textWidthChanged(width); } +/** + @brief IndependentTextItem::textResizeHandlesWanted + @return true when this text is selected and not being typed in: the + corner handles then change its width. +*/ +bool IndependentTextItem::textResizeHandlesWanted() const +{ + return isSelected() && !isEditing(); +} + void IndependentTextItem::focusOutEvent(QFocusEvent *event) { DiagramTextItem::focusOutEvent(event); diff --git a/sources/qetgraphicsitem/independenttextitem.h b/sources/qetgraphicsitem/independenttextitem.h index 571c9eeea..bc2083e6a 100644 --- a/sources/qetgraphicsitem/independenttextitem.h +++ b/sources/qetgraphicsitem/independenttextitem.h @@ -63,6 +63,7 @@ class IndependentTextItem : public DiagramTextItem protected: void focusOutEvent(QFocusEvent *event) override; + bool textResizeHandlesWanted() const override; bool hasUserTextWidth() const override {return m_text_width > 0;} private: From 9882a79a9322c3ade7668200b2bf3586aa4d7eb4 Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Wed, 7 Oct 2026 01:51:00 +0200 Subject: [PATCH 2/2] Refresh the free text handles when the text comes back to a folio itemChange() removed the corner handles when the text left the scene but did not bring them back when it returned still selected, which Qt allows (undoing a scripted delete does it) without a selection change. Refresh on every scene change. The two handle helpers are protected: nothing outside the class calls them. Co-Authored-By: Claude Fable 5.1 --- sources/qetgraphicsitem/diagramtextitem.cpp | 10 ++++++---- sources/qetgraphicsitem/diagramtextitem.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/sources/qetgraphicsitem/diagramtextitem.cpp b/sources/qetgraphicsitem/diagramtextitem.cpp index 01e4829d9..08fdfafb8 100644 --- a/sources/qetgraphicsitem/diagramtextitem.cpp +++ b/sources/qetgraphicsitem/diagramtextitem.cpp @@ -365,17 +365,19 @@ void DiagramTextItem::focusOutEvent(QFocusEvent *event) /** @brief DiagramTextItem::itemChange - Show or hide the resize handles with the selection. + Show or hide the resize handles with the selection and the scene. @param change @param value @return */ QVariant DiagramTextItem::itemChange(GraphicsItemChange change, const QVariant &value) { - if (change == QGraphicsItem::ItemSelectedHasChanged) + //Also when the text comes back to a scene: Qt keeps it selected + //across removeItem()/addItem() (undoing a delete) and sends no + //selection change, and its handles were removed with the scene. + if (change == QGraphicsItem::ItemSelectedHasChanged + || change == QGraphicsItem::ItemSceneHasChanged) refreshTextResizeHandles(); - else if (change == QGraphicsItem::ItemSceneHasChanged && !scene()) - removeTextResizeHandles(); return QGraphicsTextItem::itemChange(change, value); } diff --git a/sources/qetgraphicsitem/diagramtextitem.h b/sources/qetgraphicsitem/diagramtextitem.h index e43f1f1de..3a5d5ca4d 100644 --- a/sources/qetgraphicsitem/diagramtextitem.h +++ b/sources/qetgraphicsitem/diagramtextitem.h @@ -91,10 +91,10 @@ class DiagramTextItem : public QGraphicsTextItem void setPlainText(const QString &text); bool isHtml() const; + protected: void refreshTextResizeHandles(); TextResizeHandles *textResizeHandles() const {return m_resize_handles;} - protected: void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override;