Commit Graph

9661 Commits

Author SHA1 Message Date
ispyisail 7c4355338b Fix DXF export writing entities in a different order every time
Exporting the same folio to DXF twice gave files with the same entities
in a different order, so two exports never compared equal and a DXF could
not be diffed to spot a change. The export walked diagram->items(), whose
order follows memory addresses; saving had the same fault until
bugtracker #343.

Items are now taken in stacking order (z, then insertion order, which a
load makes the file's order), from the same rect query Diagram::toXml()
uses. Anything the query misses keeps its items() place after the rest.

Checked in the Export dialog on grafcet.qet, three folios, two exports
per build: master's differ every time, these are byte-identical, and both
contain exactly the same entities, only reordered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:28:36 +13:00
Laurent Trinques 44cabbcfe4 Merge pull request #1065 from ispyisail/feat/uuid-shapes-texts-images
Add a permanent id to shapes, texts, pictures and symbol parts
2026-09-27 09:55:25 +02:00
ispyisail a2a30ef7c4 Merge branch 'master' into feat/uuid-shapes-texts-images
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:51:23 +13:00
Laurent Trinques 0b4f480dfe Merge pull request #502 from ispyisail/proto-conductor-selection-properties
Add wire properties to the Selection properties panel, as an option (#500)
2026-09-27 09:11:22 +02:00
Laurent Trinques 05260de87b Merge pull request #1060 from ispyisail/feature/shortcut-bar-symbols
Add pinned elements to the shortcut bar, with a width you can set
2026-09-27 09:09:44 +02:00
Laurent Trinques db154a8d06 Merge pull request #1059 from ispyisail/fix/ctrl-shift-pan-stuck
Fix the folio staying in pan mode after Ctrl+Shift+M
2026-09-27 09:08:02 +02:00
Laurent Trinques 7dc1d345b0 Merge pull request #1057 from ispyisail/feature/mouse-gestures
Add right-drag mouse gestures to run commands
2026-09-27 09:02:44 +02:00
Laurent Trinques ba1811a0cf Merge pull request #1056 from ispyisail/feature/repeat-command
Add repeating the last drawing or placing command with Enter
2026-09-27 09:02:02 +02:00
Laurent Trinques a3dc8d3ad4 Merge pull request #1055 from ispyisail/feature/context-toolbar
Add a command bar beside the selection after a click
2026-09-27 09:00:29 +02:00
Laurent Trinques b1fe1ef7a7 Merge pull request #1054 from ispyisail/feature/shortcut-bar-customise
Add customising the shortcut bar in place
2026-09-27 08:59:55 +02:00
Laurent Trinques 9584fc4b7c Merge pull request #1053 from ispyisail/feature/shortcut-bar
Add a shortcut bar that opens at the cursor with S
2026-09-27 08:59:32 +02:00
Laurent Trinques 25baf48153 Merge pull request #1052 from ispyisail/feature/element-picker
Add an element picker that opens at the cursor with Insert
2026-09-27 08:58:50 +02:00
Laurent Trinques 7f51a3e10a Merge pull request #1051 from ispyisail/feature/ranked-search
Show collection search results as a ranked list
2026-09-27 08:47:30 +02:00
Laurent Trinques 503823bfce Merge pull request #1050 from ispyisail/feature/context-menu-placement
Add placing the last element and folio references to the folio menu
2026-09-27 08:47:00 +02:00
Laurent Trinques 30c97612a2 Merge pull request #1042 from ispyisail/feature/place-without-drag
Add double-click placement from the collection, and A to repeat it
2026-09-27 08:40:42 +02:00
Laurent Trinques 8cdf51405a Merge pull request #1066 from ispyisail/fix/readonly-query-all-rows
Fix table and script queries returning only their first row
2026-09-27 08:06:08 +02:00
Laurent Trinques 4a5755e552 Merge pull request #1068 from ispyisail/feat/349-image-label
Add a label to pictures (#349)
2026-09-27 08:04:44 +02:00
Laurent Trinques ef8b3866ba Merge pull request #1067 from ispyisail/perf/image-save-png-cache
Speed up saving projects that contain pictures
2026-09-27 08:04:29 +02:00
ispyisail f6f53e3f52 Stop re-encoding unchanged pictures on every save
DiagramImageItem::toXml() PNG-encoded every picture on every save,
autosave and copy, whether or not it had changed. That is most of the
cost of pictures in a project: resaving one holding 60 of them took
6.2 s and now takes 3.8 s.

The PNG bytes are now kept and reused while QPixmap::cacheKey() still
matches, so any edit (replace, crop, mirror, transparency) re-encodes
without each of those functions having to invalidate anything. On load
the cache is filled with the file's own bytes, so the first save
encodes nothing either.

Output is byte-identical to before on the example projects. A picture
whose PNG came from another encoder now keeps its original bytes
instead of being re-encoded; the pixels are identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 16:43:11 +13:00
ispyisail 5120feb417 Add an optional label to pictures (#349)
A picture can now carry a caption, set from its properties panel
("Libellé"). It is drawn centred under the picture at the folio's
normal text size whatever the picture's scale, turns with it, and
moves, copies and prints with it because the picture itself paints it.
Clicking the caption selects the picture.

Saved as a "label" attribute on <image>, written only when non-empty:
a project without labels saves byte-for-byte as before, and older
versions open a labelled project and simply ignore the caption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 16:12:51 +13:00
ispyisail cd38f6f2a2 qet-mcp: test the uuids given to drawn items in older files
A file written before drawn items carried a uuid gets them from the folio
uuid, the kind of item and its order in the file. Three integration tests
pin what that promises: opening the same file twice gives the same uuids,
the first save writes exactly those and a reopen keeps them, and a uuid
repeated in a hand-edited file ends up naming one item only.

Each fails when the derived uuid is replaced by a random one. They read
drawing_item_view unsorted, which is what found the first-row-only bug
in QETSql::execReadOnly() fixed by the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 14:00:16 +13:00
ispyisail 29fe731728 Fix read-only queries stopping after the first row
Since #1046, QETSql::execReadOnly() runs a query with PRAGMA query_only
set and switches it off before returning. Switching it off aborts a
statement SQLite is still stepping through ("abort due to ROLLBACK"), and
QSQLITE has already stepped to the first row by then. A query that
produces its rows as it goes -- a UNION ALL without ORDER BY -- therefore
came back with its first row only and no error. A sorted query was not
affected, because SQLite has read every row before returning the first.

Every query from the SQL box of a table, a saved <graphics_table> query
and the scripting qet.query() goes through here.

The checked run is now finished before query_only is switched off, and a
query that passed is run again for the caller. SQLite refuses a write at
its first step, so passing that step is what proves a statement reads
only; the second run is of a statement already shown to be read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 14:00:16 +13:00
ispyisail b740508b7f Fix read-only queries stopping after the first row
Since #1046, QETSql::execReadOnly() runs a query with PRAGMA query_only
set and switches it off before returning. Switching it off aborts a
statement SQLite is still stepping through ("abort due to ROLLBACK"), and
QSQLITE has already stepped to the first row by then. A query that
produces its rows as it goes -- a UNION ALL without ORDER BY -- therefore
came back with its first row only and no error. A sorted query was not
affected, because SQLite has read every row before returning the first.

Every query from the SQL box of a table, a saved <graphics_table> query
and the scripting qet.query() goes through here.

The checked run is now finished before query_only is switched off, and a
query that passed is run again for the caller. SQLite refuses a write at
its first step, so passing that step is what proves a statement reads
only; the second run is of a statement already shown to be read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 14:00:07 +13:00
ispyisail 76eea09192 qet-mcp: address texts, shapes and images by uuid; part uuids in symbols
- qet_edit: every op taking a text, shape or image "index" also takes its
  uuid, resolved at run time with textIndex()/shapeIndex()/imageIndex().
  Those are only required when a uuid is used.
- qet_element_build writes a uuid on every part (the caller's, or a new
  one) and returns them in part_uuids; qet_element_info lists part and
  terminal uuids.
- README and tests: drawing_item_view, uuid addressing, part uuids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail 30e83d8c52 Element editor: give every primitive a uuid
Lines, rectangles, ellipses, arcs, polygons, PLC tables and static texts
now save a uuid, as terminals and dynamic texts already did, so a tool can
name one part of a symbol. The graphic parts keep it next to their styles,
since all of them save through stylesToXml(); a part read from a
definition that predates this gets one on the next save.

Paste renews them, as it already did for terminals -- and now for dynamic
texts too, which were pasted with their source's uuid. A placed element
gives its dynamic texts fresh uuids anyway, so only the definition's own
identity changes. PartTerminal::uuid() returns the terminal's saved uuid
rather than inheriting the graphic-part one it never writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail 3a50cd9b0e Project database: tables for shapes, independent texts and images
Now that they carry a uuid, the folio's drawing furniture gets rows of its
own: shape, independent_text and image, plus drawing_item_view, which finds
any of them by uuid without knowing its kind first and says which folio it
is on.

Rows follow edits, not just loads. The script API's query() and every
other reader go through newQuery() without a rebuild, so an item's row is
queued on each change (moves, restyles, text edits, uuid renewal) and the
queue is flushed by newQuery() and updateDB() -- a queued write is a set
insertion, which matters for a drag that moves hundreds of items per mouse
step.

A pasted copy joins its folio still carrying its source's uuid and is only
renewed afterwards, so a flush in between must not let the copy overwrite
its source's row. Each row remembers the item that wrote it; another item
with the same uuid waits until uuidChanged() says it has its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail acb770909e DiagramPosition: compile the letter filter once
The constructor built QRegularExpression("[^A-Z]") on every call, and
compiling it cost more than the rest of Diagram::convertPosition() put
together -- about 95k instructions a call under callgrind. Every element
row of the project database pays it on each rebuild, as does anything
else that turns a scene point into a folio cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail a9ad6e2f34 Give shapes, independent texts and images a persisted uuid
Elements, conductors, terminals and tables already carry a uuid. The
drawing furniture beside them did not, so a script or the MCP server could
only name a line, a box or a note by its index in a position-sorted list,
which shifts whenever one is added or removed.

- QetShapeItem, IndependentTextItem and DiagramImageItem get uuid(),
  newUuid() and setUuid(), read from and written to a "uuid" attribute.
- A folio loaded from a file written before this (or carrying a duplicate
  uuid) derives one from the folio uuid, the item kind and its order in the
  file, so the same file gives the same uuids on every load and a re-save
  is stable -- the #754 lesson for conductors.
- Paste and folio duplication renew them, as they already do for elements
  and conductors.
- Scripting: texts(), shapes() and images() end each line with the uuid;
  textIndex(), shapeIndex() and imageIndex() turn one back into an index.
- misc/qet-mcp: qet_diff keys texts, shapes and images on uuid when both
  sides have one, so a move or edit reads as a change to that item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:54:54 +13:00
ispyisail ff795258c4 Conductor dock: turn it on in the preferences, off by default (#500)
Showing a selected conductor's properties in the selection-properties
dock is now a preference, on the General page under Appearance:
"Afficher les propriétés d'un conducteur sélectionné dans le panneau
Propriétés de la sélection". It is off by default, so selecting a
conductor does what it did before unless the user turns it on.

This replaces the View-menu toggle, which defaulted to on; that entry
is dropped in the merge with master. Same setting key
(diagrameditor/conductor_properties_panel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:13:55 +13:00
ispyisail 0cb424bd06 Merge remote-tracking branch 'upstream/master' into proto-conductor-selection-properties
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	lang/qet_en.ts
#	sources/qetdiagrameditor.cpp
2026-09-27 09:07:41 +13:00
ispyisail 47e5a8072b Shortcut bar: reopen at the cursor, wrap to a width the user sets
After customising, the bar now opens at the cursor like every other
time, instead of where the customising window was.

A size grip in the bar's corner sets its width. The tiles are laid out
in rows that wrap at that width, so a bar with many commands and pinned
elements becomes a block rather than one long strip; the height follows
the rows, and the width is saved. Until the user sets one, the tiles
stay on one row, as before. The grip is driven by hand: QSizeGrip asks
the window manager to resize, and on X11 a popup is not managed, so
nothing would happen. The hint line wraps rather than being cut off at
narrow widths.

The customising window remembers the size it was left at.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 03:16:56 +13:00
ispyisail 5bd55801e9 Pin elements to the shortcut bar
The bar's customising window gets an element search next to the command
list. Type part of a name, then drag a hit onto the bar, double-click
it, or press Enter to pin the best one. A pinned element shows as its
icon among the commands, and clicking it places the element, as the
picker does.

Pinned elements are saved in the same list as the commands, by
collection path (common://, custom://, company://), so the row keeps
the user's order. Elements embedded in a project are not offered: their
path names the project as loaded now. Elements are offered for the
empty-folio bar only; with something selected the bar is for acting on
it. Once any element is pinned there, the palette folder grid under the
bar is hidden, and comes back while typing a search.

Dragging a pinned element off the bar onto the commands, or a double
click, removes it. The Preferences page shows pinned elements by name
and icon, and removing one there drops it instead of listing it as a
command. The customising window is now kept on screen, since it is
taller than before.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 03:16:56 +13:00
ispyisail 5ccb089cdd Merge repeat-command into mouse-gestures (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 03:00:26 +13:00
ispyisail 416ababc41 Merge context-toolbar into repeat-command (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 63771a41b5 Merge shortcut-bar-customise into context-toolbar (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 57905a5789 Merge shortcut-bar into shortcut-bar-customise (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 451cccd615 Merge element-picker into shortcut-bar (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail af28a49b72 Merge ranked-search into element-picker (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 926bbcf75f Let search results be dragged onto a folio like the tree
While a collection search is active the tree is replaced by the flat
ranked list, and that list had no drag support: dragging a result only
moved the selection. Anyone used to searching and then dragging lost the
drag as soon as they typed.

The tree's drag is moved into a static ElementsTreeView::execElementDrag()
taking the source widget, and the results list uses it from the path each
row already carries, so the drag content and pixmap are the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:56:46 +12:00
ispyisail 6bcdb87524 Fix the folio staying in pan mode after a Ctrl+Shift shortcut
Holding Ctrl+Shift over a folio pans it, and only a key release seen by
the view ends that. A Ctrl+Shift shortcut that opens a window -- the
command search, Ctrl+Shift+M -- takes the keyboard before Ctrl and Shift
are released, so the view never sees the release. On Windows, where the
Shift press itself already reports Ctrl+Shift, the folio then stayed in
pan mode after a command was chosen: a hand cursor, and clicks ignored,
so a drawing tool picked from the search did nothing. On Linux the Enter
key's release happened to reach the view and end it.

The view now remembers that it pans because of Ctrl+Shift (not because
the hand tool was chosen) and stops when it loses the focus, or at a
click made without Ctrl+Shift, instead of waiting for a release it may
never get.

Reproduced on Linux by holding back Enter's release, as Windows does:
the line drawn after "Ctrl+Shift+M, une ligne, Enter" is not saved on
master and is with this change. Ctrl+Shift+drag and the hand tool still
pan (a dragged step does not move), as on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:35:56 +12:00
ispyisail f850d0bee8 Keep mouse gestures working while a tool is running
Most commands on the empty-folio ring start a drawing tool, and the view
left the right button alone while a tool ran, so the gesture after one
that started a tool only cancelled the tool: every other gesture opened
the context menu instead of the ring.

Now a right drag always shows the ring. A running tool is ended when the
drag starts, as picking another tool from the toolbar would. A plain
right click still goes to the tool, which cancels or finishes it as
before.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:34:55 +12:00
Laurent Trinques d7036ad807 Merge pull request #1046 from ispyisail/fix/1045-sqlite-handle
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m15s
Fix #1045: crash on clicking an element with online-installer Qt
2026-09-26 14:20:43 +02:00
Laurent Trinques 09fb81b99a Merge branch 'master' into fix/1045-sqlite-handle 2026-09-26 14:07:32 +02:00
ispyisail 501379def2 Merge repeat-command (with master) into mouse-gestures
# Conflicts:
#	cmake/qet_compilation_vars.cmake
2026-09-26 22:44:21 +12:00
ispyisail 52a831e1fa Merge context-toolbar (with master) into repeat-command
# Conflicts:
#	sources/qetdiagrameditor.cpp
2026-09-26 22:44:11 +12:00
ispyisail c1bb3b33f4 Merge shortcut-bar-customise (with master) into context-toolbar
# Conflicts:
#	cmake/qet_compilation_vars.cmake
2026-09-26 22:44:01 +12:00
ispyisail f91ec74dde Merge shortcut-bar (with master) into shortcut-bar-customise 2026-09-26 22:43:50 +12:00
ispyisail b08d0692b7 Merge element-picker (with master) into shortcut-bar
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	sources/qetdiagrameditor.cpp
2026-09-26 22:43:40 +12:00
ispyisail 627b818a30 Merge ranked-search (with master) into element-picker
# Conflicts:
#	sources/qetdiagrameditor.cpp
#	sources/qetdiagrameditor.h
2026-09-26 22:43:27 +12:00
ispyisail 0d2befbc00 Merge place-without-drag (with master) into context-menu-placement 2026-09-26 22:43:06 +12:00