DynamicElementTextItem::updateLabel() resolved %{label} in composite
text using the stale value from elementInformations()["label"], which
is only set once at load time and never updated when the folio/page
number changes.
Use element->actualLabel() instead, which resolves the label formula
(including %F, %f, %id) against the current folio at call time.
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>
Dragging an element's text -- its label, article number, any of its
information fields -- moved it in free one-unit steps while everything
else in the editor snapped to the grid. Reported by pki791 in #923 for
labels moved with Shift.
QET moves a text with the mouse along five paths. Four snap and let Ctrl
place freely:
DiagramTextItem::mouseMoveEvent an independent text
ElementTextItemGroup::mouseMoveEvent a group of element texts
ElementTextsMover::continueMovement every OTHER selected element text
QetGraphicsItem::setPos elements, images, shapes
DynamicElementTextItem::mouseMoveEvent, the text actually under the
cursor, ended "setPos(new_pos)" with no grid and no modifier check. It is
otherwise the same function as the group's, which is why this reads as an
omission rather than a decision: the line this adds is that function's,
character for character.
The inconsistency was visible in one gesture. With two element texts
selected and one of them dragged, ElementTextsMover skips the driver item
and snaps the rest, so the text under the cursor was the only one on the
folio that did not land on the grid.
Verified on a virtual display (Xvfb + openbox) against a two-lamp fixture,
grid 10, reading the saved positions rather than the screen:
Shift+drag the label before (32.95, -11.55) -> (7.95, 23.45) off-grid
after -> (10, 20) on-grid
the co-selected label (10, -10) -> (50, 20) on-grid, before and after
Shift to grab, then Ctrl -> (7.95, 23.45) off-grid, free placement kept
The last line matters: moving an element text needs Shift at press, and
the modifier is read at move time, so Ctrl still places freely -- press
with Shift, hold Ctrl to drag. Holding both from the press is a different
gesture, reserved by DiagramView::isCtrlShifting() for the view's mode
switch, and does not move the text at all. Nothing that was possible
before is lost.
Worth knowing when reviewing: 470 of the 492 element texts in the 24
example projects (95.5 %) sit off the grid today, because element
definitions place their default text at fractional offsets. The first
drag of almost any existing label will pull it onto the grid, by at most
half a grid step.
ctest 12/12, Qt 6.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Add !parentGroup() guard to stale xref removal in updateXref()
to prevent deleting xref data that ElementTextItemGroup::updateXref()
just stored for a grouped text item.
- Replace QStringLiteral("xref") with QETInformation::ELMT_XREF
in linkelementcommand.cpp and masterelement.cpp.
- Fix indentation in plclinkwidget.cpp.
Two additional bugs found after the initial xref cleanup:
1. Undo after unlink did not restore PLC variables (PLC_TYPE,
PLC_ADDRESS, etc.) because PlcLinkWidget::on_m_unlink_pb_clicked()
never stored the current group index in the LinkElementCommand.
makeLink() only populates PLC fields when group_idx >= 0, so
the slave was re-linked but appeared empty. Fixed by reading the
group index from the master via groupIndexForElement() and calling
setGroupIndex() before unlinking.
2. m_update_slave_Xref_connection was only cleared inside the
if(m_slave_Xref_item) block in the updateXref() cleanup path.
For AlignHCenter (Text field) position, no m_slave_Xref_item
is ever created — the connections are stored in
m_update_slave_Xref_connection but never cleaned up on unlink.
On re-link, the old stale entries prevented new connections from
being established. Fixed by clearing the list unconditionally
in the cleanup path.
QString::toDouble() reports a successful conversion for "nan"/"inf"/
"-inf" -- confirmed directly -- so the existing conv_ok checks in
Element::valideXml() and Terminal::valideXml() never caught a
non-finite x/y. A NaN-positioned element reaching the scene can hang
QGraphicsScene::addItem() forever: an existing conductor's itemChange()
runs a collision test (calculateTextItemPosition() ->
QGraphicsItem::collidesWithPath()) whose underlying QPathClipper spins
without terminating when fed a NaN-valued QPainterPath, since NaN
breaks the ordering comparisons the clipping algorithm's termination
depends on (#781). Short of that, a non-finite value that doesn't
happen to trigger a collision test simply gets written straight back
out on save with nothing to stop it (#782).
Element::valideXml() and Terminal::valideXml() now also check
qIsFinite() on the parsed x/y, rejecting the whole item the same way a
missing attribute already does. DynamicElementTextItem::fromXml() has
no such reject-the-item gate (void return, no caller check), so its x/y
are clamped to 0 instead -- the same fallback the attribute lookup
already uses when x/y is missing entirely.
Verified against both original findings' exact repro steps:
- #781: Habitat-Unifilaire.qet with x="nan" on one element -- hung
(SIGTERM'd by a 25s timeout) on an unfixed build, resaves cleanly
(exit 0) on this one.
- #782: grafcet.qet with y="nan" on a dynamic_elmt_text -- the value
passed straight through to the resaved file on an unfixed build;
clamped to 0 on this one. The specific field is now stable (y="0" on
two consecutive resaves) where it read "nan" both times before.
(grafcet.qet has an unrelated, already-known, unmerged fix
(PR #779) for element/terminal-order non-determinism, so a whole-file
diff across resaves still differs for reasons unconnected to this
change -- checked the specific once-NaN field in isolation instead.)
- Also checked -inf on affuteuse_250h.qet: same rejection, same result.
Full qet-dbcheck.py sweep of the unmutated example corpus (23
projects), 0 regressions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DynamicElementTextItem's slave cross-reference sub-item
(m_slave_Xref_item, the small "_(1-D3)_"-style text next to a slave
element pointing back to its master) hardcoded Qt::black in three
places: on creation, on hover-leave, and when restoring color after
text editing. The PARENT text item's color is a real, user-configurable
property (color()/setColor(), persisted in the diagram XML, exposed in
the text editor's color picker) -- but the slave-Xref sub-item never
used it, so a user applying a dark theme/stylesheet had no way to make
this specific text visible even by explicitly setting a text color,
unlike every other text item in the diagram.
Fix: use color() (the parent DynamicElementTextItem's own configured
color, inherited from DiagramTextItem) instead of the Qt::black
constant in all three places. This doesn't change the default
appearance (color() defaults to black, same as before) but makes the
slave-Xref text finally respect whatever color the user sets on the
parent text field, giving dark-theme users the same escape hatch
already available for all other diagram text.
Verified: clean rebuild, only the intended object file recompiled and
linked successfully.
Not verified: a live visual confirmation of the slave-Xref text
picking up a non-default color, since reproducing this requires
constructing a master/slave-linked element pair with composite text
containing %{label} in an actual multi-folio project, which was out of
scope for the time available. Confidence rests on this being a direct,
mechanical substitution of an existing, already-used accessor
(color()) for a hardcoded constant, applied identically to the exact
three call sites that previously hardcoded Qt::black for this item,
with no other logic changed.
setTransformOriginPoint() was only applied inside
parentElementRotationChanged(), so loading an already-rotated element,
or enabling keep_visual_rotation while rotation_point_center was
already true, left the origin at (0, 0) until the next parent rotation.
Apply the origin directly in both setters so it's always in sync.
Added .ts files (de and en) to the commit
Now a new checkbox in the dynamictextfieldeditor is available (by default set to false for legacy) to make it possible to turn dynamic text fields around its own center.
This resolves an undesirable behaviour that occurs when the text alignment is retained
Including translation to english and german
Adds two QetGraphicsHandlerItem grip handles at the left/right edges of a
selected DynamicElementTextItem's frameRect(), reusing the exact same
handle class, scene-event-filter wiring, and live-drag-then-undo-on-release
pattern QetShapeItem already uses for its own diagram-level resize handles
(sources/qetgraphicsitem/qetshapeitem.cpp).
- Handles are created/destroyed on ItemSelectedHasChanged, matching
QetShapeItem's convention (and ElementPrimitiveDecorator's, for the
element editor's own primitives).
- Position is recomputed in paint() rather than hooked to specific
mutators, since textWidth/font/text/rotation can all move frameRect()
and there's no single itemChange notification that covers all of them.
- The drag delta is resolved through mapFromScene() into the item's own
local coordinates, so a rotated text box still resizes along its own
baseline rather than along the scene's x-axis.
- setTextWidth() is called live during the drag for immediate visual
feedback (matching how QetShapeItem's handlerMouseMoveEvent live-updates
geometry); only on release is a QPropertyUndoCommand pushed -- the exact
same command the properties-panel width spinbox already uses
(sources/ui/dynamicelementtextmodel.cpp), so no new undo-command class
or XML was needed.
- The original textWidth() value is preserved as-is (including -1, the
"auto" sentinel) for the undo command's old_value, separately from the
concrete baseline used for the live drag's delta math -- otherwise an
undo would replace "auto width" with a synthesized fixed width instead
of actually restoring the auto-sizing state.
Scoped to DynamicElementTextItem per the discussion's phase 1 (the
buildable, no-new-XML piece); IndependentTextItem and the element editor's
PartText/PartDynamicTextField have no serialized width property to resize
yet and are left as explicitly out-of-scope follow-ups.
Verified headlessly (Xvfb + xdotool + scrot): selecting an element's
label text shows the two handles, dragging one live-resizes the text
(confirmed via the properties panel's width field updating in real time),
and undo/redo correctly restores the exact original width including the
auto-width (-1) case.
QFont::fromString() of Qt 5.x and Qt <= 6.10 rejects the >= 19 field
descriptions QFont::toString() emits since Qt 6.11, silently leaving a
broken font at every read site. Add QETUtils::fontFromString(): try the
native parser first, and on failure re-compose the legacy 10/11 field
form from the known Qt 6.11 field layout (OpenType weight mapped back
to the legacy scale) so no font information stored in existing files is
lost. Also salvage the 21 field double-serialized descriptions left
behind by some historical builds (a complete legacy description
embedded as the family name of a second one) by taking the embedded
leading description, matching what the lenient parser of Qt 6.11+
resolves them to. All font read sites now go through the helper; on
failure the default font of the caller is left untouched instead of a
cleared family.
Verified end to end on a Qt 5.15 build: a project whose 53 font
attributes were rewritten into the 19 field Qt 6.11 format loads and
autosaves byte-identical to the original legacy file (family, sizes,
bold/italic/underline, style name all preserved), and a mixed file
containing the exact 21 field string from the issue comes back
normalized as "Caladea,9,-1,5,75,1,0,0,0,0,Bold Italic".
See issue #553.
QFont::toString() is not stable across Qt versions: Qt 6.11 switched to
a 19-field format carrying OpenType weights, which QFont::fromString()
of Qt 5.x and Qt <= 6.10 rejects, leaving a broken font. Projects saved
by a Qt 6.11+ build were therefore unreadable by older builds.
Add QETUtils::fontToString() composing the legacy 10/11-field
description (weight mapped back to the legacy scale with the same
closest-match table Qt uses when parsing) and use it at every site that
stores a font description in a project, element, table config or
settings file. Every Qt version from 5.15 through 6.12-beta parses this
form correctly, so files stay readable by every QET build in
circulation.
See issue #553.
When using composite text in report elements, the name of the variable
was displayed when inserting the reportElement into the drawing (e.g.
%{function}). This is corrected here.
Add missing variables to assignvariables.cpp
clazy is a compiler plugin which allows clang to understand Qt
semantics. You get more than 50 Qt related compiler warnings, ranging
from unneeded memory allocations to misusage of API, including fix-its
for automatic refactoring.
https://invent.kde.org/sdk/clazy