Shapes and images can now be resized, rotated, and skewed directly
on the canvas, not just moved. Both share one small transform
struct (rotation, then skew, then scale, anchored on a movable
pivot) and one handle widget, so a corner drag, an edge skew, or
grabbing the rotate handle behaves the same way and runs through
the same matrix math everywhere, instead of every item type
reinventing its own.
Shapes also gained a proper pen tool (bezier paths, corner/smooth/
symmetric nodes), arc support, and mirroring. Images gained
non-destructive cropping and colour-keyed transparency, both
remember their own settings, so reopening the dialog picks up
where you left off instead of starting over.
Properties dialogs for both were extended to match (position,
size, angle, skew), with undo/redo wired through for every handle
drag.
Old XML files can read easily as the transformation is only added
if needed and the old syntax is still used and understood if it is
not needed.
Elements already inherited QetGraphicsItem::isMovable()/setMovable() --
the same mechanism images and drawn shapes use for their "lock position"
checkbox -- but nothing exposed it in the element properties panel, and
Element::toXml()/fromXml() never persisted it.
- ElementPropertiesWidget::generalWidget(): add a "Verrouiller la
position" checkbox mirroring ShapeGraphicsItemPropertiesWidget's
m_lock_pos_cb, toggling the element's inherited setMovable().
- Element::toXml()/fromXml(): persist is_movable, same attribute name
and default-true behavior as DiagramImageItem/QetShapeItem.
Verified via headless --resave round-trip: is_movable="0" survives
load -> save unchanged, existing elements without the attribute default
to movable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdWpDp3TrPKbHnv7YUcNJj
DiagramView::zoom() had the same unbounded scale() as the element editor:
a held scroll-wheel zoom could overflow the view transform. Clamp the
resulting scale to [m_min_zoom, m_max_zoom] before applying it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdWpDp3TrPKbHnv7YUcNJj
ElementView applied scale() on every wheel notch with no bound on the
resulting view transform. Held down, the scroll-wheel zoom drives the
transform scale (m11) to floating-point overflow; the transform becomes
non-invertible, mapToScene() returns NaN and the next background paint
aborts the editor ("program closes completely" as reported on Windows).
Route zoomIn/zoomOut/zoomInSlowly/zoomOutSlowly through a new
scaleClamped() helper that only applies the scale while the result stays
within [m_min_zoom, m_max_zoom] (0.1 .. 200). Behaviour within that range
is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdWpDp3TrPKbHnv7YUcNJj
CFBundleIconFile in misc/Info.plist was set to qelectrotech.icns, including the file extension.
Per Apple convention this key should hold the icon name without the extension — macOS appends .icns itself.
On recent macOS versions this mismatch causes the Finder to fall back to the generic placeholder icon instead of the actual app icon.
Fix: CFBundleIconFile → qelectrotech (no extension), matching the existing CFBundleTypeIconFile entries (elmt, titleblock, qet) which were already correct.
**1. `autonumberingmanagementw.ui`: legacy Qt5 font weight**
`uic` emitted `font.setWeight(75)` / `setWeight(50)` from old `<weight>` XML properties. Qt6's `QFont::setWeight()` takes a `QFont::Weight` enum instead of a raw int, so this fails to compile. GCC apparently tolerates it as a warning (`-fpermissive`), but it's a hard error on Apple Clang. `<bold>` was already set on every affected widget, so the `<weight>` tags were redundant — dropped.
**2. `CMakeLists.txt`: no macOS app bundle**
The executable target had no macOS-specific handling (`if(WIN32)/else()` only), so CMake produced a flat Mach-O binary instead of a `.app` bundle — nothing for `macdeployqt`/codesign/DMG steps to package. Added `MACOSX_BUNDLE` via `set_target_properties(APPLE)`. This in turn made `install(TARGETS ...)` fail configure (`no BUNDLE DESTINATION for MACOSX_BUNDLE executable`), so added `BUNDLE DESTINATION .`. Both changes are no-ops on Linux/Windows.
Built successfully end-to-end with `MacQetDeploy_arm64_cmake.sh` on macOS 14, arm64, Qt 6.11.1 (Homebrew) + KF6.
Without this, CMake produced a plain Mach-O binary on macOS instead
of a .app bundle, so MacQetDeploy_arm64_cmake.sh's cp/macdeployqt/
codesign steps had nothing to package. No effect on Linux/Windows.
Qt6's QFont::setWeight() now takes a QFont::Weight enum instead of
a raw int, so uic-generated code from the old <weight>75/50</weight>
XML properties fails to compile (worked on GCC via -fpermissive,
but hard error on Apple Clang for the macOS build).
<bold> is already set on all affected widgets, so <weight> was
redundant and can be dropped without any visual change.
Follow-up to the review of #790: four pre-existing messages whose source
is the DEGREE SIGN (U+00B0) were translated with the MASCULINE ORDINAL
INDICATOR (U+00BA) — GeneralConfigurationPage, IndiTextPropertiesWidget,
ReplaceConductorDialog and TextEditor. They render as an ordinal in the
rotation spin box suffixes.
Also fixes punctuation in the two SelectAutonumW help texts: a stray
space in "N ° página" / "n ° da página" and two unbalanced quotes.
Sources, comments, message count and ordering are untouched (2850
messages, 0 unfinished); .qm regenerated with lrelease.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9qWZfNpKZqrAzUE2QB3TJ
Finish the 364 messages still marked unfinished in lang/qet_pt_BR.ts,
bringing pt_BR from 87.2% to 100% of the 2850 messages. Of those, 228
were empty and are translated here; the remaining 136 carried Linguist
suggestions that were reviewed, 14 of them corrected. Several contexts
were previously untranslated in full: ContactGroupSelectionDialog,
PlcLinkWidget, TerminalNumberingDialog, ShortcutsConfigPage,
BackupDialog, DiagnosticsReportDialog, GuidesPropertiesWidget,
PdfPagesDialog and EdzArchive.
Terminology follows what the file already established: "borne" ->
"terminal", "bornier" -> "régua de terminais", "folio" -> "página",
"cartouche" -> "bloco de legenda", "schéma" -> "esquema",
"maître/esclave" -> "mestre/escravo", "pivoter" -> "girar". PLC terms
use the Brazilian abbreviation CLP, and NO/NC contacts use NA/NF.
Notable fixes among the reviewed suggestions: "Annuler" read "Desfazer"
(undo) where it is a dialog button next to OK, and the degree symbol
used U+00BA MASCULINE ORDINAL INDICATOR instead of U+00B0 DEGREE SIGN.
Only <translation> elements are touched; sources, locations and
comments are unchanged. lang/qet_pt_BR.qm is regenerated with lrelease.
QETProject::removeDiagram() detaches a diagram from m_diagrams_list and
schedules it via deleteLater(), but that deferred delete only runs on
a future event-loop iteration. If ~QETProject() runs first (e.g. a
CLI/headless caller with no event loop, or a project closed
immediately after removeDiagram()), the diagram is still a QObject
child of the project and gets destroyed later by QObject's own
automatic child cleanup -- which runs after m_data_base has already
been torn down as a plain C++ member. Diagram::~Diagram() calls back
into dataBase()->removeElement() for each of its elements, so that
ordering is a use-after-free (SIGSEGV in QSqlResult::exec()).
Delete any such still-parented diagrams synchronously in ~QETProject()
while m_data_base is still alive, before the base QObject destructor
runs. Any deleteLater() event that does eventually fire afterward is a
safe no-op on an already-deleted QObject.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ElementQueryWidget::queryStr() reads FROM element_nomenclature_view, and that
view already excludes flagged elements in its own WHERE clause (see
createElementNomenclatureView() in projectdatabase.cpp). This widget then
added a second condition on top: "exclude_from_bom IS NULL OR
exclude_from_bom != '1'" -- but nothing anywhere ever writes the literal
string "1" to this key (the only writer stores "true"/"false"), so the
clause was true for every row that could possibly reach this point and did
nothing.
Confirmed dead three separate ways while reviewing qelectrotech#765: reading
the value only ever comes back "true" or "false" (never "1"), an
exclude_from_bom="1" element still appeared in --export-bom output on a test
fixture, and the surrounding filter_ construction shows this AND'd clause
cannot change the query's result set regardless of what filter_ already
holds. Confirmed it a fourth way once already, by initially misreading this
same clause as evidence the feature was broken -- it was reading the WHERE
without the FROM three lines above, which is exactly the trap being removed
here for the next reader.
ElementQueryWidget backs the BOM export dialog and the diagram table
properties widget; neither has a headless CLI equivalent, so this could not
be verified end-to-end through --export-bom the way the case-insensitivity
fix could. Verified instead: the file compiles clean, and a
load/resave/--export-bom smoke test on examples/tremie_vibrante.qet shows no
change in app behaviour (98 components, matching the pre-change baseline --
expected, since --export-bom does not go through this widget at all).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Remove dead #if QT_VERSION conditionals (both branches were identical)
- Add settings.remove() guard on restoreState() failure consistently
across all three editors (now safe since all run after show())
The Windows Qt5 build is going to be removed from CI/CD soon (Qt6/KF6
is becoming the sole supported track). Replace the 'experimental'
warning wording on the nightly download page and in the nightly
release body with a call to action inviting users to switch to Qt6
now and report issues, ahead of the Qt5 removal.
QPdfDocument::pagePointSize() (used for PDF page import) requires
Qt >= 6.4, and the QtPdf module itself is missing entirely on some
Qt6 distributions (e.g. the Flatpak org.kde.Platform runtime), since
it ships from the qtwebengine source tree rather than Qt6 core.
CMake: probe Pdf with find_package(... QUIET) instead of REQUIRED,
mirroring the existing GuiPrivate pattern. Define QET_HAS_QTPDF
only when the module is found AND Qt >= 6.4.
Replace the ad-hoc QT_VERSION_CHECK(6, 0, 0) / (6, 4, 0) guards in
qetdiagrameditor.cpp, diagrameventaddpdf.{h,cpp} and
pdfpagesdialog.{h,cpp} with #ifdef QET_HAS_QTPDF, so version and
module-availability checks live in one place.
Fixes the Flatpak build (missing Qt6Pdf) and the Windows/Debian CI
failures (QPdfDocument::pagePointSize undeclared on Qt < 6.4). The
PDF import toolbar action is now silently unavailable wherever
QtPdf isn't usable, instead of breaking the whole build.
Compiled into the binary but never instantiated -- searching the tree
for any reference outside its own three files finds nothing, and this
still holds on current master. Contains a latent bug that would be
user-visible if the widget were ever reachable
(on_tableDiagram_customContextMenuRequested compares QMenu::exec()'s
return value against one action but falls through to "select all" on
both the other action and on a plain dismiss, since exec() returns
nullptr on Escape/click-away and that's not equal to either QAction*),
which supports genuine disuse rather than temporary disconnection.
Removed the three files and their three explicit entries in
cmake/qet_compilation_vars.cmake (qelectrotech.pro globs sources/ui/*
so needs no change). Builds clean; no other file references
diagramselection.
Fixes#756.
m_jump_to_element was the only action in the tree that set its
QKeySequence directly instead of going through
ShortcutManager::registerAction() -- of 98 actions carrying a runtime
shortcut, 95 matched a registerAction() call, 2 were Qt built-ins, and
this was the sole exception (verified by dumping every QAction from a
running instance and cross-checking against a static scan of the
source; the only other setShortcut() call in the tree clears a
shortcut rather than setting one).
Bypassing the registry meant the binding didn't appear on the
Shortcuts preferences page (so it couldn't be discovered or rebound),
and checkConflicts() couldn't see it either, so assigning Ctrl+G to
another action there would silently collide at runtime instead of
being flagged.
Fixes#758.