In Projet > Propriétés du projet > Nouveau folio, the Page Num box shows
a "Créer un Folio Numérotation Auto" entry when the project has no folio
auto-numbering. Pressing OK, even without touching anything, returned
that entry's text as the numbering's name: it was saved in the project
as auto_page_num (in the interface's language), and since it differed
from the stored empty value the project was marked modified.
When the project has numberings, an empty stored value cannot be shown
at all, so the first numbering came back instead.
Return the stored value unless the user picks an entry (activated() is
only emitted for a user's choice), and never return the placeholder.
Checked in the GUI on grafcet, schema_indus and Projet_vierge: after OK
with no edit and Ctrl+S, master saves auto_page_num="Créer un Folio
Numérotation Auto"; this branch keeps auto_page_num="". An edited field
on the same page (default author) is still saved.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The conductor properties dialog disables the "Texte visible" checkbox
when the folio has "Afficher un texte de potentiel par folio" on, but it
also hides that option, so nothing told the user why the box could not
be ticked. A wire saved with its text off then looked impossible to fix.
The locked box's tooltip now names the folio option and where to change
it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AssignVariables::replaceVariable() is a fixed list of replacements. Every
auxiliary family is listed for blocks 1 to 4 except the machine
manufacturer reference, which stopped at the plain
%{machine_manufacturer_reference}. A text using one of the four
auxiliary forms printed the variable itself on the folio and in exports.
Add the four, next to their manufacturer-reference neighbours.
Test: tst_resaveunchanged resaves a project with one terminal whose
texts combine each machine reference variable with its neighbour, and
checks the saved texts carry the values. It fails without the fix (the
variable is saved as typed) and passes with it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A contact (slave) not linked to a coil has no element to take its label
from: DynamicElementTextItem::elementUseForInfo() returns null. Since
8985babfe (#978) the composite-text branch of updateLabel() called
element->actualLabel() without checking, so a text built from %{label}
on such a contact crashed QElectroTech while the project was loading
(QETProject::refresh -> Diagram::refreshContents). The project could not
be opened again.
Check the element as the ElementInfo branch above already does; with no
element, %{label} shows empty, as it did before 8985babfe.
Test: tst_resaveunchanged opens a blank project holding one unlinked
contact with a %{label} text. It fails without the fix (the --resave
crashes) and passes with it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ElementPictureFactory::pixmap() made a pixmap of whatever width and
height the symbol file declares. A symbol whose parts span 280 000 px
(made by the GUI fuzzer in the symbol editor) asked for a ~315 GB
pixmap. Under AddressSanitizer that aborts QElectroTech; the symbol then
sat in the user collection and every later start died loading its icon.
The preview is now drawn scaled down to fit 4096 px on its longer side.
The largest symbols in the shipped collection are 3160 px, so none of
them changes. The size is also bounded before it is rounded up to a
multiple of 10, so a crafted value near INT_MAX cannot overflow.
Checked with a user collection holding one symbol declared 280000 x
280000, on ASan builds: master aborts (out of memory, exit 1) 3/3 when
the collection is expanded; with this change QElectroTech keeps running
and lists the symbol, 3/3.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Closing QElectroTech with an edited project and choosing "Close without
saving" read freed memory. Destroying the project clears each folio's
undo stack, which signals the Projects panel to refresh that project.
The refresh reached GenericPanel::updateItem(), whose only statement was
QApplication::processEvents(). That ran the editor window's pending
deleteLater(), destroying the panel, and GenericPanel::addProject() then
carried on using it (genericpanel.cpp:142).
The call was added in 2013 (70b7cd7d1) to keep the window responsive
while the panel reloaded, when it also listed the whole element
collection. The collection has its own panel now; this one lists
projects, folios and title block templates. updateItem() is again what
its comment says it is: a hook that does nothing.
Found by the GUI fuzzer under AddressSanitizer. Reproduced 3/3 on master
51b122993 (open a project, move everything on a folio, Ctrl+Q, Close
without Saving: heap-use-after-free, exit 1); 0/3 with this change
(exit 0, no report), with the save prompt confirmed on screen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Windows the request read C:/users/...; the box above it already showed
C:\users\... Found testing the Windows package under Wine.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Projet > Scripts > Enregistrer une macro (also in command search and the
shortcut bar) records what is done on the current project until clicked
again. Under <data folder>/recordings/<id>/ it saves the whole project
before and after, the folio and selection at the start, and each step from
the undo history -- its name, the commands inside it, the folio, what was
selected, and the folio after it. No editing command is taught to the
recorder, and nothing new walks the scene: the files are QElectroTech's
own serialisation.
While recording, the status bar shows "● Enregistrement : N étapes" with
Arrêter. At the end a box says where it went and offers "Copier la demande
pour l'assistant": a ready-made request naming the recording, to paste
into the assistant's chat, since QElectroTech cannot send it anything
itself. qet-assistant.json lists the recordings, and live status says
whether one is under way and which was last.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The socket name and token now go in the "live" part of the one file the
qet MCP server reads about this QElectroTech, rather than in a
live-session.json of their own, and are cleared when the channel closes.
The file also says whether the live setting is on.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
QElectroTech now writes qet-assistant.json in the standard data folder
when an editor opens, and again whenever the stored scripts or the
scripting setting change: its version and program, every folder (data,
settings, scripts, the element and title block collections), which
features are on, every call a script can make (QetScriptApi::signatures(),
apiSignatures() without a running script), and the stored scripts with
their ids, files, icons and shortcuts, plus those refused and why. On
quit it says nothing is running any more.
The qet MCP server reads it rather than guessing each folder per
platform, which is wrong as soon as QElectroTech runs with --data-dir:
the file stays where the server can find it and names the folders
actually in use. Readable by the user only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
While the user types to the assistant, QElectroTech is not the active
application, and QMdiArea then reports no active sub-window: status said
no project was open and every run was refused. Make the sub-window it
remembers active again before handling a request; the focus stays where
the user put it. Found with the Windows package under Wine; reproduced on
Linux with another window focused.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The qet MCP server connects once per request. On Windows a named pipe's
disconnection reaches QLocalSocket late, so the second of two quick calls
was turned away as "another assistant is already connected". Found
testing the Windows package under Wine with a Windows-side client.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A script the assistant writes on the spot is shown to the user before it
runs: Exécuter, Refuser, or Toujours pour cette session. Stored scripts
run as a click would. The choice is per session and never saved.
An "Assistant" dock lists every action with the time, ✓ or ✗, and its
undo step; the script, its log or its error show on hover and on a
double-click. It opens with live mode and holds the ask-first box.
The assistant may also:
- trigger an editor command from an allow-list of ones that open no
dialog and that undo can take back (selection, zoom, rotate, snap,
group, reset wires); saving, deleting, exporting and the rest are
refused;
- show another folio;
- undo the newest step only if it made it ("Assistant : …");
- take a picture of the folio on screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new setting, Configurer QElectroTech > Général > "Autoriser un assistant
IA à agir sur le projet ouvert", off by default. Off, nothing changes:
no channel is opened. On, every start shows a warning first -- Continuer,
Pas pour cette session, or Désactiver -- and nothing can connect until
the user answers Continuer. It waits for any other start-up question to
be answered, so the two never stack.
Accepted, LiveServer opens a local socket only the user's account can
use, with a random name and token written to live-session.json in the
data folder for the qet MCP server, and removed when the channel closes.
One JSON request per line: status (project, folio on screen, selection,
last undo step, stored scripts), run_script and run_stored. Requests are
queued out of the socket handler before they run (the lesson of PR #861).
Each run is one undo step named "Assistant : <name>". The status bar
shows the mode and the assistant's last action with a ✓ or ✗ and the
time, and an Arrêter button that closes the channel for the session.
Unticking the setting closes it at once.
QetScripting::runSource() runs script text and, for a live run, returns
what qet.log() wrote, the error with its line and the undo step instead
of showing boxes; qet.showMessage() is logged rather than opening a box
nobody asked for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#1210. The "Renvoi de folio" submenu offered the common coming and
going arrows only while the project had no folio report element yet.
Placing one copies it into the embedded collection, so from then on the
menu listed just that one and the other arrow could not be inserted until
the project was closed without saving.
The two common arrows are now always listed, after the project's own
report elements, each name once.
Checked in the GUI on a blank one-folio project: on master the submenu
drops from two entries to one after a coming arrow is placed and the
second pick places another coming arrow; with this change it keeps two
entries and the second pick places a going arrow (both in the saved file).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Projet > Scripts > Gérer les scripts… lists the stored scripts with their
icons, and the files that get no button with the reason. For each one it
edits the name, icon (a file copied next to the script, a theme icon, or
the initials), tooltip, shortcut, when it is enabled, and the script
itself; "Tester" saves it and runs it on the current project, one Ctrl+Z
to undo; "Supprimer" deletes it with its icon if no other script uses it.
It only reads and writes the files in the scripts folder, so a script
written here, by hand or by an assistant over the qet MCP server is the
same thing, and the folder's watcher turns each into a button.
ScriptHeader gains compose(), bodyOf() and idFor(), header-only and
tested: what the manager writes reads back as what was typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read from the meta-object, so the list is the one the running build has:
for someone writing a script, and for an assistant that has to write one
without the source at hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every .js file in the "scripts" folder of the user's data folder that
starts with a // ==QETScript== header becomes a command: in Projet >
Scripts, as a button on a new Scripts toolbar, and, because it is
registered with ShortcutManager as diagrameditor.script.<file name>, in
the shortcut settings, the shortcut bar (S) and command search. The
header gives its name, icon (a file next to the script or a theme icon;
a tile with its initials otherwise), tooltip, default shortcut and when
it is enabled (always, with a selection, with a conductor selected).
The folder is watched, so a script added, edited or deleted while QET is
open appears, changes or goes without a restart. A file with a header
that cannot be used gets no button; the Scripts menu lists it with the
reason. The menu also opens the folder, and holds "Exécuter un script…".
A click runs the script on the current project as one undo step named
after it, and asks to switch scripting on first, like "Exécuter un
script…" does: scripting stays off by default.
ShortcutManager::unregisterAction() takes a command out of the lists
when its script is deleted, and lets it come back under a new name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A script started from the editor had no way to know which folio is on
screen, and each qet.* call was its own undo step, so a script that adds
twenty items needed twenty Ctrl+Z to take back.
qet.currentFolio() returns the folio shown in the editor; through --run,
which has no view, the first folio, or -1 when there is none.
A run from the editor is now one undo macro, named after the script. A
run that changed nothing leaves no empty entry behind. qet.undo() and
qet.redo() inside a grouped run say why they cannot work (QUndoStack
ignores them inside a macro) instead of failing silently. --run is
unchanged: one step per call, so scripts that call qet.undo() keep
working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "one text per potential" switch (onetextperfolio) lives in a folio's
conductor defaults, which no scripting call or qet_edit op could reach, so
callers patched the saved .qet afterwards (#1178).
qet.setConductorDefault(folio, property, value) sets onetextperfolio or any
setConductorProperty name on a folio's defaults, or with folio -1 on the
project's defaults that new folios copy. A change to onetextperfolio
re-shows or hides the conductor texts at once, as the Folio properties
dialog does. Not on the undo stack, like both dialogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Ajouter un congé" on the drawing toolbar, in Édition > Ajouter and in
the command search. Click two lines one after the other, or once where
they meet, and a small dialog asks for the radius (remembered for the
next one); Enter makes the fillet. The tool stays on for the next
corner; Escape or a right-click ends it.
The two lines are trimmed -- or extended, when they did not reach each
other -- to the points where the arc touches them, and an arc is added
between them. For each line the part kept is the one on the side of
the corner where it was clicked; a click on the corner keeps the
longer part of each. Parallel or nearly aligned lines, and a radius
too big for the lines, are refused with a status-bar message.
The arc is an ordinary Ellipse shape with a start and end angle, as
the Arc tool and the ellipse's arc handles save it, so nothing new is
written to the file. It is added by its own small undo command rather
than AddGraphicsObjectCommand, whose redo() calls setPos(), and
QetShapeItem::setPos() snaps the outline's corner to the grid, which
would pull the arc off the lines. One undo step undoes the fillet.
New icon draw-fillet, light and dark, registered like the Align icons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1205:
- The scene gives no order for its selection, so the "first" wire whose
values the panel shows could change between two selections of the
same wires. The wires are now sorted top to bottom, then left to right.
- A text field the selected wires do not agree on (function, section,
cable...) is shown blank with the hint "Plusieurs valeurs". Typing any
value there, including the first wire's own, now differs from what was
shown, so it reaches every wire: 1.5 mm² on a 1.5 and a 2.5 wire works.
- The conductor text is locked while several wires are selected: the same
number on all of them would make duplicates. "Apply to all conductors
of this potential" with one wire selected still sets the number on the
whole potential.
- One edit of N wires emitted N signals, each reloading the panel. They
are now gathered into one reload, which matters since a reload reads
every selected wire to find the mixed fields. Selections and targets
are de-duplicated with sets, and a wire reached through an earlier
wire's potential is not walked again. An edit that changes nothing
returns before any potential is walked.
The rules move to sources/conductormultiedit.h, templates over the wire
type, so tst_conductormultiedit can check them against a fake wire:
order, targets with and without potentials, one undo step and its redo
for three wires, and the mixed fields. tst_conductorapplychanges now
gives the other wire the opposite value in every boolean field, so
copying one by mistake fails the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The middle handle of a half arc sits exactly on the middle of the top
or bottom edge of the ellipse -- a resize handle in Size mode, a skew
handle in RotateSkew mode -- and, drawn last, it covered that handle
(arummler, discussion #1203). It is now shown in Size mode only, where
the resize handle under it is hidden instead: the middle handle already
changes that height, with the ends kept in place. RotateSkew mode shows
its skew handle again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The search field gets a light green or light red background to show
whether the text matches, but only QPalette::Base was set. With a dark
theme the text colour is light too, so the typed text was light on
light and could not be read.
Set QPalette::Text to black together with the light background.
Fixes#1209
Ctrl+V moves the pasted group under the pointer as before. The new
Ctrl+Shift+V ("Coller au point d'origine", diagrameditor.paste_origin)
leaves the group exactly where it was copied from and warps the OS
cursor to the group's grid-snapped origin, so the paste baseline and
the physical cursor agree and the group starts following the mouse from
its original place instead of jumping to the pointer.
The cursor warp is only trusted as the baseline when it verifiably
lands (QCursor::pos() agrees with the target): QCursor::setPos() needs
pointer focus and is a silent no-op on compositors without
wp_pointer_warp_v1. Otherwise m_baseline_captured stays false and
moveTo() re-baselines on the first real mouse move, so a refused warp
costs at most the first movement rather than flinging the group across
the folio. An origin outside the viewport is scrolled into view first,
since the compositor rejects warp targets outside the window.
Selecting more than one wire left the Selection properties panel empty,
and so did a box selection over a single wire, because it also picks up
the wire's number label.
Now the panel opens for any number of selected wires. A selected wire
label counts as its wire, once, so a box drawn over wires works. The
fields show the first wire's values, a line above them says how many
wires are selected, and an edit is applied to each wire field by field:
setting the function on ten wires leaves each with its own number,
cable and section. With "apply to all conductors of this potential"
ticked, the potentials of all the selected wires follow the same rule.
Each edit is one undo step.
Fields whose values differ between the selected wires are not marked as
such; that can follow if wanted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "Show the properties of a selected conductor in the Selection
properties panel" switched on, editing a wire there could change things
the user never touched:
- Enter in a field (Function, Section...) is not used by the line edit,
so it reaches the checkable "Multifilaire" group box around it, which
takes it as a click. The wire was switched to single-line, gaining
ground, neutral and phase symbols. The modal dialog never shows this
because its OK button takes Enter first.
- Every edit wrote back the whole set of properties as the widget holds
them, so a value the widget cannot show exactly was rewritten: a dash
size of 1 became 2.
The panel now swallows Enter at its checkable group boxes, and writes
only the fields that differ from what it showed, through the new
ConductorProperties::applyChanges(). "Apply to all conductors of this
potential" uses the same rule, so the rest of the potential keeps its
own values too.
tst_conductorapplychanges checks each field on its own: change that one
field, and a wire whose every field differs takes it and keeps the rest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The diagram toolbar gets an "Ajouter un arc" button next to the
ellipse. Click one end, then click the other end at the height the arc
should reach: the result is a half arc bulging up or down from the line
between the two clicks (Shift: a true half circle). It is not filled,
since a fill would close it into a half disc.
A selected half arc shows one more handle, in the middle of the curve.
Dragging it makes the arc deeper or flatter while both ends stay put;
dragging it across the line between the ends turns the arc over. The
handle is hidden whenever the arc is not a half arc on a horizontal or
vertical diameter, because only then is "keep the ends, move the
middle" one well-defined change.
Nothing new is stored: an arc stays an Ellipse with a start and end
angle, exactly as the existing arc handles of an ellipse save it, so
files read by older versions are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>