duplicatesAreNotNumberedWhenSwitchedOff turns two preferences off by
writing <home>/config/QElectroTech/QElectroTech.conf and pointing
XDG_CONFIG_HOME there. Only Linux reads settings from that file. macOS
keeps them in the system preferences, so the binary never saw the two
preferences, numbered the pasted duplicates anyway, and the test failed
on macOS, on master too.
Write the file where QET_SETTINGS_DIR points instead (#1178), and set that
variable for every run of the binary, so no run reads the user's own
settings either.
No file-format change. Tests: tst_elementautonumids fails on master on
macOS and passes with this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
layoutLines() trimmed the trailing spaces of every line and kept
non-breaking spaces, which the DXF export used to write as plain spaces
(toPlainText()). A DXF R10 file has no code page, so a reader assuming
Windows-1252 could show them as 'Â '. Now a paragraph that does not
wrap gives exactly its line of toPlainText().split('\n') again
(trailing spaces and tabs kept, non-breaking spaces as plain spaces,
<br> as a line break). In a paragraph that wraps, every line loses its
trailing spaces, so spaces that wrap to a line of their own give an
empty line: not written, but it keeps the spacing of the next lines.
Test: tst_textlines compares with toPlainText() for trailing spaces,
tabs, non-breaking spaces, <br> and paragraphs, and checks wrapped
lines (fails before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- setTextWidth() returns without a signal when the width does not
change, which is also the case on every load of a text without one.
- Say why IndependentTextItem::textWidth()/setTextWidth() hide the
non-virtual QGraphicsTextItem functions, and what that means for code
holding a base pointer.
- The fixture no longer carries a /tmp path from the machine it was
made on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The independent_text table had the text and rotation of a free text but
not its width, and a width change did not update the row at all -- not
even its width and height, which follow the wrapped text. Add a
text_width column (NULL for the automatic width) and update the row on
textWidthChanged.
Test: tst_scriptfreetextwidth reads the table with qet.query() (fails
before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scene drops the event filters of an item that leaves it. The handles
installed theirs once, in the constructor, and only if the text was in
a scene: after the text was removed and added back (undoing a delete
does that), or when the handles were made before the text was in a
scene, they no longer reacted, and a drag moved the text instead.
TextResizeHandles now installs the filters again whenever it enters a
scene.
Tests: dragAfterLeavingTheScene, dragWhenCreatedOutsideAScene (both
fail before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Shortcuts page now shows:
- each command's icon, and a Menu column saying where it is
("Projet › Scripts"); the text filter matches that too
- a category filter beside the existing Bound/Unbound/Conflicts one
- "Rechercher par touche": press a key combination and only the
commands using it are listed, however the key is spelled in the
user's language. A key also finds the two-key shortcuts it starts.
- "Copier la liste": the shown rows go on the clipboard as
tab-separated text with a header, so they paste into a spreadsheet
one cell per column
ShortcutManager::ShortcutInfo carries the icon and the first live
QAction so the page needs no second registry.
New test tst_shortcutspage covers the menu path, the columns, both
filters and the copied text.
Second stage of discussion #1319.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A symbol text with a width (text_width) is drawn wrapped on the folio,
but the DXF export split texts on '\n' only, so it was written as one
long line, running past its box and over the drawing. The export now
writes the lines of folio texts as they are laid out
(TextLines::layoutLines(), new header-only textlines.h). Texts without
a width give the same lines as before.
Tests: tst_textlines, tst_dxfwrappedtext (fails without this change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TextResizeHandles puts four handles at the corners of a selected text
(any QGraphicsTextItem with a qreal "textWidth" property, -1 = automatic
width). Dragging one changes the width: the opposite corner stays in
place in scene coordinates, for any rotation and rotation point, the
text wraps and the height follows it. Vertical mouse movements are
ignored, the width never goes below the longest word and is rounded to
whole pixels, and a click without a move changes nothing.
Double-clicking a handle goes back to the automatic width.
The handles are a child of the text, so they follow it when it moves,
rotates or is zoomed (as for the symbol text handles, #1002). The width
is changed live; the owner makes it undoable with TextResizeCommand,
which sets the width then the position on both undo and redo -- a text
may move itself when its width changes, so two property commands would
restore them in the wrong order.
The geometry is header-only (textresize.h). Nothing uses the handles
yet: the follow-up changes add them to free texts and to text fields in
the element editor.
Test: tst_textresizehandles, on a plain QGraphicsTextItem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A free text (IndependentTextItem) gets a textWidth property: -1 is the
automatic width (one line, or lines broken by hand, as today); a
positive width makes the text wrap between words. Its top-left corner
stays in place. The property is what a spin box and resize handles will
set in follow-up changes.
File format: the width is saved as text_width on <input>, only when one
is set, so a project without widths is saved byte-identical
(tst_resaveunchanged). An older QElectroTech ignores the attribute and
shows the text on one line, with no load error.
DiagramTextItem::setHtml() no longer widens a centred or right-aligned
rich text to idealWidth() + 40 when the text has a width of its own;
with the automatic width it still does.
Test: tst_freetextwidth (--resave keeps the width only where set,
--export-svg draws the text wrapped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- replaceImage(), mirror() and setTransparentColor() no longer add a
dummy QUndoCommand child: the imageSource child already keeps
QPropertyUndoCommand::mergeWith() from merging two of them.
- The explanation of the position and pivot maths moves from crop() to
applyCrop(), where that code now lives; the stale older doc block of
crop() goes.
- applyCrop() returns false when nothing was cropped, and
qet.cropImage() passes that on. tst_imagecropundo checks it for the
current crop, an empty rectangle and one outside the picture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #849 the parts list has a row for every contact block (slave) and
terminal-type element. Users who draw the dots and bends where wires
branch as terminal-type symbols (114_connections) get one empty row per
junction: 109 of 151 rows on one reported cabinet.
Two options, both off by default so existing exports are unchanged:
- leave out the contact blocks: --no-slaves, "no_slaves" in qet_export,
or uncheck the new "Contacts esclaves" element type in the dialog;
- leave out the junctions: terminal-type elements with no label,
designation, manufacturer or manufacturer reference. --no-junctions,
"no_junctions", or "Laisser de côté les jonctions" in the dialog.
A terminal block with a label or a part number stays.
The element type filter had no box for slaves, so every query it built
left them out: the export dialog never listed contact blocks, although
the command line has since #849. The dialog now checks the new box by
default and gives the same rows as --export-bom. The box is unchecked by
default elsewhere, so nomenclature tables keep their rows.
docs/smart-device-bom.md still said slaves and terminals were excluded;
corrected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewriting the links copied the old trailer up to %%EOF, so the old
startxref stayed in the file before the new one. Its offset is the size
the file had before the rewrite, and the links carry the output path,
so the same project exported to two folders gave different bytes, and
a different document id. Both rewrites now copy the trailer dictionary
only, through one helper; the print window's rewrite no longer leaves a
stray %%EOF either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Crop a picture as the crop tool does, in one undo step, and read its
crop rectangle back. tst_imagecropundo uses them on the real binary:
a crop that was undone is not saved, and redoing it saves it again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each folder now holds icons from one source: qet/ for QET's own SVGs,
breeze/ for KDE Breeze (with breeze/added/), flags/, colors/, and
themes/ for script output (with themes/generated/). The unused
128x128/terminalstrip.svg is removed. The generator, qrc files,
qeticons.cpp and ico/copyright follow the new paths. ico/README.md
lists each folder's source, script and license.
Part of #1269.
A conductor draws a junction dot at each of its bends that lies on
another conductor. It only looked at the conductors on its own two
terminals, so a bend lying on a conductor of the same potential that
shares no terminal with it got no dot. Dragging the horizontal parts of
a chain of conductors onto one line, as in the report, does exactly that.
Look at every conductor of the potential on the folio instead
(relatedPotentialConductors(false)). The folio scene uses NoIndex, so a
scene lookup at each bend scans every item and made export of the
366-conductor Polonez example 60% slower; walking the potential costs
nothing measurable there (2.08 s vs 2.11 s) or on a 153-conductor
potential (0.97 s both).
The test for one other conductor moves into bendMakesJunction(). It
also stops a point being appended once per matching segment: master drew
the dot above K2 in the fixture ten times.
Across the 23 examples (133 folios) exported to SVG, 6 dots are added,
all at T-joints that had none, and none removed.
tst_junctiondots exports fixtures/junction_dot_shared_potential.qet and
checks for both dots; it fails on master with the dot above K3 missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With SOURCE_DATE_EPOCH set, the document id came from the project's uuid
and a hash of the .qet file. A project generated again from the same
data has new uuids, so its PDF differed in the id alone (XMP DocumentID
and trailer /ID, 120 bytes) although every page was the same.
Qt now writes a fixed placeholder id, and once the file is written the
placeholder is replaced with an id derived from the bytes of the PDF:
the same pages give the same id. Same length, so no offset changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test set the elements folder in a settings file under XDG_CONFIG_HOME,
which macOS does not read, so common:// paths did not resolve and the
first placement failed. It now passes --common-elements-dir.
Schemas now get saved with a UUID and do not have to be looked up according to the form. They can be renamed and it gets reflected in existing elements. The GUI for
editing them is now much easier, e.g. forms can be reshuffled. A formula can be applied. Many options now also for conductors and sheets.
Review of #1272: scorpio810 asked for a warning box for users; the inline
box added in 9a55939bb is replaced by a pop-up, as the element editor
gives for unnamed terminals. When OK in Settings > General or in Project
properties turns a rule on (a limit where there was none, or one wire
per folio report), a warning says the rules are experimental, may change
with settings to redo if wires and conductors become separate objects,
and can all be turned off in Settings > General. OK goes on. Changing a
limit that was already on, or turning rules off, shows nothing.
tst_wiringrules: when the warning is due (WiringRules::turnsRuleOn()).
Checked in the GUI from both pages, and that a plain OK shows nothing.
English for translators: "Warning"; "The wires-per-terminal rules are an
experimental feature. They count wires as QElectroTech represents them
today. They could change, and your settings need redoing, if wires and
conductors become separate objects in a future version. All these rules
can be turned off in Configure QElectroTech > General."
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
The project's CI builds QElectroTech without scripting, where --run is
not an option and the process never exits, so the test waited out its
timeout and failed (#1272's stack, CI run 37118548599). The --run checks
now skip there, as the script tests are left out of such builds; the
rules and the save round trip still run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
With "one text per potential", deleting the wire that carries the text
moves it to another wire of the potential, but only if the text
isVisible(). While wire texts were hidden by View > Show none was, so the
text was never moved and the potential came back with no text.
ShownKinds::wantsVisible() also counts a text hidden only by its kind.
Also drop the always-true/false enum range checks clang warned about.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The limits were per project only. They are now set once in Settings >
General, under the master switch: the most wires per terminal and "one
wire per folio report". Every project follows them.
Project properties > "Conducteurs par borne" gets "Utiliser les réglages
de l'application", ticked by default, showing the application's values
greyed. Unticked, the project keeps its own values, saved in the project
as before, so a drawing that needs its own limit carries it to any
computer. A project's own "no limit" overrides an application limit too.
QETProject::wiringRules() now returns the rules in force (the project's
own, or the application's), so the code that checks a wire is unchanged;
projectWiringRules() returns what the project itself stores. A project
that follows the application still writes nothing, so it saves exactly as
before.
tst_wiringrules: which rules win, the save round trip of a project's own
"no rule", and through the real binary that the application's limit
refuses a wire unless the project overrides it or the master switch is
off (checked to fail with the application's rules ignored). ctest 55/55.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
qet.deleteElement() built its DeleteQGraphicsItemCommand from the element
alone. The Delete key's selection also carries the wires on the element's
terminals (DiagramContent's conductors to update), and the command removes
those with it. From a script they stayed: still listed by
qet.conductorUuids(), still saved, attached to an element that was gone.
The wires on the element's terminals now go into the command, as for the
Delete key. tst_scriptconductoruuid checks that no wire is left with an
end on the deleted element (it failed before this change: 1 left).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
Discussion #1158, stacked on the limit itself. Two of QElectroTech's own
tools wire several terminals as a star, every terminal to one of them:
"create wires in a drawn polygon" (also qet.addConductor()), and deleting
a symbol, which rewires the far ends to keep the potential. Six terminals
give the hub five wires, which the project's limit forbids.
When the project sets a limit (and the master switch is on), both now
wire the terminals one after another instead, from the top left one to
the nearest not yet wired (WiringRules::chainOrder()). The polygon tool
also skips a wire whose terminal is already full. Without a limit, both
build the star as before.
tst_wiringrules: the chain order, and through the real binary that
deleting a symbol wired to four others leaves them with at most two
wires each under a limit, three on one of them without (checked to fail
with chaining turned off).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
qet.deleteElement() built its DeleteQGraphicsItemCommand from the element
alone. The Delete key's selection also carries the wires on the element's
terminals (DiagramContent's conductors to update), and the command removes
those with it. From a script they stayed: still listed by
qet.conductorUuids(), still saved, attached to an element that was gone.
The wires on the element's terminals now go into the command, as for the
Delete key. tst_scriptconductoruuid checks that no wire is left with an
end on the deleted element (it failed before this change: 1 left).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
Discussion #1158: a real terminal takes one or two wires (four with
double ferrules), a folio report is a virtual point that carries one, but
QElectroTech lets any number of wires be connected anywhere.
Project properties > General gets a "Conducteurs par borne" group:
- the most wires a terminal may take (0, the default, is no limit);
- "one wire per folio report".
When set, a new wire that would go past the limit is refused: the
terminal shows red while dragging, as for any refused link, and a tooltip
says why on release. Auto-connect on placing, moving or multi-pasting a
symbol skips full terminals, and qet.addConductor() refuses with its own
message. Wires already drawn, opening, pasting and undo are never
refused.
Settings > General gets a master switch, on by default, that turns every
rule of this feature off in every project; the project group is greyed
with a note while it is off.
The rules live in wiringrules.cpp. The project setting is saved as
<wiring_rules .../> only when a rule is on, so a project that never used
it saves exactly as before; an older version drops it, which means "off".
tst_wiringrules covers the rules, the save round trip and, through the
real binary, that a wire past the limit is refused unless the master
switch is off (checked to fail with the limit check removed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
A submenu under View (Affichage > Afficher) with one tick per kind:
symbol texts, wire texts, free texts, shapes, pictures, tables and
cross-references. Unticking one hides it on every folio of every open
project; the status bar says how many kinds are hidden. Session only,
not saved in the project.
Each item is tagged with its kind by its constructor (ShownKinds::tag),
so an item created while its kind is hidden starts hidden whatever
created it. A tag rather than type(): the cross-reference under a
contact's label is a plain QGraphicsTextItem.
Items that already hide themselves (wire text switched off, one text per
potential, a cross-reference snapped elsewhere) now go through
ShownKinds::setVisible(): the kind state can only veto a show, and
apply() re-shows only what it hid itself (hidden_key), never what an
item hid. Conductor::updateTextVisibility() replaces the same visibility
rule pasted in diagrampropertiesdialog.cpp and qetscriptapi.cpp.
Hidden items are not selectable (Qt), so Select All, copy and delete
skip them. Print, PDF and image export render the scene and leave them
out; the DXF export walks items itself and skips them through
ShownKinds::isHidden(). CrossRefItem::linkedChanged() keeps following
its slaves while hidden only by kind, so its label is right when shown.
Known limitation: QGraphicsScene::itemsBoundingRect() still counts hidden
items, so Zoom to fit and borderless image export keep their space.
Verified: tst_shownkinds (12 cases, two mutation checks) and ctest 55/55;
GUI hide/show of symbol texts, wire texts (incl. one text per potential),
free texts, shapes and cross-references returns pixel-identical folios;
with nothing hidden, --export-dxf of the 24 examples is byte-identical to
master (133 files) and --export-pdf renders identically (133 pages).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD