Compare commits

..

14 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
joshua 2d4f968348 Fix crash when collapse root item of macro element tree view
Add an icon to the macro type of FileElementCollectionItem.

The first time the model (ElementsCollectionModel) ask for decoration
role the FileElementCollectionItem check if icon is null, if true the
icon is set, if false the function return.

In the case of the macro type, befor this commit the macro have a null
icon, and so the setIcon is call each time (many) the mouse move hover
and cause a qet crash.
This commit fix that, only by setUp an icon
2026-05-06 00:20:07 +02:00
joshua 6f669e1074 Merge branch 'master' into qt6_cmake_joshua 2026-05-05 20:11:05 +02:00
joshua 0b91318749 Remove several QT_VERSION_CHECK
Remove several QT_VERSION_CHECK related to Qt5 and Qt4.
2026-03-19 19:54:51 +01:00
joshua 1ba97c7e92 Remove hoto_update_cmake_message.cmake 2026-03-03 21:45:48 +01:00
joshua cd09fc0d32 Re-enable git_update_submodules.cmake
Re-enable git_update_submodules.cmake, use for update the elements repo.
Remove the unused include : fetch_elements.cmake
2026-03-03 21:44:35 +01:00
Laurent Trinques 924fe082fb Update fr_window_build_msys2.md 2026-03-03 06:56:24 +01:00
joshua ad37b0f9a5 Add documentation for build under Windows
The documentation is available in french only.
Contributors, feel free to create a new documentation in English (create
a new directory named 'en' for this purpose).
2026-03-02 22:47:32 +01:00
joshua fedc1cb092 Made available compilation on windows with msys2
The aim of this commit is to easily build qelectrotech under windows
with qt6/cmake and the package tool MSYS2.

-Update some cmake file.
-Remove Git submodule for pugixml and single application
-Use cmake find_package for pugi xml
-Use cmake fetchContent for single application.
-Use cmake find_package for KCoreAddons and KWidgetsAddons.
-Minor change for pugi xml
-Minor change on ProjectPrintWindow class to compatible with Qt6 API.
2026-03-02 22:34:16 +01:00
joshua 5f318e09c8 Add forgotten file and folder.
Add folder and file to cmake.
2026-01-30 19:43:07 +01:00
joshua 27afeaefe2 Upgrade pugixml version.
Upgrade pugixml to be compatible with cmake >= 4.0.0.

Compatibility with CMake < 3.5 has been removed from CMake since 4.0.0.
Pugixml V1.11.4 used cmake 3.4. Latest version V1.15 use cmake VERSION
3.5...3.30
2026-01-30 18:46:21 +01:00
joshua ab2f933fdf Re-enable multi-threading to load collection
The name of the elements and folders of the collection are not displayed
until we hover the item with the mouse.
This due that QtConcurent::run was disabled at loading of collection in
the goal of use QtConcurrent::run with Qt6.
Run is made to run a function once.
Map is made to run a fonction for each item of a sequence (what we need
in this case).
Remove code of run and re-enable code for map.
2026-01-28 19:47:04 +01:00
joshua 7f718f672f Fix : can't open recent file 2026-01-28 00:15:09 +01:00
joshua 9ec02bc088 Build with qt6 and cmake
First build with qt6 and cmake.
QET compil, but a lot of things don't work.
Build tested on debian sid and ubuntu 25.04.

Dependency needed under debian and ubuntu :
qtcreator cmake qt6-tools-dev qt6-svg-dev libsqlite3-dev
libkf6coreaddons-dev extra-cmake-modules libkf6widgetsaddons-dev
2026-01-27 23:31:34 +01:00
129 changed files with 7749 additions and 10688 deletions
+73 -129
View File
@@ -18,6 +18,7 @@ jobs:
build-msi: build-msi:
name: Build MSI with WiX v7 name: Build MSI with WiX v7
runs-on: windows-latest runs-on: windows-latest
# Only runs if Windows Build succeeded (or triggered manually) # Only runs if Windows Build succeeded (or triggered manually)
if: > if: >
github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_dispatch' ||
@@ -26,9 +27,10 @@ jobs:
permissions: permissions:
contents: write contents: write
pages: write pages: write
id-token: write # Required by SignPath id-token: write
steps: steps:
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 1. Checkout (to retrieve QElectroTech.wxs and sources) # 1. Checkout (to retrieve QElectroTech.wxs and sources)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
@@ -39,16 +41,12 @@ jobs:
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 2. Download the portable artifact from the main build # 2. Download the portable artifact from the main build
# Requires windows-build.yml to upload an artifact named
# "qelectrotech-windows-portable" (fixed name)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Download portable artifact - name: Download portable artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: qelectrotech-windows-portable name: qelectrotech-windows-portable
path: artifact\files path: artifact\files
# workflow_run => use the triggering run's ID
# workflow_dispatch => use input run_id if provided, otherwise current run
run-id: ${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }} run-id: ${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }} repository: ${{ github.repository }}
@@ -60,12 +58,10 @@ jobs:
id: version id: version
shell: pwsh shell: pwsh
run: | run: |
# Version from qetversion.cpp (same logic as windows-build.yml)
$src = Get-Content "sources\qetversion.cpp" -Raw -ErrorAction SilentlyContinue $src = Get-Content "sources\qetversion.cpp" -Raw -ErrorAction SilentlyContinue
if ($src -match 'return QVersionNumber\{([^}]+)\}') { if ($src -match 'return QVersionNumber\{([^}]+)\}') {
$ver = $Matches[1] -replace '\s','' -replace ',','.' $ver = $Matches[1] -replace '\s','' -replace ',','.'
} else { } else {
# Fallback: CMakeLists.txt
$cmake = Get-Content "CMakeLists.txt" -Raw $cmake = Get-Content "CMakeLists.txt" -Raw
if ($cmake -match 'project\s*\([^)]*VERSION\s+([\d]+\.[\d]+\.[\d]+)') { if ($cmake -match 'project\s*\([^)]*VERSION\s+([\d]+\.[\d]+\.[\d]+)') {
$ver = $Matches[1] $ver = $Matches[1]
@@ -73,52 +69,42 @@ jobs:
$ver = "0.0.0" $ver = "0.0.0"
} }
} }
# Numeric MSI version: 4 digits required (e.g. 0.100.1.0)
$verMsi = "$ver.0" $verMsi = "$ver.0"
# Short SHA for the display version
$sha = git rev-parse --short HEAD 2>$null $sha = git rev-parse --short HEAD 2>$null
if (-not $sha) { $sha = "unknown" } if (-not $sha) { $sha = "unknown" }
# Cumulative revision number (same calculation as windows-build.yml)
$count = git rev-list HEAD --count 2>$null $count = git rev-list HEAD --count 2>$null
$rev = [int]$count + 473 $rev = [int]$count + 473
$verDisplay = "${ver}-r${rev}-${sha}_x86_64-win64" $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_MSI=$verMsi" >> $env:GITHUB_OUTPUT
echo "VERSION_DISPLAY=$verDisplay" >> $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 MSI : $verMsi"
Write-Host "Version display : $verDisplay" Write-Host "Version display : $verDisplay"
Write-Host "Product GUID : $productGuid"
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 4. Install WiX v7, accept EULA and install WixUI extension # 4. Install WiX v7, accept EULA and install WixUI extension
# All done in one step: PATH is updated within the same step
# so wix is immediately available for eula and extension commands
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Install WiX v7 - name: Install WiX v7
shell: pwsh shell: pwsh
run: | run: |
dotnet tool install --global wix --version 7.0.0 dotnet tool install --global wix --version 7.0.0
# Update PATH immediately for the rest of this step
$toolsPath = [System.IO.Path]::Combine($env:USERPROFILE, '.dotnet', 'tools') $toolsPath = [System.IO.Path]::Combine($env:USERPROFILE, '.dotnet', 'tools')
$env:PATH = "$toolsPath;$env:PATH" $env:PATH = "$toolsPath;$env:PATH"
# Also export for subsequent steps
echo $toolsPath >> $env:GITHUB_PATH echo $toolsPath >> $env:GITHUB_PATH
# Accept OSMF EULA (official CI/CD method: writes a sentinel file)
wix eula accept wix7 wix eula accept wix7
# Install WixUI extension
wix extension add WixToolset.UI.wixext/7.0.0 wix extension add WixToolset.UI.wixext/7.0.0
# Install WixUtil extension (required for custom actions: Binary:Wix4UtilCA_*)
wix extension add WixToolset.Util.wixext/7.0.0 wix extension add WixToolset.Util.wixext/7.0.0
Write-Host "WiX v7 installed, EULA accepted, UI extension added."
Write-Host "WiX v7 installed, EULA accepted, UI + Util extensions added."
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 5. Check that the WXS file exists in the repository # 5. Check that the WXS file exists in the repository
@@ -129,8 +115,6 @@ jobs:
$wxs = "build-aux\windows\QElectroTech.wxs" $wxs = "build-aux\windows\QElectroTech.wxs"
if (-not (Test-Path $wxs)) { if (-not (Test-Path $wxs)) {
Write-Error "WXS file not found: $wxs" Write-Error "WXS file not found: $wxs"
Write-Host "Contents of build-aux\windows\ :"
Get-ChildItem "build-aux\windows\" -ErrorAction SilentlyContinue
exit 1 exit 1
} }
Write-Host "WXS found: $wxs" Write-Host "WXS found: $wxs"
@@ -145,10 +129,8 @@ jobs:
Get-ChildItem -Path "artifact\files" -Depth 2 | Get-ChildItem -Path "artifact\files" -Depth 2 |
Select-Object FullName | Format-Table -AutoSize Select-Object FullName | Format-Table -AutoSize
# Search for qelectrotech.exe in the artifact
$exe = Get-ChildItem -Path "artifact\files" -Filter "qelectrotech.exe" -Recurse | Select-Object -First 1 $exe = Get-ChildItem -Path "artifact\files" -Filter "qelectrotech.exe" -Recurse | Select-Object -First 1
if (-not $exe) { if (-not $exe) {
# Also try QElectroTech.exe (capital Q)
$exe = Get-ChildItem -Path "artifact\files" -Filter "QElectroTech.exe" -Recurse | Select-Object -First 1 $exe = Get-ChildItem -Path "artifact\files" -Filter "QElectroTech.exe" -Recurse | Select-Object -First 1
} }
if (-not $exe) { if (-not $exe) {
@@ -156,40 +138,30 @@ jobs:
exit 1 exit 1
} }
Write-Host "Executable: $($exe.FullName) ($([math]::Round($exe.Length/1MB,1)) MB)" Write-Host "Executable: $($exe.FullName) ($([math]::Round($exe.Length/1MB,1)) MB)"
$binDir = $exe.Directory.FullName
# FilesDir = folder containing bin\ $filesDir = Split-Path $binDir -Parent
$binDir = $exe.Directory.FullName
$filesDir = Split-Path $binDir -Parent
echo "FILES_DIR=$filesDir" >> $env:GITHUB_ENV echo "FILES_DIR=$filesDir" >> $env:GITHUB_ENV
Write-Host "FILES_DIR: $filesDir" Write-Host "FILES_DIR: $filesDir"
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 7. Convert LICENSE (GPL-2) to RTF for the WixUI licence screen # 7. Convert LICENSE (GPL-2) to RTF for the WixUI licence screen
# RTF is the only format accepted by Windows Installer.
# The conversion wraps plain text lines in basic RTF markup.
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Convert LICENSE to RTF - name: Convert LICENSE to RTF
shell: pwsh shell: pwsh
run: | run: |
$licSrc = "LICENSE" $licSrc = "LICENSE"
$licRtf = "$env:TEMP\License.rtf" $licRtf = "$env:TEMP\License.rtf"
if (-not (Test-Path $licSrc)) { if (-not (Test-Path $licSrc)) {
Write-Error "LICENSE file not found in repository root" Write-Error "LICENSE file not found in repository root"
exit 1 exit 1
} }
$lines = Get-Content $licSrc -Encoding UTF8 $lines = Get-Content $licSrc -Encoding UTF8
# RTF header — Courier New, 9pt, black
$rtf = New-Object System.Text.StringBuilder $rtf = New-Object System.Text.StringBuilder
[void]$rtf.AppendLine('{\rtf1\ansi\ansicpg1252\deff0') [void]$rtf.AppendLine('{\rtf1\ansi\ansicpg1252\deff0')
[void]$rtf.AppendLine('{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}}') [void]$rtf.AppendLine('{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}}')
[void]$rtf.AppendLine('{\colortbl;\red0\green0\blue0;}') [void]$rtf.AppendLine('{\colortbl;\red0\green0\blue0;}')
[void]$rtf.AppendLine('\f0\fs18\cf1') [void]$rtf.AppendLine('\f0\fs18\cf1')
foreach ($line in $lines) { foreach ($line in $lines) {
# Escape RTF special characters
$escaped = $line ` $escaped = $line `
-replace '\\', '\\\\' ` -replace '\\', '\\\\' `
-replace '\{', '\{' ` -replace '\{', '\{' `
@@ -197,29 +169,30 @@ jobs:
[void]$rtf.AppendLine("$escaped\par") [void]$rtf.AppendLine("$escaped\par")
} }
[void]$rtf.AppendLine('}') [void]$rtf.AppendLine('}')
[System.IO.File]::WriteAllText($licRtf, $rtf.ToString(), [System.Text.Encoding]::ASCII) [System.IO.File]::WriteAllText($licRtf, $rtf.ToString(), [System.Text.Encoding]::ASCII)
echo "LICENSE_RTF=$licRtf" >> $env:GITHUB_ENV echo "LICENSE_RTF=$licRtf" >> $env:GITHUB_ENV
Write-Host "License.rtf generated: $licRtf ($([math]::Round((Get-Item $licRtf).Length/1KB,1)) KB)" Write-Host "License.rtf generated: $licRtf ($([math]::Round((Get-Item $licRtf).Length/1KB,1)) KB)"
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 8. Replace Lancer QET.bat with the MSI-specific version # 8. Remove Lancer QET.bat from the artifact
# The portable version uses relative paths suited for the zip. # The MSI does not use the .bat: shortcuts point directly to
# The MSI version uses %~dp0 to resolve paths relative to # qelectrotech.exe, and elements\ is set read-only via a
# the installation directory in Program Files. # CustomAction in QElectroTech.wxs.
# The .bat is kept as-is in the ZIP portable build.
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Replace Lancer QET.bat for MSI - name: Remove Lancer QET.bat from artifact
shell: pwsh shell: pwsh
run: | run: |
$bat = "$env:FILES_DIR\Lancer QET.bat" $bat = "$env:FILES_DIR\Lancer QET.bat"
$content = "@echo off`r`nstart `"`" `"%~dp0bin\qelectrotech.exe`" --common-elements-dir=`"%~dp0elements/`" --common-tbt-dir=`"%~dp0titleblocks/`" --lang-dir=`"%~dp0lang/`" -style windowsvista`r`n" if (Test-Path $bat) {
[System.IO.File]::WriteAllText($bat, $content, [System.Text.Encoding]::ASCII) Remove-Item $bat -Force
Write-Host "Lancer QET.bat replaced for MSI installation." Write-Host "Lancer QET.bat removed from artifact (MSI uses direct exe shortcut)."
Write-Host "=== Content of new Lancer QET.bat ===" } else {
Get-Content $bat Write-Host "Lancer QET.bat not found in artifact (already absent)."
}
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 9. Build the MSI (unsigned) # 9. Build the MSI
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Build MSI - name: Build MSI
shell: pwsh shell: pwsh
@@ -231,31 +204,22 @@ jobs:
$wxs = "build-aux\windows\QElectroTech.wxs" $wxs = "build-aux\windows\QElectroTech.wxs"
$outputName = "QElectroTech-${verDisplay}.msi" $outputName = "QElectroTech-${verDisplay}.msi"
# Deterministic ProductCode GUID (UUID v5) based on version.
# Same version => same GUID (required for MSI repair/uninstall).
# Different version => different GUID (triggers a proper upgrade).
$seed = "qelectrotech-msi-$version"
$sha1 = [System.Security.Cryptography.SHA1]::Create()
$hash = $sha1.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($seed))
$hash[6] = ($hash[6] -band 0x0F) -bor 0x50 # UUID version 5
$hash[8] = ($hash[8] -band 0x3F) -bor 0x80 # RFC4122 variant
$productCode = "{$([System.Guid]::new([byte[]]$hash[0..15]).ToString().ToUpper())}"
New-Item -ItemType Directory -Force -Path "dist" | Out-Null New-Item -ItemType Directory -Force -Path "dist" | Out-Null
Write-Host "=== wix build ===" Write-Host "=== wix build ==="
Write-Host " WXS : $wxs" Write-Host " WXS : $wxs"
Write-Host " Version : $version" Write-Host " Version : $version"
Write-Host " ProductCode : $productCode" Write-Host " FilesDir : $filesDir"
Write-Host " FilesDir : $filesDir" Write-Host " LicenseRtf : $licRtf"
Write-Host " LicenseRtf : $licRtf" Write-Host " Output : dist\$outputName"
Write-Host " Output : dist\$outputName"
$productGuid = "${{ steps.version.outputs.PRODUCT_GUID }}"
wix build $wxs ` wix build $wxs `
-arch x64 ` -arch x64 `
-d "Version=$version" ` -d "Version=$version" `
-d "ProductVersion=$verDisplay" ` -d "ProductVersion=$verDisplay" `
-d "ProductCode=$productCode" ` -d "ProductCode=$productGuid" `
-d "FilesDir=$filesDir" ` -d "FilesDir=$filesDir" `
-d "LicenseRtf=$licRtf" ` -d "LicenseRtf=$licRtf" `
-ext WixToolset.UI.wixext ` -ext WixToolset.UI.wixext `
@@ -266,50 +230,40 @@ jobs:
Write-Error "MSI not generated: dist\$outputName" Write-Error "MSI not generated: dist\$outputName"
exit 1 exit 1
} }
$size = [math]::Round((Get-Item "dist\$outputName").Length / 1MB, 1) $size = [math]::Round((Get-Item "dist\$outputName").Length / 1MB, 1)
Write-Host "MSI generated: dist\$outputName ($size MB) ✓" Write-Host "MSI generated: dist\$outputName ($size MB) ✓"
echo "MSI_NAME=$outputName" >> $env:GITHUB_ENV echo "MSI_NAME=$outputName" >> $env:GITHUB_ENV
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 9b. Upload unsigned MSI as artifact (required by SignPath) # 10. Sign the MSI with SignPath
# SignPath fetches artifacts directly from GitHub Actions,
# so the MSI must be uploaded before the signing request.
# We capture the artifact-id output for use by SignPath.
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Upload unsigned MSI artifact (pre-signing) - name: Install SignPath PowerShell module
id: upload_unsigned shell: pwsh
uses: actions/upload-artifact@v4 run: |
with: Install-Module -Name SignPath -Force -Scope CurrentUser
name: qelectrotech-windows-msi-unsigned
path: dist\*.msi - name: Sign MSI with SignPath
retention-days: 1 shell: pwsh
if-no-files-found: error 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)"
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 9c. Submit signing request to SignPath (OSS organization) # 11. Upload the MSI artifact
# Prerequisites:
# - SIGNPATH_API_TOKEN : CI user token from the OSS org
# - SIGNPATH_ORGANIZATION_ID : Organization ID of the OSS org
# (visible in app.signpath.io → Settings after accepting
# the OSS invitation)
# The action downloads the signed MSI and places it in
# dist/ (overwriting the unsigned one).
# ----------------------------------------------------------------
- name: Sign MSI via SignPath
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
project-slug: 'qelectrotech-source-mirror'
signing-policy-slug: 'test-signing'
artifact-configuration-slug: 'MSI'
github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: 'dist\'
# ----------------------------------------------------------------
# 10. Upload the signed MSI artifact
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Upload MSI artifact - name: Upload MSI artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -330,9 +284,9 @@ jobs:
gh release view nightly --repo "$REPO" --json assets \ gh release view nightly --repo "$REPO" --json assets \
--jq '.assets[] | select(.name | test("\\.msi$")) | .name' \ --jq '.assets[] | select(.name | test("\\.msi$")) | .name' \
| while read -r name; do | while read -r name; do
echo "Deleting old asset: $name" echo "Deleting old asset: $name"
gh release delete-asset nightly "$name" --repo "$REPO" --yes gh release delete-asset nightly "$name" --repo "$REPO" --yes
done done
echo "Old .msi assets deleted." echo "Old .msi assets deleted."
shell: bash shell: bash
@@ -353,20 +307,18 @@ jobs:
shell: pwsh shell: pwsh
run: | run: |
Write-Host "=== MSI build summary ===" Write-Host "=== MSI build summary ==="
Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}" Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}"
Write-Host "WiX : v7.0.0" Write-Host "WiX : v7.0.0"
Write-Host "Signing : SignPath OSS" Write-Host "Runner image : ${{ runner.os }} / ${{ runner.arch }}"
Write-Host "Runner : ${{ runner.os }} / ${{ runner.arch }}"
if (Test-Path "dist\$env:MSI_NAME") { if (Test-Path "dist\$env:MSI_NAME") {
$size = [math]::Round((Get-Item "dist\$env:MSI_NAME").Length / 1MB, 1) $size = [math]::Round((Get-Item "dist\$env:MSI_NAME").Length / 1MB, 1)
Write-Host "MSI : $env:MSI_NAME ($size MB) ✓" Write-Host "MSI : $env:MSI_NAME ($size MB) ✓"
} else { } else {
Write-Host "MSI : FAILED ✗" Write-Host "MSI : FAILED ✗"
} }
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# 13. Generate and deploy the GitHub Pages download page # 13. Generate and deploy the GitHub Pages download page
# Toutes les URLs sont connues ici (exe, zip, msi).
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Checkout (for generate-page.py) - name: Checkout (for generate-page.py)
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -382,31 +334,23 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
set -euo pipefail set -euo pipefail
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
# Fetch asset names from the nightly release (source of truth)
ASSETS=$(gh release view nightly --repo "$REPO" --json assets --jq '.assets[].name') ASSETS=$(gh release view nightly --repo "$REPO" --json assets --jq '.assets[].name')
EXE_NAME=$(echo "$ASSETS" | grep '\.exe$' | head -1) EXE_NAME=$(echo "$ASSETS" | grep '\.exe$' | head -1)
ZIP_NAME=$(echo "$ASSETS" | grep '\.zip$' | head -1) ZIP_NAME=$(echo "$ASSETS" | grep '\.zip$' | head -1)
MSI_NAME=$(echo "$ASSETS" | grep '\.msi$' | head -1 || echo "") MSI_NAME=$(echo "$ASSETS" | grep '\.msi$' | head -1 || echo "")
BASE="https://github.com/$REPO/releases/download/nightly" BASE="https://github.com/$REPO/releases/download/nightly"
INSTALLER_URL="$BASE/$EXE_NAME" INSTALLER_URL="$BASE/$EXE_NAME"
PORTABLE_URL="$BASE/$ZIP_NAME" PORTABLE_URL="$BASE/$ZIP_NAME"
MSI_URL="" MSI_URL=""
[ -n "$MSI_NAME" ] && MSI_URL="$BASE/$MSI_NAME" [ -n "$MSI_NAME" ] && MSI_URL="$BASE/$MSI_NAME"
SHA="${{ github.event.workflow_run.head_sha || github.sha }}" SHA="${{ github.event.workflow_run.head_sha || github.sha }}"
SHORT="${SHA:0:7}" SHORT="${SHA:0:7}"
DATE=$(date -u '+%Y-%m-%d %H:%M UTC') DATE=$(date -u '+%Y-%m-%d %H:%M UTC')
RUN_URL="https://github.com/$REPO/actions/runs/${{ github.run_id }}" RUN_URL="https://github.com/$REPO/actions/runs/${{ github.run_id }}"
RUN_NUMBER="${{ github.run_number }}" RUN_NUMBER="${{ github.run_number }}"
export DATE SHORT REPO SHA RUN_URL RUN_NUMBER export DATE SHORT REPO SHA RUN_URL RUN_NUMBER
export INSTALLER_URL PORTABLE_URL MSI_URL export INSTALLER_URL PORTABLE_URL MSI_URL
python3 source/build-aux/generate-page.py python3 source/build-aux/generate-page.py
- name: Add .nojekyll - name: Add .nojekyll
+91 -115
View File
@@ -14,25 +14,30 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. # along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
include(cmake/hoto_update_cmake_message.cmake)
cmake_minimum_required(VERSION 3.5...4.2) cmake_minimum_required(VERSION 3.5...4.2)
project(qelectrotech project(qelectrotech
VERSION 0.100.1 VERSION 0.100.0
DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features." DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features."
HOMEPAGE_URL "https://qelectrotech.org/" HOMEPAGE_URL "https://qelectrotech.org/"
LANGUAGES CXX) LANGUAGES CXX)
include(cmake/copyright_message.cmake) include(cmake/copyright_message.cmake)
set(QET_DIR ${PROJECT_SOURCE_DIR}) set(QET_DIR ${PROJECT_SOURCE_DIR})
include(cmake/qet_compilation_vars.cmake)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS ${QET_COMPONENTS})
qt_standard_project_setup()
# Add sub directories # Add sub directories
option(PACKAGE_TESTS "Build the tests" ON) option(PACKAGE_TESTS "Build the tests" NO)
if(PACKAGE_TESTS) if(PACKAGE_TESTS)
message("Add sub directory tests") message("Add sub directory tests")
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()
include(cmake/paths_compilation_installation.cmake) include(cmake/paths_compilation_installation.cmake)
@@ -43,127 +48,98 @@ include(cmake/git_last_commit_sha.cmake)
include(cmake/fetch_kdeaddons.cmake) include(cmake/fetch_kdeaddons.cmake)
include(cmake/fetch_singleapplication.cmake) include(cmake/fetch_singleapplication.cmake)
include(cmake/fetch_pugixml.cmake) include(cmake/fetch_pugixml.cmake)
include(cmake/qet_compilation_vars.cmake)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
SET(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(
QT
NAMES
Qt5
COMPONENTS
${QET_COMPONENTS}
REQUIRED
)
find_package(
Qt${QT_VERSION_MAJOR}
COMPONENTS
${QET_COMPONENTS}
REQUIRED)
find_package(SQLite3 REQUIRED)
set(CMAKE_AUTOUIC_SEARCH_PATHS ${QET_DIR}/sources/ui) set(CMAKE_AUTOUIC_SEARCH_PATHS ${QET_DIR}/sources/ui)
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang")
qt5_add_translation(QM_FILES ${TS_FILES})
# als laatse
include(cmake/define_definitions.cmake) include(cmake/define_definitions.cmake)
# On Windows, WIN32 sets /SUBSYSTEM:WINDOWS to suppress the console window. qt_add_executable(
# Qt automatically links qtmain.lib which provides the WinMain entry point, ${PROJECT_NAME}
# so no source code change is needed. ${QET_RES_FILES}
if(WIN32) ${QET_SRC_FILES}
add_executable( ${QM_FILES}
${PROJECT_NAME} ${QET_DIR}/qelectrotech.qrc
WIN32 )
${QET_RES_FILES}
${QET_SRC_FILES} if(QMFILES_AS_RESOURCE)
${QM_FILES} qt_add_translations(${PROJECT_NAME} TS_FILES ${TS_FILES} RESOURCE_PREFIX "/lang")
${QET_DIR}/qelectrotech.qrc
)
else() else()
add_executable( qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
${PROJECT_NAME} set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang")
${QET_RES_FILES} qt_add_translation(QM_FILES ${TS_FILES})
${QET_SRC_FILES}
${QM_FILES}
${QET_DIR}/qelectrotech.qrc
)
endif() endif()
find_package(SQLite3 REQUIRED)
target_link_libraries( target_link_libraries(
${PROJECT_NAME} ${PROJECT_NAME}
PUBLIC PUBLIC
PRIVATE PRIVATE
pugixml::pugixml pugixml::pugixml
SingleApplication::SingleApplication SingleApplication::SingleApplication
SQLite::SQLite3 SQLite::SQLite3
${KF5_PRIVATE_LIBRARIES} ${KF6_PRIVATE_LIBRARIES}
${QET_PRIVATE_LIBRARIES} ${QET_PRIVATE_LIBRARIES}
) )
target_include_directories( target_include_directories(
${PROJECT_NAME} ${PROJECT_NAME}
PRIVATE PRIVATE
${QET_DIR}/sources/titleblock ${QET_DIR}/sources/titleblock
${QET_DIR}/sources/ui ${QET_DIR}/sources/ui
${QET_DIR}/sources/qetgraphicsitem ${QET_DIR}/sources/qetgraphicsitem
${QET_DIR}/sources/qetgraphicsitem/ViewItem ${QET_DIR}/sources/qetgraphicsitem/ViewItem
${QET_DIR}/sources/qetgraphicsitem/ViewItem/ui ${QET_DIR}/sources/qetgraphicsitem/ViewItem/ui
${QET_DIR}/sources/richtext ${QET_DIR}/sources/richtext
${QET_DIR}/sources/factory ${QET_DIR}/sources/factory
${QET_DIR}/sources/properties ${QET_DIR}/sources/properties
${QET_DIR}/sources/dvevent ${QET_DIR}/sources/dvevent
${QET_DIR}/sources/editor ${QET_DIR}/sources/editor
${QET_DIR}/sources/editor/esevent ${QET_DIR}/sources/editor/esevent
${QET_DIR}/sources/editor/graphicspart ${QET_DIR}/sources/editor/graphicspart
${QET_DIR}/sources/editor/ui ${QET_DIR}/sources/editor/ui
${QET_DIR}/sources/editor/UndoCommand ${QET_DIR}/sources/editor/UndoCommand
${QET_DIR}/sources/undocommand ${QET_DIR}/sources/undocommand
${QET_DIR}/sources/diagramevent ${QET_DIR}/sources/diagramevent
${QET_DIR}/sources/ElementsCollection ${QET_DIR}/sources/ElementsCollection
${QET_DIR}/sources/ElementsCollection/ui ${QET_DIR}/sources/ElementsCollection/ui
${QET_DIR}/sources/autoNum ${QET_DIR}/sources/autoNum
${QET_DIR}/sources/autoNum/ui ${QET_DIR}/sources/autoNum/ui
${QET_DIR}/sources/ui/configpage ${QET_DIR}/sources/ui/configpage
${QET_DIR}/sources/SearchAndReplace ${QET_DIR}/sources/SearchAndReplace
${QET_DIR}/sources/SearchAndReplace/ui ${QET_DIR}/sources/SearchAndReplace/ui
${QET_DIR}/sources/NameList ${QET_DIR}/sources/NameList
${QET_DIR}/sources/NameList/ui ${QET_DIR}/sources/NameList/ui
${QET_DIR}/sources/utils ${QET_DIR}/sources/utils
${QET_DIR}/pugixml/src ${QET_DIR}/sources/dataBase
${QET_DIR}/sources/dataBase ${QET_DIR}/sources/dataBase/ui
${QET_DIR}/sources/dataBase/ui ${QET_DIR}/sources/factory/ui
${QET_DIR}/sources/factory/ui ${QET_DIR}/sources/print
${QET_DIR}/sources/print ${QET_DIR}/sources/svg
${QET_DIR}/sources/svg )
)
install(TARGETS ${PROJECT_NAME}) install(TARGETS ${PROJECT_NAME})
if (NOT MINGW) if (NOT MINGW)
install(DIRECTORY ico/breeze-icons/16x16 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY ico/breeze-icons/22x22 DESTINATION ${QET_ICONS_PATH}) install(DIRECTORY ico/breeze-icons/16x16 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY ico/breeze-icons/32x32 DESTINATION ${QET_ICONS_PATH}) install(DIRECTORY ico/breeze-icons/22x22 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY ico/breeze-icons/48x48 DESTINATION ${QET_ICONS_PATH}) install(DIRECTORY ico/breeze-icons/32x32 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY ico/breeze-icons/64x64 DESTINATION ${QET_ICONS_PATH}) install(DIRECTORY ico/breeze-icons/48x48 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY ico/breeze-icons/128x128 DESTINATION ${QET_ICONS_PATH}) install(DIRECTORY ico/breeze-icons/64x64 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY ico/breeze-icons/256x256 DESTINATION ${QET_ICONS_PATH}) install(DIRECTORY ico/breeze-icons/128x128 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY elements DESTINATION share/qelectrotech) install(DIRECTORY ico/breeze-icons/256x256 DESTINATION ${QET_ICONS_PATH})
install(DIRECTORY examples DESTINATION share/qelectrotech) install(DIRECTORY elements DESTINATION share/qelectrotech)
install(DIRECTORY titleblocks DESTINATION share/qelectrotech) install(DIRECTORY examples DESTINATION share/qelectrotech)
install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech) install(DIRECTORY titleblocks DESTINATION share/qelectrotech)
install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications) install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech)
install(FILES misc/qelectrotech.xml DESTINATION share/mime/packages) install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications)
install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH}) install(FILES misc/qelectrotech.xml DESTINATION share/mime/packages)
install(FILES ${QM_FILES} DESTINATION ${QET_LANG_PATH}) install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH})
if(NOT QMFILES_AS_RESOURCE)
install(FILES ${QM_FILES} DESTINATION ${QET_LANG_PATH})
endif()
endif() endif()
+2 -91
View File
@@ -2,102 +2,13 @@
## [Unreleased](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/HEAD) ## [Unreleased](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/HEAD)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/nightly...HEAD) [Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9...HEAD)
**Closed issues:**
- “Exclude from auto-numbering” and “Potential isolation” tick boxes fault [\#482](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/482)
- Add ability to change appearance of multiple lines at once [\#476](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/476)
- Save As dialog asks for "element name" but actually requires a file name [\#469](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/469)
- Some icons are illegible with dark theme [\#466](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/466)
- \[BUG\] Properties to all conductors [\#460](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/460)
- Internal links in PDF export [\#417](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/417)
- Apple silicon download is not working [\#400](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/400)
- Add missing title block variables to 'folio properties' window [\#271](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/271)
- Bug: Saving a read-only project doesn't clear the read-only state [\#217](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/217)
**Merged pull requests:**
- PartText: keep text position stable across save/reopen on font-size change \(\#158\) [\#501](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/501) ([ispyisail](https://github.com/ispyisail))
- Clear read-only state when a project is saved to a writable file [\#497](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/497) ([ispyisail](https://github.com/ispyisail))
- Fix regional system locale loading the wrong translation \(pt\_BR/nl\_BE/nl\_NL\) [\#496](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/496) ([ispyisail](https://github.com/ispyisail))
- Folio properties: auto-add a title block's custom variables [\#495](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/495) ([ispyisail](https://github.com/ispyisail))
- Element editor Save As: label the field as a file name, not 'element name' [\#494](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/494) ([ispyisail](https://github.com/ispyisail))
- CLI: add --set-titleblock, and fix headless backup crash [\#493](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/493) ([ispyisail](https://github.com/ispyisail))
- Update qet\_zh.ts [\#491](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/491) ([zi-mozhuang](https://github.com/zi-mozhuang))
- CLI: clickable cross-reference hyperlinks in PDF export [\#490](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/490) ([ispyisail](https://github.com/ispyisail))
- CLI: add verification & data-export tools \(info, BOM, nets, links, check-elements, resave\) [\#489](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/489) ([ispyisail](https://github.com/ispyisail))
- Issues 482 [\#488](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/488) ([Kellermorph](https://github.com/Kellermorph))
- Revert "Update-UI-Chinese-translation" [\#486](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/486) ([scorpio810](https://github.com/scorpio810))
- CLI export: don't draw the editor grid in PDF/PNG/SVG output [\#485](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/485) ([ispyisail](https://github.com/ispyisail))
- Update-UI-Chinese-translation [\#484](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/484) ([zi-mozhuang](https://github.com/zi-mozhuang))
- Add headless command-line export \(PDF / PNG / SVG / cable-list / wire-list\) [\#483](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/483) ([ispyisail](https://github.com/ispyisail))
- fix possible crashes in crossrefitem [\#479](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/479) ([ChuckNr11](https://github.com/ChuckNr11))
- Fix: Dynamic element text shifting/jumping when duplicating diagrams [\#477](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/477) ([Kellermorph](https://github.com/Kellermorph))
- Update German translations for duplicate diagram feature [\#475](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/475) ([Kellermorph](https://github.com/Kellermorph))
- Feat: Add ability to duplicate diagrams/folios with all metadata and … [\#473](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/473) ([Kellermorph](https://github.com/Kellermorph))
- Feature: Allow excluding specific elements from BOM \(Nomenclature\) [\#472](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/472) ([Kellermorph](https://github.com/Kellermorph))
- Potential Isolation option for terminals [\#471](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/471) ([Kellermorph](https://github.com/Kellermorph))
- Fix: Wiring list filter and dynamic text timing [\#470](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/470) ([Kellermorph](https://github.com/Kellermorph))
- New element: Line definition [\#464](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/464) ([Kellermorph](https://github.com/Kellermorph))
- Turkish Lang Update [\#463](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/463) ([scorpio810](https://github.com/scorpio810))
- follow up: wiring list [\#462](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/462) ([Kellermorph](https://github.com/Kellermorph))
- Fix and Improve Multi-selection for Diagram Operations [\#459](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/459) ([Kellermorph](https://github.com/Kellermorph))
## [nightly](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/nightly) (2026-05-10)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.100...nightly)
**Closed issues:**
- Flatpak runtimes outdated [\#446](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/446)
- Move Flemish man pages from man/be/ to man/nl\_BE/ [\#439](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/439)
- you could share an PR to fix it? [\#438](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/438)
- Diacritics in some filenames can possibly lead to the problems during packaging [\#437](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/437)
- שרטוט חשמל [\#435](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/435)
- Feature request: add circuit simulation [\#432](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/432)
- No usable sources archive for version 0.100 [\#418](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/418)
- New release ? [\#411](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/411)
- options moving when opening "file", "edition" menus [\#299](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/299)
**Merged pull requests:**
- Try to add Windows build CI workflow [\#457](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/457) ([scorpio810](https://github.com/scorpio810))
- Fixed: Prevented the selection in the project tree from jumping to the last page when saving. [\#456](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/456) ([Kellermorph](https://github.com/Kellermorph))
- Fix Thumbnail in Makrotree [\#455](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/455) ([Kellermorph](https://github.com/Kellermorph))
- Update German translation for macro feature [\#454](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/454) ([Kellermorph](https://github.com/Kellermorph))
- Fix losing Focus on moving diagram position with keyboard [\#452](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/452) ([ChuckNr11](https://github.com/ChuckNr11))
- Draft: Feature - Introduce User Templates Collection and Dedicated UI Tab [\#451](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/451) ([Kellermorph](https://github.com/Kellermorph))
- Update translation [\#450](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/450) ([Kellermorph](https://github.com/Kellermorph))
- Automatic Terminal Numbering Tool [\#449](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/449) ([Kellermorph](https://github.com/Kellermorph))
- Supplement to pull request \#444 by Kellermorph [\#448](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/448) ([ChuckNr11](https://github.com/ChuckNr11))
- Add RAM-based wiring list export [\#447](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/447) ([Kellermorph](https://github.com/Kellermorph))
- Follow-up: Address review comments for slave limit feature [\#444](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/444) ([Kellermorph](https://github.com/Kellermorph))
- Feature: Auto-select active diagram in the elements panel tree [\#443](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/443) ([Kellermorph](https://github.com/Kellermorph))
- Revert "Feature: Implement max\_slaves limit for Master elements" [\#442](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/442) ([scorpio810](https://github.com/scorpio810))
- Feature: Implement max\_slaves limit for Master elements [\#441](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/441) ([Kellermorph](https://github.com/Kellermorph))
- Update qet\_cs.ts [\#434](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/434) ([pafri](https://github.com/pafri))
- Update QCH Help file [\#433](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/433) ([Int-Circuit](https://github.com/Int-Circuit))
- Create Korean man page for QElectroTech [\#431](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/431) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Update ELEMENTS.LICENSE [\#430](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/430) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Update CREDIT [\#429](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/429) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Korean comments to QElectroTech XML file [\#428](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/428) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Spanish and Korean summaries to appdata [\#427](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/427) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Korean translations for comments and generic names [\#426](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/426) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Restore copyright and license information in QET64.nsi [\#425](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/425) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Korean language strings to lang\_extra.nsh [\#424](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/424) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Korean translation author to aboutqetdialog [\#423](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/423) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Korean language support in xml element collection [\#422](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/422) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
- Add Korean translation \(ko\) translated by jkh [\#419](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/419) ([Kyle-Code-CA](https://github.com/Kyle-Code-CA))
## [0.100](https://github.com/qelectrotech/qelectrotech-source-mirror/tree/0.100) (2026-01-25)
[Full Changelog](https://github.com/qelectrotech/qelectrotech-source-mirror/compare/0.9...0.100)
**Closed issues:** **Closed issues:**
- error in doxygen action code [\#414](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/414) - 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) - "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) - 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) - 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) - Non-perpendicular connections [\#368](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/368)
+3
View File
@@ -59,6 +59,9 @@ Here are the technical choices made for the software development:
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. 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.
### Build QElectroTech under Windows
To build QElectroTech under microsoft Windows, please follow [these instructions (french)](md/fr/fr_window_build_summary.md)
# Features # Features
+10 -5
View File
@@ -62,10 +62,15 @@ message("PROJECT_SOURCE_DIR :" ${PROJECT_SOURCE_DIR})
message("QET_DIR :" ${QET_DIR}) message("QET_DIR :" ${QET_DIR})
message("GIT_COMMIT_SHA :" ${GIT_COMMIT_SHA}) message("GIT_COMMIT_SHA :" ${GIT_COMMIT_SHA})
if(BUILD_WITH_KF5) if(BUILD_WITH_KF6 AND BUILD_KF6)
message("KF5_GIT_TAG :" ${KF5_GIT_TAG}) message("KF6_GIT_TAG :" ${KF6_GIT_TAG})
else() endif()
add_definitions(-DBUILD_WITHOUT_KF5) if(NOT BUILD_WITH_KF6)
add_definitions(-DBUILD_WITHOUT_KF6)
endif() endif()
message("QET_COMPONENTS :" ${QET_COMPONENTS}) message("QET_COMPONENTS :" ${QET_COMPONENTS})
message("QT_VERSION_MAJOR :" ${QT_VERSION_MAJOR}) message("Qt version :" ${Qt6_VERSION})
if(QMFILES_AS_RESOURCE)
add_definitions(-DQMFILES_AS_RESOURCE)
endif()
+5 -2
View File
@@ -31,5 +31,8 @@ add_definitions(-DQT_MESSAGELOGCONTEXT)
# In order to do so, uncomment the following line. # In order to do so, uncomment the following line.
#add_definitions(-DTODO_LIST) #add_definitions(-DTODO_LIST)
# Build with KF5 # Build with KF6
option(BUILD_WITH_KF5 "Build with KF5" ON) option(BUILD_WITH_KF6 "Build with KF6" ON)
# Use translations as a Qt resource
option(QMFILES_AS_RESOURCE "Use .qm files as Qt resource" ON)
+26 -38
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -14,54 +14,42 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. # along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
message(" - fetch_kdeaddons") option(BUILD_KF6 "Build KF6 libraries, use system ones otherwise" OFF)
if(BUILD_KF6)
block(PROPAGATE KF6_GIT_TAG)
message(STATUS " - fetch_kdeaddons")
set(KDE_SKIP_TEST_SETTINGS ON)
set(KCOREADDONS_USE_QML OFF)
set(KWIDGETSADDONS_USE_QML OFF)
set(BUILD_TESTING OFF)
set(BUILD_DESIGNERPLUGIN OFF)
set(BUILD_QCH OFF)
set(BUILD_SHARED_LIBS OFF)
if(DEFINED BUILD_WITH_KF5) Include(FetchContent)
Include(FetchContent)
option(BUILD_KF5 "Build KF5 libraries, use system ones otherwise" YES) if(NOT DEFINED KF6_GIT_TAG)
set(KF6_GIT_TAG v6.22.0)
if(BUILD_KF5)
if(NOT DEFINED KF5_GIT_TAG)
#https://qelectrotech.org/forum/viewtopic.php?pid=13924#p13924
set(KF5_GIT_TAG v5.77.0)
endif() endif()
# Fix stop the run autotests of kcoreaddons
# see
# https://invent.kde.org/frameworks/kcoreaddons/-/blob/master/CMakeLists.txt#L98
# issue:
# CMake Error at /usr/share/ECM/modules/ECMAddTests.cmake:89 (add_executable):
# Cannot find source file:
# see
# https://qelectrotech.org/forum/viewtopic.php?pid=13929#p13929
set(KDE_SKIP_TEST_SETTINGS "TRUE")
set(BUILD_TESTING "0")
FetchContent_Declare(
ecm
GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git
GIT_TAG ${KF5_GIT_TAG})
FetchContent_MakeAvailable(ecm)
FetchContent_Declare( FetchContent_Declare(
kcoreaddons kcoreaddons
GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git
GIT_TAG ${KF5_GIT_TAG}) GIT_TAG ${KF6_GIT_TAG})
FetchContent_MakeAvailable(kcoreaddons) FetchContent_MakeAvailable(kcoreaddons)
FetchContent_Declare( FetchContent_Declare(
kwidgetsaddons kwidgetsaddons
GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git
GIT_TAG ${KF5_GIT_TAG}) GIT_TAG ${KF6_GIT_TAG})
FetchContent_MakeAvailable(kwidgetsaddons) FetchContent_MakeAvailable(kwidgetsaddons)
else() endblock()
find_package(KF5CoreAddons REQUIRED) else()
find_package(KF5WidgetsAddons REQUIRED) find_package(KF6CoreAddons REQUIRED)
endif() find_package(KF6WidgetsAddons REQUIRED)
set(KF5_PRIVATE_LIBRARIES
KF5::WidgetsAddons
KF5::CoreAddons
)
endif() endif()
set(KF6_PRIVATE_LIBRARIES
KF6::CoreAddons
KF6::WidgetsAddons
)
+4 -8
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -14,14 +14,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. # along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
message(" - fetch_pugixml") option(BUILD_PUGIXML "Build pugixml library, use system one otherwise" OFF)
Include(FetchContent)
option(BUILD_PUGIXML "Build pugixml library, use system one otherwise" YES)
if(BUILD_PUGIXML) if(BUILD_PUGIXML)
Include(FetchContent)
message(" - fetch pugixml")
FetchContent_Declare( FetchContent_Declare(
pugixml pugixml
GIT_REPOSITORY https://github.com/zeux/pugixml.git GIT_REPOSITORY https://github.com/zeux/pugixml.git
+2 -5
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -16,9 +16,6 @@
message(" - fetch_singleapplication") message(" - fetch_singleapplication")
# https://github.com/itay-grudev/SingleApplication/issues/18
#qmake
#DEFINES += QAPPLICATION_CLASS=QGuiApplication
set(QAPPLICATION_CLASS QApplication) set(QAPPLICATION_CLASS QApplication)
Include(FetchContent) Include(FetchContent)
@@ -26,6 +23,6 @@ Include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
SingleApplication SingleApplication
GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git
GIT_TAG v3.2.0) GIT_TAG v3.5.4)
FetchContent_MakeAvailable(SingleApplication) FetchContent_MakeAvailable(SingleApplication)
-25
View File
@@ -1,25 +0,0 @@
# Copyright 2006 The QElectroTech Team
# This file is part of QElectroTech.
#
# QElectroTech 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.
#
# QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
if(${CMAKE_VERSION} VERSION_LESS 3.14)
message(
"_____________________________________________________________________")
message("to update Cmake on linux:")
message("https://github.com/Kitware/CMake/")
message("linux => cmake-3.19.1-Linux-x86_64.sh")
message(" sudo ./cmake.sh --prefix=/usr/local/ --exclude-subdir")
message("windows good luck :)")
endif()
+10 -12
View File
@@ -17,6 +17,8 @@
message(" - qet_compilation_vars") message(" - qet_compilation_vars")
set(QET_COMPONENTS set(QET_COMPONENTS
Core
Gui
LinguistTools LinguistTools
PrintSupport PrintSupport
Xml Xml
@@ -29,7 +31,6 @@ set(QET_COMPONENTS
set(QET_PRIVATE_LIBRARIES set(QET_PRIVATE_LIBRARIES
Qt::PrintSupport Qt::PrintSupport
Qt::Gui Qt::Gui
Qt::GuiPrivate # Required for QPdfEngine::drawHyperlink (PDF internal links)
Qt::Xml Qt::Xml
Qt::Svg Qt::Svg
Qt::Sql Qt::Sql
@@ -107,14 +108,16 @@ set(QET_RES_FILES
${QET_DIR}/sources/ui/configpage/generalconfigurationpage.ui ${QET_DIR}/sources/ui/configpage/generalconfigurationpage.ui
) )
set(QET_SRC_FILES set(QET_SRC_FILES
${QET_DIR}/sources/cli_export.cpp
${QET_DIR}/sources/cli_export.h
${QET_DIR}/sources/pdf_links.cpp
${QET_DIR}/sources/pdf_links.h
${QET_DIR}/sources/borderproperties.cpp ${QET_DIR}/sources/borderproperties.cpp
${QET_DIR}/sources/borderproperties.h ${QET_DIR}/sources/borderproperties.h
${QET_DIR}/sources/bordertitleblock.cpp ${QET_DIR}/sources/bordertitleblock.cpp
${QET_DIR}/sources/bordertitleblock.h ${QET_DIR}/sources/bordertitleblock.h
# ${QET_DIR}/sources/colorbutton.cpp
# ${QET_DIR}/sources/colorbutton.h
# ${QET_DIR}/sources/colorcombobox.cpp
# ${QET_DIR}/sources/colorcombobox.h
# ${QET_DIR}/sources/colorcomboboxdelegate.cpp
# ${QET_DIR}/sources/colorcomboboxdelegate.h
${QET_DIR}/sources/conductorautonumerotation.cpp ${QET_DIR}/sources/conductorautonumerotation.cpp
${QET_DIR}/sources/conductorautonumerotation.h ${QET_DIR}/sources/conductorautonumerotation.h
${QET_DIR}/sources/conductornumexport.cpp ${QET_DIR}/sources/conductornumexport.cpp
@@ -423,10 +426,6 @@ set(QET_SRC_FILES
${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.cpp ${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.cpp
${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.h ${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.h
${QET_DIR}/pugixml/src/pugiconfig.hpp
${QET_DIR}/pugixml/src/pugixml.cpp
${QET_DIR}/pugixml/src/pugixml.hpp
${QET_DIR}/sources/qetgraphicsitem/conductor.cpp ${QET_DIR}/sources/qetgraphicsitem/conductor.cpp
${QET_DIR}/sources/qetgraphicsitem/conductor.h ${QET_DIR}/sources/qetgraphicsitem/conductor.h
${QET_DIR}/sources/qetgraphicsitem/conductortextitem.cpp ${QET_DIR}/sources/qetgraphicsitem/conductortextitem.cpp
@@ -504,6 +503,7 @@ set(QET_SRC_FILES
${QET_DIR}/sources/SearchAndReplace/ui/replacefoliowidget.h ${QET_DIR}/sources/SearchAndReplace/ui/replacefoliowidget.h
${QET_DIR}/sources/SearchAndReplace/ui/searchandreplacewidget.cpp ${QET_DIR}/sources/SearchAndReplace/ui/searchandreplacewidget.cpp
${QET_DIR}/sources/SearchAndReplace/ui/searchandreplacewidget.h ${QET_DIR}/sources/SearchAndReplace/ui/searchandreplacewidget.h
${QET_DIR}/sources/svg/qetsvg.cpp ${QET_DIR}/sources/svg/qetsvg.cpp
${QET_DIR}/sources/svg/qetsvg.h ${QET_DIR}/sources/svg/qetsvg.h
@@ -742,19 +742,17 @@ set(TS_FILES
${QET_DIR}/lang/qet_mn.ts ${QET_DIR}/lang/qet_mn.ts
${QET_DIR}/lang/qet_nb.ts ${QET_DIR}/lang/qet_nb.ts
${QET_DIR}/lang/qet_nl.ts ${QET_DIR}/lang/qet_nl.ts
${QET_DIR}/lang/qet_nl_BE.ts ${QET_DIR}/lang/qet_nl_BE.ts
${QET_DIR}/lang/qet_no.ts ${QET_DIR}/lang/qet_no.ts
${QET_DIR}/lang/qet_pl.ts ${QET_DIR}/lang/qet_pl.ts
${QET_DIR}/lang/qet_pt.ts ${QET_DIR}/lang/qet_pt.ts
${QET_DIR}/lang/qet_pt_BR.ts ${QET_DIR}/lang/qet_pt_BR.ts
${QET_DIR}/lang/qet_ro.ts ${QET_DIR}/lang/qet_ro.ts
${QET_DIR}/lang/qet_rs.ts
${QET_DIR}/lang/qet_ru.ts ${QET_DIR}/lang/qet_ru.ts
${QET_DIR}/lang/qet_sk.ts ${QET_DIR}/lang/qet_sk.ts
${QET_DIR}/lang/qet_sl.ts ${QET_DIR}/lang/qet_sl.ts
${QET_DIR}/lang/qet_sr.ts ${QET_DIR}/lang/qet_sr.ts
${QET_DIR}/lang/qet_sv.ts ${QET_DIR}/lang/qet_sv.ts
${QET_DIR}/lang/qet_tr.ts ${QET_DIR}/lang/qet_tr.ts
${QET_DIR}/lang/qet_uk.ts
${QET_DIR}/lang/qet_zh.ts ${QET_DIR}/lang/qet_zh.ts
) )
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+205 -228
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+192 -216
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+191 -214
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+192 -215
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+192 -215
View File
File diff suppressed because it is too large Load Diff
+192 -215
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+191 -214
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+191 -214
View File
File diff suppressed because it is too large Load Diff
+194 -217
View File
File diff suppressed because it is too large Load Diff
+191 -214
View File
File diff suppressed because it is too large Load Diff
+191 -214
View File
File diff suppressed because it is too large Load Diff
+191 -214
View File
File diff suppressed because it is too large Load Diff
+192 -215
View File
File diff suppressed because it is too large Load Diff
+192 -215
View File
File diff suppressed because it is too large Load Diff
+192 -215
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+1438 -1489
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+101
View File
@@ -0,0 +1,101 @@
Compiler QElectroTech sous microsoft Windows 10 et 11 avec MSYS2
================================
Ce document décrit les étapes nécessaire afin de compilé QElectroTech sous Windows avec Qt6 et cmake en utilisant MSYS2.
# MSYS2
L'ensemble des outils nécessaire au développement et à la compilation de QElectroTech sous Windows sera installé par lintermédiaire de [MSYS2](https://www.msys2.org/). Cela comprend entre autre le framework [Qt6](https://www.qt.io/development/qt-framework/qt6), les outils cmake, les dépendances ([kde framework](https://develop.kde.org/docs/), [sqlite](https://sqlite.org/), [pugixml](https://pugixml.org/)), les outils de compilation [minGW](https://www.mingw-w64.org/)...
>Il sera nécessaire d'utiliser [winget](https://learn.microsoft.com/fr-fr/windows/package-manager/winget/), celui-ci est présent par défaut sous Windows 11, dans le cas de Windows 10, winget peut necessité d'être activé manuellement
# Installer GIT et MSYS2 avec winget
Avec power shell.
```
winget install Git.Git
```
puis
```
winget install MSYS2.MSYS2
```
## Mise à jour de MSYS2
Lors de la première utilisation de MSYS2 il est nécessaire de mettre celui-ci à jour.
Lancer "MSYS2 MSYS" depuis le menu démarré de Windows.
Une fenêtre avec un shell s'ouvre, dans celui-ci lancer la commande :
```
pacman -Syu
```
A la fin de la mise à jour MSYS2 MSYS se fermera automatiquement. Ouvrez le à nouveau et relancé la commande
```
pacman -Syu
```
## Installation des outils de devellopement
Toujours dans le shell MSYS2 MSYS lancer la commande suivante.
```
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-qt6-svg mingw-w64-ucrt-x86_64-qt6-base mingw-w64-ucrt-x86_64-sqlite3 mingw-w64-ucrt-x86_64-pugixml mingw-w64-ucrt-x86_64-kcoreaddons mingw-w64-ucrt-x86_64-kwidgetsaddons mingw-w64-ucrt-x86_64-extra-cmake-modules mingw-w64-ucrt-x86_64-gdb mingw-w64-ucrt-x86_64-qt6-translations mingw-w64-ucrt-x86_64-qt6-tools
```
> La quantité de paquets à installer est conséquent, en fonction de votre connexion internet cela peut prendre plusieurs dizaine de minute
L'ensemble des outils est mantenant installé 😀
# Installer Qt creator
Télécharger [l'installateur online de Qt](https://www.qt.io/development/download-qt-installer-oss) et lancer l'installation en suivant les indications de ce dernier.
>Dans le cas où vous comptez utilisé Qt Creator uniquement pour développez QElectroTech, lors de l'installation choisissez l'option "installation personnalisée" puis dans la page suivante sélectionné uniquement Qt Creator.
## Configurer Qt creator
Ouvrir Qt creator puis rendez vous dans "édition -> préférence -> kit"
### Versions de Qt
- Cliquer sur _ajouter_
- Renseigner _Chemin de qmake_ (exemple C:\\msys64\\ucrt64\\bin\\qmake.exe).
- Dans le champ _Nom :_ ajouter (msys2).
![](assets/windows_msys2_setup/qt_version.png)
### Compilateurs
- Cliquer sur _ajouter_ puis choisir _MinGW_.
- Renseigner _Emplacement du compilateur C_ (exemple C:\\msys64\\ucrt64\\bin\\g++.exe).
- Dans le champ _Nom :_ ajouter (msys2).
![](assets/windows_msys2_setup/compiler.png)
### Débogueurs
- Cliquer sur _ajouter_
- Renseigner _Chemin :_ (exemple C:\\msys64\\ucrt64\\bin\\gdb.exe).
- Dans le champ _Nom :_ ajouter (msys2).
![](assets/windows_msys2_setup/debugger.png)
### cmake
- Outils -> _Ajouter_
- Renseigner _Chemin :_ (exemple C:\\msys64\\ucrt64\\bin\\cmake.exe).
- Dans le champ _Nom :_ ajouter (msys2).
![](assets/windows_msys2_setup/cmake.png)
### KIT
Maintenant que tous les prérequis sont fait nous allons crée un kit utilisant les outils fournis par MSYS2. Cliquer sur _Ajouter_, un nouveau kit _manuel_ apparaît, nommer celui-ci par exemple _Qt6 msys2_ puis renseigner le compilateur, le débogueur, la version de Qt et Outils CMake en choisissant à chaque fois ceux que nous venons de créer.
puis cliquer sur _appliquer_.
![](assets/windows_msys2_setup/kit.png)
Bravo 🥳🥳 vous avez terminé l'installation de la totalité des outils de développement.
# Clonez le dépôts de QElectrotech
Clonez le dépôt de QElectroTech comme vous le faite habituellement, sinon utilisez les commandes suivante dans power shell.
Crée et/ou se rendre dans le dossier dans lequel vous voulez clonez le dépôt (dans l'exemple nous allons crée un dossier QElectroTech dans C:)
```
mkdir C:\QElectroTech
cd C:\QElectroTech
git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
```
Une fois le dépôt cloné lancer Qt creator puis choisir d'ouvrir un projet existant, en choisissant le _CMakeLists.txt_ se trouvant à la racine du projet QElectroTech, enfin dans l'assistant de création de projet choisir comme kit le kit que nous avons créer précédemment.
+13
View File
@@ -0,0 +1,13 @@
Compiler QElectroTech sous microsoft Windows 10 et 11
================================
Compiler QElectroTech pour et/ou sous Windows peut être effectué avec plusieurs méthode différente.
Ce document énumère uniquement les différentes méthode possible
N'est mentionné que les étapes nécessaire afin de compilé QElectroTech sous Windows avec Qt6 et cmake. Ce document ne traite pas la compilation avec Qt5 et qmake.
>QElectroTech 0.100 est la dernière version à utiliser Qt5. Les version suivante sont développé avec Qt6 et utilise cmake au lieu de qmake.
Il existe deux méthodes pour cela :
1. [Utiliser msys2 (méthode recommandé)](fr_window_build_msys2.md)
2. Télécharger et compiler l'ensemble des dépendances (non rédigé)
Submodule pugixml deleted from 5a1892b321
+1 -5
View File
@@ -230,11 +230,7 @@ RESOURCES += qelectrotech.qrc
TRANSLATIONS += lang/*.ts TRANSLATIONS += lang/*.ts
# Modules Qt utilises par l'application # Modules Qt utilises par l'application
QT += xml svg network sql widgets printsupport concurrent KWidgetsAddons KCoreAddons gui-private QT += xml svg network sql widgets printsupport concurrent KWidgetsAddons KCoreAddons
# Private Qt GUI headers (needed for QPdfEngine::drawHyperlink)
# gui-private should add this automatically, but some distros need it explicit
INCLUDEPATH += $$[QT_INSTALL_HEADERS]/QtGui/$$[QT_VERSION]/QtGui
# UI DESIGNER FILES AND GENERATION SOURCES FILES # UI DESIGNER FILES AND GENERATION SOURCES FILES
FORMS += $$files(sources/richtext/*.ui) \ FORMS += $$files(sources/richtext/*.ui) \
@@ -26,12 +26,7 @@
#include "xmlprojectelementcollectionitem.h" #include "xmlprojectelementcollectionitem.h"
#include <QFutureWatcher> #include <QFutureWatcher>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
#include <QtConcurrentMap> #include <QtConcurrentMap>
#else
#include <QtConcurrentRun>
#endif
/** /**
@brief ElementsCollectionModel::ElementsCollectionModel @brief ElementsCollectionModel::ElementsCollectionModel
Constructor Constructor
@@ -294,14 +289,14 @@ void ElementsCollectionModel::loadCollections(bool common_collection,
connect(watcher, &QFutureWatcher<void>::progressRangeChanged, connect(watcher, &QFutureWatcher<void>::progressRangeChanged,
this, &ElementsCollectionModel::loadingProgressRangeChanged); this, &ElementsCollectionModel::loadingProgressRangeChanged);
connect(watcher, &QFutureWatcher<void>::finished, connect(watcher, &QFutureWatcher<void>::finished,
this, &ElementsCollectionModel::loadingFinished); this, &ElementsCollectionModel::loadingFinished);
connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater); connect(
watcher,
&QFutureWatcher<void>::finished,
watcher,
&QFutureWatcher<void>::deleteLater);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
m_future = QtConcurrent::map(m_items_list_to_setUp, setUpData); m_future = QtConcurrent::map(m_items_list_to_setUp, setUpData);
#else
qDebug() << "Help code for QT 6 or later";
#endif
watcher->setFuture(m_future); watcher->setFuture(m_future);
} }
@@ -835,14 +835,8 @@ void ElementsCollectionWidget::search()
} }
hideCollection(true); hideCollection(true);
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
const QStringList text_list = text.split("+", QString::SkipEmptyParts);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
const QStringList text_list = text.split("+", Qt::SkipEmptyParts); const QStringList text_list = text.split("+", Qt::SkipEmptyParts);
#endif
QModelIndexList match_index; QModelIndexList match_index;
for (QString txt : text_list) { for (QString txt : text_list) {
match_index << m_model->match(m_showed_index.isValid() match_index << m_model->match(m_showed_index.isValid()
@@ -803,13 +803,13 @@ bool ElementsLocation::setXml(const QDomDocument &xml_document) const
QString path_ = collectionPath(false); QString path_ = collectionPath(false);
QRegularExpression rx("^(.*)/(.*\\.elmt)$"); QRegularExpression rx("^(.*)/(.*\\.elmt)$");
if (rx.exactMatch(path_)) if (auto regex_match = rx.match(path_); regex_match.hasMatch())
{ {
return project() return project()
->embeddedElementCollection() ->embeddedElementCollection()
->addElementDefinition( ->addElementDefinition(
rx.cap(1), regex_match.captured(1),
rx.cap(2), regex_match.captured(2),
xml_document.documentElement()); xml_document.documentElement());
} }
else else
@@ -20,7 +20,7 @@
#include "../NameList/nameslist.h" #include "../NameList/nameslist.h"
#include "../diagramcontext.h" #include "../diagramcontext.h"
#include "pugixml/src/pugixml.hpp" #include "pugixml.hpp"
#include <QIcon> #include <QIcon>
#include <QString> #include <QString>
@@ -87,11 +87,7 @@ void ElementsTreeView::startElementDrag(const ElementsLocation &location)
{ {
if (! location.exist()) return; if (! location.exist()) return;
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) auto drag = new QDrag{this};
QDrag* drag = new QDrag(this);
#else
QScopedPointer<QDrag> drag(new QDrag(this));
#endif
QString location_str = location.toString(); QString location_str = location.toString();
QMimeData *mime_data = new QMimeData(); QMimeData *mime_data = new QMimeData();
@@ -361,7 +361,7 @@ void FileElementCollectionItem::setUpIcon()
setIcon(QET::Icons::Folder); setIcon(QET::Icons::Folder);
} else { } else {
if (m_path.endsWith(".qetmak")) { if (m_path.endsWith(".qetmak")) {
setIcon(QIcon()); setIcon(QET::Icons::PartRectangle);
} else { } else {
ElementsLocation loc(collectionPath()); ElementsLocation loc(collectionPath());
setIcon(loc.icon()); setIcon(loc.icon());
+1 -1
View File
@@ -17,7 +17,7 @@
*/ */
#ifndef NAMES_LIST_H #ifndef NAMES_LIST_H
#define NAMES_LIST_H #define NAMES_LIST_H
#include "pugixml/src/pugixml.hpp" #include "pugixml.hpp"
#include <QtXml> #include <QtXml>
/** /**
@@ -18,6 +18,7 @@
#include "terminalstripdrawer.h" #include "terminalstripdrawer.h"
#include <QPainter> #include <QPainter>
#include <QHash>
namespace TerminalStripDrawer { namespace TerminalStripDrawer {
+1 -6
View File
@@ -130,12 +130,7 @@ bool PhysicalTerminal::setLevelOf(const QSharedPointer<RealTerminal> &terminal,
const int i = m_real_terminal.indexOf(terminal); const int i = m_real_terminal.indexOf(terminal);
if (i >= 0) if (i >= 0)
{ {
#if QT_VERSION >= QT_VERSION_CHECK(5,14,0) m_real_terminal.swapItemsAt(i, std::min(static_cast<qsizetype>(level), m_real_terminal.size()-1));
m_real_terminal.swapItemsAt(i, std::min(level, m_real_terminal.size()-1));
#else
auto j = std::min(level, m_real_terminal.size()-1);
std::swap(m_real_terminal.begin()[i], m_real_terminal.begin()[j]);
#endif
return true; return true;
} }
return false; return false;
+1 -4
View File
@@ -64,11 +64,8 @@ bool TerminalStripData::fromXml(const QDomElement &xml_element)
"due to wrong tag name. Expected " << this->xmlTagName() << " used " << xml_element.tagName(); "due to wrong tag name. Expected " << this->xmlTagName() << " used " << xml_element.tagName();
return false; return false;
} }
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
m_uuid = QUuid::fromString(xml_element.attribute(QStringLiteral("uuid"))); m_uuid = QUuid::fromString(xml_element.attribute(QStringLiteral("uuid")));
#else
m_uuid = QUuid(xml_element.attribute(QStringLiteral("uuid")));
#endif
for (auto &xml_info : for (auto &xml_info :
QETXML::findInDomElement(xml_element.firstChildElement(QStringLiteral("informations")), QETXML::findInDomElement(xml_element.firstChildElement(QStringLiteral("informations")),
@@ -35,11 +35,7 @@ TerminalStripTreeDockWidget::TerminalStripTreeDockWidget(QETProject *project, QW
ui->setupUi(this); ui->setupUi(this);
setProject(project); setProject(project);
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex()); ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex());
#else
ui->m_tree_view->expandAll();
#endif
} }
TerminalStripTreeDockWidget::~TerminalStripTreeDockWidget() TerminalStripTreeDockWidget::~TerminalStripTreeDockWidget()
@@ -93,11 +89,7 @@ void TerminalStripTreeDockWidget::reload()
buildTree(); buildTree();
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex()); ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex());
#else
ui->m_tree_view->expandAll();
#endif
//Reselect the tree widget item of the current edited strip //Reselect the tree widget item of the current edited strip
auto item = m_item_strip_H.key(current_); auto item = m_item_strip_H.key(current_);
-4
View File
@@ -55,11 +55,7 @@ BorderTitleBlock::BorderTitleBlock(QObject *parent) :
m_titleblock_template_renderer = new TitleBlockTemplateRenderer(this); m_titleblock_template_renderer = new TitleBlockTemplateRenderer(this);
m_titleblock_template_renderer -> setTitleBlockTemplate(QETApp::defaultTitleBlockTemplate()); m_titleblock_template_renderer -> setTitleBlockTemplate(QETApp::defaultTitleBlockTemplate());
// disable the QPicture-based cache from Qt 4.8 to avoid rendering errors and crashes
#if QT_VERSION < QT_VERSION_CHECK(4, 8, 0) // ### Qt 6: remove
#else
m_titleblock_template_renderer -> setUseCache(false); m_titleblock_template_renderer -> setUseCache(false);
#endif
// dimensions par defaut du schema // dimensions par defaut du schema
importBorder(BorderProperties()); importBorder(BorderProperties());
-825
View File
@@ -1,825 +0,0 @@
/*
Copyright 2006-2025 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cli_export.h"
#include "bordertitleblock.h"
#include "conductornumexport.h"
#include "conductorproperties.h"
#include "dataBase/projectdatabase.h"
#include "diagram.h"
#include "diagramcontext.h"
#include "pdf_links.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/terminal.h"
#include "qetproject.h"
#include "titleblockproperties.h"
#include "wiringlistexport.h"
// Private Qt PDF engine for drawHyperlink() — see pdf_links / projectprintwindow.
#include <private/qpdf_p.h>
#include <QDir>
#include <QDirIterator>
#include <QDomDocument>
#include <QDate>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>
#include <QPageLayout>
#include <QPair>
#include <QPainter>
#include <QPdfWriter>
#include <QSet>
#include <QSqlError>
#include <QSqlQuery>
#include <QSvgGenerator>
#include <QTextStream>
#include <QTransform>
namespace {
QTextStream out(stdout);
QTextStream err(stderr);
/// All CLI option flags, mapped to a short format name.
const QHash<QString, QString> &exportFlags()
{
static const QHash<QString, QString> flags {
{"--export-pdf", "pdf"},
{"--export-png", "png"},
{"--export-svg", "svg"},
{"--export-cables", "cables"},
{"--export-wires", "wires"},
{"--export-bom", "bom"},
{"--export-nets", "nets"},
{"--export-links", "links"},
{"--info", "info"},
{"--check-elements", "check"},
{"--resave", "resave"},
{"--set-titleblock", "settb"},
};
return flags;
}
/// Device tag of an element ("K1", "Q55"), falling back to its name.
QString elementLabel(Element *element)
{
const QString label = element->elementInformations()["label"].toString();
return label.isEmpty() ? element->name() : label;
}
/// Pixel rect of a diagram's border + title block (the printable page area).
QRect diagramRect(Diagram *diagram)
{
QRectF r = diagram->border_and_titleblock.borderAndTitleBlockRect();
r.adjust(0, 0, 1, 1); // include the 1px border line
return r.toAlignedRect();
}
/// A filesystem-safe per-diagram file stem: "01_Title".
QString diagramStem(Diagram *diagram, int index)
{
QString title = diagram->title();
title.replace(QRegularExpression("[^\\w \\-]"), "_");
title = title.simplified();
if (title.isEmpty())
title = "diagram";
return QStringLiteral("%1_%2")
.arg(index, 2, 10, QChar('0'))
.arg(title);
}
/// Render @p diagram into @p painter, fitting @p target to the page rect.
void renderDiagram(Diagram *diagram, QPainter &painter, const QRectF &target)
{
const QRect source = diagramRect(diagram);
// Export without the editor grid: drawBackground() only paints it when
// draw_grid_ is set (default true), so toggle it off around the render
// and restore it afterwards.
const bool was_drawing_grid = diagram->displayGrid();
diagram->setDisplayGrid(false);
diagram->render(&painter, target, source, Qt::KeepAspectRatio);
diagram->setDisplayGrid(was_drawing_grid);
}
int exportPdf(QETProject &project, const QString &output)
{
const QList<Diagram *> diagrams = project.diagrams();
if (diagrams.isEmpty()) {
err << "No diagrams to export.\n";
return 1;
}
// Page numbers (1-based) for cross-reference hyperlink targets: each
// diagram is exactly one page in the CLI export (no tiling).
QMap<Diagram *, int> pageMap;
for (int i = 0; i < diagrams.size(); ++i)
pageMap.insert(diagrams.at(i), i + 1);
QPdfWriter writer(output);
writer.setCreator("QElectroTech");
writer.setResolution(96);
QPainter painter;
bool first = true;
for (Diagram *diagram : diagrams) {
const QRect r = diagramRect(diagram);
// Match the page to the diagram (in points: 1px @ 96dpi = 0.75pt).
const QPageSize page(QSizeF(r.width() * 72.0 / 96.0,
r.height() * 72.0 / 96.0),
QPageSize::Point);
writer.setPageSize(page);
writer.setPageMargins(QMarginsF(0, 0, 0, 0));
if (first) {
if (!painter.begin(&writer)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
first = false;
} else {
writer.newPage();
}
const QRectF target(0, 0,
writer.width(), writer.height());
renderDiagram(diagram, painter, target);
// Inject clickable cross-reference / folio-report hyperlinks for this
// page. The geometry is rebuilt from the QPdfWriter (not a QPrinter):
// render() anchors the diagram top-left with KeepAspectRatio, and the
// page is sized to the diagram so the scale is ~1.
if (auto *engine = dynamic_cast<QPdfEngine *>(painter.paintEngine())) {
const QRectF source(r);
const qreal s = qMin(target.width() / source.width(),
target.height() / source.height());
QTransform fit;
fit.translate(target.x(), target.y());
fit.scale(s, s);
fit.translate(-source.x(), -source.y());
// Device pixels -> PDF points, replicating the engine's page matrix
// (72/resolution scale + Y flip; zero margins -> no paint offset).
const qreal pt_scale = 72.0 / writer.resolution();
const qreal fullH_pt = writer.pageLayout().fullRectPoints().height();
const bool fullPageMode =
(writer.pageLayout().mode() == QPageLayout::FullPageMode);
const QRect paintPx =
writer.pageLayout().paintRectPixels(writer.resolution());
PdfLinks::PageGeometry geom;
geom.sceneToDevice = fit;
geom.target = target;
geom.pageBounds = QRectF(0, 0, target.width(), target.height());
geom.devToPdf = [=](const QPointF &d) -> QPointF {
qreal dx = d.x(), dy = d.y();
if (!fullPageMode) { dx += paintPx.left(); dy += paintPx.top(); }
return QPointF(pt_scale * dx, fullH_pt - pt_scale * dy);
};
geom.sourceRectOf = [](Diagram *dg) {
return QRectF(diagramRect(dg));
};
PdfLinks::injectCrossRefLinks(engine, diagram, geom, pageMap, output);
}
}
painter.end();
// Rewrite the URI link annotations into native internal GoTo actions, so
// the cross-references jump inside the document in any PDF viewer.
PdfLinks::convertUriToGoTo(output);
out << "Exported " << diagrams.size() << " page(s) -> " << output << "\n";
return 0;
}
int exportImages(QETProject &project, const QString &format,
const QString &out_dir)
{
const QList<Diagram *> diagrams = project.diagrams();
if (diagrams.isEmpty()) {
err << "No diagrams to export.\n";
return 1;
}
QDir().mkpath(out_dir);
int index = 0;
for (Diagram *diagram : diagrams) {
++index;
const QRect r = diagramRect(diagram);
const QString path = QDir(out_dir).filePath(
diagramStem(diagram, index) + "." + format);
if (format == "svg") {
QSvgGenerator gen;
gen.setFileName(path);
gen.setSize(r.size());
gen.setViewBox(QRect(0, 0, r.width(), r.height()));
gen.setTitle(diagram->title());
QPainter painter(&gen);
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()));
painter.end();
} else { // png
QImage image(r.size(), QImage::Format_ARGB32);
image.fill(Qt::white);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing, true);
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()));
painter.end();
if (!image.save(path)) {
err << "Failed to write '" << path << "'.\n";
return 1;
}
}
out << " " << path << "\n";
}
out << "Exported " << diagrams.size() << " diagram(s) -> " << out_dir << "\n";
return 0;
}
int exportCsv(QETProject &project, const QString &format, const QString &output)
{
QString csv;
if (format == "cables") {
WiringListExport wle(&project, nullptr);
csv = wle.toCsvString();
} else { // wires
ConductorNumExport cne(&project, nullptr);
csv = cne.wiresNum();
}
if (csv.isEmpty()) {
err << "Nothing to export (empty list).\n";
return 1;
}
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << csv;
file.close();
out << "Exported " << format << " list -> " << output << "\n";
return 0;
}
/// Quote a field for CSV output (RFC-4180 style, ';' delimiter).
QString csvField(const QString &value)
{
if (value.contains(';') || value.contains('"')
|| value.contains('\n') || value.contains('\r')) {
QString v = value;
v.replace('"', "\"\"");
return '"' % v % '"';
}
return value;
}
/// Bill of materials: one row per element, key component-data fields.
/// Pulls from QET's own project database (the same source as the GUI BOM
/// export), so the output matches what the editor produces.
int exportBom(QETProject &project, const QString &output)
{
// The project database is built lazily; force a (re)build before querying.
project.dataBase()->updateDB();
static const QStringList columns {
"label", "designation", "manufacturer", "manufacturer_reference",
"quantity", "location", "function", "title", "folio"
};
QSqlQuery query = project.dataBase()->newQuery(
"SELECT " % columns.join(", ") %
" FROM element_nomenclature_view ORDER BY label");
if (!query.exec()) {
err << "BOM query failed: " << query.lastError().text() << "\n";
return 1;
}
QString csv = columns.join(";") % "\n";
int rows = 0;
while (query.next()) {
QStringList values;
for (int i = 0; i < columns.size(); ++i)
values << csvField(query.value(i).toString());
csv += values.join(";") % "\n";
++rows;
}
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << csv;
file.close();
out << "Exported " << rows << " component(s) -> " << output << "\n";
return 0;
}
/// Count terminals on @p element that no conductor connects to.
int freeTerminals(Element *element)
{
int free = 0;
const QList<Terminal *> terminals = element->terminals();
for (Terminal *t : terminals)
if (t->conductorsCount() == 0)
++free;
return free;
}
/// Structural ground-truth dump of a project, as JSON, to stdout (or a file).
/// Uses QET's own loaded model, so it reports what the editor actually sees:
/// per-page element / conductor counts and unconnected terminals.
int exportInfo(QETProject &project, const QString &output)
{
const QList<Diagram *> diagrams = project.diagrams();
int total_elements = 0, total_conductors = 0, total_free = 0;
QJsonArray pages;
int index = 0;
for (Diagram *diagram : diagrams) {
++index;
const QList<Element *> elements = diagram->elements();
const int conductors = diagram->conductors().size();
int page_free = 0;
for (Element *e : elements)
page_free += freeTerminals(e);
const QRect r = diagramRect(diagram);
QJsonObject page;
page["index"] = index;
page["title"] = diagram->title();
page["folio"] = QStringLiteral("%1 of %2")
.arg(index).arg(diagrams.size());
page["width_px"] = r.width();
page["height_px"] = r.height();
page["elements"] = elements.size();
page["conductors"] = conductors;
page["free_terminals"] = page_free;
pages.append(page);
total_elements += elements.size();
total_conductors += conductors;
total_free += page_free;
}
QJsonObject root;
root["project"] = project.title();
root["diagrams"] = diagrams.size();
root["elements"] = total_elements;
root["conductors"] = total_conductors;
root["free_terminals"] = total_free;
root["pages"] = pages;
const QByteArray json =
QJsonDocument(root).toJson(QJsonDocument::Indented);
if (output.isEmpty()) {
out << QString::fromUtf8(json);
} else {
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
file.write(json);
file.close();
out << "Wrote project info -> " << output << "\n";
}
return 0;
}
/// Validate one .elmt file against QET's element schema.
/// @return 0 = OK, 1 = warning (loads but suspicious), 2 = failure.
int checkOneElement(const QString &path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
out << "FAIL " << path << " (cannot open)\n";
return 2;
}
QDomDocument doc;
QString error;
int line = 0;
if (!doc.setContent(&file, &error, &line)) {
file.close();
out << "FAIL " << path << " (XML error line "
<< line << ": " << error << ")\n";
return 2;
}
file.close();
const QDomElement root = doc.documentElement();
if (root.tagName() != "definition" || root.attribute("type") != "element") {
out << "FAIL " << path << " (root is not <definition type=\"element\">)\n";
return 2;
}
bool w_ok = false, h_ok = false;
const double w = root.attribute("width").toDouble(&w_ok);
const double h = root.attribute("height").toDouble(&h_ok);
if (!w_ok || !h_ok || w == 0 || h == 0) {
out << "FAIL " << path << " (missing/zero bounding box "
<< root.attribute("width") << "x"
<< root.attribute("height") << ")\n";
return 2;
}
const int terminals = root.elementsByTagName("terminal").count();
// Negative dimensions are malformed but QET still loads them; surface as a
// warning rather than a failure so this agrees with QET's own loader.
if (w < 0 || h < 0) {
out << "WARN " << path << " (negative bounding box "
<< w << "x" << h << ", " << terminals << " terminals)\n";
return 1;
}
if (terminals == 0) {
out << "WARN " << path << " (loads, but 0 terminals)\n";
return 1;
}
out << "OK " << path << " (" << terminals << " terminals)\n";
return 0;
}
/// Validate a single .elmt file or every .elmt under a directory.
int checkElements(const QString &path)
{
QStringList files;
const QFileInfo info(path);
if (info.isDir()) {
QDirIterator it(path, {"*.elmt"}, QDir::Files,
QDirIterator::Subdirectories);
while (it.hasNext())
files << it.next();
files.sort();
} else if (info.isFile()) {
files << path;
} else {
err << "Not found: " << path << "\n";
return 2;
}
if (files.isEmpty()) {
err << "No .elmt files found under: " << path << "\n";
return 2;
}
int warnings = 0, failures = 0;
for (const QString &f : files) {
const int r = checkOneElement(f);
if (r == 1) ++warnings;
else if (r == 2) ++failures;
}
out << files.size() << " file(s), " << warnings
<< " warning(s), " << failures << " failure(s)\n";
return failures > 0 ? 1 : 0;
}
/// Map every element in the project to its 1-based folio (page) position.
QHash<Element *, int> folioIndex(QETProject &project)
{
QHash<Element *, int> folio;
int index = 0;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams) {
++index;
const QList<Element *> elements = diagram->elements();
for (Element *e : elements)
folio.insert(e, index);
}
return folio;
}
/// Electrical nets: groups of terminals joined into one potential.
/// Walks QET's own potential graph, so each net is a connected component
/// of terminals across all folios. The ground truth for connectivity.
int exportNets(QETProject &project, const QString &output)
{
const QHash<Element *, int> folio = folioIndex(project);
QList<Conductor *> all_conductors;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams)
all_conductors << diagram->conductors();
QSet<Conductor *> visited;
QJsonArray nets;
int net_no = 0;
for (Conductor *c : all_conductors) {
if (visited.contains(c))
continue;
// The whole potential this conductor belongs to. relatedPotential-
// Conductors() also fills t_list with every terminal in the net
// (following folio reports and terminal blocks too).
QList<Terminal *> t_list;
QSet<Conductor *> group = c->relatedPotentialConductors(true, &t_list);
group.insert(c);
for (Conductor *g : group)
visited.insert(g);
if (c->terminal1) t_list << c->terminal1;
if (c->terminal2) t_list << c->terminal2;
// Wire number: smallest non-empty conductor text (deterministic).
QStringList wire_nos;
for (Conductor *g : group)
if (!g->properties().text.isEmpty())
wire_nos << g->properties().text;
wire_nos.sort();
++net_no;
QJsonArray terminals;
QSet<Terminal *> seen;
for (Terminal *t : t_list) {
if (!t || seen.contains(t))
continue;
seen.insert(t);
Element *pe = t->parentElement();
QJsonObject to;
to["element"] = pe ? elementLabel(pe) : QString();
to["terminal"] = t->name();
to["folio"] = pe ? folio.value(pe, 0) : 0;
terminals.append(to);
}
QJsonObject net;
net["net"] = net_no;
net["wire_no"] = wire_nos.value(0);
net["terminals"] = terminals;
nets.append(net);
}
QJsonObject root;
root["project"] = project.title();
root["nets"] = nets.size();
root["list"] = nets;
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
file.close();
out << "Exported " << nets.size() << " net(s) -> " << output << "\n";
return 0;
}
/// Cross-references: each linkable element (coil / contact / report) and the
/// elements it links to, flagging masters/slaves with no link as unresolved.
int exportLinks(QETProject &project, const QString &output)
{
const QHash<Element *, int> folio = folioIndex(project);
QString csv("element;link_type;linked_to;folio;status\n");
int linkable = 0, unresolved = 0;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams) {
const QList<Element *> elements = diagram->elements();
for (Element *e : elements) {
if (e->linkType() == Element::Simple)
continue;
++linkable;
const QList<Element *> linked = e->linkedElements();
QStringList names;
for (Element *le : linked)
names << elementLabel(le) % "(f"
% QString::number(folio.value(le, 0)) % ")";
QString status = "linked";
if ((e->linkType() == Element::Master
|| e->linkType() == Element::Slave)
&& linked.isEmpty()) {
status = "UNRESOLVED";
++unresolved;
}
csv += csvField(elementLabel(e)) % ";"
% e->linkTypeToString() % ";"
% csvField(names.join(", ")) % ";"
% QString::number(folio.value(e, 0)) % ";"
% status % "\n";
}
}
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << csv;
file.close();
out << "Exported " << linkable << " linkable element(s), "
<< unresolved << " unresolved -> " << output << "\n";
return 0;
}
/// Round-trip: load the project and write its XML back out, so an external
/// diff can reveal markup QET silently normalises (tolerated-but-invalid XML).
int resaveProject(QETProject &project, const QString &output)
{
const QDomDocument doc = project.toXml();
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << doc.toString(4);
file.close();
out << "Re-saved project -> " << output << "\n";
return 0;
}
/// Stamp title-block fields onto every folio (and the project default), then
/// save. Each assignment is "key=value". Standard keys map to the documented
/// title-block fields; "date=today" uses the current date; any other key is
/// stored as a custom title-block field. Aimed at CI/revision workflows
/// (e.g. set revision + date before exporting a new revision).
int setTitleBlock(QETProject &project, const QString &output,
const QStringList &assignments)
{
if (assignments.isEmpty()) {
err << "No field assignments given (expected key=value).\n";
return 2;
}
// Parse "key=value" assignments up front so a bad one fails before writing.
QList<QPair<QString, QString>> fields;
for (const QString &a : assignments) {
const int eq = a.indexOf('=');
if (eq <= 0) {
err << "Bad assignment '" << a << "' (expected key=value).\n";
return 2;
}
const QString key = a.left(eq);
const QString val = a.mid(eq + 1);
if (key.compare("date", Qt::CaseInsensitive) == 0
&& val.compare("today", Qt::CaseInsensitive) != 0
&& !QDate::fromString(val, Qt::ISODate).isValid()) {
err << "Bad date '" << val << "' (expected YYYY-MM-DD or 'today').\n";
return 2;
}
fields << qMakePair(key, val);
}
auto apply = [&](TitleBlockProperties &p) {
for (const auto &f : fields) {
const QString k = f.first.toLower();
const QString &v = f.second;
if (k == "title") p.title = v;
else if (k == "author") p.author = v;
else if (k == "filename") p.filename = v;
else if (k == "plant") p.plant = v;
else if (k == "location") p.locmach = v;
else if (k == "revision") p.indexrev = v;
else if (k == "version") p.version = v;
else if (k == "date") {
p.date = (v.compare("today", Qt::CaseInsensitive) == 0)
? QDate::currentDate()
: QDate::fromString(v, Qt::ISODate);
// An explicit date is only honoured when the folio is in
// "use the date value" mode (not "now"/"null").
p.useDate = TitleBlockProperties::UseDateValue;
}
else // unknown key -> custom title-block field
p.context.addValue(f.first, v);
}
};
// Project default (the template applied to new folios).
TitleBlockProperties def = project.defaultTitleBlockProperties();
apply(def);
project.setDefaultTitleBlockProperties(def);
// Every existing folio's own title block.
int folios = 0;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams) {
TitleBlockProperties p =
diagram->border_and_titleblock.exportTitleBlock();
apply(p);
diagram->border_and_titleblock.importTitleBlock(p);
++folios;
}
const QDomDocument doc = project.toXml();
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << doc.toString(4);
file.close();
out << "Stamped " << fields.size() << " field(s) on "
<< folios << " folio(s) -> " << output << "\n";
return 0;
}
} // anonymous namespace
namespace CLIExport {
bool isExportRequest(const QStringList &args)
{
for (const QString &a : args)
if (exportFlags().contains(a))
return true;
return false;
}
int run(const QStringList &args)
{
QString flag;
QStringList rest;
for (int i = 0; i < args.size(); ++i) {
if (exportFlags().contains(args.at(i))) {
flag = args.at(i);
for (int j = i + 1; j < args.size(); ++j)
rest << args.at(j);
break;
}
}
const QString format = exportFlags().value(flag);
// --check-elements operates on an element file/directory, not a project.
if (format == "check") {
if (rest.isEmpty()) {
err << "Usage: qelectrotech --check-elements "
"<element.elmt | directory>\n";
return 2;
}
return checkElements(rest.at(0));
}
const QString input = rest.value(0);
if (input.isEmpty()) {
err << "Usage: qelectrotech " << flag << " <project.qet> <output>\n";
return 2;
}
if (!QFileInfo::exists(input)) {
err << "Project not found: " << input << "\n";
return 2;
}
QETProject project(input);
if (project.state() != QETProject::Ok) {
err << "Failed to open project: " << input
<< " (state " << project.state() << ")\n";
return 1;
}
// --info writes JSON to stdout, or to an optional output file.
if (format == "info")
return exportInfo(project, rest.value(1));
const QString output = rest.value(1);
if (output.isEmpty()) {
err << "Usage: qelectrotech " << flag
<< " <project.qet> <output>\n";
return 2;
}
if (format == "pdf")
return exportPdf(project, output);
if (format == "cables" || format == "wires")
return exportCsv(project, format, output);
if (format == "bom")
return exportBom(project, output);
if (format == "nets")
return exportNets(project, output);
if (format == "links")
return exportLinks(project, output);
if (format == "resave")
return resaveProject(project, output);
if (format == "settb")
return setTitleBlock(project, output, rest.mid(2));
return exportImages(project, format, output);
}
} // namespace CLIExport
-79
View File
@@ -1,79 +0,0 @@
/*
Copyright 2006-2025 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLI_EXPORT_H
#define CLI_EXPORT_H
#include <QStringList>
/**
@brief Headless command-line export.
Implements the long-requested batch/headless export
(qelectrotech.org bugtracker #171, GitHub #309): render a project's
diagrams to files without opening the GUI.
Detected and handled in main() before the GUI is created.
*/
namespace CLIExport {
/**
@brief True if @p args request a CLI export
(i.e. contain one of the export options).
*/
bool isExportRequest(const QStringList &args);
/**
@brief Run the CLI export described by @p args.
@return process exit code (0 on success).
Usage:
qelectrotech --export-pdf <project.qet> <output.pdf>
qelectrotech --export-png <project.qet> <output_dir>
qelectrotech --export-svg <project.qet> <output_dir>
qelectrotech --export-cables <project.qet> <output.csv>
qelectrotech --export-wires <project.qet> <output.csv>
qelectrotech --export-bom <project.qet> <output.csv>
qelectrotech --export-nets <project.qet> <output.json>
qelectrotech --export-links <project.qet> <output.csv>
qelectrotech --info <project.qet> [output.json]
qelectrotech --check-elements <element.elmt | directory>
qelectrotech --resave <project.qet> <output.qet>
qelectrotech --set-titleblock <project.qet> <output.qet> key=value...
PDF: one multi-page document (one diagram per page).
PNG/SVG: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
cables: wiring list (one row per conductor) as CSV.
wires: list of distinct wire numbers as CSV.
bom: bill of materials (one row per element) as CSV.
nets: electrical nets (connected-terminal groups) as JSON.
links: element cross-references (coil/contact) as CSV, with
unresolved links flagged.
info: structural project summary as JSON (stdout, or a file) —
per-page element / conductor counts and unconnected terminals.
check-elements: validate .elmt file(s) against the element schema.
resave: load and rewrite the project XML (round-trip integrity).
set-titleblock: stamp title-block fields onto every folio, then save.
Keys: title, author, date (or date=today), plant, location,
revision, version, filename; any other key becomes a custom
field. E.g. --set-titleblock in.qet out.qet revision=B date=today
*/
int run(const QStringList &args);
}
#endif // CLI_EXPORT_H
-7
View File
@@ -72,14 +72,7 @@ bool ConductorNumExport::toCsv()
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) if (file.open(QIODevice::WriteOnly | QIODevice::Text))
{ {
QTextStream stream(&file); QTextStream stream(&file);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // ### Qt 6: remove
stream << wiresNum() << endl;
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.15 or later")
#endif
stream << wiresNum() << &Qt::endl(stream); stream << wiresNum() << &Qt::endl(stream);
#endif
} }
else { else {
return false; return false;
-7
View File
@@ -811,14 +811,7 @@ void ConductorProperties::readStyle(const QString &style_string) {
if (style_string.isEmpty()) return; if (style_string.isEmpty()) return;
// recupere la liste des couples style / valeur // recupere la liste des couples style / valeur
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
QStringList styles = style_string.split(";", QString::SkipEmptyParts);
#else
#if TODO_LIST
#pragma message("@TODO remove code QString::SkipEmptyParts for QT 5.14 or later")
#endif
QStringList styles = style_string.split(";", Qt::SkipEmptyParts); QStringList styles = style_string.split(";", Qt::SkipEmptyParts);
#endif
QRegularExpression Rx("^(?<name>[a-z-]+): (?<value>[a-z-]+)$"); QRegularExpression Rx("^(?<name>[a-z-]+): (?<value>[a-z-]+)$");
if (!Rx.isValid()) if (!Rx.isValid())
+2 -2
View File
@@ -383,7 +383,7 @@ void projectDataBase::createElementNomenclatureView()
"ei.supplier_auxiliary4 AS supplier_auxiliary4," "ei.supplier_auxiliary4 AS supplier_auxiliary4,"
"ei.quantity_auxiliary4 AS quantity_auxiliary4," "ei.quantity_auxiliary4 AS quantity_auxiliary4,"
"ei.unity_auxiliary4 AS unity_auxiliary4," "ei.unity_auxiliary4 AS unity_auxiliary4,"
"ei.exclude_from_bom AS exclude_from_bom,"
"d.pos AS diagram_position," "d.pos AS diagram_position,"
"e.type AS element_type," "e.type AS element_type,"
@@ -392,7 +392,7 @@ void projectDataBase::createElementNomenclatureView()
"di.folio AS folio," "di.folio AS folio,"
"e.pos AS position " "e.pos AS position "
" FROM element_info ei, diagram_info di, element e, diagram d" " FROM element_info ei, diagram_info di, element e, diagram d"
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid AND (ei.exclude_from_bom IS NOT 'true')"); " WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid");
QSqlQuery query(m_data_base); QSqlQuery query(m_data_base);
if (!query.exec(create_view)) { if (!query.exec(create_view)) {
+2 -13
View File
@@ -47,14 +47,8 @@ ElementQueryWidget::ElementQueryWidget(QWidget *parent) :
m_button_group.addButton(ui->m_coil_cb, 4); m_button_group.addButton(ui->m_coil_cb, 4);
m_button_group.addButton(ui->m_protection_cb, 5); m_button_group.addButton(ui->m_protection_cb, 5);
m_button_group.addButton(ui->m_thumbnail_cb, 6); m_button_group.addButton(ui->m_thumbnail_cb, 6);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // ### Qt 6: remove
connect(&m_button_group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), [this](int id)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.15 or later")
#endif
connect(&m_button_group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::idClicked), [this](int id) connect(&m_button_group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::idClicked), [this](int id)
#endif
{ {
auto check_box = static_cast<QCheckBox *>(m_button_group.button(0)); auto check_box = static_cast<QCheckBox *>(m_button_group.button(0));
if (id == 0) if (id == 0)
@@ -373,11 +367,6 @@ QString ElementQueryWidget::queryStr() const
where.clear(); where.clear();
} }
QString exclude_condition = "(exclude_from_bom IS NULL OR exclude_from_bom != '1')";
filter_ += " AND " + exclude_condition;
// -------------------------------------------------------------
if (where.isEmpty() && !filter_.isEmpty()) { if (where.isEmpty() && !filter_.isEmpty()) {
filter_.remove(0, 4); //Remove the first " AND" of filter. filter_.remove(0, 4); //Remove the first " AND" of filter.
filter_.prepend( " WHERE"); filter_.prepend( " WHERE");
@@ -461,7 +450,7 @@ void ElementQueryWidget::setUpItems()
{ {
for(QString key : QETInformation::elementInfoKeys()) for(QString key : QETInformation::elementInfoKeys())
{ {
if (key == "formula" || key == "exclude_from_bom") if (key == "formula")
continue; continue;
auto item = new QListWidgetItem(QETInformation::translatedInfoKey(key), ui->m_var_list); auto item = new QListWidgetItem(QETInformation::translatedInfoKey(key), ui->m_var_list);
-9
View File
@@ -1514,14 +1514,6 @@ bool Diagram::fromXml(QDomElement &document,
if (content_ptr) { if (content_ptr) {
content_ptr -> m_elements = added_elements; content_ptr -> m_elements = added_elements;
content_ptr -> m_conductors_to_move = added_conductors; content_ptr -> m_conductors_to_move = added_conductors;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
content_ptr -> m_text_fields = added_texts.toSet();
content_ptr -> m_images = added_images.toSet();
content_ptr -> m_shapes = added_shapes.toSet();
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
content_ptr -> m_text_fields = QSet<IndependentTextItem *>( content_ptr -> m_text_fields = QSet<IndependentTextItem *>(
added_texts.begin(), added_texts.begin(),
added_texts.end()); added_texts.end());
@@ -1532,7 +1524,6 @@ bool Diagram::fromXml(QDomElement &document,
added_shapes.begin(), added_shapes.begin(),
added_shapes.end()); added_shapes.end());
content_ptr->m_terminal_strip.swap(added_strips); content_ptr->m_terminal_strip.swap(added_strips);
#endif
content_ptr->m_tables.swap(added_tables); content_ptr->m_tables.swap(added_tables);
} }
+2 -3
View File
@@ -142,11 +142,10 @@ class Diagram : public QGraphicsScene
void wheelEvent (QGraphicsSceneWheelEvent *event) override; void wheelEvent (QGraphicsSceneWheelEvent *event) override;
void keyPressEvent (QKeyEvent *event) override; void keyPressEvent (QKeyEvent *event) override;
void keyReleaseEvent (QKeyEvent *) override; void keyReleaseEvent (QKeyEvent *) override;
public:
void correctTextPos(Element* elmt); void correctTextPos(Element* elmt);
void restoreText(Element* elmt); void restoreText(Element* elmt);
public:
QUuid uuid(); QUuid uuid();
void setEventInterface (DiagramEventInterface *event_interface); void setEventInterface (DiagramEventInterface *event_interface);
void clearEventInterface(); void clearEventInterface();
+1 -1
View File
@@ -17,7 +17,7 @@
*/ */
#ifndef DIAGRAM_CONTEXT_H #ifndef DIAGRAM_CONTEXT_H
#define DIAGRAM_CONTEXT_H #define DIAGRAM_CONTEXT_H
#include "pugixml/src/pugixml.hpp" #include "pugixml.hpp"
#include <QDomElement> #include <QDomElement>
#include <QHash> #include <QHash>
+2 -47
View File
@@ -210,17 +210,10 @@ void DiagramView::handleElementDrop(QDropEvent *event)
return; return;
} }
QPointF drop_pos;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
drop_pos = mapToScene(event->pos());
#else
drop_pos = event->position();
#endif
if (location.path().endsWith(".qetmak")) { if (location.path().endsWith(".qetmak")) {
diagram()->setEventInterface(new DiagramEventAddMacro(location, diagram(), drop_pos)); diagram()->setEventInterface(new DiagramEventAddMacro(location, diagram(), event->position()));
} else { } else {
diagram()->setEventInterface(new DiagramEventAddElement(location, diagram(), drop_pos)); diagram()->setEventInterface(new DiagramEventAddElement(location, diagram(), event->position()));
} }
//Set focus to the view to get event //Set focus to the view to get event
@@ -290,17 +283,8 @@ void DiagramView::handleTextDrop(QDropEvent *e) {
iti -> setHtml (e -> mimeData() -> text()); iti -> setHtml (e -> mimeData() -> text());
} }
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
m_diagram->undoStack().push(new AddGraphicsObjectCommand(
iti, m_diagram, mapToScene(e->pos())));
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
m_diagram->undoStack().push(new AddGraphicsObjectCommand( m_diagram->undoStack().push(new AddGraphicsObjectCommand(
iti, m_diagram, e->position())); iti, m_diagram, e->position()));
#endif
} }
/** /**
@@ -458,14 +442,7 @@ void DiagramView::mousePressEvent(QMouseEvent *e)
if (m_event_interface && m_event_interface->mousePressEvent(e)) return; if (m_event_interface && m_event_interface->mousePressEvent(e)) return;
//Start drag view when hold the middle button //Start drag view when hold the middle button
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
if (e->button() == Qt::MidButton)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
if (e->button() == Qt::MiddleButton) if (e->button() == Qt::MiddleButton)
#endif
{ {
m_drag_last_pos = e->pos(); m_drag_last_pos = e->pos();
viewport()->setCursor(Qt::ClosedHandCursor); viewport()->setCursor(Qt::ClosedHandCursor);
@@ -515,14 +492,7 @@ void DiagramView::mouseMoveEvent(QMouseEvent *e)
if (m_event_interface && m_event_interface->mouseMoveEvent(e)) return; if (m_event_interface && m_event_interface->mouseMoveEvent(e)) return;
// Drag the view // Drag the view
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
if (e->buttons() == Qt::MidButton)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
if (e->buttons() == Qt::MiddleButton) if (e->buttons() == Qt::MiddleButton)
#endif
{ {
QScrollBar *h = horizontalScrollBar(); QScrollBar *h = horizontalScrollBar();
QScrollBar *v = verticalScrollBar(); QScrollBar *v = verticalScrollBar();
@@ -583,14 +553,7 @@ void DiagramView::mouseReleaseEvent(QMouseEvent *e)
if (m_event_interface && m_event_interface->mouseReleaseEvent(e)) return; if (m_event_interface && m_event_interface->mouseReleaseEvent(e)) return;
// Stop drag view // Stop drag view
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
if (e->button() == Qt::MidButton)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
if (e->button() == Qt::MiddleButton) if (e->button() == Qt::MiddleButton)
#endif
{ {
viewport()->setCursor(Qt::ArrowCursor); viewport()->setCursor(Qt::ArrowCursor);
} }
@@ -624,14 +587,7 @@ void DiagramView::mouseReleaseEvent(QMouseEvent *e)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->addAction(act); menu->addAction(act);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
menu->popup(e->globalPos());
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
menu->popup(e->pos()); menu->popup(e->pos());
#endif
} }
m_free_rubberbanding = false; m_free_rubberbanding = false;
@@ -1355,7 +1311,6 @@ void DiagramView::createTemplateFromSelection()
QFile file(full_path); QFile file(full_path);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file); QTextStream out(&file);
out.setCodec("UTF-8");
out << macro_doc.toString(4); out << macro_doc.toString(4);
file.close(); file.close();
qDebug() << "Template successfully saved to:" << full_path; qDebug() << "Template successfully saved to:" << full_path;
-14
View File
@@ -276,14 +276,7 @@ void ChangeZValueCommand::applyRaise(const QList<QGraphicsItem *> &items_list) {
for (int i = my_items_list.count() - 2 ; i >= 0 ; -- i) { for (int i = my_items_list.count() - 2 ; i >= 0 ; -- i) {
if (my_items_list[i] -> isSelected()) { if (my_items_list[i] -> isSelected()) {
if (!my_items_list[i +1] -> isSelected()) { if (!my_items_list[i +1] -> isSelected()) {
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) // ### Qt 6: remove
my_items_list.swap(i, i + 1);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.13 or later")
#endif
my_items_list.swapItemsAt(i, i + 1); my_items_list.swapItemsAt(i, i + 1);
#endif
} }
} }
} }
@@ -301,14 +294,7 @@ void ChangeZValueCommand::applyLower(const QList<QGraphicsItem *> &items_list) {
for (int i = 1 ; i < my_items_list.count() ; ++ i) { for (int i = 1 ; i < my_items_list.count() ; ++ i) {
if (my_items_list[i] -> isSelected()) { if (my_items_list[i] -> isSelected()) {
if (!my_items_list[i - 1] -> isSelected()) { if (!my_items_list[i - 1] -> isSelected()) {
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) // ### Qt 6: remove
my_items_list.swap(i, i - 1);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.13 or later")
#endif
my_items_list.swapItemsAt(i, i - 1); my_items_list.swapItemsAt(i, i - 1);
#endif
} }
} }
} }
-21
View File
@@ -372,14 +372,7 @@ ElementContent ElementView::pasteWithOffset(const QDomDocument &xml_document) {
*/ */
void ElementView::mousePressEvent(QMouseEvent* e) void ElementView::mousePressEvent(QMouseEvent* e)
{ {
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
if (e->button() == Qt::MidButton)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
if (e->button() == Qt::MiddleButton) if (e->button() == Qt::MiddleButton)
#endif
{ {
setCursor( (Qt::ClosedHandCursor)); setCursor( (Qt::ClosedHandCursor));
reference_view_ = e->pos(); reference_view_ = e->pos();
@@ -394,14 +387,7 @@ void ElementView::mousePressEvent(QMouseEvent* e)
*/ */
void ElementView::mouseMoveEvent(QMouseEvent* e) void ElementView::mouseMoveEvent(QMouseEvent* e)
{ {
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
if (e->buttons() == Qt::MidButton)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
if (e->buttons() == Qt::MiddleButton) if (e->buttons() == Qt::MiddleButton)
#endif
{ {
QScrollBar *h = horizontalScrollBar(); QScrollBar *h = horizontalScrollBar();
QScrollBar *v = verticalScrollBar(); QScrollBar *v = verticalScrollBar();
@@ -420,14 +406,7 @@ void ElementView::mouseMoveEvent(QMouseEvent* e)
*/ */
void ElementView::mouseReleaseEvent(QMouseEvent* e) void ElementView::mouseReleaseEvent(QMouseEvent* e)
{ {
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
if (e->button() == Qt::MidButton)
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
if (e->button() == Qt::MiddleButton) if (e->button() == Qt::MiddleButton)
#endif
{ {
setCursor(Qt::ArrowCursor); setCursor(Qt::ArrowCursor);
adjustSceneRect(); adjustSceneRect();
@@ -519,14 +519,7 @@ void CustomElementGraphicPart::stylesFromXml(const QDomElement &qde)
resetStyles(); resetStyles();
//Get the list of pair style/value //Get the list of pair style/value
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
QStringList styles = qde.attribute("style").split(";", QString::SkipEmptyParts);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
QStringList styles = qde.attribute("style").split(";", Qt::SkipEmptyParts); QStringList styles = qde.attribute("style").split(";", Qt::SkipEmptyParts);
#endif
//Check each pair of style //Check each pair of style
QRegularExpression rx("^\\s*([a-z-]+)\\s*:\\s*([a-zA-Z-]+)\\s*$"); QRegularExpression rx("^\\s*([a-z-]+)\\s*:\\s*([a-zA-Z-]+)\\s*$");
-6
View File
@@ -313,12 +313,6 @@ void PartText::setPlainText(const QString &text) {
void PartText::setFont(const QFont &font) { void PartText::setFont(const QFont &font) {
if (font != this -> font()) { if (font != this -> font()) {
QGraphicsTextItem::setFont(font); QGraphicsTextItem::setFont(font);
// Re-anchor: the item's position transform is -margin(), and margin()
// depends on the font ascent. Without re-running this on a font change,
// the transform keeps the previous font's ascent — so the text renders
// at a different spot after save/reopen (the position recomputes from
// the saved font on load). See #158.
adjustItemPosition();
emit fontChanged(font); emit fontChanged(font);
} }
} }
@@ -291,11 +291,9 @@ void ElementPropertiesEditorWidget::on_m_base_type_cb_currentIndexChanged(int in
ui->m_master_gb->setVisible(master); ui->m_master_gb->setVisible(master);
ui->m_terminal_gb->setVisible(terminal); ui->m_terminal_gb->setVisible(terminal);
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
ui->tabWidget->setTabVisible(1, ui->tabWidget->setTabVisible(1,
(type_ == ElementData::Simple || (type_ == ElementData::Simple ||
type_ == ElementData::Master)); type_ == ElementData::Master));
#endif
updateTree(); updateTree();
} }
+1 -11
View File
@@ -124,17 +124,7 @@ void ElementDialog::setUpWidget()
} else if (m_mode == SaveTemplate) { } else if (m_mode == SaveTemplate) {
m_text_field->setPlaceholderText(tr("Nom du nouveau template")); m_text_field->setPlaceholderText(tr("Nom du nouveau template"));
} else { } else {
// This is the element's file name, not its display name: the field m_text_field->setPlaceholderText(tr("Nom du nouvel élément"));
// only accepts file-name characters (QFileNameEdit). The visible
// element name is edited separately in the element properties.
m_text_field->setPlaceholderText(
tr("Nom de fichier de l'élément",
"placeholder: the element's file name, not its display name"));
m_text_field->setToolTip(
tr("Nom de fichier de l'élément : chiffres, minuscules, « - », "
"« _ » et « . » uniquement.\nLe nom affiché de l'élément se "
"modifie séparément dans les propriétés de l'élément.",
"tooltip for the element file-name field"));
} }
layout->addWidget(m_text_field); layout->addWidget(m_text_field);
+3 -60
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "elementspanelwidget.h" #include "elementspanelwidget.h"
#include "diagram.h" #include "diagram.h"
#include "editor/ui/qetelementeditor.h" #include "editor/ui/qetelementeditor.h"
#include "elementscategoryeditor.h" #include "elementscategoryeditor.h"
@@ -25,7 +26,6 @@
#include "titleblock/templatedeleter.h" #include "titleblock/templatedeleter.h"
#include <QFileInfo> #include <QFileInfo>
#include <QMessageBox> #include <QMessageBox>
#include "qetgraphicsitem/element.h"
/* /*
When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel
@@ -59,8 +59,7 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) {
prj_close = new QAction(QET::Icons::DocumentClose, tr("Fermer ce projet"), this); prj_close = new QAction(QET::Icons::DocumentClose, tr("Fermer ce projet"), this);
prj_edit_prop = new QAction(QET::Icons::DialogInformation, tr("Propriétés du projet"), this); prj_edit_prop = new QAction(QET::Icons::DialogInformation, tr("Propriétés du projet"), this);
prj_prop_diagram = new QAction(QET::Icons::DialogInformation, tr("Propriétés du folio"), this); prj_prop_diagram = new QAction(QET::Icons::DialogInformation, tr("Propriétés du folio"), this);
prj_add_diagram = new QAction(QET::Icons::DiagramAdd, tr("Ajouter un folio"), this); prj_add_diagram = new QAction(QET::Icons::DiagramAdd, tr("Ajouter un folio"), this);
prj_duplicate_diagram = new QAction(QET::Icons::IC_CopyFile, tr("Copier et coller"), this);
prj_del_diagram = new QAction(QET::Icons::DiagramDelete, tr("Supprimer ce folio"), this); prj_del_diagram = new QAction(QET::Icons::DiagramDelete, tr("Supprimer ce folio"), this);
prj_move_diagram_up = new QAction(QET::Icons::GoUp, tr("Remonter ce folio"), this); prj_move_diagram_up = new QAction(QET::Icons::GoUp, tr("Remonter ce folio"), this);
prj_move_diagram_down = new QAction(QET::Icons::GoDown, tr("Abaisser ce folio"), this); prj_move_diagram_down = new QAction(QET::Icons::GoDown, tr("Abaisser ce folio"), this);
@@ -101,7 +100,6 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) {
connect(prj_prop_diagram, SIGNAL(triggered()), this, SLOT(editDiagramProperties())); connect(prj_prop_diagram, SIGNAL(triggered()), this, SLOT(editDiagramProperties()));
connect(prj_add_diagram, SIGNAL(triggered()), this, SLOT(newDiagram())); connect(prj_add_diagram, SIGNAL(triggered()), this, SLOT(newDiagram()));
connect(prj_del_diagram, SIGNAL(triggered()), this, SLOT(deleteDiagram())); connect(prj_del_diagram, SIGNAL(triggered()), this, SLOT(deleteDiagram()));
connect(prj_duplicate_diagram, SIGNAL(triggered()), this, SLOT(duplicateDiagram()));
connect(prj_move_diagram_up, SIGNAL(triggered()), this, SLOT(moveDiagramUp())); connect(prj_move_diagram_up, SIGNAL(triggered()), this, SLOT(moveDiagramUp()));
connect(prj_move_diagram_down, SIGNAL(triggered()), this, SLOT(moveDiagramDown())); connect(prj_move_diagram_down, SIGNAL(triggered()), this, SLOT(moveDiagramDown()));
connect(prj_move_diagram_top, SIGNAL(triggered()), this, SLOT(moveDiagramUpTop())); connect(prj_move_diagram_top, SIGNAL(triggered()), this, SLOT(moveDiagramUpTop()));
@@ -449,8 +447,7 @@ void ElementsPanelWidget::updateButtons()
} }
prj_del_diagram -> setEnabled(is_writable); prj_del_diagram -> setEnabled(is_writable);
prj_duplicate_diagram -> setEnabled(is_writable); prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
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_down -> setEnabled(is_writable && max_position < project_diagrams_count - 1);
prj_move_diagram_top -> setEnabled(is_writable && min_position > 0); prj_move_diagram_top -> setEnabled(is_writable && min_position > 0);
@@ -504,7 +501,6 @@ void ElementsPanelWidget::handleContextMenu(const QPoint &pos) {
case QET::Diagram: case QET::Diagram:
context_menu -> addAction(prj_prop_diagram); context_menu -> addAction(prj_prop_diagram);
context_menu -> addAction(prj_del_diagram); context_menu -> addAction(prj_del_diagram);
context_menu -> addAction(prj_duplicate_diagram);
context_menu -> addAction(prj_move_diagram_top); context_menu -> addAction(prj_move_diagram_top);
context_menu -> addAction(prj_move_diagram_upx10); context_menu -> addAction(prj_move_diagram_upx10);
context_menu -> addAction(prj_move_diagram_upx100); context_menu -> addAction(prj_move_diagram_upx100);
@@ -597,56 +593,3 @@ void ElementsPanelWidget::keyPressEvent(QKeyEvent *e) {
break; break;
} }
} }
/**
* Duplicates the selected folios (pages) along with their content
* and properties, and cleanly resolves cross-references.
*/
void ElementsPanelWidget::duplicateDiagram()
{
QList<Diagram *> diagrams_to_duplicate = elements_panel->selectedDiagrams();
if (diagrams_to_duplicate.isEmpty()) return;
QETProject *project = diagrams_to_duplicate.first()->project();
if (!project || project->isReadOnly()) return;
for (Diagram *source_diagram : diagrams_to_duplicate) {
Diagram *new_diagram = project->addNewDiagram();
if (!new_diagram) continue;
QString template_name = source_diagram->border_and_titleblock.titleBlockTemplateName();
new_diagram->setTitleBlockTemplate(template_name);
TitleBlockProperties tbp = source_diagram->border_and_titleblock.exportTitleBlock();
new_diagram->border_and_titleblock.importTitleBlock(tbp);
BorderProperties bp = source_diagram->border_and_titleblock.exportBorder();
new_diagram->border_and_titleblock.importBorder(bp);
for (QGraphicsItem *item : source_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
source_diagram->correctTextPos(elmt);
}
}
QDomDocument doc = source_diagram->toXml();
QDomElement diagram_elmt = doc.documentElement();
for (QGraphicsItem *item : source_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
source_diagram->restoreText(elmt);
}
}
new_diagram->fromXml(diagram_elmt, QPointF(0, 0), false, nullptr);
for (QGraphicsItem *item : new_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
new_diagram->restoreText(elmt);
}
}
}
elements_panel->reload();
}
-2
View File
@@ -47,7 +47,6 @@ class ElementsPanelWidget : public QWidget {
*prj_prop_diagram, *prj_prop_diagram,
*prj_add_diagram, *prj_add_diagram,
*prj_del_diagram, *prj_del_diagram,
*prj_duplicate_diagram,
*prj_move_diagram_up, *prj_move_diagram_up,
*prj_move_diagram_top, *prj_move_diagram_top,
*prj_move_diagram_down, *prj_move_diagram_down,
@@ -89,7 +88,6 @@ class ElementsPanelWidget : public QWidget {
void editDiagramProperties(); void editDiagramProperties();
void newDiagram(); void newDiagram();
void deleteDiagram(); void deleteDiagram();
void duplicateDiagram();
void moveDiagramUp(); void moveDiagramUp();
void moveDiagramDown(); void moveDiagramDown();
void moveDiagramUpTop(); void moveDiagramUpTop();
@@ -567,14 +567,7 @@ void ElementPictureFactory::setPainterStyle(const QDomElement &dom, QPainter &pa
pen.setCapStyle(Qt::SquareCap); pen.setCapStyle(Qt::SquareCap);
//Get the couples style/value //Get the couples style/value
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
const QStringList styles = dom.attribute("style").split(";", QString::SkipEmptyParts);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
const QStringList styles = dom.attribute("style").split(";", Qt::SkipEmptyParts); const QStringList styles = dom.attribute("style").split(";", Qt::SkipEmptyParts);
#endif
QRegularExpression rx("^(?<name>[a-z-]+):(?<value>[a-zA-Z-]+)$"); QRegularExpression rx("^(?<name>[a-z-]+):(?<value>[a-zA-Z-]+)$");
if (!rx.isValid()) if (!rx.isValid())
+3 -9
View File
@@ -188,10 +188,8 @@ void MachineInfo::send_info_to_debug()
QDirIterator it1(QETApp::commonElementsDir().toLatin1(),nameFilters, QDir::Files, QDirIterator::Subdirectories); QDirIterator it1(QETApp::commonElementsDir().toLatin1(),nameFilters, QDir::Files, QDirIterator::Subdirectories);
while (it1.hasNext()) while (it1.hasNext())
{ {
if(it1.next() > 0 ) it1.next();
{
commomElementsDir ++; commomElementsDir ++;
}
} }
qInfo()<< " Common Elements count:"<< commomElementsDir << "Elements"; qInfo()<< " Common Elements count:"<< commomElementsDir << "Elements";
@@ -200,10 +198,8 @@ void MachineInfo::send_info_to_debug()
QDirIterator it2(QETApp::customElementsDir().toLatin1(), nameFilters, QDir::Files, QDirIterator::Subdirectories); QDirIterator it2(QETApp::customElementsDir().toLatin1(), nameFilters, QDir::Files, QDirIterator::Subdirectories);
while (it2.hasNext()) while (it2.hasNext())
{ {
if(it2.next() > 0 ) it2.next();
{
customElementsDir ++; customElementsDir ++;
}
} }
qInfo()<< " Custom Elements count:"<< customElementsDir << "Elements"; qInfo()<< " Custom Elements count:"<< customElementsDir << "Elements";
@@ -211,10 +207,8 @@ void MachineInfo::send_info_to_debug()
QDirIterator it3(QETApp::companyElementsDir().toLatin1(), nameFilters, QDir::Files, QDirIterator::Subdirectories); QDirIterator it3(QETApp::companyElementsDir().toLatin1(), nameFilters, QDir::Files, QDirIterator::Subdirectories);
while (it3.hasNext()) while (it3.hasNext())
{ {
if(it3.next() > 0 ) it3.next();
{
companyElementsDir ++; companyElementsDir ++;
}
} }
qInfo()<< " Company Elements count:"<< companyElementsDir << "Elements"; qInfo()<< " Company Elements count:"<< companyElementsDir << "Elements";
+2 -38
View File
@@ -15,17 +15,13 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "cli_export.h"
#include "machine_info.h" #include "machine_info.h"
#include "qet.h" #include "qet.h"
#include "qetapp.h" #include "qetapp.h"
#include "qetproject.h"
#include "singleapplication.h" #include "singleapplication.h"
#include "utils/macosxopenevent.h" #include "utils/macosxopenevent.h"
#include "utils/qetsettings.h" #include "utils/qetsettings.h"
#include <QApplication>
#include <QStyleFactory> #include <QStyleFactory>
#include <QtConcurrentRun> #include <QtConcurrentRun>
@@ -178,42 +174,10 @@ int main(int argc, char **argv)
QCoreApplication::setApplicationName("QElectroTech"); QCoreApplication::setApplicationName("QElectroTech");
//Creation and execution of the application //Creation and execution of the application
//HighDPI //HighDPI
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
#endif
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFactorRoundingPolicy());
#if QT_VERSION > QT_VERSION_CHECK(5, 7, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFactorRoundingPolicy());
#endif
// Headless command-line export: render a project to PDF/PNG/SVG without
// opening the GUI, then exit. Must be handled before SingleApplication
// (which would forward the args to an already-running instance).
{
QStringList raw_args;
for (int i = 0; i < argc; ++i)
raw_args << QString::fromLocal8Bit(argv[i]);
if (CLIExport::isExportRequest(raw_args)) {
QApplication export_app(argc, argv);
// No crash-recovery backups in one-shot CLI mode: the backup write
// runs on a background thread referencing the project and races the
// process exit (intermittent segfault in QET::writeToFile).
QETProject::setBackupEnabled(false);
return CLIExport::run(export_app.arguments());
}
}
SingleApplication app(argc, argv, true); SingleApplication app(argc, argv, true);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
-382
View File
@@ -1,382 +0,0 @@
/*
Copyright 2006-2025 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pdf_links.h"
#include "diagram.h"
#include "qetgraphicsitem/crossrefitem.h"
#include "qetgraphicsitem/dynamicelementtextitem.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/elementtextitemgroup.h"
// Private Qt PDF engine for drawHyperlink() — not public API, stable since Qt4.
// Requires QT += gui-private in qelectrotech.pro / gui-private in CMake.
#include <private/qpdf_p.h>
#include <QByteArray>
#include <QFile>
#include <QGraphicsTextItem>
#include <QList>
#include <QRegularExpression>
#include <QUrl>
#include <QVector>
namespace PdfLinks {
void injectCrossRefLinks(QPdfEngine *engine, Diagram *diagram,
const PageGeometry &geom,
const QMap<Diagram *, int> &pageMap,
const QString &outputFileName)
{
if (!engine || !diagram)
return;
const QTransform &fit = geom.sceneToDevice;
const QRectF &target = geom.target;
const QRectF &pageBounds = geom.pageBounds;
// Compute, in PDF points on its OWN page, the rectangle to frame for a
// target element (used as a /FitR destination so the link zooms onto it).
auto destRectPdf = [&](Element *tgt) -> QRectF {
Diagram *dg = tgt ? tgt->diagram() : nullptr;
if (!dg) return QRectF();
const QRectF srcT = geom.sourceRectOf(dg);
if (srcT.width() <= 0.0 || srcT.height() <= 0.0) return QRectF();
const qreal sT = qMin(target.width() / srcT.width(),
target.height() / srcT.height());
QTransform fitT;
fitT.translate(target.x(), target.y());
fitT.scale(sT, sT);
fitT.translate(-srcT.x(), -srcT.y());
QRectF elemScene = tgt->mapRectToScene(tgt->boundingRect());
// Frame the element with a little context, and enforce a minimum
// framed size so tiny contacts don't zoom in extremely.
const qreal pad = 25.0;
elemScene.adjust(-pad, -pad, pad, pad);
const qreal minSide = 160.0;
if (elemScene.width() < minSide)
elemScene.adjust(-(minSide - elemScene.width()) / 2.0, 0,
(minSide - elemScene.width()) / 2.0, 0);
if (elemScene.height() < minSide)
elemScene.adjust(0, -(minSide - elemScene.height()) / 2.0,
0, (minSide - elemScene.height()) / 2.0);
const QRectF devT = fitT.mapRect(elemScene);
const QPointF a = geom.devToPdf(devT.topLeft());
const QPointF b = geom.devToPdf(devT.bottomRight());
return QRectF(QPointF(qMin(a.x(), b.x()), qMin(a.y(), b.y())),
QPointF(qMax(a.x(), b.x()), qMax(a.y(), b.y())));
};
auto injectLink = [&](const QRectF &sceneRect, Element *targetElmt) {
if (!targetElmt || !targetElmt->diagram()) return;
const int targetPage = pageMap.value(targetElmt->diagram(), -1);
if (targetPage < 1) return;
const QRectF devRect = fit.mapRect(sceneRect);
if (!devRect.isValid() || !pageBounds.intersects(devRect)) return;
QString frag = QString("page=%1").arg(targetPage);
const QRectF d = destRectPdf(targetElmt); // /FitR L_B_R_T
if (d.isValid())
frag += QString("&fitr=%1_%2_%3_%4")
.arg(qRound(d.left())).arg(qRound(d.top()))
.arg(qRound(d.right())).arg(qRound(d.bottom()));
QUrl url = QUrl::fromLocalFile(outputFileName);
url.setFragment(frag);
engine->drawHyperlink(devRect, url);
};
for (auto *item : diagram->items()) {
// --- CrossRefItem links ---
if (auto *xref = dynamic_cast<CrossRefItem*>(item)) {
for (auto it = xref->hoveredContactsMap().begin();
it != xref->hoveredContactsMap().end(); ++it)
{
Element *targetElmt = it.key();
if (!targetElmt || !targetElmt->diagram()) continue;
// it.value() is in the CrossRefItem's LOCAL coords -> scene
injectLink(xref->mapRectToScene(it.value()), targetElmt);
}
continue;
}
// --- Folio report links (DynamicElementTextItem) ---
if (auto *deti = dynamic_cast<DynamicElementTextItem*>(item)) {
Element *parent = deti->parentElement();
if (!parent) continue;
// (a) Report element : label -> linked report on another folio
if (parent->linkType() & Element::AllReport) {
if (parent->linkedElements().isEmpty()) continue;
bool showsLabel =
(deti->textFrom() == DynamicElementTextItem::ElementInfo
&& deti->infoName() == QLatin1String("label")) ||
(deti->textFrom() == DynamicElementTextItem::CompositeText
&& deti->compositeText().contains(QStringLiteral("%{label}")));
if (!showsLabel) continue;
Element *targetElmt = parent->linkedElements().first();
if (!targetElmt || !targetElmt->diagram()) continue;
injectLink(deti->mapRectToScene(deti->boundingRect()), targetElmt);
continue;
}
// (b) Slave element : the "(folio-pos)" text -> master element
if (parent->linkType() == Element::Slave) {
QGraphicsTextItem *sx = deti->slaveXrefItem();
Element *master = deti->masterElement();
if (sx && master && master->diagram()) {
injectLink(sx->mapRectToScene(sx->boundingRect()), master);
}
continue;
}
continue;
}
// --- Slave cross-reference carried by a grouped text ---
if (auto *grp = dynamic_cast<ElementTextItemGroup*>(item)) {
Element *parent = grp->parentElement();
if (!parent || parent->linkType() != Element::Slave) continue;
if (parent->linkedElements().isEmpty()) continue;
QGraphicsTextItem *sx = grp->slaveXrefItem();
if (!sx) continue;
Element *master = parent->linkedElements().first();
if (!master || !master->diagram()) continue;
injectLink(sx->mapRectToScene(sx->boundingRect()), master);
continue;
}
}
}
void convertUriToGoTo(const QString &pdfPath)
{
// --- 1. Read raw bytes ---
QFile f(pdfPath);
if (!f.open(QIODevice::ReadOnly)) return;
QByteArray data = f.readAll();
f.close();
// --- 2. Collect page object numbers in document order ---
// Read them from the page tree (/Type /Pages -> /Kids [ N 0 R ... ]).
// This is reliable; scanning raw bytes for "/Type /Page" is NOT: that
// marker also occurs inside content streams, and a forward lookahead
// wrongly tags neighbouring objects (it found 280 "pages" for a 137-page
// document). Qt writes a single, flat /Kids array listing every page.
QVector<int> pageObjs;
{
int pagesPos = data.indexOf("/Type /Pages");
int kidsPos = (pagesPos == -1) ? -1 : data.indexOf("/Kids", pagesPos);
int lb = (kidsPos == -1) ? -1 : data.indexOf('[', kidsPos);
int rb = (lb == -1) ? -1 : data.indexOf(']', lb);
if (lb != -1 && rb != -1 && rb > lb) {
const QString kids =
QString::fromLatin1(data.mid(lb + 1, rb - lb - 1));
QRegularExpression re(QStringLiteral("(\\d+)\\s+\\d+\\s+R"));
auto it = re.globalMatch(kids);
while (it.hasNext()) {
int objNum = it.next().captured(1).toInt();
if (objNum > 0) pageObjs.append(objNum);
}
}
}
if (pageObjs.isEmpty()) return; // nothing to do
// --- 3. Replace URI annotations with GoTo ---
// Pattern (Qt always writes exactly this):
// /S /URI\n/URI (file:///...<anything>#page=N)\n
// or (older patches without file://):
// /S /URI\n/URI (page=N)\n
bool changed = false;
{
// We do a manual scan to handle variable-length replacements.
QByteArray out;
out.reserve(data.size());
const QByteArray sUri = "/S /URI\n/URI (";
const QByteArray sGoTo = "/S /GoTo\n/D [";
int pos = 0;
while (pos < data.size()) {
int found = data.indexOf(sUri, pos);
if (found == -1) {
out.append(data.mid(pos));
break;
}
// Copy everything up to the match
out.append(data.mid(pos, found - pos));
// Find closing ')' of the URI value
int uriStart = found + sUri.size();
int closeParen = data.indexOf(')', uriStart);
if (closeParen == -1) {
// Malformed — copy rest verbatim
out.append(data.mid(found));
pos = data.size();
break;
}
QByteArray uriVal = data.mid(uriStart, closeParen - uriStart);
// Extract page number: look for #page=N or bare page=N
int pageNum = -1;
int hashPos = uriVal.lastIndexOf("#page=");
int digitStart = -1;
if (hashPos != -1) {
digitStart = hashPos + 6;
} else if (uriVal.startsWith("page=")) {
digitStart = 5;
}
if (digitStart != -1) {
// Take only the leading digits: the fragment may carry extra
// parameters after the page number (e.g. "22&fitr=15_489_..."),
// and QByteArray::toInt() would fail on the whole remainder.
int e = digitStart;
while (e < uriVal.size()
&& uriVal[e] >= '0' && uriVal[e] <= '9')
++e;
if (e > digitStart)
pageNum = uriVal.mid(digitStart, e - digitStart).toInt();
}
if (pageNum >= 1 && pageNum <= pageObjs.size()) {
// Valid page reference — emit GoTo action.
int pageObjNum = pageObjs[pageNum - 1];
// Optional precise destination: &fitr=Left_Bottom_Right_Top
// (integer PDF points). If present -> /FitR (frame the element);
// otherwise -> /Fit (whole page, top).
QByteArray dest = " /Fit]";
int fr = uriVal.indexOf("fitr=");
if (fr != -1) {
QByteArray rest = uriVal.mid(fr + 5);
// stop at first char that is not part of the number list
int end = 0;
while (end < rest.size()
&& ((rest[end] >= '0' && rest[end] <= '9')
|| rest[end] == '_' || rest[end] == '-'))
++end;
QList<QByteArray> parts = rest.left(end).split('_');
if (parts.size() == 4) {
dest = " /FitR " + parts[0] + " " + parts[1] + " "
+ parts[2] + " " + parts[3] + "]";
}
}
QByteArray goTo = sGoTo
+ QByteArray::number(pageObjNum)
+ " 0 R" + dest;
out.append(goTo);
changed = true;
} else {
// Unknown page — keep original URI
out.append(sUri);
out.append(uriVal);
out.append(')');
}
pos = closeParen + 1; // skip past ')'
}
if (!changed) return; // nothing was replaced
data = out;
}
// --- 4. Rebuild xref table ---
// Find start of existing xref (last occurrence)
int xrefStart = data.lastIndexOf("\nxref\n");
if (xrefStart == -1) xrefStart = data.lastIndexOf("\nxref ");
if (xrefStart == -1) return; // malformed PDF
++xrefStart; // skip the leading '\n'
QByteArray body = data.left(xrefStart);
// Collect all object offsets from the body
QMap<int, int> offsets; // objNum -> byte offset
{
const QByteArray objMarker = " 0 obj";
int pos = 0;
while ((pos = body.indexOf(objMarker, pos)) != -1) {
int numStart = pos - 1;
while (numStart > 0 && body[numStart-1] != '\n' && body[numStart-1] != '\r')
--numStart;
QByteArray numStr = body.mid(numStart, pos - numStart).trimmed();
bool ok = false;
int objNum = numStr.toInt(&ok);
if (ok && objNum > 0)
offsets[objNum] = numStart;
++pos;
}
}
if (offsets.isEmpty()) return;
int maxObj = offsets.lastKey();
// Build xref table
QByteArray xref;
xref += "xref\n";
xref += "0 " + QByteArray::number(maxObj + 1) + "\n";
xref += "0000000000 65535 f \n";
for (int i = 1; i <= maxObj; ++i) {
if (offsets.contains(i)) {
xref += QByteArray::number(offsets[i]).rightJustified(10, '0')
+ " 00000 n \n";
} else {
xref += "0000000000 65535 f \n";
}
}
// Find trailer dict from the original xref section
int trailerPos = data.indexOf("trailer", xrefStart);
int trailerEnd = -1;
if (trailerPos != -1) {
trailerEnd = data.indexOf("%%EOF", trailerPos);
if (trailerEnd != -1) trailerEnd += 5;
}
QByteArray trailer;
if (trailerPos != -1 && trailerEnd != -1)
trailer = data.mid(trailerPos, trailerEnd - trailerPos);
else
trailer = "trailer\n<<>>\n%%EOF";
int newXrefOffset = body.size();
QByteArray result;
result.reserve(body.size() + xref.size() + trailer.size() + 30);
result += body;
result += xref;
result += trailer;
result += "\nstartxref\n";
result += QByteArray::number(newXrefOffset);
result += "\n%%EOF\n";
// --- 5. Write back ---
QFile out(pdfPath);
if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
out.write(result);
out.close();
}
} // namespace PdfLinks
-79
View File
@@ -1,79 +0,0 @@
/*
Copyright 2006-2025 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PDF_LINKS_H
#define PDF_LINKS_H
#include <QMap>
#include <QPointF>
#include <QRectF>
#include <QString>
#include <QTransform>
#include <functional>
class QPdfEngine;
class Diagram;
/**
Shared helper that turns a project's cross-references and folio reports
into clickable internal hyperlinks in a Qt-generated PDF. Used by both the
GUI print path (ProjectPrintWindow) and the headless CLI export, each of
which builds its own page geometry and passes it in — this code never
computes the scene-to-page mapping itself.
*/
namespace PdfLinks {
/**
Geometry mapping for one rendered PDF page. Each caller builds this
from its OWN page setup (printer page layout vs QPdfWriter), since the
device-pixel and point conversions differ between them.
*/
struct PageGeometry {
/// scene coordinates -> device pixels (the same "fit" render() applied)
QTransform sceneToDevice;
/// device paint rectangle, in pixels (the page area)
QRectF target;
/// links whose rectangle falls outside this are dropped
QRectF pageBounds;
/// device pixels -> PDF points (replicates the engine's page matrix)
std::function<QPointF(const QPointF &)> devToPdf;
/// a diagram -> its source rectangle in scene pixels (for /FitR framing)
std::function<QRectF(Diagram *)> sourceRectOf;
};
/**
Inject clickable cross-reference / folio-report hyperlinks for @p diagram
into the current page of @p engine. Each link is emitted as a URI
annotation encoding the target page and a /FitR rectangle;
convertUriToGoTo() then rewrites those into native internal GoTo actions.
*/
void injectCrossRefLinks(QPdfEngine *engine, Diagram *diagram,
const PageGeometry &geom,
const QMap<Diagram *, int> &pageMap,
const QString &outputFileName);
/**
Post-process a Qt-generated PDF file: rewrite every "/S /URI" link
annotation into a native internal "/S /GoTo" action (page + /FitR or
/Fit destination) and rebuild the xref table. No-op if the file has no
such annotations.
*/
void convertUriToGoTo(const QString &pdfPath);
}
#endif // PDF_LINKS_H
+35 -158
View File
@@ -18,20 +18,12 @@
#include "projectprintwindow.h" #include "projectprintwindow.h"
#include "../diagram.h" #include "../diagram.h"
#include "../pdf_links.h"
#include "../qeticons.h" #include "../qeticons.h"
#include "../qetproject.h" #include "../qetproject.h"
#include "../qetversion.h" #include "../qetversion.h"
#include "../qetgraphicsitem/crossrefitem.h"
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "../qetgraphicsitem/elementtextitemgroup.h"
#include "ui_projectprintwindow.h" #include "ui_projectprintwindow.h"
// Private Qt PDF engine for drawHyperlink() — not public API, stable since Qt4
// Requires QT += gui-private in qelectrotech.pro
#include <private/qpdf_p.h>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
# include <QDesktopWidget> # include <QDesktopWidget>
#else #else
@@ -45,11 +37,6 @@
#include <QPrintDialog> #include <QPrintDialog>
#include <QPrintPreviewWidget> #include <QPrintPreviewWidget>
#include <QScreen> #include <QScreen>
#include <QFile>
#include <QRegularExpression>
#include <QMap>
#include <QTimer>
#include <QVector>
/** /**
* @brief ProjectPrintWindow::ProjectPrintWindow * @brief ProjectPrintWindow::ProjectPrintWindow
@@ -203,71 +190,40 @@ ProjectPrintWindow::~ProjectPrintWindow()
*/ */
void ProjectPrintWindow::requestPaint() void ProjectPrintWindow::requestPaint()
{ {
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#ifdef QT_DEBUG auto screen = this->screen();
qDebug() << "--"; if(screen)
qDebug() << "DiagramPrintDialog::print printer_->resolution() before " << m_printer->resolution(); {
qDebug() << "DiagramPrintDialog::print screennumber " << QApplication::desktop()->screenNumber(); #ifdef QT_DEBUG
#endif qDebug() << "--";
qDebug() << "DiagramPrintDialog::print printer_->resolution() before " << m_printer->resolution();
qDebug() << "DiagramPrintDialog::print screennumber " << screen->name();
#endif
QScreen *srn = QApplication::screens().at(QApplication::desktop()->screenNumber()); qreal dotsPerInch = (qreal)screen->logicalDotsPerInch();
qreal dotsPerInch = (qreal)srn->logicalDotsPerInch(); m_printer->setResolution(dotsPerInch);
m_printer->setResolution(dotsPerInch);
#ifdef QT_DEBUG #ifdef QT_DEBUG
qDebug() << "DiagramPrintDialog::print dotsPerInch " << dotsPerInch; qDebug() << "DiagramPrintDialog::print dotsPerInch " << dotsPerInch;
qDebug() << "DiagramPrintDialog::print printer_->resolution() after" << m_printer->resolution(); qDebug() << "DiagramPrintDialog::print printer_->resolution() after" << m_printer->resolution();
qDebug() << "--"; qDebug() << "--";
#endif #endif
#endif }
#endif #endif
#endif
if (!m_project->diagrams().count()) { if (!m_project->diagrams().count()) {
return; return;
} }
// Build diagram -> first physical PDF page number map (1-based)
// Must be done before the print loop since page numbers depend on order
QMap<Diagram*, int> diagramPageMap;
{
int pageNum = 1;
for (auto diagram : selectedDiagram()) {
diagramPageMap.insert(diagram, pageNum);
// Each diagram may span multiple pages if not fit_page
if (!ui->m_fit_in_page_cb->isChecked()) {
auto option = exportProperties();
bool full_page = m_printer->fullPage();
int h = horizontalPagesCount(diagram, option, full_page);
int v = verticalPagesCount(diagram, option, full_page);
pageNum += h * v;
} else {
pageNum += 1;
}
}
}
bool first = true; bool first = true;
QPainter painter(m_printer); QPainter painter(m_printer);
// A real PDF export uses the QPdfEngine; the on-screen preview uses a
// preview paint engine. We only post-process when actually writing a PDF.
const bool pdfExport =
(m_printer->outputFormat() == QPrinter::PdfFormat)
&& (dynamic_cast<QPdfEngine*>(painter.paintEngine()) != nullptr);
for (auto diagram : selectedDiagram()) for (auto diagram : selectedDiagram())
{ {
first ? first = false : m_printer->newPage(); first ? first = false : m_printer->newPage();
printDiagram(diagram, ui->m_fit_in_page_cb->isChecked(), &painter, m_printer, diagramPageMap); printDiagram(diagram, ui->m_fit_in_page_cb->isChecked(), &painter, m_printer);
} }
// Note: do NOT call painter.end() or pdfConvertUriToGoTo() here.
// We are inside the paintRequested slot: the QPrintPreviewWidget still
// owns the paint cycle. On macOS arm64 (Metal/CALayer compositor),
// closing the QPainter manually inside this slot leaves the backing
// store in an undefined state, producing a black screen after export.
// pdfConvertUriToGoTo() is deferred to print() via QTimer::singleShot(0).
} }
/** /**
@@ -277,7 +233,7 @@ void ProjectPrintWindow::requestPaint()
* @param fit_page * @param fit_page
* @param printer * @param printer
*/ */
void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer, const QMap<Diagram*, int> &diagramPageMap) void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer)
{ {
////Prepare the print//// ////Prepare the print////
@@ -312,9 +268,9 @@ void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter
#if TODO_LIST #if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later") #pragma message("@TODO remove code for QT 6 or later")
#endif #endif
qDebug()<<"Help code for QT 6 or later"; qDebug()<<"Help code for QT 6 or later";
auto printed_rect = full_page ? printer->paperRect(QPrinter::Millimeter) : auto printed_rect = full_page ? printer->paperRect(QPrinter::Millimeter) :
printer->pageRect(QPrinter::Millimeter); printer->pageRect(QPrinter::Millimeter);
#endif #endif
auto used_width = printed_rect.width(); auto used_width = printed_rect.width();
auto used_height = printed_rect.height(); auto used_height = printed_rect.height();
@@ -364,65 +320,6 @@ void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter
} }
} }
////Inject PDF cross-reference links////
if (printer->outputFormat() == QPrinter::PdfFormat && fit_page) {
auto *pdfEngine = dynamic_cast<QPdfEngine*>(painter->paintEngine());
if (pdfEngine) {
// QGraphicsScene::render() fait save()/restore() : worldTransform()
// est revenu a l'identite ici. On reconstruit DONC explicitement la
// transform appliquee par :
// diagram->render(painter, QRectF(), diagram_rect, KeepAspectRatio)
// cible vide => painter->viewport() ; source = diagram_rect ; centre.
const QRectF target = QRectF(painter->viewport());
const QRectF source = QRectF(diagram_rect); // meme source que render()
// render() ANCRE en haut-gauche (pas de centrage) :
// translate(target.topLeft) . scale(s,s) . translate(-source.topLeft)
// On reproduit EXACTEMENT ca — surtout PAS de (target-source*s)/2.
const qreal s = qMin(target.width() / source.width(),
target.height() / source.height());
QTransform fit;
fit.translate(target.x(), target.y());
fit.scale(s, s);
fit.translate(-source.x(), -source.y()); // scene -> pixels device
// IMPORTANT : QPdfEngine::drawHyperlink() applique lui-meme
// pageMatrix() (echelle 72/resolution + inversion de Y + marges).
// On lui passe donc le rectangle en PIXELS DEVICE, sans aucune
// conversion en points ni flip de notre cote.
const QRectF pageBounds(0, 0, target.width(), target.height());
// ---- Device-pixels -> PDF points, replicating QPdfEnginePrivate::pageMatrix()
// (same geometry for every page: same printer, page size and margins). ----
const qreal pt_scale = 72.0 / printer->resolution();
const qreal fullH_pt = printer->pageLayout().fullRectPoints().height();
const bool fullPageMode =
(printer->pageLayout().mode() == QPageLayout::FullPageMode);
const QRect paintPx =
printer->pageLayout().paintRectPixels(printer->resolution());
auto devToPdf = [=](const QPointF &d) -> QPointF {
qreal dx = d.x(), dy = d.y();
if (!fullPageMode) { dx += paintPx.left(); dy += paintPx.top(); }
return QPointF(pt_scale * dx, fullH_pt - pt_scale * dy);
};
PdfLinks::PageGeometry geom;
geom.sceneToDevice = fit;
geom.target = target;
geom.pageBounds = pageBounds;
geom.devToPdf = devToPdf;
geom.sourceRectOf = [this](Diagram *dg) {
return QRectF(diagramRect(dg, exportProperties()));
};
PdfLinks::injectCrossRefLinks(
pdfEngine, diagram, geom, diagramPageMap,
printer->outputFileName());
}
}
////PDF links end////
////Print is finished, restore diagram and graphics item properties ////Print is finished, restore diagram and graphics item properties
for (auto view : diagram->views()) { for (auto view : diagram->views()) {
view->setInteractive(true); view->setInteractive(true);
@@ -447,7 +344,7 @@ QRect ProjectPrintWindow::diagramRect(Diagram *diagram, const ExportProperties &
diagram_rect.setHeight(diagram_rect.height() - titleblock_height); diagram_rect.setHeight(diagram_rect.height() - titleblock_height);
} }
//Adjust the border of diagram to 1px (width of the line) //Adjust the border of diagram to 1px (width of the line)
diagram_rect.adjust(0,0,1,1); diagram_rect.adjust(0,0,1,1);
return (diagram_rect.toAlignedRect()); return (diagram_rect.toAlignedRect());
@@ -462,7 +359,7 @@ QRect ProjectPrintWindow::diagramRect(Diagram *diagram, const ExportProperties &
* with the orientation and the paper format used by the actual printer * with the orientation and the paper format used by the actual printer
*/ */
int ProjectPrintWindow::horizontalPagesCount( int ProjectPrintWindow::horizontalPagesCount(
Diagram *diagram, const ExportProperties &option, bool full_page) const Diagram *diagram, const ExportProperties &option, bool full_page) const
{ {
QRect printable_area; QRect printable_area;
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove #if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
@@ -491,7 +388,7 @@ int ProjectPrintWindow::horizontalPagesCount(
* with the orientation and paper format used by the actual printer * with the orientation and paper format used by the actual printer
*/ */
int ProjectPrintWindow::verticalPagesCount( int ProjectPrintWindow::verticalPagesCount(
Diagram *diagram, const ExportProperties &option, bool full_page) const Diagram *diagram, const ExportProperties &option, bool full_page) const
{ {
QRect printable_area; QRect printable_area;
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove #if QT_VERSION < QT_VERSION_CHECK(5, 15, 1) // ### Qt 6: remove
@@ -617,7 +514,7 @@ void ProjectPrintWindow::loadPageSetupForCurrentPrinter()
QString value = settings.value("orientation", "landscape").toString(); QString value = settings.value("orientation", "landscape").toString();
m_printer->setPageOrientation( m_printer->setPageOrientation(
value == "landscape" ? QPageLayout::Landscape : value == "landscape" ? QPageLayout::Landscape :
QPageLayout::Portrait); QPageLayout::Portrait);
} }
if (settings.contains("papersize")) if (settings.contains("papersize"))
{ {
@@ -878,37 +775,17 @@ void ProjectPrintWindow::on_m_uncheck_all_clicked()
void ProjectPrintWindow::print() void ProjectPrintWindow::print()
{ {
const bool isPdf = (m_printer->outputFormat() == QPrinter::PdfFormat); m_preview->print();
const QString pdfFile = isPdf ? m_printer->outputFileName() : QString();
m_preview->print(); // triggers requestPaint() synchronously; painter
// is created/destroyed inside that call
savePageSetupForCurrentPrinter(); savePageSetupForCurrentPrinter();
this->close();
if (isPdf && !pdfFile.isEmpty()) {
// Defer post-processing and window close to the next event-loop
// iteration. This lets the macOS arm64 Metal compositor finish
// compositing the backing store before the window is destroyed,
// which prevents the black screen observed on Apple Silicon under
// macOS Sequoia (QPrintPreviewWidget + CALayer timing issue).
QTimer::singleShot(0, this, [this, pdfFile]() {
// Convert URI link annotations into native internal GoTo/FitR
// actions so cross-references jump inside the document.
PdfLinks::convertUriToGoTo(pdfFile);
this->close();
});
} else {
this->close();
}
} }
void ProjectPrintWindow::on_m_date_cb_userDateChanged(const QDate &date) void ProjectPrintWindow::on_m_date_cb_userDateChanged(const QDate &date)
{ {
auto index = ui->m_date_from_cb->currentIndex(); auto index = ui->m_date_from_cb->currentIndex();
// 0 = all date // 0 = all date
// 1 = from the date // 1 = from the date
// 2 = at the date // 2 = at the date
if (index) { on_m_uncheck_all_clicked(); } if (index) { on_m_uncheck_all_clicked(); }
else { on_m_check_all_pb_clicked(); } else { on_m_check_all_pb_clicked(); }
@@ -918,7 +795,7 @@ void ProjectPrintWindow::on_m_date_cb_userDateChanged(const QDate &date)
{ {
auto diagram_date = diagram->border_and_titleblock.date(); auto diagram_date = diagram->border_and_titleblock.date();
if ( (index == 1 && diagram_date >= date) || if ( (index == 1 && diagram_date >= date) ||
(index == 2 && diagram_date == date) ) (index == 2 && diagram_date == date) )
m_diagram_list_hash.value(diagram)->setChecked(true); m_diagram_list_hash.value(diagram)->setChecked(true);
} }
+1 -2
View File
@@ -21,7 +21,6 @@
#include "../exportproperties.h" #include "../exportproperties.h"
#include <QMainWindow> #include <QMainWindow>
#include <QMap>
#include <QPrinter> #include <QPrinter>
namespace Ui { namespace Ui {
@@ -80,7 +79,7 @@ class ProjectPrintWindow : public QMainWindow
private: private:
void requestPaint(); void requestPaint();
void printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer, const QMap<Diagram*, int> &diagramPageMap = {}); void printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer);
QRect diagramRect(Diagram *diagram, const ExportProperties &option) const; QRect diagramRect(Diagram *diagram, const ExportProperties &option) const;
int horizontalPagesCount(Diagram *diagram, const ExportProperties &option, bool full_page) const; int horizontalPagesCount(Diagram *diagram, const ExportProperties &option, bool full_page) const;
int verticalPagesCount(Diagram *diagram, const ExportProperties &option, bool full_page) const; int verticalPagesCount(Diagram *diagram, const ExportProperties &option, bool full_page) const;
+2 -33
View File
@@ -183,16 +183,7 @@ bool QET::orthogonalProjection(
// determine le point d'intersection des deux droites = le projete orthogonal // determine le point d'intersection des deux droites = le projete orthogonal
QPointF intersection_point; QPointF intersection_point;
#if TODO_LIST QLineF::IntersectType it = line.intersects(perpendicular_line, &intersection_point);
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
QLineF::IntersectType it = line.
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
intersect // ### Qt 6: remove
#else
intersects
#endif
(perpendicular_line, &intersection_point);
// ne devrait pas arriver (mais bon...) // ne devrait pas arriver (mais bon...)
if (it == QLineF::NoIntersection) return(false); if (it == QLineF::NoIntersection) return(false);
@@ -545,16 +536,8 @@ QString QET::joinWithSpaces(const QStringList &string_list) {
QStringList QET::splitWithSpaces(const QString &string) { QStringList QET::splitWithSpaces(const QString &string) {
// les chaines sont separees par des espaces non echappes // les chaines sont separees par des espaces non echappes
// = avec un nombre nul ou pair de backslashes devant // = avec un nombre nul ou pair de backslashes devant
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
QStringList escaped_strings = string.split(QRegularExpression("[^\\]?(?:\\\\)* "), QStringList escaped_strings = string.split(QRegularExpression("[^\\]?(?:\\\\)* "),
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove Qt::SkipEmptyParts);
QString
#else
Qt
#endif
::SkipEmptyParts);
QStringList returned_list; QStringList returned_list;
foreach(QString escaped_string, escaped_strings) { foreach(QString escaped_string, escaped_strings) {
@@ -684,14 +667,7 @@ bool QET::writeXmlFile(QDomDocument &xml_doc, const QString &filepath, QString *
} }
QTextStream out(&file); QTextStream out(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
out.setCodec("UTF-8");
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
out.setEncoding(QStringConverter::Utf8); out.setEncoding(QStringConverter::Utf8);
#endif
out.setGenerateByteOrderMark(false); out.setGenerateByteOrderMark(false);
out << xml_doc.toString(4); out << xml_doc.toString(4);
if (!file.commit()) if (!file.commit())
@@ -822,14 +798,7 @@ bool QET::writeToFile(QDomDocument &xml_doc, QFile *file, QString *error_message
QTextStream out(file); QTextStream out(file);
out.seek(0); out.seek(0);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
out.setCodec("UTF-8");
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
out.setEncoding(QStringConverter::Utf8); out.setEncoding(QStringConverter::Utf8);
#endif
out.setGenerateByteOrderMark(false); out.setGenerateByteOrderMark(false);
out << xml_doc.toString(4); out << xml_doc.toString(4);
if (opened_here) { if (opened_here) {
+16 -41
View File
@@ -204,14 +204,7 @@ void QETApp::setLanguage(const QString &desired_language) {
QString languages_path = languagesPath(); QString languages_path = languagesPath();
// load Qt library translations // load Qt library translations
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
QString qt_l10n_path = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
QString qt_l10n_path = QLibraryInfo::path(QLibraryInfo::TranslationsPath); QString qt_l10n_path = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
#endif
if (!qtTranslator.load("qt_" + desired_language, qt_l10n_path)) if (!qtTranslator.load("qt_" + desired_language, qt_l10n_path))
{ {
qWarning() << "failed to load" qWarning() << "failed to load"
@@ -226,20 +219,20 @@ void QETApp::setLanguage(const QString &desired_language) {
// load translations for the QET application // load translations for the QET application
// charge les traductions pour l'application QET // charge les traductions pour l'application QET
// desired_language may be a full locale such as "pt_BR": try that exact if (!qetTranslator.load("qet_" + desired_language, languages_path)) {
// translation, then the base language ("pt"), then fall back to English. /* in case of failure,
// French is the application's source language and needs no translation. * we fall back on the native channels for French
const QString base_language = desired_language.section('_', 0, 0); * en cas d'echec,
bool loaded = qetTranslator.load("qet_" + desired_language, languages_path); * on retombe sur les chaines natives pour le francais
if (!loaded && base_language != desired_language) */
loaded = qetTranslator.load("qet_" + base_language, languages_path); if (desired_language != "fr") {
if (!loaded && base_language != "fr") { // use of the English version by default
// use of the English version by default // utilisation de la version anglaise par defaut
// utilisation de la version anglaise par defaut if(!qetTranslator.load("qet_en", languages_path))
if(!qetTranslator.load("qet_en", languages_path)) qWarning() << "failed to load"
qWarning() << "failed to load" << "qet_en" << languages_path << "(" << __FILE__
<< "qet_en" << languages_path << "(" << __FILE__ << __LINE__ << __FUNCTION__ << ")";
<< __LINE__ << __FUNCTION__ << ")"; }
} }
qApp->installTranslator(&qetTranslator); qApp->installTranslator(&qetTranslator);
@@ -263,11 +256,7 @@ QString QETApp::langFromSetting()
QSettings settings; QSettings settings;
system_language = settings.value("lang", "system").toString(); system_language = settings.value("lang", "system").toString();
if(system_language == "system") { if(system_language == "system") {
// Keep the full locale (e.g. "pt_BR"), not just the base language system_language = QLocale::system().name().left(2);
// ("pt"): QET ships regional translations (pt_BR, nl_BE, nl_NL) and
// truncating here loaded the wrong one. setLanguage() falls back to
// the base language when no regional translation exists.
system_language = QLocale::system().name();
} }
lang_is_set = true; lang_is_set = true;
} }
@@ -1232,21 +1221,7 @@ QString QETApp::languagesPath()
* en l'absence d'option de compilation, on utilise le dossier lang, * en l'absence d'option de compilation, on utilise le dossier lang,
* situe a cote du binaire executable * situe a cote du binaire executable
*/ */
{ return(QCoreApplication::applicationDirPath() + "/lang/");
const QString bin_dir = QCoreApplication::applicationDirPath();
const QString next_to_bin = bin_dir + "/lang/";
// Some packagings (notably the Windows installer) put the binary in a
// "bin" subfolder while "lang" sits beside it (../lang). Fall back to
// that layout when the folder next to the binary is absent, so the
// translations are found without a --lang-dir argument. See issue #86.
if (!QDir(next_to_bin).exists()) {
const QString sibling_of_bin =
QDir::cleanPath(bin_dir + "/../lang") + "/";
if (QDir(sibling_of_bin).exists())
return(sibling_of_bin);
}
return(next_to_bin);
}
#else #else
#ifndef QET_LANG_PATH_RELATIVE_TO_BINARY_PATH #ifndef QET_LANG_PATH_RELATIVE_TO_BINARY_PATH
/* the compilation option represents /* the compilation option represents
@@ -451,16 +451,8 @@ void GraphicsTablePropertiesEditor::setUpEditConnection()
m_edit_connection << connect(ui->m_table_left_margin, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply); m_edit_connection << connect(ui->m_table_left_margin, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply);
m_edit_connection << connect(ui->m_table_right_margin, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply); m_edit_connection << connect(ui->m_table_right_margin, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply);
m_edit_connection << connect(ui->m_table_bottom_margin, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply); m_edit_connection << connect(ui->m_table_bottom_margin, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // ### Qt 6: remove m_edit_connection << connect(m_table_button_group, QOverload<int>::of(&QButtonGroup::idClicked), this, &GraphicsTablePropertiesEditor::apply);
m_edit_connection << connect(m_table_button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &GraphicsTablePropertiesEditor::apply); m_edit_connection << connect(m_header_button_group, QOverload<int>::of(&QButtonGroup::idClicked), this, &GraphicsTablePropertiesEditor::apply);
m_edit_connection << connect(m_header_button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &GraphicsTablePropertiesEditor::apply);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.15 or later")
#endif
m_edit_connection << connect(m_table_button_group, QOverload<int>::of(&QButtonGroup::idClicked), this, &GraphicsTablePropertiesEditor::apply);
m_edit_connection << connect(m_header_button_group, QOverload<int>::of(&QButtonGroup::idClicked), this, &GraphicsTablePropertiesEditor::apply);
#endif
m_edit_connection << connect(ui->m_display_n_row_sb, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply); m_edit_connection << connect(ui->m_display_n_row_sb, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::apply);
m_edit_connection << connect(ui->m_display_n_row_sb, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::updateInfoLabel); m_edit_connection << connect(ui->m_display_n_row_sb, QOverload<int>::of(&QSpinBox::valueChanged), this, &GraphicsTablePropertiesEditor::updateInfoLabel);
} }
-7
View File
@@ -1725,14 +1725,7 @@ QSet<Conductor *> Conductor::relatedPotentialConductors(const bool all_diagram,
for (Conductor *c : other_conductors_list_t) { for (Conductor *c : other_conductors_list_t) {
other_conductors += c->relatedPotentialConductors(all_diagram, t_list); other_conductors += c->relatedPotentialConductors(all_diagram, t_list);
} }
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
other_conductors += other_conductors_list_t.toSet();
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
other_conductors += QSet<Conductor*>(other_conductors_list_t.begin(),other_conductors_list_t.end()); other_conductors += QSet<Conductor*>(other_conductors_list_t.begin(),other_conductors_list_t.end());
#endif
} }
} }

Some files were not shown because too many files have changed in this diff Show More