Compare commits

...

89 Commits

Author SHA1 Message Date
Laurent Trinques f16cf7dac8 Merge remote-tracking branch 'origin/master' into qt6_cmake_joshua
# Conflicts:
#	CMakeLists.txt
2026-05-28 09:32:07 +02:00
Laurent Trinques 471d1f2538 Merge pull request #470 from Kellermorph/makro-fix
Fix: Wiring list filter and dynamic text timing
2026-05-27 06:19:06 +02:00
Kellermorph dc52105868 Fix: Wiring list filter and dynamic text timing 2026-05-26 18:31:37 +02:00
Laurent Trinques 1b8dc5f410 texteditor: set font size spinbox minimum to 4pt to prevent SIGSEGV 2026-05-25 13:14:57 +02:00
Laurent Trinques 26d5d019cc texteditor: fix SIGSEGV caused by font size spinbox reaching 0 2026-05-25 13:04:37 +02:00
Laurent Trinques 6dd7c2d926 crossrefitem: fix SIGSEGV crash + improve double-click navigation + fix PDF/SVG/print rendering
Windows Build / build-windows (push) Has been cancelled
Test Windows VS2026 migration / Build on windows-2025-vs2026 (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
Fix a use-after-free crash (SIGSEGV in QRegion::begin, Qt5Gui+0x49af60)
confirmed by analysis of 19 coredumps. The crash was triggered when the
scene viewport clip region was freed during zoom/resize events while
QPicture::play() replayed drawPolyline commands through the scene painter.
Qt's raster engine then dereferenced a stale QRegionData pointer.

Root cause: CrossRefItem used three nested QPicture objects (m_drawing,
m_hdr_no_ctc, m_hdr_nc_ctc). The nested drawPicture() calls amplified
the use-after-free risk on any repaint event.

Fix: remove all QPicture from CrossRefItem entirely.
- updateLabel() now uses a QImage-backed dummy painter to compute
  m_bounding_rect, m_shape_path and m_hovered_contacts_map geometry.
  A bool m_update_map flag prevents the map from being overwritten
  during paint().
- paint() calls drawAsCross()/drawAsContacts() directly on the scene
  painter — no QPicture::play() anywhere in the class.
- buildHeaderContact() now draws NO/NC symbols directly onto the painter
  instead of recording them into QPicture members.

Also fix mouseDoubleClickEvent: the element under the click is now found
directly from m_hovered_contacts_map using the event position, rather
than relying on m_hovered_contact which could be reset by hoverMoveEvent
between the two clicks of a double-click.

Also remove setBold(true) on terminal name labels: the Qt PDF/SVG/print
engine rendered bold at 4pt as extremely thick glyphs, making exports
unreadable. Normal weight at 4pt is correct and legible on all backends.

Fixes: SIGSEGV in CrossRefItem::paint() on zoom/resize
Fixes: double-click navigation unreliable on Xref contact symbols
Fixes: terminal name labels unreadable in PDF/SVG/print export
2026-05-24 16:00:45 +02:00
Laurent Trinques 47796e183a Update translation files 2026-05-23 19:28:22 +02:00
Laurent Trinques ddf5ffcd89 xref: add option to hide terminal names in cross-references
Add a new boolean property 'showTerminalName' (default: true) to
XRefProperties, with full persistence in XML and QSettings.

A new checkbox "Afficher les numéros de bornes dans les Xrefs" is
added to the XRefPropertiesWidget in the main display group (not in
the cross-only group), so it is active in both Cross and Contacts modes.

When unchecked, terminal names are hidden in all three rendering paths:
  - drawContact()            (Contacts mode: NO/NC/SW symbols)
  - fillCrossRef()           (Cross mode: NO and NC columns)
  - setUpCrossBoundingRect() (Cross mode: bounding rect sizing)

Backward compatible: existing project files without the attribute
default to showTerminalName=true (no visual change).

Files changed:
  sources/properties/xrefproperties.h
  sources/properties/xrefproperties.cpp
  sources/ui/xrefpropertieswidget.ui
  sources/ui/xrefpropertieswidget.cpp
  sources/qetgraphicsitem/crossrefitem.cpp
2026-05-23 19:23:43 +02:00
Laurent Trinques 7d718bb9a0 crossrefitem: fix terminal name sorting for power contacts
The previous sort used QString::toInt() to order terminal names,
which returns 0 for any string containing a non-numeric prefix
(e.g. "R1", "R2", "L1", "L2"...). This caused undefined sort order
and incorrect pole pairing in the Xref contact mirror.

Example: a 4-pole NC power contact with terminals R1..R8 was
displaying R1/R3, R5/R7, R2/R4, R6/R8 instead of the correct
R1/R2, R3/R4, R5/R6, R7/R8.

Fix: extract the trailing numeric part of each terminal name and
compare prefixes separately. If both names share the same prefix
and both have a trailing number, sort numerically on that number;
otherwise fall back to full string comparison.

This covers all naming conventions: "1"/"2"/"3", "R1"/"R2"/"R3",
"L1"/"L2"/"L3", etc.

Applied in both drawContact() and setUpCrossBoundingRect().
2026-05-23 15:50:11 +02:00
Laurent Trinques d949e6eb8c crossrefitem: fix SIGSEGV when dropping power NC contact on diagram
Three bugs were causing a crash (SIGSEGV in QRegion::begin) when
a power NC slave element was placed on a folio, even before linking
it to a master element.

1. m_drawing (QPicture) was never reset between updateLabel() calls.
   QPicture accumulates paint commands — calling qp.begin() on an
   existing QPicture appends to it rather than replacing its content.
   After several updates (load, move, hover...) the picture became
   corrupted and crashed on play().
   Fix: reset m_drawing = QPicture() at the start of updateLabel().

2. m_drawed_contacts was only initialized to 0 in drawAsContacts(),
   but not in drawAsCross(). When drawing in Cross mode, fillCrossRef()
   called drawContact() with an uninitialized m_drawed_contacts value,
   producing a garbage offset. The NC contact symbol uses drawPolyline()
   with a sub-pixel Y coordinate (offset+2.5); with a random offset Qt
   generated an invalid QRegion and crashed.
   This explains why NC contacts crashed but NO contacts did not: the
   NO symbol only uses drawLine() which is more tolerant of bad coords.
   Fix: add m_drawed_contacts = 0 at the start of drawAsCross().

3. setUpCrossBoundingRect() used QRectF() (null rect) as the reference
   rect for painter.boundingRect(), which can return invalid dimensions.
   Additionally, height was accumulated incorrectly: united() + setHeight()
   doubled the height at each iteration, causing an exponentially growing
   bounding rect with multiple contacts.
   Fix: use QRectF(0, 0, 500, 20) as reference rect and accumulate
   height and width independently.
2026-05-23 15:32:28 +02:00
Laurent Trinques dbda958261 terminaldata: add No, Nc, Common types for SW contacts
Extend TerminalData::Type enum with three new semantic values:
- No     : Normally Open terminal of a switch (SW) contact
- Nc     : Normally Closed terminal of a switch (SW) contact
- Common : Common terminal of a switch (SW) contact

Update typeToString() and typeFromString() accordingly.
Fully backward compatible: existing Generic/Inner/Outer types
are unchanged. Elements without typed terminals fall back
to the previous behavior (first 2 named terminals).

terminal: expose terminalType() as public accessor

Add Terminal::terminalType() returning the TerminalData::Type
of this terminal. This allows crossrefitem and other consumers
to filter terminals by semantic role (No, Nc, Common) without
accessing TerminalData internals directly.

terminaleditor: add No, Nc, Common entries to type combobox

Expose the three new TerminalData types (No, Nc, Common) in
the element editor UI so users can assign a semantic role to
each terminal of a SW contact element.

Also fix a pre-existing bug in updateForm() where m_type_cb
was incorrectly using m_orientation_cb->findData() instead
of m_type_cb->findData(), preventing the type from being
restored correctly when selecting a terminal.

terminaleditor: add No, Nc, Common entries to type combobox

Expose the three new TerminalData types (No, Nc, Common) in
the element editor UI so users can assign a semantic role to
each terminal of a SW contact element.

Also fix a pre-existing bug in updateForm() where m_type_cb
was incorrectly using m_orientation_cb->findData() instead
of m_type_cb->findData(), preventing the type from being
restored correctly when selecting a terminal.
2026-05-23 02:09:32 +02:00
Laurent Trinques d691489165 Update translations files 2026-05-22 10:16:24 +02:00
Laurent Trinques 202ea38e40 Merge pull request #464 from Kellermorph/makro-fix
New element: Line definition
2026-05-22 10:06:20 +02:00
Kellermorph 86dafcb576 Merge branch 'master' into makro-fix 2026-05-21 20:52:18 +02:00
Kellermorph f416c2a97e New element: Line definition 2026-05-21 20:47:44 +02:00
Laurent Trinques 7426fedba3 crossrefitem: display terminal names on contact symbols in Xref
When a slave element has named terminals in its element definition
(.elmt), the terminal names (e.g. 13/14 for NO, 11/12 for NC,
12/13/14 for SW) are now displayed on each side of the contact
symbol in the cross-reference view.

- NO/NC contacts: name[0] on the left, name[1] on the right
- SW contacts: name[0] (NO) top-left, name[1] (common) top-right,
  name[2] (NC) bottom-left

Terminal names are read from Terminal::name() which is populated
from TerminalData::m_name during element parsing. If terminals are
not named, nothing is displayed (fully backward compatible).

Users are expected to name their own terminals in the element
editor to avoid duplicating elements in the official collection.
2026-05-21 17:10:44 +02:00
Laurent Trinques 027050c7e7 Update windows-msi.yml 2026-05-21 14:32:07 +02:00
Laurent Trinques fc948ad963 QElectroTech.wxs — add ProductCode="$(var.ProductCode)" to <Package>
+ AllowSameVersionUpgrades="yes" to <MajorUpgrade>
windows-msi.yml — in the ‘Extract version’ step, calculate a unique GUID
based on the commit’s SHA, then pass -d ‘ProductCode=$productGuid’ to the WIX build
This ensures that each build will have a different ProductCode → MajorUpgrade will always be triggered
2026-05-21 14:10:17 +02:00
Laurent Trinques 24d075b64c msi: add The AllowSameVersionUpgrades="yes" setting forces uninstallation
even when the MSI version is identical (which is the case here, as 0.100.1.0
remains unchanged between two nightly builds).
2026-05-21 13:59:53 +02:00
Laurent Trinques f914f91e77 tests sign msi installer 2026-05-21 12:33:46 +02:00
Laurent Trinques fe03a0f643 Restore workflows to pre-test state 2026-05-21 12:29:22 +02:00
Laurent Trinques a7ad0278a6 Update windows-build.yml 2026-05-21 11:49:30 +02:00
Laurent Trinques f517489421 Update windows-build.yml 2026-05-21 11:15:25 +02:00
Laurent Trinques c8fa1c9fa4 test 2026-05-21 10:45:14 +02:00
Laurent Trinques d85aff0c0f Update CMakeLists.txt 2026-05-21 03:40:14 +02:00
Laurent Trinques 96b8e4b19c fix(msi): move Custom element conditions to Condition attribute (WIX0400)
WiX v7 requires conditions to be set via the Condition attribute,
not as inner text of the Custom element.
2026-05-21 03:00:49 +02:00
Laurent Trinques 9760288db6 fix(msi): correct CustomAction pattern for elements\ read-only
- Use SetProperty + WixQuietExec two-step pattern to pass runtime
  INSTALLDIR to a deferred CustomAction (fixes WIX1077 and WIX0400)
- Add WixToolset.Util.wixext/7.0.0 extension (required for WixQuietExec)
- Fix condition syntax: collapse multi-line conditions to single line
- Add -ext WixToolset.Util.wixext to wix build command in windows-msi.yml
2026-05-21 02:55:28 +02:00
Laurent Trinques eeaa059a77 This PR improves the MSI installer by removing the Lancer QET.bat wrapper and handling everything natively in QElectroTech.wxs.
**`build-aux/windows/QElectroTech.wxs`**
- Desktop and Start Menu shortcuts now point directly to `bin\qelectrotech.exe` with all required arguments (`--common-elements-dir`, `--common-tbt-dir`, `--lang-dir`, `-style windowsvista`) — no `.bat` wrapper needed
- Added a deferred `CustomAction` that runs after `InstallFiles` and recursively sets all files in `elements\` to read-only using an inline PowerShell command

**`.github/workflows/windows-msi.yml`**
- Replaced the step that created `Lancer QET.bat` with a step that removes it from the artifact before the WiX build, so it is not embedded in the MSI
- The `.bat` file remains untouched in the ZIP portable build (managed by `windows-build.yml`)

- No console window flashing when launching QElectroTech from the MSI shortcuts
- The `elements\` directory is properly set to read-only after installation, as required
- Cleaner MSI package — no `.bat` file shipped to end users installing via MSI
2026-05-21 02:34:38 +02:00
Laurent Trinques fa334d34a4 git submodule update --remote elements 2026-05-20 22:14:43 +02:00
Laurent Trinques 9664ff54ea Merge pull request #462 from Kellermorph/makro-fix
follow up: wiring list
2026-05-19 13:15:14 +02:00
Laurent Trinques be0da461bd Merge pull request #463 from zakb120/patch-Tr-Translate
Turkish Lang Update
2026-05-19 13:14:45 +02:00
Kellermorph 8c557a7f29 follow up: wiring list 2026-05-18 21:31:11 +02:00
zakb120 413e13a38c Tr Translate Zekeriya AKBABA 2026-05-18 09:29:46 +03:00
Laurent Trinques 87f9f40e91 Update MacQetDeploy_arm64.sh
Windows Build / build-windows (push) Has been cancelled
Test Windows VS2026 migration / Build on windows-2025-vs2026 (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-14 13:12:43 +02:00
Laurent Trinques 43fb34f852 Update MacQetDeploy_arm64.sh 2026-05-14 12:44:45 +02:00
Laurent Trinques d6251c901e Update MacQetDeploy_arm64.sh 2026-05-14 12:11:21 +02:00
Laurent Trinques c0ba961fb3 Update MacQetDeploy_arm64.sh 2026-05-14 11:45:11 +02:00
Laurent Trinques 56f5a09737 Update MacQetDeploy_arm64.sh 2026-05-14 11:20:36 +02:00
Laurent Trinques b5eebb2123 Update MacQetDeploy_arm64.sh 2026-05-14 11:06:56 +02:00
Laurent Trinques 7bf395afab Every Monday at 2 am (UTC) (cron) or manually
↓
  windows-build.yml
  ├── build-windows  →  generates an exe file + zip + portable artefact
  └── deploy-pages   →  clears old files, uploads the exe file + zip to the ‘release nightly’ repository
        ↓ (workflow_run: completed + successful)
  windows-msi.yml
  ├── uploads the portable artefact
  ├── builds the MSI with WiX v7
  ├── deletes the old .msi, uploads the MSI to the nightly version
  └── generates and deploys GitHub Pages  ← the 3 URLs are known here
The GitHub Pages page is no longer generated by windows-build.yml but by windows-msi.yml once the MSI is in the release
2026-05-14 09:53:29 +02:00
Laurent Trinques 93baa00d00 Update windows-msi.yml 2026-05-14 09:26:01 +02:00
Laurent Trinques 6a7ae44ce5 git submodule update --remote elements
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-14 03:21:35 +02:00
Laurent Trinques 89131a21a3 Merge pull request #459 from Kellermorph/makro-fix
Fix and Improve Multi-selection for Diagram Operations
2026-05-14 03:18:20 +02:00
Kellermorph 0b79dfd149 Fix and Improve Multi-selection for Diagram Operations 2026-05-13 19:42:11 +02:00
Laurent Trinques 72178714fd git submodule update --remote elements
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-13 17:25:39 +02:00
Laurent Trinques c7e236cd48 Update test-vs2026.yml 2026-05-13 13:43:55 +02:00
Laurent Trinques fb769b884c Try to fix https://github.com/qelectrotech/qelectrotech-source-mirror/issues/307
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-13 13:16:43 +02:00
Laurent Trinques 2ae9ec87bb Update windows-build.yml
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-13 01:40:55 +02:00
Laurent Trinques 76d311cb35 Move build-aux/windows/generate-page.py -> build-aux/generate-page.py 2026-05-13 01:23:18 +02:00
Laurent Trinques b016cc9f54 Update windows-build.yml 2026-05-13 01:06:17 +02:00
Laurent Trinques 526e39e909 Add files via upload 2026-05-13 01:05:31 +02:00
Laurent Trinques d781105dfd Update windows-build.yml 2026-05-13 01:02:32 +02:00
Laurent Trinques f6b93c6b71 Update windows-build.yml 2026-05-13 00:40:17 +02:00
Laurent Trinques 61319bbbd6 Update windows-build.yml 2026-05-13 00:08:18 +02:00
Laurent Trinques 1b522c251b Update windows-build.yml 2026-05-12 23:34:10 +02:00
Laurent Trinques acfdab77fa Update windows-msi.yml 2026-05-12 23:33:27 +02:00
Laurent Trinques 8e327448cc Update QElectroTech.wxs 2026-05-12 23:01:54 +02:00
Laurent Trinques 7a8cee0ce6 Update QElectroTech.wxs 2026-05-12 22:58:00 +02:00
Laurent Trinques 82b8e7947e Update windows-msi.yml 2026-05-12 22:54:20 +02:00
Laurent Trinques e542a05d3f Update QElectroTech.wxs 2026-05-12 22:53:33 +02:00
Laurent Trinques 0b337a1514 Update windows-msi.yml 2026-05-12 22:44:12 +02:00
Laurent Trinques 48fec1db98 Update QElectroTech.wxs 2026-05-12 22:30:23 +02:00
Laurent Trinques 31f946426b Update QElectroTech.wxs 2026-05-12 22:18:58 +02:00
Laurent Trinques efa74dd0f5 Update QElectroTech.wxs 2026-05-12 22:12:42 +02:00
Laurent Trinques 703797bb97 Update QElectroTech.wxs 2026-05-12 22:08:24 +02:00
Laurent Trinques ef75ee736a Update windows-msi.yml 2026-05-12 22:02:53 +02:00
Laurent Trinques 15e623ac5f Update QElectroTech.wxs 2026-05-12 21:58:07 +02:00
Laurent Trinques df82a1125d Update windows-msi.yml 2026-05-12 21:56:59 +02:00
Laurent Trinques 7edc2e0241 Update windows-msi.yml 2026-05-12 21:50:32 +02:00
Laurent Trinques 55ae3fc3c6 Update QElectroTech.wxs 2026-05-12 21:46:11 +02:00
Laurent Trinques 2f72e6164c Update windows-msi.yml
Removal of all envs: WIX_ACCEPT_EULA: true (does not work)
Addition of a dedicated ‘Accept WiX EULA’ step with wix eula accept wix7 before any other WiX command — this is the official CI/CD method, which writes a sentinel file to the user profile, thereby authorising all subsequent WiX commands in the same job.
2026-05-12 21:39:36 +02:00
Laurent Trinques e40f9c6b72 Update windows-msi.yml 2026-05-12 21:33:05 +02:00
Laurent Trinques ef261a7afd Update windows-msi.yml
Remove --accept-eula on dotnet tool install
2026-05-12 21:25:57 +02:00
Laurent Trinques 1550944011 Update windows-msi.yml
dotnet tool install --global wix --version 7.0.0 --accept-eula
wix extension add WixToolset.UI.wixext/7.0.0 --accept-eula
wix build ... --accept-eula
2026-05-12 20:55:22 +02:00
Laurent Trinques 32733187b8 Update windows-build.yml
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-12 20:12:02 +02:00
Laurent Trinques 79542edd3b Try to build Windows msi files 2026-05-12 19:50:20 +02:00
Laurent Trinques 9e0ec69c61 Add windows spec files for msi test 2026-05-12 19:49:34 +02:00
Laurent Trinques 2e0a1a55e3 Test Windows VS2026 migration on GitHUB action 2026-05-12 19:43:06 +02:00
Laurent Trinques 308f2ea838 Update windows-build.yml 2026-05-12 15:01:21 +02:00
Laurent Trinques 9a9a5446cf Update windows-build.yml 2026-05-12 14:53:03 +02:00
Laurent Trinques 0f8d835a1b git submodule update --remote elements 2026-05-12 14:22:35 +02:00
Laurent Trinques 663336a7bc Update windows-build.yml
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-11 15:00:59 +02:00
Laurent Trinques 4fab90d5b9 Update windows-build.yml
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
2026-05-10 21:27:10 +02:00
Laurent Trinques f67df92f0e Update windows-build.yml
Windows Build / build-windows (push) Has been cancelled
Windows Build / deploy-pages (push) Has been cancelled
Use 7-Zip is already installed on all GitHub Windows computers (C:\Program Files\7-Zip\7z.exe), and it is much faster than Compress-Archive when dealing with 9,931 files.
2026-05-10 13:04:31 +02:00
Laurent Trinques 7e2c2cccf8 Update windows-build.yml
Add Ready_to_use packages
2026-05-10 12:42:30 +02:00
Laurent Trinques b8ed1713a7 Update windows-build.yml 2026-05-10 12:18:40 +02:00
Laurent Trinques ae3e01e564 Update windows CI/CD pipeline
Windows Build / build-windows (push) Has been cancelled
2026-05-10 09:56:02 +02:00
Laurent Trinques a4bdade3db git submodule update --remote elements
Windows Build / build-windows (push) Has been cancelled
2026-05-08 13:25:59 +02:00
Laurent Trinques 4bbc54f6e3 git submodule update --remote elements 2026-05-08 12:36:40 +02:00
93 changed files with 17932 additions and 12533 deletions
+112
View File
@@ -0,0 +1,112 @@
name: Test Windows VS2026 migration
# Ce workflow vérifie que le build QElectroTech fonctionne sur l'image
# windows-2025-vs2026, avant la migration forcée du 8 juin 2026.
# Il peut être supprimé une fois la migration confirmée OK.
on:
workflow_dispatch: # déclenchement manuel uniquement
schedule:
- cron: '0 4 * * 1' # chaque lundi à 4h00 UTC (optionnel)
jobs:
test-vs2026:
name: Build on windows-2025-vs2026
runs-on: windows-2025-vs2026 # <-- image avec VS 2026
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Install MSYS2 + dependencies
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: false
install: >-
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-ninja
mingw-w64-ucrt-x86_64-qt5-base
mingw-w64-ucrt-x86_64-qt5-svg
mingw-w64-ucrt-x86_64-qt5-tools
mingw-w64-ucrt-x86_64-qt5-translations
mingw-w64-ucrt-x86_64-sqlite3
mingw-w64-ucrt-x86_64-pkgconf
mingw-w64-ucrt-x86_64-extra-cmake-modules
mingw-w64-ucrt-x86_64-kwidgetsaddons-qt5
mingw-w64-ucrt-x86_64-kcoreaddons-qt5
mingw-w64-ucrt-x86_64-nsis
mingw-w64-ucrt-x86_64-ccache
mingw-w64-ucrt-x86_64-7zip
git
- name: Force Qt5 (remove Qt6 interference)
shell: msys2 {0}
run: |
rm -rf /ucrt64/lib/cmake/Qt6 || true
pacman -R --noconfirm mingw-w64-ucrt-x86_64-qt6-tools 2>/dev/null || true
- name: Cache ccache
uses: actions/cache@v4
with:
path: C:\Users\runneradmin\AppData\Local\ccache
key: ccache-vs2026-${{ runner.os }}-${{ github.sha }}
restore-keys: |
ccache-vs2026-${{ runner.os }}-
- name: Configure (CMake)
shell: msys2 {0}
run: |
set -euo pipefail
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DQt5_DIR=/ucrt64/lib/cmake/Qt5 \
-DQT_VERSION_MAJOR=5 \
-DCMAKE_DISABLE_FIND_PACKAGE_Qt6=ON \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_FLAGS="-DQET_EXPORT_PROJECT_DB" \
-DBUILD_TESTING=OFF \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
- name: Build
shell: msys2 {0}
run: |
set -euo pipefail
cmake --build build --parallel $(nproc)
- name: Verify executable
shell: msys2 {0}
run: |
set -euo pipefail
EXE=$(find build -name "qelectrotech.exe" | head -1)
if [ -z "$EXE" ]; then
echo "ERROR: qelectrotech.exe introuvable après le build"
exit 1
fi
SIZE=$(stat -c%s "$EXE")
echo "Executable trouvé : $EXE ($SIZE octets)"
if [ "$SIZE" -lt 100000 ]; then
echo "ERROR: exe trop petit ($SIZE octets), build probablement incomplet"
exit 1
fi
echo "BUILD VS2026 : OK ✓"
- name: Summary
if: always()
shell: msys2 {0}
run: |
echo "=== Résumé de compatibilité VS2026 ==="
gcc --version
cmake --version
ninja --version
echo "Image runner : windows-2025-vs2026"
echo "Date du test : $(date -u)"
+280 -140
View File
@@ -1,11 +1,9 @@
name: Windows Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
schedule:
- cron: '0 2 * * 1' # Every Monday at 2:00 UTC
workflow_dispatch: # Manual trigger available at any time
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,12 +12,12 @@ concurrency:
jobs:
build-windows:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Install MSYS2
uses: msys2/setup-msys2@v2
@@ -28,6 +26,8 @@ jobs:
update: true
cache: true
install: >-
git
mingw-w64-ucrt-x86_64-ccache
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-ninja
@@ -43,14 +43,56 @@ jobs:
mingw-w64-ucrt-x86_64-nsis
mingw-w64-ucrt-x86_64-angleproject
- name: Force Qt5 — supprimer Qt6 cmake + tools
- name: Cache ccache
uses: actions/cache@v4
with:
path: C:\Users\runneradmin\AppData\Local\ccache
key: ccache-windows-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
ccache-windows-${{ github.ref_name }}-
ccache-windows-
- name: Configure ccache
shell: msys2 {0}
run: |
/ucrt64/bin/ccache --set-config=max_size=500M
/ucrt64/bin/ccache --set-config=compression=true
/ucrt64/bin/ccache -z
echo "=== ccache config ==="
/ucrt64/bin/ccache -p
- name: Patch NSIS Welcome page — fix title font size
shell: msys2 {0}
run: |
set -euo pipefail
WELCOME_NSH=$(find /ucrt64 -path "*/Modern UI 2/Pages/Welcome.nsh" | head -1)
if [ -z "$WELCOME_NSH" ]; then
echo "WARNING: Welcome.nsh not found, skipping font patch"
else
echo "Patching: $WELCOME_NSH"
sed -i '/WelcomePage\.Title\.Font/s/"[0-9]\+" "700"/"10" "700"/' "$WELCOME_NSH"
grep 'WelcomePage.Title.Font' "$WELCOME_NSH"
echo " OK font size patched to 10"
fi
FINISH_NSH=$(find /ucrt64 -path "*/Modern UI 2/Pages/Finish.nsh" | head -1)
if [ -z "$FINISH_NSH" ]; then
echo "WARNING: Finish.nsh not found, skipping font patch"
else
echo "Patching: $FINISH_NSH"
sed -i '/FinishPage\.Title\.Font/s/"[0-9]\+" "700"/"10" "700"/' "$FINISH_NSH"
grep 'FinishPage.Title.Font' "$FINISH_NSH"
echo " OK font size patched to 10"
fi
- name: Force Qt5 — remove Qt6 cmake + tools
shell: msys2 {0}
run: |
set -euo pipefail
rm -rf /ucrt64/lib/cmake/Qt6
pacman -R --noconfirm mingw-w64-ucrt-x86_64-qt6-tools 2>/dev/null || true
echo "=== windeployqt binaries ==="
ls /ucrt64/bin/windeployqt* || echo "AUCUN windeployqt trouve !"
ls /ucrt64/bin/windeployqt* || echo "NO windeployqt found!"
- name: Build with cmake
shell: msys2 {0}
@@ -58,6 +100,9 @@ jobs:
set -euo pipefail
cd "$GITHUB_WORKSPACE"
mkdir build && cd build
NPROC=$(nproc)
echo "Available CPUs: $NPROC"
cmake -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/ucrt64 \
@@ -66,8 +111,19 @@ jobs:
-DCMAKE_DISABLE_FIND_PACKAGE_Qt6=ON \
-DBUILD_TESTING=OFF \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_CXX_FLAGS="-DQET_EXPORT_PROJECT_DB" \
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
..
ninja
ninja -j"$NPROC"
- name: Show ccache stats
shell: msys2 {0}
run: |
echo "=== ccache statistics ==="
/ucrt64/bin/ccache -s
- name: Verify exe was built
shell: msys2 {0}
@@ -75,15 +131,15 @@ jobs:
set -euo pipefail
EXE=$(find "$GITHUB_WORKSPACE/build" -maxdepth 3 -iname "qelectrotech.exe" | head -1)
if [ -z "$EXE" ]; then
echo "ERROR: aucun qelectrotech.exe trouve dans build/"
echo "ERROR: no qelectrotech.exe found in build/"
find "$GITHUB_WORKSPACE/build" -maxdepth 3 -name "*.exe" || true
exit 1
fi
SIZE=$(stat -c%s "$EXE")
echo "Exe trouve : $EXE ($SIZE octets)"
[ "$SIZE" -gt 100000 ] || { echo "ERROR: exe trop petit"; exit 1; }
echo "Exe found: $EXE ($SIZE bytes)"
[ "$SIZE" -gt 100000 ] || { echo "ERROR: exe too small"; exit 1; }
- name: Deploy — copie exe + windeployqt + DLLs
- name: Deploy — copy exe + windeployqt + DLLs
shell: msys2 {0}
run: |
set -euo pipefail
@@ -93,7 +149,7 @@ jobs:
mkdir -p "$BIN"
EXE=$(find "$GITHUB_WORKSPACE/build" -maxdepth 3 -iname "qelectrotech.exe" | head -1)
echo "Copie exe : $EXE -> $BIN/QElectroTech.exe"
echo "Copying exe: $EXE -> $BIN/QElectroTech.exe"
cp "$EXE" "$BIN/QElectroTech.exe"
cd "$BIN"
@@ -101,167 +157,152 @@ jobs:
--release \
--no-translations \
--no-compiler-runtime \
--no-opengl-sw \
./QElectroTech.exe || true
ls -lh "$BIN/QElectroTech.exe" || { echo "ERROR: exe absent de bin/"; exit 1; }
echo "=== 3-pass transitive DLL scan ==="
set +e
for PASS in 1 2 3; do
echo "-- Pass $PASS --"
for bin_file in "$BIN"/*.dll "$BIN"/*.exe "$BIN"/sqldrivers/*.dll "$BIN"/platforms/*.dll "$BIN"/imageformats/*.dll; do
[ -f "$bin_file" ] || continue
while IFS= read -r line; do
dll_path=$(echo "$line" | awk '{print $3}')
[ -f "$dll_path" ] || continue
dll_name=$(basename "$dll_path")
dst="$BIN/$dll_name"
if [ ! -f "$dst" ]; then
cp "$dll_path" "$dst"
echo " Copied (pass $PASS): $dll_name"
fi
done < <(ldd "$bin_file" 2>/dev/null | grep -i '/ucrt64/bin/')
done
done
set -e
DLL_COUNT=$(find "$BIN" -name "*.dll" | wc -l)
echo "DLLs presentes : $DLL_COUNT"
[ "$DLL_COUNT" -gt 5 ] || { echo "ERROR: trop peu de DLLs"; exit 1; }
echo "=== $DLL_COUNT DLLs present after scan ==="
ls -lh "$BIN/QElectroTech.exe" || { echo "ERROR: exe missing from bin/"; exit 1; }
[ "$DLL_COUNT" -gt 5 ] || { echo "ERROR: too few DLLs"; exit 1; }
cd "$GITHUB_WORKSPACE"
# --- DLLs runtime compilateur (non copiées par windeployqt) ---
# --- DLLs runtime : copie automatique via ldd ---
# On copie d'abord les DLLs garanties (runtime GCC + KF5 + ICU)
# car ldd ne les liste pas toujours toutes (chargement dynamique).
echo "=== Copie DLLs runtime garanties ==="
UCRT=/ucrt64/bin
for dll in \
libgcc_s_seh-1.dll \
libstdc++-6.dll \
libwinpthread-1.dll \
libgomp-1.dll; do
[ -f "$UCRT/$dll" ] && cp -v "$UCRT/$dll" "$BIN/" || echo "WARN: $dll non trouve"
done
cp -v "$UCRT"/libicu*.dll "$BIN/" 2>/dev/null || echo "WARN: libicu*.dll non trouvees"
cp -v "$UCRT"/KF5*.dll "$BIN/" 2>/dev/null || echo "WARN: KF5*.dll non trouvees"
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
SQLITE=$(find /ucrt64/bin -name "libsqlite3*.dll" | head -1)
if [ -n "$SQLITE" ]; then
cp "$SQLITE" "$BIN/"
echo "SQLite3 copied: $(basename $SQLITE)"
else
echo "WARNING: libsqlite3 not found in /ucrt64/bin/"
fi
# Copie automatique de toutes les dependances UCRT64 detectees par ldd
echo "=== Copie automatique dependances ldd ==="
ldd "$BIN/QElectroTech.exe" \
| grep -i '/ucrt64/bin/' \
| awk '{print $3}' \
| while read -r dep; do
cp -v "$dep" "$BIN/" 2>/dev/null || true
done
# Passe recursive : certaines DLLs ont elles-memes des dependances UCRT64
echo "=== Passe recursive sur les DLLs copiees ==="
find "$BIN" -maxdepth 1 -name "*.dll" | while read -r lib; do
ldd "$lib" 2>/dev/null \
| grep -i '/ucrt64/bin/' \
| awk '{print $3}' \
| while read -r dep; do
[ -f "$BIN/$(basename "$dep")" ] || cp -v "$dep" "$BIN/" 2>/dev/null || true
done
done
echo "DLLs totales apres runtime : $(find "$BIN" -name '*.dll' | wc -l)"
# --- Diagnostic final ---
echo "=== ldd — dependances non resolues ==="
ldd "$BIN/QElectroTech.exe" | grep -i "not found" || echo "Aucune dependance manquante detectee"
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra_fr.nsh" "$NSIS_ROOT/"
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra_missing.nsh" "$NSIS_ROOT/"
cp -r "$GITHUB_WORKSPACE/build-aux/windows/nsis_base/." "$NSIS_ROOT/"
curl -fsSL \
"https://raw.githubusercontent.com/qelectrotech/qelectrotech-source-mirror/refs/heads/master/misc/Lancer%20QET.bat" \
-o "$FILES/Lancer QET.bat"
cp -r "$GITHUB_WORKSPACE/elements" "$FILES/elements" || true
cp -r "$GITHUB_WORKSPACE/lang" "$FILES/lang" || true
cp -r "$GITHUB_WORKSPACE/titleblocks" "$FILES/titleblocks" || true
cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true
cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true
cp -r "$GITHUB_WORKSPACE/ico" "$FILES/ico" || true
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE \
qet_uninstall_file_associations.reg register_filetypes.bat; do
cp -r "$GITHUB_WORKSPACE/lang" "$FILES/lang" || true
find "$GITHUB_WORKSPACE/build" -name "*.qm" -exec cp {} "$FILES/lang/" \; 2>/dev/null || true
echo "=== .qm files in files/lang/ ==="
ls "$FILES/lang/"*.qm 2>/dev/null | wc -l || echo "0 .qm files"
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE; do
cp "$GITHUB_WORKSPACE/$f" "$FILES/$f" 2>/dev/null || true
done
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/" || true
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra_fr.nsh" "$NSIS_ROOT/" || true
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra_missing.nsh" "$NSIS_ROOT/" || true
cp -r "$GITHUB_WORKSPACE/build-aux/windows/images" "$NSIS_ROOT/" || true
if [ -f "$GITHUB_WORKSPACE/build-aux/windows/Lancer QET.bat" ]; then
cp "$GITHUB_WORKSPACE/build-aux/windows/Lancer QET.bat" "$NSIS_ROOT/"
else
printf '@echo off\r\nstart "" "%%~dp0bin\\QElectroTech.exe" %%*\r\n' \
> "$NSIS_ROOT/Lancer QET.bat"
fi
- name: Ensure NSIS images (wizard.bmp + header.bmp)
shell: msys2 {0}
run: |
set -euo pipefail
IMG_SRC="$GITHUB_WORKSPACE/build-aux/windows/images"
IMG_DST="$GITHUB_WORKSPACE/nsis_root/images"
mkdir -p "$IMG_DST"
if [ -f "$IMG_SRC/wizard.bmp" ] && [ -f "$IMG_SRC/header.bmp" ]; then
echo "Images trouvees dans le repo, copie directe."
cp "$IMG_SRC/wizard.bmp" "$IMG_DST/wizard.bmp"
cp "$IMG_SRC/header.bmp" "$IMG_DST/header.bmp"
else
echo "Images absentes du repo — generation avec ImageMagick."
# wizard.bmp : 164x314 px, fond bleu QET, texte blanc
convert \
-size 164x314 \
gradient:"#1a3a5c"-"#2e6da4" \
-gravity Center \
-fill white \
-pointsize 13 \
-annotate 0 "QElectroTech" \
-type TrueColor \
BMP3:"$IMG_DST/wizard.bmp"
# header.bmp : 150x57 px, même palette
convert \
-size 150x57 \
gradient:"#1a3a5c"-"#2e6da4" \
-gravity Center \
-fill white \
-pointsize 11 \
-annotate 0 "QElectroTech" \
-type TrueColor \
BMP3:"$IMG_DST/header.bmp"
echo "BMPs generes."
fi
echo "Contenu de nsis_root/images/ :"
ls -lh "$IMG_DST/"
echo "=== Verification of key files in files/ ==="
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE \
qet_uninstall_file_associations.reg register_filetypes.bat "Lancer QET.bat"; do
[ -f "$FILES/$f" ] \
&& echo " OK : $f" \
|| echo " MISSING: $f"
done
for d in ico elements lang titleblocks fonts examples bin; do
[ -d "$FILES/$d" ] \
&& echo " OK : $d/" \
|| echo " MISSING: $d/"
done
- name: Extract version for installer name
shell: msys2 {0}
id: qet_version
run: |
set -euo pipefail
VERSION=$(grep -A5 '^project(' "$GITHUB_WORKSPACE/CMakeLists.txt" \
| grep -oP '(?<=VERSION )[0-9]+\.[0-9]+[0-9.]*' | head -1 || true)
[ -z "$VERSION" ] && VERSION="dev"
GIT_SHORT=$(git -C "$GITHUB_WORKSPACE" rev-parse --short HEAD 2>/dev/null || echo "unknown")
FULL_VERSION="${VERSION}+git${GIT_SHORT}_x86_64-win64"
echo "version=$FULL_VERSION" >> "$GITHUB_OUTPUT"
echo "Detected version: $FULL_VERSION"
- name: Patch QET64.nsi — version uniquement
GITCOMMIT=$(git -C "$GITHUB_WORKSPACE" rev-parse --short HEAD)
A=$(git -C "$GITHUB_WORKSPACE" rev-list HEAD --count)
HEAD=$(( A + 473 ))
VERSION=$(grep 'return QVersionNumber{' "$GITHUB_WORKSPACE/sources/qetversion.cpp" \
| head -1 \
| awk -F '{' '{ print $2 }' \
| awk -F '}' '{ print $1 }' \
| sed -e 's/,/./g' -e 's/ //g')
[ -z "$VERSION" ] && VERSION="dev"
FULL_VERSION="${VERSION}-r${HEAD}-${GITCOMMIT}_x86_64-win64"
echo "version=$FULL_VERSION" >> "$GITHUB_OUTPUT"
echo "base_version=$VERSION" >> "$GITHUB_OUTPUT"
echo "gitcommit=$GITCOMMIT" >> "$GITHUB_OUTPUT"
echo "head=$HEAD" >> "$GITHUB_OUTPUT"
echo "VERSION : $VERSION"
echo "GITCOMMIT : $GITCOMMIT"
echo "HEAD (rev) : $HEAD"
echo "FULL : $FULL_VERSION"
- name: Patch QET64.nsi — version + exe name + absolute paths
shell: msys2 {0}
run: |
set -euo pipefail
VERSION="${{ steps.qet_version.outputs.version }}"
NSI="$GITHUB_WORKSPACE/nsis_root/QET64.nsi"
FILES_WIN=$(cygpath -w "$GITHUB_WORKSPACE/nsis_root/files")
SCRIPT="$GITHUB_WORKSPACE/build-aux/windows/patch_nsi.py"
# Patch version uniquement.
# Tous les chemins du .nsi sont relatifs (./files/, .\images\) et
# sont resolus par makensis depuis nsis_root/ — aucun patch necessaire.
sed -i "s|!define SOFT_VERSION .*|!define SOFT_VERSION \"${VERSION}\"|" "$NSI"
python3 "$SCRIPT" "$NSI" "$VERSION" "$FILES_WIN"
echo '=== SOFT_VERSION ==='
grep 'SOFT_VERSION' "$NSI"
echo '=== Structure nsis_root ==='
ls "$GITHUB_WORKSPACE/nsis_root/"
echo '=== Contenu files/bin (premiers fichiers) ==='
ls "$GITHUB_WORKSPACE/nsis_root/files/bin/" | head -10
echo "=== Verification ==="
grep 'SOFT_VERSION' "$NSI" | head -1
grep -m2 'nsis_root' "$NSI" | head -2
echo "=== Contents of nsis_root/files/ ==="
ls "$GITHUB_WORKSPACE/nsis_root/files/"
- name: Build NSIS installer
shell: msys2 {0}
run: |
set -euo pipefail
cd "$GITHUB_WORKSPACE/nsis_root"
makensis QET64.nsi
NSIS_ROOT="$GITHUB_WORKSPACE/nsis_root"
cd "$NSIS_ROOT"
echo "=== CWD : $(pwd) ==="
MSYS2_ARG_CONV_EXCL="*" makensis /V4 QET64.nsi
RC=$?
echo "=== Contents of nsis_root after makensis ==="
ls "$NSIS_ROOT/"
[ $RC -eq 0 ] || { echo "ERROR: makensis failed (exit $RC)"; exit 1; }
- name: Move installer to dist/
shell: msys2 {0}
run: |
set -euo pipefail
mkdir -p "$GITHUB_WORKSPACE/dist"
mv "$GITHUB_WORKSPACE/nsis_root"/Installer_QElectroTech-*.exe \
"$GITHUB_WORKSPACE/dist/"
INSTALLER=$(find "$GITHUB_WORKSPACE/nsis_root" -maxdepth 1 -iname "installer_*.exe" | head -1)
if [ -z "$INSTALLER" ]; then
echo "ERROR: no installer .exe found in nsis_root/"
ls "$GITHUB_WORKSPACE/nsis_root/"
exit 1
fi
echo "Moving: $INSTALLER -> dist/"
mv "$INSTALLER" "$GITHUB_WORKSPACE/dist/"
- name: Upload build logs on failure
if: failure()
@@ -273,16 +314,115 @@ jobs:
nsis_root/files/bin/
if-no-files-found: warn
- name: Upload portable (files/ sans installeur)
- name: Zip portable (readytouse)
id: zip_portable
shell: pwsh
run: |
$version = "${{ steps.qet_version.outputs.base_version }}"
$head = "${{ steps.qet_version.outputs.head }}"
$zipName = "qelectrotech-${version}+git${head}-x86-win64-readytouse.zip"
$src = "$env:GITHUB_WORKSPACE\nsis_root\files"
$dst = "$env:GITHUB_WORKSPACE\dist\$zipName"
$7z = "C:\Program Files\7-Zip\7z.exe"
New-Item -ItemType Directory -Force -Path "$env:GITHUB_WORKSPACE\dist" | Out-Null
& $7z a -tzip -mx=5 -mmt=on $dst "$src\*"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$sizeMB = [math]::Round((Get-Item $dst).Length / 1MB, 1)
Write-Output "ZIP created: $zipName ($sizeMB MB)"
"zip_name=$zipName" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Upload portable (files/ without installer)
uses: actions/upload-artifact@v4
with:
name: qelectrotech-windows-portable
path: nsis_root/files/
name: qelectrotech-${{ steps.qet_version.outputs.base_version }}+git${{ steps.qet_version.outputs.head }}-x86-win64-readytouse
path: dist/${{ steps.zip_portable.outputs.zip_name }}
retention-days: 14
- name: Upload NSIS installer
uses: actions/upload-artifact@v4
with:
name: qelectrotech-windows-installer
path: dist/Installer_QElectroTech-*.exe
path: dist/Installer_*.exe
retention-days: 14
- name: Upload portable (nom fixe pour le workflow MSI)
uses: actions/upload-artifact@v4
with:
name: qelectrotech-windows-portable
path: nsis_root/files/
retention-days: 14
# ---------------------------------------------------------------------------
# Job 2 : Publie une release nightly + page GitHub Pages
# Ne tourne que sur push master (pas sur les PRs)
# ---------------------------------------------------------------------------
deploy-pages:
needs: build-windows
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
permissions:
contents: write
steps:
- name: Checkout master (for build-aux scripts)
uses: actions/checkout@v4
with:
ref: master
path: source
sparse-checkout: build-aux/generate-page.py
sparse-checkout-cone-mode: false
- name: Download installer artifact
uses: actions/download-artifact@v4
with:
name: qelectrotech-windows-installer
path: downloaded/installer/
- name: Download portable artifact
uses: actions/download-artifact@v4
with:
pattern: qelectrotech-*-readytouse
path: downloaded/portable/
merge-multiple: true
- name: Delete old nightly assets (.exe and .zip)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
# Fetch existing .exe and .zip asset names and delete them
gh release view nightly --repo "$REPO" --json assets \
--jq '.assets[] | select(.name | test("\\.(exe|zip)$")) | .name' \
| while read -r name; do
echo "Deleting old asset: $name"
gh release delete-asset nightly "$name" --repo "$REPO" --yes
done
echo "Old .exe and .zip assets deleted."
- name: Update nightly release
uses: softprops/action-gh-release@v2
with:
tag_name: nightly
name: "Nightly Build Windows"
body: |
🔧 **Automated nightly build** — may be unstable.
| | |
|---|---|
| **Commit** | ${{ github.sha }} |
| **Run** | https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} |
| **Date** | ${{ github.event.head_commit.timestamp }} |
> ⚠️ This is a development version; it introduces new features you want, but may cause bugs that have not yet been identified yet.
> For stable releases, see the [Releases page](https://github.com/${{ github.repository }}/releases).
prerelease: true
make_latest: false
files: |
downloaded/installer/*.exe
downloaded/portable/*.zip
token: ${{ secrets.GITHUB_TOKEN }}
# GitHub Pages is generated and deployed by windows-msi.yml
# after the MSI upload, so that all 3 URLs (exe, zip, msi) are known.
+366
View File
@@ -0,0 +1,366 @@
name: Windows MSI (WiX v7)
on:
# Triggered automatically after a successful Windows build on master
workflow_run:
workflows: ["Windows Build"]
types: [completed]
branches: [master]
# Manual trigger still available (e.g. for a specific run_id)
workflow_dispatch:
inputs:
run_id:
description: "Run ID of 'Windows Build' (leave empty for automatic)"
required: false
default: ""
jobs:
build-msi:
name: Build MSI with WiX v7
runs-on: windows-latest
# Only runs if Windows Build succeeded (or triggered manually)
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
permissions:
contents: write
pages: write
id-token: write
steps:
# ----------------------------------------------------------------
# 1. Checkout (to retrieve QElectroTech.wxs and sources)
# ----------------------------------------------------------------
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# ----------------------------------------------------------------
# 2. Download the portable artifact from the main build
# ----------------------------------------------------------------
- name: Download portable artifact
uses: actions/download-artifact@v4
with:
name: qelectrotech-windows-portable
path: artifact\files
run-id: ${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
# ----------------------------------------------------------------
# 3. Extract version from sources
# ----------------------------------------------------------------
- name: Extract version
id: version
shell: pwsh
run: |
$src = Get-Content "sources\qetversion.cpp" -Raw -ErrorAction SilentlyContinue
if ($src -match 'return QVersionNumber\{([^}]+)\}') {
$ver = $Matches[1] -replace '\s','' -replace ',','.'
} else {
$cmake = Get-Content "CMakeLists.txt" -Raw
if ($cmake -match 'project\s*\([^)]*VERSION\s+([\d]+\.[\d]+\.[\d]+)') {
$ver = $Matches[1]
} else {
$ver = "0.0.0"
}
}
$verMsi = "$ver.0"
$sha = git rev-parse --short HEAD 2>$null
if (-not $sha) { $sha = "unknown" }
$count = git rev-list HEAD --count 2>$null
$rev = [int]$count + 473
$verDisplay = "${ver}-r${rev}-${sha}_x86_64-win64"
# Generate a unique ProductCode GUID from the commit SHA
# This ensures MajorUpgrade always triggers, even for same-version builds
$fullSha = git rev-parse HEAD 2>$null
if (-not $fullSha) { $fullSha = [System.Guid]::NewGuid().ToString() }
$bytes = [System.Text.Encoding]::UTF8.GetBytes($fullSha)
$md5 = [System.Security.Cryptography.MD5]::Create().ComputeHash($bytes)
$guidBytes = [byte[]]$md5[0..15]
$productGuid = [System.Guid]::new($guidBytes).ToString().ToUpper()
echo "VERSION_MSI=$verMsi" >> $env:GITHUB_OUTPUT
echo "VERSION_DISPLAY=$verDisplay" >> $env:GITHUB_OUTPUT
echo "PRODUCT_GUID=$productGuid" >> $env:GITHUB_OUTPUT
Write-Host "Version MSI : $verMsi"
Write-Host "Version display : $verDisplay"
Write-Host "Product GUID : $productGuid"
# ----------------------------------------------------------------
# 4. Install WiX v7, accept EULA and install WixUI extension
# ----------------------------------------------------------------
- name: Install WiX v7
shell: pwsh
run: |
dotnet tool install --global wix --version 7.0.0
$toolsPath = [System.IO.Path]::Combine($env:USERPROFILE, '.dotnet', 'tools')
$env:PATH = "$toolsPath;$env:PATH"
echo $toolsPath >> $env:GITHUB_PATH
wix eula accept wix7
wix extension add WixToolset.UI.wixext/7.0.0
wix extension add WixToolset.Util.wixext/7.0.0
Write-Host "WiX v7 installed, EULA accepted, UI extension added."
# ----------------------------------------------------------------
# 5. Check that the WXS file exists in the repository
# ----------------------------------------------------------------
- name: Check WXS file
shell: pwsh
run: |
$wxs = "build-aux\windows\QElectroTech.wxs"
if (-not (Test-Path $wxs)) {
Write-Error "WXS file not found: $wxs"
exit 1
}
Write-Host "WXS found: $wxs"
# ----------------------------------------------------------------
# 6. Check the artifact structure and locate files/
# ----------------------------------------------------------------
- name: Check artifact structure
shell: pwsh
run: |
Write-Host "=== Contents of artifact\files (2 levels) ==="
Get-ChildItem -Path "artifact\files" -Depth 2 |
Select-Object FullName | Format-Table -AutoSize
$exe = Get-ChildItem -Path "artifact\files" -Filter "qelectrotech.exe" -Recurse | Select-Object -First 1
if (-not $exe) {
$exe = Get-ChildItem -Path "artifact\files" -Filter "QElectroTech.exe" -Recurse | Select-Object -First 1
}
if (-not $exe) {
Write-Error "qelectrotech.exe not found in artifact"
exit 1
}
Write-Host "Executable: $($exe.FullName) ($([math]::Round($exe.Length/1MB,1)) MB)"
$binDir = $exe.Directory.FullName
$filesDir = Split-Path $binDir -Parent
echo "FILES_DIR=$filesDir" >> $env:GITHUB_ENV
Write-Host "FILES_DIR: $filesDir"
# ----------------------------------------------------------------
# 7. Convert LICENSE (GPL-2) to RTF for the WixUI licence screen
# ----------------------------------------------------------------
- name: Convert LICENSE to RTF
shell: pwsh
run: |
$licSrc = "LICENSE"
$licRtf = "$env:TEMP\License.rtf"
if (-not (Test-Path $licSrc)) {
Write-Error "LICENSE file not found in repository root"
exit 1
}
$lines = Get-Content $licSrc -Encoding UTF8
$rtf = New-Object System.Text.StringBuilder
[void]$rtf.AppendLine('{\rtf1\ansi\ansicpg1252\deff0')
[void]$rtf.AppendLine('{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}}')
[void]$rtf.AppendLine('{\colortbl;\red0\green0\blue0;}')
[void]$rtf.AppendLine('\f0\fs18\cf1')
foreach ($line in $lines) {
$escaped = $line `
-replace '\\', '\\\\' `
-replace '\{', '\{' `
-replace '\}', '\}'
[void]$rtf.AppendLine("$escaped\par")
}
[void]$rtf.AppendLine('}')
[System.IO.File]::WriteAllText($licRtf, $rtf.ToString(), [System.Text.Encoding]::ASCII)
echo "LICENSE_RTF=$licRtf" >> $env:GITHUB_ENV
Write-Host "License.rtf generated: $licRtf ($([math]::Round((Get-Item $licRtf).Length/1KB,1)) KB)"
# ----------------------------------------------------------------
# 8. Remove Lancer QET.bat from the artifact
# The MSI does not use the .bat: shortcuts point directly to
# qelectrotech.exe, and elements\ is set read-only via a
# CustomAction in QElectroTech.wxs.
# The .bat is kept as-is in the ZIP portable build.
# ----------------------------------------------------------------
- name: Remove Lancer QET.bat from artifact
shell: pwsh
run: |
$bat = "$env:FILES_DIR\Lancer QET.bat"
if (Test-Path $bat) {
Remove-Item $bat -Force
Write-Host "Lancer QET.bat removed from artifact (MSI uses direct exe shortcut)."
} else {
Write-Host "Lancer QET.bat not found in artifact (already absent)."
}
# ----------------------------------------------------------------
# 9. Build the MSI
# ----------------------------------------------------------------
- name: Build MSI
shell: pwsh
run: |
$version = "${{ steps.version.outputs.VERSION_MSI }}"
$verDisplay = "${{ steps.version.outputs.VERSION_DISPLAY }}"
$filesDir = $env:FILES_DIR
$licRtf = $env:LICENSE_RTF
$wxs = "build-aux\windows\QElectroTech.wxs"
$outputName = "QElectroTech-${verDisplay}.msi"
New-Item -ItemType Directory -Force -Path "dist" | Out-Null
Write-Host "=== wix build ==="
Write-Host " WXS : $wxs"
Write-Host " Version : $version"
Write-Host " FilesDir : $filesDir"
Write-Host " LicenseRtf : $licRtf"
Write-Host " Output : dist\$outputName"
$productGuid = "${{ steps.version.outputs.PRODUCT_GUID }}"
wix build $wxs `
-arch x64 `
-d "Version=$version" `
-d "ProductVersion=$verDisplay" `
-d "ProductCode=$productGuid" `
-d "FilesDir=$filesDir" `
-d "LicenseRtf=$licRtf" `
-ext WixToolset.UI.wixext `
-ext WixToolset.Util.wixext `
-o "dist\$outputName"
if (-not (Test-Path "dist\$outputName")) {
Write-Error "MSI not generated: dist\$outputName"
exit 1
}
$size = [math]::Round((Get-Item "dist\$outputName").Length / 1MB, 1)
Write-Host "MSI generated: dist\$outputName ($size MB) ✓"
echo "MSI_NAME=$outputName" >> $env:GITHUB_ENV
# ----------------------------------------------------------------
# 10. Sign the MSI with SignPath
# ----------------------------------------------------------------
- name: Install SignPath PowerShell module
shell: pwsh
run: |
Install-Module -Name SignPath -Force -Scope CurrentUser
- name: Sign MSI with SignPath
shell: pwsh
run: |
$msi = Get-ChildItem "$env:GITHUB_WORKSPACE\dist\*.msi" | Select-Object -First 1
if (-not $msi) {
Write-Error "No .msi found in dist/"
exit 1
}
Write-Host "Signing: $($msi.FullName)"
Submit-SigningRequest `
-InputArtifactPath $msi.FullName `
-ApiToken "${{ secrets.SIGNPATH_API_TOKEN }}" `
-OrganizationId "${{ secrets.SIGNPATH_ORGANIZATION_ID }}" `
-ProjectSlug "MSI" `
-SigningPolicySlug "test-signing" `
-OutputArtifactPath $msi.FullName `
-Force `
-WaitForCompletion
Write-Host "Signing complete: $($msi.Name)"
# ----------------------------------------------------------------
# 11. Upload the MSI artifact
# ----------------------------------------------------------------
- name: Upload MSI artifact
uses: actions/upload-artifact@v4
with:
name: qelectrotech-windows-msi
path: dist\*.msi
retention-days: 14
if-no-files-found: error
# ----------------------------------------------------------------
# 11. Delete old .msi asset then upload new MSI to nightly release
# ----------------------------------------------------------------
- name: Delete old nightly .msi asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
gh release view nightly --repo "$REPO" --json assets \
--jq '.assets[] | select(.name | test("\\.msi$")) | .name' \
| while read -r name; do
echo "Deleting old asset: $name"
gh release delete-asset nightly "$name" --repo "$REPO" --yes
done
echo "Old .msi assets deleted."
shell: bash
- name: Upload MSI to nightly release
uses: softprops/action-gh-release@v2
with:
tag_name: nightly
files: dist/*.msi
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ----------------------------------------------------------------
# 12. Summary
# ----------------------------------------------------------------
- name: Summary
if: always()
shell: pwsh
run: |
Write-Host "=== MSI build summary ==="
Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}"
Write-Host "WiX : v7.0.0"
Write-Host "Runner image : ${{ runner.os }} / ${{ runner.arch }}"
if (Test-Path "dist\$env:MSI_NAME") {
$size = [math]::Round((Get-Item "dist\$env:MSI_NAME").Length / 1MB, 1)
Write-Host "MSI : $env:MSI_NAME ($size MB) ✓"
} else {
Write-Host "MSI : FAILED ✗"
}
# ----------------------------------------------------------------
# 13. Generate and deploy the GitHub Pages download page
# ----------------------------------------------------------------
- name: Checkout (for generate-page.py)
uses: actions/checkout@v4
with:
ref: master
path: source
sparse-checkout: build-aux/generate-page.py
sparse-checkout-cone-mode: false
- name: Generate download page (index.html)
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
REPO="${{ github.repository }}"
ASSETS=$(gh release view nightly --repo "$REPO" --json assets --jq '.assets[].name')
EXE_NAME=$(echo "$ASSETS" | grep '\.exe$' | head -1)
ZIP_NAME=$(echo "$ASSETS" | grep '\.zip$' | head -1)
MSI_NAME=$(echo "$ASSETS" | grep '\.msi$' | head -1 || echo "")
BASE="https://github.com/$REPO/releases/download/nightly"
INSTALLER_URL="$BASE/$EXE_NAME"
PORTABLE_URL="$BASE/$ZIP_NAME"
MSI_URL=""
[ -n "$MSI_NAME" ] && MSI_URL="$BASE/$MSI_NAME"
SHA="${{ github.event.workflow_run.head_sha || github.sha }}"
SHORT="${SHA:0:7}"
DATE=$(date -u '+%Y-%m-%d %H:%M UTC')
RUN_URL="https://github.com/$REPO/actions/runs/${{ github.run_id }}"
RUN_NUMBER="${{ github.run_number }}"
export DATE SHORT REPO SHA RUN_URL RUN_NUMBER
export INSTALLER_URL PORTABLE_URL MSI_URL
python3 source/build-aux/generate-page.py
- name: Add .nojekyll
shell: bash
run: touch gh-pages/.nojekyll
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: gh-pages/
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
generate-page.py — Generates gh-pages/index.html for QElectroTech nightly builds.
Called from windows-build.yml deploy-pages job.
Environment variables required:
DATE, SHORT, REPO, SHA, RUN_URL, RUN_NUMBER,
INSTALLER_URL, PORTABLE_URL, MSI_URL (optional)
"""
import os
date = os.environ.get("DATE", "")
short = os.environ.get("SHORT", "")
repo = os.environ.get("REPO", "")
sha = os.environ.get("SHA", "")
run_url = os.environ.get("RUN_URL", "")
run_number = os.environ.get("RUN_NUMBER", "")
installer_url = os.environ.get("INSTALLER_URL", "")
portable_url = os.environ.get("PORTABLE_URL", "")
msi_url = os.environ.get("MSI_URL", "")
msi_block = ""
if msi_url:
msi_block = f"""
<a class="btn btn-msi" href="{msi_url}">
<span class="btn-icon">&#11015;</span>
<span class="btn-text">Windows Installer .msi<small>.msi &mdash; for enterprise / GPO deployment</small></span>
</a>"""
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QElectroTech &ndash; Nightly Builds</title>
<style>
*,*::before,*::after{{box-sizing:border-box;margin:0;padding:0}}
body{{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f0f4f8;color:#2d3748;min-height:100vh}}
header{{background:linear-gradient(135deg,#1a365d 0%,#2b6cb0 100%);color:white;padding:48px 24px 40px;text-align:center}}
header h1{{font-size:2.2em;letter-spacing:-0.5px;margin-bottom:8px}}
header p{{opacity:.8;font-size:1.05em}}
main{{max-width:680px;margin:40px auto;padding:0 20px 60px}}
.card{{background:white;border-radius:12px;padding:28px;margin-bottom:24px;box-shadow:0 2px 12px rgba(0,0,0,.08)}}
.card h2{{font-size:1em;text-transform:uppercase;letter-spacing:.06em;color:#718096;margin-bottom:16px}}
.meta{{font-size:.875em;color:#4a5568;line-height:1.8;margin-bottom:20px}}
.meta a{{color:#2b6cb0;text-decoration:none}}
.meta a:hover{{text-decoration:underline}}
.badge{{display:inline-block;background:#ebf8ff;color:#2b6cb0;border-radius:4px;font-size:.8em;font-weight:600;padding:2px 8px;margin-left:6px;vertical-align:middle}}
.warning{{background:#fffbeb;border-left:4px solid #f6ad55;border-radius:4px;padding:12px 16px;font-size:.875em;color:#744210;margin-bottom:24px;line-height:1.5}}
.warning a{{color:#c05621}}
.downloads{{display:flex;flex-direction:column;gap:12px}}
.btn{{display:flex;align-items:center;gap:12px;padding:14px 20px;border-radius:8px;font-size:.95em;font-weight:600;text-decoration:none;transition:transform .1s,box-shadow .1s}}
.btn:hover{{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.15)}}
.btn-primary{{background:#2b6cb0;color:white}}
.btn-msi{{background:#6b46c1;color:white}}
.btn-secondary{{background:#edf2f7;color:#2d3748}}
.btn-icon{{font-size:1.3em}}
.btn-text small{{display:block;font-weight:400;font-size:.8em;opacity:.75;margin-top:1px}}
footer{{text-align:center;font-size:.8em;color:#a0aec0;padding:32px 0 0}}
footer a{{color:#718096;text-decoration:none}}
</style>
</head>
<body>
<header>
<h1>&#9889; QElectroTech</h1>
<p>Nightly Windows Builds</p>
</header>
<main>
<div class="card">
<h2>Build info</h2>
<div class="meta">
&#128197; &nbsp;<strong>{date}</strong><br>
&#128256; &nbsp;Commit <a href="https://github.com/{repo}/commit/{sha}"><code>{short}</code></a><br>
&#128295; &nbsp;<a href="{run_url}">CI Run #{run_number}</a>
<span class="badge">nightly</span>
</div>
<div class="warning">
&#9888;&#65039; This is a development version; it introduces new features you want,
but may cause bugs that have not yet been identified yet in <code>master</code>.
For production use, download a <a href="https://github.com/{repo}/releases">stable release</a>.
</div>
</div>
<div class="card">
<h2>&#127993; Windows &mdash; x86_64</h2>
<div class="downloads">
<a class="btn btn-primary" href="{installer_url}">
<span class="btn-icon">&#11015;</span>
<span class="btn-text">Windows Installer<small>.exe &mdash; recommended, includes all dependencies</small></span>
</a>
{msi_block}
<a class="btn btn-secondary" href="{portable_url}">
<span class="btn-icon">&#128230;</span>
<span class="btn-text">Windows Portable<small>.zip &mdash; no installation required, extract and run &quot;Lancer QET.bat&quot;</small></span>
</a>
<a class="btn btn-secondary" href="https://github.com/{repo}/releases/tag/nightly">
<span class="btn-icon">&#128230;</span>
<span class="btn-text">All nightly files on GitHub<small>Release page with checksums</small></span>
</a>
</div>
</div>
</main>
<footer>
Auto-generated by GitHub Actions &nbsp;&middot;&nbsp;
<a href="https://github.com/{repo}">Source on GitHub</a> &nbsp;&middot;&nbsp;
<a href="https://qelectrotech.org">qelectrotech.org</a>
</footer>
</body>
</html>"""
os.makedirs("gh-pages", exist_ok=True)
with open("gh-pages/index.html", "w", encoding="utf-8") as f:
f.write(html)
print("index.html written OK")
+40 -54
View File
@@ -27,6 +27,8 @@
; - MUI2.nsh is unchanged; MUI_LANGDLL_ALLLANGUAGES is still valid
; - FileFunc.nsh / Locate macro: unchanged
; - Var /GLOBAL must be declared at global scope, not inside a Section
; - ${__FILEDIR__} resolves to the directory of the .nsi at compile time;
; used for bitmaps and LICENSE so no sed path-patching is needed.
;==============================================================================
;--------------------------------
@@ -100,11 +102,14 @@ Var final_titleblock_ico
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\nsis3-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\nsis3-uninstall.ico"
!define MUI_WELCOMEFINISHPAGE_BITMAP ".\images\wizard.bmp"
; ${__FILEDIR__} resolves at compile time to the directory containing this
; .nsi file (= nsis_root/ when makensis runs from that folder).
; No sed path-patching needed for these two defines.
!define MUI_WELCOMEFINISHPAGE_BITMAP "${__FILEDIR__}\images\wizard.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP_NOSTRETCH
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP ".\images\header.bmp"
!define MUI_HEADERIMAGE_BITMAP "${__FILEDIR__}\images\header.bmp"
;--------------------------------
; Language Selection Dialog Settings (remember chosen language in registry)
@@ -116,7 +121,8 @@ Var final_titleblock_ico
; Pages
!define MUI_COMPONENTSPAGE_SMALLDESC
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "files\LICENSE"
; ${__FILEDIR__} resolves to nsis_root/ at compile time — no sed needed.
!insertmacro MUI_PAGE_LICENSE "${__FILEDIR__}\files\LICENSE"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
@@ -124,7 +130,7 @@ Var final_titleblock_ico
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
; Finish page checkbox to launch QElectroTech
; Finish page - checkbox to launch QElectroTech
!define MUI_FINISHPAGE_RUN "$INSTDIR\Lancer QET.bat"
!define MUI_FINISHPAGE_RUN_NOTCHECKED
!define MUI_FINISHPAGE_RUN_TEXT "$(Check)"
@@ -173,7 +179,7 @@ Var final_titleblock_ico
; NOTE: The string "uninstFailed" must be defined in lang_extra.nsh and
; lang_extra_fr.nsh (and any other lang_extra_*.nsh) like so:
; LangString uninstFailed ${LANG_ENGLISH} "Uninstallation of the previous version failed.$\nPlease uninstall QElectroTech manually before continuing."
; LangString uninstFailed ${LANG_FRENCH} "La désinstallation de la version précédente a échoué.$\nVeuillez désinstaller QElectroTech manuellement avant de continuer."
; LangString uninstFailed ${LANG_FRENCH} "La desinstallation de la version precedente a echoue.$\nVeuillez desinstaller QElectroTech manuellement avant de continuer."
;==============================================================================
; SECTIONS
@@ -182,76 +188,65 @@ Var final_titleblock_ico
SetOverwrite on
Section "Main Program"
SectionIn RO ; Read-only always installed
SectionIn RO ; Read-only - always installed
SetOutPath "$INSTDIR\bin\"
File "./files/bin/${SOFT_NAME}.exe"
File /r "./files/bin\*"
SetOutPath "$INSTDIR"
File "./files/ChangeLog"
File "./files/CREDIT"
File "./files/ELEMENTS.LICENSE"
File "./files/LICENSE"
File "./files/qet_uninstall_file_associations.reg"
File "./files/README"
File "./files/register_filetypes.bat"
File /nonfatal "./files/ChangeLog"
File /nonfatal "./files/CREDIT"
File /nonfatal "./files/ELEMENTS.LICENSE"
File /nonfatal "./files/LICENSE"
File /nonfatal "./files/qet_uninstall_file_associations.reg"
File /nonfatal "./files/README"
File /nonfatal "./files/register_filetypes.bat"
File "Lancer QET.bat"
SetOutPath "$INSTDIR"
File /r "./files/ico"
File /nonfatal /r "./files/ico"
SectionEnd
;---------------------------
SetOverwrite on
SubSection "$(Elements)" SEC01
SetOverwrite on
Section "$(Electric)"
SetOutPath "$INSTDIR\elements"
File /r "./files/elements/10_electric"
File /nonfatal /r "./files/elements/10_electric"
SectionEnd
SetOverwrite on
Section "$(Logic)"
SetOutPath "$INSTDIR\elements"
File /r "./files/elements/20_logic"
File /nonfatal /r "./files/elements/20_logic"
SectionEnd
SetOverwrite on
Section "$(Hydraulic)"
SetOutPath "$INSTDIR\elements"
File /r "./files/elements/30_hydraulic"
File /nonfatal /r "./files/elements/30_hydraulic"
SectionEnd
SetOverwrite on
Section "$(Pneumatic)"
SetOutPath "$INSTDIR\elements"
File /r "./files/elements/50_pneumatic"
File /nonfatal /r "./files/elements/50_pneumatic"
SectionEnd
;---------------------------------
SubSection "$(Energy)"
SetOverwrite on
Section "$(water)"
SetOutPath "$INSTDIR\elements\60_energy"
File /r "./files/elements/60_energy/11_water"
File /r "./files/elements/60_energy/"
File /nonfatal /r "./files/elements/60_energy/11_water"
SectionEnd
SetOverwrite on
Section "$(Refrigeration)"
SetOutPath "$INSTDIR\elements\60_energy"
File /r "./files/elements/60_energy/21_refrigeration"
File /r "./files/elements/60_energy/"
File /nonfatal /r "./files/elements/60_energy/21_refrigeration"
SectionEnd
SetOverwrite on
Section "$(Solar_thermal)"
SetOutPath "$INSTDIR\elements\60_energy"
File /r "./files/elements/60_energy/31_solar_thermal"
File /r "./files/elements/60_energy/"
File /nonfatal /r "./files/elements/60_energy/31_solar_thermal"
SectionEnd
SubSectionEnd
@@ -259,28 +254,24 @@ SubSection "$(Elements)" SEC01
SubSectionEnd
;-------------------------------
SetOverwrite on
Section "$(Lang)" SEC02
SetOutPath "$INSTDIR\lang"
File "./files/lang/*.qm"
File /nonfatal "./files/lang/*.qm"
SectionEnd
SetOverwrite on
Section "$(Titleblocks)" SEC03
SetOutPath "$INSTDIR"
File /r "./files/titleblocks"
File /nonfatal /r "./files/titleblocks"
SectionEnd
SetOverwrite on
Section "$(Examples)" SEC04
SetOutPath "$INSTDIR"
File /r "./files/examples"
File /nonfatal /r "./files/examples"
SectionEnd
SetOverwrite on
Section "$(Fonts)" SEC05
SetOutPath "$INSTDIR"
File /r "./files/fonts"
File /nonfatal /r "./files/fonts"
SectionEnd
;--------------------------------
@@ -320,7 +311,7 @@ Section ""
StrCpy $final_element_ico "$INSTDIR\ico\application-x-qet-element.ico"
StrCpy $final_titleblock_ico "$INSTDIR\ico\application-x-qet-titleblock.ico"
; File associations .qet
; File associations - .qet
WriteRegStr HKEY_CLASSES_ROOT "Applications\qelectrotech.exe\shell\open\command" "" \
'"$final_qet_exe" "%1"'
WriteRegStr HKEY_CLASSES_ROOT ".qet" "" "qet_diagram_file"
@@ -330,7 +321,7 @@ Section ""
WriteRegStr HKEY_CLASSES_ROOT "qet_diagram_file\DefaultIcon" "" "$final_project_ico"
WriteRegStr HKEY_CLASSES_ROOT "qet_diagram_file\shell\open\command" "" '"$final_qet_exe" "%1"'
; File associations .elmt
; File associations - .elmt
WriteRegStr HKEY_CLASSES_ROOT ".elmt" "" "qet_element_file"
WriteRegStr HKEY_CLASSES_ROOT "qet_element_file" "" "Element QET"
WriteRegDWORD HKEY_CLASSES_ROOT "qet_element_file" "EditFlags" 0x00000000
@@ -338,7 +329,7 @@ Section ""
WriteRegStr HKEY_CLASSES_ROOT "qet_element_file\DefaultIcon" "" "$final_element_ico"
WriteRegStr HKEY_CLASSES_ROOT "qet_element_file\shell\open\command" "" '"$final_qet_exe" "%1"'
; File associations .titleblock
; File associations - .titleblock
WriteRegStr HKEY_CLASSES_ROOT ".titleblock" "" "qet_titleblock_file"
WriteRegStr HKEY_CLASSES_ROOT "qet_titleblock_file" "" "Titleblock QET"
WriteRegDWORD HKEY_CLASSES_ROOT "qet_titleblock_file" "EditFlags" 0x00000000
@@ -379,7 +370,7 @@ Section ""
SectionEnd
;--------------------------------
; Locate callback sets FILE_ATTRIBUTE_READONLY on each .elmt file
; Locate callback - sets FILE_ATTRIBUTE_READONLY on each .elmt file
Function LocateCallback
SetFileAttributes $R9 FILE_ATTRIBUTE_READONLY
Push $0
@@ -408,7 +399,7 @@ Function .onInit
"Software\Microsoft\Windows\CurrentVersion\Uninstall\${SOFT_NAME}" \
"UninstallString"
; No previous installation found proceed normally
; No previous installation found - proceed normally
StrCmp $R0 "" done
; Also read the install dir of the previous version
@@ -416,11 +407,10 @@ Function .onInit
; Ask user whether to uninstall the existing version
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(installed)" IDOK uninst
Abort ; user clicked Cancel stop the installer
Abort ; user clicked Cancel - stop the installer
uninst:
; Remove surrounding quotes from the UninstallString if present
; (some installers write: "C:\path\Uninstall.exe" — ExecWait needs clean path)
StrCpy $R2 $R0 1 ; first character
StrCmp $R2 '"' 0 unquoted
; Strip leading and trailing quote
@@ -430,19 +420,15 @@ uninst:
StrCpy $R0 $R0 $R3 ; remove trailing "
unquoted:
; Run the uninstaller silently, keeping it in its own directory
; _?= prevents NSIS from copying the uninstaller to a temp folder,
; so it can delete itself and the whole $INSTDIR tree.
ClearErrors
${If} $R1 != ""
ExecWait '"$R0" /S _?=$R1' ; silent uninstall using saved install dir
ExecWait '"$R0" /S _?=$R1'
${Else}
ExecWait '"$R0" /S' ; fallback if install dir unknown
ExecWait '"$R0" /S'
${EndIf}
IfErrors uninstall_failed
; Verify the old installation is gone before continuing
${If} $R1 != ""
IfFileExists "$R1\${SOFT_NAME}.exe" uninstall_failed
IfFileExists "$R1\bin\${SOFT_NAME}.exe" uninstall_failed
+161
View File
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
QElectroTech.wxs - WiX v7 installer definition
Generates a self-contained x64 MSI for QElectroTech (MSYS2/UCRT64 build).
Variables passed from the wix build command line:
-d FilesDir=<absolute path to artifact\files>
-d Version=<numeric version e.g. 0.100.1.0>
-d ProductVersion=<display version e.g. 0.100.1-r8770-abc1234_x86_64-win64>
-d LicenseRtf=<absolute path to License.rtf>
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
<Package
Name="QElectroTech"
Manufacturer="QElectroTech Team"
Version="$(var.Version)"
ProductCode="$(var.ProductCode)"
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
Language="1033"
Codepage="1252"
InstallerVersion="500"
Scope="perMachine">
<!-- Embed all cabinet files inside the MSI (self-contained installer) -->
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<!-- In-place upgrade: automatically uninstalls the previous version -->
<MajorUpgrade
DowngradeErrorMessage="A newer version of QElectroTech is already installed."
AllowSameVersionUpgrades="yes"
Schedule="afterInstallInitialize" />
<!-- Installation directory -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLDIR" Name="QElectroTech" />
</StandardDirectory>
<!-- ============================================================
All application files harvested in one pass from files\**
(Lancer QET.bat has been removed from the artifact before
this build — see windows-msi.yml step "Remove Lancer QET.bat")
============================================================ -->
<ComponentGroup Id="CG_AllFiles" Directory="INSTALLDIR">
<Files Include="$(var.FilesDir)\**" />
</ComponentGroup>
<!-- ============================================================
Desktop + Start Menu shortcuts
Point directly to qelectrotech.exe with all required arguments.
No .bat wrapper needed.
============================================================ -->
<ComponentGroup Id="CG_Shortcuts" Directory="INSTALLDIR">
<Component Id="C_ShortcutDesktop" Guid="F1A2B3C4-D5E6-7890-5678-012345678901">
<Shortcut Id="DesktopShortcut"
Directory="DesktopFolder"
Name="QElectroTech"
Target="[INSTALLDIR]bin\qelectrotech.exe"
Arguments="--common-elements-dir=&quot;[INSTALLDIR]elements/&quot; --common-tbt-dir=&quot;[INSTALLDIR]titleblocks/&quot; --lang-dir=&quot;[INSTALLDIR]lang/&quot; -style windowsvista"
Icon="qet.ico"
WorkingDirectory="INSTALLDIR" />
<RegistryValue Root="HKCU"
Key="Software\QElectroTech"
Name="DesktopShortcut"
Type="integer" Value="1"
KeyPath="yes" />
</Component>
<Component Id="C_ShortcutStartMenu" Guid="A2B3C4D5-E6F7-8901-6789-123456789012">
<Shortcut Id="StartMenuShortcut"
Directory="ProgramMenuFolder"
Name="QElectroTech"
Target="[INSTALLDIR]bin\qelectrotech.exe"
Arguments="--common-elements-dir=&quot;[INSTALLDIR]elements/&quot; --common-tbt-dir=&quot;[INSTALLDIR]titleblocks/&quot; --lang-dir=&quot;[INSTALLDIR]lang/&quot; -style windowsvista"
Icon="qet.ico"
WorkingDirectory="INSTALLDIR" />
<RegistryValue Root="HKCU"
Key="Software\QElectroTech"
Name="StartMenuShortcut"
Type="integer" Value="1"
KeyPath="yes" />
</Component>
</ComponentGroup>
<!-- ============================================================
.qet file association
============================================================ -->
<ComponentGroup Id="CG_FileAssoc" Directory="INSTALLDIR">
<Component Id="C_FileAssoc" Guid="B3C4D5E6-F7A8-9012-7890-234567890123">
<RegistryValue Root="HKCR" Key=".qet" Type="string" Value="QElectroTech.Document" KeyPath="yes" />
<RegistryValue Root="HKCR" Key="QElectroTech.Document" Type="string" Value="QElectroTech Project" />
<RegistryValue Root="HKCR" Key="QElectroTech.Document\DefaultIcon" Type="string"
Value="[INSTALLDIR]ico\qelectrotech.ico" />
<RegistryValue Root="HKCR" Key="QElectroTech.Document\shell\open\command" Type="string"
Value="&quot;[INSTALLDIR]bin\qelectrotech.exe&quot; &quot;%1&quot;" />
</Component>
</ComponentGroup>
<!-- ============================================================
Icon for shortcuts
============================================================ -->
<Icon Id="qet.ico" SourceFile="$(var.FilesDir)\ico\qelectrotech.ico" />
<!-- ============================================================
CustomAction: set elements\ subtree read-only after install
Pattern used: two-step SetProperty + Exec
1. CA_ResolveElementsPath (immediate, SetProperty):
copies the runtime value of INSTALLDIR into the property
ELEMENTS_READONLY_CMD, building the full powershell command.
2. CA_SetElementsReadOnly (deferred, Execute="deferred"):
runs the command stored in ELEMENTS_READONLY_CMD.
This is the correct WiX v4/v7 pattern for passing a
directory path (resolved at runtime) to a deferred CA.
============================================================ -->
<!-- Step 1: build the powershell command with the resolved INSTALLDIR -->
<CustomAction Id="CA_ResolveElementsPath"
Property="CA_SetElementsReadOnly"
Value="powershell.exe -NonInteractive -NoProfile -WindowStyle Hidden -Command &quot;Get-ChildItem -LiteralPath '[INSTALLDIR]elements' -Recurse -File | ForEach-Object { `$_.IsReadOnly = `$true }&quot;" />
<!-- Step 2: deferred execution of the powershell command -->
<CustomAction Id="CA_SetElementsReadOnly"
BinaryRef="Wix4UtilCA_X86"
DllEntry="WixQuietExec"
Execute="deferred"
Impersonate="no"
Return="ignore" />
<InstallExecuteSequence>
<Custom Action="CA_ResolveElementsPath" After="InstallFiles" Condition="NOT Installed AND NOT REMOVE" />
<Custom Action="CA_SetElementsReadOnly" After="CA_ResolveElementsPath" Condition="NOT Installed AND NOT REMOVE" />
</InstallExecuteSequence>
<!-- ============================================================
Main feature (everything included)
============================================================ -->
<Feature Id="ProductFeature" Title="QElectroTech" Level="1">
<ComponentGroupRef Id="CG_AllFiles" />
<ComponentGroupRef Id="CG_Shortcuts" />
<ComponentGroupRef Id="CG_FileAssoc" />
</Feature>
<!-- WixUI_Minimal with QElectroTech GPL-2 license -->
<ui:WixUI Id="WixUI_Minimal" />
<!-- WixVariable without ui: namespace, at Package level -->
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
<!-- Properties shown in Programs and Features -->
<Property Id="ARPPRODUCTICON" Value="qet.ico" />
<Property Id="ARPHELPLINK" Value="https://qelectrotech.org" />
<Property Id="ARPURLINFOABOUT" Value="https://qelectrotech.org" />
<Property Id="ARPCOMMENTS" Value="Free electrical schematic editor" />
</Package>
</Wix>
@@ -0,0 +1,11 @@
@echo off
rem Se rend dans le dossier qui convient
set current_dir=%~dp0
cd /d %current_dir%
rem lance QElectroTech
rem Sans option --config-dir, la configuration de QElectroTech ainsi que la
rem collection d'elements perso seront dans "%APPDATA%\qet"
set command=bin\qelectrotech.exe -platform windows:fontengine=freetype --common-elements-dir=elements/ --common-tbt-dir=titleblocks/ --lang-dir=lang/ %*
@start %command%
+4
View File
@@ -0,0 +1,4 @@
Place all files of "*win32-readytouse.zip" in the "files/" directory
and run "QET.nsi"
enjoy
+300
View File
@@ -0,0 +1,300 @@
[ca]
Gràcies a Qt Software per la biblioteca Qt ( http://www.qtsoftware.com/ ), amb llicència GNU/GPL.
Gràcies al projecte KDE ( http://www.kde.org/ ).
Gràcies a Loic per les seves explicacions d'ordre matemàtic.
Gràcies a Remi Collet pels paquets Fedora.
Gràcies a Laurent Trinques pels paquets Debian.
Gràcies a `trem' pels paquets Mandriva.
Gràcies a TuxFamily ( http://tuxfamily.org/ ) per a l'allotjament del projecte.
Gràcies a `Nishiki' pels seus elements i el seu suport suport.
Gràcies a qtcentre.org per la seva classe SingleApplication.
Gràcies a Alfredo Carreto per les seves traduccions i correccions al castellà ( http://electronicosmx.net )
Gràcies a 'Dr.Slump' et Sivio pour leurs traductions a l'italià
Gràcies a Jose Carlos Martins per les seves traduccions al portuguès
Gràcies a Pavel Fric per les seves traduccions al txec
Gràcies a Pawel Smiech per les seves traduccions al polonès
Gràcies a Yuriy Litkevich per les seves traduccions al rus
Gràcies a Youssef Ouamalkran i Antoni Mirabete per les seves traduccions al català
Gràcies a Gabi Mandoc per les seves traduccions al romanès
Gràcies a Markus Budde i Jonas Stein et Noah Braden per les seves traduccions a l'alemany
Gràcies a Mohamed Souabni per les seves traduccions a l'àrab
Gràcies a Uroš Platiše per les seves traduccions a l'eslovè
Gràcies a Antun Marakovic per les seves traduccions al croat
Gràcies a Nikos Papadopoylos && Yannis Gyftomitros per les seves traduccions al grec
Gràcies a Markos Chandras pels paquets Gentoo
Gràcies a David pels paquets Slackware
Gràcies a Chipsterjulien pels paquets Archlinux AUR
Gràcies a Elbert de NL pels paquets OS/2
Gràcies a Zloidemon pels paquets (port GCC)
Gràcies a Mrbit per ebuild els paquets Gentoo
[en]
Thanks to Qt Software for their Qt library ( http://www.qtsoftware.com/ ), licensed under GNU/GPL.
Thanks to the KDE project ( http://www.kde.org/ ).
Thanks to Loic for his mathematics-related explanations.
Thanks to Remi Collet for the Fedora packaging.
Thanks to Laurent Trinques for the Debian packaging.
Thanks to `trem' for the Mandriva packaging.
Thanks to TuxFamily ( http://tuxfamily.org/ ) for hosting the project.
Thanks to `Nishiki' for his elements and his support.
Thanks to qtcentre.org for their SingleApplication class.
Thanks to Alfredo Carreto for his Spanish translations and fixing Spanish translations ( http://electronicosmx.net )
Thanks to 'Dr.Slump' and Sivio for their translations in Italian
Thanks to Jose Carlos Martins for his translations in Portuguese
Thanks to Pavel Fric for his translations in Czech
Thanks to Pawel Smiech for His Polish translations
Thanks to Yuriy Litkevich for his translations into Russian
Thanks to Youssef Ouamalkran for his translations Catatan
Thanks to Gabi Mandoc for his translations in Romanian
Thanks to Markus Budde and Jonas Stein and Noah Braden for his translations into German
Thanks to Mohamed Souabni for his translations into Arabic
Thanks to Uroš Platiše for his translations into Slovenian
Thanks to Antun Marakovic for his translations Croatian
Thanks to Nikos Papadopoylos && Yannis Gyftomitros their Greek translations
Thanks to Markos Chandras for Gentoo packaging
Thanks to David for packaging Slackware
Thanks to Chipsterjulien for packaging Archlinux AUR packages
Thanks to Elbert from the NL for packaging OS/2
Thanks to zloidemon from for packaging FreeBsd (port GCC)
Thanks to Mrbit for ebuild Gentoo packaging.
[fr]
Merci à Qt Software pour la bibliothèque Qt ( http://www.qtsoftware.com/ ), sous licence GNU/GPL.
Merci au projet KDE ( http://www.kde.org/ ).
Merci à Loic pour ses explications d'ordre mathématique.
Merci à Remi Collet pour les paquets Fedora.
Merci à Laurent Trinques pour les paquets Debian.
Merci à `trem' pour les paquets Mandriva.
Merci à TuxFamily ( http://tuxfamily.org/ ) pour l'hébergement du projet.
Merci à `Nishiki' pour ses éléments et son soutien.
Merci à qtcentre.org pour leur classe SingleApplication.
Merci à Alfredo Carreto pour ses traductions et corrections en espagnol ( http://electronicosmx.net )
Merci à 'Dr.Slump' et Sivio pour leurs traductions en italien
Merci à Jose Carlos Martins pour ses traductions en portugais
Merci à Pavel Fric pour ses traductions en Tchèque
Merci à Pawel Smiech pour ses traductions en polonais
Merci à Yuriy Litkevich pour ses traductions en russe
Merci à Youssef Ouamalkran pour ses traductions en catatan
Merci à Gabi Mandoc pour ses traductions en Roumain
Merci à Markus Budde et Jonas Stein et Noah Braden pour ses traductions en allemand
Merci à Mohamed Souabni pour ses traductions en arabe
Merci à Uroš Platiše pour ses traductions en Slovene
Merci à Antun Marakovic pour ses traductions Croate
Merci à Nikos Papadopoylos && Yannis Gyftomitros pour leurs traductions en grec
Merci à Markos Chandras pour les paquets Gentoo
Merci à David les paquets Slackware
Merci à Chipsterjulien les paquets Archlinux AUR packages
Merci à Elbert from the NL les paquets OS/2
Merci à Zloidemon from for les paquets (port GCC)
Merci à Mrbit for ebuild les paquets Gentoo
[ru]
Спасибо Qt Software за их библиотеку Qt ( http://www.qtsoftware.com/ ), лицензированную на условиях GNU/GPL.
Спасибо проекту KDE ( http://www.kde.org/ ).
Спасибо Loic за объяснения связанные с математикой.
Спасибо Remi Collet за пакет для Fedora.
Спасибо Laurent Trinques за пакет для Debian.
Спасибо `trem' за пакет для Mandriva.
Спасибо TuxFamily ( http://tuxfamily.org/ ) за хостинг для проекта.
Спасибо `Nishiki' за элементы и поддержку.
Спасибо qtcentre.org за их класс SingleApplication.
Спасибо Alfredo Carreto за исправления и перевод на испанский ( http://electronicosmx.net )
Спасибо 'Dr.Slump' за исправления и перевод
[pt]
Agradecimentos a Qt Software pela sua biblioteca Qt ( http://www.qtsoftware.com/ ), licenciada de acordo com a GNU/GPL.
Agradecimentos ao projecto KDE ( http://www.kde.org/ ).
Agradecimentos a Loic pelas suas explicações relacionadas com problemas matemáticos.
Agradecimentos a Remi Collet pela criação dos pacotes para Fedora.
Agradecimentos a Laurent Trinques pela criação dos pacotes para Debian.
Agradecimentos a "trem" pela criação dos pacotes para Mandriva.
Agradecimentos a TuxFamily ( http://tuxfamily.org/ ) por albergarem este projecto.
Agradecimentos a "Nishiki" pela criação de elementos e o seu suporte.
Agradecimentos a qtcentre.org pela classe SingleApplication.
Agradecimentos a Alfredo Carreto pela tradução para Espanhol e pela correcção de traduções em Espanhol ( http://electronicosmx.net ).
Agradecimentos a 'Dr.Slump'pela tradução para italiano
[es]
Agradecimientos a Qt Software por su biblioteca Qt ( http://www.qtsoftware.com/ ), licenciada bajo GNU/GPL.
Agradecimientos al proyecto KDE ( http://www.kde.org/ ).
Agradecimientos a Loic por sus explicaciones relacionadas con problemas matemáticos.
Agradecimientos a Remi Collet por criación de paquetes para Fedora.
Agradecimientos a Laurent Trinques por la creación de paquetes para Debian.
Agradecimientos a "trem" por creación de paquetes para Mandriva.
Agradecimientos a TuxFamily ( http://tuxfamily.org/ ) por el alojamiento de este proyecto.
Agradecimientos a "Nishiki" por creación de elementos e de su suporte.
Agradecimientos a qtcentre.org por classe SingleApplication.
Agradecimientos a Alfredo Carreto por sus traducciones en español y correcciones de traducción en español ( http://electronicosmx.net)
Agradecimientos a 'Dr.Slump' por sus traducciones en italiano
[cs]
Díky Qt Software za jejich knihovnu Qt ( http://www.qtsoftware.com/ ), pod licencí GNU/GPL.
Díky projektu KDE ( http://www.kde.org/ ).
Díky Loicovi za jeho vysvětlení vztahující se k matematice.
Díky Remi Colletovi za balíček pro Fedoru.
Díky Laurentu Trinquesovi za balíček pro Debian.
Díky `trem' za balíček pro Mandrivu.
Díky TuxFamily ( http://tuxfamily.org/ ) za poskytování hostingu pro projekt.
Díky `Nishiki' za jeho prvky a jeho podporu.
Díky qtcentre.org za jejich třídu SingleApplication.
Poděkování Alfredovi Carretovi za jeho španělský překlad a opravy španělského překladu ( http://electronicosmx.net )
Dìky 'Dr.Slump' za jeho italianský překlad
[pl]
Podziękowania dla Qt Software, za biblioteki Qt (http://www.qtsoftware.com/) na licencji GNU / GPL.
Podziękowania dla projektu KDE (http://www.kde.org/).
Podziękowania dla Loic, za pomoc w rozwiązaniu problemów matematycznych.
Podziękowania dla Remi Collet, za pakiety dla Fedory.
Podziękowania dla Laurent Trinquesovi, za pakiety dla Debiana.
Podziękowania dla "trem", za pakiety dla Mandrivy.
Podziękowania dla TuxFamily (http://tuxfamily.org/), za organizację projektu.
Podziękowanie dla "Nishiki", zajego elementy i poparcie.
Podziękowania dla qtcentre.org, za klasę SingleApplication.
Podziękowania dla Alfredo Carreto, za jego tłumaczenie na język hiszpański i korektę ( http://electronicosmx.net)
[it]
Grazie a Qt Software per le loro librerie Qt (http://www.qtsoftware.com/), licenzate sotto GNU/GPL.
Grazie al progetto KDE (http://www.kde.org/).
Grazie a Loic per le sue spiegazioni matematiche.
Grazie a Remi Collet per i pacchetti per Fedora.
Grazie a Laurent Trinques per i pacchetti per Debian.
Grazie a `trem' per i pacchetti per Mandriva.
Grazie a TuxFamily (http://tuxfamily.org/) per l'ospitalità al progetto.
Grazie a `Nishiki' per i suoi elementi ed il supporto.
Grazie a qtcentre.org per la loro classe SingleApplication.
Grazie a Alfredo Carreto per le traduzioni e le correzioni in spagnolo (http://electronicosmx.net).
Grazie a 'Dr.Slump' e 'Silvio' per la traduzione in italiano.
Grazie a Jose Carlos Martins per la traduzione in portoghese.
Grazie a Pavel Fric per la traduzione in ceco.
Grazie a Pawel Smiech per la traduzione in polacco.
Grazie a Yuriy Litkevich per la traduzione in russo .
Grazie a Youssef Ouamalkran per la traduzione in catalano.
Grazie a Gabi Mandoc per la traduzione in rumeno.
Grazie a Markus Budde e Jonas Stein per la traduzione in tedesco.
Grazie a Mohammed Souabni per la traduzione in arabo.
Grazie a Uroš Platiše per la traduzione in sloveno.
Grazie a Antun Marakovic per la traduzione in croato.
Grazie a Nikos Papadopoylos e Yannis Gyftomitros per la traduzione in greco.
Grazie a Markos Chandras per i pacchetti per Gentoo.
[el]
Ευχαριστίες στην Qt Software για την βιβλιοθήκη Qt ( http://www.qtsoftware.com/ ), αδειοδοτημένο ως GNU/GPL.
Ευχαριστίες στο έργο KDE ( http://www.kde.org/ ).
Ευχαριστίες στον Loic για τις εξηγήσεις σχετικές με μαθηματικά.
Ευχαριστίες στον Remi Collet για τα πακέτα Fedora.
Ευχαριστίες στον Laurent Trinques για τα πακέτα Debian.
Ευχαριστίες στον `trem' για τα πακέτα Mandriva.
Ευχαριστίες στο TuxFamily ( http://tuxfamily.org/ ) για τη φιλοξενία του έργου.
Ευχαριστίες στον `Nishiki' για τα στοιχεία και την υποστήριξη του.
Ευχαριστίες στο qtcentre.org για την κλάση SingleApplication.
Ευχαριστίες στον Alfredo Carreto για τις μεταφράσεις του και για την επισκευή της Ισπανικής μετάφρασης ( http://electronicosmx.net )
Ευχαριστίες στον 'Dr.Slump' και τον Sivio για τις μεταφράσεις τους στα Ιταλικά
Ευχαριστίες στον Jose Carlos Martins για την μετάφραση στα Πορτογαλικά
Ευχαριστίες στον Pavel Fric Για την μετάφραση στα Τσέχικα
Ευχαριστίες στον Pawel Smiech για την Πολωνική μετάφραση
Ευχαριστίες στον Yuriy Litkevich για τις μεταφράσεις του στα Ρώσικα
Ευχαριστίες στον Youssef Ouamalkran για τις μεταφράσεις του στα Καταλανικά
Ευχαριστίες στον Gabi Mandoc για τις μεταφράσεις του στα Ρουμανικά
Ευχαριστίες στους Markus Budde και Jonas Stein για τις μεταφράσεις τους στα Γερμανικά
Ευχαριστίες στον Mohamed Souabni για τις μεταφράσεις του στα Αραβικά
Ευχαριστίες στον Uroš Platiše για τις μεταφράσεις του στα Σλοβένικα
Ευχαριστίες στον Antun Marakovic για τις μεταφράσεις του στα Κροατικά
Ευχαριστίες στους Νίκο Παπαδόπουλο και Γιάννη Γυφτομήτρο για τις μεταφράσεις τους στα Ελληνικά
Ευχαριστίες στον Markos Chandras για τα πακέτα Gentoo
Ευχαριστίες στον David για τα πακέτα Slackware
Ευχαριστίες στον Chipsterjulien για τα πακέτα Archlinux AUR
Ευχαριστίες στον Elbert για τα πακέτα OS/2
Ευχαριστίες στον zloidemon για τα πακέτα FreeBsd (port GCC)
Ευχαριστίες στον Mrbit για τα πακέτα ebuild για Gentoo.
[nl]
Dank aan Qt Software voor hun Qt library ( http://www.qtsoftware.com/ ) , onder de GNU / GPL licentie .
Dank aan het KDE-project ( http://www.kde.org/ ) .
Dank aan Loic voor zijn wiskunde - gerelateerde verklaringen .
Met dank aan Remi Collet voor de Fedora pakket.
Met dank aan Laurent Trinques voor de Debian pakket.
Dank aan ` tremolo ' voor de Mandriva pakket.
Dank aan TuxFamily ( http://tuxfamily.org/ ) voor het hosten van het project .
Dank aan ` Nishiki ' voor zijn elementen en zijn steun .
Dank aan qtcentre.org voor hun SingleApplication klasse .
Dank aan Alfredo Carreto voor zijn Spaanse vertalingen en tot vaststelling Spaanse vertalingen ( http://electronicosmx.net )
Dank aan ' Dr.Slump ' en Sivio voor hun vertalingen in het Italiaans
Met dank aan Jose Carlos Martins voor zijn vertalingen in het Portugees
Met dank aan Pavel Fric voor zijn vertalingen in het Tsjechisch
Dank aan Pawel miech voor zijn Poolse vertalingen
Dank aan Yuriy Litkevich voor zijn vertalingen in het Russisch
Dank aan Youssef Ouamalkran voor zijn vertalingen Catatan
Met dank aan Gabi Mandoc voor zijn vertalingen in het Roemeens
Dank aan Markus Budde en Jonas Stein en Noah Braden voor zijn vertalingen in het Duits
Met dank aan Mohamed Souabni voor zijn vertalingen in het Arabisch
Dank aan Uro ? Plati ? E voor zijn vertalingen in het Sloveens
Dank aan Antun Marakovic voor zijn vertalingen Kroatisch
Dank aan Nikos Papadopoylos && Yannis Gyftomitros hun Griekse vertalingen
Dank aan Markos Chandras voor Gentoo pakket
Met dank aan David voor het verpakken van Slackware
Dank aan Chipsterjulien voor het verpakken van Archlinux AUR pakketten
Dank aan Elbert uit de NL voor het pakket van OS/2
Dank aan zloidemon voor het verpakken van FreeBSD ( poort GCC )
Dank aan Mrbit voor ebuild Gentoo pakket.
[be]
Dank aan Qt Software bibliotheek voor Qt ( http://www.qtsoftware.com/ ), onder licentie van GNU/GPL.
Dank aan project KDE ( http://www.kde.org/ ).
Dank aan Loic voor zijn uitleg van de mathematische orde.
Dank aan Remi Collet voor de pakketten Fedora.
Dank aan Laurent Trinques voor de pakkette Debian.
Dank aan `trem' voor de pakketten Mandriva.
Dank aan TuxFamily ( http://tuxfamily.org/ ) voor het hosten van het project.
Dank aan `Nishiki' voor zijn elementen en ondersteuning.
Dank aan qtcentre.org voor hun SingleApplication klasse.
Dank aan Alfredo Carreto voor zijn vertalingen en correcties in het Spaans ( http://electronicosmx.net )
Dank aan 'Dr.Slump' en Sivio hun vertaling in het Italiaans
Dank aan Jose Carlos Martins voor zijn vertalingen in het Portugees
Dank aan Pavel Fric voor zijn vertalingen in het Tsjechisch
Dank aan Pawel Smiech voor zijn vertaling in het Pools
Dank aan Yuriy Litkevich voor zijn vertalingen in het Russisch
Dank aan Youssef Ouamalkran voor zijn vertalingen Catatan
Dank aan Gabi Mandoc voor zijn vertalingen in het Roemeens
Dank aan Markus Budde en Jonas Stein et Noah Braden voor hun vertaling in het Duitse
Dank aan Mohamed Souabni voor zijn vertalingen in het Arabisch
Dank aan Uroš Platiše zijn onze vertalingen Sloveense
Dank aan Antun Marakovic voor zijn vertalingen Kroatisch
Dank aan Nikos Papadopoylos en Yannis Gyftomitros hun vertalingen in het Grieks
Dank aan Markos Chandras voor de pakkette Gentoo
Dank aan David voor de pakkette Slackware
Dank aan Chipsterjulien voor de pakkette Archlinux AUR
Dank aan Elbert voor de pakkette OS/2
Dank aan Zloidemon fvoor de pakkette (port GCC)
Dank aan Mrbit van ebuild voor de pakkette Gentoo
[ko]
Qt 라이브러리(Qt Software, http://www.qtsoftware.com/)를 제공해 주신 Qt Software에 감사드립니다. (GNU/GPL 라이선스)
KDE 프로젝트 ( http://www.kde.org/ )에 감사드립니다.
수학적인 설명을 제공해 주신 Loic에게 감사드립니다.
Fedora 패키지를 제공해 주신 Remi Collet에게 감사드립니다.
Debian 패키지를 제공해 주신 Laurent Trinques에게 감사드립니다.
Mandriva 패키지를 제공해 주신 `trem`에게 감사드립니다.
프로젝트 호스팅을 지원해 주신 TuxFamily ( http://tuxfamily.org/ )에 감사드립니다.
요소 제공 및 지원을 해주신 `Nishiki`에게 감사드립니다.
SingleApplication 클래스를 제공해 준 qtcentre.org에 감사드립니다.
스페인어 번역 및 번역 수정에 기여해 주신 Alfredo Carreto ( http://electronicosmx.net )에게 감사드립니다.
이탈리아어 번역에 기여해 주신 'Dr.Slump'와 Silvio에게 감사드립니다.
포르투갈어 번역에 기여해 주신 Jose Carlos Martins에게 감사드립니다.
체코어 번역에 기여해 주신 Pavel Fric에게 감사드립니다.
폴란드어 번역에 기여해 주신 Pawel Smiech에게 감사드립니다.
러시아어 번역에 기여해 주신 Yuriy Litkevich에게 감사드립니다.
카탈로니아어 번역에 기여해 주신 Youssef Ouamalkran에게 감사드립니다.
루마니아어 번역에 기여해 주신 Gabi Mandoc에게 감사드립니다.
독일어 번역에 기여해 주신 Markus Budde, Jonas Stein, Noah Braden에게 감사드립니다.
아랍어 번역에 기여해 주신 Mohamed Souabni에게 감사드립니다.
슬로베니아어 번역에 기여해 주신 Uroš Platiše에게 감사드립니다.
크로아티아어 번역에 기여해 주신 Antun Marakovic에게 감사드립니다.
그리스어 번역에 기여해 주신 Nikos Papadopoylos와 Yannis Gyftomitros에게 감사드립니다.
한국어 번역에 기여해 주신 정광호 님께 감사드립니다.
Gentoo 패키지를 제공해 주신 Markos Chandras에게 감사드립니다.
Slackware 패키지를 제공해 주신 David에게 감사드립니다.
Arch Linux AUR 패키지를 제공해 주신 Chipsterjulien에게 감사드립니다.
OS/2 패키지를 제공해 주신 Elbert에게 감사드립니다.
FreeBSD(GCC 포트) 패키지를 제공해 주신 zloidemon에게 감사드립니다.
Gentoo ebuild 패키지를 제공해 주신 Mrbit에게 감사드립니다.
+249
View File
@@ -0,0 +1,249 @@
====== ChangeLog from 0.4 to 0.5 ======
In the official collection, there are now 2625 elements, and 418 catégoris for a total of 3043 files.
* Port to Qt 5 framework
* New QSettings native format for config files.
* In the diagram editor, the grid is not displayed by default outside the diagram, the minimum zoom is blocked. A button allows you to un-validate this operation.
* It is now possible to put the tittle block on the right vertical mode.
* The default tittle block can be defined for the next folios of the project.
* The summary now takes the font set in the QElectroTech.conf
* The floating dock is now operational, variables, actions are taken into account on the fly.
* A transformation tool transforms quickly and finely each primitive by handles.
* Add UUID tag for element XML.
* The database enables faster loading a large number of managing symbols in tables changes pixmaps collections, it no longer compares the modification date of the files but their use UUID attributes to update the cache .
* In terms of basic shapes, the transform tool works directly on vectors, it replaces the reduction tool / enlargement that has just been deleted as unnecessary.
* Improve Undo command by QPropertyUndoCommand class.
====== ChangeLog from 0.3 to 0.4 ======
In the official collection, there are now 2298 elements, and 376 catégoris for a total of 2674 files.
* We have removed the flag '-fno-ipa-sra "This settled the compilation problems on Mac OS X and FreeBSD clang.
* The official collection has been redesigned, through the work of Nuri a new structure is in place.
* A menu has been added, allowing you to change the application language.
* we added a summary creation tool.
* Added button "export the nomenclature" transforms data from diagrams to CSV file for spreadsheet.
Arun wrote a detailed manual in English.
* New tools have been added, they can create mechanical connections and draw cabinets, desks, junction boxes, or areas on the schematic (line tool, rectangle, ellipse, polygon type: respect for style dashes).
* An aid in positioning cross, drawing, was added.
* The locked state images and basic forms (basic shapes) is now stored in the project.
* The "control" during the movement of an element, text field disables snapping to the grid, for free positioning.
It is now possible to choose the background folios in white or gray.
* Add supports trackpad gestures (multitouch).
The dates of the cartridges are now using the short system date and date format according to the language detected setting in the OS.
We take advantage of the transition to standard C ++ 11, and a big cleanup in the code was done.
* The undo action or redo the undo stack are now animated graphically.
When the action save, save as, the status bar displays the name and path of the backup job.
Qet is now able to come to load a style sheet (stylesheet) directly from the conf directory.
* A DXF export has been added, the entire project folios can be exported in this format.
* Added reports folio, Cross references.
* Added a variable font size for text of conductors.
* Added new properties to all conductors at the same potential, even through referrals.
* When several conductors have the same value potential equalization, it is not useful to display on all conductors.
* Added button to activates the automatic connection of the conductors of the element when moving it.
* Numbering rules are now available for the entire project.
Qet detects the Windows version and applies the appropriate graphic style, depending on the version of Windows.
====== ChangeLog from 0.3 rc to 0.3 ======
First, the collection of symbols has made a big step forward, with about 1560 new elements.
There are now symbols for pneumatics, hydraulics, process, solar, cold, etc. Considerable effort has been done to organize the collection in a better way.
We hope that the new organisation is clearer for all. We would like to thank all the contributors who send us symbols.
=====-Element Editor: =====
Considerable work has be done to replace the manual defining zone of the symbol, aka hotspot.And fix bugs, It is now automatic. You do not have to care about it anymore.
Primary colors have been added for the drawing shapes.
A contextual menu (right click) has been added. So, you can now work more quickly with symbols. It is also more user-friendly.
====== ChangeLog from v0.3 rc ======
=====-Element Editor: =====
* Replacing checkboxes with lists of colors.
* Removed the manual hotspot, it is now automatic and you do not have to worry.
Officially Collection: a large classification work on the structure was realized. It should be clear to everyone.
The collection is enriched with 1711 items in 286 categories (ie 1997 files)
=====-Schema Editor:=====
* Added import image, image rotation, image resizing and saving the file in the project.
(Double click on the image called a widget and cursor that reduce or enlarge the selected image.)
NB: Following the "edit image" entry will also be added in the right click menu.
* F5 keyboard shortcut can recharge symbol collections.
Some bugs have been resolved, and the translation status continues to grow.
======ChangeLog from v0.3 beta ======
Two more items for the changelog:
* In the official collection, there are now 1672 elements and 256 categoris for a total of 1928 files. In version 0.3 alpha, there were 1465 elements and 233 categories, while version 0.22 had153 elements and 51 categories.
*Progress in the translation (see http://qelectrotech.org/wiki/doc/translation/stats for current state)
* Functions (edit element and find in panel) have been moved to the context
Here is the changelog, for version 0.3 beta:
* Functions (edit element and find in panel) have been moved to the context menu, that can be accessed with right click. This is more user friendly.
* Refresh of categories when an element is moved.
* DateNow button added in the "Diagram property" dialog.
* Dotted lines can now been added between conductors.
* Rich text can be added to the diagram text fields.
[screenshot]
* HTML WYSIWYG editor for rich text: bold, italic, underlined, font size from 6 to 72 pixels, font colour, etc.
* You can change between the two modes(Selection mode <-> View mode) with the scroll button.
* Symbol editor: focus on the new value for language, languages sorted in alphabetical order.
* Added a widget that reflects the loading of a big project.
* Automated numbering of conductors according to your rules. See note from Joshua http://qelectrotech.org/wiki/doc/autonum
* Added a dialog to automatically rotate the text if the associated conductor is vertical or horizontal. Parameters are saved in qelectrotech.conf
* Added basic colours on the tools for lines and for the filling of the primitives, and also for the style line and point in the element editor.
* Added several protection to prevent from saving an element if one of its primitive is beyond the hotspot.
====== ChangeLog from 0.22 to 0.3a ======
===== Application =====
Elements collection: QElectroTech now provides 1465 elements within 233 categories (0.22 provided 153 elements within 51 categories). Most elements are related to electricity though some relate to chillers, solar, hydraulic and pneumatic engineering.
A new kind of collections appeared to store title block templates; as for elements, there is a distinction between common (system-wide) templates and custom (user-wide) templates.
Translations:
English, Spanish, French, Portuguese and Czech translations have been maintained.
Russian translations have been removed because they are not maintained anymore.
Polish, German, Italian, Arabic and Croatian translations have been added.
Following translation to Arabic, some work was done to improve Right-To-Left languages support.
Elements names are fully translated to English, French, Czech and Polish.
Main windows: added a “What's this?” action.
QElectroTech now handles *.titleblock files.
===== Diagram editor =====
It is now possible to move and rotate all texts on a diagram : element texts, conductor texts and independent texts.
When moving a text related to an electrical element, this element is highlighted.
Texts related to a conductor cannot be moved too far away from it.
It is now possible to create diagrams with more than 100 rows/columns.
Elements panel:
During a drag and drop operation, the hovered item is now expanded after a short time not moving the mouse.
Items are now expanded/collapsed by a double click.
Common, custom and embedded collections of title block templates are displayed within the elements panel.
Elements previews and names are now cached into a SQLite database stored in the user configuration directory, thus speeding up the elements panel (re)loading
The elements panel now displays the folio index before each diagram title.
UI consistency: renamed “Import element” to “Open an element file”, separated this action from those related to the current selection, and ensured elements-related actions are disabled when selecting a project/diagram/title block template.
Freshly integrated elements are now highlighted in the elements panel this behaviour can be disabled though.
When clearing the search field, the panel state is restored to its previous state.
Title blocks are now rendered using templates:
For each diagram, users can choose the template to be used in the diagram properties.
They may also drag and drop it from the elements panel to the diagram.
Title block templates are always integrated within the parent project.
Fixed a bug in the print preview dialog.
Added a F2 shortcut for the widget “Edit the color of the given conductor”.
As elements, diagrams now have a “version” attribute for compatibility purposes.
Better handling of file opening for documents saved with newer versions of QElectroTech.
Diagram loading: removed an optimization that could lead to conductors not being loaded when several terminals share the same coordinates.
Users may now enter visualisation mode by pressing Ctrl and Shift.
Printing: when printing diagrams with no title block, use the space left by the title block.
Added a few status and “What's this?” tips.
Got rid of the green icon used for projects, changed a few other icons.
===== Element editor =====
Both static and dynamic texts can now be rotated
Added “dotted” line style
Added white color for texts
Newly added parts are placed above existing ones.
===== Title block template editor =====
A third kind of editor was implemented so users can create their own title block templates:
It allows users to customize the layout and content of cells that constitute the title block.
Cells can be merged and splitted.
Their width can be fixed, relative to the total width or relative to the remaining widths.
Their height is a simple fixed length.
They contain either a logo (be it in SVG or a usual bitmap format) or some text.
The text value is optionally preceded by a label.
As other texts within QElectroTech, labels and texts can be translated to other languages.
Texts and labels may contain variables (e.g. %company-name); these variables are replaced by real world values once the template is applied to a diagram.
Those real-world values can be set among the diagram properties.
====== Changelog 0.11 -> 0.2 ======
À partir de la version 0.2, QElectroTech est disponible en français, anglais, mais aussi :
* en espagnol, grâce aux traductions de Youssef ;
* en russe, grâce aux traductions de Yuriy ;
* en portugais, grâce aux traductions de José.
L'application utilise désormais le thème d'icônes Oxygen, réalisé par Nuno Pinheiro pour le projet KDE.
===== Notion de fichier projet =====
Un fichier .qet peut désormais contenir zéro, un ou plusieurs schémas électriques. Les éléments composant ces schémas sont embarqués dans le fichier projet au moment où ils sont posés sur un schéma. Le panel d'éléments affiche donc désormais :
* les projets ouverts, avec, sous chaque projet :
* les schémas de ce projet,
* la collection embarquée du projet (catégories et éléments utilisés dans les schémas)
* la collection commune fournie par QET,
* et la collection personnelle de l'utilisateur.
===== Éditeur de schémas =====
* Il est désormais possible de déplacer et copier les catégories et éléments par simple glisser-déposer (drag'n drop) dans le panel d'éléments.
* La collection embarquée est manipulable au même titre que la collection utilisateur. Les éléments inutilisés dans le projet apparaissent sur fond rouge et un dialogue permet de les purger rapidement.
* Chaque projet embarque également (au niveau de ses propriétés) les paramétrages par défaut pour les nouveaux schémas, cartouches et conducteurs.
* Il est possible de changer l'ordre des schémas dans le projet en déplaçant les onglets qui les représente. Dans le champ "Folio" des cartouches, on peut se référer à la position du schéma courant ou au nombre total de schémas dans le projet en écrivant respectivement %id et %total.
* Lors du chargement d'un fichier .qet, si des éléments ne sont pas trouvés, ils sont remplacés par un élément "fantôme", ce qui évite de perdre certaines informations lors de l'enregistrement du fichier.
* Le rendu avec un zoom réduit a été amélioré.
* Enfin, le logiciel gère l'ouverture en lecture seule d'un fichier projet.
==== Impression et export ====
À partir de la version 0.2, QElectroTech :
* propose d'utiliser une imprimante réelle ou bien de générer un document PDF ou PostScript, et ce sous Windows comme sous X11.
* génère un aperçu avant l'impression d'un projet. Cet aperçu permet de choisir les options d'impression mais également les schémas à imprimer ou non.
À noter toutefois une limitation pour les impressions PDF/PS sous Windows : le dialogue de mise en page, permettant de spécifier le format du papier ainsi que ses marges, n'est pas disponible.
Le dialogue "Exporter" (pour générer un fichier image d'un schéma) a également été refait dans l'optique d'un export simultané de tous les schémas du projet.
===== Éditeur d'éléments =====
* Lorsque l'on dessine une ligne dans l'éditeur d'éléments, il est possible de choisir un embout différent pour chaque extrémité, comme par exemple une flèche, un cercle, un carré ou, tout simplement, un bout de ligne normal.
* La forme "Rectangle" a été ajoutée.
* On peut enregistrer un élément en désignant un fichier (= comportement en 0.11) ou bien en choisissant un élément cible dans une liste reprenant l'arborescence du panel d'éléments.
* Si l'on maintient la touche Shift lorsque l'on ajoute une partie (droite, cercle, texte, ...), l'outil en cours est conservé après le dessin. Sinon l'éditeur repasse sur l'outil de sélection.
* La grille a été améliorée : sa densité varie en fonction du zoom ; les points correspondant à ceux de la grille de l'éditeur de schémas sont mis en valeur.
* L'accrochage à la grille (aka "snap to grid", également connu sous le nom de grille magnétique ou encore grille aimantée) a été ajouté. Le dessin s'y accroche désormais avec une précision de 1px. On peut travailler en coordonnées libres en maintenant la touche Ctrl enfoncée durant le dessin.
* Le copier-coller a été implémenté : il est possible de coller :
* avec le bouton du milieu de la souris
* en choisissant une "zone de collage" sur l'élément (Ctrl+Shift+V)
* directement (Ctrl+V) : les parties collées sont placées à côté des parties copiées ; si on recolle les parties, elles sont collées encore un cran à côté, et ce de manière incrémentale.
* Des contrôles sont désormais effectués à l'enregistrement : présence de bornes, respect du cadre, etc.
* Uniformisation des menus par rapport à l'éditeur de schémas
====== Changelog 0.1 -> 0.11 ======
===== Fonctionnalités et interface =====
* L'application est désormais capable d'ouvrir un fichier élément passe en paramètre
* L'application se lance désormais une seule fois par utilisateur
* Lors de l'ouverture d'un fichier en dehors de l'application alors que QET est déjà démarré celui-ci essaye de s'afficher ou d'attirer l'attention de l'utilisateur.
* L'application vérifie que ce fichier n'est pas déjà ouvert dans tous les éditeurs de schémas / éléments.
* Ajout de fichiers permettant d'automatiser les associations de fichiers sous Windows (.bat et .reg) et X11 (.desktop et .xml)
* Ajout de menus "Récemment ouverts" pour accéder aux fichiers récents dans les éditeurs de schémas et éléments.
* Ajout d'un splash screen
* La hauteur du schéma est désormais gérée via un système de lignes, dont le nombre et la hauteur sont ajustables.
* Il est également possible d'afficher ou non les en-têtes des lignes et/ou des colonnes.
* Ajout d'une option --lang-dir
* Ajout d'une description dans le dialogue des options d'impression
* Ajout de pages de manuel Unix (`man') en anglais et en français
===== Corrections de bugs =====
* Bug #12 : QET provoquait une erreur de segmentation dès son démarrage dans un environnement sans systray
* Bug #14 : il manquait un / dans le chemin proposé lors de l'impression vers un PDF
* Bug #15 : Mauvais positionnement des champs de texte sur le schéma
* Bug #16 : Mauvaise gestion des modifications du texte d'un conducteur
* La classe DiagramView écrivait sur la sortie d'erreur sans fin de ligne
* L'option --config-dir était mal prise en compte
* Après fermeture d'un schema, le menu Fenêtres n'était pas correctement mis à jour
* Les textes des éléments, des conducteurs, du cartouche ainsi que les textes indépendants utilisent désormais tous la même police.
* Remise à niveau de l'impression suite au passage à Qt 4.4
===== Code et détails techniques =====
* Corrections pour que QET compile avec gcc-4.3
* Les classes Conductor et Element héritent désormais de QObject (dépendance sur Qt 4.4)
* Affinage du constructeur de la classe QETApp
* Moins d'avertissements à la compilation (testé avec gcc 4.3)
* Moins d'inclusions non pertinentes
* Nettoyage du trunk : déplacement des sources dans un sous-répertoire
@@ -0,0 +1,344 @@
# Changelog
## [Unreleased](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/HEAD)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9...HEAD)
**Closed issues:**
- error in doxygen action code [\#414](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/414)
- "NoName" is automatically inserted into empty text cells in title block [\#407](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/407)
- Apple silicon download is not working [\#400](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/400)
- Apple silicon download is not working [\#394](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/394)
- Differenciating connector for proper labeling [\#390](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/390)
- Non-perpendicular connections [\#368](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/368)
- using the wrong Application Data folder on Windows [\#325](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/325)
- Unclear which PPA to use [\#321](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/321)
- missing group functionality [\#318](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/318)
- segfault due to calling method of uninitialized object [\#311](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/311)
- Cannot open qelectrotech.app on macOS Sequoia 15.0 [\#307](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/307)
- Dark Mode [\#301](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/301)
- README 404 Not Found URL: qelectrotech.org/download.html needs to be qelectrotech.org/download.php [\#298](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/298)
- Malware warning when trying to install dev version 0.100 [\#290](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/290)
- The page sorting of folio [\#279](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/279)
- Bad file name for translations [\#278](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/278)
- Error using Portuguese Language [\#274](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/274)
- Uninstaller [\#265](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/265)
- New Maintainer [\#263](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/263)
- crash on export project db \(sqlite\) [\#262](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/262)
- https://qelectrotech.org/ is down for several days now ! [\#261](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/261)
- right click on text crashes app [\#260](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/260)
- broken link on github [\#259](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/259)
- Build on Bullseye 11.5 fails [\#254](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/254)
- Question about ARM target in future release [\#238](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/238)
- Component library disappears completely after reset of program [\#87](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/87)
- Can't change language in portable version [\#75](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/75)
- Transformation Matrix for Element Editor [\#56](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/56)
**Merged pull requests:**
- Update QCH Help file [\#416](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/416) ([Int-Circuit](https://github.com/Int-Circuit))
- no random hashes to have more constant order of XML-tags [\#415](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/415) ([plc-user](https://github.com/plc-user))
- Delete outdated QET docs [\#412](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/412) ([Int-Circuit](https://github.com/Int-Circuit))
- Fixing translation file list in CMake [\#404](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/404) ([arummler](https://github.com/arummler))
- Update dependencies to fix compilation errors [\#403](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/403) ([arummler](https://github.com/arummler))
- Minor corrections to prevent crashes [\#401](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/401) ([Evilscrack](https://github.com/Evilscrack))
- Correct compositeText alignment on copying [\#399](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/399) ([ChuckNr11](https://github.com/ChuckNr11))
- Better handling of conductors when moving [\#398](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/398) ([ChuckNr11](https://github.com/ChuckNr11))
- A few small improvements [\#395](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/395) ([ChuckNr11](https://github.com/ChuckNr11))
- Added updated automatic doxygen build on push + theme to make it fit with docs page [\#389](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/389) ([Int-Circuit](https://github.com/Int-Circuit))
- qet\_de updated [\#388](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/388) ([Bisku](https://github.com/Bisku))
- only calculate grid-point-size, when min != max [\#387](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/387) ([plc-user](https://github.com/plc-user))
- Mouse hover text for dynamic text items [\#386](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/386) ([elevatormind](https://github.com/elevatormind))
- improvement: adjust size of grid-dots with zoom-factor [\#384](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/384) ([plc-user](https://github.com/plc-user))
- adjust zoom-factor to use cosmetic-line and fixed comments [\#383](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/383) ([plc-user](https://github.com/plc-user))
- element-editor: fix jumping positions when rotate, mirror or flip [\#382](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/382) ([plc-user](https://github.com/plc-user))
- unify some more code for Qt5 & Qt6 \(and more\) [\#379](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/379) ([plc-user](https://github.com/plc-user))
- same simplifications as in \#376 "use the same code for Qt5 & Qt6" [\#377](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/377) ([plc-user](https://github.com/plc-user))
- simplify and use the same code for Qt5 & Qt6 [\#376](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/376) ([plc-user](https://github.com/plc-user))
- bordertitleblock: use same code for Qt5 & Qt6 for "numbering" rows [\#375](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/375) ([plc-user](https://github.com/plc-user))
- some minor changes [\#374](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/374) ([plc-user](https://github.com/plc-user))
- implement setting of point-size of grids [\#372](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/372) ([plc-user](https://github.com/plc-user))
- some small changes for selective move [\#370](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/370) ([plc-user](https://github.com/plc-user))
- Added slovak translation to org.qelectrotech.qelectrotech.desktop [\#369](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/369) ([prescott66](https://github.com/prescott66))
- unify calls to "setRotation" for element-primitives again [\#367](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/367) ([plc-user](https://github.com/plc-user))
- Added option to only move dynamic texts [\#365](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/365) ([scorpio810](https://github.com/scorpio810))
- New variables for conductor text formulas [\#364](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/364) ([scorpio810](https://github.com/scorpio810))
- Fix typo widht to width [\#362](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/362) ([pkess](https://github.com/pkess))
- element-editor: add mirror and flip for "text" [\#361](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/361) ([plc-user](https://github.com/plc-user))
- Add Swedish translation [\#360](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/360) ([scorpio810](https://github.com/scorpio810))
- German text for launcher and debian package code style [\#359](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/359) ([pkess](https://github.com/pkess))
- some more rotation, mirror and flip [\#358](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/358) ([plc-user](https://github.com/plc-user))
- BugFix: Flip and Mirror of terminals [\#357](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/357) ([plc-user](https://github.com/plc-user))
- element-editor: fix rotation and more [\#356](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/356) ([plc-user](https://github.com/plc-user))
- minor: mostly typos [\#355](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/355) ([plc-user](https://github.com/plc-user))
- a few translated shortcuts were still there ... fixed! [\#354](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/354) ([plc-user](https://github.com/plc-user))
- FIX: some shortcuts do not work with language set to local [\#353](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/353) ([plc-user](https://github.com/plc-user))
- fix movement of element, when origin is outside of graphics [\#352](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/352) ([plc-user](https://github.com/plc-user))
- FIX copy-and-paste in element-editor: set paste-position to meaningful values [\#351](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/351) ([plc-user](https://github.com/plc-user))
- some cleaning for element-file [\#350](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/350) ([plc-user](https://github.com/plc-user))
- fix: properties in project-file [\#348](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/348) ([plc-user](https://github.com/plc-user))
- translation: update German and English [\#347](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/347) ([plc-user](https://github.com/plc-user))
- export: set maximum width / height according limitations in QPainter [\#346](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/346) ([plc-user](https://github.com/plc-user))
- export: set maximum width / height according specifications of export-type [\#345](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/345) ([plc-user](https://github.com/plc-user))
- some clean-up for element-file and in code [\#344](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/344) ([plc-user](https://github.com/plc-user))
- minor: typos, comments, whitespace, translation [\#343](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/343) ([plc-user](https://github.com/plc-user))
- Sort names in element-file by language-code [\#342](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/342) ([plc-user](https://github.com/plc-user))
- more precise Log-Text for search of "qet\_tb\_generator" [\#341](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/341) ([plc-user](https://github.com/plc-user))
- machine\_info: add entry for QETApp::configDir\(\) also for win [\#340](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/340) ([plc-user](https://github.com/plc-user))
- remove dead code \(local variables that were never used\) [\#339](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/339) ([plc-user](https://github.com/plc-user))
- minor changes [\#338](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/338) ([plc-user](https://github.com/plc-user))
- Update of qet\_de [\#337](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/337) ([Bisku](https://github.com/Bisku))
- rewrite code for executing “qet\_tb\_generator” plugin [\#335](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/335) ([plc-user](https://github.com/plc-user))
- build-aux/snap/snapcraft.yaml: python3.8 -\> 3.10 [\#334](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/334) ([zultron](https://github.com/zultron))
- corrected a few places where QETApp::documentDir\(\) should also be used [\#333](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/333) ([plc-user](https://github.com/plc-user))
- add commandline-parameter "--data-dir" [\#332](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/332) ([plc-user](https://github.com/plc-user))
- machine\_info: fix element-count and make static text a bit shorter [\#331](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/331) ([plc-user](https://github.com/plc-user))
- formatting / whitespace - unify declarations [\#330](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/330) ([plc-user](https://github.com/plc-user))
- Set default-location for projects to documents-dir. [\#329](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/329) ([plc-user](https://github.com/plc-user))
- machine\_info.cpp: add explaining text for directory-list [\#328](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/328) ([plc-user](https://github.com/plc-user))
- set config- and data-dir to system-specific paths [\#327](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/327) ([plc-user](https://github.com/plc-user))
- Update qet\_cs.ts [\#326](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/326) ([pafri](https://github.com/pafri))
- update German translation [\#324](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/324) ([plc-user](https://github.com/plc-user))
- fix copyright-year [\#323](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/323) ([plc-user](https://github.com/plc-user))
- PT-BR language update [\#322](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/322) ([gleissonjoaquim3](https://github.com/gleissonjoaquim3))
- Fix: Only scroll diagram-view, when moved text leaves visible area [\#320](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/320) ([plc-user](https://github.com/plc-user))
- Change Sorting of ElementInfo ComboBox [\#319](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/319) ([ChuckNr11](https://github.com/ChuckNr11))
- Revert "ElementEditor elmt\_info\_cb sorting changed" [\#317](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/317) ([scorpio810](https://github.com/scorpio810))
- Fix typo and some whitespace [\#316](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/316) ([plc-user](https://github.com/plc-user))
- Fix missing company-titleblocks in properties-dialog [\#315](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/315) ([plc-user](https://github.com/plc-user))
- ElementEditor elmt\_info\_cb sorting changed [\#314](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/314) ([ChuckNr11](https://github.com/ChuckNr11))
- fix typos and whitespace [\#313](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/313) ([plc-user](https://github.com/plc-user))
- Force light mode in collections like projects [\#312](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/312) ([Arusekk](https://github.com/Arusekk))
- About QET: improvements in usability [\#310](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/310) ([plc-user](https://github.com/plc-user))
- use MessageBox to inform user about additional info when importing scaled element [\#308](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/308) ([plc-user](https://github.com/plc-user))
- make text for missing software "dxf2elmt" translatable [\#304](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/304) ([plc-user](https://github.com/plc-user))
- QET\_ElementScaler: fix error for Qt 5.9 and added mirroring [\#303](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/303) ([plc-user](https://github.com/plc-user))
- integrate "QET\_ElementScaler" as external software [\#302](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/302) ([plc-user](https://github.com/plc-user))
- move code into else-clause to avoid possible crashes [\#300](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/300) ([plc-user](https://github.com/plc-user))
- add terminal-names to connection in qet-file [\#297](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/297) ([plc-user](https://github.com/plc-user))
- fix: editing SpinBoxes with keyboard lose focus [\#296](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/296) ([plc-user](https://github.com/plc-user))
- Spanish lang update [\#295](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/295) ([joseyspain](https://github.com/joseyspain))
- More spanish translations.Josey [\#294](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/294) ([joseyspain](https://github.com/joseyspain))
- update German and English translations [\#293](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/293) ([plc-user](https://github.com/plc-user))
- hide SVG background checkbox in print preferences [\#292](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/292) ([plc-user](https://github.com/plc-user))
- fixed indentations of the remaining \*.cpp/\*.h files [\#291](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/291) ([plc-user](https://github.com/plc-user))
- correct more indentations / whitespace [\#289](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/289) ([plc-user](https://github.com/plc-user))
- update German and English translations [\#288](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/288) ([plc-user](https://github.com/plc-user))
- some minor changes [\#286](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/286) ([plc-user](https://github.com/plc-user))
- correct comments [\#285](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/285) ([plc-user](https://github.com/plc-user))
- FIX SegFault: Disable menu-entry for DB-export when no project loaded [\#284](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/284) ([plc-user](https://github.com/plc-user))
- changed some remaining "pt\_br" to "pt\_BR" [\#282](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/282) ([plc-user](https://github.com/plc-user))
- add option "transparent background" in SVG-export [\#281](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/281) ([plc-user](https://github.com/plc-user))
- Fix sizes [\#280](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/280) ([plc-user](https://github.com/plc-user))
- added folder "company-titleblocks" \(incl. language-files\) [\#277](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/277) ([plc-user](https://github.com/plc-user))
- update translations: de, en, nl [\#276](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/276) ([plc-user](https://github.com/plc-user))
- fix: set default "company-element-dir" [\#275](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/275) ([plc-user](https://github.com/plc-user))
- Fix Cmake build [\#273](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/273) ([De-Backer](https://github.com/De-Backer))
- added "company-collection" as second user-collection [\#272](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/272) ([plc-user](https://github.com/plc-user))
- corrected german texts for "line-style" [\#269](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/269) ([plc-user](https://github.com/plc-user))
- Too many parts [\#268](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/268) ([scorpio810](https://github.com/scorpio810))
- Merge Terminal strip to master [\#267](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/267) ([scorpio810](https://github.com/scorpio810))
- Terminal strip [\#266](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/266) ([scorpio810](https://github.com/scorpio810))
- Added new symbols [\#264](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/264) ([kamikazzyyyy](https://github.com/kamikazzyyyy))
## [0.9](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.9) (2023-01-03)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9-dev-2022/12/30...0.9)
## [0.9-dev-2022/12/30](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.9-dev-2022/12/30) (2022-12-30)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9-dev-2022/08/15...0.9-dev-2022/12/30)
## [0.9-dev-2022/08/15](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.9-dev-2022/08/15) (2022-08-13)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/9-dev-2022/04/22...0.9-dev-2022/08/15)
**Closed issues:**
- Polylines always closed on dxf export [\#228](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/228)
- Refreshing after making changes to elements [\#168](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/168)
## [9-dev-2022/04/22](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/9-dev-2022/04/22) (2022-04-09)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/9-dev-2021/09/09...9-dev-2022/04/22)
**Closed issues:**
- File dialog should enforce suffix [\#206](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/206)
- snap: Update stable release to core20 & introduce branch for stable releases [\#201](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/201)
- Can we submit Flatpak to Flathub? [\#143](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/143)
## [9-dev-2021/09/09](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/9-dev-2021/09/09) (2021-09-08)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/9-dev-2021/06/28...9-dev-2021/09/09)
## [9-dev-2021/06/28](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/9-dev-2021/06/28) (2021-07-06)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/9-dev-2021/05/09...9-dev-2021/06/28)
## [9-dev-2021/05/09](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/9-dev-2021/05/09) (2021-05-09)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9-dev-2021/05...9-dev-2021/05/09)
## [0.9-dev-2021/05](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.9-dev-2021/05) (2021-04-30)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.8.0...0.9-dev-2021/05)
**Merged pull requests:**
- Rewrite how Properties are stored in the Project file [\#144](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/144) ([Murmele](https://github.com/Murmele))
- Xml properties rebase2 [\#80](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/80) ([Murmele](https://github.com/Murmele))
## [0.8.0](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.8.0) (2021-02-21)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9-dev...0.8.0)
## [0.9-dev](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.9-dev) (2021-02-21)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.8.rc...0.9-dev)
**Closed issues:**
- QET font [\#110](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/110)
## [0.8.rc](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.8.rc) (2020-12-01)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.8-dev...0.8.rc)
**Closed issues:**
- overlapping comparisons always evaluate to true [\#78](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/78)
- New snap break HiDPI [\#41](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/41)
## [0.8-dev](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.8-dev) (2019-08-06)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.7.0...0.8-dev)
## [0.7.0](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.7.0) (2019-07-17)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.2a...0.7.0)
## [0.2a](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.2a) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.2b...0.2a)
## [0.2b](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.2b) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.2rc1...0.2b)
## [0.2rc1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.2rc1) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.2rc2...0.2rc1)
## [0.2rc2](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.2rc2) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.2...0.2rc2)
## [0.2](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.2) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.3a...0.2)
## [0.3a](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.3a) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.11...0.3a)
## [0.11](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.11) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.21...0.11)
## [0.21](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.21) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.22...0.21)
## [0.22](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.22) (2019-06-26)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.70rc2...0.22)
## [0.70rc2](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.70rc2) (2019-06-25)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.70rc1...0.70rc2)
## [0.70rc1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.70rc1) (2019-04-12)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.61...0.70rc1)
## [0.61](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.61) (2018-08-23)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.6...0.61)
## [0.6](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.6) (2018-03-06)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.6rc4...0.6)
## [0.6rc4](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.6rc4) (2018-01-12)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.6rc3...0.6rc4)
## [0.6rc3](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.6rc3) (2017-09-20)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.6rc2...0.6rc3)
## [0.6rc2](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.6rc2) (2017-06-13)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.6rc1...0.6rc2)
## [0.6rc1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.6rc1) (2017-04-23)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.5...0.6rc1)
## [0.5](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.5) (2015-11-27)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.5rc1...0.5)
## [0.5rc1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.5rc1) (2015-10-30)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.5b...0.5rc1)
## [0.5b](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.5b) (2015-10-04)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.4...0.5b)
## [0.4](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.4) (2015-02-20)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.4rc2...0.4)
## [0.4rc2](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.4rc2) (2014-12-27)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.4rc1...0.4rc2)
## [0.4rc1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.4rc1) (2014-11-10)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.4b...0.4rc1)
## [0.4b](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.4b) (2014-11-02)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.3...0.4b)
## [0.3](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.3) (2013-09-28)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.3rc...0.3)
## [0.3rc](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.3rc) (2013-09-10)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.3b...0.3rc)
## [0.3b](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.3b) (2013-06-18)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.1...0.3b)
## [0.1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.1) (2008-03-08)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.1rc1...0.1)
## [0.1rc1](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.1rc1) (2008-03-02)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.1b...0.1rc1)
## [0.1b](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.1b) (2007-12-23)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/5cadf173c7b73460b62409c81568fc8999177d52...0.1b)
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
@@ -0,0 +1,233 @@
[en]
The elements collection provided along with QElectroTech is provided as is and
without any warranty of fitness for your purpose or working.
The usage, the modification and the integration of the elements into electric
diagrams is allowed without any condition, whatever the final license of the
diagrams is.
Permission is not granted to use this software or any of the associated files
as sample data for the purposes of building machine learning models.
If you redistribute all or a part of the QElectroTech collection, with or
without any modification, out of an electric diagram, you must respect the
conditions of the CC-BY license:
This work is licensed under the Creative Commons Attribution 3.0 License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[fr]
La collection d'éléments fournie avec QElectroTech est fournie telle quelle et
sans la moindre garantie qu'elle convienne à votre utilisation ou qu'elle
fonctionne.
L'utilisation, la modification et l'intégration des éléments dans des schémas
électriques est autorisée sans condition, quelle que soit la licence finale des
schémas.
L'autorisation n'est pas accordée pour utiliser ce logiciel ou l'un des fichiers associés
comme exemples de données aux fins de création de modèles dapprentissage automatique.
Si vous redistribuez tout ou partie de la collection QElectroTech, avec ou sans
modification, en dehors d'un schéma électrique, vous devrez respecter les
conditions de la licence CC-BY :
Cette création est mise à disposition selon le Contrat Paternité 3.0
disponible en ligne http://creativecommons.org/licenses/by/3.0/ ou par
courrier postal à Creative Commons, 171 Second Street, Suite 300, San Francisco,
California 94105, USA.
[de]
Die mit QElectroTech zur Verfügung gestellte Sammlung von Elementen wird ohne
Gewährleistung der Eignung für einen bestimmten Zweck oder der Funktions-
fähigkeit zur Verfügung gestellt.
Die Verwendung, Modifikation und Integration der Elemente in elektrische
Schaltpläne ist uneingeschränkt erlaubt, unabhängig von der endgültigen Lizenz
der Schaltpläne.
Es ist nicht gestattet, diese Software oder eine der zugehörigen Dateien
als Beispieldaten für die Erstellung von Modellen für maschinelles Lernen
zu verwenden.
Wenn Sie die gesamte QElectroTech-Sammlung oder Teile davon, mit oder ohne
Modifikationen, aus einem Schaltplan weitergeben, müssen Sie die Bedingungen
der CC-BY-Lizenz einhalten.
Dieses Werk steht unter einer Creative Commons Attribution 3.0 Lizenz.
Eine Kopie dieser Lizenz finden Sie unter:
http://creativecommons.org/licenses/by/3.0/
oder senden Sie einen Brief an:
Creative Commons, 171 Second Street, Suite 300,
San Francisco, Kalifornien, 94105, USA.
[ru]
Коллекция элементов, поставляемая вместе с QElectroTech, поставляется "как есть"
и без каких-либо гарантий пригодности для той или иной цели или работы.
Использование, изменение и интеграция элементов в электрическую
схему разрешается без каких-либо условий, безотносительно конечной лицензии на
схему.
Если Вы распространяете всю или часть коллекции QElectroTech, с или без
изменений, отдельно от электрической схемы, Вы должны соблюдать условия лицензии
CC-BY:
Эта работа лицензирована на условиях Creative Commons Attribution 3.0 License.
Чтобы увидеть копию этой лицензии, посетите
http://creativecommons.org/licenses/by/3.0/ или отправте письмо в Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
(данный перевод, на русский язык, является вольным и выполнен не юристом!)
[pt]
A colecção de elementos fornecida com o programa QElectroTech é fornecida como é
e sem nenhuma garantia da aptidão para o seu uso e sem garantia de que funciona.
É permitido, sem condição alguma, qualquer que seja a licença final, usar,
editar e incluir estes elementos em esquemas eléctricos.
Se você redistribuir uma parte ou toda a colecção de elementos do programa
QElectroTech, tendo editado ou não os elementos, sem ser num esquema eléctrico,
tem de respeitar as condições da licença CC-BY:
Este trabalho está licenciado de acordo com os termos da licença Creative
Commons Attribution 3.0 License.
Para ver uma cópia da licença visite http://creativecommons.org/licenses/by/3.0/
ou envie uma carta para o endereço Creative Commons, 171 Second Street, Suite
300, San Francisco, California, 94105, USA.
[es]
La colección de elementos QElectrotech es distruibida tal cual y sin ninguna
garantía a la conveniencia de su uso y sin garantía de que funciona.
Se permite sin condicion alguna, cualquiera que sea la licencia final, usar,
editar, e incluir estos elementos en esquemas eléctricos.
Si usted redistribuye una parte de la colección o toda la collección de
QElectrotech, con o sin ediciones, fuera de un esquema eléctrico, tiene que
respetar las condiciones de la licencia CC-BY:
Esta obra está bajo una licencia Reconocimiento 3.0 de Creative Commons.
Para ver una copia de esta licencia, visite
http://creativecommons.org/licenses/by/3.0/ o envie una carta a Creative
Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
[ca]
La col·lecció de símbols QElectrotech és distribuïda tal qual i sense cap
garantia d'idoneïtat d'ús ni de funcionament.
Es permet incondicionalment, amb independència de la llicència final, emprar,
editar, i incloure aquests símbols en esquemes elèctrics.
Si vostè redistribueix una part de la col·lecció de QElectrotech o tota ella,
amb condicions o sense, separadament d'un esquema elèctric, haurà de respectar
les condicions de la llicència CC-BY:
Aquesta obra es troba sota una llicència Reconeixement 3.0 de Creative Commons.
Per veure una còpia d'aquesta llicència visiti
http://creativecommons.org/licenses/by/3.0/ o enviï una carta a Creative
Commons, 171 Second Street, Suite 300, San Francisco, California 94105,
[cs]
Sbírka prvků poskytovaná společně s QElectroTechem je poskytována tak, jak je,
bez záruky nebo vhodnosti pro váš účal nebo práci.
Používání, úpravy a začlenění prvků do nákresů elektrických
obvodů se povoluje bez jakýchkoli podmínek, cokoli je konečná licence nákresu.
Pokud rozdáte celou nebo část ze sbírky QElectroTechu, s nebo bez
jakýchkoli úprav, mimo elektrický nákres, musíte brát ohledy na podmínky
licence CC-BY:
tato práce je licencována pod licencí Creative Commons Attribution 3.0 License.
Kopii této licence si můžete prohlédnout, navštivte
http://creativecommons.org/licenses/by/3.0/ nebo pošlete dopis Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[pl]
Biblioteka elementów dostarczana wraz z QElectroTech jest w formie "taka jaka jest",
bez żadnych gwarancji przydatności.
Dozwolona jest edycja, modyfikacja i użytkowanie elementów bez żadnych warunków
i bez względu na końcową licencję tworzonych schematów.
W przypadku wykorzystywania całości lub części biblioteki elementów QElectroTech
do innych celów niż tworzenie schematów elektrycznych, należy przestrzegać
warunków licencji CC-BY:
Niniejsza praca jest licencjonowana na zasadach Creative Commons Attribution 3.0 License.
Aby zobaczyć kopię licencji, należy odwiedzić stronę internetową:
http://creativecommons.org/licenses/by/3.0/ lub wysłać list do Creative
Commons, 171 Second Street, Suite 300, San Francisco, Kalifornia 94105, USA.
[it]
La collezione di elementi che si trova in QElectroTech è fornita così com'è
senza alcuna garanzia di usabilità o funzionamento.
L'uso, la modifica e l'integrazione degli elementi negli schemi elettrici
è permessa senza condizioni, qualunque si ala licenza dello schema finale.
Distribuendo tutto o parte della collezione di QElettroTech, con o senza
modifiche, fuori da uno schema elettrico, bisogna rispettare le condizioni
della licenza CC-BY:
Questo lavoro è licenziato sotto la Licenza Creative Commons 3.0.
Per vedere una copia di questa licenza, visitate il sito
http://creativecommons.org/licenses/by/3.0/ o inviate una lettera a Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[el]
Η συλλογή στοιχείων που παρέχεται μαζί με το QElectroTech παρέχεται ως έχει και
χωρίς καμία εγγύηση καταλληλότητας για συγκεκριμένο σκοπό ή την εργασία σας.
Η χρήση, η τροποποίηση και η ενσωμάτωση των στοιχείων στα ηλεκτρικά
διαγράμματα επιτρέπεται χωρίς καμία προϋπόθεση, όποια και αν είναι η τελική άδεια
των διαγραμμάτων.
Εάν αναδιανείμετε το σύνολο ή ένα μέρος της συλλογής του QElectroTech, με ή
χωρίς καμία τροποποίηση, έξω από ένα ηλεκτρικό διάγραμμα, θα πρέπει να σεβαστείτε
τους όρους της άδειας CC-BY:
Το έργο αυτό είναι υπό την άδεια Creative Commons Attribution 3.0 License.
Για να δείτε ένα αντίγραφο της άδειας αυτής, επισκεφτείτε το
http://creativecommons.org/licenses/by/3.0/ ή στείλτε μια επιστολή στο Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[nl]
De elementen collectie voorzien, samen met QElectroTech wordt geleverd als en
zonder enige garantie van geschiktheid voor uw doel of werk.
Het gebruik, de wijziging en de integratie van de elementen in elektrische
diagrammen wordt toegestaan zonder enige voorwaarden, ongeacht wat de uiteindelijke
vergunning van het diagram is.
Als u alle of een deel van de QElectroTech collectie, met of herdistribueren
zonder enige wijziging, van een elektrisch schema, moet u voldoen aan de
voorwaarden van de CC-BY-licentie:
Dit werk is gelicenseerd onder de Creative Commons Attribution 3.0-licentie.
Om een kopie van deze licentie te bekijken, bezoek
http://creativecommons.org/licenses/by/3.0/ of stuur een brief naar Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[be]
De elementen collectie welke samen met QElectroTech wordt geleverd zonder enige garantie
of deze geschikt zijn voor uw doel of de werking.
Het gebruik, wijzigen en integratie van de elementen in uw elektrische
schema's wordt toegestaan zonder enige voorwaarden, ongeacht wat de uiteindelijke
liventie van het schema is.
Als u één of meerdere elementen van de QElectroTech collectie, met of zonder wijzigingen, herdistribuer in een elektrisch schema of zonder schzma , moet u de voorwaarden van de
CC-BY-licentie volgen:
Dit werk is gelicenseerd onder de Creative Commons Attribution 3.0-licentie.
Om een kopie van deze licentie te bekijken, bezoek
http://creativecommons.org/licenses/by/3.0/ of stuur een brief naar Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[da]
Element samlinger leveret sammen med QElectroTech er tilvejebragt som er og
uden nogen garanti for egnethed til dit formål eller arbejde.
Brug, modifikation og integration af elementer til elektrisk diagrammer er
tilladt uden nogen betingelse uanset den endelige diagram licens.
Omfordeling af hele eller dele af QElectroTech samlingen, med eller
uden ændring af et elektrisk diagram, skal du respektere betingelser for CC-BY-licens:
Dette værk er licenseret under Creative Commons Attribution 3.0 License.
For at se en kopi af denne licens, besøg
http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
[ja]
QElectroTech と一緒に提供される要素コレクションは現状のまま提供され、
あなたの目的や作業に適合することを保証するものではありません。
回路図での要素の利用、変更、統合は、回路図の最終的なライセンスに関わらず
無条件で許可されます。
回路図とは別に QElectroTech コレクションの全部または一部を
変更の有無に関わらず再配布する場合は CC-BY ライセンスを尊重しなければなりません:
この作品は Creative Commons Attribution 3.0 の下でライセンスされます。
ライセンスのコピーを見るには http://creativecommons.org/licenses/by/3.0/ にアクセスするか、
「Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.」に
手紙を送ってください。
[ko]
QElectroTech와 함께 제공되는 요소 컬렉션은 “있는 그대로(as is)” 제공되며,
특정 목적에 대한 적합성이나 정상 동작에 대한 어떠한 보증도 제공되지 않습니다.
요소의 사용, 수정 및 전기 도면에의 통합은 도면의 최종 라이선스와 관계없이
아무런 조건 없이 허용됩니다.
본 소프트웨어 또는 관련 파일을 기계 학습(machine learning) 모델을 구축하기 위한
샘플 데이터로 사용하는 것은 허용되지 않습니다.
전기 도면과 분리된 형태로 QElectroTech 요소 컬렉션의 전부 또는 일부를,
수정 여부와 관계없이 재배포하는 경우에는 CC-BY 라이선스 조건을 준수해야 합니다.
본 저작물은 Creative Commons Attribution 3.0 라이선스에 따라 제공됩니다.
라이선스 사본은 다음 주소에서 확인할 수 있습니다.
http://creativecommons.org/licenses/by/3.0/
또는 다음 주소로 서신을 보내실 수 있습니다.
Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
+342
View File
@@ -0,0 +1,342 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
+60
View File
@@ -0,0 +1,60 @@
[ca]
QElectroTech és una aplicació Qt5 per crear esquemes elèctrics.
QET utilitza el format XML per als seus elements i esquemes i inclou un editor d'esquemes, un editor d'elements i un editor de caixetins.
[en]
QElectroTech is a Qt5 application to design electric diagrams.
It uses XML files for elements and diagrams, and includes both a diagram editor, a element editor, and an titleblock editor.
[fr]
QElectroTech est une application Qt5 pour réaliser des schémas électriques.
QET utilise le format XML pour ses éléments et ses schémas et inclut un éditeur de schémas, un éditeur d'élément, ainsi qu'un editeur de cartouche.
[de]
QElectroTech ist eine Qt5 Software, um Schaltpläne zu erstellen.
QET benutzt das XML Format für seine Bauteile und seine Projekte, und beinhaltet einen Schaltplaneditor, einen Bauteileditor sowie einen Schriftfeldeditor.
[ru]
QElectroTech - приложение написанное на Qt5 и предназначенное для разработки электрических схем.
Оно использует XML-файлы для элементов и схем, и включает, как редактор схем, так и редактор элементов.
[pt]
QElectroTech é uma aplicação baseada em Qt5 para desenhar esquemas eléctricos.
QET utiliza ficheiros XML para os elementos e para os esquemas e inclui um editor de esquemas e um editor de elementos.
[es]
QElectroTech es una aplicación Qt5 para diseñar esquemas eléctricos.
Utiliza archivos XML para los elementos y esquemas, e incluye un editor de esquemas y un editor de elementos.
[cs]
QElectroTech je aplikací Qt5 určenou pro návrh nákresů elektrických obvodů.
Pro prvky a nákresy používá soubory XML, a zahrnuje v sobě jak editor nákresů, tak editor prvků.
[pl]
QElectroTech to aplikacja napisana w Qt5, przeznaczona do tworzenia schematów elektrycznych.
Wykorzystuje XML do zapisywania plików elementów i projektów. Posiada edytor schematów i elementów.
[it]
QElectroTech è una applicazione fatta in Qt5 per disegnare schemi elettrici.
QET usa il formato XML per i suoi elementi e schemi, includendo anche un editor per gli stessi.
[el]
Το QElectroTech είναι μια εφαρμογή Qt5 για σχεδίαση ηλεκτρικών διαγραμμάτων.
Χρησιμοποιεί αρχεία XML για στοιχεία και διαγράμματα, και περιλαμβάνει επεξεργαστή διαγραμμάτων καθώς και επεξεργαστή στοιχείων.
[nl]
QElectroTech is een Qt5 applicatie om elektrische schema's te ontwerpen.
Het maakt gebruik van XML-bestanden voor elementen en diagrammen, en omvat zowel een diagram bewerker, een element bewerker, en een bloksjabloon bewerker.
[be]
QElectroTech is een Qt5 toepassing voor het maken en beheren van elektrische schema's.
QET gebruikt XML voor de elementen en schema's en omvat een schematische editor, itemeditor, en een titel sjabloon editor.
[da]
QElectroTech er et Qt5 program til at redigere elektriske diagrammer.
Det bruger XML filer for symboler og diagrammer og inkluderer diagram, symbol og titelblok redigering.
[ja]
QElectroTech は電気回路図を作成する Qt5 アプリケーションです。
QET は要素と回路図に XML 形式を利用し、回路図エディタ、要素エディタ、表題欄エディタを含みます。
+229
View File
@@ -0,0 +1,229 @@
![](logo.png)
# QElectroTech
### What it is
QElectroTech, or QET in short, is a libre and open source desktop application to create diagrams and schematics.
The software is primarily intended to create electrical documentation but it can also be used to draw any kinds of diagrams, such as those made in pneumatics, hydraulics, process industries, electronics...
Generally speaking, QET is a **CAD/CAE editor focusing on schematics drawing features**.
This means that there are no embedded simulating or calculating functionalities and it is not planned to implement them.
The main goal of the developers is to provide a libre, easy to use and effective software for **schematics drawing purposes**.
### Version
The current stable version is 0.100 and was released on 2026.01.25.
Once it has been officially released, the stable version is always frozen and is no longer developed.
New functionalities, bug and issue fixings are further made in the development version (currently 0.100.1 or 0.200.0 if based on new Qt6 port), which can also be [downloaded](https://qelectrotech.org/download.php).
Users who want to test and take benefits from the last software implementations should use the development version. But... use it at your own risk, since things are sometimes broken or only partially implemented until they are done!
### License
The software is licensed under [GNU/GPL](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
You are free to use, copy, modify and redistribute it under the terms of the license.
Like many other open source software, QElectroTech is provided as is, without any warranty.
### Development / technical choices
The development follows the classical way of free and open source software: the source code, written by a community of users, is freely accessible.
* Get sources and sub-modules
```txt
git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
```
Here are the technical choices made for the software development:
* Integrated development environment: [Qt Framework](https://www.qt.io/ide/)
* Libraries: Qt 5.x
* [KF5 Framework](https://github.com/KDE)
[Cmake](https://cmake.org/install/)
[kcoreaddons](https://github.com/KDE/kcoreaddons/tree/kf5)
[kwidgetsaddons](https://github.com/KDE/kwidgetsaddons/tree/kf5).
* Coding language: [C++](https://en.wikipedia.org/wiki/C%2B%2B)
* GUI translations: [Qt Linguist](http://doc.qt.io/qt-5/qtlinguist-index.html)
* Version control: [GIT](https://github.com/qelectrotech/qelectrotech-source-mirror.git)
* Doxygen documentation :[Doxygen](https://qelectrotech.github.io/qelectrotech-source-mirror/)
* QtCreator qch doxygen :[QElectroTech.qch](https://github.com/qelectrotech/qelectrotech-source-mirror/blob/master/doc/QElectroTech.qch)
* File format for projects, elements and titleblocks: [XML](http://www.w3schools.com/xml/xml_whatis.asp)
* Main development platform: [GNU/Linux](http://getgnulinux.org/en/linux/)
* Targeted platforms: Windows, GNU/Linux, Mac OS X, BSDs
* [Forum](https://qelectrotech.org/forum/index.php)
* [Wiki](https://qelectrotech.org/wiki_new/)
* [Mantis_bugtracker](https://qelectrotech.org/bugtracker/my_view_page.php)
If you wish to be informed of the latest developments, browse the [archive](https://listengine.tuxfamily.org/lists.tuxfamily.org/qet/) of the project mailing list where all commits (changes) are registered. This archive is publicly available, you don't need any account to access it.
# Features
QElectroTech is a free and open source software.
No need to worry about restrictive licensing, privacy violation or dependency on a company.
Zero cost and no licensing fees!
But you are welcome to make a donation to support the development
QElectroTech runs on the 3 most widespread operating systems for desktop computers in the world.
Files that were created on an OS can be edited on another OS without any conversion or restriction.
MS Windows users can even run the "ready-to-use" version of QElectroTech from an external medium with no need to install it on an access restricted computer.
Take advantage of the modern GUI
Toolbars and panels can be enabled/disabled, moved and displayed the way you want to work.
Panels can be stacked on each other (as tabs) or docked on the sides (as docks) or completely separated from the main window (as windows).
The GUI can fit to small or big screens, and even to multi-display configurations.
![](https://download.qelectrotech.org/qet/images-features/0030.png "GUI overview")
The GUI of QElectroTech is translated in 25 languages.
You only need to restart the application for the new selected language to take effect.
![](https://download.qelectrotech.org/qet/images-features/0040.png "Lang menu")
Create technical documentation in professional quality
Size, look and informations of the folios (sheets) are fully configurable.
You can set vertical and horizontal headers (printed rulers) individually on and off, set number of columns and rows, and set width/height of each column/row.
Titlebocks can be created and edited with the embedded titleblock editor to perfectly suit your needs.
Custom variables can be defined to display the informations you wish in the titleblock.
![](https://download.qelectrotech.org/qet/images-features/0055.png "Titleblock editor")
With only 2 mouse clicks you can add a full automatic generated table of content.
Changes in the documentation are updated on the fly.
![](https://download.qelectrotech.org/qet/images-features/0060.png "Table of content")
Choose from more than 8.200 symbols...
The embedded QET collection contains a rich library of electric, logic, pneumatic, hydraulic and fluid symbols.
The library grows at every new release thanks to an active user community.
![](https://download.qelectrotech.org/qet/images-features/0070.png "Collections")
...or create your own collection
The embedded element editor is a nice tool to create your own elements (symbols or anything else).
Your own elements are stored in the user collection.
![](https://download.qelectrotech.org/qet/images-features/0080.png "Element editor")
Quickly find what you need
All collections can quickly be searched with the integrated search engine.
Furthermore, the search request can be restricted to the folder of your choice.
![](https://download.qelectrotech.org/qet/images-features/0090.png "Search engine")
Easily draw complex schematics
To add an element on the drawing area, it only needs a drag & drop from the collection panel.
![](https://download.qelectrotech.org/qet/images-features/0102.png "Drag and drop")
Elements are automatically connected if they are aligned, or connected from point to point by pulling a conductor with the mouse.
![](https://download.qelectrotech.org/qet/images-features/0105.png "Conductor connections")
The path of every conductor can be modified by moving its handles with the mouse.
![](https://download.qelectrotech.org/qet/images-features/0107.png "Conductor handles")
And of course, you can accurately zoom with the mouse wheel over the drawing area to catch the smallest details.
Link elements together to create cross references
Several types of element can be linked together to display a cross reference text.
All types of cross references are automatically updated on the fly, you don't need to think about them if you make changes.
![](https://download.qelectrotech.org/qet/images-features/0112.png "Cross ref elements")
To speed up your work, linkable elements are easily searched and shown.
![](https://download.qelectrotech.org/qet/images-features/0115.png "Cross ref search")
Export informations to a parts list
Informations of all elements in the project can be exported to a .csv file that can be read and edited by any spreadsheet application.
![](https://download.qelectrotech.org/qet/images-features/0122.png "Element informations")
This way, you can make your own parts list or bill of material using the full power of a spreadsheet program.
![](https://download.qelectrotech.org/qet/images-features/0125.png "Spreadsheet")
Print to pdf and/or export your work to images
Your whole documentation or only selected parts of it can be printed to a real printer or to a pdf file.
Alternatively, you can export to vector (svg) or pixel (png, jpg, bmp) format images.
### And much more:
* open and edit several projects at the same time
* import images (.bmp, .jpg, .png, .svg) in your diagrams
* add basic shapes (lines, rectangles, ellipses, polygons) to your drawings
* edit the thickness, the line style and the color of conductors
* define some autonum patterns for conductors, symbols and folios
* take advantage of the open xml standard of elements and projects to create custom tools
* search and replace Widget (Ctrl + F) in entire project
* conductors num can be exported to csv file.
* ***
Nomenclature
A new nomenclature tool appears in the menu: project -> Add a nomenclature.
The nomenclature is presented in the form of a configurable table separated into two parts: the display (the form) and the content (the background).
- Display: the size and position of the table, the margins between text and the table cell, the alignment of the text in the cells and the font. The configuration of the table headers and the table itself are separate.
- Content: the information to display in the table and the order in which it should be displayed.
![](https://download.qelectrotech.org/qet/images_depeche_linuxfr/08/dialogue_nomenclature.png "nomenclature dialogue")
In order to speed up the establishment of a nomenclature, it is possible to export / import the display and content configurations separately. This is the "Configuration" part that can be seen in the photos above.
Behind the scenes, an SQLite database does the work, so setting up the content is nothing more or less than an SQL query created using a dialog (screenshot by right).
The SQL query is configured as follows (from top to bottom in the screenshot):
- “Available information”: the information to display;
- "Filter": filter the information (is not empty, is empty, contains, does not contain, is equal to, is not equal to) only one filter can be applied per information, it is not possible combine several;
- "Type of elements": allows you to filter on what type of element you want to obtain information.
At the bottom, a checkmark "SQL query" allows you to edit a personalized query, if the basic options are not sufficient.
When a nomenclature is too large to be contained in a single folio, it is possible to separate it on several folios, the tables of each folio are then linked together. When creating a nomenclature, this option is activated by default, which has the effect of adding the necessary number of folios, adding a table in each of them and linking them together.
Finally two buttons are available in the property panel:
- "Fit the table to the folio": positions and adjusts the size and determines the number of rows in the table in relation to the folio;
- "Apply geometry to all tables linked to this one": applies the three properties mentioned above to all linked tables in order to save time and maintain aesthetic consistency.
And to finish a table
![](https://download.qelectrotech.org/qet/images_depeche_linuxfr/08/tableau.png "table")
Summary
The old summary has been completely removed from the code in order to make room for the new one which is exactly the same as the nomenclature (a large amount of the code is common), with the exception of the SQL query (and its dialog to configure it) which offers specific information for editing a summary.
Export of the internal database
The database used by the nomenclature and the summary can be exported in a “.sqlite” file.
Currently this is irrelevant, as the function was created during development for debugging purposes, we left it.
Note that the database will become increasingly important in the future of Qet.
Export of the wiring list
In order to be able to use the wiring number printers more easily, the names of conductors can be exported in CSV format, the export respects the quantity of conductors in order to print the right quantity of numbers, for example a potential numbered 240 composed of 3 wires will give 6 × 240 (2 numbers per wire × 3 wires) in the CSV.
### Story
The QElectroTech project was founded in 2007 by two french students, Xavier and Benoit.
Xavier developed the base application itself and made all technical choices about the development.
The first version of QET (0.1) was released on 09.03.2008.
However, both Xavier and Benoit do not participate anymore in the project since 2013.
Following this period, new developers and contributors took over the project and kept it alive.
The development and the many translations are actively maintained.
New functionalities and evolutions are planned to make QET ever better.
Nowadays, QET is not only used by many individuals, teachers and students but also by professional electricians and companies all over the world.
### Donate Money
If you love QElectroTech, you can help developers to buy new hardware to test
and implement new features. Thanks in advance for your generous donations.
For more information, look at [Paypal](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=ZZHC9D7C3MDPC&ssrt=1694606609672)
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,9 @@
Windows Registry Editor Version 5.00
[-HKEY_CLASSES_ROOT\Applications\qelectrotech.exe\shell\open\command]
[-HKEY_CLASSES_ROOT\.qet]
[-HKEY_CLASSES_ROOT\qet_diagram_file]
[-HKEY_CLASSES_ROOT\.elmt]
[-HKEY_CLASSES_ROOT\qet_element_file]
[-HKEY_CLASSES_ROOT\.titleblock]
[-HKEY_CLASSES_ROOT\qet_titleblock_file]
@@ -0,0 +1,111 @@
@echo off
rem Enregistre les associations de fichiers de QElectroTech, a savoir les fichiers .qet
rem detecte le dossier courant et suppose que celui-ci contient bin\qelectrotech.exe ainsi que windows_icon\diagram_icon\qet-diagram.ico
set current_dir=%~dp0
cd /d %current_dir%
set expected_qet_exe=%current_dir%Lancer QET.bat
set expected_project_ico=%current_dir%ico\application-x-qet-project.ico
set expected_element_ico=%current_dir%ico\application-x-qet-element.ico
set expected_titleblock_ico=%current_dir%ico\application-x-qet-titleblock.ico
rem verifie la presence du fichier qelectrotech.exe
if not exist "%expected_qet_exe%" (
echo Le fichier %expected_qet_exe% n'a pas ete trouve. Abandon.
pause
exit /b 1
)
rem verifie la presence du fichier qet-diagram.ico
if not exist "%expected_project_ico%" (
echo Le fichier %expected_project_ico% n'a pas ete trouve. Abandon.
pause
exit /b 1
)
rem verifie la presence du fichier qet-element.ico
if not exist "%expected_element_ico%" (
echo Le fichier %expected_element_ico% n'a pas ete trouve. Abandon.
pause
exit /b 1
)
rem verifie la presence du fichier qet-titleblock.ico
if not exist "%expected_titleblock_ico%" (
echo Le fichier %expected_titleblock_ico% n'a pas ete trouve. Abandon.
pause
exit /b 1
)
rem echappe les backslashs dans les chemins absolus
set final_qet_exe=%expected_qet_exe:\=\\%
set final_project_ico=%expected_project_ico:\=\\%
set final_element_ico=%expected_element_ico:\=\\%
set final_titleblock_ico=%expected_titleblock_ico:\=\\%
rem genere le fichier .reg pour enregistrer les associations de fichiers
set reg_file=qet_install_file_associations.reg
(
echo Windows Registry Editor Version 5.00
echo.
rem Declaration de l'application
echo [HKEY_CLASSES_ROOT\Applications\qelectrotech.exe\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\""
rem association de fichier *.qet
echo [HKEY_CLASSES_ROOT\.qet]
echo @="qet_diagram_file"
echo [HKEY_CLASSES_ROOT\qet_diagram_file]
echo @="QET diagram"
echo "EditFlags"=dword:00000000
echo "BrowserFlags"=dword:00000008
echo [HKEY_CLASSES_ROOT\qet_diagram_file\DefaultIcon]
echo @="%final_project_ico%,0"
echo [HKEY_CLASSES_ROOT\qet_diagram_file\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\""
rem association de fichier *.elmt
echo [HKEY_CLASSES_ROOT\.elmt]
echo @="qet_element_file"
echo [HKEY_CLASSES_ROOT\qet_element_file]
echo @="QET element"
echo "EditFlags"=dword:00000000
echo "BrowserFlags"=dword:00000008
echo [HKEY_CLASSES_ROOT\qet_element_file\DefaultIcon]
echo @="%final_element_ico%,0"
echo [HKEY_CLASSES_ROOT\qet_element_file\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\""
rem association de fichier *.titleblock
echo [HKEY_CLASSES_ROOT\.titleblock]
echo @="qet_titleblock_file"
echo [HKEY_CLASSES_ROOT\qet_titleblock_file]
echo @="QET title block template"
echo "EditFlags"=dword:00000000
echo "BrowserFlags"=dword:00000008
echo [HKEY_CLASSES_ROOT\qet_titleblock_file\DefaultIcon]
echo @="%final_titleblock_ico%,0"
echo [HKEY_CLASSES_ROOT\qet_titleblock_file\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\""
) > %reg_file%
rem verifie que le fichier a bien ete ecrit
if not exist %reg_file% (
echo Impossible de creer le fichier %reg_file%. Abandon.
pause
exit /b 1
)
rem Applique le .reg genere
regedit.exe /s %reg_file%
if errorlevel 1 (
echo La prise en compte du fichier %reg_file% a echoue.
pause
exit /b 1
) else (
echo Les associations de fichier ont bien ete crees.
pause
exit /b 0
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
Patch QET64.nsi for GitHub Actions Windows build.
Usage:
python3 patch_nsi.py <nsi_path> <version> <files_win_path>
Arguments:
nsi_path : path to QET64.nsi (modified in place)
version : full version string e.g. 0.100.1-r8819-abc1234_x86_64-win64
files_win_path: absolute Windows path to nsis_root/files/
e.g. D:\\a\\repo\\nsis_root\\files
"""
import sys
import re
def fix_mixed_paths(nsi, files_win):
"""
After injecting the absolute path, some sub-paths may still contain
forward slashes (e.g. files\\elements/10_electric).
Normalize all slashes inside quoted strings that contain our absolute path.
"""
marker = files_win.lower()
def fix_quoted(m):
content = m.group(1)
if marker in content.lower():
content = content.replace('/', '\\')
return '"' + content + '"'
return re.sub(r'"([^"\r\n]*)"', fix_quoted, nsi)
def main():
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <nsi_path> <version> <files_win_path>")
sys.exit(1)
nsi_path = sys.argv[1]
version = sys.argv[2]
files_win = sys.argv[3].rstrip('\\/')
print(f"Patching : {nsi_path}")
print(f"Version : {version}")
print(f"Files dir: {files_win}")
with open(nsi_path, encoding='utf-8', errors='replace') as f:
nsi = f.read()
# 1. Patch SOFT_VERSION
nsi = re.sub(
r'!define SOFT_VERSION\s+.*',
f'!define SOFT_VERSION "{version}"',
nsi
)
print(" [1] SOFT_VERSION patched")
# 2. Rename QElectroTech.exe -> qelectrotech.exe (literal value, not NSIS var)
nsi = nsi.replace('/bin/QElectroTech.exe', '/bin/qelectrotech.exe')
print(" [2] Exe name patched")
# 3. Replace relative ./files/ paths with absolute Windows path + backslash
sep = '\\'
abs_files = files_win + sep
nsi = nsi.replace('./files/', abs_files)
nsi = nsi.replace('.\\files\\', abs_files)
nsi = nsi.replace('.\\\\files\\\\', abs_files)
print(f" [3] Paths ./files/ -> {abs_files}")
# 4. Normalize mixed slashes in injected absolute paths
# e.g. D:\path\files\elements/10_electric -> D:\path\files\elements\10_electric
nsi = fix_mixed_paths(nsi, files_win)
print(" [4] Mixed slashes normalized in absolute paths")
with open(nsi_path, 'w', encoding='utf-8') as f:
f.write(nsi)
# Verifications
version_found = re.search(r'!define SOFT_VERSION\s+"([^"]+)"', nsi)
if version_found:
print(f" OK SOFT_VERSION = {version_found.group(1)}")
else:
print(" ERROR: SOFT_VERSION not found after patch")
sys.exit(1)
count = nsi.count(abs_files)
print(f" OK {count} occurrences of '{abs_files}'")
mixed = re.findall(r'[A-Z]:[^"\s\r\n]*files[^"\s\r\n]*/[^"\s\r\n]*', nsi)
if mixed:
print(f" WARNING: {len(mixed)} mixed paths remaining:")
for m in mixed[:5]:
print(f" {m}")
else:
print(" OK no mixed paths remaining")
if __name__ == '__main__':
main()
+405 -354
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+405 -355
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+402 -351
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+405 -354
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+401 -350
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
+406 -355
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+405 -354
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+401 -350
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+1198 -1104
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
+402 -351
View File
File diff suppressed because it is too large Load Diff
+293 -121
View File
@@ -16,7 +16,6 @@
# along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
# Need homebrew and coreutils installed see <http://brew.sh>.
#Force MacOSX12.3.sdk
#see: https://www.downtowndougbrown.com/2023/08/how-to-create-a-qt-5-arm-intel-universal-binary-for-mac/
@@ -26,14 +25,20 @@ export DEVELOPER_DIR=/Applications/Xcode_14.01.app/Contents/Developer
APPNAME='qelectrotech'
BUNDLE=$APPNAME.app
APPBIN="$BUNDLE/Contents/MacOS/$APPNAME"
IDENTITY="Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)"
# Emplacement du script
# Temp paths
RW_DMG="/tmp/qet_rw.dmg"
MOUNT_POINT="/tmp/qet_dmg_mount"
STAGING="/tmp/qet_dmg_staging"
# Script location
current_dir=$(dirname "$0")
# On se remet au depart
# Go back to repo root
cd "${current_dir}/../"
# Emplacement courant
# Current directory
current_dir=$(PWD)
@@ -46,11 +51,11 @@ echo "Please see the \"Deploying an Application on Qt/Mac\""
echo "page in the Qt documentation for more information."
echo
echo "This script :"
echo "\t - up date the git depot"
echo "\t - built the application bundle,"
echo "\t - update the git depot"
echo "\t - build the application bundle,"
echo "\t - copy over required Qt frameworks,"
echo "\t - copy additional files: translations, titleblocks and elements,"
echo "\t - create image disk."
echo "\t - notarize the .app, then create a signed DMG."
echo
echo "Enjoy ;-)"
echo
@@ -70,25 +75,23 @@ echo
echo "______________________________________________________________"
echo "Run GIT:"
# Fait une mise à jour
git submodule init
git submodule update
git pull --recurse-submodules
git pull
#git checkout foliolist_position
# recupere le numero de la nouvelle revision
# Get revision number and version
GITCOMMIT=$(git rev-parse --short HEAD)
A=$(git rev-list HEAD --count)
HEAD=$(($A+473))
VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
#VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{ 0, "| head -n 1| cut -c32-40| sed -e 's/,/./g' -e 's/ //g') #Find major, minor, and micro version numbers in sources/qetversion.cp
# Tarball de la dernière revision déjà créé
if [ -e "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip" ] ; then
DMG_NAME="${APPNAME}-$VERSION-r$HEAD-arm64.dmg"
DMG_PATH="build-aux/mac-osx/$DMG_NAME"
# Check if already built
if [ -e "$DMG_PATH" ] ; then
echo "There are not new updates, make disk image can"
echo "take a lot of time (5 min). Can you continu?"
echo "[y/n]"
@@ -108,28 +111,27 @@ echo
echo "______________________________________________________________"
echo "Run make install:"
# pour effacer lancienne compilation
# Remove old bundle
if [ -d $BUNDLE ] ; then
echo "Removing hold bundle..."
echo "Removing old bundle..."
rm -rf $BUNDLE
fi
if [ -e Makefile ] ; then
echo "Removing hold Makefile..."
echo "Removing old Makefile..."
rm .qmake.stash
make clean
fi
# genere le Makefile
# Generate Makefile
echo "Generating new makefile..."
qmake -spec macx-clang
qmake -spec macx-clang
# compilation
# Compile
if [ -e Makefile.Release ] ; then
START_TIME=$SECONDS
# arret du script si erreur de compilation
START_TIME=$SECONDS
testSuccessBuild () {
if [ $? -ne 0 ]; then
if [ $? -ne 0 ]; then
cleanVerionTag
ELAPSED_TIME=$(($SECONDS - $START_TIME))
echo
@@ -138,19 +140,18 @@ if [ -e Makefile.Release ] ; then
fi
}
# utilise tout les coeurs pour une compilation plus rapide
coeur=$(sysctl hw.ncpu | awk '{print $2}')
if [ $? -ne 0 ]; then
if [ $? -ne 0 ]; then
make -f Makefile.Release
testSuccessBuild
else
make -j$(($coeur + 1)) -f Makefile.Release
testSuccessBuild
fi
ELAPSED_TIME=$(($SECONDS - $START_TIME))
echo
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
echo
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
else
echo "ERROR: Makefile not found. This script requires the macx-clang makespec"
exit
@@ -158,8 +159,7 @@ fi
cp -R ${current_dir}/misc/Info.plist qelectrotech.app/Contents/
cp -R ${current_dir}/ico/mac_icon/*.icns qelectrotech.app/Contents/Resources/
# On rajoute le numero de version pour "cmd + i"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist" # Version number
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist"
### copy over frameworks ############################################
@@ -168,70 +168,58 @@ echo
echo "______________________________________________________________"
echo "Copy Qt libraries and private frameworks:"
echo "Processing Mac deployment tool..."
echo "Processing Mac deployment tool..."
if [ ! -d $BUNDLE ] ; then
echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory"
exit
fi
#~/Qt/5.5/clang_64/bin/macdeployqt $BUNDLE
macdeployqt $BUNDLE
### add file missing #######################################
### add missing files ###############################################
echo
echo "______________________________________________________________"
echo "Copy file missing:"
echo "Copy missing files:"
# Dossier à ajouter
QET_ELMT_DIR="${current_dir}/elements/"
QET_TBT_DIR="${current_dir}/titleblocks/"
QET_LANG_DIR="${current_dir}/lang/"
QET_EXAMPLES_DIR="${current_dir}/examples/"
QET_FONTS_DIR="${current_dir}/fonts/"
QET_LICENSES_DIR="${current_dir}/licenses/"
# Add new folder for Qt dialog translation see
## see <https://download.tuxfamily.org/qet/Qt_lang/>.
LANG_DIR="${current_dir}/lang1/"
if [ -d "${QET_ELMT_DIR}" ]; then
echo "Copying add elements in the bundle..."
#mkdir $BUNDLE/Contents/Resources/elements
echo "Copying elements in the bundle..."
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
fi
if [ -d "${QET_TBT_DIR}" ]; then
echo "Copying titleblocks in the bundle..."
#mkdir $BUNDLE/Contents/Resources/titleblocks
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
fi
if [ -d "${QET_LANG_DIR}" ]; then
echo "Copying translations in the bundle... "
echo "Copying translations in the bundle..."
mkdir $BUNDLE/Contents/Resources/lang
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
fi
if [ -d "${LANG_DIR}" ]; then
echo "Copying translations in the bundle... "
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
echo "Copying extra translations in the bundle..."
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
fi
if [ -d "${QET_EXAMPLES_DIR}" ]; then
echo "Copying examples in the bundle... "
mkdir $BUNDLE/Contents/Resources/examples
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
echo "Copying examples in the bundle..."
mkdir $BUNDLE/Contents/Resources/examples
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
fi
if [ -d "${QET_FONTS_DIR}" ]; then
echo "Copying fonts in the bundle... "
mkdir $BUNDLE/Contents/Resources/fonts
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
echo "Copying fonts in the bundle..."
mkdir $BUNDLE/Contents/Resources/fonts
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
fi
if [ -d "${QET_LICENSES_DIR}" ]; then
@@ -240,98 +228,282 @@ if [ -d "${QET_LICENSES_DIR}" ]; then
cp -R -L ${QET_LICENSES_DIR} $BUNDLE/Contents/Resources/licenses
fi
codesign --force --deep --sign --timestamp -s "Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)" --options=runtime $BUNDLE
### create zip tarball ###############################################
### Sign the bundle #################################################
# Sign in correct order: all dylibs first (including flat libs copied
# by macdeployqt from Homebrew), then frameworks, plugins, bundle last.
echo
echo "______________________________________________________________"
echo "Create zip tarball:"
echo "Code signing (dylibs -> frameworks -> plugins -> bundle):"
/usr/bin/ditto -c -k --keepParent $BUNDLE "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip"
# 1. Sign all flat .dylib files in Frameworks/
echo "-- Signing dylibs in Frameworks/..."
find "$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
echo " $(basename $lib)"
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
### notarize zip tarball ###############################################
echo -e "\033[1;31mWould you like to upload for Notarize packages "${APPNAME}"-"$VERSION"-"r$HEAD-arm64.zip", n/Y?.\033[m"
# 2. Sign .framework bundles
echo "-- Signing .framework bundles..."
find "$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
echo " $(basename $fw)"
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
done
# 3. Sign plugins
echo "-- Signing plugins..."
find "$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
echo " $(basename $lib)"
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
# 4. Sign any dylibs in MacOS/
echo "-- Signing dylibs in MacOS/..."
find "$BUNDLE/Contents/MacOS" -name "*.dylib" | while read lib; do
echo " $(basename $lib)"
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
# 5. Sign the main executable explicitly
echo "-- Signing main executable..."
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
"$BUNDLE/Contents/MacOS/$APPNAME"
# 6. Sign the bundle itself last
echo "-- Signing bundle..."
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE"
# 7. Verify
echo
echo "Verifying bundle signature..."
codesign --verify --deep --strict --verbose=2 "$BUNDLE"
if [ $? -ne 0 ]; then
echo "ERROR: bundle signature verification failed, aborting."
exit 1
fi
spctl -a -vv "$BUNDLE"
echo "Bundle signature OK."
### Notarize the .app (via temporary ZIP) ###########################
echo
echo "______________________________________________________________"
echo "Create temporary ZIP for notarization:"
NOTARIZE_ZIP="/tmp/${APPNAME}-$VERSION-r$HEAD-arm64-notarize.zip"
/usr/bin/ditto -c -k --keepParent "$BUNDLE" "$NOTARIZE_ZIP"
echo -e "\033[1;31mWould you like to notarize the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
echo
echo "______________________________________________________________"
echo "Notarize zip tarball:"
xcrun notarytool submit build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip --keychain-profile "org.qelectrotech" --wait
echo
echo "______________________________________________________________"
echo "Notarizing .app:"
xcrun notarytool submit "$NOTARIZE_ZIP" --keychain-profile "org.qelectrotech" --wait
if [ $? -ne 0 ]; then
echo "ERROR: notarization failed. Check the log with:"
echo " xcrun notarytool log <submission-id> --keychain-profile org.qelectrotech"
rm -f "$NOTARIZE_ZIP"
exit 1
fi
else
echo -e "\033[1;33mExit.\033[m"
echo -e "\033[1;33mExit.\033[m"
fi
### The end, process is done ##########################################
echo "Cleaning up temporary notarization ZIP..."
rm -f "$NOTARIZE_ZIP"
echo
echo "______________________________________________________________"
echo "The process of creating deployable application zip is done."
echo The disque image is in the folder \'build-aux/mac-osx\'.
# Affiche les mise à jour depuis l'ancienne revision
#if [ ! $(($HEAD - $revAv)) -eq 0 ] ; then
# echo
# echo "There are new updates. This numero of revision is $HEAD."
# svn log -l $(($HEAD - $revAv))
#else
# echo
# echo "There are not new updates. This numero of revision is $HEAD."
# fi
# echo
### Staple the .app #################################################
# La version en local n'est pas conforme à la dernière version svn
# svnversion | grep -q '[MS:]' ; if [ $? -eq 0 ] ; then
# echo Please note that the latest \local version is $(svnversion).
# echo This is not the same version as the deposit.
# echo You can use \'svn diff\' to see the differences.
# echo And use \'svn revert \<fichier\>\' to delete the difference.
# echo To go back, you can use svn update -r 360
# echo to go to revision number 360.
# echo
#fi
# Clean up disk folder
echo 'Cleaning up... '
rm "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip"
# staple the app
echo -e "\033[1;31mWould you like to staple the app MacOS packages "${APPNAME}"-"$VERSION"-"r$HEAD", n/Y?.\033[m"
echo -e "\033[1;31mWould you like to staple the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
xcrun stapler staple -v $BUNDLE
xcrun stapler staple -v "$BUNDLE"
if [ $? -ne 0 ]; then
echo "ERROR: stapling .app failed."
exit 1
fi
echo "Verifying staple on .app..."
xcrun stapler validate -v "$BUNDLE"
spctl -a -vv "$BUNDLE"
echo ".app stapled OK."
else
echo -e "\033[1;33mExit.\033[m"
echo -e "\033[1;33mExit.\033[m"
fi
### Create staging folder with Applications symlink #################
# The staging folder contains the .app and a symlink to /Applications
# so the user can drag-and-drop to install directly from the DMG.
echo
echo "______________________________________________________________"
echo "Re Create zip tarball:"
echo "Preparing DMG staging folder:"
/usr/bin/ditto -c -k --sequesterRsrc --keepParent $BUNDLE "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip"
rm -rf "$STAGING"
mkdir -p "$STAGING"
cp -R "$BUNDLE" "$STAGING/"
ln -s /Applications "$STAGING/Applications"
echo "Staging folder ready: $STAGING"
### Create writable DMG (UDRW) ######################################
# We use a writable DMG first so we can re-sign the .app inside
# after hdiutil copies it (hdiutil can invalidate Sealed Resources
# during the copy, so we must re-sign inside the mounted volume).
# Clean up disk folder
echo 'Cleaning up... '
rm -rf $BUNDLE
echo
echo "______________________________________________________________"
echo "Create writable DMG (UDRW) and re-sign .app inside:"
rm -f "$RW_DMG"
hdiutil create \
-volname "QElectroTech $VERSION" \
-srcfolder "$STAGING" \
-ov \
-format UDRW \
-fs HFS+ \
"$RW_DMG"
#rsync to TF DMG builds
echo -e "\033[1;31mWould you like to upload MacOS packages "${APPNAME}"-"$VERSION"-"r$HEAD-arm64.zip", n/Y?.\033[m"
if [ $? -ne 0 ]; then
echo "ERROR: hdiutil failed to create writable DMG."
rm -rf "$STAGING"
exit 1
fi
# Mount the writable DMG
rm -rf "$MOUNT_POINT"
mkdir -p "$MOUNT_POINT"
hdiutil attach "$RW_DMG" -mountpoint "$MOUNT_POINT" -nobrowse -noverify
if [ $? -ne 0 ]; then
echo "ERROR: failed to mount writable DMG."
rm -f "$RW_DMG"
rm -rf "$STAGING"
exit 1
fi
# Re-sign all binaries inside the mounted DMG
echo "-- Re-signing dylibs inside DMG..."
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
done
find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
echo "-- Re-signing main executable inside DMG..."
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
echo "-- Re-signing bundle inside DMG..."
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
"$MOUNT_POINT/$BUNDLE"
# Verify signature inside the mounted DMG
echo "Verifying bundle signature inside DMG..."
codesign --verify --deep --strict --verbose=2 "$MOUNT_POINT/$BUNDLE"
if [ $? -ne 0 ]; then
echo "ERROR: bundle signature invalid inside DMG, aborting."
hdiutil detach "$MOUNT_POINT"
rm -f "$RW_DMG"
rm -rf "$STAGING" "$MOUNT_POINT"
exit 1
fi
echo "Bundle signature inside DMG OK."
# Detach the writable DMG
hdiutil detach "$MOUNT_POINT"
### Convert UDRW to final compressed UDZO ###########################
echo
echo "______________________________________________________________"
echo "Convert to final compressed DMG (UDZO):"
mkdir -p "build-aux/mac-osx"
rm -f "$DMG_PATH"
hdiutil convert "$RW_DMG" \
-format UDZO \
-o "$DMG_PATH"
if [ $? -ne 0 ]; then
echo "ERROR: hdiutil convert failed."
rm -f "$RW_DMG"
rm -rf "$STAGING" "$MOUNT_POINT"
exit 1
fi
rm -f "$RW_DMG"
rm -rf "$STAGING" "$MOUNT_POINT"
### Sign the final DMG ##############################################
echo "Signing final DMG..."
codesign --sign "$IDENTITY" --timestamp "$DMG_PATH"
### Notarize and staple the final DMG ###############################
echo -e "\033[1;31mWould you like to notarize the DMG \"${DMG_NAME}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
cp -Rf "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip" /Users/laurent/MAC_OS_X/
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w --progress --exclude='.DS_Store' /Users/laurent/MAC_OS_X/ server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
if [ $? != 0 ]; then
{
echo "RSYNC ERROR: problem syncing ${APPNAME}-$VERSION-r$HEAD-arm64.zip"
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w --progress --exclude='.DS_Store' /Users/laurent/MAC_OS_X/ server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
echo
echo "______________________________________________________________"
echo "Notarizing DMG:"
xcrun notarytool submit "$DMG_PATH" --keychain-profile "org.qelectrotech" --wait
if [ $? -ne 0 ]; then
echo "ERROR: DMG notarization failed. Check the log with:"
echo " xcrun notarytool log <submission-id> --keychain-profile org.qelectrotech"
exit 1
fi
} fi
echo "Stapling DMG..."
xcrun stapler staple "$DMG_PATH"
if [ $? -ne 0 ]; then
echo "ERROR: stapling DMG failed."
exit 1
fi
echo "DMG notarized and stapled OK."
echo "Verifying final DMG..."
spctl -a -vv "$DMG_PATH"
else
echo -e "\033[1;33mExit.\033[m"
echo -e "\033[1;33mExit.\033[m"
fi
### Clean up bundle #################################################
echo "Cleaning up bundle..."
rm -rf "$BUNDLE"
### The end #########################################################
echo
echo "______________________________________________________________"
echo "The process is done."
echo "DMG is in the folder 'build-aux/mac-osx'."
### Upload via rsync ################################################
echo -e "\033[1;31mWould you like to upload MacOS package \"${DMG_NAME}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
cp -Rf "$DMG_PATH" /Users/laurent/MAC_OS_X/
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
--progress --exclude='.DS_Store' \
/Users/laurent/MAC_OS_X/ \
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
if [ $? != 0 ]; then
echo "RSYNC ERROR: problem syncing ${DMG_NAME}, retrying..."
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
--progress --exclude='.DS_Store' \
/Users/laurent/MAC_OS_X/ \
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
fi
else
echo -e "\033[1;33mExit.\033[m"
fi
+3 -2
View File
@@ -220,7 +220,7 @@ void DynamicTextFieldEditor::fillInfoComboBox()
QStringList strl;
auto type = elementEditor()->elementScene()->elementData().m_type;
if(type & ElementData::AllReport) {
if((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
strl = QETInformation::folioReportInfoKeys();
}
else {
@@ -365,7 +365,8 @@ void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
void DynamicTextFieldEditor::on_m_composite_text_pb_clicked()
{
bool isReport = false;
if (elementEditor()->elementScene()->elementData().m_type & ElementData::AllReport) {
auto type = elementEditor()->elementScene()->elementData().m_type;
if ((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
isReport = true;
}
@@ -133,6 +133,7 @@ void ElementPropertiesEditorWidget::setUpInterface()
ui->m_base_type_cb->addItem (tr("Renvoi de folio précédent"), ElementData::PreviousReport);
ui->m_base_type_cb->addItem (tr("Bornier"), ElementData::Terminal);
ui->m_base_type_cb->addItem (tr("Vignette"), ElementData::Thumbnail);
ui->m_base_type_cb->addItem (tr("Définition de conducteur"), ElementData::ConductorDefinition);
// Slave option
ui->m_state_cb->addItem(tr("Normalement ouvert"), ElementData::NO);
@@ -188,6 +189,9 @@ void ElementPropertiesEditorWidget::updateTree()
case ElementData::PreviousReport:
ui->m_tree->setDisabled(true);
break;
case ElementData::ConductorDefinition:
ui->m_tree->setDisabled(true);
break;
case ElementData::Master:
ui->m_tree->setEnabled(true);
break;
+47 -25
View File
@@ -736,40 +736,62 @@ bool QETElementEditor::checkElement()
QList<QETWarning> warnings;
QList<QETWarning> errors;
// Warning #1: Element haven't got terminal
// (except for report, because report must have one terminal and this checking is do below)
// Warning #1: Element haven't got terminal
// (except for report and conductor definition, because they must have one terminal and this checking is done below)
if (!m_elmt_scene -> containsTerminals() &&
!(m_elmt_scene->elementData().m_type & ElementData::AllReport)) {
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition) {
warnings << qMakePair(
tr("Absence de borne", "warning title"),
tr(
"<br>En l'absence de borne, l'élément ne pourra être"
" relié à d'autres éléments par l'intermédiaire de conducteurs.",
"warning description"
)
);
}
tr("Absence de borne", "warning title"),
tr(
"<br>En l'absence de borne, l'élément ne pourra être"
" relié à d'autres éléments par l'intermédiaire de conducteurs.",
"warning description"
)
);
}
// Check folio report element
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
{
int terminal =0;
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
{
int terminal =0;
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
//Error folio report must have only one terminal
if (terminal != 1) {
errors << qMakePair (tr("Absence de borne"),
tr("<br><b>Erreur</b> :"
"<br>Les reports de folio doivent posséder une seul borne."
"<br><b>Solution</b> :"
"<br>Verifier que l'élément ne possède qu'une seul borne"));
}
}
//Error folio report must have only one terminal
if (terminal != 1) {
errors << qMakePair (tr("Absence de borne"),
tr("<br><b>Erreur</b> :"
"<br>Les reports de folio doivent posséder une seul borne."
"<br><b>Solution</b> :"
"<br>Verifier que l'élément ne possède qu'une seul borne"));
// Check conductor definition element
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
{
int terminal =0;
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Error: Conductor definition must have exactly one terminal
if (terminal != 1) {
errors << qMakePair (tr("Nombre de bornes incorrect"),
tr("<br><b>Erreur</b> :"
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
"<br><b>Solution</b> :"
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
}
}
}
if (!errors.count() && !warnings.count()) {
return(true);
+4 -1
View File
@@ -61,7 +61,7 @@ void TerminalEditor::updateForm()
ui->m_y_dsb->setValue(m_part->property("y").toReal());
ui->m_orientation_cb->setCurrentIndex(ui->m_orientation_cb->findData(m_part->property("orientation")));
ui->m_name_le->setText(m_part->terminalName());
ui->m_type_cb->setCurrentIndex(ui->m_orientation_cb->findData(m_part->terminalType()));
ui->m_type_cb->setCurrentIndex(ui->m_type_cb->findData(m_part->terminalType()));
activeConnections(true);
}
@@ -122,6 +122,9 @@ void TerminalEditor::init()
ui->m_type_cb->addItem(tr("Générique"), TerminalData::Generic);
ui->m_type_cb->addItem(tr("Bornier intérieur"), TerminalData::Inner);
ui->m_type_cb->addItem(tr("Bornier extérieur"), TerminalData::Outer);
ui->m_type_cb->addItem(tr("NO (contact SW)"), TerminalData::No);
ui->m_type_cb->addItem(tr("NC (contact SW)"), TerminalData::Nc);
ui->m_type_cb->addItem(tr("Commun (contact SW)"), TerminalData::Common);
}
/**
+1
View File
@@ -335,6 +335,7 @@ void TextEditor::setUpWidget(QWidget *parent)
m_size_sb = new QSpinBox(parent);
m_size_sb->setObjectName(QString::fromUtf8("m_size_sb"));
m_size_sb->setMinimum(4);
gridLayout->addWidget(m_size_sb, 2, 1, 1, 1);
+26 -5
View File
@@ -22,6 +22,7 @@
#include "qeticons.h"
#include "qetproject.h"
#include "titleblock/templatescollection.h"
#include <QApplication>
/*
Lorsque le flag ENABLE_PANEL_DND_CHECKS est defini, le panel d'elements
@@ -42,7 +43,7 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
first_reload_(true)
{
// selection unique
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setColumnCount(1);
setExpandsOnDoubleClick(true);
setMouseTracking(true);
@@ -299,11 +300,14 @@ void ElementsPanel::reload()
}
/**
@brief ElementsPanel::slot_clicked
handle click on qtwi
@param qtwi item that was clickerd on
*/
* @brief ElementsPanel::slot_clicked
* handle click on qtwi
* @param qtwi item that was clickerd on
*/
void ElementsPanel::slot_clicked(QTreeWidgetItem *clickedItem, int) {
if (QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) {
return;
}
requestForItem(clickedItem);
}
@@ -553,3 +557,20 @@ void ElementsPanel::keyPressEvent(QKeyEvent *event)
QTreeView::keyPressEvent(event);
}
}
/**
* @brief ElementsPanel::selectedDiagrams
* @return A list of all currently selected diagrams in the panel.
*/
QList<Diagram *> ElementsPanel::selectedDiagrams() const
{
QList<Diagram *> diagrams;
foreach (QTreeWidgetItem *item, selectedItems()) {
if (item->type() == QET::Diagram) {
if (Diagram *diagram = valueForItem<Diagram *>(item)) {
diagrams.append(diagram);
}
}
}
return diagrams;
}
+1
View File
@@ -49,6 +49,7 @@ class ElementsPanel : public GenericPanel {
// methods used to get what is represented by a particular visual item
QString dirPathForItem(QTreeWidgetItem *);
QString filePathForItem(QTreeWidgetItem *);
QList<Diagram *> selectedDiagrams() const;
signals:
void requestForProject(QETProject *);
+181 -122
View File
@@ -25,6 +25,7 @@
#include "qetproject.h"
#include "titleblock/templatedeleter.h"
#include <QFileInfo>
#include <QMessageBox>
/*
When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel
@@ -242,85 +243,134 @@ void ElementsPanelWidget::newDiagram()
}
/**
Emet le signal requestForDiagramDeletion avec le schema selectionne
*/
* Emet le signal requestForDiagramsDeletion avec les schemas selectionnes
*/
void ElementsPanelWidget::deleteDiagram()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramDeletion(selected_diagram));
elements_panel->reload();
QList<Diagram *> diagrams_to_delete = elements_panel->selectedDiagrams();
if (diagrams_to_delete.isEmpty()) return;
emit(requestForDiagramsDeletion(diagrams_to_delete));
elements_panel->reload();
}
/**
* Emits the requestForDiagramMoveUpTop signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramUpTop() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveUpTop(diagrams_to_move);
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
/**
Emet le signal requestForDiagramMoveUpTop avec le schema selectionne
+*/
void ElementsPanelWidget::moveDiagramUpTop()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveUpTop(selected_diagram));
}
}
* Emits the requestForDiagramMoveUp signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramUp() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveUp(diagrams_to_move);
/**
Emet le signal requestForDiagramMoveUp avec le schema selectionne
*/
void ElementsPanelWidget::moveDiagramUp()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveUp(selected_diagram));
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
/**
Emet le signal requestForDiagramMoveDown avec le schema selectionne
*/
void ElementsPanelWidget::moveDiagramDown()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveDown(selected_diagram));
* Emits the requestForDiagramMoveDown signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramDown() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveDown(diagrams_to_move);
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
/**
Emet le signal requestForDiagramMoveUpx10 avec le schema selectionne
*/
void ElementsPanelWidget::moveDiagramUpx10()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveUpx10(selected_diagram));
* Emits the requestForDiagramMoveUpx10 signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramUpx10() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveUpx10(diagrams_to_move);
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
/**
Emet le signal requestForDiagramMoveUpx100 avec le schema selectionne
*/
void ElementsPanelWidget::moveDiagramUpx100()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveUpx100(selected_diagram));
* Emits the requestForDiagramMoveUpx100 signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramUpx100() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveUpx100(diagrams_to_move);
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
/**
Emet le signal requestForDiagramMoveDownx10 avec le schema selectionne
*/
void ElementsPanelWidget::moveDiagramDownx10()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveDownx10(selected_diagram));
* Emits the requestForDiagramMoveDownx10 signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramDownx10() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveDownx10(diagrams_to_move);
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
/**
Emet le signal requestForDiagramMoveDownx100 avec le schema selectionne
*/
void ElementsPanelWidget::moveDiagramDownx100()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramMoveDownx100(selected_diagram));
* Emits the requestForDiagramMoveDownx100 signal with all selected diagrams.
*/
void ElementsPanelWidget::moveDiagramDownx100() {
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
if (diagrams_to_move.isEmpty()) return;
// Emit the entire list at once
emit requestForDiagramMoveDownx100(diagrams_to_move);
// Clear messy tree selection caused by moving items, then restore clean selection
elements_panel->clearSelection();
for (Diagram *d : diagrams_to_move) {
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
}
}
@@ -378,21 +428,35 @@ void ElementsPanelWidget::updateButtons()
bool is_writable = !(elements_panel -> selectedProject() -> isReadOnly());
prj_add_diagram -> setEnabled(is_writable);
} else if (current_type == QET::Diagram) {
Diagram *selected_diagram = elements_panel -> selectedDiagram();
QETProject *selected_diagram_project = selected_diagram -> project();
// Fetch ALL selected diagrams instead of just one
QList<Diagram *> selected_diagrams = elements_panel -> selectedDiagrams();
bool is_writable = !(selected_diagram_project -> isReadOnly());
int project_diagrams_count = selected_diagram_project -> diagrams().count();
int diagram_position = selected_diagram_project -> diagrams().indexOf(selected_diagram);
if (!selected_diagrams.isEmpty()) {
QETProject *selected_diagram_project = selected_diagrams.first() -> project();
bool is_writable = !(selected_diagram_project -> isReadOnly());
int project_diagrams_count = selected_diagram_project -> diagrams().count();
prj_del_diagram -> setEnabled(is_writable);
prj_move_diagram_up -> setEnabled(is_writable && diagram_position > 0);
prj_move_diagram_down -> setEnabled(is_writable && diagram_position < project_diagrams_count - 1);
prj_move_diagram_top -> setEnabled(is_writable && diagram_position > 0);
prj_move_diagram_upx10 -> setEnabled(is_writable && diagram_position > 10);
prj_move_diagram_upx100 -> setEnabled(is_writable && diagram_position > 100);
prj_move_diagram_downx10 -> setEnabled(is_writable && diagram_position < project_diagrams_count - 10);
prj_move_diagram_downx100 -> setEnabled(is_writable && diagram_position < project_diagrams_count - 100);
// Find the highest (min) and lowest (max) index among the selection
int min_position = project_diagrams_count;
int max_position = -1;
for (Diagram *diagram : selected_diagrams) {
int pos = selected_diagram_project -> diagrams().indexOf(diagram);
if (pos < min_position) min_position = pos;
if (pos > max_position) max_position = pos;
}
prj_del_diagram -> setEnabled(is_writable);
prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
prj_move_diagram_down -> setEnabled(is_writable && max_position < project_diagrams_count - 1);
prj_move_diagram_top -> setEnabled(is_writable && min_position > 0);
// Adjusted to >= to allow exactly 10 or 100 steps if space permits
prj_move_diagram_upx10 -> setEnabled(is_writable && min_position > 10);
prj_move_diagram_upx100 -> setEnabled(is_writable && min_position > 100);
prj_move_diagram_downx10 -> setEnabled(is_writable && max_position < project_diagrams_count - 10);
prj_move_diagram_downx100 -> setEnabled(is_writable && max_position < project_diagrams_count - 100);
}
} else if (current_type == QET::TitleBlockTemplatesCollection) {
TitleBlockTemplateLocation location = elements_panel -> templateLocationForItem(current_item);
tbt_add -> setEnabled(!location.isReadOnly());
@@ -475,62 +539,57 @@ void ElementsPanelWidget::filterEdited(const QString &next_text) {
}
/**
Treat key press event inside elements panel widget
*/
void ElementsPanelWidget::keyPressEvent (QKeyEvent *e) {
switch(e -> key()) {
case Qt::Key_Delete: //delete diagram through elements panel widget
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramDeletion(selected_diagram));
}
break;
case Qt::Key_F3:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUp(selected_diagram));
}
break;
case Qt::Key_F4:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveDown(selected_diagram));
}
break;
case Qt::Key_F5:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUpTop(selected_diagram));
}
break;
case Qt::Key_F6:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveDownx10(selected_diagram));
}
break;
case Qt::Key_F7:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveDownx100(selected_diagram));
}
break;
case Qt::Key_F8:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUpx10(selected_diagram));
}
break;
case Qt::Key_F9:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUpx100(selected_diagram));
}
break;
}
return;
* Treat key press event inside elements panel widget
*/
/**
* Treat key press event inside elements panel widget.
* Respects the enabled/disabled state of the corresponding QActions.
*/
void ElementsPanelWidget::keyPressEvent(QKeyEvent *e) {
switch(e->key()) {
case Qt::Key_Delete:
if (prj_del_diagram && prj_del_diagram->isEnabled()) {
deleteDiagram();
}
break;
case Qt::Key_F3:
if (prj_move_diagram_up && prj_move_diagram_up->isEnabled()) {
moveDiagramUp();
}
break;
case Qt::Key_F4:
if (prj_move_diagram_down && prj_move_diagram_down->isEnabled()) {
moveDiagramDown();
}
break;
case Qt::Key_F5:
if (prj_move_diagram_top && prj_move_diagram_top->isEnabled()) {
moveDiagramUpTop();
}
break;
case Qt::Key_F6:
if (prj_move_diagram_downx10 && prj_move_diagram_downx10->isEnabled()) {
moveDiagramDownx10();
}
break;
case Qt::Key_F7:
if (prj_move_diagram_downx100 && prj_move_diagram_downx100->isEnabled()) {
moveDiagramDownx100();
}
break;
case Qt::Key_F8:
if (prj_move_diagram_upx10 && prj_move_diagram_upx10->isEnabled()) {
moveDiagramUpx10();
}
break;
case Qt::Key_F9:
if (prj_move_diagram_upx100 && prj_move_diagram_upx100->isEnabled()) {
moveDiagramUpx100();
}
break;
default:
// Pass unhandled key events to the base class
QWidget::keyPressEvent(e);
break;
}
}
+8 -7
View File
@@ -69,13 +69,14 @@ class ElementsPanelWidget : public QWidget {
void requestForProjectPropertiesEdition(QETProject *);
void requestForDiagramPropertiesEdition(Diagram *);
void requestForDiagramDeletion(Diagram *);
void requestForDiagramMoveUp(Diagram *);
void requestForDiagramMoveDown(Diagram *);
void requestForDiagramMoveUpTop(Diagram *);
void requestForDiagramMoveUpx10(Diagram *);
void requestForDiagramMoveUpx100(Diagram *);
void requestForDiagramMoveDownx10(Diagram *);
void requestForDiagramMoveDownx100(Diagram *);
void requestForDiagramsDeletion(const QList<Diagram *> &diagrams);
void requestForDiagramMoveUp(const QList<Diagram *> &diagrams);
void requestForDiagramMoveDown(const QList<Diagram *> &diagrams);
void requestForDiagramMoveUpTop(const QList<Diagram *> &diagrams);
void requestForDiagramMoveUpx10(const QList<Diagram *> &diagrams);
void requestForDiagramMoveUpx100(const QList<Diagram *> &diagrams);
void requestForDiagramMoveDownx10(const QList<Diagram *> &diagrams);
void requestForDiagramMoveDownx100(const QList<Diagram *> &diagrams);
public slots:
void openDirectoryForSelectedItem();
+74 -21
View File
@@ -364,11 +364,12 @@ QETResult ProjectView::noProjectResult() const
}
/**
@brief ProjectView::removeDiagram
Remove a diagram (folio) of the project
@param diagram_view : diagram view to remove
*/
void ProjectView::removeDiagram(DiagramView *diagram_view)
* @brief ProjectView::removeDiagram
* Remove a diagram (folio) of the project
* @param diagram_view : diagram view to remove
* @param silent : if true, bypasses the confirmation message box
*/
void ProjectView::removeDiagram(DiagramView *diagram_view, bool silent)
{
if (!diagram_view)
return;
@@ -377,17 +378,18 @@ void ProjectView::removeDiagram(DiagramView *diagram_view)
if (!m_diagram_ids.values().contains(diagram_view))
return;
//Ask confirmation to user.
int answer = QET::QetMessageBox::question(
this,
tr("Supprimer le folio ?", "message box title"),
tr("Êtes-vous sûr de vouloir supprimer ce folio du projet ? Ce changement est irréversible.", "message box content"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::No
);
if (answer != QMessageBox::Yes) {
return;
if (!silent) {
//Ask confirmation to user.
int answer = QET::QetMessageBox::question(
this,
tr("Supprimer le folio ?", "message box title"),
tr("Êtes-vous sûr de vouloir supprimer ce folio du projet ? Ce changement est irréversible.", "message box content"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::No
);
if (answer != QMessageBox::Yes) {
return;
}
}
//Remove the diagram view of the tabs widget
@@ -405,14 +407,15 @@ void ProjectView::removeDiagram(DiagramView *diagram_view)
}
/**
Enleve un schema du ProjectView
@param diagram Schema a enlever
*/
void ProjectView::removeDiagram(Diagram *diagram) {
* Enleve un schema du ProjectView
* @param diagram Schema a enlever
* @param silent Si vrai, supprime sans demander confirmation
*/
void ProjectView::removeDiagram(Diagram *diagram, bool silent) {
if (!diagram) return;
if (DiagramView *diagram_view = findDiagram(diagram)) {
removeDiagram(diagram_view);
removeDiagram(diagram_view, silent);
}
}
@@ -557,6 +560,56 @@ void ProjectView::moveDiagramUpx10(Diagram *diagram) {
moveDiagramUpx10(findDiagram(diagram));
}
/**
* @brief ProjectView::moveDiagramUpx100
* Moves the diagram_view up / left x100
* @param diagram_view View to move
*/
void ProjectView::moveDiagramUpx100(DiagramView *diagram_view) {
if (!diagram_view) return;
int diagram_view_position = m_diagram_ids.key(diagram_view);
if (!diagram_view_position) {
// The diagram is the first of the project
return;
}
m_tab->tabBar()->moveTab(diagram_view_position, diagram_view_position - 100);
}
/**
* @brief ProjectView::moveDiagramUpx100
* Moves the diagram up / left x100
* @param diagram Diagram to move
*/
void ProjectView::moveDiagramUpx100(Diagram *diagram) {
moveDiagramUpx100(findDiagram(diagram));
}
/**
* @brief ProjectView::moveDiagramDownx100
* Moves the diagram_view down / right x100
* @param diagram_view View to move
*/
void ProjectView::moveDiagramDownx100(DiagramView *diagram_view) {
if (!diagram_view) return;
int diagram_view_position = m_diagram_ids.key(diagram_view);
if (diagram_view_position + 1 == m_diagram_ids.count()) {
// The diagram is the last of the project
return;
}
m_tab->tabBar()->moveTab(diagram_view_position, diagram_view_position + 100);
}
/**
* @brief ProjectView::moveDiagramDownx100
* Moves the diagram down / right x100
* @param diagram Diagram to move
*/
void ProjectView::moveDiagramDownx100(Diagram *diagram) {
moveDiagramDownx100(findDiagram(diagram));
}
/**
Deplace le schema diagram_view vers le bas / la droite x10
*/
+6 -2
View File
@@ -104,8 +104,8 @@ class ProjectView : public QWidget
void changeLastTab();
public slots:
void removeDiagram(DiagramView *);
void removeDiagram(Diagram *);
void removeDiagram(DiagramView *diagram_view, bool silent = false);
void removeDiagram(Diagram *diagram, bool silent = false);
void showDiagram(DiagramView *);
void showDiagram(Diagram *);
void editProjectProperties();
@@ -122,6 +122,10 @@ class ProjectView : public QWidget
void moveDiagramUpx10(Diagram *);
void moveDiagramDownx10(DiagramView *);
void moveDiagramDownx10(Diagram *);
void moveDiagramUpx100(DiagramView *);
void moveDiagramUpx100(Diagram *);
void moveDiagramDownx100(DiagramView *);
void moveDiagramDownx100(Diagram *);
void exportProject();
QETResult save();
QETResult saveAs();
+20 -4
View File
@@ -45,14 +45,26 @@ QDomElement ElementData::toXml(QDomDocument &xml_element) const {
bool ElementData::fromXml(const QDomElement &xml_element)
{
if(xml_element.tagName() != QLatin1String("definition") ||
xml_element.attribute(QStringLiteral("type")) != QLatin1String("element")) {
xml_element.attribute(QStringLiteral("type")) != QLatin1String("element")) {
return false;
}
}
// --- HIER STARTET UNSER DEBUG-BLOCK ---
// Wir holen den String aus der XML und speichern ihn kurz zwischen
QString raw_type_string = xml_element.attribute(QStringLiteral("link_type"), QStringLiteral("simple"));
qDebug() << "\n=== NEUES BAUTEIL WIRD GELADEN ===";
qDebug() << "[XML Parser] Roher 'link_type' String aus der .elmt Datei:" << raw_type_string;
// Jetzt übergeben wir ihn an deine Übersetzungs-Funktion
m_type = typeFromString(raw_type_string);
qDebug() << "[XML Parser] Übersetzter ElementData-Typ:" << typeToString(m_type);
// --- HIER ENDET UNSER DEBUG-BLOCK ---
m_type = typeFromString(xml_element.attribute(QStringLiteral("link_type"), QStringLiteral("simple")));
kindInfoFromXml(xml_element);
m_informations.fromXml(xml_element.firstChildElement(QStringLiteral("elementInformations")),
QStringLiteral("elementInformation"));
QStringLiteral("elementInformation"));
m_names_list.fromXml(xml_element);
auto xml_draw_info = xml_element.firstChildElement(QStringLiteral("informations"));
@@ -323,6 +335,8 @@ QString ElementData::typeToString(ElementData::Type type)
return QStringLiteral("terminal");
case ElementData::Thumbnail:
return QStringLiteral("thumbnail");
case ElementData::ConductorDefinition:
return QStringLiteral("conductor_definition");
default:
qDebug() << "ElementData::typeToString : type don't exist"
<< "return failsafe value 'simple'";
@@ -346,6 +360,8 @@ ElementData::Type ElementData::typeFromString(const QString &string)
return ElementData::Terminal;
} else if (string == QLatin1String("thumbnail")) {
return ElementData::Thumbnail;
} else if (string == QLatin1String("conductor_definition")) {
return ElementData::ConductorDefinition;
}
//Return simple if nothing match
+2 -1
View File
@@ -41,7 +41,8 @@ class ElementData : public PropertiesInterface
Master = 8,
Slave = 16,
Terminal = 32,
Thumbnail = 64};
Thumbnail = 64,
ConductorDefinition = 128};
Q_ENUM(Type)
Q_DECLARE_FLAGS(Types, Type)
+12
View File
@@ -174,6 +174,12 @@ QString TerminalData::typeToString(TerminalData::Type type)
return QString("Inner");
case Outer :
return QString("Outer");
case No :
return QString("No");
case Nc :
return QString("Nc");
case Common :
return QString("Common");
}
return QString("Generic");
}
@@ -193,6 +199,12 @@ TerminalData::Type TerminalData::typeFromString(const QString &string)
return TerminalData::Inner;
} else if (string == "Outer") {
return TerminalData::Outer;
} else if (string == "No") {
return TerminalData::No;
} else if (string == "Nc") {
return TerminalData::Nc;
} else if (string == "Common") {
return TerminalData::Common;
} else {
qDebug() << "TerminalData::typeFromString, argument string is invalid"
" failsafe type 'TerminalData::Generic' is returned";
+4 -1
View File
@@ -41,7 +41,10 @@ class TerminalData : public PropertiesInterface
enum Type {
Generic,
Inner,
Outer
Outer,
No, ///< Normally Open terminal (for SW contacts)
Nc, ///< Normally Closed terminal (for SW contacts)
Common ///< Common terminal (for SW contacts)
};
Q_ENUM(Type)
+6
View File
@@ -29,6 +29,7 @@
XRefProperties::XRefProperties()
{
m_show_power_ctc = true;
m_show_terminal_name = true;
m_display = Cross;
m_snap_to = Bottom;
m_prefix_keys << "power" << "delay" << "switch";
@@ -48,6 +49,7 @@ void XRefProperties::toSettings(QSettings &settings,
const QString prefix) const
{
settings.setValue(prefix % "showpowerctc", m_show_power_ctc);
settings.setValue(prefix % "showterminalname", m_show_terminal_name);
QString display = m_display == Cross? "cross" : "contacts";
settings.setValue(prefix % "displayhas", display);
QString snap = m_snap_to == Bottom? "bottom" : "label";
@@ -78,6 +80,7 @@ void XRefProperties::fromSettings(const QSettings &settings,
const QString prefix)
{
m_show_power_ctc = settings.value(prefix % "showpowerctc", true).toBool();
m_show_terminal_name = settings.value(prefix % "showterminalname", true).toBool();
QString display = settings.value(prefix % "displayhas", "cross").toString();
display == "cross"? m_display = Cross : m_display = Contacts;
QString snap = settings.value(prefix % "snapto", "label").toString();
@@ -107,6 +110,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const
xml_element.setAttribute("type", m_key);
xml_element.setAttribute("showpowerctc", m_show_power_ctc? "true" : "false");
xml_element.setAttribute("showterminalname", m_show_terminal_name? "true" : "false");
QString display = m_display == Cross? "cross" : "contacts";
xml_element.setAttribute("displayhas", display);
QString snap = m_snap_to == Bottom? "bottom" : "label";
@@ -137,6 +141,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const
*/
bool XRefProperties::fromXml(const QDomElement &xml_element) {
m_show_power_ctc = xml_element.attribute("showpowerctc") == "true";
m_show_terminal_name = xml_element.attribute("showterminalname", "true") == "true";
QString display = xml_element.attribute("displayhas", "cross");
display == "cross"? m_display = Cross : m_display = Contacts;
QString snap = xml_element.attribute("snapto", "label");
@@ -188,6 +193,7 @@ QHash<QString, XRefProperties> XRefProperties::defaultProperties()
bool XRefProperties::operator ==(const XRefProperties &xrp) const{
return (m_show_power_ctc == xrp.m_show_power_ctc
&& m_show_terminal_name == xrp.m_show_terminal_name
&& m_display == xrp.m_display
&& m_snap_to == xrp.m_snap_to
&& m_prefix == xrp.m_prefix
+4
View File
@@ -57,6 +57,9 @@ class XRefProperties : public PropertiesInterface
void setShowPowerContac (const bool a) {m_show_power_ctc = a;}
bool showPowerContact () const {return m_show_power_ctc;}
void setShowTerminalName (const bool a) {m_show_terminal_name = a;}
bool showTerminalName () const {return m_show_terminal_name;}
void setDisplayHas (const DisplayHas dh) {m_display = dh;}
DisplayHas displayHas () const {return m_display;}
@@ -81,6 +84,7 @@ class XRefProperties : public PropertiesInterface
private:
bool m_show_power_ctc;
bool m_show_terminal_name;
DisplayHas m_display;
SnapTo m_snap_to;
Qt::AlignmentFlag m_xref_pos;
+153 -95
View File
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qetdiagrameditor.h"
#include <QCoreApplication>
#include "ElementsCollection/elementscollectionwidget.h"
#include "QWidgetAnimation/qwidgetanimation.h"
#include "autoNum/ui/autonumberingdockwidget.h"
@@ -47,7 +47,7 @@
#include "TerminalStrip/ui/addterminalstripitemdialog.h"
#include "wiringlistexport.h"
#include "ui/terminalnumberingdialog.h"
#include <QDebug>
#ifdef BUILD_WITHOUT_KF5
#else
# include <KAutoSaveFile>
@@ -176,12 +176,14 @@ void QETDiagramEditor::setUpElementsPanel()
connect(pa, SIGNAL(requestForProjectPropertiesEdition (QETProject *)), this, SLOT(editProjectProperties(QETProject *)));
connect(pa, SIGNAL(requestForNewDiagram (QETProject *)), this, SLOT(addDiagramToProject(QETProject *)));
connect(pa, SIGNAL(requestForDiagramPropertiesEdition (Diagram *)), this, SLOT(editDiagramProperties(Diagram *)));
connect(pa, SIGNAL(requestForDiagramDeletion (Diagram *)), this, SLOT(removeDiagram(Diagram *)));
connect(pa, SIGNAL(requestForDiagramMoveUp (Diagram *)), this, SLOT(moveDiagramUp(Diagram *)));
connect(pa, SIGNAL(requestForDiagramMoveDown (Diagram *)), this, SLOT(moveDiagramDown(Diagram *)));
connect(pa, SIGNAL(requestForDiagramMoveUpTop (Diagram *)), this, SLOT(moveDiagramUpTop(Diagram *)));
connect(pa, SIGNAL(requestForDiagramMoveUpx10 (Diagram *)), this, SLOT(moveDiagramUpx10(Diagram *)));
connect(pa, SIGNAL(requestForDiagramMoveDownx10 (Diagram *)), this, SLOT(moveDiagramDownx10(Diagram *)));
connect(pa, SIGNAL(requestForDiagramsDeletion (const QList<Diagram *> &)), this, SLOT(removeDiagrams(const QList<Diagram *> &)));
connect(pa, SIGNAL(requestForDiagramMoveUp (const QList<Diagram *> &)), this, SLOT(moveDiagramUp(const QList<Diagram *>&)));
connect(pa, SIGNAL(requestForDiagramMoveDown (const QList<Diagram *> &)), this, SLOT(moveDiagramDown(const QList<Diagram *>&)));
connect(pa, SIGNAL(requestForDiagramMoveUpTop (const QList<Diagram *> &)), this, SLOT(moveDiagramUpTop(const QList<Diagram *>&)));
connect(pa, SIGNAL(requestForDiagramMoveUpx10 (const QList<Diagram *> &)), this, SLOT(moveDiagramUpx10(const QList<Diagram *>&)));
connect(pa, SIGNAL(requestForDiagramMoveDownx10 (const QList<Diagram *> &)), this, SLOT(moveDiagramDownx10(const QList<Diagram *>&)));
connect(pa, SIGNAL(requestForDiagramMoveUpx100 (const QList<Diagram *> &)), this, SLOT(moveDiagramUpx100(const QList<Diagram *>&)));
connect(pa, SIGNAL(requestForDiagramMoveDownx100 (const QList<Diagram *> &)), this, SLOT(moveDiagramDownx100(const QList<Diagram *>&)));
}
/**
@@ -2183,126 +2185,182 @@ void QETDiagramEditor::addDiagramToProject(QETProject *project)
project_view->project()->addNewDiagram();
}
}
/**
* @brief QETDiagramEditor::removeDiagram
* Wrapper für einzelne Diagramme, um Abwärtskompatibilität zu erhalten.
*/
void QETDiagramEditor::removeDiagram(Diagram *diagram)
{
if (!diagram) return;
QList<Diagram *> list;
list << diagram;
removeDiagrams(list);
}
/**
* @brief QETDiagramEditor::removeDiagrams
* Deletes a list of folios with a single query.
*/
void QETDiagramEditor::removeDiagrams(const QList<Diagram *> &diagrams)
{
if (diagrams.isEmpty()) return;
if (diagrams.count() == 1) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, tr("Supprimer le folio"),
tr("Êtes-vous sûr de vouloir supprimer ce folio ?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) return;
} else {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, tr("Supprimer les folios"),
tr("Êtes-vous sûr de vouloir supprimer les %1 folios sélectionnés ?").arg(diagrams.count()),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) return;
}
ProjectView *project_view = nullptr;
if (QETProject *diagram_project = diagrams.first()->project()) {
project_view = findProject(diagram_project);
}
if (project_view) project_view->setUpdatesEnabled(false);
if (pa) pa->setUpdatesEnabled(false);
foreach (Diagram *diagram, diagrams) {
removeDiagramSilent(diagram);
}
if (pa) pa->setUpdatesEnabled(true);
if (project_view) project_view->setUpdatesEnabled(true);
emit syncElementsPanel();
}
/**
Supprime un schema de son projet
@param diagram Schema a supprimer
*/
void QETDiagramEditor::removeDiagram(Diagram *diagram)
void QETDiagramEditor::removeDiagramSilent(Diagram *diagram)
{
if (!diagram) return;
// recupere le projet contenant le schema
if (QETProject *diagram_project = diagram -> project()) {
// recupere la vue sur ce projet
if (ProjectView *project_view = findProject(diagram_project)) {
// affiche le schema en question
project_view -> showDiagram(diagram);
// supprime le schema
project_view -> removeDiagram(diagram);
project_view -> removeDiagram(diagram, true);
}
}
}
void QETDiagramEditor::moveDiagramUp(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Forward loop for moving up
for (int i = 0; i < safeDiagrams.size(); ++i) {
project_view->moveDiagramUp(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
/**
Change l'ordre des schemas d'un projet, en decalant le schema vers le haut /
la gauche
@param diagram Schema a decaler vers le haut / la gauche
*/
void QETDiagramEditor::moveDiagramUp(Diagram *diagram)
{
if (!diagram) return;
// recupere le projet contenant le schema
if (QETProject *diagram_project = diagram -> project()) {
if (diagram_project -> isReadOnly()) return;
// recupere la vue sur ce projet
if (ProjectView *project_view = findProject(diagram_project)) {
project_view -> moveDiagramUp(diagram);
void QETDiagramEditor::moveDiagramDown(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Backward loop for moving down
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
project_view->moveDiagramDown(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
/**
Change l'ordre des schemas d'un projet, en decalant le schema vers le bas /
la droite
@param diagram Schema a decaler vers le bas / la droite
*/
void QETDiagramEditor::moveDiagramDown(Diagram *diagram)
{
if (!diagram) return;
// recupere le projet contenant le schema
if (QETProject *diagram_project = diagram -> project()) {
if (diagram_project -> isReadOnly()) return;
// recupere la vue sur ce projet
if (ProjectView *project_view = findProject(diagram_project)) {
project_view -> moveDiagramDown(diagram);
void QETDiagramEditor::moveDiagramUpTop(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Backward loop to preserve relative order of the selected items when moving to top
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
project_view->moveDiagramUpTop(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
/**
Change l'ordre des schemas d'un projet, en decalant le schema vers le haut /
la gauche en position 0
@param diagram Schema a decaler vers le haut / la gauche en position 0
*/
void QETDiagramEditor::moveDiagramUpTop(Diagram *diagram)
{
if (!diagram) return;
// recupere le projet contenant le schema
if (QETProject *diagram_project = diagram -> project()) {
if (diagram_project -> isReadOnly()) return;
// recupere la vue sur ce projet
if (ProjectView *project_view = findProject(diagram_project)) {
project_view -> moveDiagramUpTop(diagram);
void QETDiagramEditor::moveDiagramUpx10(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Forward loop for moving up
for (int i = 0; i < safeDiagrams.size(); ++i) {
project_view->moveDiagramUpx10(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
/**
Change l'ordre des schemas d'un projet, en decalant le schema vers le haut /
la gauche x10
@param diagram Schema a decaler vers le haut / la gauche x10
*/
void QETDiagramEditor::moveDiagramUpx10(Diagram *diagram)
{
if (!diagram) return;
// recupere le projet contenant le schema
if (QETProject *diagram_project = diagram -> project()) {
if (diagram_project -> isReadOnly()) return;
// recupere la vue sur ce projet
if (ProjectView *project_view = findProject(diagram_project)) {
project_view -> moveDiagramUpx10(diagram);
void QETDiagramEditor::moveDiagramDownx10(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Backward loop for moving down
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
project_view->moveDiagramDownx10(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
/**
Change l'ordre des schemas d'un projet, en decalant le schema vers le bas /
la droite x10
@param diagram Schema a decaler vers le bas / la droite x10
*/
void QETDiagramEditor::moveDiagramDownx10(Diagram *diagram)
{
if (!diagram) return;
void QETDiagramEditor::moveDiagramUpx100(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Forward loop for moving up
for (int i = 0; i < safeDiagrams.size(); ++i) {
project_view->moveDiagramUpx100(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
// recupere le projet contenant le schema
if (QETProject *diagram_project = diagram -> project()) {
if (diagram_project -> isReadOnly()) return;
// recupere la vue sur ce projet
if (ProjectView *project_view = findProject(diagram_project)) {
project_view -> moveDiagramDownx10(diagram);
void QETDiagramEditor::moveDiagramDownx100(const QList<Diagram *> &diagrams) {
if (diagrams.isEmpty()) return;
QList<Diagram *> safeDiagrams = diagrams;
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
if (!diagram_project->isReadOnly()) {
if (ProjectView *project_view = findProject(diagram_project)) {
// Backward loop for moving down
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
project_view->moveDiagramDownx100(safeDiagrams.at(i));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
}
}
}
+10 -5
View File
@@ -138,12 +138,15 @@ class QETDiagramEditor : public QETMainWindow
void editDiagramProperties(Diagram *);
void addDiagramToProject(QETProject *);
void removeDiagram(Diagram *);
void removeDiagrams(const QList<Diagram *> &diagrams);
void removeDiagramFromProject();
void moveDiagramUp(Diagram *);
void moveDiagramDown(Diagram *);
void moveDiagramUpTop(Diagram *);
void moveDiagramUpx10(Diagram *);
void moveDiagramDownx10(Diagram *);
void moveDiagramUp(const QList<Diagram *> &diagrams);
void moveDiagramDown(const QList<Diagram *> &diagrams);
void moveDiagramUpTop(const QList<Diagram *> &diagrams);
void moveDiagramUpx10(const QList<Diagram *> &diagrams);
void moveDiagramDownx10(const QList<Diagram *> &diagrams);
void moveDiagramUpx100(const QList<Diagram *> &diagrams);
void moveDiagramDownx100(const QList<Diagram *> &diagrams);
void reloadOldElementPanel();
void diagramWasAdded(DiagramView *);
void findElementInPanel(const ElementsLocation &);
@@ -222,6 +225,8 @@ class QETDiagramEditor : public QETMainWindow
QList <QAction *> m_zoom_action_toolBar; ///Only zoom action must displayed in the toolbar
void removeDiagramSilent(Diagram *diagram);
QMdiArea m_workspace;
QSignalMapper windowMapper;
QDir open_dialog_dir; /// Directory to use for file dialogs such as File > save
+405 -99
View File
@@ -17,6 +17,8 @@
*/
#include "crossrefitem.h"
#include <QTimer>
#include "../autoNum/assignvariables.h"
#include "../diagram.h"
#include "../diagramposition.h"
@@ -25,6 +27,7 @@
#include "element.h"
#include "elementtextitemgroup.h"
#include "qgraphicsitemutility.h"
#include "terminal.h"
//define the height of the header.
static int header = 5;
@@ -221,9 +224,12 @@ void CrossRefItem::updateLabel()
prepareGeometryChange();
m_bounding_rect = QRectF();
//init the painter
QPainter qp;
qp.begin(&m_drawing);
// Build geometry and m_hovered_contacts_map using a QImage-backed
// painter so font metrics match the screen painter in paint().
// m_update_map=true allows the draw functions to populate the map;
// paint() calls them with m_update_map=false so the map is stable.
QImage dummy(1, 1, QImage::Format_ARGB32_Premultiplied);
QPainter qp(&dummy);
QPen pen_;
pen_.setWidthF(0.5);
qp.setPen(pen_);
@@ -232,17 +238,21 @@ void CrossRefItem::updateLabel()
//Draw cross or contact, only if master element is linked.
if (! m_element->linkedElements().isEmpty())
{
m_update_map = true;
XRefProperties::DisplayHas dh = m_properties.displayHas();
if (dh == XRefProperties::Cross)
drawAsCross(qp);
else if (dh == XRefProperties::Contacts)
drawAsContacts(qp);
m_update_map = false;
}
qp.end();
autoPos();
update();
// Schedule a second update after the scene has finished laying out,
// so the initial render uses the correct bounding rect.
QTimer::singleShot(0, this, [this]{ update(); });
}
/**
@@ -310,7 +320,26 @@ void CrossRefItem::paint(
{
Q_UNUSED(option)
Q_UNUSED(widget)
m_drawing.play(painter);
// Draw directly — no QPicture involved anywhere.
// QPicture::play() + nested drawPicture() (m_hdr_no_ctc/m_hdr_nc_ctc)
// caused a use-after-free crash (QRegion::begin, Qt5Gui+0x49af60)
// confirmed by analysis of 19+ coredumps.
// m_update_map=false: draw functions do not overwrite m_hovered_contacts_map.
if (m_element->linkedElements().isEmpty()) return;
QPen pen_;
pen_.setWidthF(0.5);
painter->save();
painter->setPen(pen_);
painter->setFont(QETApp::diagramTextsFont(5));
m_update_map = false;
XRefProperties::DisplayHas dh = m_properties.displayHas();
if (dh == XRefProperties::Cross)
drawAsCross(*painter);
else if (dh == XRefProperties::Contacts)
drawAsContacts(*painter);
painter->restore();
}
/**
@@ -320,7 +349,24 @@ void CrossRefItem::paint(
void CrossRefItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
event->accept();
QetGraphicsItem::showItem(m_hovered_contact);
// Find the element under the click position directly from the map,
// rather than relying on m_hovered_contact which may have been reset
// by hoverMoveEvent between the two clicks of the double-click.
QPointF pos = event->pos();
Element *target = m_hovered_contact;
if (!target) {
for (auto it = m_hovered_contacts_map.begin();
it != m_hovered_contacts_map.end(); ++it) {
if (it.value().contains(pos)) {
target = it.key();
break;
}
}
}
QetGraphicsItem::showItem(target);
}
/**
@@ -432,49 +478,41 @@ void CrossRefItem::linkedChanged()
/**
@brief CrossRefItem::buildHeaderContact
Draw the QPicture of m_hdr_no_ctc and m_hdr_nc_ctc
Draw NO and NC contact symbols directly onto painter.
Previously used QPicture (m_hdr_no_ctc/m_hdr_nc_ctc) which caused
use-after-free crashes via nested QPicture::play() calls.
*/
void CrossRefItem::buildHeaderContact()
void CrossRefItem::buildHeaderContact(QPainter &painter, QPointF no_pos, QPointF nc_pos)
{
if (!m_hdr_no_ctc.isNull() && !m_hdr_nc_ctc.isNull()) return;
//init the painter
QPainter qp;
QPen pen_;
pen_.setWidthF(0.2);
painter.save();
painter.setPen(pen_);
//draw the NO contact
if (m_hdr_no_ctc.isNull()) {
qp.begin(&m_hdr_no_ctc);
qp.setPen(pen_);
qp.drawLine(0, 3, 5, 3);
QPointF p1[3] = {
QPointF(5, 0),
QPointF(10, 3),
QPointF(15, 3),
};
qp.drawPolyline(p1,3);
qp.end();
}
//draw the NO contact header symbol
painter.drawLine(no_pos.x()+0, no_pos.y()+3, no_pos.x()+5, no_pos.y()+3);
QPointF p1[3] = {
QPointF(no_pos.x()+5, no_pos.y()+0),
QPointF(no_pos.x()+10, no_pos.y()+3),
QPointF(no_pos.x()+15, no_pos.y()+3),
};
painter.drawPolyline(p1, 3);
//draw the NC contact
if (m_hdr_nc_ctc.isNull()) {
qp.begin(&m_hdr_nc_ctc);
qp.setPen(pen_);
QPointF p2[3] = {
QPointF(0, 3),
QPointF(5, 3),
QPointF(5, 0)
};
qp.drawPolyline(p2,3);
QPointF p3[3] = {
QPointF(4, 0),
QPointF(10, 3),
QPointF(15, 3),
};
qp.drawPolyline(p3,3);
qp.end();
}
//draw the NC contact header symbol
QPointF p2[3] = {
QPointF(nc_pos.x()+0, nc_pos.y()+3),
QPointF(nc_pos.x()+5, nc_pos.y()+3),
QPointF(nc_pos.x()+5, nc_pos.y()+0)
};
painter.drawPolyline(p2, 3);
QPointF p3[3] = {
QPointF(nc_pos.x()+4, nc_pos.y()+0),
QPointF(nc_pos.x()+10, nc_pos.y()+3),
QPointF(nc_pos.x()+15, nc_pos.y()+3),
};
painter.drawPolyline(p3, 3);
painter.restore();
}
/**
@@ -492,11 +530,95 @@ void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
QStringList no_str, nc_str;
// Helper lambda: build "[13-14] pos" string for an element.
// For power contacts (e.g. 3-pole contactor), all named terminals
// are collected (e.g. "[1-2-3-4-5-6] pos").
// For other contacts (NO/NC/SW), only the first 2 named terminals
// are used — users are expected to name and order their terminals
// in the element editor.
// Helper lambda: build "[13-14] pos" for an element.
// - Power: all terminals sorted numerically → "[1-2-3-4-5-6] pos"
// - SW with typed terminals: show relevant pair per column (handled below)
// - Others: first 2 named terminals
auto buildLabel = [this](Element *elmt, bool is_no_col) -> QString {
const bool is_power =
elmt->kindInformations()["type"].toString() == "power";
const bool is_sw =
elmt->kindInformations()["state"].toString() == "SW";
QStringList tnames;
if (is_sw) {
// Check if terminals have explicit No/Nc/Common types
bool has_typed = false;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (t->terminalType() == TerminalData::No ||
t->terminalType() == TerminalData::Nc ||
t->terminalType() == TerminalData::Common) {
has_typed = true; break;
}
}
if (has_typed) {
QString no_name, nc_name, common_name;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (!t->name().isEmpty()) {
if (t->terminalType() == TerminalData::No) no_name = t->name();
else if (t->terminalType() == TerminalData::Nc) nc_name = t->name();
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
}
}
// NO column: show NO+Common pair; NC column: show NC+Common pair
if (is_no_col)
tnames << no_name << common_name;
else
tnames << nc_name << common_name;
} else {
// Fallback: first 2 named terminals
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tn = t->name();
if (!tn.isEmpty()) { tnames << tn; if (tnames.size() >= 2) break; }
}
}
} else {
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tn = t->name();
if (!tn.isEmpty()) {
tnames << tn;
if (!is_power && tnames.size() >= 2) break;
}
}
if (is_power) {
std::sort(tnames.begin(), tnames.end(),
[](const QString &a, const QString &b){
int i_a = a.size();
while (i_a > 0 && a[i_a-1].isDigit()) --i_a;
int i_b = b.size();
while (i_b > 0 && b[i_b-1].isDigit()) --i_b;
bool a_ok = false, b_ok = false;
int ai = a.mid(i_a).toInt(&a_ok);
int bi = b.mid(i_b).toInt(&b_ok);
if (a_ok && b_ok && a.left(i_a) == b.left(i_b))
return ai < bi;
return a < b;
});
}
}
QString pos = elementPositionText(elmt, true);
if (!tnames.isEmpty() && m_properties.showTerminalName())
return QStringLiteral("[") + tnames.join("-") + QStringLiteral("] ") + pos;
return pos;
};
for (auto elmt : NOElements()) {
no_str.append(elementPositionText(elmt, true));
no_str.append(buildLabel(elmt, true));
}
for (auto elmt : NCElements()) {
nc_str.append(elementPositionText(elmt, true));
nc_str.append(buildLabel(elmt, false));
}
//There is no string to display, we return now
@@ -509,9 +631,10 @@ void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
QRectF no_bounding;
for (auto str : no_str)
{
QRectF bounding = painter.boundingRect(QRectF (), Qt::AlignCenter, str);
no_bounding = no_bounding.united(bounding);
QRectF bounding = painter.boundingRect(QRectF(0, 0, 500, 20), Qt::AlignLeft, str);
no_bounding.setHeight(no_bounding.height() + bounding.height());
if (bounding.width() > no_bounding.width())
no_bounding.setWidth(bounding.width());
}
//Adjust according to the NO
if (no_bounding.height() > default_bounding.height() - header)
@@ -523,9 +646,10 @@ void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
QRectF nc_bounding;
for (auto str : nc_str)
{
QRectF bounding = painter.boundingRect(QRectF (), Qt::AlignCenter, str);
nc_bounding = nc_bounding.united(bounding);
QRectF bounding = painter.boundingRect(QRectF(0, 0, 500, 20), Qt::AlignLeft, str);
nc_bounding.setHeight(nc_bounding.height() + bounding.height());
if (bounding.width() > nc_bounding.width())
nc_bounding.setWidth(bounding.width());
}
//Adjust according to the NC
if (nc_bounding.height() > default_bounding.height() - header)
@@ -549,7 +673,8 @@ void CrossRefItem::drawAsCross(QPainter &painter)
{
//calculate the size of the cross
setUpCrossBoundingRect(painter);
m_hovered_contacts_map.clear();
m_drawed_contacts = 0;
if (m_update_map) m_hovered_contacts_map.clear();
//Bounding rect is empty that mean there's no contact to draw
if (boundingRect().isEmpty()) return;
@@ -559,12 +684,11 @@ void CrossRefItem::drawAsCross(QPainter &painter)
painter.drawLine(br.width()/2, 0, br.width()/2, br.height()); //vertical line
painter.drawLine(0, header, br.width(), header); //horizontal line
//Add the symbolic contacts
buildHeaderContact();
QPointF p((m_bounding_rect.width()/4) - (m_hdr_no_ctc.width()/2), 0);
painter.drawPicture (p, m_hdr_no_ctc);
p.setX((m_bounding_rect.width() * 3/4) - (m_hdr_nc_ctc.width()/2));
painter.drawPicture (p, m_hdr_nc_ctc);
//Add the symbolic contacts (drawn directly, no QPicture)
static const qreal hdr_symbol_width = 15.0;
QPointF no_pos((m_bounding_rect.width()/4) - (hdr_symbol_width/2), 0);
QPointF nc_pos((m_bounding_rect.width() * 3/4) - (hdr_symbol_width/2), 0);
buildHeaderContact(painter, no_pos, nc_pos);
//and fill it
fillCrossRef(painter);
@@ -581,7 +705,7 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
return;
m_drawed_contacts = 0;
m_hovered_contacts_map.clear();
if (m_update_map) m_hovered_contacts_map.clear();
QRectF bounding_rect;
//Draw each linked contact
@@ -605,7 +729,7 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
else if (type == "delayOff") option += DelayOff;
else if (type == "delayOnOff") option += DelayOnOff;
QRectF br = drawContact(painter, option, elmt);
QRectF br = drawContact(painter, option, elmt, i);
bounding_rect = bounding_rect.united(br);
}
}
@@ -624,9 +748,81 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
@param elmt : the element to display text (the position of the contact)
@return The bounding rect of the draw (contact + text)
*/
QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, int pole_index)
{
QString str = elementPositionText(elmt);
// Collect terminal names from the element definition (.elmt)
// e.g. name="13" and name="14" on each terminal
// For power contacts, sort numerically and pick the pair for pole_index.
// For SW contacts with typed terminals (No/Nc/Common), filter by role.
QStringList terminal_names;
const bool is_power_ctc =
elmt->kindInformations()["type"].toString() == "power";
const bool is_sw = (flags & SW) && !(flags & NOC);
// Check if SW terminals have explicit No/Nc/Common types
bool sw_has_typed_terminals = false;
if (is_sw) {
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (t->terminalType() == TerminalData::No ||
t->terminalType() == TerminalData::Nc ||
t->terminalType() == TerminalData::Common) {
sw_has_typed_terminals = true;
break;
}
}
}
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tname = t->name();
if (!tname.isEmpty())
terminal_names << tname;
}
if (is_power_ctc) {
// Sort terminals alphanumerically so names like "R1","R2"... or "1","2"...
// are ordered correctly. Extract trailing digits for numeric comparison;
// fall back to full string comparison when no digits are found.
std::sort(terminal_names.begin(), terminal_names.end(),
[](const QString &a, const QString &b){
// Extract trailing numeric part
int i_a = a.size();
while (i_a > 0 && a[i_a-1].isDigit()) --i_a;
int i_b = b.size();
while (i_b > 0 && b[i_b-1].isDigit()) --i_b;
bool a_ok = false, b_ok = false;
int ai = a.mid(i_a).toInt(&a_ok);
int bi = b.mid(i_b).toInt(&b_ok);
if (a_ok && b_ok && a.left(i_a) == b.left(i_b))
return ai < bi;
return a < b;
});
// Pick the pair for this pole: pole 0 → [0,1], pole 1 → [2,3], etc.
int idx = pole_index * 2;
if (idx + 1 < terminal_names.size())
terminal_names = QStringList() << terminal_names[idx] << terminal_names[idx+1];
else
terminal_names.clear();
} else if (is_sw && sw_has_typed_terminals) {
// Build [NO_name, Common_name, NC_name] from typed terminals
QString no_name, nc_name, common_name;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (!t->name().isEmpty()) {
if (t->terminalType() == TerminalData::No) no_name = t->name();
else if (t->terminalType() == TerminalData::Nc) nc_name = t->name();
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
}
}
// drawText expects: [0]=NC, [1]=NO, [2]=Common
// (drawText uses [1] for NO top-left, [0] for NC bottom-left, [2] for Common right)
terminal_names.clear();
terminal_names << nc_name << no_name << common_name;
}
int offset = m_drawed_contacts*10;
QRectF bounding_rect = QRectF(0, offset, 24, 10);
@@ -643,15 +839,17 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
painter.drawLine(0, offset+6, 8, offset+6);
painter.drawLine(16, offset+6, 24, offset+6);
///take example of this code for display the terminal text
/*QFont font = QETApp::diagramTextsFont(4);
font.setBold(true);
painter.setFont(font);
QRectF bt(0, offset, 24, 10);
int txt = 10 + m_drawed_contacts;
painter.drawText(bt, Qt::AlignLeft|Qt::AlignTop, QString::number(txt));
painter.drawText(bt, Qt::AlignRight|Qt::AlignTop, QString::number(txt));
painter.setFont(QETApp::diagramTextsFont(5));*/
// Draw terminal names on each side of the contact symbol
// terminal_names[0] on the left, terminal_names[1] on the right
if (!terminal_names.isEmpty() && m_properties.showTerminalName()) {
painter.setFont(QETApp::diagramTextsFont(4));
QRectF bt(0, offset, 24, 10);
if (terminal_names.size() >= 1)
painter.drawText(bt, Qt::AlignLeft|Qt::AlignTop, terminal_names[0]);
if (terminal_names.size() >= 2)
painter.drawText(bt, Qt::AlignRight|Qt::AlignTop, terminal_names[1]);
painter.setFont(QETApp::diagramTextsFont(5));
}
//draw open contact
if (flags &NO) {
@@ -729,14 +927,8 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str);
bounding_rect = bounding_rect.united(text_rect);
if (m_hovered_contacts_map.contains(elmt))
{
if (m_update_map)
m_hovered_contacts_map.insert(elmt, bounding_rect);
}
else
{
m_hovered_contacts_map.insert(elmt, bounding_rect);
}
++m_drawed_contacts;
}
@@ -768,6 +960,26 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
};
painter.drawPolyline(p2, 3);
// Draw terminal names for switch contact (3 terminals)
// terminal_names[0] = NO side (top left)
// terminal_names[1] = NC side (bottom left)
// terminal_names[2] = common side (right)
if (!terminal_names.isEmpty() && m_properties.showTerminalName()) {
painter.setFont(QETApp::diagramTextsFont(4));
// Sort order from parseTerminal (top->bottom, left->right):
// [0]=12 (NO, top-left), [1]=14 (common, top-center), [2]=13 (NC, bottom-center)
if (terminal_names.size() >= 1)
painter.drawText(QRectF(0, offset, 8, 8),
Qt::AlignLeft|Qt::AlignTop, terminal_names[1]); // 12 NO left
if (terminal_names.size() >= 2)
painter.drawText(QRectF(16, offset+4, 8, 6),
Qt::AlignRight|Qt::AlignTop, terminal_names[2]); // 14 common right
if (terminal_names.size() >= 3)
painter.drawText(QRectF(0, offset+9, 8, 6),
Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); // 13 NC left-bottom
painter.setFont(QETApp::diagramTextsFont(5));
}
//Draw the half ellipse off delay
if (flags &Delay)
{
@@ -799,12 +1011,8 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
str);
bounding_rect = bounding_rect.united(text_rect);
if (m_hovered_contacts_map.contains(elmt)) {
if (m_update_map)
m_hovered_contacts_map.insert(elmt, bounding_rect);
}
else {
m_hovered_contacts_map.insert(elmt, bounding_rect);
}
//a switch contact take place of two normal contact
m_drawed_contacts += 2;
@@ -835,12 +1043,8 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
str);
bounding_rect = bounding_rect.united(text_rect);
if (m_hovered_contacts_map.contains(elmt)) {
if (m_update_map)
m_hovered_contacts_map.insert(elmt, bounding_rect);
}
else {
m_hovered_contacts_map.insert(elmt, bounding_rect);
}
++m_drawed_contacts;
}
return bounding_rect;
@@ -866,7 +1070,60 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black);
painter.setPen(pen);
// Collect terminal names for NO column.
// Power: all terminals sorted numerically.
// SW with typed terminals: NO+Common pair.
// Others: first 2 named terminals.
const bool is_power_no =
elmt->kindInformations()["type"].toString() == "power";
const bool is_sw_no =
elmt->kindInformations()["state"].toString() == "SW";
QStringList tnames;
if (is_sw_no) {
bool has_typed = false;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (t->terminalType() == TerminalData::No ||
t->terminalType() == TerminalData::Nc ||
t->terminalType() == TerminalData::Common) {
has_typed = true; break;
}
}
if (has_typed) {
QString no_name, common_name;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (!t->name().isEmpty()) {
if (t->terminalType() == TerminalData::No) no_name = t->name();
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
}
}
tnames << no_name << common_name;
} else {
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tn = t->name();
if (!tn.isEmpty()) { tnames << tn; if (tnames.size() >= 2) break; }
}
}
} else {
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tn = t->name();
if (!tn.isEmpty()) {
tnames << tn;
if (!is_power_no && tnames.size() >= 2) break;
}
}
}
QString terminal_label;
if (!tnames.isEmpty() && m_properties.showTerminalName())
terminal_label = QStringLiteral("[") + tnames.join("-") + QStringLiteral("]");
QString str = elementPositionText(elmt, true);
if (!terminal_label.isEmpty())
str = terminal_label + QStringLiteral(" ") + str;
QRectF bounding = painter.boundingRect(
QRectF(no_top_left,
QSize(middle_cross, 1)),
@@ -874,13 +1131,11 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
str);
painter.drawText(bounding, Qt::AlignLeft, str);
if (m_hovered_contacts_map.contains(elmt))
{
m_hovered_contacts_map.insert(elmt, bounding);
}
else
{
m_hovered_contacts_map.insert(elmt, bounding);
if (m_update_map) {
QString pos_str = elementPositionText(elmt, true);
QRectF pos_rect = painter.boundingRect(bounding, Qt::AlignRight, pos_str);
pos_rect.adjust(-2, -1, 2, 1); // extend hit area slightly
m_hovered_contacts_map.insert(elmt, pos_rect);
}
no_top_left.ry() += bounding.height();
@@ -895,7 +1150,60 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
:pen.setColor(Qt::black);
painter.setPen(pen);
// Collect terminal names for NC column.
// Power: all terminals sorted numerically.
// SW with typed terminals: NC+Common pair.
// Others: first 2 named terminals.
const bool is_power_nc =
elmt->kindInformations()["type"].toString() == "power";
const bool is_sw_nc =
elmt->kindInformations()["state"].toString() == "SW";
QStringList tnames_nc;
if (is_sw_nc) {
bool has_typed = false;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (t->terminalType() == TerminalData::No ||
t->terminalType() == TerminalData::Nc ||
t->terminalType() == TerminalData::Common) {
has_typed = true; break;
}
}
if (has_typed) {
QString nc_name, common_name;
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
if (!t->name().isEmpty()) {
if (t->terminalType() == TerminalData::Nc) nc_name = t->name();
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
}
}
tnames_nc << nc_name << common_name;
} else {
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tn = t->name();
if (!tn.isEmpty()) { tnames_nc << tn; if (tnames_nc.size() >= 2) break; }
}
}
} else {
for (Terminal *t : elmt->terminals()) {
if (!t) continue;
const QString tn = t->name();
if (!tn.isEmpty()) {
tnames_nc << tn;
if (!is_power_nc && tnames_nc.size() >= 2) break;
}
}
}
QString terminal_label;
if (!tnames_nc.isEmpty() && m_properties.showTerminalName())
terminal_label = QStringLiteral("[") + tnames_nc.join("-") + QStringLiteral("]");
QString str = elementPositionText(elmt, true);
if (!terminal_label.isEmpty())
str = terminal_label + QStringLiteral(" ") + str;
QRectF bounding = painter.boundingRect(
QRectF(nc_top_left,
QSize(middle_cross, 1)),
@@ -903,13 +1211,11 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
str);
painter.drawText(bounding, Qt::AlignRight, str);
if (m_hovered_contacts_map.contains(elmt))
{
m_hovered_contacts_map.insert(elmt, bounding);
}
else
{
m_hovered_contacts_map.insert(elmt, bounding);
if (m_update_map) {
QString pos_str = elementPositionText(elmt, true);
QRectF pos_rect = painter.boundingRect(bounding, Qt::AlignRight, pos_str);
pos_rect.adjust(-2, -1, 2, 1); // extend hit area slightly
m_hovered_contacts_map.insert(elmt, pos_rect);
}
nc_top_left.ry() += bounding.height();
+3 -4
View File
@@ -22,7 +22,6 @@
#include <QGraphicsObject>
#include <QMultiMap>
#include <QPicture>
class Element;
class DynamicElementTextItem;
@@ -103,11 +102,11 @@ class CrossRefItem : public QGraphicsObject
private:
void linkedChanged();
void buildHeaderContact();
void buildHeaderContact(QPainter &painter, QPointF no_pos, QPointF nc_pos);
void setUpCrossBoundingRect(QPainter &painter);
void drawAsCross(QPainter &painter);
void drawAsContacts(QPainter &painter);
QRectF drawContact(QPainter &painter, int flags, Element *elmt);
QRectF drawContact(QPainter &painter, int flags, Element *elmt, int pole_index = 0);
void fillCrossRef(QPainter &painter);
void AddExtraInfo(QPainter &painter, const QString&);
QList<Element *> NOElements() const;
@@ -117,10 +116,10 @@ class CrossRefItem : public QGraphicsObject
private:
Element *m_element; //element to display the cross reference
QRectF m_bounding_rect;
QPicture m_drawing, m_hdr_no_ctc, m_hdr_nc_ctc;
QPainterPath m_shape_path;
XRefProperties m_properties;
int m_drawed_contacts;
bool m_update_map = false;
QMultiMap <Element *, QRectF> m_hovered_contacts_map;
Element *m_hovered_contact = nullptr;
DynamicElementTextItem *m_text = nullptr;
@@ -26,7 +26,7 @@
#include "crossrefitem.h"
#include "element.h"
#include "elementtextitemgroup.h"
#include <QTimer>
#include <QDomDocument>
#include <QDomElement>
#include <QGraphicsSceneMouseEvent>
@@ -302,7 +302,8 @@ void DynamicElementTextItem::refreshLabelConnection()
if ((m_text_from == ElementInfo && m_info_name == "label") ||
(m_text_from == CompositeText && m_composite_text.contains("%{label}")))
{
if(m_parent_element.data()->linkType() & Element::AllReport)
if((m_parent_element.data()->linkType() & Element::AllReport) ||
m_parent_element.data()->linkType() == Element::ConductorDefinition)
{
updateReportFormulaConnection();
updateReportText();
@@ -418,7 +419,8 @@ void DynamicElementTextItem::setInfoName(const QString &info_name)
updateXref();
}
if (m_parent_element && (m_parent_element.data()->linkType() & Element::AllReport)) //special treatment for report
if (m_parent_element && ((m_parent_element.data()->linkType() & Element::AllReport) ||
m_parent_element.data()->linkType() == Element::ConductorDefinition)) //special treatment for report
{
if(old_info_name != info_name)
{
@@ -472,7 +474,8 @@ void DynamicElementTextItem::setCompositeText(const QString &text)
updateXref();
}
if (m_parent_element && (m_parent_element.data()->linkType() & Element::AllReport)) //special treatment for report
if (m_parent_element && ((m_parent_element.data()->linkType() & Element::AllReport) ||
m_parent_element.data()->linkType() == Element::ConductorDefinition)) //special treatment for report
{
/*
* May be in some case the old and new composite text both have the var %{label},
@@ -726,28 +729,29 @@ QVariant DynamicElementTextItem::itemChange(QGraphicsItem::GraphicsItemChange ch
if(!m_parent_element.data()->linkedElements().isEmpty())
masterChanged();
}
else if(m_parent_element.data()->linkType() & Element::AllReport)
else if((m_parent_element.data()->linkType() & Element::AllReport) ||
m_parent_element.data()->linkType() == Element::ConductorDefinition)
{
//Get the report formula, and add connection to keep up to date the formula.
//Get the report formula, and add connection to keep up to date the formula.
if (m_parent_element.data()->diagram() && m_parent_element.data()->diagram()->project())
{
m_report_formula = m_parent_element.data()->diagram()->project()->defaultReportProperties();
m_report_formula_con = connect(m_parent_element.data()->diagram()->project(), &QETProject::reportPropertiesChanged, this, &DynamicElementTextItem::reportFormulaChanged);
}
//Add connection to keep up to date the status of the element linked to the parent folio report of this text.
//Add connection to keep up to date the status of the element linked to the parent folio report of this text.
connect(m_parent_element.data(), &Element::linkedElementChanged, this, &DynamicElementTextItem::reportChanged);
//The parent is already linked, we call reportChanged for init the connection
//The parent is already linked, we call reportChanged for init the connection
if(!m_parent_element.data()->linkedElements().isEmpty())
reportChanged();
if(m_parent_element.data()->terminals().size())
{
//Add connection to keep up date the conductors added or removed to the parent folio report element
//Add connection to keep up date the conductors added or removed to the parent folio report element
connect(m_parent_element.data()->terminals().first(), &Terminal::conductorWasAdded, this, &DynamicElementTextItem::conductorWasAdded);
connect(m_parent_element.data()->terminals().first(), &Terminal::conductorWasRemoved, this, &DynamicElementTextItem::conductorWasRemoved);
}
//Get a conductor in the potential
//Get a conductor in the potential
setPotentialConductor();
}
else if(m_parent_element.data()->linkType() == Element::Master)
@@ -1027,7 +1031,8 @@ void DynamicElementTextItem::clearFormulaConnection()
void DynamicElementTextItem::updateReportFormulaConnection()
{
if(!(m_parent_element.data()->linkType() & Element::AllReport))
if(!(m_parent_element.data()->linkType() & Element::AllReport) &&
m_parent_element.data()->linkType() != Element::ConductorDefinition)
return;
removeConnectionForReportFormula(m_report_formula);
@@ -1041,7 +1046,8 @@ void DynamicElementTextItem::updateReportFormulaConnection()
*/
void DynamicElementTextItem::updateReportText()
{
if(!(m_parent_element.data()->linkType() & Element::AllReport))
if(!(m_parent_element.data()->linkType() & Element::AllReport) &&
m_parent_element.data()->linkType() != Element::ConductorDefinition)
return;
if (m_text_from == ElementInfo && m_info_name == "label")
@@ -1098,7 +1104,10 @@ void DynamicElementTextItem::updateLabel()
void DynamicElementTextItem::conductorWasAdded(Conductor *conductor)
{
Q_UNUSED(conductor)
setPotentialConductor();
QTimer::singleShot(100, this, [this]() {
setPotentialConductor();
conductorPropertiesChanged();
});
}
/**
@@ -1123,7 +1132,8 @@ void DynamicElementTextItem::conductorWasRemoved(Conductor *conductor)
*/
void DynamicElementTextItem::setPotentialConductor()
{
if(parentElement() && (parentElement()->linkType() & Element::AllReport))
if(parentElement() && ((parentElement()->linkType() & Element::AllReport) || (parentElement()->linkType() == Element::ConductorDefinition) ||
parentElement()->linkType() == Element::ConductorDefinition))
{
if(parentElement()->terminals().isEmpty())
return;
@@ -1156,6 +1166,7 @@ void DynamicElementTextItem::setPotentialConductor()
connect(m_watched_conductor.data(), &Conductor::propertiesChange, this, &DynamicElementTextItem::conductorPropertiesChanged);
}
}
conductorPropertiesChanged();
}
else //This text haven't got a parent element, then ther isn't a conductor in the potential
{
@@ -1172,7 +1183,8 @@ void DynamicElementTextItem::setPotentialConductor()
*/
void DynamicElementTextItem::conductorPropertiesChanged()
{
if(m_parent_element && (m_parent_element.data()->linkType() & Element::AllReport))
if(m_parent_element && ((m_parent_element.data()->linkType() & Element::AllReport) || (m_parent_element.data()->linkType() == Element::ConductorDefinition) ||
m_parent_element.data()->linkType() == Element::ConductorDefinition))
{
if(m_text_from == ElementInfo)
{
@@ -1200,8 +1212,9 @@ void DynamicElementTextItem::conductorPropertiesChanged()
QString DynamicElementTextItem::reportReplacedCompositeText() const
{
QString string;
if(m_parent_element.data()->linkType() & Element::AllReport)
if((m_parent_element.data()->linkType() & Element::AllReport) || (m_parent_element.data()->linkType() == Element::ConductorDefinition) ||
m_parent_element.data()->linkType() == Element::ConductorDefinition)
{
string = m_composite_text;
+33
View File
@@ -36,6 +36,7 @@
#include "../qetxml.h"
#include "../qetversion.h"
#include "qgraphicsitemutility.h"
#include <QDebug>
#include <QDomElement>
#include <utility>
@@ -431,6 +432,11 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
m_data.fromXml(xml_def_elmt);
setToolTip(name());
QString my_type_str = xml_def_elmt.attribute(QStringLiteral("link_type"), QStringLiteral("simple"));
if (my_type_str == QLatin1String("conductor_definition")) {
m_link_type = Element::ConductorDefinition;
}
//load kind informations
m_kind_informations.fromXml(
xml_def_elmt.firstChildElement(QStringLiteral("kindInformations")),
@@ -627,6 +633,9 @@ Terminal *Element::parseTerminal(const QDomElement &dom_element)
Terminal *new_terminal = new Terminal(data, this);
m_terminals << new_terminal;
connect(new_terminal, &Terminal::conductorWasAdded, this, &Element::updateConductorTexts);
connect(new_terminal, &Terminal::conductorWasRemoved, this, &Element::updateConductorTexts);
//Sort from top to bottom and left to right
std::sort(m_terminals.begin(),
m_terminals.end(),
@@ -1288,6 +1297,8 @@ QString Element::linkTypeToString() const
return QStringLiteral("Terminale");
case Thumbnail:
return QStringLiteral("Thumbnail");
case ConductorDefinition:
return QStringLiteral("ConductorDefinition");
default:
return QStringLiteral("Unknown");
}
@@ -1555,3 +1566,25 @@ ElementsLocation Element::location() const
{
return m_location;
}
/**
* @brief Element::updateConductorTexts
*Slot that is triggered when a cable is *
*connected to or disconnected from a terminal on this component.
*/
/**
* @brief Element::updateConductorTexts
*/
void Element::updateConductorTexts()
{
if (m_link_type != Element::ConductorDefinition) {
return;
}
for (DynamicElementTextItem *deti : m_dynamic_text_list) {
if (deti) {
deti->setPotentialConductor();
deti->updateLabel();
}
}
}
+11 -8
View File
@@ -52,14 +52,15 @@ class Element : public QetGraphicsItem
(master, slave, report ect...)
*/
enum kind {
Simple = 1,
NextReport = 2,
PreviousReport = 4,
AllReport = 6,
Master = 8,
Slave = 16,
Terminale = 32,
Thumbnail = 64};
Simple = 1,
NextReport = 2,
PreviousReport = 4,
AllReport = 6,
Master = 8,
Slave = 16,
Terminale = 32,
Thumbnail = 64,
ConductorDefinition = 128};
Element(const ElementsLocation &location,
QGraphicsItem * = nullptr,
@@ -95,6 +96,8 @@ class Element : public QetGraphicsItem
DynamicElementTextItem *text,
ElementTextItemGroup *group);
public slots:
void updateConductorTexts();
public:
QList<Terminal *> terminals() const;
+9
View File
@@ -753,6 +753,15 @@ QString Terminal::name() const
return d->m_name;
}
/**
@brief Terminal::terminalType
@return the type of this terminal (Generic, Inner, Outer, No, Nc, Common)
*/
TerminalData::Type Terminal::terminalType() const
{
return d->m_type;
}
/**
@brief Conductor::relatedPotentialTerminal
Return terminal at the same potential from the same
+3 -1
View File
@@ -18,13 +18,14 @@
#ifndef TERMINAL_H
#define TERMINAL_H
#include "../qet.h"
#include "../properties/terminaldata.h"
#include <QtWidgets>
#include <QtXml>
class Conductor;
class Diagram;
class Element;
class TerminalData;
/**
@brief The Terminal class
@@ -75,6 +76,7 @@ class Terminal : public QGraphicsObject
Element *parentElement () const;
QUuid uuid () const;
QString name () const;
TerminalData::Type terminalType() const;
QList<Conductor *> conductors() const;
Qet::Orientation orientation() const;
+3
View File
@@ -308,6 +308,9 @@ void ElementPropertiesWidget::updateUi()
case Element::Terminale:
m_list_editor << new ElementInfoWidget(m_element, this);
break;
case Element::ConductorDefinition:
break;
default:
break;
}
+2
View File
@@ -139,6 +139,7 @@ void XRefPropertiesWidget::saveProperties(int index) {
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "right") xrp.setXrefPos(Qt::AlignRight);
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "alignment") xrp.setXrefPos(Qt::AlignBaseline);
xrp.setShowPowerContac(ui->m_show_power_cb->isChecked());
xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked());
xrp.setPrefix("power", ui->m_power_prefix_le->text());
xrp.setPrefix("delay", ui->m_delay_prefix_le->text());
xrp.setPrefix("switch", ui->m_switch_prefix_le->text());
@@ -190,6 +191,7 @@ void XRefPropertiesWidget::updateDisplay()
else if(xrp.getXrefPos() == Qt::AlignBaseline) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("alignment"));
else if(xrp.getXrefPos() == Qt::AlignBottom) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("bottom"));
ui->m_show_power_cb->setChecked(xrp.showPowerContact());
ui->m_show_terminal_name_cb->setChecked(xrp.showTerminalName());
ui->m_power_prefix_le-> setText(xrp.prefix("power"));
ui->m_delay_prefix_le-> setText(xrp.prefix("delay"));
ui->m_switch_prefix_le->setText(xrp.prefix("switch"));
+8
View File
@@ -108,6 +108,13 @@
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="m_show_terminal_name_cb">
<property name="text">
<string>Afficher les numéros de bornes dans les Xrefs</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="m_display_has_contacts_rb">
<property name="text">
@@ -283,6 +290,7 @@
<tabstop>m_master_le</tabstop>
<tabstop>m_slave_le</tabstop>
<tabstop>m_show_power_cb</tabstop>
<tabstop>m_show_terminal_name_cb</tabstop>
<tabstop>m_power_prefix_le</tabstop>
<tabstop>m_delay_prefix_le</tabstop>
<tabstop>m_switch_prefix_le</tabstop>
+205 -114
View File
@@ -5,9 +5,7 @@
#include <QTextStream>
#include <QDomDocument>
#include <QFile>
#include <QRegularExpression>
#include <QQueue>
#include <QSet>
#include <algorithm>
WiringListExport::WiringListExport(QETProject *project, QWidget *parent) :
QObject(parent),
@@ -45,8 +43,27 @@ QDomElement WiringListExport::climbToDiagram(QDomNode node) const
QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomElement &root) const
{
QMap<QString, ElementInfo> infoMap;
QDomNodeList elements = root.elementsByTagName("element");
QSet<QString> placeholderTypes;
QDomElement collection = root.firstChildElement("collection");
if (!collection.isNull()) {
QDomNodeList defs = collection.elementsByTagName("definition");
for (int i = 0; i < defs.size(); ++i) {
QDomElement def = defs.at(i).toElement();
QString ltype = def.attribute("link_type");
if (ltype == "next_report" || ltype == "previous_report") {
QDomElement parentEl = def.parentNode().toElement();
if (parentEl.tagName().toLower() == "element") {
QString name = parentEl.attribute("name");
if (!name.isEmpty()) {
placeholderTypes.insert(name);
}
}
}
}
}
QDomNodeList elements = root.elementsByTagName("element");
for (int i = 0; i < elements.size(); ++i) {
QDomElement el = elements.at(i).toElement();
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
@@ -75,13 +92,16 @@ QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomEleme
}
}
QString typeVal = el.attribute("type").toLower();
if (typeVal.contains("naechste") || typeVal.contains("vorherige") ||
typeVal.contains("next") || typeVal.contains("previous")) {
info.isPlaceholder = true;
QString typeVal = el.attribute("type");
info.isPlaceholder = false;
for (const QString &ptype : placeholderTypes) {
if (typeVal.endsWith(ptype)) {
info.isPlaceholder = true;
break;
}
}
infoMap.insert(uuid, info);
infoMap.insert(uuid, info);
}
return infoMap;
}
@@ -102,7 +122,15 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
data.el2_uuid = normalizeUuid(cond.attribute("element2", cond.attribute("element2id", "")));
data.element1_label = cond.attribute("element1_label");
if (data.element1_label.isEmpty()) {
data.element1_label = cond.attribute("element1_linked");
}
data.element2_label = cond.attribute("element2_label");
if (data.element2_label.isEmpty()) {
data.element2_label = cond.attribute("element2_linked");
}
data.terminalname1 = cond.attribute("terminalname1");
data.terminalname2 = cond.attribute("terminalname2");
data.tension_protocol = cond.attribute("tension_protocol");
@@ -119,101 +147,6 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
return conductors;
}
void WiringListExport::resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const
{
QRegularExpression numericLabelRe("^\\d+(\\.\\d+)?$");
QMap<QString, QList<ConductorData>> el_to_cons;
for (const ConductorData &c : conductors) {
if (!c.el1_uuid.isEmpty()) el_to_cons[c.el1_uuid].append(c);
if (!c.el2_uuid.isEmpty()) el_to_cons[c.el2_uuid].append(c);
}
for (int i = 0; i < conductors.size(); ++i) {
ConductorData &c = conductors[i];
auto resolveSide = [&](const QString &startUuid, QString &outLabel, QString &outTerminal) {
if (startUuid.isEmpty() || !elementsInfo.contains(startUuid)) return;
const ElementInfo &startInfo = elementsInfo[startUuid];
if (!startInfo.links.isEmpty() || startInfo.isPlaceholder) {
QQueue<QString> q;
QSet<QString> visited;
q.enqueue(startUuid);
visited.insert(startUuid);
int depth = 0;
while (!q.isEmpty() && depth < 3) {
int levelSize = q.size();
for (int k = 0; k < levelSize; ++k) {
QString curr = q.dequeue();
if (elementsInfo.contains(curr)) {
const ElementInfo &currInfo = elementsInfo[curr];
if (!currInfo.isPlaceholder && !currInfo.label.isEmpty() && !numericLabelRe.match(currInfo.label).hasMatch()) {
outLabel = currInfo.label;
return;
}
for (const QString &lnk : currInfo.links) {
if (!visited.contains(lnk)) {
visited.insert(lnk);
q.enqueue(lnk);
}
}
}
for (const ConductorData &cond : el_to_cons.value(curr)) {
if (cond.index == c.index) continue;
QString other;
QString terminalHint;
if (cond.el1_uuid == curr) {
other = cond.el2_uuid;
terminalHint = cond.terminalname2;
} else {
other = cond.el1_uuid;
terminalHint = cond.terminalname1;
}
if (!other.isEmpty() && !visited.contains(other)) {
if (elementsInfo.contains(other)) {
const ElementInfo &oInfo = elementsInfo[other];
if (!oInfo.isPlaceholder && !oInfo.label.isEmpty() && !numericLabelRe.match(oInfo.label).hasMatch()) {
outLabel = oInfo.label;
if (outTerminal.isEmpty()) outTerminal = terminalHint;
return;
}
}
visited.insert(other);
q.enqueue(other);
}
}
}
depth++;
}
} else {
if (outLabel.isEmpty()) {
outLabel = startInfo.label.isEmpty() ? startInfo.name : startInfo.label;
}
}
};
bool p1 = elementsInfo.value(c.el1_uuid).isPlaceholder;
bool p2 = elementsInfo.value(c.el2_uuid).isPlaceholder;
if (c.element1_label.isEmpty() || p1) {
if (p1) c.element1_label = "";
resolveSide(c.el1_uuid, c.element1_label, c.terminalname1);
}
if (c.element2_label.isEmpty() || p2) {
if (p2) c.element2_label = "";
resolveSide(c.el2_uuid, c.element2_label, c.terminalname2);
}
}
}
void WiringListExport::toCsv()
{
if (!m_project) return;
@@ -225,6 +158,45 @@ void WiringListExport::toCsv()
return;
}
QSet<QString> conductorDefinitionTypes;
QDomElement rootElem = doc.documentElement();
QDomElement collection = rootElem.firstChildElement("collection");
if (!collection.isNull()) {
QDomNodeList defs = collection.elementsByTagName("definition");
for (int i = 0; i < defs.size(); ++i) {
QDomElement def = defs.at(i).toElement();
if (def.attribute("link_type") == "conductor_definition") {
QDomElement parentEl = def.parentNode().toElement();
if (parentEl.tagName().toLower() == "element") {
QString name = parentEl.attribute("name");
if (!name.isEmpty()) {
conductorDefinitionTypes.insert(name);
}
}
}
}
}
QSet<QString> conductorDefinitionUuids;
QDomNodeList projectElements = rootElem.elementsByTagName("element");
for (int i = 0; i < projectElements.size(); ++i) {
QDomElement el = projectElements.at(i).toElement();
QString typeVal = el.attribute("type");
bool isCondDef = false;
for (const QString &cType : conductorDefinitionTypes) {
if (typeVal.endsWith(cType)) {
isCondDef = true;
break;
}
}
if (isCondDef) {
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
if (!uuid.isEmpty()) {
conductorDefinitionUuids.insert(uuid);
}
}
}
QFileDialog dialog(m_parent);
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setWindowTitle(tr("Exporter le plan de câblage"));
@@ -243,25 +215,144 @@ void WiringListExport::toCsv()
QMap<QString, ElementInfo> elementsInfo = collectElementsInfo(doc.documentElement());
QList<ConductorData> conductors = collectConductors(doc.documentElement());
resolveEndpoints(conductors, elementsInfo);
QList<ConductorData> uniqueConductors;
QSet<QString> seenConnections;
QMap<QString, ConductorData> partialWires;
for (const ConductorData &c : conductors) {
if (c.element1_label.isEmpty() && c.element2_label.isEmpty()) continue;
auto normalizePartial = [](ConductorData c, const QString &ph_uuid) {
if (c.el1_uuid == ph_uuid) {
std::swap(c.el1_uuid, c.el2_uuid);
std::swap(c.element1_label, c.element2_label);
std::swap(c.terminalname1, c.terminalname2);
}
return c;
};
QString sideA = c.element1_label + ":" + c.terminalname1;
QString sideB = c.element2_label + ":" + c.terminalname2;
auto mergeField = [](const QString &a, const QString &b) {
QString at = a.trimmed();
QString bt = b.trimmed();
if (at.isEmpty()) return bt;
if (bt.isEmpty()) return at;
if (at == bt) return at;
return at + ", " + bt;
};
QString key = (sideA < sideB) ? (sideA + "||" + sideB) : (sideB + "||" + sideA);
for (int i = 0; i < conductors.size(); ++i) {
ConductorData c = conductors[i];
if (!seenConnections.contains(key)) {
seenConnections.insert(key);
if (conductorDefinitionUuids.contains(c.el1_uuid) || conductorDefinitionUuids.contains(c.el2_uuid)) {
continue;
}
if (c.element1_label.isEmpty() && elementsInfo.contains(c.el1_uuid)) {
c.element1_label = elementsInfo[c.el1_uuid].label;
if (c.element1_label.isEmpty()) c.element1_label = elementsInfo[c.el1_uuid].name;
}
if (c.element2_label.isEmpty() && elementsInfo.contains(c.el2_uuid)) {
c.element2_label = elementsInfo[c.el2_uuid].label;
if (c.element2_label.isEmpty()) c.element2_label = elementsInfo[c.el2_uuid].name;
}
bool el1_ph = elementsInfo.value(c.el1_uuid).isPlaceholder;
bool el2_ph = elementsInfo.value(c.el2_uuid).isPlaceholder;
if (!el1_ph && !el2_ph) {
uniqueConductors.append(c);
continue;
}
if (el1_ph && el2_ph) {
uniqueConductors.append(c);
continue;
}
QString ph_uuid = el1_ph ? c.el1_uuid : c.el2_uuid;
ConductorData normC = normalizePartial(c, ph_uuid);
QString matching_ph_uuid;
if (!elementsInfo[ph_uuid].links.isEmpty()) {
matching_ph_uuid = elementsInfo[ph_uuid].links.first();
}
if (!matching_ph_uuid.isEmpty() && partialWires.contains(matching_ph_uuid)) {
ConductorData otherHalf = partialWires.take(matching_ph_uuid);
ConductorData merged;
merged.folio = mergeField(otherHalf.folio, normC.folio);
merged.el1_uuid = otherHalf.el1_uuid;
merged.element1_label = otherHalf.element1_label;
merged.terminalname1 = otherHalf.terminalname1;
merged.el2_uuid = normC.el1_uuid;
merged.element2_label = normC.element1_label;
merged.terminalname2 = normC.terminalname1;
merged.tension_protocol = mergeField(otherHalf.tension_protocol, normC.tension_protocol);
merged.conductor_color = mergeField(otherHalf.conductor_color, normC.conductor_color);
merged.conductor_section = mergeField(otherHalf.conductor_section, normC.conductor_section);
merged.function = mergeField(otherHalf.function, normC.function);
uniqueConductors.append(merged);
} else {
partialWires.insert(ph_uuid, normC);
}
}
for (const ConductorData &leftover : partialWires.values()) {
uniqueConductors.append(leftover);
}
for (ConductorData &c : uniqueConductors) {
if (!c.element2_label.isEmpty() && (c.element1_label.isEmpty() || c.element2_label.toLower() < c.element1_label.toLower())) {
std::swap(c.element1_label, c.element2_label);
std::swap(c.terminalname1, c.terminalname2);
std::swap(c.el1_uuid, c.el2_uuid);
}
}
std::sort(uniqueConductors.begin(), uniqueConductors.end(), [](const ConductorData &a, const ConductorData &b) {
QStringList partsA = a.folio.split(',');
QStringList partsB = b.folio.split(',');
int minLen = std::min(partsA.size(), partsB.size());
int folioCmp = 0;
for (int i = 0; i < minLen; ++i) {
bool okA, okB;
int numA = partsA[i].trimmed().toInt(&okA);
int numB = partsB[i].trimmed().toInt(&okB);
if (okA && okB) {
if (numA != numB) {
folioCmp = (numA < numB) ? -1 : 1;
break;
}
} else {
int strCmp = partsA[i].trimmed().compare(partsB[i].trimmed(), Qt::CaseInsensitive);
if (strCmp != 0) {
folioCmp = strCmp;
break;
}
}
}
if (folioCmp == 0 && partsA.size() != partsB.size()) {
folioCmp = (partsA.size() < partsB.size()) ? -1 : 1;
}
if (folioCmp != 0) return folioCmp < 0;
int el1Cmp = a.element1_label.toLower().compare(b.element1_label.toLower());
if (el1Cmp != 0) return el1Cmp < 0;
int el2Cmp = a.element2_label.toLower().compare(b.element2_label.toLower());
if (el2Cmp != 0) return el2Cmp < 0;
int term1Cmp = a.terminalname1.compare(b.terminalname1);
if (term1Cmp != 0) return term1Cmp < 0;
return a.terminalname2 < b.terminalname2;
});
QTextStream out(&file);
out << tr("Page", "Wiring list CSV header") << ";"
<< tr("Composant 1", "Wiring list CSV header") << ";"
+1 -11
View File
@@ -12,7 +12,6 @@ class QWidget;
class QDomElement;
class QDomNode;
// Internal data structures for parsing the XML graph
struct ElementInfo {
QString folio;
QStringList links;
@@ -34,18 +33,11 @@ struct ConductorData {
QString conductor_section;
QString function;
QString folio;
// Resolved endpoints
QString chosen_a_uuid;
QString chosen_a_label;
QString chosen_b_uuid;
QString chosen_b_label;
};
/**
* @brief The WiringListExport class
* Handles the export of the wiring list (Verdrahtungsplan) to a CSV file.
* Automatically resolves links and placeholders to find physical endpoints.
* Exports the wiring diagram from QElectroTech as a CSV file.
*/
class WiringListExport : public QObject
{
@@ -64,8 +56,6 @@ private:
QMap<QString, ElementInfo> collectElementsInfo(const QDomElement &root) const;
QList<ConductorData> collectConductors(const QDomElement &root) const;
void resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const;
};
#endif // WIRINGLISTEXPORT_H