mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-06-08 19:23:13 +02:00
Compare commits
56 Commits
87f9f40e91
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c4711a8d0 | |||
| 8a8a338a2e | |||
| 407cc7a4c2 | |||
| 5cb8930732 | |||
| a24acfac24 | |||
| 8b0b1d10d4 | |||
| 57dfa28674 | |||
| 3848c7821a | |||
| 1572c23d51 | |||
| e234f063f8 | |||
| be21604ad0 | |||
| e1ccc1e568 | |||
| e202b5bc2b | |||
| 2b7e62f901 | |||
| 23e8258ae1 | |||
| 457d265f0a | |||
| cd76b6a1d6 | |||
| b522a94556 | |||
| 399bc0e897 | |||
| de9eeed542 | |||
| c071e92c58 | |||
| d22e4abf96 | |||
| 38b91e8083 | |||
| 19704cf5ca | |||
| 471d1f2538 | |||
| dc52105868 | |||
| 1b8dc5f410 | |||
| 26d5d019cc | |||
| 6dd7c2d926 | |||
| 47796e183a | |||
| ddf5ffcd89 | |||
| 7d718bb9a0 | |||
| d949e6eb8c | |||
| dbda958261 | |||
| d691489165 | |||
| 202ea38e40 | |||
| 86dafcb576 | |||
| f416c2a97e | |||
| 7426fedba3 | |||
| 027050c7e7 | |||
| fc948ad963 | |||
| 24d075b64c | |||
| f914f91e77 | |||
| fe03a0f643 | |||
| a7ad0278a6 | |||
| f517489421 | |||
| c8fa1c9fa4 | |||
| d85aff0c0f | |||
| 96b8e4b19c | |||
| 9760288db6 | |||
| eeaa059a77 | |||
| fa334d34a4 | |||
| 9664ff54ea | |||
| be0da461bd | |||
| 8c557a7f29 | |||
| 413e13a38c |
@@ -18,16 +18,19 @@ 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' ||
|
||||||
github.event.workflow_run.conclusion == 'success'
|
github.event.workflow_run.conclusion == 'success'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pages: write
|
pages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# 1. Checkout (to retrieve QElectroTech.wxs and sources)
|
# 1. Checkout (to retrieve QElectroTech.wxs and sources)
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
@@ -38,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 }}
|
||||||
@@ -59,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]
|
||||||
@@ -72,43 +69,41 @@ 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
|
||||||
|
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 extension added."
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
@@ -120,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"
|
||||||
@@ -136,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) {
|
||||||
@@ -147,69 +138,58 @@ 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)"
|
||||||
|
|
||||||
# FilesDir = folder containing bin\
|
|
||||||
$binDir = $exe.Directory.FullName
|
$binDir = $exe.Directory.FullName
|
||||||
$filesDir = Split-Path $binDir -Parent
|
$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 '\{', '\{' `
|
||||||
-replace '\}', '\}'
|
-replace '\}', '\}'
|
||||||
[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
|
# 9. Build the MSI
|
||||||
@@ -227,32 +207,63 @@ jobs:
|
|||||||
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 " 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=$productGuid" `
|
||||||
-d "FilesDir=$filesDir" `
|
-d "FilesDir=$filesDir" `
|
||||||
-d "LicenseRtf=$licRtf" `
|
-d "LicenseRtf=$licRtf" `
|
||||||
-ext WixToolset.UI.wixext `
|
-ext WixToolset.UI.wixext `
|
||||||
|
-ext WixToolset.Util.wixext `
|
||||||
-o "dist\$outputName"
|
-o "dist\$outputName"
|
||||||
|
|
||||||
if (-not (Test-Path "dist\$outputName")) {
|
if (-not (Test-Path "dist\$outputName")) {
|
||||||
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
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# 10. Upload the MSI artifact
|
# 10. Sign the MSI with SignPath
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
- name: Install SignPath PowerShell module
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Install-Module -Name SignPath -Force -Scope CurrentUser
|
||||||
|
|
||||||
|
- name: Sign MSI with SignPath
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$msi = Get-ChildItem "$env:GITHUB_WORKSPACE\dist\*.msi" | Select-Object -First 1
|
||||||
|
if (-not $msi) {
|
||||||
|
Write-Error "No .msi found in dist/"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "Signing: $($msi.FullName)"
|
||||||
|
Submit-SigningRequest `
|
||||||
|
-InputArtifactPath $msi.FullName `
|
||||||
|
-ApiToken "${{ secrets.SIGNPATH_API_TOKEN }}" `
|
||||||
|
-OrganizationId "${{ secrets.SIGNPATH_ORGANIZATION_ID }}" `
|
||||||
|
-ProjectSlug "MSI" `
|
||||||
|
-SigningPolicySlug "test-signing" `
|
||||||
|
-OutputArtifactPath $msi.FullName `
|
||||||
|
-Force `
|
||||||
|
-WaitForCompletion
|
||||||
|
Write-Host "Signing complete: $($msi.Name)"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# 11. Upload the MSI artifact
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
- name: Upload MSI artifact
|
- name: Upload MSI artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -272,7 +283,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
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
|
||||||
@@ -308,7 +319,6 @@ jobs:
|
|||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# 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
|
||||||
@@ -325,29 +335,22 @@ jobs:
|
|||||||
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
|
||||||
|
|||||||
+101
-87
@@ -16,13 +16,13 @@
|
|||||||
|
|
||||||
include(cmake/hoto_update_cmake_message.cmake)
|
include(cmake/hoto_update_cmake_message.cmake)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.14...3.19 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.5...4.2)
|
||||||
|
|
||||||
project(qelectrotech
|
project(qelectrotech
|
||||||
VERSION 0.9.0
|
VERSION 0.100.1
|
||||||
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)
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ set(QET_DIR ${PROJECT_SOURCE_DIR})
|
|||||||
# Add sub directories
|
# Add sub directories
|
||||||
option(PACKAGE_TESTS "Build the tests" ON)
|
option(PACKAGE_TESTS "Build the tests" ON)
|
||||||
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)
|
||||||
@@ -46,31 +46,31 @@ include(cmake/fetch_pugixml.cmake)
|
|||||||
include(cmake/qet_compilation_vars.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_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
SET(CMAKE_CXX_STANDARD 17)
|
SET(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(
|
find_package(
|
||||||
QT
|
QT
|
||||||
NAMES
|
NAMES
|
||||||
Qt5
|
Qt5
|
||||||
COMPONENTS
|
COMPONENTS
|
||||||
${QET_COMPONENTS}
|
${QET_COMPONENTS}
|
||||||
REQUIRED
|
REQUIRED
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(
|
find_package(
|
||||||
Qt${QT_VERSION_MAJOR}
|
Qt${QT_VERSION_MAJOR}
|
||||||
COMPONENTS
|
COMPONENTS
|
||||||
${QET_COMPONENTS}
|
${QET_COMPONENTS}
|
||||||
REQUIRED)
|
REQUIRED)
|
||||||
|
|
||||||
find_package(SQLite3 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})
|
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||||
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang")
|
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang")
|
||||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||||
@@ -78,78 +78,92 @@ qt5_add_translation(QM_FILES ${TS_FILES})
|
|||||||
# als laatse
|
# als laatse
|
||||||
include(cmake/define_definitions.cmake)
|
include(cmake/define_definitions.cmake)
|
||||||
|
|
||||||
add_executable(
|
# On Windows, WIN32 sets /SUBSYSTEM:WINDOWS to suppress the console window.
|
||||||
${PROJECT_NAME}
|
# Qt automatically links qtmain.lib which provides the WinMain entry point,
|
||||||
${QET_RES_FILES}
|
# so no source code change is needed.
|
||||||
${QET_SRC_FILES}
|
if(WIN32)
|
||||||
${QM_FILES}
|
add_executable(
|
||||||
${QET_DIR}/qelectrotech.qrc
|
${PROJECT_NAME}
|
||||||
)
|
WIN32
|
||||||
|
${QET_RES_FILES}
|
||||||
|
${QET_SRC_FILES}
|
||||||
|
${QM_FILES}
|
||||||
|
${QET_DIR}/qelectrotech.qrc
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
add_executable(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
${QET_RES_FILES}
|
||||||
|
${QET_SRC_FILES}
|
||||||
|
${QM_FILES}
|
||||||
|
${QET_DIR}/qelectrotech.qrc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
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}
|
${KF5_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}/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
|
||||||
|
)
|
||||||
|
|
||||||
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/16x16 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY ico/breeze-icons/22x22 DESTINATION ${QET_ICONS_PATH})
|
||||||
install(DIRECTORY ico/breeze-icons/22x22 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY ico/breeze-icons/32x32 DESTINATION ${QET_ICONS_PATH})
|
||||||
install(DIRECTORY ico/breeze-icons/32x32 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY ico/breeze-icons/48x48 DESTINATION ${QET_ICONS_PATH})
|
||||||
install(DIRECTORY ico/breeze-icons/48x48 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY ico/breeze-icons/64x64 DESTINATION ${QET_ICONS_PATH})
|
||||||
install(DIRECTORY ico/breeze-icons/64x64 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY ico/breeze-icons/128x128 DESTINATION ${QET_ICONS_PATH})
|
||||||
install(DIRECTORY ico/breeze-icons/128x128 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY ico/breeze-icons/256x256 DESTINATION ${QET_ICONS_PATH})
|
||||||
install(DIRECTORY ico/breeze-icons/256x256 DESTINATION ${QET_ICONS_PATH})
|
install(DIRECTORY elements DESTINATION share/qelectrotech)
|
||||||
install(DIRECTORY elements DESTINATION share/qelectrotech)
|
install(DIRECTORY examples DESTINATION share/qelectrotech)
|
||||||
install(DIRECTORY examples DESTINATION share/qelectrotech)
|
install(DIRECTORY titleblocks DESTINATION share/qelectrotech)
|
||||||
install(DIRECTORY titleblocks DESTINATION share/qelectrotech)
|
install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech)
|
||||||
install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech)
|
install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications)
|
||||||
install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications)
|
install(FILES misc/qelectrotech.xml DESTINATION share/mime/packages)
|
||||||
install(FILES misc/qelectrotech.xml DESTINATION share/mime/packages)
|
install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH})
|
||||||
install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH})
|
install(FILES ${QM_FILES} DESTINATION ${QET_LANG_PATH})
|
||||||
install(FILES ${QM_FILES} DESTINATION ${QET_LANG_PATH})
|
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
Name="QElectroTech"
|
Name="QElectroTech"
|
||||||
Manufacturer="QElectroTech Team"
|
Manufacturer="QElectroTech Team"
|
||||||
Version="$(var.Version)"
|
Version="$(var.Version)"
|
||||||
|
ProductCode="$(var.ProductCode)"
|
||||||
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
|
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
|
||||||
Language="1033"
|
Language="1033"
|
||||||
Codepage="1252"
|
Codepage="1252"
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
<!-- In-place upgrade: automatically uninstalls the previous version -->
|
<!-- In-place upgrade: automatically uninstalls the previous version -->
|
||||||
<MajorUpgrade
|
<MajorUpgrade
|
||||||
DowngradeErrorMessage="A newer version of QElectroTech is already installed."
|
DowngradeErrorMessage="A newer version of QElectroTech is already installed."
|
||||||
|
AllowSameVersionUpgrades="yes"
|
||||||
Schedule="afterInstallInitialize" />
|
Schedule="afterInstallInitialize" />
|
||||||
|
|
||||||
<!-- Installation directory -->
|
<!-- Installation directory -->
|
||||||
@@ -37,20 +39,26 @@
|
|||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
All application files harvested in one pass from files\**
|
All application files harvested in one pass from files\**
|
||||||
============================================================ -->
|
(Lancer QET.bat has been removed from the artifact before
|
||||||
|
this build — see windows-msi.yml step "Remove Lancer QET.bat")
|
||||||
|
============================================================ -->
|
||||||
<ComponentGroup Id="CG_AllFiles" Directory="INSTALLDIR">
|
<ComponentGroup Id="CG_AllFiles" Directory="INSTALLDIR">
|
||||||
<Files Include="$(var.FilesDir)\**" />
|
<Files Include="$(var.FilesDir)\**" />
|
||||||
</ComponentGroup>
|
</ComponentGroup>
|
||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
Desktop + Start Menu shortcuts
|
Desktop + Start Menu shortcuts
|
||||||
============================================================ -->
|
Point directly to qelectrotech.exe with all required arguments.
|
||||||
|
No .bat wrapper needed.
|
||||||
|
============================================================ -->
|
||||||
<ComponentGroup Id="CG_Shortcuts" Directory="INSTALLDIR">
|
<ComponentGroup Id="CG_Shortcuts" Directory="INSTALLDIR">
|
||||||
|
|
||||||
<Component Id="C_ShortcutDesktop" Guid="F1A2B3C4-D5E6-7890-5678-012345678901">
|
<Component Id="C_ShortcutDesktop" Guid="F1A2B3C4-D5E6-7890-5678-012345678901">
|
||||||
<Shortcut Id="DesktopShortcut"
|
<Shortcut Id="DesktopShortcut"
|
||||||
Directory="DesktopFolder"
|
Directory="DesktopFolder"
|
||||||
Name="QElectroTech"
|
Name="QElectroTech"
|
||||||
Target="[INSTALLDIR]Lancer QET.bat"
|
Target="[INSTALLDIR]bin\qelectrotech.exe"
|
||||||
|
Arguments="--common-elements-dir="[INSTALLDIR]elements/" --common-tbt-dir="[INSTALLDIR]titleblocks/" --lang-dir="[INSTALLDIR]lang/" -style windowsvista"
|
||||||
Icon="qet.ico"
|
Icon="qet.ico"
|
||||||
WorkingDirectory="INSTALLDIR" />
|
WorkingDirectory="INSTALLDIR" />
|
||||||
<RegistryValue Root="HKCU"
|
<RegistryValue Root="HKCU"
|
||||||
@@ -59,11 +67,13 @@
|
|||||||
Type="integer" Value="1"
|
Type="integer" Value="1"
|
||||||
KeyPath="yes" />
|
KeyPath="yes" />
|
||||||
</Component>
|
</Component>
|
||||||
|
|
||||||
<Component Id="C_ShortcutStartMenu" Guid="A2B3C4D5-E6F7-8901-6789-123456789012">
|
<Component Id="C_ShortcutStartMenu" Guid="A2B3C4D5-E6F7-8901-6789-123456789012">
|
||||||
<Shortcut Id="StartMenuShortcut"
|
<Shortcut Id="StartMenuShortcut"
|
||||||
Directory="ProgramMenuFolder"
|
Directory="ProgramMenuFolder"
|
||||||
Name="QElectroTech"
|
Name="QElectroTech"
|
||||||
Target="[INSTALLDIR]Lancer QET.bat"
|
Target="[INSTALLDIR]bin\qelectrotech.exe"
|
||||||
|
Arguments="--common-elements-dir="[INSTALLDIR]elements/" --common-tbt-dir="[INSTALLDIR]titleblocks/" --lang-dir="[INSTALLDIR]lang/" -style windowsvista"
|
||||||
Icon="qet.ico"
|
Icon="qet.ico"
|
||||||
WorkingDirectory="INSTALLDIR" />
|
WorkingDirectory="INSTALLDIR" />
|
||||||
<RegistryValue Root="HKCU"
|
<RegistryValue Root="HKCU"
|
||||||
@@ -72,11 +82,12 @@
|
|||||||
Type="integer" Value="1"
|
Type="integer" Value="1"
|
||||||
KeyPath="yes" />
|
KeyPath="yes" />
|
||||||
</Component>
|
</Component>
|
||||||
|
|
||||||
</ComponentGroup>
|
</ComponentGroup>
|
||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
.qet file association
|
.qet file association
|
||||||
============================================================ -->
|
============================================================ -->
|
||||||
<ComponentGroup Id="CG_FileAssoc" Directory="INSTALLDIR">
|
<ComponentGroup Id="CG_FileAssoc" Directory="INSTALLDIR">
|
||||||
<Component Id="C_FileAssoc" Guid="B3C4D5E6-F7A8-9012-7890-234567890123">
|
<Component Id="C_FileAssoc" Guid="B3C4D5E6-F7A8-9012-7890-234567890123">
|
||||||
<RegistryValue Root="HKCR" Key=".qet" Type="string" Value="QElectroTech.Document" KeyPath="yes" />
|
<RegistryValue Root="HKCR" Key=".qet" Type="string" Value="QElectroTech.Document" KeyPath="yes" />
|
||||||
@@ -90,12 +101,44 @@
|
|||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
Icon for shortcuts
|
Icon for shortcuts
|
||||||
============================================================ -->
|
============================================================ -->
|
||||||
<Icon Id="qet.ico" SourceFile="$(var.FilesDir)\ico\qelectrotech.ico" />
|
<Icon Id="qet.ico" SourceFile="$(var.FilesDir)\ico\qelectrotech.ico" />
|
||||||
|
|
||||||
|
<!-- ============================================================
|
||||||
|
CustomAction: set elements\ subtree read-only after install
|
||||||
|
|
||||||
|
Pattern used: two-step SetProperty + Exec
|
||||||
|
1. CA_ResolveElementsPath (immediate, SetProperty):
|
||||||
|
copies the runtime value of INSTALLDIR into the property
|
||||||
|
ELEMENTS_READONLY_CMD, building the full powershell command.
|
||||||
|
2. CA_SetElementsReadOnly (deferred, Execute="deferred"):
|
||||||
|
runs the command stored in ELEMENTS_READONLY_CMD.
|
||||||
|
|
||||||
|
This is the correct WiX v4/v7 pattern for passing a
|
||||||
|
directory path (resolved at runtime) to a deferred CA.
|
||||||
|
============================================================ -->
|
||||||
|
|
||||||
|
<!-- Step 1: build the powershell command with the resolved INSTALLDIR -->
|
||||||
|
<CustomAction Id="CA_ResolveElementsPath"
|
||||||
|
Property="CA_SetElementsReadOnly"
|
||||||
|
Value="powershell.exe -NonInteractive -NoProfile -WindowStyle Hidden -Command "Get-ChildItem -LiteralPath '[INSTALLDIR]elements' -Recurse -File | ForEach-Object { `$_.IsReadOnly = `$true }"" />
|
||||||
|
|
||||||
|
<!-- Step 2: deferred execution of the powershell command -->
|
||||||
|
<CustomAction Id="CA_SetElementsReadOnly"
|
||||||
|
BinaryRef="Wix4UtilCA_X86"
|
||||||
|
DllEntry="WixQuietExec"
|
||||||
|
Execute="deferred"
|
||||||
|
Impersonate="no"
|
||||||
|
Return="ignore" />
|
||||||
|
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action="CA_ResolveElementsPath" After="InstallFiles" Condition="NOT Installed AND NOT REMOVE" />
|
||||||
|
<Custom Action="CA_SetElementsReadOnly" After="CA_ResolveElementsPath" Condition="NOT Installed AND NOT REMOVE" />
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
Main feature (everything included)
|
Main feature (everything included)
|
||||||
============================================================ -->
|
============================================================ -->
|
||||||
<Feature Id="ProductFeature" Title="QElectroTech" Level="1">
|
<Feature Id="ProductFeature" Title="QElectroTech" Level="1">
|
||||||
<ComponentGroupRef Id="CG_AllFiles" />
|
<ComponentGroupRef Id="CG_AllFiles" />
|
||||||
<ComponentGroupRef Id="CG_Shortcuts" />
|
<ComponentGroupRef Id="CG_Shortcuts" />
|
||||||
@@ -110,9 +153,9 @@
|
|||||||
|
|
||||||
<!-- Properties shown in Programs and Features -->
|
<!-- Properties shown in Programs and Features -->
|
||||||
<Property Id="ARPPRODUCTICON" Value="qet.ico" />
|
<Property Id="ARPPRODUCTICON" Value="qet.ico" />
|
||||||
<Property Id="ARPHELPLINK" Value="https://qelectrotech.org" />
|
<Property Id="ARPHELPLINK" Value="https://qelectrotech.org" />
|
||||||
<Property Id="ARPURLINFOABOUT" Value="https://qelectrotech.org" />
|
<Property Id="ARPURLINFOABOUT" Value="https://qelectrotech.org" />
|
||||||
<Property Id="ARPCOMMENTS" Value="Free electrical schematic editor" />
|
<Property Id="ARPCOMMENTS" Value="Free electrical schematic editor" />
|
||||||
|
|
||||||
</Package>
|
</Package>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ if(BUILD_PUGIXML)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
pugixml
|
pugixml
|
||||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||||
GIT_TAG v1.11.4)
|
GIT_TAG v1.15)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(pugixml)
|
FetchContent_MakeAvailable(pugixml)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ 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
|
||||||
@@ -499,6 +500,8 @@ 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.h
|
||||||
|
|
||||||
${QET_DIR}/sources/titleblock/dimension.cpp
|
${QET_DIR}/sources/titleblock/dimension.cpp
|
||||||
${QET_DIR}/sources/titleblock/dimension.h
|
${QET_DIR}/sources/titleblock/dimension.h
|
||||||
@@ -713,6 +716,8 @@ set(QET_SRC_FILES
|
|||||||
|
|
||||||
${QET_DIR}/sources/xml/terminalstripitemxml.cpp
|
${QET_DIR}/sources/xml/terminalstripitemxml.cpp
|
||||||
${QET_DIR}/sources/xml/terminalstripitemxml.h
|
${QET_DIR}/sources/xml/terminalstripitemxml.h
|
||||||
|
${QET_DIR}/sources/xml/terminalstriplayoutpatternxml.cpp
|
||||||
|
${QET_DIR}/sources/xml/terminalstriplayoutpatternxml.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TS_FILES
|
set(TS_FILES
|
||||||
|
|||||||
+1
-1
Submodule elements updated: 9fd938d36c...3aab395fc4
+509
-443
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
+509
-444
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+510
-444
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+509
-443
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+508
-442
File diff suppressed because it is too large
Load Diff
+508
-442
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
+510
-444
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
+505
-439
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
+508
-442
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
+512
-446
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
+512
-446
File diff suppressed because it is too large
Load Diff
+508
-442
File diff suppressed because it is too large
Load Diff
+509
-443
File diff suppressed because it is too large
Load Diff
+508
-442
File diff suppressed because it is too large
Load Diff
+508
-442
File diff suppressed because it is too large
Load Diff
+508
-442
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1311
-1202
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
+506
-440
File diff suppressed because it is too large
Load Diff
+5
-1
@@ -230,7 +230,11 @@ 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
|
QT += xml svg network sql widgets printsupport concurrent KWidgetsAddons KCoreAddons gui-private
|
||||||
|
|
||||||
|
# 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) \
|
||||||
|
|||||||
@@ -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");
|
" 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')");
|
||||||
|
|
||||||
QSqlQuery query(m_data_base);
|
QSqlQuery query(m_data_base);
|
||||||
if (!query.exec(create_view)) {
|
if (!query.exec(create_view)) {
|
||||||
|
|||||||
@@ -373,6 +373,11 @@ 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");
|
||||||
@@ -456,7 +461,7 @@ void ElementQueryWidget::setUpItems()
|
|||||||
{
|
{
|
||||||
for(QString key : QETInformation::elementInfoKeys())
|
for(QString key : QETInformation::elementInfoKeys())
|
||||||
{
|
{
|
||||||
if (key == "formula")
|
if (key == "formula" || key == "exclude_from_bom")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto item = new QListWidgetItem(QETInformation::translatedInfoKey(key), ui->m_var_list);
|
auto item = new QListWidgetItem(QETInformation::translatedInfoKey(key), ui->m_var_list);
|
||||||
|
|||||||
+3
-2
@@ -142,10 +142,11 @@ 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;
|
||||||
void correctTextPos(Element* elmt);
|
|
||||||
void restoreText(Element* elmt);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
void correctTextPos(Element* elmt);
|
||||||
|
void restoreText(Element* elmt);
|
||||||
QUuid uuid();
|
QUuid uuid();
|
||||||
void setEventInterface (DiagramEventInterface *event_interface);
|
void setEventInterface (DiagramEventInterface *event_interface);
|
||||||
void clearEventInterface();
|
void clearEventInterface();
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ void DynamicTextFieldEditor::fillInfoComboBox()
|
|||||||
QStringList strl;
|
QStringList strl;
|
||||||
auto type = elementEditor()->elementScene()->elementData().m_type;
|
auto type = elementEditor()->elementScene()->elementData().m_type;
|
||||||
|
|
||||||
if(type & ElementData::AllReport) {
|
if((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
|
||||||
strl = QETInformation::folioReportInfoKeys();
|
strl = QETInformation::folioReportInfoKeys();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -365,7 +365,8 @@ void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
|
|||||||
void DynamicTextFieldEditor::on_m_composite_text_pb_clicked()
|
void DynamicTextFieldEditor::on_m_composite_text_pb_clicked()
|
||||||
{
|
{
|
||||||
bool isReport = false;
|
bool isReport = false;
|
||||||
if (elementEditor()->elementScene()->elementData().m_type & ElementData::AllReport) {
|
auto type = elementEditor()->elementScene()->elementData().m_type;
|
||||||
|
if ((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
|
||||||
isReport = true;
|
isReport = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ void ElementPropertiesEditorWidget::setUpInterface()
|
|||||||
ui->m_base_type_cb->addItem (tr("Renvoi de folio précédent"), ElementData::PreviousReport);
|
ui->m_base_type_cb->addItem (tr("Renvoi de folio précédent"), ElementData::PreviousReport);
|
||||||
ui->m_base_type_cb->addItem (tr("Bornier"), ElementData::Terminal);
|
ui->m_base_type_cb->addItem (tr("Bornier"), ElementData::Terminal);
|
||||||
ui->m_base_type_cb->addItem (tr("Vignette"), ElementData::Thumbnail);
|
ui->m_base_type_cb->addItem (tr("Vignette"), ElementData::Thumbnail);
|
||||||
|
ui->m_base_type_cb->addItem (tr("Définition de conducteur"), ElementData::ConductorDefinition);
|
||||||
|
|
||||||
// Slave option
|
// Slave option
|
||||||
ui->m_state_cb->addItem(tr("Normalement ouvert"), ElementData::NO);
|
ui->m_state_cb->addItem(tr("Normalement ouvert"), ElementData::NO);
|
||||||
@@ -188,6 +189,9 @@ void ElementPropertiesEditorWidget::updateTree()
|
|||||||
case ElementData::PreviousReport:
|
case ElementData::PreviousReport:
|
||||||
ui->m_tree->setDisabled(true);
|
ui->m_tree->setDisabled(true);
|
||||||
break;
|
break;
|
||||||
|
case ElementData::ConductorDefinition:
|
||||||
|
ui->m_tree->setDisabled(true);
|
||||||
|
break;
|
||||||
case ElementData::Master:
|
case ElementData::Master:
|
||||||
ui->m_tree->setEnabled(true);
|
ui->m_tree->setEnabled(true);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -736,40 +736,62 @@ bool QETElementEditor::checkElement()
|
|||||||
QList<QETWarning> warnings;
|
QList<QETWarning> warnings;
|
||||||
QList<QETWarning> errors;
|
QList<QETWarning> errors;
|
||||||
|
|
||||||
// Warning #1: Element haven't got terminal
|
// Warning #1: Element haven't got terminal
|
||||||
// (except for report, because report must have one terminal and this checking is do below)
|
// (except for report and conductor definition, because they must have one terminal and this checking is done below)
|
||||||
if (!m_elmt_scene -> containsTerminals() &&
|
if (!m_elmt_scene -> containsTerminals() &&
|
||||||
!(m_elmt_scene->elementData().m_type & ElementData::AllReport)) {
|
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
|
||||||
|
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition) {
|
||||||
warnings << qMakePair(
|
warnings << qMakePair(
|
||||||
tr("Absence de borne", "warning title"),
|
tr("Absence de borne", "warning title"),
|
||||||
tr(
|
tr(
|
||||||
"<br>En l'absence de borne, l'élément ne pourra être"
|
"<br>En l'absence de borne, l'élément ne pourra être"
|
||||||
" relié à d'autres éléments par l'intermédiaire de conducteurs.",
|
" relié à d'autres éléments par l'intermédiaire de conducteurs.",
|
||||||
"warning description"
|
"warning description"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check folio report element
|
// Check folio report element
|
||||||
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
|
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
|
||||||
{
|
{
|
||||||
int terminal =0;
|
int terminal =0;
|
||||||
|
|
||||||
for(auto qgi : m_elmt_scene -> items()) {
|
for(auto qgi : m_elmt_scene -> items()) {
|
||||||
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
|
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
|
||||||
terminal ++;
|
terminal ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Error folio report must have only one terminal
|
||||||
|
if (terminal != 1) {
|
||||||
|
errors << qMakePair (tr("Absence de borne"),
|
||||||
|
tr("<br><b>Erreur</b> :"
|
||||||
|
"<br>Les reports de folio doivent posséder une seul borne."
|
||||||
|
"<br><b>Solution</b> :"
|
||||||
|
"<br>Verifier que l'élément ne possède qu'une seul borne"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Error folio report must have only one terminal
|
// Check conductor definition element
|
||||||
if (terminal != 1) {
|
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
|
||||||
errors << qMakePair (tr("Absence de borne"),
|
{
|
||||||
tr("<br><b>Erreur</b> :"
|
int terminal =0;
|
||||||
"<br>Les reports de folio doivent posséder une seul borne."
|
|
||||||
"<br><b>Solution</b> :"
|
for(auto qgi : m_elmt_scene -> items()) {
|
||||||
"<br>Verifier que l'élément ne possède qu'une seul borne"));
|
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
|
||||||
|
terminal ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error: Conductor definition must have exactly one terminal
|
||||||
|
if (terminal != 1) {
|
||||||
|
errors << qMakePair (tr("Nombre de bornes incorrect"),
|
||||||
|
tr("<br><b>Erreur</b> :"
|
||||||
|
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
|
||||||
|
"<br><b>Solution</b> :"
|
||||||
|
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!errors.count() && !warnings.count()) {
|
if (!errors.count() && !warnings.count()) {
|
||||||
return(true);
|
return(true);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ void TerminalEditor::updateForm()
|
|||||||
ui->m_y_dsb->setValue(m_part->property("y").toReal());
|
ui->m_y_dsb->setValue(m_part->property("y").toReal());
|
||||||
ui->m_orientation_cb->setCurrentIndex(ui->m_orientation_cb->findData(m_part->property("orientation")));
|
ui->m_orientation_cb->setCurrentIndex(ui->m_orientation_cb->findData(m_part->property("orientation")));
|
||||||
ui->m_name_le->setText(m_part->terminalName());
|
ui->m_name_le->setText(m_part->terminalName());
|
||||||
ui->m_type_cb->setCurrentIndex(ui->m_orientation_cb->findData(m_part->terminalType()));
|
ui->m_type_cb->setCurrentIndex(ui->m_type_cb->findData(m_part->terminalType()));
|
||||||
|
|
||||||
activeConnections(true);
|
activeConnections(true);
|
||||||
}
|
}
|
||||||
@@ -122,6 +122,9 @@ void TerminalEditor::init()
|
|||||||
ui->m_type_cb->addItem(tr("Générique"), TerminalData::Generic);
|
ui->m_type_cb->addItem(tr("Générique"), TerminalData::Generic);
|
||||||
ui->m_type_cb->addItem(tr("Bornier intérieur"), TerminalData::Inner);
|
ui->m_type_cb->addItem(tr("Bornier intérieur"), TerminalData::Inner);
|
||||||
ui->m_type_cb->addItem(tr("Bornier extérieur"), TerminalData::Outer);
|
ui->m_type_cb->addItem(tr("Bornier extérieur"), TerminalData::Outer);
|
||||||
|
ui->m_type_cb->addItem(tr("NO (contact SW)"), TerminalData::No);
|
||||||
|
ui->m_type_cb->addItem(tr("NC (contact SW)"), TerminalData::Nc);
|
||||||
|
ui->m_type_cb->addItem(tr("Commun (contact SW)"), TerminalData::Common);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ void TextEditor::setUpWidget(QWidget *parent)
|
|||||||
|
|
||||||
m_size_sb = new QSpinBox(parent);
|
m_size_sb = new QSpinBox(parent);
|
||||||
m_size_sb->setObjectName(QString::fromUtf8("m_size_sb"));
|
m_size_sb->setObjectName(QString::fromUtf8("m_size_sb"));
|
||||||
|
m_size_sb->setMinimum(4);
|
||||||
|
|
||||||
gridLayout->addWidget(m_size_sb, 2, 1, 1, 1);
|
gridLayout->addWidget(m_size_sb, 2, 1, 1, 1);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
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"
|
||||||
@@ -26,6 +25,7 @@
|
|||||||
#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,7 +59,8 @@ 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);
|
||||||
@@ -100,6 +101,7 @@ 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()));
|
||||||
@@ -447,7 +449,8 @@ void ElementsPanelWidget::updateButtons()
|
|||||||
}
|
}
|
||||||
|
|
||||||
prj_del_diagram -> setEnabled(is_writable);
|
prj_del_diagram -> setEnabled(is_writable);
|
||||||
prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
|
prj_duplicate_diagram -> setEnabled(is_writable);
|
||||||
|
prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
|
||||||
prj_move_diagram_down -> setEnabled(is_writable && max_position < project_diagrams_count - 1);
|
prj_move_diagram_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);
|
||||||
|
|
||||||
@@ -501,6 +504,7 @@ 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);
|
||||||
@@ -593,3 +597,56 @@ 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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ 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,
|
||||||
@@ -88,6 +89,7 @@ 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();
|
||||||
|
|||||||
@@ -21,9 +21,16 @@
|
|||||||
#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
|
||||||
@@ -37,6 +44,11 @@
|
|||||||
#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
|
||||||
@@ -188,6 +200,241 @@ ProjectPrintWindow::~ProjectPrintWindow()
|
|||||||
* @brief ProjectPrintWindow::requestPaint
|
* @brief ProjectPrintWindow::requestPaint
|
||||||
* @param slot called when m_preview emit paintRequested
|
* @param slot called when m_preview emit paintRequested
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @brief ProjectPrintWindow::pdfConvertUriToGoTo
|
||||||
|
* Post-processes a Qt-generated PDF to replace URI link annotations
|
||||||
|
* (file:///path/to/file.pdf#page=N) with native PDF GoTo actions
|
||||||
|
* ([pageObj 0 R /Fit]). This makes cross-reference links work in all
|
||||||
|
* PDF viewers regardless of where the file is stored.
|
||||||
|
*
|
||||||
|
* The function:
|
||||||
|
* 1. Reads the PDF as raw bytes.
|
||||||
|
* 2. Collects page object numbers in document order by scanning for
|
||||||
|
* objects that contain "/Type /Page" (but not "/Type /Pages").
|
||||||
|
* 3. Replaces every annotation action block
|
||||||
|
* /S /URI\n/URI (file://...#page=N)
|
||||||
|
* with
|
||||||
|
* /S /GoTo\n/D [<pageObj> 0 R /Fit]
|
||||||
|
* 4. Rebuilds the cross-reference table (offsets change because the
|
||||||
|
* replacement strings have different lengths).
|
||||||
|
* 5. Writes the result back to the same file.
|
||||||
|
*
|
||||||
|
* The function is intentionally conservative: if any step fails (file
|
||||||
|
* not found, malformed PDF, no URI annotations) it returns silently
|
||||||
|
* without corrupting the file.
|
||||||
|
*/
|
||||||
|
static void pdfConvertUriToGoTo(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();
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectPrintWindow::requestPaint()
|
void ProjectPrintWindow::requestPaint()
|
||||||
{
|
{
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
||||||
@@ -214,13 +461,47 @@ void ProjectPrintWindow::requestPaint()
|
|||||||
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);
|
printDiagram(diagram, ui->m_fit_in_page_cb->isChecked(), &painter, m_printer, diagramPageMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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).
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,7 +511,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)
|
void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer, const QMap<Diagram*, int> &diagramPageMap)
|
||||||
{
|
{
|
||||||
|
|
||||||
////Prepare the print////
|
////Prepare the print////
|
||||||
@@ -317,6 +598,171 @@ 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 = QRectF(diagramRect(dg, exportProperties()));
|
||||||
|
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 = devToPdf(devT.topLeft());
|
||||||
|
const QPointF b = 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 =
|
||||||
|
diagramPageMap.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(printer->outputFileName());
|
||||||
|
url.setFragment(frag);
|
||||||
|
pdfEngine->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() est en coords LOCALES du CrossRefItem -> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
////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);
|
||||||
@@ -772,9 +1218,29 @@ void ProjectPrintWindow::on_m_uncheck_all_clicked()
|
|||||||
|
|
||||||
void ProjectPrintWindow::print()
|
void ProjectPrintWindow::print()
|
||||||
{
|
{
|
||||||
m_preview->print();
|
const bool isPdf = (m_printer->outputFormat() == QPrinter::PdfFormat);
|
||||||
|
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.
|
||||||
|
pdfConvertUriToGoTo(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)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "../exportproperties.h"
|
#include "../exportproperties.h"
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QMap>
|
||||||
#include <QPrinter>
|
#include <QPrinter>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@@ -79,7 +80,7 @@ class ProjectPrintWindow : public QMainWindow
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void requestPaint();
|
void requestPaint();
|
||||||
void printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer);
|
void printDiagram(Diagram *diagram, bool fit_page, QPainter *painter, QPrinter *printer, const QMap<Diagram*, int> &diagramPageMap = {});
|
||||||
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;
|
||||||
|
|||||||
@@ -45,14 +45,26 @@ QDomElement ElementData::toXml(QDomDocument &xml_element) const {
|
|||||||
bool ElementData::fromXml(const QDomElement &xml_element)
|
bool ElementData::fromXml(const QDomElement &xml_element)
|
||||||
{
|
{
|
||||||
if(xml_element.tagName() != QLatin1String("definition") ||
|
if(xml_element.tagName() != QLatin1String("definition") ||
|
||||||
xml_element.attribute(QStringLiteral("type")) != QLatin1String("element")) {
|
xml_element.attribute(QStringLiteral("type")) != QLatin1String("element")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- HIER STARTET UNSER DEBUG-BLOCK ---
|
||||||
|
// Wir holen den String aus der XML und speichern ihn kurz zwischen
|
||||||
|
QString raw_type_string = xml_element.attribute(QStringLiteral("link_type"), QStringLiteral("simple"));
|
||||||
|
|
||||||
|
qDebug() << "\n=== NEUES BAUTEIL WIRD GELADEN ===";
|
||||||
|
qDebug() << "[XML Parser] Roher 'link_type' String aus der .elmt Datei:" << raw_type_string;
|
||||||
|
|
||||||
|
// Jetzt übergeben wir ihn an deine Übersetzungs-Funktion
|
||||||
|
m_type = typeFromString(raw_type_string);
|
||||||
|
|
||||||
|
qDebug() << "[XML Parser] Übersetzter ElementData-Typ:" << typeToString(m_type);
|
||||||
|
// --- HIER ENDET UNSER DEBUG-BLOCK ---
|
||||||
|
|
||||||
m_type = typeFromString(xml_element.attribute(QStringLiteral("link_type"), QStringLiteral("simple")));
|
|
||||||
kindInfoFromXml(xml_element);
|
kindInfoFromXml(xml_element);
|
||||||
m_informations.fromXml(xml_element.firstChildElement(QStringLiteral("elementInformations")),
|
m_informations.fromXml(xml_element.firstChildElement(QStringLiteral("elementInformations")),
|
||||||
QStringLiteral("elementInformation"));
|
QStringLiteral("elementInformation"));
|
||||||
m_names_list.fromXml(xml_element);
|
m_names_list.fromXml(xml_element);
|
||||||
|
|
||||||
auto xml_draw_info = xml_element.firstChildElement(QStringLiteral("informations"));
|
auto xml_draw_info = xml_element.firstChildElement(QStringLiteral("informations"));
|
||||||
@@ -323,6 +335,8 @@ QString ElementData::typeToString(ElementData::Type type)
|
|||||||
return QStringLiteral("terminal");
|
return QStringLiteral("terminal");
|
||||||
case ElementData::Thumbnail:
|
case ElementData::Thumbnail:
|
||||||
return QStringLiteral("thumbnail");
|
return QStringLiteral("thumbnail");
|
||||||
|
case ElementData::ConductorDefinition:
|
||||||
|
return QStringLiteral("conductor_definition");
|
||||||
default:
|
default:
|
||||||
qDebug() << "ElementData::typeToString : type don't exist"
|
qDebug() << "ElementData::typeToString : type don't exist"
|
||||||
<< "return failsafe value 'simple'";
|
<< "return failsafe value 'simple'";
|
||||||
@@ -346,6 +360,8 @@ ElementData::Type ElementData::typeFromString(const QString &string)
|
|||||||
return ElementData::Terminal;
|
return ElementData::Terminal;
|
||||||
} else if (string == QLatin1String("thumbnail")) {
|
} else if (string == QLatin1String("thumbnail")) {
|
||||||
return ElementData::Thumbnail;
|
return ElementData::Thumbnail;
|
||||||
|
} else if (string == QLatin1String("conductor_definition")) {
|
||||||
|
return ElementData::ConductorDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Return simple if nothing match
|
//Return simple if nothing match
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ class ElementData : public PropertiesInterface
|
|||||||
Master = 8,
|
Master = 8,
|
||||||
Slave = 16,
|
Slave = 16,
|
||||||
Terminal = 32,
|
Terminal = 32,
|
||||||
Thumbnail = 64};
|
Thumbnail = 64,
|
||||||
|
ConductorDefinition = 128};
|
||||||
Q_ENUM(Type)
|
Q_ENUM(Type)
|
||||||
Q_DECLARE_FLAGS(Types, Type)
|
Q_DECLARE_FLAGS(Types, Type)
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,12 @@ QString TerminalData::typeToString(TerminalData::Type type)
|
|||||||
return QString("Inner");
|
return QString("Inner");
|
||||||
case Outer :
|
case Outer :
|
||||||
return QString("Outer");
|
return QString("Outer");
|
||||||
|
case No :
|
||||||
|
return QString("No");
|
||||||
|
case Nc :
|
||||||
|
return QString("Nc");
|
||||||
|
case Common :
|
||||||
|
return QString("Common");
|
||||||
}
|
}
|
||||||
return QString("Generic");
|
return QString("Generic");
|
||||||
}
|
}
|
||||||
@@ -193,6 +199,12 @@ TerminalData::Type TerminalData::typeFromString(const QString &string)
|
|||||||
return TerminalData::Inner;
|
return TerminalData::Inner;
|
||||||
} else if (string == "Outer") {
|
} else if (string == "Outer") {
|
||||||
return TerminalData::Outer;
|
return TerminalData::Outer;
|
||||||
|
} else if (string == "No") {
|
||||||
|
return TerminalData::No;
|
||||||
|
} else if (string == "Nc") {
|
||||||
|
return TerminalData::Nc;
|
||||||
|
} else if (string == "Common") {
|
||||||
|
return TerminalData::Common;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "TerminalData::typeFromString, argument string is invalid"
|
qDebug() << "TerminalData::typeFromString, argument string is invalid"
|
||||||
" failsafe type 'TerminalData::Generic' is returned";
|
" failsafe type 'TerminalData::Generic' is returned";
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ class TerminalData : public PropertiesInterface
|
|||||||
enum Type {
|
enum Type {
|
||||||
Generic,
|
Generic,
|
||||||
Inner,
|
Inner,
|
||||||
Outer
|
Outer,
|
||||||
|
No, ///< Normally Open terminal (for SW contacts)
|
||||||
|
Nc, ///< Normally Closed terminal (for SW contacts)
|
||||||
|
Common ///< Common terminal (for SW contacts)
|
||||||
};
|
};
|
||||||
Q_ENUM(Type)
|
Q_ENUM(Type)
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
XRefProperties::XRefProperties()
|
XRefProperties::XRefProperties()
|
||||||
{
|
{
|
||||||
m_show_power_ctc = true;
|
m_show_power_ctc = true;
|
||||||
|
m_show_terminal_name = true;
|
||||||
m_display = Cross;
|
m_display = Cross;
|
||||||
m_snap_to = Bottom;
|
m_snap_to = Bottom;
|
||||||
m_prefix_keys << "power" << "delay" << "switch";
|
m_prefix_keys << "power" << "delay" << "switch";
|
||||||
@@ -48,6 +49,7 @@ void XRefProperties::toSettings(QSettings &settings,
|
|||||||
const QString prefix) const
|
const QString prefix) const
|
||||||
{
|
{
|
||||||
settings.setValue(prefix % "showpowerctc", m_show_power_ctc);
|
settings.setValue(prefix % "showpowerctc", m_show_power_ctc);
|
||||||
|
settings.setValue(prefix % "showterminalname", m_show_terminal_name);
|
||||||
QString display = m_display == Cross? "cross" : "contacts";
|
QString display = m_display == Cross? "cross" : "contacts";
|
||||||
settings.setValue(prefix % "displayhas", display);
|
settings.setValue(prefix % "displayhas", display);
|
||||||
QString snap = m_snap_to == Bottom? "bottom" : "label";
|
QString snap = m_snap_to == Bottom? "bottom" : "label";
|
||||||
@@ -78,6 +80,7 @@ void XRefProperties::fromSettings(const QSettings &settings,
|
|||||||
const QString prefix)
|
const QString prefix)
|
||||||
{
|
{
|
||||||
m_show_power_ctc = settings.value(prefix % "showpowerctc", true).toBool();
|
m_show_power_ctc = settings.value(prefix % "showpowerctc", true).toBool();
|
||||||
|
m_show_terminal_name = settings.value(prefix % "showterminalname", true).toBool();
|
||||||
QString display = settings.value(prefix % "displayhas", "cross").toString();
|
QString display = settings.value(prefix % "displayhas", "cross").toString();
|
||||||
display == "cross"? m_display = Cross : m_display = Contacts;
|
display == "cross"? m_display = Cross : m_display = Contacts;
|
||||||
QString snap = settings.value(prefix % "snapto", "label").toString();
|
QString snap = settings.value(prefix % "snapto", "label").toString();
|
||||||
@@ -107,6 +110,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const
|
|||||||
xml_element.setAttribute("type", m_key);
|
xml_element.setAttribute("type", m_key);
|
||||||
|
|
||||||
xml_element.setAttribute("showpowerctc", m_show_power_ctc? "true" : "false");
|
xml_element.setAttribute("showpowerctc", m_show_power_ctc? "true" : "false");
|
||||||
|
xml_element.setAttribute("showterminalname", m_show_terminal_name? "true" : "false");
|
||||||
QString display = m_display == Cross? "cross" : "contacts";
|
QString display = m_display == Cross? "cross" : "contacts";
|
||||||
xml_element.setAttribute("displayhas", display);
|
xml_element.setAttribute("displayhas", display);
|
||||||
QString snap = m_snap_to == Bottom? "bottom" : "label";
|
QString snap = m_snap_to == Bottom? "bottom" : "label";
|
||||||
@@ -137,6 +141,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const
|
|||||||
*/
|
*/
|
||||||
bool XRefProperties::fromXml(const QDomElement &xml_element) {
|
bool XRefProperties::fromXml(const QDomElement &xml_element) {
|
||||||
m_show_power_ctc = xml_element.attribute("showpowerctc") == "true";
|
m_show_power_ctc = xml_element.attribute("showpowerctc") == "true";
|
||||||
|
m_show_terminal_name = xml_element.attribute("showterminalname", "true") == "true";
|
||||||
QString display = xml_element.attribute("displayhas", "cross");
|
QString display = xml_element.attribute("displayhas", "cross");
|
||||||
display == "cross"? m_display = Cross : m_display = Contacts;
|
display == "cross"? m_display = Cross : m_display = Contacts;
|
||||||
QString snap = xml_element.attribute("snapto", "label");
|
QString snap = xml_element.attribute("snapto", "label");
|
||||||
@@ -188,6 +193,7 @@ QHash<QString, XRefProperties> XRefProperties::defaultProperties()
|
|||||||
|
|
||||||
bool XRefProperties::operator ==(const XRefProperties &xrp) const{
|
bool XRefProperties::operator ==(const XRefProperties &xrp) const{
|
||||||
return (m_show_power_ctc == xrp.m_show_power_ctc
|
return (m_show_power_ctc == xrp.m_show_power_ctc
|
||||||
|
&& m_show_terminal_name == xrp.m_show_terminal_name
|
||||||
&& m_display == xrp.m_display
|
&& m_display == xrp.m_display
|
||||||
&& m_snap_to == xrp.m_snap_to
|
&& m_snap_to == xrp.m_snap_to
|
||||||
&& m_prefix == xrp.m_prefix
|
&& m_prefix == xrp.m_prefix
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ class XRefProperties : public PropertiesInterface
|
|||||||
void setShowPowerContac (const bool a) {m_show_power_ctc = a;}
|
void setShowPowerContac (const bool a) {m_show_power_ctc = a;}
|
||||||
bool showPowerContact () const {return m_show_power_ctc;}
|
bool showPowerContact () const {return m_show_power_ctc;}
|
||||||
|
|
||||||
|
void setShowTerminalName (const bool a) {m_show_terminal_name = a;}
|
||||||
|
bool showTerminalName () const {return m_show_terminal_name;}
|
||||||
|
|
||||||
void setDisplayHas (const DisplayHas dh) {m_display = dh;}
|
void setDisplayHas (const DisplayHas dh) {m_display = dh;}
|
||||||
DisplayHas displayHas () const {return m_display;}
|
DisplayHas displayHas () const {return m_display;}
|
||||||
|
|
||||||
@@ -81,6 +84,7 @@ class XRefProperties : public PropertiesInterface
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_show_power_ctc;
|
bool m_show_power_ctc;
|
||||||
|
bool m_show_terminal_name;
|
||||||
DisplayHas m_display;
|
DisplayHas m_display;
|
||||||
SnapTo m_snap_to;
|
SnapTo m_snap_to;
|
||||||
Qt::AlignmentFlag m_xref_pos;
|
Qt::AlignmentFlag m_xref_pos;
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
#include "crossrefitem.h"
|
#include "crossrefitem.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "../autoNum/assignvariables.h"
|
#include "../autoNum/assignvariables.h"
|
||||||
#include "../diagram.h"
|
#include "../diagram.h"
|
||||||
#include "../diagramposition.h"
|
#include "../diagramposition.h"
|
||||||
@@ -25,6 +27,7 @@
|
|||||||
#include "element.h"
|
#include "element.h"
|
||||||
#include "elementtextitemgroup.h"
|
#include "elementtextitemgroup.h"
|
||||||
#include "qgraphicsitemutility.h"
|
#include "qgraphicsitemutility.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
//define the height of the header.
|
//define the height of the header.
|
||||||
static int header = 5;
|
static int header = 5;
|
||||||
@@ -221,9 +224,12 @@ void CrossRefItem::updateLabel()
|
|||||||
prepareGeometryChange();
|
prepareGeometryChange();
|
||||||
m_bounding_rect = QRectF();
|
m_bounding_rect = QRectF();
|
||||||
|
|
||||||
//init the painter
|
// Build geometry and m_hovered_contacts_map using a QImage-backed
|
||||||
QPainter qp;
|
// painter so font metrics match the screen painter in paint().
|
||||||
qp.begin(&m_drawing);
|
// m_update_map=true allows the draw functions to populate the map;
|
||||||
|
// paint() calls them with m_update_map=false so the map is stable.
|
||||||
|
QImage dummy(1, 1, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
QPainter qp(&dummy);
|
||||||
QPen pen_;
|
QPen pen_;
|
||||||
pen_.setWidthF(0.5);
|
pen_.setWidthF(0.5);
|
||||||
qp.setPen(pen_);
|
qp.setPen(pen_);
|
||||||
@@ -232,17 +238,21 @@ void CrossRefItem::updateLabel()
|
|||||||
//Draw cross or contact, only if master element is linked.
|
//Draw cross or contact, only if master element is linked.
|
||||||
if (! m_element->linkedElements().isEmpty())
|
if (! m_element->linkedElements().isEmpty())
|
||||||
{
|
{
|
||||||
|
m_update_map = true;
|
||||||
XRefProperties::DisplayHas dh = m_properties.displayHas();
|
XRefProperties::DisplayHas dh = m_properties.displayHas();
|
||||||
|
|
||||||
if (dh == XRefProperties::Cross)
|
if (dh == XRefProperties::Cross)
|
||||||
drawAsCross(qp);
|
drawAsCross(qp);
|
||||||
else if (dh == XRefProperties::Contacts)
|
else if (dh == XRefProperties::Contacts)
|
||||||
drawAsContacts(qp);
|
drawAsContacts(qp);
|
||||||
|
m_update_map = false;
|
||||||
}
|
}
|
||||||
qp.end();
|
|
||||||
|
|
||||||
autoPos();
|
autoPos();
|
||||||
update();
|
update();
|
||||||
|
// Schedule a second update after the scene has finished laying out,
|
||||||
|
// so the initial render uses the correct bounding rect.
|
||||||
|
QTimer::singleShot(0, this, [this]{ update(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -310,7 +320,26 @@ void CrossRefItem::paint(
|
|||||||
{
|
{
|
||||||
Q_UNUSED(option)
|
Q_UNUSED(option)
|
||||||
Q_UNUSED(widget)
|
Q_UNUSED(widget)
|
||||||
m_drawing.play(painter);
|
// Draw directly — no QPicture involved anywhere.
|
||||||
|
// QPicture::play() + nested drawPicture() (m_hdr_no_ctc/m_hdr_nc_ctc)
|
||||||
|
// caused a use-after-free crash (QRegion::begin, Qt5Gui+0x49af60)
|
||||||
|
// confirmed by analysis of 19+ coredumps.
|
||||||
|
// m_update_map=false: draw functions do not overwrite m_hovered_contacts_map.
|
||||||
|
if (m_element->linkedElements().isEmpty()) return;
|
||||||
|
|
||||||
|
QPen pen_;
|
||||||
|
pen_.setWidthF(0.5);
|
||||||
|
painter->save();
|
||||||
|
painter->setPen(pen_);
|
||||||
|
painter->setFont(QETApp::diagramTextsFont(5));
|
||||||
|
|
||||||
|
m_update_map = false;
|
||||||
|
XRefProperties::DisplayHas dh = m_properties.displayHas();
|
||||||
|
if (dh == XRefProperties::Cross)
|
||||||
|
drawAsCross(*painter);
|
||||||
|
else if (dh == XRefProperties::Contacts)
|
||||||
|
drawAsContacts(*painter);
|
||||||
|
painter->restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -320,7 +349,24 @@ void CrossRefItem::paint(
|
|||||||
void CrossRefItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
void CrossRefItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||||
{
|
{
|
||||||
event->accept();
|
event->accept();
|
||||||
QetGraphicsItem::showItem(m_hovered_contact);
|
|
||||||
|
// Find the element under the click position directly from the map,
|
||||||
|
// rather than relying on m_hovered_contact which may have been reset
|
||||||
|
// by hoverMoveEvent between the two clicks of the double-click.
|
||||||
|
QPointF pos = event->pos();
|
||||||
|
Element *target = m_hovered_contact;
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
for (auto it = m_hovered_contacts_map.begin();
|
||||||
|
it != m_hovered_contacts_map.end(); ++it) {
|
||||||
|
if (it.value().contains(pos)) {
|
||||||
|
target = it.key();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QetGraphicsItem::showItem(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -432,49 +478,41 @@ void CrossRefItem::linkedChanged()
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
@brief CrossRefItem::buildHeaderContact
|
@brief CrossRefItem::buildHeaderContact
|
||||||
Draw the QPicture of m_hdr_no_ctc and m_hdr_nc_ctc
|
Draw NO and NC contact symbols directly onto painter.
|
||||||
|
Previously used QPicture (m_hdr_no_ctc/m_hdr_nc_ctc) which caused
|
||||||
|
use-after-free crashes via nested QPicture::play() calls.
|
||||||
*/
|
*/
|
||||||
void CrossRefItem::buildHeaderContact()
|
void CrossRefItem::buildHeaderContact(QPainter &painter, QPointF no_pos, QPointF nc_pos)
|
||||||
{
|
{
|
||||||
if (!m_hdr_no_ctc.isNull() && !m_hdr_nc_ctc.isNull()) return;
|
|
||||||
|
|
||||||
//init the painter
|
|
||||||
QPainter qp;
|
|
||||||
QPen pen_;
|
QPen pen_;
|
||||||
pen_.setWidthF(0.2);
|
pen_.setWidthF(0.2);
|
||||||
|
painter.save();
|
||||||
|
painter.setPen(pen_);
|
||||||
|
|
||||||
//draw the NO contact
|
//draw the NO contact header symbol
|
||||||
if (m_hdr_no_ctc.isNull()) {
|
painter.drawLine(no_pos.x()+0, no_pos.y()+3, no_pos.x()+5, no_pos.y()+3);
|
||||||
qp.begin(&m_hdr_no_ctc);
|
QPointF p1[3] = {
|
||||||
qp.setPen(pen_);
|
QPointF(no_pos.x()+5, no_pos.y()+0),
|
||||||
qp.drawLine(0, 3, 5, 3);
|
QPointF(no_pos.x()+10, no_pos.y()+3),
|
||||||
QPointF p1[3] = {
|
QPointF(no_pos.x()+15, no_pos.y()+3),
|
||||||
QPointF(5, 0),
|
};
|
||||||
QPointF(10, 3),
|
painter.drawPolyline(p1, 3);
|
||||||
QPointF(15, 3),
|
|
||||||
};
|
|
||||||
qp.drawPolyline(p1,3);
|
|
||||||
qp.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
//draw the NC contact
|
//draw the NC contact header symbol
|
||||||
if (m_hdr_nc_ctc.isNull()) {
|
QPointF p2[3] = {
|
||||||
qp.begin(&m_hdr_nc_ctc);
|
QPointF(nc_pos.x()+0, nc_pos.y()+3),
|
||||||
qp.setPen(pen_);
|
QPointF(nc_pos.x()+5, nc_pos.y()+3),
|
||||||
QPointF p2[3] = {
|
QPointF(nc_pos.x()+5, nc_pos.y()+0)
|
||||||
QPointF(0, 3),
|
};
|
||||||
QPointF(5, 3),
|
painter.drawPolyline(p2, 3);
|
||||||
QPointF(5, 0)
|
QPointF p3[3] = {
|
||||||
};
|
QPointF(nc_pos.x()+4, nc_pos.y()+0),
|
||||||
qp.drawPolyline(p2,3);
|
QPointF(nc_pos.x()+10, nc_pos.y()+3),
|
||||||
QPointF p3[3] = {
|
QPointF(nc_pos.x()+15, nc_pos.y()+3),
|
||||||
QPointF(4, 0),
|
};
|
||||||
QPointF(10, 3),
|
painter.drawPolyline(p3, 3);
|
||||||
QPointF(15, 3),
|
|
||||||
};
|
painter.restore();
|
||||||
qp.drawPolyline(p3,3);
|
|
||||||
qp.end();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -492,11 +530,95 @@ void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
|
|||||||
|
|
||||||
QStringList no_str, nc_str;
|
QStringList no_str, nc_str;
|
||||||
|
|
||||||
|
// Helper lambda: build "[13-14] pos" string for an element.
|
||||||
|
// For power contacts (e.g. 3-pole contactor), all named terminals
|
||||||
|
// are collected (e.g. "[1-2-3-4-5-6] pos").
|
||||||
|
// For other contacts (NO/NC/SW), only the first 2 named terminals
|
||||||
|
// are used — users are expected to name and order their terminals
|
||||||
|
// in the element editor.
|
||||||
|
// Helper lambda: build "[13-14] pos" for an element.
|
||||||
|
// - Power: all terminals sorted numerically → "[1-2-3-4-5-6] pos"
|
||||||
|
// - SW with typed terminals: show relevant pair per column (handled below)
|
||||||
|
// - Others: first 2 named terminals
|
||||||
|
auto buildLabel = [this](Element *elmt, bool is_no_col) -> QString {
|
||||||
|
const bool is_power =
|
||||||
|
elmt->kindInformations()["type"].toString() == "power";
|
||||||
|
const bool is_sw =
|
||||||
|
elmt->kindInformations()["state"].toString() == "SW";
|
||||||
|
|
||||||
|
QStringList tnames;
|
||||||
|
|
||||||
|
if (is_sw) {
|
||||||
|
// Check if terminals have explicit No/Nc/Common types
|
||||||
|
bool has_typed = false;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (t->terminalType() == TerminalData::No ||
|
||||||
|
t->terminalType() == TerminalData::Nc ||
|
||||||
|
t->terminalType() == TerminalData::Common) {
|
||||||
|
has_typed = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_typed) {
|
||||||
|
QString no_name, nc_name, common_name;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (!t->name().isEmpty()) {
|
||||||
|
if (t->terminalType() == TerminalData::No) no_name = t->name();
|
||||||
|
else if (t->terminalType() == TerminalData::Nc) nc_name = t->name();
|
||||||
|
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NO column: show NO+Common pair; NC column: show NC+Common pair
|
||||||
|
if (is_no_col)
|
||||||
|
tnames << no_name << common_name;
|
||||||
|
else
|
||||||
|
tnames << nc_name << common_name;
|
||||||
|
} else {
|
||||||
|
// Fallback: first 2 named terminals
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tn = t->name();
|
||||||
|
if (!tn.isEmpty()) { tnames << tn; if (tnames.size() >= 2) break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tn = t->name();
|
||||||
|
if (!tn.isEmpty()) {
|
||||||
|
tnames << tn;
|
||||||
|
if (!is_power && tnames.size() >= 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_power) {
|
||||||
|
std::sort(tnames.begin(), tnames.end(),
|
||||||
|
[](const QString &a, const QString &b){
|
||||||
|
int i_a = a.size();
|
||||||
|
while (i_a > 0 && a[i_a-1].isDigit()) --i_a;
|
||||||
|
int i_b = b.size();
|
||||||
|
while (i_b > 0 && b[i_b-1].isDigit()) --i_b;
|
||||||
|
bool a_ok = false, b_ok = false;
|
||||||
|
int ai = a.mid(i_a).toInt(&a_ok);
|
||||||
|
int bi = b.mid(i_b).toInt(&b_ok);
|
||||||
|
if (a_ok && b_ok && a.left(i_a) == b.left(i_b))
|
||||||
|
return ai < bi;
|
||||||
|
return a < b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString pos = elementPositionText(elmt, true);
|
||||||
|
if (!tnames.isEmpty() && m_properties.showTerminalName())
|
||||||
|
return QStringLiteral("[") + tnames.join("-") + QStringLiteral("] ") + pos;
|
||||||
|
return pos;
|
||||||
|
};
|
||||||
|
|
||||||
for (auto elmt : NOElements()) {
|
for (auto elmt : NOElements()) {
|
||||||
no_str.append(elementPositionText(elmt, true));
|
no_str.append(buildLabel(elmt, true));
|
||||||
}
|
}
|
||||||
for (auto elmt : NCElements()) {
|
for (auto elmt : NCElements()) {
|
||||||
nc_str.append(elementPositionText(elmt, true));
|
nc_str.append(buildLabel(elmt, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
//There is no string to display, we return now
|
//There is no string to display, we return now
|
||||||
@@ -509,9 +631,10 @@ void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
|
|||||||
QRectF no_bounding;
|
QRectF no_bounding;
|
||||||
for (auto str : no_str)
|
for (auto str : no_str)
|
||||||
{
|
{
|
||||||
QRectF bounding = painter.boundingRect(QRectF (), Qt::AlignCenter, str);
|
QRectF bounding = painter.boundingRect(QRectF(0, 0, 500, 20), Qt::AlignLeft, str);
|
||||||
no_bounding = no_bounding.united(bounding);
|
|
||||||
no_bounding.setHeight(no_bounding.height() + bounding.height());
|
no_bounding.setHeight(no_bounding.height() + bounding.height());
|
||||||
|
if (bounding.width() > no_bounding.width())
|
||||||
|
no_bounding.setWidth(bounding.width());
|
||||||
}
|
}
|
||||||
//Adjust according to the NO
|
//Adjust according to the NO
|
||||||
if (no_bounding.height() > default_bounding.height() - header)
|
if (no_bounding.height() > default_bounding.height() - header)
|
||||||
@@ -523,9 +646,10 @@ void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
|
|||||||
QRectF nc_bounding;
|
QRectF nc_bounding;
|
||||||
for (auto str : nc_str)
|
for (auto str : nc_str)
|
||||||
{
|
{
|
||||||
QRectF bounding = painter.boundingRect(QRectF (), Qt::AlignCenter, str);
|
QRectF bounding = painter.boundingRect(QRectF(0, 0, 500, 20), Qt::AlignLeft, str);
|
||||||
nc_bounding = nc_bounding.united(bounding);
|
|
||||||
nc_bounding.setHeight(nc_bounding.height() + bounding.height());
|
nc_bounding.setHeight(nc_bounding.height() + bounding.height());
|
||||||
|
if (bounding.width() > nc_bounding.width())
|
||||||
|
nc_bounding.setWidth(bounding.width());
|
||||||
}
|
}
|
||||||
//Adjust according to the NC
|
//Adjust according to the NC
|
||||||
if (nc_bounding.height() > default_bounding.height() - header)
|
if (nc_bounding.height() > default_bounding.height() - header)
|
||||||
@@ -549,7 +673,8 @@ void CrossRefItem::drawAsCross(QPainter &painter)
|
|||||||
{
|
{
|
||||||
//calculate the size of the cross
|
//calculate the size of the cross
|
||||||
setUpCrossBoundingRect(painter);
|
setUpCrossBoundingRect(painter);
|
||||||
m_hovered_contacts_map.clear();
|
m_drawed_contacts = 0;
|
||||||
|
if (m_update_map) m_hovered_contacts_map.clear();
|
||||||
|
|
||||||
//Bounding rect is empty that mean there's no contact to draw
|
//Bounding rect is empty that mean there's no contact to draw
|
||||||
if (boundingRect().isEmpty()) return;
|
if (boundingRect().isEmpty()) return;
|
||||||
@@ -559,12 +684,11 @@ void CrossRefItem::drawAsCross(QPainter &painter)
|
|||||||
painter.drawLine(br.width()/2, 0, br.width()/2, br.height()); //vertical line
|
painter.drawLine(br.width()/2, 0, br.width()/2, br.height()); //vertical line
|
||||||
painter.drawLine(0, header, br.width(), header); //horizontal line
|
painter.drawLine(0, header, br.width(), header); //horizontal line
|
||||||
|
|
||||||
//Add the symbolic contacts
|
//Add the symbolic contacts (drawn directly, no QPicture)
|
||||||
buildHeaderContact();
|
static const qreal hdr_symbol_width = 15.0;
|
||||||
QPointF p((m_bounding_rect.width()/4) - (m_hdr_no_ctc.width()/2), 0);
|
QPointF no_pos((m_bounding_rect.width()/4) - (hdr_symbol_width/2), 0);
|
||||||
painter.drawPicture (p, m_hdr_no_ctc);
|
QPointF nc_pos((m_bounding_rect.width() * 3/4) - (hdr_symbol_width/2), 0);
|
||||||
p.setX((m_bounding_rect.width() * 3/4) - (m_hdr_nc_ctc.width()/2));
|
buildHeaderContact(painter, no_pos, nc_pos);
|
||||||
painter.drawPicture (p, m_hdr_nc_ctc);
|
|
||||||
|
|
||||||
//and fill it
|
//and fill it
|
||||||
fillCrossRef(painter);
|
fillCrossRef(painter);
|
||||||
@@ -581,7 +705,7 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_drawed_contacts = 0;
|
m_drawed_contacts = 0;
|
||||||
m_hovered_contacts_map.clear();
|
if (m_update_map) m_hovered_contacts_map.clear();
|
||||||
QRectF bounding_rect;
|
QRectF bounding_rect;
|
||||||
|
|
||||||
//Draw each linked contact
|
//Draw each linked contact
|
||||||
@@ -605,7 +729,7 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
|
|||||||
else if (type == "delayOff") option += DelayOff;
|
else if (type == "delayOff") option += DelayOff;
|
||||||
else if (type == "delayOnOff") option += DelayOnOff;
|
else if (type == "delayOnOff") option += DelayOnOff;
|
||||||
|
|
||||||
QRectF br = drawContact(painter, option, elmt);
|
QRectF br = drawContact(painter, option, elmt, i);
|
||||||
bounding_rect = bounding_rect.united(br);
|
bounding_rect = bounding_rect.united(br);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,9 +748,81 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
|
|||||||
@param elmt : the element to display text (the position of the contact)
|
@param elmt : the element to display text (the position of the contact)
|
||||||
@return The bounding rect of the draw (contact + text)
|
@return The bounding rect of the draw (contact + text)
|
||||||
*/
|
*/
|
||||||
QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
|
QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, int pole_index)
|
||||||
{
|
{
|
||||||
QString str = elementPositionText(elmt);
|
QString str = elementPositionText(elmt);
|
||||||
|
|
||||||
|
// Collect terminal names from the element definition (.elmt)
|
||||||
|
// e.g. name="13" and name="14" on each terminal
|
||||||
|
// For power contacts, sort numerically and pick the pair for pole_index.
|
||||||
|
// For SW contacts with typed terminals (No/Nc/Common), filter by role.
|
||||||
|
QStringList terminal_names;
|
||||||
|
const bool is_power_ctc =
|
||||||
|
elmt->kindInformations()["type"].toString() == "power";
|
||||||
|
const bool is_sw = (flags & SW) && !(flags & NOC);
|
||||||
|
|
||||||
|
// Check if SW terminals have explicit No/Nc/Common types
|
||||||
|
bool sw_has_typed_terminals = false;
|
||||||
|
if (is_sw) {
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (t->terminalType() == TerminalData::No ||
|
||||||
|
t->terminalType() == TerminalData::Nc ||
|
||||||
|
t->terminalType() == TerminalData::Common) {
|
||||||
|
sw_has_typed_terminals = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tname = t->name();
|
||||||
|
if (!tname.isEmpty())
|
||||||
|
terminal_names << tname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_power_ctc) {
|
||||||
|
// Sort terminals alphanumerically so names like "R1","R2"... or "1","2"...
|
||||||
|
// are ordered correctly. Extract trailing digits for numeric comparison;
|
||||||
|
// fall back to full string comparison when no digits are found.
|
||||||
|
std::sort(terminal_names.begin(), terminal_names.end(),
|
||||||
|
[](const QString &a, const QString &b){
|
||||||
|
// Extract trailing numeric part
|
||||||
|
int i_a = a.size();
|
||||||
|
while (i_a > 0 && a[i_a-1].isDigit()) --i_a;
|
||||||
|
int i_b = b.size();
|
||||||
|
while (i_b > 0 && b[i_b-1].isDigit()) --i_b;
|
||||||
|
bool a_ok = false, b_ok = false;
|
||||||
|
int ai = a.mid(i_a).toInt(&a_ok);
|
||||||
|
int bi = b.mid(i_b).toInt(&b_ok);
|
||||||
|
if (a_ok && b_ok && a.left(i_a) == b.left(i_b))
|
||||||
|
return ai < bi;
|
||||||
|
return a < b;
|
||||||
|
});
|
||||||
|
// Pick the pair for this pole: pole 0 → [0,1], pole 1 → [2,3], etc.
|
||||||
|
int idx = pole_index * 2;
|
||||||
|
if (idx + 1 < terminal_names.size())
|
||||||
|
terminal_names = QStringList() << terminal_names[idx] << terminal_names[idx+1];
|
||||||
|
else
|
||||||
|
terminal_names.clear();
|
||||||
|
} else if (is_sw && sw_has_typed_terminals) {
|
||||||
|
// Build [NO_name, Common_name, NC_name] from typed terminals
|
||||||
|
QString no_name, nc_name, common_name;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (!t->name().isEmpty()) {
|
||||||
|
if (t->terminalType() == TerminalData::No) no_name = t->name();
|
||||||
|
else if (t->terminalType() == TerminalData::Nc) nc_name = t->name();
|
||||||
|
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// drawText expects: [0]=NC, [1]=NO, [2]=Common
|
||||||
|
// (drawText uses [1] for NO top-left, [0] for NC bottom-left, [2] for Common right)
|
||||||
|
terminal_names.clear();
|
||||||
|
terminal_names << nc_name << no_name << common_name;
|
||||||
|
}
|
||||||
|
|
||||||
int offset = m_drawed_contacts*10;
|
int offset = m_drawed_contacts*10;
|
||||||
QRectF bounding_rect = QRectF(0, offset, 24, 10);
|
QRectF bounding_rect = QRectF(0, offset, 24, 10);
|
||||||
|
|
||||||
@@ -643,15 +839,17 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
|
|||||||
painter.drawLine(0, offset+6, 8, offset+6);
|
painter.drawLine(0, offset+6, 8, offset+6);
|
||||||
painter.drawLine(16, offset+6, 24, offset+6);
|
painter.drawLine(16, offset+6, 24, offset+6);
|
||||||
|
|
||||||
///take example of this code for display the terminal text
|
// Draw terminal names on each side of the contact symbol
|
||||||
/*QFont font = QETApp::diagramTextsFont(4);
|
// terminal_names[0] on the left, terminal_names[1] on the right
|
||||||
font.setBold(true);
|
if (!terminal_names.isEmpty() && m_properties.showTerminalName()) {
|
||||||
painter.setFont(font);
|
painter.setFont(QETApp::diagramTextsFont(4));
|
||||||
QRectF bt(0, offset, 24, 10);
|
QRectF bt(0, offset, 24, 10);
|
||||||
int txt = 10 + m_drawed_contacts;
|
if (terminal_names.size() >= 1)
|
||||||
painter.drawText(bt, Qt::AlignLeft|Qt::AlignTop, QString::number(txt));
|
painter.drawText(bt, Qt::AlignLeft|Qt::AlignTop, terminal_names[0]);
|
||||||
painter.drawText(bt, Qt::AlignRight|Qt::AlignTop, QString::number(txt));
|
if (terminal_names.size() >= 2)
|
||||||
painter.setFont(QETApp::diagramTextsFont(5));*/
|
painter.drawText(bt, Qt::AlignRight|Qt::AlignTop, terminal_names[1]);
|
||||||
|
painter.setFont(QETApp::diagramTextsFont(5));
|
||||||
|
}
|
||||||
|
|
||||||
//draw open contact
|
//draw open contact
|
||||||
if (flags &NO) {
|
if (flags &NO) {
|
||||||
@@ -729,14 +927,8 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
|
|||||||
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str);
|
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str);
|
||||||
bounding_rect = bounding_rect.united(text_rect);
|
bounding_rect = bounding_rect.united(text_rect);
|
||||||
|
|
||||||
if (m_hovered_contacts_map.contains(elmt))
|
if (m_update_map)
|
||||||
{
|
m_hovered_contacts_map.insert(elmt, text_rect);
|
||||||
m_hovered_contacts_map.insert(elmt, bounding_rect);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_hovered_contacts_map.insert(elmt, bounding_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
++m_drawed_contacts;
|
++m_drawed_contacts;
|
||||||
}
|
}
|
||||||
@@ -768,6 +960,25 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
|
|||||||
};
|
};
|
||||||
painter.drawPolyline(p2, 3);
|
painter.drawPolyline(p2, 3);
|
||||||
|
|
||||||
|
// Draw terminal names for switch contact (3 terminals)
|
||||||
|
// terminal_names[0] = NC (bottom-left)
|
||||||
|
// terminal_names[1] = NO (top-left)
|
||||||
|
// terminal_names[2] = Common (right)
|
||||||
|
if (!terminal_names.isEmpty() && m_properties.showTerminalName()) {
|
||||||
|
painter.setFont(QETApp::diagramTextsFont(4));
|
||||||
|
// Storage order set above: [0]=NC, [1]=NO, [2]=Common
|
||||||
|
if (terminal_names.size() >= 2)
|
||||||
|
painter.drawText(QRectF(0, offset, 8, 8),
|
||||||
|
Qt::AlignLeft|Qt::AlignTop, terminal_names[1]); // NO top-left
|
||||||
|
if (terminal_names.size() >= 3)
|
||||||
|
painter.drawText(QRectF(16, offset+4, 8, 6),
|
||||||
|
Qt::AlignRight|Qt::AlignTop, terminal_names[2]); // Common right
|
||||||
|
if (terminal_names.size() >= 1)
|
||||||
|
painter.drawText(QRectF(0, offset+9, 8, 6),
|
||||||
|
Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); // NC bottom-left
|
||||||
|
painter.setFont(QETApp::diagramTextsFont(5));
|
||||||
|
}
|
||||||
|
|
||||||
//Draw the half ellipse off delay
|
//Draw the half ellipse off delay
|
||||||
if (flags &Delay)
|
if (flags &Delay)
|
||||||
{
|
{
|
||||||
@@ -799,12 +1010,8 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
|
|||||||
str);
|
str);
|
||||||
bounding_rect = bounding_rect.united(text_rect);
|
bounding_rect = bounding_rect.united(text_rect);
|
||||||
|
|
||||||
if (m_hovered_contacts_map.contains(elmt)) {
|
if (m_update_map)
|
||||||
m_hovered_contacts_map.insert(elmt, bounding_rect);
|
m_hovered_contacts_map.insert(elmt, text_rect);
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_hovered_contacts_map.insert(elmt, bounding_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
//a switch contact take place of two normal contact
|
//a switch contact take place of two normal contact
|
||||||
m_drawed_contacts += 2;
|
m_drawed_contacts += 2;
|
||||||
@@ -835,12 +1042,8 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt)
|
|||||||
str);
|
str);
|
||||||
bounding_rect = bounding_rect.united(text_rect);
|
bounding_rect = bounding_rect.united(text_rect);
|
||||||
|
|
||||||
if (m_hovered_contacts_map.contains(elmt)) {
|
if (m_update_map)
|
||||||
m_hovered_contacts_map.insert(elmt, bounding_rect);
|
m_hovered_contacts_map.insert(elmt, text_rect);
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_hovered_contacts_map.insert(elmt, bounding_rect);
|
|
||||||
}
|
|
||||||
++m_drawed_contacts;
|
++m_drawed_contacts;
|
||||||
}
|
}
|
||||||
return bounding_rect;
|
return bounding_rect;
|
||||||
@@ -866,7 +1069,60 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
|
|||||||
m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black);
|
m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black);
|
||||||
painter.setPen(pen);
|
painter.setPen(pen);
|
||||||
|
|
||||||
|
// Collect terminal names for NO column.
|
||||||
|
// Power: all terminals sorted numerically.
|
||||||
|
// SW with typed terminals: NO+Common pair.
|
||||||
|
// Others: first 2 named terminals.
|
||||||
|
const bool is_power_no =
|
||||||
|
elmt->kindInformations()["type"].toString() == "power";
|
||||||
|
const bool is_sw_no =
|
||||||
|
elmt->kindInformations()["state"].toString() == "SW";
|
||||||
|
QStringList tnames;
|
||||||
|
if (is_sw_no) {
|
||||||
|
bool has_typed = false;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (t->terminalType() == TerminalData::No ||
|
||||||
|
t->terminalType() == TerminalData::Nc ||
|
||||||
|
t->terminalType() == TerminalData::Common) {
|
||||||
|
has_typed = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_typed) {
|
||||||
|
QString no_name, common_name;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (!t->name().isEmpty()) {
|
||||||
|
if (t->terminalType() == TerminalData::No) no_name = t->name();
|
||||||
|
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tnames << no_name << common_name;
|
||||||
|
} else {
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tn = t->name();
|
||||||
|
if (!tn.isEmpty()) { tnames << tn; if (tnames.size() >= 2) break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tn = t->name();
|
||||||
|
if (!tn.isEmpty()) {
|
||||||
|
tnames << tn;
|
||||||
|
if (!is_power_no && tnames.size() >= 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString terminal_label;
|
||||||
|
if (!tnames.isEmpty() && m_properties.showTerminalName())
|
||||||
|
terminal_label = QStringLiteral("[") + tnames.join("-") + QStringLiteral("]");
|
||||||
|
|
||||||
QString str = elementPositionText(elmt, true);
|
QString str = elementPositionText(elmt, true);
|
||||||
|
if (!terminal_label.isEmpty())
|
||||||
|
str = terminal_label + QStringLiteral(" ") + str;
|
||||||
|
|
||||||
QRectF bounding = painter.boundingRect(
|
QRectF bounding = painter.boundingRect(
|
||||||
QRectF(no_top_left,
|
QRectF(no_top_left,
|
||||||
QSize(middle_cross, 1)),
|
QSize(middle_cross, 1)),
|
||||||
@@ -874,13 +1130,11 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
|
|||||||
str);
|
str);
|
||||||
painter.drawText(bounding, Qt::AlignLeft, str);
|
painter.drawText(bounding, Qt::AlignLeft, str);
|
||||||
|
|
||||||
if (m_hovered_contacts_map.contains(elmt))
|
if (m_update_map) {
|
||||||
{
|
QString pos_str = elementPositionText(elmt, true);
|
||||||
m_hovered_contacts_map.insert(elmt, bounding);
|
QRectF pos_rect = painter.boundingRect(bounding, Qt::AlignRight, pos_str);
|
||||||
}
|
pos_rect.adjust(-2, -1, 2, 1); // extend hit area slightly
|
||||||
else
|
m_hovered_contacts_map.insert(elmt, pos_rect);
|
||||||
{
|
|
||||||
m_hovered_contacts_map.insert(elmt, bounding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
no_top_left.ry() += bounding.height();
|
no_top_left.ry() += bounding.height();
|
||||||
@@ -895,7 +1149,60 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
|
|||||||
:pen.setColor(Qt::black);
|
:pen.setColor(Qt::black);
|
||||||
painter.setPen(pen);
|
painter.setPen(pen);
|
||||||
|
|
||||||
|
// Collect terminal names for NC column.
|
||||||
|
// Power: all terminals sorted numerically.
|
||||||
|
// SW with typed terminals: NC+Common pair.
|
||||||
|
// Others: first 2 named terminals.
|
||||||
|
const bool is_power_nc =
|
||||||
|
elmt->kindInformations()["type"].toString() == "power";
|
||||||
|
const bool is_sw_nc =
|
||||||
|
elmt->kindInformations()["state"].toString() == "SW";
|
||||||
|
QStringList tnames_nc;
|
||||||
|
if (is_sw_nc) {
|
||||||
|
bool has_typed = false;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (t->terminalType() == TerminalData::No ||
|
||||||
|
t->terminalType() == TerminalData::Nc ||
|
||||||
|
t->terminalType() == TerminalData::Common) {
|
||||||
|
has_typed = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_typed) {
|
||||||
|
QString nc_name, common_name;
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
if (!t->name().isEmpty()) {
|
||||||
|
if (t->terminalType() == TerminalData::Nc) nc_name = t->name();
|
||||||
|
else if (t->terminalType() == TerminalData::Common) common_name = t->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tnames_nc << nc_name << common_name;
|
||||||
|
} else {
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tn = t->name();
|
||||||
|
if (!tn.isEmpty()) { tnames_nc << tn; if (tnames_nc.size() >= 2) break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Terminal *t : elmt->terminals()) {
|
||||||
|
if (!t) continue;
|
||||||
|
const QString tn = t->name();
|
||||||
|
if (!tn.isEmpty()) {
|
||||||
|
tnames_nc << tn;
|
||||||
|
if (!is_power_nc && tnames_nc.size() >= 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString terminal_label;
|
||||||
|
if (!tnames_nc.isEmpty() && m_properties.showTerminalName())
|
||||||
|
terminal_label = QStringLiteral("[") + tnames_nc.join("-") + QStringLiteral("]");
|
||||||
|
|
||||||
QString str = elementPositionText(elmt, true);
|
QString str = elementPositionText(elmt, true);
|
||||||
|
if (!terminal_label.isEmpty())
|
||||||
|
str = terminal_label + QStringLiteral(" ") + str;
|
||||||
|
|
||||||
QRectF bounding = painter.boundingRect(
|
QRectF bounding = painter.boundingRect(
|
||||||
QRectF(nc_top_left,
|
QRectF(nc_top_left,
|
||||||
QSize(middle_cross, 1)),
|
QSize(middle_cross, 1)),
|
||||||
@@ -903,13 +1210,11 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
|
|||||||
str);
|
str);
|
||||||
painter.drawText(bounding, Qt::AlignRight, str);
|
painter.drawText(bounding, Qt::AlignRight, str);
|
||||||
|
|
||||||
if (m_hovered_contacts_map.contains(elmt))
|
if (m_update_map) {
|
||||||
{
|
QString pos_str = elementPositionText(elmt, true);
|
||||||
m_hovered_contacts_map.insert(elmt, bounding);
|
QRectF pos_rect = painter.boundingRect(bounding, Qt::AlignRight, pos_str);
|
||||||
}
|
pos_rect.adjust(-2, -1, 2, 1); // extend hit area slightly
|
||||||
else
|
m_hovered_contacts_map.insert(elmt, pos_rect);
|
||||||
{
|
|
||||||
m_hovered_contacts_map.insert(elmt, bounding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nc_top_left.ry() += bounding.height();
|
nc_top_left.ry() += bounding.height();
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
#include <QGraphicsObject>
|
#include <QGraphicsObject>
|
||||||
#include <QMultiMap>
|
#include <QMultiMap>
|
||||||
#include <QPicture>
|
|
||||||
|
|
||||||
class Element;
|
class Element;
|
||||||
class DynamicElementTextItem;
|
class DynamicElementTextItem;
|
||||||
@@ -103,11 +102,11 @@ class CrossRefItem : public QGraphicsObject
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void linkedChanged();
|
void linkedChanged();
|
||||||
void buildHeaderContact();
|
void buildHeaderContact(QPainter &painter, QPointF no_pos, QPointF nc_pos);
|
||||||
void setUpCrossBoundingRect(QPainter &painter);
|
void setUpCrossBoundingRect(QPainter &painter);
|
||||||
void drawAsCross(QPainter &painter);
|
void drawAsCross(QPainter &painter);
|
||||||
void drawAsContacts(QPainter &painter);
|
void drawAsContacts(QPainter &painter);
|
||||||
QRectF drawContact(QPainter &painter, int flags, Element *elmt);
|
QRectF drawContact(QPainter &painter, int flags, Element *elmt, int pole_index = 0);
|
||||||
void fillCrossRef(QPainter &painter);
|
void fillCrossRef(QPainter &painter);
|
||||||
void AddExtraInfo(QPainter &painter, const QString&);
|
void AddExtraInfo(QPainter &painter, const QString&);
|
||||||
QList<Element *> NOElements() const;
|
QList<Element *> NOElements() const;
|
||||||
@@ -117,16 +116,22 @@ class CrossRefItem : public QGraphicsObject
|
|||||||
private:
|
private:
|
||||||
Element *m_element; //element to display the cross reference
|
Element *m_element; //element to display the cross reference
|
||||||
QRectF m_bounding_rect;
|
QRectF m_bounding_rect;
|
||||||
QPicture m_drawing, m_hdr_no_ctc, m_hdr_nc_ctc;
|
|
||||||
QPainterPath m_shape_path;
|
QPainterPath m_shape_path;
|
||||||
XRefProperties m_properties;
|
XRefProperties m_properties;
|
||||||
int m_drawed_contacts;
|
int m_drawed_contacts;
|
||||||
|
bool m_update_map = false;
|
||||||
QMultiMap <Element *, QRectF> m_hovered_contacts_map;
|
QMultiMap <Element *, QRectF> m_hovered_contacts_map;
|
||||||
Element *m_hovered_contact = nullptr;
|
Element *m_hovered_contact = nullptr;
|
||||||
DynamicElementTextItem *m_text = nullptr;
|
DynamicElementTextItem *m_text = nullptr;
|
||||||
ElementTextItemGroup *m_group = nullptr;
|
ElementTextItemGroup *m_group = nullptr;
|
||||||
QList <QMetaObject::Connection> m_slave_connection;
|
QList <QMetaObject::Connection> m_slave_connection;
|
||||||
QList <QMetaObject::Connection> m_update_connection;
|
QList <QMetaObject::Connection> m_update_connection;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Returns the map of linked elements and their clickable rects (local coords).
|
||||||
|
/// Used by the PDF export to inject hyperlink annotations.
|
||||||
|
const QMultiMap<Element *, QRectF> &hoveredContactsMap() const
|
||||||
|
{ return m_hovered_contacts_map; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CROSSREFITEM_H
|
#endif // CROSSREFITEM_H
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
#include "crossrefitem.h"
|
#include "crossrefitem.h"
|
||||||
#include "element.h"
|
#include "element.h"
|
||||||
#include "elementtextitemgroup.h"
|
#include "elementtextitemgroup.h"
|
||||||
|
#include <QTimer>
|
||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
@@ -302,7 +302,8 @@ void DynamicElementTextItem::refreshLabelConnection()
|
|||||||
if ((m_text_from == ElementInfo && m_info_name == "label") ||
|
if ((m_text_from == ElementInfo && m_info_name == "label") ||
|
||||||
(m_text_from == CompositeText && m_composite_text.contains("%{label}")))
|
(m_text_from == CompositeText && m_composite_text.contains("%{label}")))
|
||||||
{
|
{
|
||||||
if(m_parent_element.data()->linkType() & Element::AllReport)
|
if((m_parent_element.data()->linkType() & Element::AllReport) ||
|
||||||
|
m_parent_element.data()->linkType() == Element::ConductorDefinition)
|
||||||
{
|
{
|
||||||
updateReportFormulaConnection();
|
updateReportFormulaConnection();
|
||||||
updateReportText();
|
updateReportText();
|
||||||
@@ -418,7 +419,8 @@ void DynamicElementTextItem::setInfoName(const QString &info_name)
|
|||||||
updateXref();
|
updateXref();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_parent_element && (m_parent_element.data()->linkType() & Element::AllReport)) //special treatment for report
|
if (m_parent_element && ((m_parent_element.data()->linkType() & Element::AllReport) ||
|
||||||
|
m_parent_element.data()->linkType() == Element::ConductorDefinition)) //special treatment for report
|
||||||
{
|
{
|
||||||
if(old_info_name != info_name)
|
if(old_info_name != info_name)
|
||||||
{
|
{
|
||||||
@@ -472,7 +474,8 @@ void DynamicElementTextItem::setCompositeText(const QString &text)
|
|||||||
updateXref();
|
updateXref();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_parent_element && (m_parent_element.data()->linkType() & Element::AllReport)) //special treatment for report
|
if (m_parent_element && ((m_parent_element.data()->linkType() & Element::AllReport) ||
|
||||||
|
m_parent_element.data()->linkType() == Element::ConductorDefinition)) //special treatment for report
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* May be in some case the old and new composite text both have the var %{label},
|
* May be in some case the old and new composite text both have the var %{label},
|
||||||
@@ -726,28 +729,29 @@ QVariant DynamicElementTextItem::itemChange(QGraphicsItem::GraphicsItemChange ch
|
|||||||
if(!m_parent_element.data()->linkedElements().isEmpty())
|
if(!m_parent_element.data()->linkedElements().isEmpty())
|
||||||
masterChanged();
|
masterChanged();
|
||||||
}
|
}
|
||||||
else if(m_parent_element.data()->linkType() & Element::AllReport)
|
else if((m_parent_element.data()->linkType() & Element::AllReport) ||
|
||||||
|
m_parent_element.data()->linkType() == Element::ConductorDefinition)
|
||||||
{
|
{
|
||||||
//Get the report formula, and add connection to keep up to date the formula.
|
//Get the report formula, and add connection to keep up to date the formula.
|
||||||
if (m_parent_element.data()->diagram() && m_parent_element.data()->diagram()->project())
|
if (m_parent_element.data()->diagram() && m_parent_element.data()->diagram()->project())
|
||||||
{
|
{
|
||||||
m_report_formula = m_parent_element.data()->diagram()->project()->defaultReportProperties();
|
m_report_formula = m_parent_element.data()->diagram()->project()->defaultReportProperties();
|
||||||
m_report_formula_con = connect(m_parent_element.data()->diagram()->project(), &QETProject::reportPropertiesChanged, this, &DynamicElementTextItem::reportFormulaChanged);
|
m_report_formula_con = connect(m_parent_element.data()->diagram()->project(), &QETProject::reportPropertiesChanged, this, &DynamicElementTextItem::reportFormulaChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add connection to keep up to date the status of the element linked to the parent folio report of this text.
|
//Add connection to keep up to date the status of the element linked to the parent folio report of this text.
|
||||||
connect(m_parent_element.data(), &Element::linkedElementChanged, this, &DynamicElementTextItem::reportChanged);
|
connect(m_parent_element.data(), &Element::linkedElementChanged, this, &DynamicElementTextItem::reportChanged);
|
||||||
//The parent is already linked, we call reportChanged for init the connection
|
//The parent is already linked, we call reportChanged for init the connection
|
||||||
if(!m_parent_element.data()->linkedElements().isEmpty())
|
if(!m_parent_element.data()->linkedElements().isEmpty())
|
||||||
reportChanged();
|
reportChanged();
|
||||||
|
|
||||||
if(m_parent_element.data()->terminals().size())
|
if(m_parent_element.data()->terminals().size())
|
||||||
{
|
{
|
||||||
//Add connection to keep up date the conductors added or removed to the parent folio report element
|
//Add connection to keep up date the conductors added or removed to the parent folio report element
|
||||||
connect(m_parent_element.data()->terminals().first(), &Terminal::conductorWasAdded, this, &DynamicElementTextItem::conductorWasAdded);
|
connect(m_parent_element.data()->terminals().first(), &Terminal::conductorWasAdded, this, &DynamicElementTextItem::conductorWasAdded);
|
||||||
connect(m_parent_element.data()->terminals().first(), &Terminal::conductorWasRemoved, this, &DynamicElementTextItem::conductorWasRemoved);
|
connect(m_parent_element.data()->terminals().first(), &Terminal::conductorWasRemoved, this, &DynamicElementTextItem::conductorWasRemoved);
|
||||||
}
|
}
|
||||||
//Get a conductor in the potential
|
//Get a conductor in the potential
|
||||||
setPotentialConductor();
|
setPotentialConductor();
|
||||||
}
|
}
|
||||||
else if(m_parent_element.data()->linkType() == Element::Master)
|
else if(m_parent_element.data()->linkType() == Element::Master)
|
||||||
@@ -1027,7 +1031,8 @@ void DynamicElementTextItem::clearFormulaConnection()
|
|||||||
|
|
||||||
void DynamicElementTextItem::updateReportFormulaConnection()
|
void DynamicElementTextItem::updateReportFormulaConnection()
|
||||||
{
|
{
|
||||||
if(!(m_parent_element.data()->linkType() & Element::AllReport))
|
if(!(m_parent_element.data()->linkType() & Element::AllReport) &&
|
||||||
|
m_parent_element.data()->linkType() != Element::ConductorDefinition)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
removeConnectionForReportFormula(m_report_formula);
|
removeConnectionForReportFormula(m_report_formula);
|
||||||
@@ -1041,7 +1046,8 @@ void DynamicElementTextItem::updateReportFormulaConnection()
|
|||||||
*/
|
*/
|
||||||
void DynamicElementTextItem::updateReportText()
|
void DynamicElementTextItem::updateReportText()
|
||||||
{
|
{
|
||||||
if(!(m_parent_element.data()->linkType() & Element::AllReport))
|
if(!(m_parent_element.data()->linkType() & Element::AllReport) &&
|
||||||
|
m_parent_element.data()->linkType() != Element::ConductorDefinition)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_text_from == ElementInfo && m_info_name == "label")
|
if (m_text_from == ElementInfo && m_info_name == "label")
|
||||||
@@ -1098,7 +1104,10 @@ void DynamicElementTextItem::updateLabel()
|
|||||||
void DynamicElementTextItem::conductorWasAdded(Conductor *conductor)
|
void DynamicElementTextItem::conductorWasAdded(Conductor *conductor)
|
||||||
{
|
{
|
||||||
Q_UNUSED(conductor)
|
Q_UNUSED(conductor)
|
||||||
setPotentialConductor();
|
QTimer::singleShot(100, this, [this]() {
|
||||||
|
setPotentialConductor();
|
||||||
|
conductorPropertiesChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1123,7 +1132,8 @@ void DynamicElementTextItem::conductorWasRemoved(Conductor *conductor)
|
|||||||
*/
|
*/
|
||||||
void DynamicElementTextItem::setPotentialConductor()
|
void DynamicElementTextItem::setPotentialConductor()
|
||||||
{
|
{
|
||||||
if(parentElement() && (parentElement()->linkType() & Element::AllReport))
|
if(parentElement() && ((parentElement()->linkType() & Element::AllReport) || (parentElement()->linkType() == Element::ConductorDefinition) ||
|
||||||
|
parentElement()->linkType() == Element::ConductorDefinition))
|
||||||
{
|
{
|
||||||
if(parentElement()->terminals().isEmpty())
|
if(parentElement()->terminals().isEmpty())
|
||||||
return;
|
return;
|
||||||
@@ -1156,6 +1166,7 @@ void DynamicElementTextItem::setPotentialConductor()
|
|||||||
connect(m_watched_conductor.data(), &Conductor::propertiesChange, this, &DynamicElementTextItem::conductorPropertiesChanged);
|
connect(m_watched_conductor.data(), &Conductor::propertiesChange, this, &DynamicElementTextItem::conductorPropertiesChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
conductorPropertiesChanged();
|
||||||
}
|
}
|
||||||
else //This text haven't got a parent element, then ther isn't a conductor in the potential
|
else //This text haven't got a parent element, then ther isn't a conductor in the potential
|
||||||
{
|
{
|
||||||
@@ -1172,7 +1183,8 @@ void DynamicElementTextItem::setPotentialConductor()
|
|||||||
*/
|
*/
|
||||||
void DynamicElementTextItem::conductorPropertiesChanged()
|
void DynamicElementTextItem::conductorPropertiesChanged()
|
||||||
{
|
{
|
||||||
if(m_parent_element && (m_parent_element.data()->linkType() & Element::AllReport))
|
if(m_parent_element && ((m_parent_element.data()->linkType() & Element::AllReport) || (m_parent_element.data()->linkType() == Element::ConductorDefinition) ||
|
||||||
|
m_parent_element.data()->linkType() == Element::ConductorDefinition))
|
||||||
{
|
{
|
||||||
if(m_text_from == ElementInfo)
|
if(m_text_from == ElementInfo)
|
||||||
{
|
{
|
||||||
@@ -1200,8 +1212,9 @@ void DynamicElementTextItem::conductorPropertiesChanged()
|
|||||||
QString DynamicElementTextItem::reportReplacedCompositeText() const
|
QString DynamicElementTextItem::reportReplacedCompositeText() const
|
||||||
{
|
{
|
||||||
QString string;
|
QString string;
|
||||||
|
|
||||||
if(m_parent_element.data()->linkType() & Element::AllReport)
|
if((m_parent_element.data()->linkType() & Element::AllReport) || (m_parent_element.data()->linkType() == Element::ConductorDefinition) ||
|
||||||
|
m_parent_element.data()->linkType() == Element::ConductorDefinition)
|
||||||
{
|
{
|
||||||
string = m_composite_text;
|
string = m_composite_text;
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ class DynamicElementTextItem : public DiagramTextItem
|
|||||||
void fromXml(const QDomElement &dom_elmt) override;
|
void fromXml(const QDomElement &dom_elmt) override;
|
||||||
|
|
||||||
Element *parentElement() const;
|
Element *parentElement() const;
|
||||||
|
/// PDF export: slave cross-reference text item ("(folio-pos)") and its master target.
|
||||||
|
QGraphicsTextItem *slaveXrefItem() const { return m_slave_Xref_item; }
|
||||||
|
Element *masterElement() const { return m_master_element.data(); }
|
||||||
ElementTextItemGroup *parentGroup() const;
|
ElementTextItemGroup *parentGroup() const;
|
||||||
Element *elementUseForInfo() const;
|
Element *elementUseForInfo() const;
|
||||||
void refreshLabelConnection();
|
void refreshLabelConnection();
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "../qetxml.h"
|
#include "../qetxml.h"
|
||||||
#include "../qetversion.h"
|
#include "../qetversion.h"
|
||||||
#include "qgraphicsitemutility.h"
|
#include "qgraphicsitemutility.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -431,6 +432,11 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
|
|||||||
m_data.fromXml(xml_def_elmt);
|
m_data.fromXml(xml_def_elmt);
|
||||||
setToolTip(name());
|
setToolTip(name());
|
||||||
|
|
||||||
|
QString my_type_str = xml_def_elmt.attribute(QStringLiteral("link_type"), QStringLiteral("simple"));
|
||||||
|
if (my_type_str == QLatin1String("conductor_definition")) {
|
||||||
|
m_link_type = Element::ConductorDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
//load kind informations
|
//load kind informations
|
||||||
m_kind_informations.fromXml(
|
m_kind_informations.fromXml(
|
||||||
xml_def_elmt.firstChildElement(QStringLiteral("kindInformations")),
|
xml_def_elmt.firstChildElement(QStringLiteral("kindInformations")),
|
||||||
@@ -627,6 +633,9 @@ Terminal *Element::parseTerminal(const QDomElement &dom_element)
|
|||||||
Terminal *new_terminal = new Terminal(data, this);
|
Terminal *new_terminal = new Terminal(data, this);
|
||||||
m_terminals << new_terminal;
|
m_terminals << new_terminal;
|
||||||
|
|
||||||
|
connect(new_terminal, &Terminal::conductorWasAdded, this, &Element::updateConductorTexts);
|
||||||
|
connect(new_terminal, &Terminal::conductorWasRemoved, this, &Element::updateConductorTexts);
|
||||||
|
|
||||||
//Sort from top to bottom and left to right
|
//Sort from top to bottom and left to right
|
||||||
std::sort(m_terminals.begin(),
|
std::sort(m_terminals.begin(),
|
||||||
m_terminals.end(),
|
m_terminals.end(),
|
||||||
@@ -1288,6 +1297,8 @@ QString Element::linkTypeToString() const
|
|||||||
return QStringLiteral("Terminale");
|
return QStringLiteral("Terminale");
|
||||||
case Thumbnail:
|
case Thumbnail:
|
||||||
return QStringLiteral("Thumbnail");
|
return QStringLiteral("Thumbnail");
|
||||||
|
case ConductorDefinition:
|
||||||
|
return QStringLiteral("ConductorDefinition");
|
||||||
default:
|
default:
|
||||||
return QStringLiteral("Unknown");
|
return QStringLiteral("Unknown");
|
||||||
}
|
}
|
||||||
@@ -1555,3 +1566,25 @@ ElementsLocation Element::location() const
|
|||||||
{
|
{
|
||||||
return m_location;
|
return m_location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Element::updateConductorTexts
|
||||||
|
*Slot that is triggered when a cable is *
|
||||||
|
*connected to or disconnected from a terminal on this component.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Element::updateConductorTexts
|
||||||
|
*/
|
||||||
|
void Element::updateConductorTexts()
|
||||||
|
{
|
||||||
|
if (m_link_type != Element::ConductorDefinition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DynamicElementTextItem *deti : m_dynamic_text_list) {
|
||||||
|
if (deti) {
|
||||||
|
deti->setPotentialConductor();
|
||||||
|
deti->updateLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,14 +52,15 @@ class Element : public QetGraphicsItem
|
|||||||
(master, slave, report ect...)
|
(master, slave, report ect...)
|
||||||
*/
|
*/
|
||||||
enum kind {
|
enum kind {
|
||||||
Simple = 1,
|
Simple = 1,
|
||||||
NextReport = 2,
|
NextReport = 2,
|
||||||
PreviousReport = 4,
|
PreviousReport = 4,
|
||||||
AllReport = 6,
|
AllReport = 6,
|
||||||
Master = 8,
|
Master = 8,
|
||||||
Slave = 16,
|
Slave = 16,
|
||||||
Terminale = 32,
|
Terminale = 32,
|
||||||
Thumbnail = 64};
|
Thumbnail = 64,
|
||||||
|
ConductorDefinition = 128};
|
||||||
|
|
||||||
Element(const ElementsLocation &location,
|
Element(const ElementsLocation &location,
|
||||||
QGraphicsItem * = nullptr,
|
QGraphicsItem * = nullptr,
|
||||||
@@ -95,6 +96,8 @@ class Element : public QetGraphicsItem
|
|||||||
DynamicElementTextItem *text,
|
DynamicElementTextItem *text,
|
||||||
ElementTextItemGroup *group);
|
ElementTextItemGroup *group);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateConductorTexts();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QList<Terminal *> terminals() const;
|
QList<Terminal *> terminals() const;
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ class ElementTextItemGroup : public QObject, public QGraphicsItemGroup
|
|||||||
QList<DynamicElementTextItem *> texts() const;
|
QList<DynamicElementTextItem *> texts() const;
|
||||||
Diagram *diagram() const;
|
Diagram *diagram() const;
|
||||||
Element *parentElement() const;
|
Element *parentElement() const;
|
||||||
|
/// PDF export: slave cross-reference text item of the group, if any.
|
||||||
|
QGraphicsTextItem *slaveXrefItem() const { return m_slave_Xref_item; }
|
||||||
|
|
||||||
QDomElement toXml(QDomDocument &dom_document) const;
|
QDomElement toXml(QDomDocument &dom_document) const;
|
||||||
void fromXml(QDomElement &dom_element);
|
void fromXml(QDomElement &dom_element);
|
||||||
|
|||||||
@@ -753,6 +753,15 @@ QString Terminal::name() const
|
|||||||
return d->m_name;
|
return d->m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Terminal::terminalType
|
||||||
|
@return the type of this terminal (Generic, Inner, Outer, No, Nc, Common)
|
||||||
|
*/
|
||||||
|
TerminalData::Type Terminal::terminalType() const
|
||||||
|
{
|
||||||
|
return d->m_type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Conductor::relatedPotentialTerminal
|
@brief Conductor::relatedPotentialTerminal
|
||||||
Return terminal at the same potential from the same
|
Return terminal at the same potential from the same
|
||||||
@@ -779,6 +788,12 @@ QList<Terminal *> relatedPotentialTerminal (
|
|||||||
// If terminal parent element is a Terminal element.
|
// If terminal parent element is a Terminal element.
|
||||||
else if (terminal -> parentElement() -> linkType() & Element::Terminale)
|
else if (terminal -> parentElement() -> linkType() & Element::Terminale)
|
||||||
{
|
{
|
||||||
|
// English: Check if the user activated the potential isolation checkbox for this terminal
|
||||||
|
if (terminal->parentElement()->elementInformations().value(QStringLiteral("potential_isolating")).toString() == QLatin1String("true")) {
|
||||||
|
// English: Potential is isolated. Return an empty list so it does not propagate to the other side.
|
||||||
|
return QList<Terminal *>();
|
||||||
|
}
|
||||||
|
|
||||||
QList <Terminal *> terminals = terminal->parentElement()->terminals();
|
QList <Terminal *> terminals = terminal->parentElement()->terminals();
|
||||||
terminals.removeAll(const_cast<Terminal *>(terminal));
|
terminals.removeAll(const_cast<Terminal *>(terminal));
|
||||||
return terminals;
|
return terminals;
|
||||||
|
|||||||
@@ -18,13 +18,14 @@
|
|||||||
#ifndef TERMINAL_H
|
#ifndef TERMINAL_H
|
||||||
#define TERMINAL_H
|
#define TERMINAL_H
|
||||||
#include "../qet.h"
|
#include "../qet.h"
|
||||||
|
#include "../properties/terminaldata.h"
|
||||||
|
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
#include <QtXml>
|
#include <QtXml>
|
||||||
class Conductor;
|
class Conductor;
|
||||||
class Diagram;
|
class Diagram;
|
||||||
class Element;
|
class Element;
|
||||||
class TerminalData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief The Terminal class
|
@brief The Terminal class
|
||||||
@@ -75,6 +76,7 @@ class Terminal : public QGraphicsObject
|
|||||||
Element *parentElement () const;
|
Element *parentElement () const;
|
||||||
QUuid uuid () const;
|
QUuid uuid () const;
|
||||||
QString name () const;
|
QString name () const;
|
||||||
|
TerminalData::Type terminalType() const;
|
||||||
|
|
||||||
QList<Conductor *> conductors() const;
|
QList<Conductor *> conductors() const;
|
||||||
Qet::Orientation orientation() const;
|
Qet::Orientation orientation() const;
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ QStringList QETInformation::elementInfoKeys()
|
|||||||
ELMT_MACHINE_MANUFACTURER_REF_AUX4,
|
ELMT_MACHINE_MANUFACTURER_REF_AUX4,
|
||||||
ELMT_SUPPLIER_AUX4,
|
ELMT_SUPPLIER_AUX4,
|
||||||
ELMT_QUANTITY_AUX4,
|
ELMT_QUANTITY_AUX4,
|
||||||
ELMT_UNITY_AUX4, };
|
ELMT_UNITY_AUX4,
|
||||||
|
"exclude_from_bom" };
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +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 "elementinfowidget.h"
|
#include "elementinfowidget.h"
|
||||||
|
#include <QCheckBox>
|
||||||
#include "../diagram.h"
|
#include "../diagram.h"
|
||||||
#include "../qetapp.h"
|
#include "../qetapp.h"
|
||||||
#include "../qetgraphicsitem/element.h"
|
#include "../qetgraphicsitem/element.h"
|
||||||
@@ -161,6 +161,13 @@ void ElementInfoWidget::enableLiveEdit()
|
|||||||
for (ElementInfoPartWidget *eipw : m_eipw_list)
|
for (ElementInfoPartWidget *eipw : m_eipw_list)
|
||||||
connect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
|
connect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
|
||||||
connect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
connect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||||
|
|
||||||
|
if (m_potential_isolating_cb) {
|
||||||
|
connect(m_potential_isolating_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||||
|
}
|
||||||
|
if (m_exclude_from_bom_cb) {
|
||||||
|
connect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,6 +179,13 @@ void ElementInfoWidget::disableLiveEdit()
|
|||||||
for (ElementInfoPartWidget *eipw : m_eipw_list)
|
for (ElementInfoPartWidget *eipw : m_eipw_list)
|
||||||
disconnect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
|
disconnect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
|
||||||
disconnect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
disconnect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||||
|
|
||||||
|
if (m_potential_isolating_cb) {
|
||||||
|
disconnect(m_potential_isolating_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||||
|
}
|
||||||
|
if (m_exclude_from_bom_cb) {
|
||||||
|
disconnect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,16 +207,34 @@ void ElementInfoWidget::buildInterface()
|
|||||||
ui->scroll_vlayout->addWidget(eipw);
|
ui->scroll_vlayout->addWidget(eipw);
|
||||||
m_eipw_list << eipw;
|
m_eipw_list << eipw;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->scroll_vlayout->addStretch();
|
ui->scroll_vlayout->addStretch();
|
||||||
|
|
||||||
|
// Existing potential isolating checkbox
|
||||||
|
m_potential_isolating_cb = new QCheckBox(tr("Séparation de potentiel"), this);
|
||||||
|
m_potential_isolating_cb->setStyleSheet(QStringLiteral("margin: 5px; font-weight: bold;"));
|
||||||
|
|
||||||
|
// English: Initialize and style the BOM exclusion checkbox
|
||||||
|
m_exclude_from_bom_cb = new QCheckBox(tr("Exclure de la nomenclature"), this);
|
||||||
|
m_exclude_from_bom_cb->setStyleSheet(QStringLiteral("margin: 5px; font-weight: bold;"));
|
||||||
|
|
||||||
|
if (QVBoxLayout *mainLayout = qobject_cast<QVBoxLayout*>(this->layout())) {
|
||||||
|
mainLayout->insertWidget(1, m_potential_isolating_cb);
|
||||||
|
// English: Insert the new checkbox into the main vertical layout
|
||||||
|
mainLayout->insertWidget(2, m_exclude_from_bom_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// English: BOM exclusion applies to all elements, so it's always visible
|
||||||
|
m_exclude_from_bom_cb->setVisible(true);
|
||||||
|
|
||||||
// Show checkbox only if the element is a terminal
|
// Show checkbox only if the element is a terminal
|
||||||
if (m_element.data()->elementData().m_type == ElementData::Terminal) {
|
if (m_element.data()->elementData().m_type == ElementData::Terminal) {
|
||||||
ui->m_auto_num_locked_cb->setVisible(true);
|
ui->m_auto_num_locked_cb->setVisible(true);
|
||||||
|
m_potential_isolating_cb->setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
ui->m_auto_num_locked_cb->setVisible(false);
|
ui->m_auto_num_locked_cb->setVisible(false);
|
||||||
|
m_potential_isolating_cb->setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief ElementInfoWidget::infoPartWidgetForKey
|
@brief ElementInfoWidget::infoPartWidgetForKey
|
||||||
@param key
|
@param key
|
||||||
@@ -241,8 +273,13 @@ void ElementInfoWidget::updateUi()
|
|||||||
}
|
}
|
||||||
// Load the lock status for auto numbering
|
// Load the lock status for auto numbering
|
||||||
if (m_element->elementData().m_type == ElementData::Terminal) {
|
if (m_element->elementData().m_type == ElementData::Terminal) {
|
||||||
QString lock_value = element_info.value(QStringLiteral("auto_num_locked")).toString();
|
// ... (bestehender Terminal-Code für auto_num_locked und potential_isolating) ...
|
||||||
ui->m_auto_num_locked_cb->setChecked(lock_value == QLatin1String("true"));
|
}
|
||||||
|
|
||||||
|
// English: Load the BOM exclusion status from the element information mapping
|
||||||
|
if (m_exclude_from_bom_cb) {
|
||||||
|
QString exclude_bom_value = element_info.value(QStringLiteral("exclude_from_bom")).toString();
|
||||||
|
m_exclude_from_bom_cb->setChecked(exclude_bom_value == QLatin1String("true"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_live_edit) {
|
if (m_live_edit) {
|
||||||
@@ -274,11 +311,14 @@ DiagramContext ElementInfoWidget::currentInfo() const
|
|||||||
|
|
||||||
// Save the auto numbering lock status
|
// Save the auto numbering lock status
|
||||||
if (m_element->elementData().m_type == ElementData::Terminal) {
|
if (m_element->elementData().m_type == ElementData::Terminal) {
|
||||||
info_.addValue(QStringLiteral("auto_num_locked"), ui->m_auto_num_locked_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_exclude_from_bom_cb) {
|
||||||
|
info_.addValue(QStringLiteral("exclude_from_bom"), m_exclude_from_bom_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
|
||||||
|
}
|
||||||
|
|
||||||
return info_;
|
return info_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief ElementInfoWidget::firstActivated
|
@brief ElementInfoWidget::firstActivated
|
||||||
Slot activated when this widget is show.
|
Slot activated when this widget is show.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class Element;
|
|||||||
class QUndoCommand;
|
class QUndoCommand;
|
||||||
class ElementInfoPartWidget;
|
class ElementInfoPartWidget;
|
||||||
class ChangeElementInformationCommand;
|
class ChangeElementInformationCommand;
|
||||||
|
class QCheckBox;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ElementInfoWidget;
|
class ElementInfoWidget;
|
||||||
@@ -71,6 +72,8 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
|
|||||||
private:
|
private:
|
||||||
Ui::ElementInfoWidget *ui;
|
Ui::ElementInfoWidget *ui;
|
||||||
QList <ElementInfoPartWidget *> m_eipw_list;
|
QList <ElementInfoPartWidget *> m_eipw_list;
|
||||||
|
QCheckBox *m_potential_isolating_cb = nullptr;
|
||||||
|
QCheckBox *m_exclude_from_bom_cb = nullptr;
|
||||||
bool m_first_activation;
|
bool m_first_activation;
|
||||||
bool m_ui_builded = false;
|
bool m_ui_builded = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -308,6 +308,9 @@ void ElementPropertiesWidget::updateUi()
|
|||||||
case Element::Terminale:
|
case Element::Terminale:
|
||||||
m_list_editor << new ElementInfoWidget(m_element, this);
|
m_list_editor << new ElementInfoWidget(m_element, this);
|
||||||
break;
|
break;
|
||||||
|
case Element::ConductorDefinition:
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ void XRefPropertiesWidget::saveProperties(int index) {
|
|||||||
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "right") xrp.setXrefPos(Qt::AlignRight);
|
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "right") xrp.setXrefPos(Qt::AlignRight);
|
||||||
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "alignment") xrp.setXrefPos(Qt::AlignBaseline);
|
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "alignment") xrp.setXrefPos(Qt::AlignBaseline);
|
||||||
xrp.setShowPowerContac(ui->m_show_power_cb->isChecked());
|
xrp.setShowPowerContac(ui->m_show_power_cb->isChecked());
|
||||||
|
xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked());
|
||||||
xrp.setPrefix("power", ui->m_power_prefix_le->text());
|
xrp.setPrefix("power", ui->m_power_prefix_le->text());
|
||||||
xrp.setPrefix("delay", ui->m_delay_prefix_le->text());
|
xrp.setPrefix("delay", ui->m_delay_prefix_le->text());
|
||||||
xrp.setPrefix("switch", ui->m_switch_prefix_le->text());
|
xrp.setPrefix("switch", ui->m_switch_prefix_le->text());
|
||||||
@@ -190,6 +191,7 @@ void XRefPropertiesWidget::updateDisplay()
|
|||||||
else if(xrp.getXrefPos() == Qt::AlignBaseline) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("alignment"));
|
else if(xrp.getXrefPos() == Qt::AlignBaseline) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("alignment"));
|
||||||
else if(xrp.getXrefPos() == Qt::AlignBottom) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("bottom"));
|
else if(xrp.getXrefPos() == Qt::AlignBottom) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("bottom"));
|
||||||
ui->m_show_power_cb->setChecked(xrp.showPowerContact());
|
ui->m_show_power_cb->setChecked(xrp.showPowerContact());
|
||||||
|
ui->m_show_terminal_name_cb->setChecked(xrp.showTerminalName());
|
||||||
ui->m_power_prefix_le-> setText(xrp.prefix("power"));
|
ui->m_power_prefix_le-> setText(xrp.prefix("power"));
|
||||||
ui->m_delay_prefix_le-> setText(xrp.prefix("delay"));
|
ui->m_delay_prefix_le-> setText(xrp.prefix("delay"));
|
||||||
ui->m_switch_prefix_le->setText(xrp.prefix("switch"));
|
ui->m_switch_prefix_le->setText(xrp.prefix("switch"));
|
||||||
|
|||||||
@@ -108,6 +108,13 @@
|
|||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="m_show_terminal_name_cb">
|
||||||
|
<property name="text">
|
||||||
|
<string>Afficher les numéros de bornes dans les Xrefs</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="m_display_has_contacts_rb">
|
<widget class="QRadioButton" name="m_display_has_contacts_rb">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -283,6 +290,7 @@
|
|||||||
<tabstop>m_master_le</tabstop>
|
<tabstop>m_master_le</tabstop>
|
||||||
<tabstop>m_slave_le</tabstop>
|
<tabstop>m_slave_le</tabstop>
|
||||||
<tabstop>m_show_power_cb</tabstop>
|
<tabstop>m_show_power_cb</tabstop>
|
||||||
|
<tabstop>m_show_terminal_name_cb</tabstop>
|
||||||
<tabstop>m_power_prefix_le</tabstop>
|
<tabstop>m_power_prefix_le</tabstop>
|
||||||
<tabstop>m_delay_prefix_le</tabstop>
|
<tabstop>m_delay_prefix_le</tabstop>
|
||||||
<tabstop>m_switch_prefix_le</tabstop>
|
<tabstop>m_switch_prefix_le</tabstop>
|
||||||
|
|||||||
+205
-114
@@ -5,9 +5,7 @@
|
|||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QRegularExpression>
|
#include <algorithm>
|
||||||
#include <QQueue>
|
|
||||||
#include <QSet>
|
|
||||||
|
|
||||||
WiringListExport::WiringListExport(QETProject *project, QWidget *parent) :
|
WiringListExport::WiringListExport(QETProject *project, QWidget *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
@@ -45,8 +43,27 @@ QDomElement WiringListExport::climbToDiagram(QDomNode node) const
|
|||||||
QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomElement &root) const
|
QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomElement &root) const
|
||||||
{
|
{
|
||||||
QMap<QString, ElementInfo> infoMap;
|
QMap<QString, ElementInfo> infoMap;
|
||||||
QDomNodeList elements = root.elementsByTagName("element");
|
|
||||||
|
|
||||||
|
QSet<QString> placeholderTypes;
|
||||||
|
QDomElement collection = root.firstChildElement("collection");
|
||||||
|
if (!collection.isNull()) {
|
||||||
|
QDomNodeList defs = collection.elementsByTagName("definition");
|
||||||
|
for (int i = 0; i < defs.size(); ++i) {
|
||||||
|
QDomElement def = defs.at(i).toElement();
|
||||||
|
QString ltype = def.attribute("link_type");
|
||||||
|
if (ltype == "next_report" || ltype == "previous_report") {
|
||||||
|
QDomElement parentEl = def.parentNode().toElement();
|
||||||
|
if (parentEl.tagName().toLower() == "element") {
|
||||||
|
QString name = parentEl.attribute("name");
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
placeholderTypes.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QDomNodeList elements = root.elementsByTagName("element");
|
||||||
for (int i = 0; i < elements.size(); ++i) {
|
for (int i = 0; i < elements.size(); ++i) {
|
||||||
QDomElement el = elements.at(i).toElement();
|
QDomElement el = elements.at(i).toElement();
|
||||||
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
|
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
|
||||||
@@ -75,13 +92,16 @@ QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomEleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString typeVal = el.attribute("type").toLower();
|
QString typeVal = el.attribute("type");
|
||||||
if (typeVal.contains("naechste") || typeVal.contains("vorherige") ||
|
info.isPlaceholder = false;
|
||||||
typeVal.contains("next") || typeVal.contains("previous")) {
|
for (const QString &ptype : placeholderTypes) {
|
||||||
info.isPlaceholder = true;
|
if (typeVal.endsWith(ptype)) {
|
||||||
|
info.isPlaceholder = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
infoMap.insert(uuid, info);
|
infoMap.insert(uuid, info);
|
||||||
}
|
}
|
||||||
return infoMap;
|
return infoMap;
|
||||||
}
|
}
|
||||||
@@ -102,7 +122,15 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
|
|||||||
data.el2_uuid = normalizeUuid(cond.attribute("element2", cond.attribute("element2id", "")));
|
data.el2_uuid = normalizeUuid(cond.attribute("element2", cond.attribute("element2id", "")));
|
||||||
|
|
||||||
data.element1_label = cond.attribute("element1_label");
|
data.element1_label = cond.attribute("element1_label");
|
||||||
|
if (data.element1_label.isEmpty()) {
|
||||||
|
data.element1_label = cond.attribute("element1_linked");
|
||||||
|
}
|
||||||
|
|
||||||
data.element2_label = cond.attribute("element2_label");
|
data.element2_label = cond.attribute("element2_label");
|
||||||
|
if (data.element2_label.isEmpty()) {
|
||||||
|
data.element2_label = cond.attribute("element2_linked");
|
||||||
|
}
|
||||||
|
|
||||||
data.terminalname1 = cond.attribute("terminalname1");
|
data.terminalname1 = cond.attribute("terminalname1");
|
||||||
data.terminalname2 = cond.attribute("terminalname2");
|
data.terminalname2 = cond.attribute("terminalname2");
|
||||||
data.tension_protocol = cond.attribute("tension_protocol");
|
data.tension_protocol = cond.attribute("tension_protocol");
|
||||||
@@ -119,101 +147,6 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
|
|||||||
return conductors;
|
return conductors;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiringListExport::resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const
|
|
||||||
{
|
|
||||||
QRegularExpression numericLabelRe("^\\d+(\\.\\d+)?$");
|
|
||||||
|
|
||||||
QMap<QString, QList<ConductorData>> el_to_cons;
|
|
||||||
for (const ConductorData &c : conductors) {
|
|
||||||
if (!c.el1_uuid.isEmpty()) el_to_cons[c.el1_uuid].append(c);
|
|
||||||
if (!c.el2_uuid.isEmpty()) el_to_cons[c.el2_uuid].append(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < conductors.size(); ++i) {
|
|
||||||
ConductorData &c = conductors[i];
|
|
||||||
|
|
||||||
auto resolveSide = [&](const QString &startUuid, QString &outLabel, QString &outTerminal) {
|
|
||||||
if (startUuid.isEmpty() || !elementsInfo.contains(startUuid)) return;
|
|
||||||
|
|
||||||
const ElementInfo &startInfo = elementsInfo[startUuid];
|
|
||||||
if (!startInfo.links.isEmpty() || startInfo.isPlaceholder) {
|
|
||||||
QQueue<QString> q;
|
|
||||||
QSet<QString> visited;
|
|
||||||
q.enqueue(startUuid);
|
|
||||||
visited.insert(startUuid);
|
|
||||||
|
|
||||||
int depth = 0;
|
|
||||||
while (!q.isEmpty() && depth < 3) {
|
|
||||||
int levelSize = q.size();
|
|
||||||
for (int k = 0; k < levelSize; ++k) {
|
|
||||||
QString curr = q.dequeue();
|
|
||||||
|
|
||||||
if (elementsInfo.contains(curr)) {
|
|
||||||
const ElementInfo &currInfo = elementsInfo[curr];
|
|
||||||
|
|
||||||
if (!currInfo.isPlaceholder && !currInfo.label.isEmpty() && !numericLabelRe.match(currInfo.label).hasMatch()) {
|
|
||||||
outLabel = currInfo.label;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString &lnk : currInfo.links) {
|
|
||||||
if (!visited.contains(lnk)) {
|
|
||||||
visited.insert(lnk);
|
|
||||||
q.enqueue(lnk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ConductorData &cond : el_to_cons.value(curr)) {
|
|
||||||
if (cond.index == c.index) continue;
|
|
||||||
|
|
||||||
QString other;
|
|
||||||
QString terminalHint;
|
|
||||||
if (cond.el1_uuid == curr) {
|
|
||||||
other = cond.el2_uuid;
|
|
||||||
terminalHint = cond.terminalname2;
|
|
||||||
} else {
|
|
||||||
other = cond.el1_uuid;
|
|
||||||
terminalHint = cond.terminalname1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!other.isEmpty() && !visited.contains(other)) {
|
|
||||||
if (elementsInfo.contains(other)) {
|
|
||||||
const ElementInfo &oInfo = elementsInfo[other];
|
|
||||||
if (!oInfo.isPlaceholder && !oInfo.label.isEmpty() && !numericLabelRe.match(oInfo.label).hasMatch()) {
|
|
||||||
outLabel = oInfo.label;
|
|
||||||
if (outTerminal.isEmpty()) outTerminal = terminalHint;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visited.insert(other);
|
|
||||||
q.enqueue(other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (outLabel.isEmpty()) {
|
|
||||||
outLabel = startInfo.label.isEmpty() ? startInfo.name : startInfo.label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool p1 = elementsInfo.value(c.el1_uuid).isPlaceholder;
|
|
||||||
bool p2 = elementsInfo.value(c.el2_uuid).isPlaceholder;
|
|
||||||
|
|
||||||
if (c.element1_label.isEmpty() || p1) {
|
|
||||||
if (p1) c.element1_label = "";
|
|
||||||
resolveSide(c.el1_uuid, c.element1_label, c.terminalname1);
|
|
||||||
}
|
|
||||||
if (c.element2_label.isEmpty() || p2) {
|
|
||||||
if (p2) c.element2_label = "";
|
|
||||||
resolveSide(c.el2_uuid, c.element2_label, c.terminalname2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WiringListExport::toCsv()
|
void WiringListExport::toCsv()
|
||||||
{
|
{
|
||||||
if (!m_project) return;
|
if (!m_project) return;
|
||||||
@@ -225,6 +158,45 @@ void WiringListExport::toCsv()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSet<QString> conductorDefinitionTypes;
|
||||||
|
QDomElement rootElem = doc.documentElement();
|
||||||
|
QDomElement collection = rootElem.firstChildElement("collection");
|
||||||
|
if (!collection.isNull()) {
|
||||||
|
QDomNodeList defs = collection.elementsByTagName("definition");
|
||||||
|
for (int i = 0; i < defs.size(); ++i) {
|
||||||
|
QDomElement def = defs.at(i).toElement();
|
||||||
|
if (def.attribute("link_type") == "conductor_definition") {
|
||||||
|
QDomElement parentEl = def.parentNode().toElement();
|
||||||
|
if (parentEl.tagName().toLower() == "element") {
|
||||||
|
QString name = parentEl.attribute("name");
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
conductorDefinitionTypes.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> conductorDefinitionUuids;
|
||||||
|
QDomNodeList projectElements = rootElem.elementsByTagName("element");
|
||||||
|
for (int i = 0; i < projectElements.size(); ++i) {
|
||||||
|
QDomElement el = projectElements.at(i).toElement();
|
||||||
|
QString typeVal = el.attribute("type");
|
||||||
|
bool isCondDef = false;
|
||||||
|
for (const QString &cType : conductorDefinitionTypes) {
|
||||||
|
if (typeVal.endsWith(cType)) {
|
||||||
|
isCondDef = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isCondDef) {
|
||||||
|
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
|
||||||
|
if (!uuid.isEmpty()) {
|
||||||
|
conductorDefinitionUuids.insert(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QFileDialog dialog(m_parent);
|
QFileDialog dialog(m_parent);
|
||||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||||
dialog.setWindowTitle(tr("Exporter le plan de câblage"));
|
dialog.setWindowTitle(tr("Exporter le plan de câblage"));
|
||||||
@@ -243,25 +215,144 @@ void WiringListExport::toCsv()
|
|||||||
QMap<QString, ElementInfo> elementsInfo = collectElementsInfo(doc.documentElement());
|
QMap<QString, ElementInfo> elementsInfo = collectElementsInfo(doc.documentElement());
|
||||||
QList<ConductorData> conductors = collectConductors(doc.documentElement());
|
QList<ConductorData> conductors = collectConductors(doc.documentElement());
|
||||||
|
|
||||||
resolveEndpoints(conductors, elementsInfo);
|
|
||||||
|
|
||||||
QList<ConductorData> uniqueConductors;
|
QList<ConductorData> uniqueConductors;
|
||||||
QSet<QString> seenConnections;
|
QMap<QString, ConductorData> partialWires;
|
||||||
|
|
||||||
for (const ConductorData &c : conductors) {
|
auto normalizePartial = [](ConductorData c, const QString &ph_uuid) {
|
||||||
if (c.element1_label.isEmpty() && c.element2_label.isEmpty()) continue;
|
if (c.el1_uuid == ph_uuid) {
|
||||||
|
std::swap(c.el1_uuid, c.el2_uuid);
|
||||||
|
std::swap(c.element1_label, c.element2_label);
|
||||||
|
std::swap(c.terminalname1, c.terminalname2);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
QString sideA = c.element1_label + ":" + c.terminalname1;
|
auto mergeField = [](const QString &a, const QString &b) {
|
||||||
QString sideB = c.element2_label + ":" + c.terminalname2;
|
QString at = a.trimmed();
|
||||||
|
QString bt = b.trimmed();
|
||||||
|
if (at.isEmpty()) return bt;
|
||||||
|
if (bt.isEmpty()) return at;
|
||||||
|
if (at == bt) return at;
|
||||||
|
return at + ", " + bt;
|
||||||
|
};
|
||||||
|
|
||||||
QString key = (sideA < sideB) ? (sideA + "||" + sideB) : (sideB + "||" + sideA);
|
for (int i = 0; i < conductors.size(); ++i) {
|
||||||
|
ConductorData c = conductors[i];
|
||||||
|
|
||||||
if (!seenConnections.contains(key)) {
|
if (conductorDefinitionUuids.contains(c.el1_uuid) || conductorDefinitionUuids.contains(c.el2_uuid)) {
|
||||||
seenConnections.insert(key);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.element1_label.isEmpty() && elementsInfo.contains(c.el1_uuid)) {
|
||||||
|
c.element1_label = elementsInfo[c.el1_uuid].label;
|
||||||
|
if (c.element1_label.isEmpty()) c.element1_label = elementsInfo[c.el1_uuid].name;
|
||||||
|
}
|
||||||
|
if (c.element2_label.isEmpty() && elementsInfo.contains(c.el2_uuid)) {
|
||||||
|
c.element2_label = elementsInfo[c.el2_uuid].label;
|
||||||
|
if (c.element2_label.isEmpty()) c.element2_label = elementsInfo[c.el2_uuid].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool el1_ph = elementsInfo.value(c.el1_uuid).isPlaceholder;
|
||||||
|
bool el2_ph = elementsInfo.value(c.el2_uuid).isPlaceholder;
|
||||||
|
|
||||||
|
if (!el1_ph && !el2_ph) {
|
||||||
uniqueConductors.append(c);
|
uniqueConductors.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el1_ph && el2_ph) {
|
||||||
|
uniqueConductors.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ph_uuid = el1_ph ? c.el1_uuid : c.el2_uuid;
|
||||||
|
ConductorData normC = normalizePartial(c, ph_uuid);
|
||||||
|
|
||||||
|
QString matching_ph_uuid;
|
||||||
|
if (!elementsInfo[ph_uuid].links.isEmpty()) {
|
||||||
|
matching_ph_uuid = elementsInfo[ph_uuid].links.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matching_ph_uuid.isEmpty() && partialWires.contains(matching_ph_uuid)) {
|
||||||
|
ConductorData otherHalf = partialWires.take(matching_ph_uuid);
|
||||||
|
|
||||||
|
ConductorData merged;
|
||||||
|
merged.folio = mergeField(otherHalf.folio, normC.folio);
|
||||||
|
|
||||||
|
merged.el1_uuid = otherHalf.el1_uuid;
|
||||||
|
merged.element1_label = otherHalf.element1_label;
|
||||||
|
merged.terminalname1 = otherHalf.terminalname1;
|
||||||
|
|
||||||
|
merged.el2_uuid = normC.el1_uuid;
|
||||||
|
merged.element2_label = normC.element1_label;
|
||||||
|
merged.terminalname2 = normC.terminalname1;
|
||||||
|
|
||||||
|
merged.tension_protocol = mergeField(otherHalf.tension_protocol, normC.tension_protocol);
|
||||||
|
merged.conductor_color = mergeField(otherHalf.conductor_color, normC.conductor_color);
|
||||||
|
merged.conductor_section = mergeField(otherHalf.conductor_section, normC.conductor_section);
|
||||||
|
merged.function = mergeField(otherHalf.function, normC.function);
|
||||||
|
|
||||||
|
uniqueConductors.append(merged);
|
||||||
|
} else {
|
||||||
|
partialWires.insert(ph_uuid, normC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const ConductorData &leftover : partialWires.values()) {
|
||||||
|
uniqueConductors.append(leftover);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ConductorData &c : uniqueConductors) {
|
||||||
|
if (!c.element2_label.isEmpty() && (c.element1_label.isEmpty() || c.element2_label.toLower() < c.element1_label.toLower())) {
|
||||||
|
std::swap(c.element1_label, c.element2_label);
|
||||||
|
std::swap(c.terminalname1, c.terminalname2);
|
||||||
|
std::swap(c.el1_uuid, c.el2_uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(uniqueConductors.begin(), uniqueConductors.end(), [](const ConductorData &a, const ConductorData &b) {
|
||||||
|
QStringList partsA = a.folio.split(',');
|
||||||
|
QStringList partsB = b.folio.split(',');
|
||||||
|
int minLen = std::min(partsA.size(), partsB.size());
|
||||||
|
int folioCmp = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < minLen; ++i) {
|
||||||
|
bool okA, okB;
|
||||||
|
int numA = partsA[i].trimmed().toInt(&okA);
|
||||||
|
int numB = partsB[i].trimmed().toInt(&okB);
|
||||||
|
|
||||||
|
if (okA && okB) {
|
||||||
|
if (numA != numB) {
|
||||||
|
folioCmp = (numA < numB) ? -1 : 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int strCmp = partsA[i].trimmed().compare(partsB[i].trimmed(), Qt::CaseInsensitive);
|
||||||
|
if (strCmp != 0) {
|
||||||
|
folioCmp = strCmp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folioCmp == 0 && partsA.size() != partsB.size()) {
|
||||||
|
folioCmp = (partsA.size() < partsB.size()) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folioCmp != 0) return folioCmp < 0;
|
||||||
|
|
||||||
|
int el1Cmp = a.element1_label.toLower().compare(b.element1_label.toLower());
|
||||||
|
if (el1Cmp != 0) return el1Cmp < 0;
|
||||||
|
|
||||||
|
int el2Cmp = a.element2_label.toLower().compare(b.element2_label.toLower());
|
||||||
|
if (el2Cmp != 0) return el2Cmp < 0;
|
||||||
|
|
||||||
|
int term1Cmp = a.terminalname1.compare(b.terminalname1);
|
||||||
|
if (term1Cmp != 0) return term1Cmp < 0;
|
||||||
|
|
||||||
|
return a.terminalname2 < b.terminalname2;
|
||||||
|
});
|
||||||
|
|
||||||
QTextStream out(&file);
|
QTextStream out(&file);
|
||||||
out << tr("Page", "Wiring list CSV header") << ";"
|
out << tr("Page", "Wiring list CSV header") << ";"
|
||||||
<< tr("Composant 1", "Wiring list CSV header") << ";"
|
<< tr("Composant 1", "Wiring list CSV header") << ";"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class QWidget;
|
|||||||
class QDomElement;
|
class QDomElement;
|
||||||
class QDomNode;
|
class QDomNode;
|
||||||
|
|
||||||
// Internal data structures for parsing the XML graph
|
|
||||||
struct ElementInfo {
|
struct ElementInfo {
|
||||||
QString folio;
|
QString folio;
|
||||||
QStringList links;
|
QStringList links;
|
||||||
@@ -34,18 +33,11 @@ struct ConductorData {
|
|||||||
QString conductor_section;
|
QString conductor_section;
|
||||||
QString function;
|
QString function;
|
||||||
QString folio;
|
QString folio;
|
||||||
|
|
||||||
// Resolved endpoints
|
|
||||||
QString chosen_a_uuid;
|
|
||||||
QString chosen_a_label;
|
|
||||||
QString chosen_b_uuid;
|
|
||||||
QString chosen_b_label;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The WiringListExport class
|
* @brief The WiringListExport class
|
||||||
* Handles the export of the wiring list (Verdrahtungsplan) to a CSV file.
|
* Exports the wiring diagram from QElectroTech as a CSV file.
|
||||||
* Automatically resolves links and placeholders to find physical endpoints.
|
|
||||||
*/
|
*/
|
||||||
class WiringListExport : public QObject
|
class WiringListExport : public QObject
|
||||||
{
|
{
|
||||||
@@ -64,8 +56,6 @@ private:
|
|||||||
|
|
||||||
QMap<QString, ElementInfo> collectElementsInfo(const QDomElement &root) const;
|
QMap<QString, ElementInfo> collectElementsInfo(const QDomElement &root) const;
|
||||||
QList<ConductorData> collectConductors(const QDomElement &root) const;
|
QList<ConductorData> collectConductors(const QDomElement &root) const;
|
||||||
|
|
||||||
void resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WIRINGLISTEXPORT_H
|
#endif // WIRINGLISTEXPORT_H
|
||||||
|
|||||||
Reference in New Issue
Block a user