mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-06-09 12:03:13 +02:00
Compare commits
66 Commits
nightly
...
f914f91e77
| Author | SHA1 | Date | |
|---|---|---|---|
| f914f91e77 | |||
| fe03a0f643 | |||
| a7ad0278a6 | |||
| f517489421 | |||
| c8fa1c9fa4 | |||
| d85aff0c0f | |||
| 96b8e4b19c | |||
| 9760288db6 | |||
| eeaa059a77 | |||
| fa334d34a4 | |||
| 9664ff54ea | |||
| be0da461bd | |||
| 8c557a7f29 | |||
| 413e13a38c | |||
| 87f9f40e91 | |||
| 43fb34f852 | |||
| d6251c901e | |||
| c0ba961fb3 | |||
| 56f5a09737 | |||
| b5eebb2123 | |||
| 7bf395afab | |||
| 93baa00d00 | |||
| 6a7ae44ce5 | |||
| 89131a21a3 | |||
| 0b79dfd149 | |||
| 72178714fd | |||
| c7e236cd48 | |||
| fb769b884c | |||
| 2ae9ec87bb | |||
| 76d311cb35 | |||
| b016cc9f54 | |||
| 526e39e909 | |||
| d781105dfd | |||
| f6b93c6b71 | |||
| 61319bbbd6 | |||
| 1b522c251b | |||
| acfdab77fa | |||
| 8e327448cc | |||
| 7a8cee0ce6 | |||
| 82b8e7947e | |||
| e542a05d3f | |||
| 0b337a1514 | |||
| 48fec1db98 | |||
| 31f946426b | |||
| efa74dd0f5 | |||
| 703797bb97 | |||
| ef75ee736a | |||
| 15e623ac5f | |||
| df82a1125d | |||
| 7edc2e0241 | |||
| 55ae3fc3c6 | |||
| 2f72e6164c | |||
| e40f9c6b72 | |||
| ef261a7afd | |||
| 1550944011 | |||
| 32733187b8 | |||
| 79542edd3b | |||
| 9e0ec69c61 | |||
| 2e0a1a55e3 | |||
| 308f2ea838 | |||
| 9a9a5446cf | |||
| 0f8d835a1b | |||
| 663336a7bc | |||
| 4fab90d5b9 | |||
| f67df92f0e | |||
| 7e2c2cccf8 |
@@ -0,0 +1,112 @@
|
||||
name: Test Windows VS2026 migration
|
||||
|
||||
# Ce workflow vérifie que le build QElectroTech fonctionne sur l'image
|
||||
# windows-2025-vs2026, avant la migration forcée du 8 juin 2026.
|
||||
# Il peut être supprimé une fois la migration confirmée OK.
|
||||
|
||||
on:
|
||||
workflow_dispatch: # déclenchement manuel uniquement
|
||||
schedule:
|
||||
- cron: '0 4 * * 1' # chaque lundi à 4h00 UTC (optionnel)
|
||||
|
||||
jobs:
|
||||
test-vs2026:
|
||||
name: Build on windows-2025-vs2026
|
||||
runs-on: windows-2025-vs2026 # <-- image avec VS 2026
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install MSYS2 + dependencies
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: false
|
||||
install: >-
|
||||
mingw-w64-ucrt-x86_64-gcc
|
||||
mingw-w64-ucrt-x86_64-cmake
|
||||
mingw-w64-ucrt-x86_64-ninja
|
||||
mingw-w64-ucrt-x86_64-qt5-base
|
||||
mingw-w64-ucrt-x86_64-qt5-svg
|
||||
mingw-w64-ucrt-x86_64-qt5-tools
|
||||
mingw-w64-ucrt-x86_64-qt5-translations
|
||||
mingw-w64-ucrt-x86_64-sqlite3
|
||||
mingw-w64-ucrt-x86_64-pkgconf
|
||||
mingw-w64-ucrt-x86_64-extra-cmake-modules
|
||||
mingw-w64-ucrt-x86_64-kwidgetsaddons-qt5
|
||||
mingw-w64-ucrt-x86_64-kcoreaddons-qt5
|
||||
mingw-w64-ucrt-x86_64-nsis
|
||||
mingw-w64-ucrt-x86_64-ccache
|
||||
mingw-w64-ucrt-x86_64-7zip
|
||||
git
|
||||
|
||||
- name: Force Qt5 (remove Qt6 interference)
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
rm -rf /ucrt64/lib/cmake/Qt6 || true
|
||||
pacman -R --noconfirm mingw-w64-ucrt-x86_64-qt6-tools 2>/dev/null || true
|
||||
|
||||
- name: Cache ccache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:\Users\runneradmin\AppData\Local\ccache
|
||||
key: ccache-vs2026-${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-vs2026-${{ runner.os }}-
|
||||
|
||||
- name: Configure (CMake)
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DQt5_DIR=/ucrt64/lib/cmake/Qt5 \
|
||||
-DQT_VERSION_MAJOR=5 \
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_Qt6=ON \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_FLAGS="-DQET_EXPORT_PROJECT_DB" \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
|
||||
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
|
||||
|
||||
- name: Build
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cmake --build build --parallel $(nproc)
|
||||
|
||||
- name: Verify executable
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXE=$(find build -name "qelectrotech.exe" | head -1)
|
||||
if [ -z "$EXE" ]; then
|
||||
echo "ERROR: qelectrotech.exe introuvable après le build"
|
||||
exit 1
|
||||
fi
|
||||
SIZE=$(stat -c%s "$EXE")
|
||||
echo "Executable trouvé : $EXE ($SIZE octets)"
|
||||
if [ "$SIZE" -lt 100000 ]; then
|
||||
echo "ERROR: exe trop petit ($SIZE octets), build probablement incomplet"
|
||||
exit 1
|
||||
fi
|
||||
echo "BUILD VS2026 : OK ✓"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
echo "=== Résumé de compatibilité VS2026 ==="
|
||||
gcc --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
echo "Image runner : windows-2025-vs2026"
|
||||
echo "Date du test : $(date -u)"
|
||||
@@ -1,11 +1,9 @@
|
||||
name: Windows Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 2 * * 1' # Every Monday at 2:00 UTC
|
||||
workflow_dispatch: # Manual trigger available at any time
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -316,11 +314,30 @@ jobs:
|
||||
nsis_root/files/bin/
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Zip portable (readytouse)
|
||||
id: zip_portable
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = "${{ steps.qet_version.outputs.base_version }}"
|
||||
$head = "${{ steps.qet_version.outputs.head }}"
|
||||
$zipName = "qelectrotech-${version}+git${head}-x86-win64-readytouse.zip"
|
||||
$src = "$env:GITHUB_WORKSPACE\nsis_root\files"
|
||||
$dst = "$env:GITHUB_WORKSPACE\dist\$zipName"
|
||||
$7z = "C:\Program Files\7-Zip\7z.exe"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "$env:GITHUB_WORKSPACE\dist" | Out-Null
|
||||
& $7z a -tzip -mx=5 -mmt=on $dst "$src\*"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
$sizeMB = [math]::Round((Get-Item $dst).Length / 1MB, 1)
|
||||
Write-Output "ZIP created: $zipName ($sizeMB MB)"
|
||||
"zip_name=$zipName" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
|
||||
- name: Upload portable (files/ without installer)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qelectrotech-${{ steps.qet_version.outputs.base_version }}+git${{ steps.qet_version.outputs.head }}-x86-win64-readytouse
|
||||
path: nsis_root/files/
|
||||
path: dist/${{ steps.zip_portable.outputs.zip_name }}
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload NSIS installer
|
||||
@@ -330,6 +347,13 @@ jobs:
|
||||
path: dist/Installer_*.exe
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload portable (nom fixe pour le workflow MSI)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qelectrotech-windows-portable
|
||||
path: nsis_root/files/
|
||||
retention-days: 14
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 2 : Publie une release nightly + page GitHub Pages
|
||||
# Ne tourne que sur push master (pas sur les PRs)
|
||||
@@ -342,11 +366,13 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout gh-pages branch
|
||||
- name: Checkout master (for build-aux scripts)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: gh-pages
|
||||
path: gh-pages
|
||||
ref: master
|
||||
path: source
|
||||
sparse-checkout: build-aux/generate-page.py
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Download installer artifact
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -354,6 +380,27 @@ jobs:
|
||||
name: qelectrotech-windows-installer
|
||||
path: downloaded/installer/
|
||||
|
||||
- name: Download portable artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: qelectrotech-*-readytouse
|
||||
path: downloaded/portable/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Delete old nightly assets (.exe and .zip)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
# Fetch existing .exe and .zip asset names and delete them
|
||||
gh release view nightly --repo "$REPO" --json assets \
|
||||
--jq '.assets[] | select(.name | test("\\.(exe|zip)$")) | .name' \
|
||||
| while read -r name; do
|
||||
echo "Deleting old asset: $name"
|
||||
gh release delete-asset nightly "$name" --repo "$REPO" --yes
|
||||
done
|
||||
echo "Old .exe and .zip assets deleted."
|
||||
|
||||
- name: Update nightly release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
@@ -368,190 +415,14 @@ jobs:
|
||||
| **Run** | https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} |
|
||||
| **Date** | ${{ github.event.head_commit.timestamp }} |
|
||||
|
||||
> ⚠️ This release is overwritten on every push to `master`.
|
||||
> ⚠️ This is a development version; it introduces new features you want, but may cause bugs that have not yet been identified yet.
|
||||
> For stable releases, see the [Releases page](https://github.com/${{ github.repository }}/releases).
|
||||
prerelease: true
|
||||
make_latest: false
|
||||
files: downloaded/installer/*.exe
|
||||
files: |
|
||||
downloaded/installer/*.exe
|
||||
downloaded/portable/*.zip
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate download page (index.html)
|
||||
run: |
|
||||
DATE=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||
SHORT="${{ github.sha }}"
|
||||
SHORT="${SHORT:0:7}"
|
||||
REPO="${{ github.repository }}"
|
||||
RUN_URL="https://github.com/$REPO/actions/runs/${{ github.run_id }}"
|
||||
EXE_NAME=$(ls downloaded/installer/*.exe | xargs -I{} basename {})
|
||||
INSTALLER_URL="https://github.com/$REPO/releases/download/nightly/$EXE_NAME"
|
||||
|
||||
mkdir -p gh-pages
|
||||
cat > gh-pages/index.html << HTMLEOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QElectroTech – Nightly Builds</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f0f4f8;
|
||||
color: #2d3748;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
background: linear-gradient(135deg, #1a365d 0%, #2b6cb0 100%);
|
||||
color: white;
|
||||
padding: 48px 24px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
header h1 { font-size: 2.2em; letter-spacing: -0.5px; margin-bottom: 8px; }
|
||||
header p { opacity: 0.8; font-size: 1.05em; }
|
||||
main {
|
||||
max-width: 680px;
|
||||
margin: 40px auto;
|
||||
padding: 0 20px 60px;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
.card h2 {
|
||||
font-size: 1em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #718096;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.meta {
|
||||
font-size: 0.875em;
|
||||
color: #4a5568;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.meta a { color: #2b6cb0; text-decoration: none; }
|
||||
.meta a:hover { text-decoration: underline; }
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background: #ebf8ff;
|
||||
color: #2b6cb0;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.warning {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid #f6ad55;
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
font-size: 0.875em;
|
||||
color: #744210;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.warning a { color: #c05621; }
|
||||
.downloads { display: flex; flex-direction: column; gap: 12px; }
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95em;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
}
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
.btn-primary { background: #2b6cb0; color: white; }
|
||||
.btn-secondary { background: #edf2f7; color: #2d3748; }
|
||||
.btn-icon { font-size: 1.3em; }
|
||||
.btn-text small {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
font-size: 0.8em;
|
||||
opacity: 0.75;
|
||||
margin-top: 1px;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
color: #a0aec0;
|
||||
padding: 32px 0 0;
|
||||
}
|
||||
footer a { color: #718096; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>⚡ QElectroTech</h1>
|
||||
<p>Nightly Windows Builds</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="card">
|
||||
<h2>Build info</h2>
|
||||
<div class="meta">
|
||||
📅 <strong>$DATE</strong><br>
|
||||
🔀 Commit <a href="https://github.com/$REPO/commit/${{ github.sha }}"><code>$SHORT</code></a><br>
|
||||
🔧 <a href="$RUN_URL">CI Run #${{ github.run_number }}</a>
|
||||
<span class="badge">nightly</span>
|
||||
</div>
|
||||
<div class="warning">
|
||||
⚠️ These builds are generated automatically on every commit to <code>master</code>
|
||||
and may be unstable or incomplete.
|
||||
For production use, download a
|
||||
<a href="https://github.com/$REPO/releases">stable release</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>🪟 Windows — x86_64</h2>
|
||||
<div class="downloads">
|
||||
<a class="btn btn-primary" href="$INSTALLER_URL">
|
||||
<span class="btn-icon">⬇</span>
|
||||
<span class="btn-text">
|
||||
Windows Installer
|
||||
<small>.exe — recommended, includes all dependencies</small>
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="https://github.com/$REPO/releases/tag/nightly">
|
||||
<span class="btn-icon">📦</span>
|
||||
<span class="btn-text">
|
||||
All nightly files on GitHub
|
||||
<small>Release page with checksums</small>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Auto-generated by GitHub Actions ·
|
||||
<a href="https://github.com/$REPO">Source on GitHub</a> ·
|
||||
<a href="https://qelectrotech.org">qelectrotech.org</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
|
||||
- name: Commit and push to gh-pages
|
||||
run: |
|
||||
cd gh-pages
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add index.html
|
||||
git diff --staged --quiet \
|
||||
|| git commit -m "nightly: build #${{ github.run_number }} (${{ github.sha }})"
|
||||
git push origin gh-pages
|
||||
# GitHub Pages is generated and deployed by windows-msi.yml
|
||||
# after the MSI upload, so that all 3 URLs (exe, zip, msi) are known.
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
name: Windows MSI (WiX v7)
|
||||
|
||||
on:
|
||||
# Triggered automatically after a successful Windows build on master
|
||||
workflow_run:
|
||||
workflows: ["Windows Build"]
|
||||
types: [completed]
|
||||
branches: [master]
|
||||
# Manual trigger still available (e.g. for a specific run_id)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_id:
|
||||
description: "Run ID of 'Windows Build' (leave empty for automatic)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
build-msi:
|
||||
name: Build MSI with WiX v7
|
||||
runs-on: windows-latest
|
||||
|
||||
# Only runs if Windows Build succeeded (or triggered manually)
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 1. Checkout (to retrieve QElectroTech.wxs and sources)
|
||||
# ----------------------------------------------------------------
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 2. Download the portable artifact from the main build
|
||||
# ----------------------------------------------------------------
|
||||
- name: Download portable artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: qelectrotech-windows-portable
|
||||
path: artifact\files
|
||||
run-id: ${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.repository }}
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3. Extract version from sources
|
||||
# ----------------------------------------------------------------
|
||||
- name: Extract version
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$src = Get-Content "sources\qetversion.cpp" -Raw -ErrorAction SilentlyContinue
|
||||
if ($src -match 'return QVersionNumber\{([^}]+)\}') {
|
||||
$ver = $Matches[1] -replace '\s','' -replace ',','.'
|
||||
} else {
|
||||
$cmake = Get-Content "CMakeLists.txt" -Raw
|
||||
if ($cmake -match 'project\s*\([^)]*VERSION\s+([\d]+\.[\d]+\.[\d]+)') {
|
||||
$ver = $Matches[1]
|
||||
} else {
|
||||
$ver = "0.0.0"
|
||||
}
|
||||
}
|
||||
$verMsi = "$ver.0"
|
||||
$sha = git rev-parse --short HEAD 2>$null
|
||||
if (-not $sha) { $sha = "unknown" }
|
||||
$count = git rev-list HEAD --count 2>$null
|
||||
$rev = [int]$count + 473
|
||||
$verDisplay = "${ver}-r${rev}-${sha}_x86_64-win64"
|
||||
echo "VERSION_MSI=$verMsi" >> $env:GITHUB_OUTPUT
|
||||
echo "VERSION_DISPLAY=$verDisplay" >> $env:GITHUB_OUTPUT
|
||||
Write-Host "Version MSI : $verMsi"
|
||||
Write-Host "Version display : $verDisplay"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 4. Install WiX v7, accept EULA and install WixUI extension
|
||||
# ----------------------------------------------------------------
|
||||
- name: Install WiX v7
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global wix --version 7.0.0
|
||||
$toolsPath = [System.IO.Path]::Combine($env:USERPROFILE, '.dotnet', 'tools')
|
||||
$env:PATH = "$toolsPath;$env:PATH"
|
||||
echo $toolsPath >> $env:GITHUB_PATH
|
||||
wix eula accept wix7
|
||||
wix extension add WixToolset.UI.wixext/7.0.0
|
||||
wix extension add WixToolset.Util.wixext/7.0.0
|
||||
Write-Host "WiX v7 installed, EULA accepted, UI extension added."
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 5. Check that the WXS file exists in the repository
|
||||
# ----------------------------------------------------------------
|
||||
- name: Check WXS file
|
||||
shell: pwsh
|
||||
run: |
|
||||
$wxs = "build-aux\windows\QElectroTech.wxs"
|
||||
if (-not (Test-Path $wxs)) {
|
||||
Write-Error "WXS file not found: $wxs"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "WXS found: $wxs"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 6. Check the artifact structure and locate files/
|
||||
# ----------------------------------------------------------------
|
||||
- name: Check artifact structure
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "=== Contents of artifact\files (2 levels) ==="
|
||||
Get-ChildItem -Path "artifact\files" -Depth 2 |
|
||||
Select-Object FullName | Format-Table -AutoSize
|
||||
|
||||
$exe = Get-ChildItem -Path "artifact\files" -Filter "qelectrotech.exe" -Recurse | Select-Object -First 1
|
||||
if (-not $exe) {
|
||||
$exe = Get-ChildItem -Path "artifact\files" -Filter "QElectroTech.exe" -Recurse | Select-Object -First 1
|
||||
}
|
||||
if (-not $exe) {
|
||||
Write-Error "qelectrotech.exe not found in artifact"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Executable: $($exe.FullName) ($([math]::Round($exe.Length/1MB,1)) MB)"
|
||||
$binDir = $exe.Directory.FullName
|
||||
$filesDir = Split-Path $binDir -Parent
|
||||
echo "FILES_DIR=$filesDir" >> $env:GITHUB_ENV
|
||||
Write-Host "FILES_DIR: $filesDir"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 7. Convert LICENSE (GPL-2) to RTF for the WixUI licence screen
|
||||
# ----------------------------------------------------------------
|
||||
- name: Convert LICENSE to RTF
|
||||
shell: pwsh
|
||||
run: |
|
||||
$licSrc = "LICENSE"
|
||||
$licRtf = "$env:TEMP\License.rtf"
|
||||
if (-not (Test-Path $licSrc)) {
|
||||
Write-Error "LICENSE file not found in repository root"
|
||||
exit 1
|
||||
}
|
||||
$lines = Get-Content $licSrc -Encoding UTF8
|
||||
$rtf = New-Object System.Text.StringBuilder
|
||||
[void]$rtf.AppendLine('{\rtf1\ansi\ansicpg1252\deff0')
|
||||
[void]$rtf.AppendLine('{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}}')
|
||||
[void]$rtf.AppendLine('{\colortbl;\red0\green0\blue0;}')
|
||||
[void]$rtf.AppendLine('\f0\fs18\cf1')
|
||||
foreach ($line in $lines) {
|
||||
$escaped = $line `
|
||||
-replace '\\', '\\\\' `
|
||||
-replace '\{', '\{' `
|
||||
-replace '\}', '\}'
|
||||
[void]$rtf.AppendLine("$escaped\par")
|
||||
}
|
||||
[void]$rtf.AppendLine('}')
|
||||
[System.IO.File]::WriteAllText($licRtf, $rtf.ToString(), [System.Text.Encoding]::ASCII)
|
||||
echo "LICENSE_RTF=$licRtf" >> $env:GITHUB_ENV
|
||||
Write-Host "License.rtf generated: $licRtf ($([math]::Round((Get-Item $licRtf).Length/1KB,1)) KB)"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 8. Remove Lancer QET.bat from the artifact
|
||||
# The MSI does not use the .bat: shortcuts point directly to
|
||||
# qelectrotech.exe, and elements\ is set read-only via a
|
||||
# CustomAction in QElectroTech.wxs.
|
||||
# The .bat is kept as-is in the ZIP portable build.
|
||||
# ----------------------------------------------------------------
|
||||
- name: Remove Lancer QET.bat from artifact
|
||||
shell: pwsh
|
||||
run: |
|
||||
$bat = "$env:FILES_DIR\Lancer QET.bat"
|
||||
if (Test-Path $bat) {
|
||||
Remove-Item $bat -Force
|
||||
Write-Host "Lancer QET.bat removed from artifact (MSI uses direct exe shortcut)."
|
||||
} else {
|
||||
Write-Host "Lancer QET.bat not found in artifact (already absent)."
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 9. Build the MSI
|
||||
# ----------------------------------------------------------------
|
||||
- name: Build MSI
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = "${{ steps.version.outputs.VERSION_MSI }}"
|
||||
$verDisplay = "${{ steps.version.outputs.VERSION_DISPLAY }}"
|
||||
$filesDir = $env:FILES_DIR
|
||||
$licRtf = $env:LICENSE_RTF
|
||||
$wxs = "build-aux\windows\QElectroTech.wxs"
|
||||
$outputName = "QElectroTech-${verDisplay}.msi"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "dist" | Out-Null
|
||||
|
||||
Write-Host "=== wix build ==="
|
||||
Write-Host " WXS : $wxs"
|
||||
Write-Host " Version : $version"
|
||||
Write-Host " FilesDir : $filesDir"
|
||||
Write-Host " LicenseRtf : $licRtf"
|
||||
Write-Host " Output : dist\$outputName"
|
||||
|
||||
wix build $wxs `
|
||||
-arch x64 `
|
||||
-d "Version=$version" `
|
||||
-d "ProductVersion=$verDisplay" `
|
||||
-d "FilesDir=$filesDir" `
|
||||
-d "LicenseRtf=$licRtf" `
|
||||
-ext WixToolset.UI.wixext `
|
||||
-ext WixToolset.Util.wixext `
|
||||
-o "dist\$outputName"
|
||||
|
||||
if (-not (Test-Path "dist\$outputName")) {
|
||||
Write-Error "MSI not generated: dist\$outputName"
|
||||
exit 1
|
||||
}
|
||||
$size = [math]::Round((Get-Item "dist\$outputName").Length / 1MB, 1)
|
||||
Write-Host "MSI generated: dist\$outputName ($size MB) ✓"
|
||||
echo "MSI_NAME=$outputName" >> $env:GITHUB_ENV
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 10. Sign the MSI with SignPath
|
||||
# ----------------------------------------------------------------
|
||||
- name: Install SignPath PowerShell module
|
||||
shell: pwsh
|
||||
run: |
|
||||
Install-Module -Name SignPath -Force -Scope CurrentUser
|
||||
|
||||
- name: Sign MSI with SignPath
|
||||
shell: pwsh
|
||||
run: |
|
||||
$msi = Get-ChildItem "$env:GITHUB_WORKSPACE\dist\*.msi" | Select-Object -First 1
|
||||
if (-not $msi) {
|
||||
Write-Error "No .msi found in dist/"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Signing: $($msi.FullName)"
|
||||
Submit-SigningRequest `
|
||||
-InputArtifactPath $msi.FullName `
|
||||
-ApiToken "${{ secrets.SIGNPATH_API_TOKEN }}" `
|
||||
-OrganizationId "${{ secrets.SIGNPATH_ORGANIZATION_ID }}" `
|
||||
-ProjectSlug "MSI" `
|
||||
-SigningPolicySlug "test-signing" `
|
||||
-OutputArtifactPath $msi.FullName `
|
||||
-Force `
|
||||
-WaitForCompletion
|
||||
Write-Host "Signing complete: $($msi.Name)"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 11. Upload the MSI artifact
|
||||
# ----------------------------------------------------------------
|
||||
- name: Upload MSI artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qelectrotech-windows-msi
|
||||
path: dist\*.msi
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 11. Delete old .msi asset then upload new MSI to nightly release
|
||||
# ----------------------------------------------------------------
|
||||
- name: Delete old nightly .msi asset
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
gh release view nightly --repo "$REPO" --json assets \
|
||||
--jq '.assets[] | select(.name | test("\\.msi$")) | .name' \
|
||||
| while read -r name; do
|
||||
echo "Deleting old asset: $name"
|
||||
gh release delete-asset nightly "$name" --repo "$REPO" --yes
|
||||
done
|
||||
echo "Old .msi assets deleted."
|
||||
shell: bash
|
||||
|
||||
- name: Upload MSI to nightly release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: nightly
|
||||
files: dist/*.msi
|
||||
fail_on_unmatched_files: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 12. Summary
|
||||
# ----------------------------------------------------------------
|
||||
- name: Summary
|
||||
if: always()
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "=== MSI build summary ==="
|
||||
Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}"
|
||||
Write-Host "WiX : v7.0.0"
|
||||
Write-Host "Runner image : ${{ runner.os }} / ${{ runner.arch }}"
|
||||
if (Test-Path "dist\$env:MSI_NAME") {
|
||||
$size = [math]::Round((Get-Item "dist\$env:MSI_NAME").Length / 1MB, 1)
|
||||
Write-Host "MSI : $env:MSI_NAME ($size MB) ✓"
|
||||
} else {
|
||||
Write-Host "MSI : FAILED ✗"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 13. Generate and deploy the GitHub Pages download page
|
||||
# ----------------------------------------------------------------
|
||||
- name: Checkout (for generate-page.py)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
path: source
|
||||
sparse-checkout: build-aux/generate-page.py
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Generate download page (index.html)
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REPO="${{ github.repository }}"
|
||||
ASSETS=$(gh release view nightly --repo "$REPO" --json assets --jq '.assets[].name')
|
||||
EXE_NAME=$(echo "$ASSETS" | grep '\.exe$' | head -1)
|
||||
ZIP_NAME=$(echo "$ASSETS" | grep '\.zip$' | head -1)
|
||||
MSI_NAME=$(echo "$ASSETS" | grep '\.msi$' | head -1 || echo "")
|
||||
BASE="https://github.com/$REPO/releases/download/nightly"
|
||||
INSTALLER_URL="$BASE/$EXE_NAME"
|
||||
PORTABLE_URL="$BASE/$ZIP_NAME"
|
||||
MSI_URL=""
|
||||
[ -n "$MSI_NAME" ] && MSI_URL="$BASE/$MSI_NAME"
|
||||
SHA="${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
SHORT="${SHA:0:7}"
|
||||
DATE=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||
RUN_URL="https://github.com/$REPO/actions/runs/${{ github.run_id }}"
|
||||
RUN_NUMBER="${{ github.run_number }}"
|
||||
export DATE SHORT REPO SHA RUN_URL RUN_NUMBER
|
||||
export INSTALLER_URL PORTABLE_URL MSI_URL
|
||||
python3 source/build-aux/generate-page.py
|
||||
|
||||
- name: Add .nojekyll
|
||||
shell: bash
|
||||
run: touch gh-pages/.nojekyll
|
||||
|
||||
- name: Upload GitHub Pages artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: gh-pages/
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: actions/deploy-pages@v4
|
||||
+99
-86
@@ -19,10 +19,10 @@ include(cmake/hoto_update_cmake_message.cmake)
|
||||
cmake_minimum_required(VERSION 3.14...3.19 FATAL_ERROR)
|
||||
|
||||
project(qelectrotech
|
||||
VERSION 0.9.0
|
||||
DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features."
|
||||
HOMEPAGE_URL "https://qelectrotech.org/"
|
||||
LANGUAGES CXX)
|
||||
VERSION 0.100.1
|
||||
DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features."
|
||||
HOMEPAGE_URL "https://qelectrotech.org/"
|
||||
LANGUAGES CXX)
|
||||
|
||||
include(cmake/copyright_message.cmake)
|
||||
|
||||
@@ -31,8 +31,8 @@ set(QET_DIR ${PROJECT_SOURCE_DIR})
|
||||
# Add sub directories
|
||||
option(PACKAGE_TESTS "Build the tests" ON)
|
||||
if(PACKAGE_TESTS)
|
||||
message("Add sub directory tests")
|
||||
add_subdirectory(tests)
|
||||
message("Add sub directory tests")
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
include(cmake/paths_compilation_installation.cmake)
|
||||
@@ -46,31 +46,31 @@ include(cmake/fetch_pugixml.cmake)
|
||||
include(cmake/qet_compilation_vars.cmake)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
SET(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(
|
||||
QT
|
||||
NAMES
|
||||
Qt5
|
||||
COMPONENTS
|
||||
${QET_COMPONENTS}
|
||||
REQUIRED
|
||||
)
|
||||
QT
|
||||
NAMES
|
||||
Qt5
|
||||
COMPONENTS
|
||||
${QET_COMPONENTS}
|
||||
REQUIRED
|
||||
)
|
||||
|
||||
find_package(
|
||||
Qt${QT_VERSION_MAJOR}
|
||||
COMPONENTS
|
||||
${QET_COMPONENTS}
|
||||
REQUIRED)
|
||||
Qt${QT_VERSION_MAJOR}
|
||||
COMPONENTS
|
||||
${QET_COMPONENTS}
|
||||
REQUIRED)
|
||||
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
||||
set(CMAKE_AUTOUIC_SEARCH_PATHS ${QET_DIR}/sources/ui)
|
||||
|
||||
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang")
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
@@ -78,78 +78,91 @@ qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
# als laatse
|
||||
include(cmake/define_definitions.cmake)
|
||||
|
||||
add_executable(
|
||||
${PROJECT_NAME}
|
||||
${QET_RES_FILES}
|
||||
${QET_SRC_FILES}
|
||||
${QM_FILES}
|
||||
${QET_DIR}/qelectrotech.qrc
|
||||
)
|
||||
# On Windows, WIN32 sets /SUBSYSTEM:WINDOWS to suppress the console window.
|
||||
# Qt automatically links qtmain.lib which provides the WinMain entry point,
|
||||
# so no source code change is needed.
|
||||
if(WIN32)
|
||||
add_executable(
|
||||
${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(
|
||||
${PROJECT_NAME}
|
||||
PUBLIC
|
||||
PRIVATE
|
||||
pugixml::pugixml
|
||||
SingleApplication::SingleApplication
|
||||
SQLite::SQLite3
|
||||
${KF5_PRIVATE_LIBRARIES}
|
||||
${QET_PRIVATE_LIBRARIES}
|
||||
)
|
||||
${PROJECT_NAME}
|
||||
PUBLIC
|
||||
PRIVATE
|
||||
pugixml::pugixml
|
||||
SingleApplication::SingleApplication
|
||||
SQLite::SQLite3
|
||||
${KF5_PRIVATE_LIBRARIES}
|
||||
${QET_PRIVATE_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${QET_DIR}/sources/titleblock
|
||||
${QET_DIR}/sources/ui
|
||||
${QET_DIR}/sources/qetgraphicsitem
|
||||
${QET_DIR}/sources/qetgraphicsitem/ViewItem
|
||||
${QET_DIR}/sources/qetgraphicsitem/ViewItem/ui
|
||||
${QET_DIR}/sources/richtext
|
||||
${QET_DIR}/sources/factory
|
||||
${QET_DIR}/sources/properties
|
||||
${QET_DIR}/sources/dvevent
|
||||
${QET_DIR}/sources/editor
|
||||
${QET_DIR}/sources/editor/esevent
|
||||
${QET_DIR}/sources/editor/graphicspart
|
||||
${QET_DIR}/sources/editor/ui
|
||||
${QET_DIR}/sources/editor/UndoCommand
|
||||
${QET_DIR}/sources/undocommand
|
||||
${QET_DIR}/sources/diagramevent
|
||||
${QET_DIR}/sources/ElementsCollection
|
||||
${QET_DIR}/sources/ElementsCollection/ui
|
||||
${QET_DIR}/sources/autoNum
|
||||
${QET_DIR}/sources/autoNum/ui
|
||||
${QET_DIR}/sources/ui/configpage
|
||||
${QET_DIR}/sources/SearchAndReplace
|
||||
${QET_DIR}/sources/SearchAndReplace/ui
|
||||
${QET_DIR}/sources/NameList
|
||||
${QET_DIR}/sources/NameList/ui
|
||||
${QET_DIR}/sources/utils
|
||||
${QET_DIR}/pugixml/src
|
||||
${QET_DIR}/sources/dataBase
|
||||
${QET_DIR}/sources/dataBase/ui
|
||||
${QET_DIR}/sources/factory/ui
|
||||
${QET_DIR}/sources/print
|
||||
)
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${QET_DIR}/sources/titleblock
|
||||
${QET_DIR}/sources/ui
|
||||
${QET_DIR}/sources/qetgraphicsitem
|
||||
${QET_DIR}/sources/qetgraphicsitem/ViewItem
|
||||
${QET_DIR}/sources/qetgraphicsitem/ViewItem/ui
|
||||
${QET_DIR}/sources/richtext
|
||||
${QET_DIR}/sources/factory
|
||||
${QET_DIR}/sources/properties
|
||||
${QET_DIR}/sources/dvevent
|
||||
${QET_DIR}/sources/editor
|
||||
${QET_DIR}/sources/editor/esevent
|
||||
${QET_DIR}/sources/editor/graphicspart
|
||||
${QET_DIR}/sources/editor/ui
|
||||
${QET_DIR}/sources/editor/UndoCommand
|
||||
${QET_DIR}/sources/undocommand
|
||||
${QET_DIR}/sources/diagramevent
|
||||
${QET_DIR}/sources/ElementsCollection
|
||||
${QET_DIR}/sources/ElementsCollection/ui
|
||||
${QET_DIR}/sources/autoNum
|
||||
${QET_DIR}/sources/autoNum/ui
|
||||
${QET_DIR}/sources/ui/configpage
|
||||
${QET_DIR}/sources/SearchAndReplace
|
||||
${QET_DIR}/sources/SearchAndReplace/ui
|
||||
${QET_DIR}/sources/NameList
|
||||
${QET_DIR}/sources/NameList/ui
|
||||
${QET_DIR}/sources/utils
|
||||
${QET_DIR}/pugixml/src
|
||||
${QET_DIR}/sources/dataBase
|
||||
${QET_DIR}/sources/dataBase/ui
|
||||
${QET_DIR}/sources/factory/ui
|
||||
${QET_DIR}/sources/print
|
||||
)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME})
|
||||
|
||||
if (NOT MINGW)
|
||||
|
||||
install(DIRECTORY ico/breeze-icons/16x16 DESTINATION ${QET_ICONS_PATH})
|
||||
install(DIRECTORY ico/breeze-icons/22x22 DESTINATION ${QET_ICONS_PATH})
|
||||
install(DIRECTORY ico/breeze-icons/32x32 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/128x128 DESTINATION ${QET_ICONS_PATH})
|
||||
install(DIRECTORY ico/breeze-icons/256x256 DESTINATION ${QET_ICONS_PATH})
|
||||
install(DIRECTORY elements DESTINATION share/qelectrotech)
|
||||
install(DIRECTORY examples DESTINATION share/qelectrotech)
|
||||
install(DIRECTORY titleblocks DESTINATION share/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/qelectrotech.xml DESTINATION share/mime/packages)
|
||||
install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH})
|
||||
install(FILES ${QM_FILES} DESTINATION ${QET_LANG_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/32x32 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/128x128 DESTINATION ${QET_ICONS_PATH})
|
||||
install(DIRECTORY ico/breeze-icons/256x256 DESTINATION ${QET_ICONS_PATH})
|
||||
install(DIRECTORY elements DESTINATION share/qelectrotech)
|
||||
install(DIRECTORY examples DESTINATION share/qelectrotech)
|
||||
install(DIRECTORY titleblocks DESTINATION share/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/qelectrotech.xml DESTINATION share/mime/packages)
|
||||
install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH})
|
||||
install(FILES ${QM_FILES} DESTINATION ${QET_LANG_PATH})
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
generate-page.py — Generates gh-pages/index.html for QElectroTech nightly builds.
|
||||
Called from windows-build.yml deploy-pages job.
|
||||
Environment variables required:
|
||||
DATE, SHORT, REPO, SHA, RUN_URL, RUN_NUMBER,
|
||||
INSTALLER_URL, PORTABLE_URL, MSI_URL (optional)
|
||||
"""
|
||||
import os
|
||||
|
||||
date = os.environ.get("DATE", "")
|
||||
short = os.environ.get("SHORT", "")
|
||||
repo = os.environ.get("REPO", "")
|
||||
sha = os.environ.get("SHA", "")
|
||||
run_url = os.environ.get("RUN_URL", "")
|
||||
run_number = os.environ.get("RUN_NUMBER", "")
|
||||
installer_url = os.environ.get("INSTALLER_URL", "")
|
||||
portable_url = os.environ.get("PORTABLE_URL", "")
|
||||
msi_url = os.environ.get("MSI_URL", "")
|
||||
|
||||
msi_block = ""
|
||||
if msi_url:
|
||||
msi_block = f"""
|
||||
<a class="btn btn-msi" href="{msi_url}">
|
||||
<span class="btn-icon">⬇</span>
|
||||
<span class="btn-text">Windows Installer .msi<small>.msi — for enterprise / GPO deployment</small></span>
|
||||
</a>"""
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QElectroTech – Nightly Builds</title>
|
||||
<style>
|
||||
*,*::before,*::after{{box-sizing:border-box;margin:0;padding:0}}
|
||||
body{{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f0f4f8;color:#2d3748;min-height:100vh}}
|
||||
header{{background:linear-gradient(135deg,#1a365d 0%,#2b6cb0 100%);color:white;padding:48px 24px 40px;text-align:center}}
|
||||
header h1{{font-size:2.2em;letter-spacing:-0.5px;margin-bottom:8px}}
|
||||
header p{{opacity:.8;font-size:1.05em}}
|
||||
main{{max-width:680px;margin:40px auto;padding:0 20px 60px}}
|
||||
.card{{background:white;border-radius:12px;padding:28px;margin-bottom:24px;box-shadow:0 2px 12px rgba(0,0,0,.08)}}
|
||||
.card h2{{font-size:1em;text-transform:uppercase;letter-spacing:.06em;color:#718096;margin-bottom:16px}}
|
||||
.meta{{font-size:.875em;color:#4a5568;line-height:1.8;margin-bottom:20px}}
|
||||
.meta a{{color:#2b6cb0;text-decoration:none}}
|
||||
.meta a:hover{{text-decoration:underline}}
|
||||
.badge{{display:inline-block;background:#ebf8ff;color:#2b6cb0;border-radius:4px;font-size:.8em;font-weight:600;padding:2px 8px;margin-left:6px;vertical-align:middle}}
|
||||
.warning{{background:#fffbeb;border-left:4px solid #f6ad55;border-radius:4px;padding:12px 16px;font-size:.875em;color:#744210;margin-bottom:24px;line-height:1.5}}
|
||||
.warning a{{color:#c05621}}
|
||||
.downloads{{display:flex;flex-direction:column;gap:12px}}
|
||||
.btn{{display:flex;align-items:center;gap:12px;padding:14px 20px;border-radius:8px;font-size:.95em;font-weight:600;text-decoration:none;transition:transform .1s,box-shadow .1s}}
|
||||
.btn:hover{{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.15)}}
|
||||
.btn-primary{{background:#2b6cb0;color:white}}
|
||||
.btn-msi{{background:#6b46c1;color:white}}
|
||||
.btn-secondary{{background:#edf2f7;color:#2d3748}}
|
||||
.btn-icon{{font-size:1.3em}}
|
||||
.btn-text small{{display:block;font-weight:400;font-size:.8em;opacity:.75;margin-top:1px}}
|
||||
footer{{text-align:center;font-size:.8em;color:#a0aec0;padding:32px 0 0}}
|
||||
footer a{{color:#718096;text-decoration:none}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>⚡ QElectroTech</h1>
|
||||
<p>Nightly Windows Builds</p>
|
||||
</header>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h2>Build info</h2>
|
||||
<div class="meta">
|
||||
📅 <strong>{date}</strong><br>
|
||||
🔀 Commit <a href="https://github.com/{repo}/commit/{sha}"><code>{short}</code></a><br>
|
||||
🔧 <a href="{run_url}">CI Run #{run_number}</a>
|
||||
<span class="badge">nightly</span>
|
||||
</div>
|
||||
<div class="warning">
|
||||
⚠️ This is a development version; it introduces new features you want,
|
||||
but may cause bugs that have not yet been identified yet in <code>master</code>.
|
||||
For production use, download a <a href="https://github.com/{repo}/releases">stable release</a>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>🏹 Windows — x86_64</h2>
|
||||
<div class="downloads">
|
||||
<a class="btn btn-primary" href="{installer_url}">
|
||||
<span class="btn-icon">⬇</span>
|
||||
<span class="btn-text">Windows Installer<small>.exe — recommended, includes all dependencies</small></span>
|
||||
</a>
|
||||
{msi_block}
|
||||
<a class="btn btn-secondary" href="{portable_url}">
|
||||
<span class="btn-icon">📦</span>
|
||||
<span class="btn-text">Windows Portable<small>.zip — no installation required, extract and run "Lancer QET.bat"</small></span>
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="https://github.com/{repo}/releases/tag/nightly">
|
||||
<span class="btn-icon">📦</span>
|
||||
<span class="btn-text">All nightly files on GitHub<small>Release page with checksums</small></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Auto-generated by GitHub Actions ·
|
||||
<a href="https://github.com/{repo}">Source on GitHub</a> ·
|
||||
<a href="https://qelectrotech.org">qelectrotech.org</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
os.makedirs("gh-pages", exist_ok=True)
|
||||
with open("gh-pages/index.html", "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
print("index.html written OK")
|
||||
@@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
QElectroTech.wxs - WiX v7 installer definition
|
||||
Generates a self-contained x64 MSI for QElectroTech (MSYS2/UCRT64 build).
|
||||
|
||||
Variables passed from the wix build command line:
|
||||
-d FilesDir=<absolute path to artifact\files>
|
||||
-d Version=<numeric version e.g. 0.100.1.0>
|
||||
-d ProductVersion=<display version e.g. 0.100.1-r8770-abc1234_x86_64-win64>
|
||||
-d LicenseRtf=<absolute path to License.rtf>
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
|
||||
<Package
|
||||
Name="QElectroTech"
|
||||
Manufacturer="QElectroTech Team"
|
||||
Version="$(var.Version)"
|
||||
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
|
||||
Language="1033"
|
||||
Codepage="1252"
|
||||
InstallerVersion="500"
|
||||
Scope="perMachine">
|
||||
|
||||
<!-- Embed all cabinet files inside the MSI (self-contained installer) -->
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
<!-- In-place upgrade: automatically uninstalls the previous version -->
|
||||
<MajorUpgrade
|
||||
DowngradeErrorMessage="A newer version of QElectroTech is already installed."
|
||||
Schedule="afterInstallInitialize" />
|
||||
|
||||
<!-- Installation directory -->
|
||||
<StandardDirectory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLDIR" Name="QElectroTech" />
|
||||
</StandardDirectory>
|
||||
|
||||
<!-- ============================================================
|
||||
All application files harvested in one pass from files\**
|
||||
(Lancer QET.bat has been removed from the artifact before
|
||||
this build — see windows-msi.yml step "Remove Lancer QET.bat")
|
||||
============================================================ -->
|
||||
<ComponentGroup Id="CG_AllFiles" Directory="INSTALLDIR">
|
||||
<Files Include="$(var.FilesDir)\**" />
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- ============================================================
|
||||
Desktop + Start Menu shortcuts
|
||||
Point directly to qelectrotech.exe with all required arguments.
|
||||
No .bat wrapper needed.
|
||||
============================================================ -->
|
||||
<ComponentGroup Id="CG_Shortcuts" Directory="INSTALLDIR">
|
||||
|
||||
<Component Id="C_ShortcutDesktop" Guid="F1A2B3C4-D5E6-7890-5678-012345678901">
|
||||
<Shortcut Id="DesktopShortcut"
|
||||
Directory="DesktopFolder"
|
||||
Name="QElectroTech"
|
||||
Target="[INSTALLDIR]bin\qelectrotech.exe"
|
||||
Arguments="--common-elements-dir="[INSTALLDIR]elements/" --common-tbt-dir="[INSTALLDIR]titleblocks/" --lang-dir="[INSTALLDIR]lang/" -style windowsvista"
|
||||
Icon="qet.ico"
|
||||
WorkingDirectory="INSTALLDIR" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\QElectroTech"
|
||||
Name="DesktopShortcut"
|
||||
Type="integer" Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<Component Id="C_ShortcutStartMenu" Guid="A2B3C4D5-E6F7-8901-6789-123456789012">
|
||||
<Shortcut Id="StartMenuShortcut"
|
||||
Directory="ProgramMenuFolder"
|
||||
Name="QElectroTech"
|
||||
Target="[INSTALLDIR]bin\qelectrotech.exe"
|
||||
Arguments="--common-elements-dir="[INSTALLDIR]elements/" --common-tbt-dir="[INSTALLDIR]titleblocks/" --lang-dir="[INSTALLDIR]lang/" -style windowsvista"
|
||||
Icon="qet.ico"
|
||||
WorkingDirectory="INSTALLDIR" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\QElectroTech"
|
||||
Name="StartMenuShortcut"
|
||||
Type="integer" Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- ============================================================
|
||||
.qet file association
|
||||
============================================================ -->
|
||||
<ComponentGroup Id="CG_FileAssoc" Directory="INSTALLDIR">
|
||||
<Component Id="C_FileAssoc" Guid="B3C4D5E6-F7A8-9012-7890-234567890123">
|
||||
<RegistryValue Root="HKCR" Key=".qet" Type="string" Value="QElectroTech.Document" KeyPath="yes" />
|
||||
<RegistryValue Root="HKCR" Key="QElectroTech.Document" Type="string" Value="QElectroTech Project" />
|
||||
<RegistryValue Root="HKCR" Key="QElectroTech.Document\DefaultIcon" Type="string"
|
||||
Value="[INSTALLDIR]ico\qelectrotech.ico" />
|
||||
<RegistryValue Root="HKCR" Key="QElectroTech.Document\shell\open\command" Type="string"
|
||||
Value=""[INSTALLDIR]bin\qelectrotech.exe" "%1"" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- ============================================================
|
||||
Icon for shortcuts
|
||||
============================================================ -->
|
||||
<Icon Id="qet.ico" SourceFile="$(var.FilesDir)\ico\qelectrotech.ico" />
|
||||
|
||||
<!-- ============================================================
|
||||
CustomAction: set elements\ subtree read-only after install
|
||||
|
||||
Pattern used: two-step SetProperty + Exec
|
||||
1. CA_ResolveElementsPath (immediate, SetProperty):
|
||||
copies the runtime value of INSTALLDIR into the property
|
||||
ELEMENTS_READONLY_CMD, building the full powershell command.
|
||||
2. CA_SetElementsReadOnly (deferred, Execute="deferred"):
|
||||
runs the command stored in ELEMENTS_READONLY_CMD.
|
||||
|
||||
This is the correct WiX v4/v7 pattern for passing a
|
||||
directory path (resolved at runtime) to a deferred CA.
|
||||
============================================================ -->
|
||||
|
||||
<!-- Step 1: build the powershell command with the resolved INSTALLDIR -->
|
||||
<CustomAction Id="CA_ResolveElementsPath"
|
||||
Property="CA_SetElementsReadOnly"
|
||||
Value="powershell.exe -NonInteractive -NoProfile -WindowStyle Hidden -Command "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)
|
||||
============================================================ -->
|
||||
<Feature Id="ProductFeature" Title="QElectroTech" Level="1">
|
||||
<ComponentGroupRef Id="CG_AllFiles" />
|
||||
<ComponentGroupRef Id="CG_Shortcuts" />
|
||||
<ComponentGroupRef Id="CG_FileAssoc" />
|
||||
</Feature>
|
||||
|
||||
<!-- WixUI_Minimal with QElectroTech GPL-2 license -->
|
||||
<ui:WixUI Id="WixUI_Minimal" />
|
||||
|
||||
<!-- WixVariable without ui: namespace, at Package level -->
|
||||
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
|
||||
|
||||
<!-- Properties shown in Programs and Features -->
|
||||
<Property Id="ARPPRODUCTICON" Value="qet.ico" />
|
||||
<Property Id="ARPHELPLINK" Value="https://qelectrotech.org" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://qelectrotech.org" />
|
||||
<Property Id="ARPCOMMENTS" Value="Free electrical schematic editor" />
|
||||
|
||||
</Package>
|
||||
</Wix>
|
||||
+1
-1
Submodule elements updated: 73c0dfb4bf...f6a422ab00
Binary file not shown.
+5641
-968
File diff suppressed because it is too large
Load Diff
+293
-121
@@ -16,7 +16,6 @@
|
||||
# along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Need homebrew and coreutils installed see <http://brew.sh>.
|
||||
|
||||
|
||||
#Force MacOSX12.3.sdk
|
||||
#see: https://www.downtowndougbrown.com/2023/08/how-to-create-a-qt-5-arm-intel-universal-binary-for-mac/
|
||||
|
||||
@@ -26,14 +25,20 @@ export DEVELOPER_DIR=/Applications/Xcode_14.01.app/Contents/Developer
|
||||
APPNAME='qelectrotech'
|
||||
BUNDLE=$APPNAME.app
|
||||
APPBIN="$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
IDENTITY="Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)"
|
||||
|
||||
# Emplacement du script
|
||||
# Temp paths
|
||||
RW_DMG="/tmp/qet_rw.dmg"
|
||||
MOUNT_POINT="/tmp/qet_dmg_mount"
|
||||
STAGING="/tmp/qet_dmg_staging"
|
||||
|
||||
# Script location
|
||||
current_dir=$(dirname "$0")
|
||||
|
||||
# On se remet au depart
|
||||
# Go back to repo root
|
||||
cd "${current_dir}/../"
|
||||
|
||||
# Emplacement courant
|
||||
# Current directory
|
||||
current_dir=$(PWD)
|
||||
|
||||
|
||||
@@ -46,11 +51,11 @@ echo "Please see the \"Deploying an Application on Qt/Mac\""
|
||||
echo "page in the Qt documentation for more information."
|
||||
echo
|
||||
echo "This script :"
|
||||
echo "\t - up date the git depot"
|
||||
echo "\t - built the application bundle,"
|
||||
echo "\t - update the git depot"
|
||||
echo "\t - build the application bundle,"
|
||||
echo "\t - copy over required Qt frameworks,"
|
||||
echo "\t - copy additional files: translations, titleblocks and elements,"
|
||||
echo "\t - create image disk."
|
||||
echo "\t - notarize the .app, then create a signed DMG."
|
||||
echo
|
||||
echo "Enjoy ;-)"
|
||||
echo
|
||||
@@ -70,25 +75,23 @@ echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Run GIT:"
|
||||
|
||||
# Fait une mise à jour
|
||||
git submodule init
|
||||
git submodule update
|
||||
git pull --recurse-submodules
|
||||
git pull
|
||||
#git checkout foliolist_position
|
||||
|
||||
# recupere le numero de la nouvelle revision
|
||||
|
||||
# Get revision number and version
|
||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
||||
A=$(git rev-list HEAD --count)
|
||||
HEAD=$(($A+473))
|
||||
|
||||
|
||||
VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
||||
#VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{ 0, "| head -n 1| cut -c32-40| sed -e 's/,/./g' -e 's/ //g') #Find major, minor, and micro version numbers in sources/qetversion.cp
|
||||
|
||||
# Tarball de la dernière revision déjà créé
|
||||
if [ -e "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip" ] ; then
|
||||
DMG_NAME="${APPNAME}-$VERSION-r$HEAD-arm64.dmg"
|
||||
DMG_PATH="build-aux/mac-osx/$DMG_NAME"
|
||||
|
||||
# Check if already built
|
||||
if [ -e "$DMG_PATH" ] ; then
|
||||
echo "There are not new updates, make disk image can"
|
||||
echo "take a lot of time (5 min). Can you continu?"
|
||||
echo "[y/n]"
|
||||
@@ -108,28 +111,27 @@ echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Run make install:"
|
||||
|
||||
# pour effacer l’ancienne compilation
|
||||
# Remove old bundle
|
||||
if [ -d $BUNDLE ] ; then
|
||||
echo "Removing hold bundle..."
|
||||
echo "Removing old bundle..."
|
||||
rm -rf $BUNDLE
|
||||
fi
|
||||
if [ -e Makefile ] ; then
|
||||
echo "Removing hold Makefile..."
|
||||
echo "Removing old Makefile..."
|
||||
rm .qmake.stash
|
||||
make clean
|
||||
fi
|
||||
|
||||
# genere le Makefile
|
||||
# Generate Makefile
|
||||
echo "Generating new makefile..."
|
||||
qmake -spec macx-clang
|
||||
qmake -spec macx-clang
|
||||
|
||||
# compilation
|
||||
# Compile
|
||||
if [ -e Makefile.Release ] ; then
|
||||
START_TIME=$SECONDS
|
||||
|
||||
# arret du script si erreur de compilation
|
||||
START_TIME=$SECONDS
|
||||
|
||||
testSuccessBuild () {
|
||||
if [ $? -ne 0 ]; then
|
||||
if [ $? -ne 0 ]; then
|
||||
cleanVerionTag
|
||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
||||
echo
|
||||
@@ -138,19 +140,18 @@ if [ -e Makefile.Release ] ; then
|
||||
fi
|
||||
}
|
||||
|
||||
# utilise tout les coeurs pour une compilation plus rapide
|
||||
coeur=$(sysctl hw.ncpu | awk '{print $2}')
|
||||
if [ $? -ne 0 ]; then
|
||||
if [ $? -ne 0 ]; then
|
||||
make -f Makefile.Release
|
||||
testSuccessBuild
|
||||
else
|
||||
make -j$(($coeur + 1)) -f Makefile.Release
|
||||
testSuccessBuild
|
||||
fi
|
||||
|
||||
|
||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
||||
echo
|
||||
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
||||
echo
|
||||
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
||||
else
|
||||
echo "ERROR: Makefile not found. This script requires the macx-clang makespec"
|
||||
exit
|
||||
@@ -158,8 +159,7 @@ fi
|
||||
|
||||
cp -R ${current_dir}/misc/Info.plist qelectrotech.app/Contents/
|
||||
cp -R ${current_dir}/ico/mac_icon/*.icns qelectrotech.app/Contents/Resources/
|
||||
# On rajoute le numero de version pour "cmd + i"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist" # Version number
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist"
|
||||
|
||||
|
||||
### copy over frameworks ############################################
|
||||
@@ -168,70 +168,58 @@ echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Copy Qt libraries and private frameworks:"
|
||||
|
||||
echo "Processing Mac deployment tool..."
|
||||
echo "Processing Mac deployment tool..."
|
||||
if [ ! -d $BUNDLE ] ; then
|
||||
echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory"
|
||||
exit
|
||||
fi
|
||||
#~/Qt/5.5/clang_64/bin/macdeployqt $BUNDLE
|
||||
macdeployqt $BUNDLE
|
||||
|
||||
### add file missing #######################################
|
||||
### add missing files ###############################################
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Copy file missing:"
|
||||
echo "Copy missing files:"
|
||||
|
||||
# Dossier à ajouter
|
||||
QET_ELMT_DIR="${current_dir}/elements/"
|
||||
QET_TBT_DIR="${current_dir}/titleblocks/"
|
||||
QET_LANG_DIR="${current_dir}/lang/"
|
||||
QET_EXAMPLES_DIR="${current_dir}/examples/"
|
||||
QET_FONTS_DIR="${current_dir}/fonts/"
|
||||
QET_LICENSES_DIR="${current_dir}/licenses/"
|
||||
|
||||
|
||||
# Add new folder for Qt dialog translation see
|
||||
## see <https://download.tuxfamily.org/qet/Qt_lang/>.
|
||||
|
||||
LANG_DIR="${current_dir}/lang1/"
|
||||
|
||||
if [ -d "${QET_ELMT_DIR}" ]; then
|
||||
echo "Copying add elements in the bundle..."
|
||||
#mkdir $BUNDLE/Contents/Resources/elements
|
||||
echo "Copying elements in the bundle..."
|
||||
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
||||
fi
|
||||
|
||||
if [ -d "${QET_TBT_DIR}" ]; then
|
||||
echo "Copying titleblocks in the bundle..."
|
||||
#mkdir $BUNDLE/Contents/Resources/titleblocks
|
||||
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
||||
fi
|
||||
|
||||
if [ -d "${QET_LANG_DIR}" ]; then
|
||||
echo "Copying translations in the bundle... "
|
||||
echo "Copying translations in the bundle..."
|
||||
mkdir $BUNDLE/Contents/Resources/lang
|
||||
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
|
||||
fi
|
||||
|
||||
if [ -d "${LANG_DIR}" ]; then
|
||||
echo "Copying translations in the bundle... "
|
||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
||||
|
||||
echo "Copying extra translations in the bundle..."
|
||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
||||
fi
|
||||
|
||||
if [ -d "${QET_EXAMPLES_DIR}" ]; then
|
||||
echo "Copying examples in the bundle... "
|
||||
mkdir $BUNDLE/Contents/Resources/examples
|
||||
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
|
||||
|
||||
echo "Copying examples in the bundle..."
|
||||
mkdir $BUNDLE/Contents/Resources/examples
|
||||
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
|
||||
fi
|
||||
|
||||
if [ -d "${QET_FONTS_DIR}" ]; then
|
||||
echo "Copying fonts in the bundle... "
|
||||
mkdir $BUNDLE/Contents/Resources/fonts
|
||||
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
|
||||
|
||||
echo "Copying fonts in the bundle..."
|
||||
mkdir $BUNDLE/Contents/Resources/fonts
|
||||
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
|
||||
fi
|
||||
|
||||
if [ -d "${QET_LICENSES_DIR}" ]; then
|
||||
@@ -240,98 +228,282 @@ if [ -d "${QET_LICENSES_DIR}" ]; then
|
||||
cp -R -L ${QET_LICENSES_DIR} $BUNDLE/Contents/Resources/licenses
|
||||
fi
|
||||
|
||||
codesign --force --deep --sign --timestamp -s "Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)" --options=runtime $BUNDLE
|
||||
### create zip tarball ###############################################
|
||||
### Sign the bundle #################################################
|
||||
# Sign in correct order: all dylibs first (including flat libs copied
|
||||
# by macdeployqt from Homebrew), then frameworks, plugins, bundle last.
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Create zip tarball:"
|
||||
echo "Code signing (dylibs -> frameworks -> plugins -> bundle):"
|
||||
|
||||
/usr/bin/ditto -c -k --keepParent $BUNDLE "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip"
|
||||
# 1. Sign all flat .dylib files in Frameworks/
|
||||
echo "-- Signing dylibs in Frameworks/..."
|
||||
find "$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
|
||||
echo " $(basename $lib)"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
### notarize zip tarball ###############################################
|
||||
echo -e "\033[1;31mWould you like to upload for Notarize packages "${APPNAME}"-"$VERSION"-"r$HEAD-arm64.zip", n/Y?.\033[m"
|
||||
# 2. Sign .framework bundles
|
||||
echo "-- Signing .framework bundles..."
|
||||
find "$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
|
||||
echo " $(basename $fw)"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
|
||||
done
|
||||
|
||||
# 3. Sign plugins
|
||||
echo "-- Signing plugins..."
|
||||
find "$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
|
||||
echo " $(basename $lib)"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
# 4. Sign any dylibs in MacOS/
|
||||
echo "-- Signing dylibs in MacOS/..."
|
||||
find "$BUNDLE/Contents/MacOS" -name "*.dylib" | while read lib; do
|
||||
echo " $(basename $lib)"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
# 5. Sign the main executable explicitly
|
||||
echo "-- Signing main executable..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
"$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
|
||||
# 6. Sign the bundle itself last
|
||||
echo "-- Signing bundle..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE"
|
||||
|
||||
# 7. Verify
|
||||
echo
|
||||
echo "Verifying bundle signature..."
|
||||
codesign --verify --deep --strict --verbose=2 "$BUNDLE"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: bundle signature verification failed, aborting."
|
||||
exit 1
|
||||
fi
|
||||
spctl -a -vv "$BUNDLE"
|
||||
echo "Bundle signature OK."
|
||||
|
||||
### Notarize the .app (via temporary ZIP) ###########################
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Create temporary ZIP for notarization:"
|
||||
|
||||
NOTARIZE_ZIP="/tmp/${APPNAME}-$VERSION-r$HEAD-arm64-notarize.zip"
|
||||
/usr/bin/ditto -c -k --keepParent "$BUNDLE" "$NOTARIZE_ZIP"
|
||||
|
||||
echo -e "\033[1;31mWould you like to notarize the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
|
||||
read a
|
||||
if [[ $a == "Y" || $a == "y" ]]; then
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Notarize zip tarball:"
|
||||
|
||||
xcrun notarytool submit build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip --keychain-profile "org.qelectrotech" --wait
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Notarizing .app:"
|
||||
xcrun notarytool submit "$NOTARIZE_ZIP" --keychain-profile "org.qelectrotech" --wait
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: notarization failed. Check the log with:"
|
||||
echo " xcrun notarytool log <submission-id> --keychain-profile org.qelectrotech"
|
||||
rm -f "$NOTARIZE_ZIP"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
fi
|
||||
|
||||
### The end, process is done ##########################################
|
||||
echo "Cleaning up temporary notarization ZIP..."
|
||||
rm -f "$NOTARIZE_ZIP"
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "The process of creating deployable application zip is done."
|
||||
echo The disque image is in the folder \'build-aux/mac-osx\'.
|
||||
# Affiche les mise à jour depuis l'ancienne revision
|
||||
#if [ ! $(($HEAD - $revAv)) -eq 0 ] ; then
|
||||
# echo
|
||||
# echo "There are new updates. This numero of revision is $HEAD."
|
||||
# svn log -l $(($HEAD - $revAv))
|
||||
#else
|
||||
# echo
|
||||
# echo "There are not new updates. This numero of revision is $HEAD."
|
||||
# fi
|
||||
# echo
|
||||
### Staple the .app #################################################
|
||||
|
||||
# La version en local n'est pas conforme à la dernière version svn
|
||||
# svnversion | grep -q '[MS:]' ; if [ $? -eq 0 ] ; then
|
||||
# echo Please note that the latest \local version is $(svnversion).
|
||||
# echo This is not the same version as the deposit.
|
||||
# echo You can use \'svn diff\' to see the differences.
|
||||
# echo And use \'svn revert \<fichier\>\' to delete the difference.
|
||||
# echo To go back, you can use svn update -r 360
|
||||
# echo to go to revision number 360.
|
||||
# echo
|
||||
#fi
|
||||
|
||||
# Clean up disk folder
|
||||
echo 'Cleaning up... '
|
||||
rm "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip"
|
||||
|
||||
# staple the app
|
||||
echo -e "\033[1;31mWould you like to staple the app MacOS packages "${APPNAME}"-"$VERSION"-"r$HEAD", n/Y?.\033[m"
|
||||
echo -e "\033[1;31mWould you like to staple the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
|
||||
read a
|
||||
if [[ $a == "Y" || $a == "y" ]]; then
|
||||
xcrun stapler staple -v $BUNDLE
|
||||
xcrun stapler staple -v "$BUNDLE"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: stapling .app failed."
|
||||
exit 1
|
||||
fi
|
||||
echo "Verifying staple on .app..."
|
||||
xcrun stapler validate -v "$BUNDLE"
|
||||
spctl -a -vv "$BUNDLE"
|
||||
echo ".app stapled OK."
|
||||
else
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
fi
|
||||
|
||||
### Create staging folder with Applications symlink #################
|
||||
# The staging folder contains the .app and a symlink to /Applications
|
||||
# so the user can drag-and-drop to install directly from the DMG.
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Re Create zip tarball:"
|
||||
echo "Preparing DMG staging folder:"
|
||||
|
||||
/usr/bin/ditto -c -k --sequesterRsrc --keepParent $BUNDLE "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip"
|
||||
rm -rf "$STAGING"
|
||||
mkdir -p "$STAGING"
|
||||
cp -R "$BUNDLE" "$STAGING/"
|
||||
ln -s /Applications "$STAGING/Applications"
|
||||
echo "Staging folder ready: $STAGING"
|
||||
|
||||
### Create writable DMG (UDRW) ######################################
|
||||
# We use a writable DMG first so we can re-sign the .app inside
|
||||
# after hdiutil copies it (hdiutil can invalidate Sealed Resources
|
||||
# during the copy, so we must re-sign inside the mounted volume).
|
||||
|
||||
# Clean up disk folder
|
||||
echo 'Cleaning up... '
|
||||
rm -rf $BUNDLE
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Create writable DMG (UDRW) and re-sign .app inside:"
|
||||
|
||||
rm -f "$RW_DMG"
|
||||
hdiutil create \
|
||||
-volname "QElectroTech $VERSION" \
|
||||
-srcfolder "$STAGING" \
|
||||
-ov \
|
||||
-format UDRW \
|
||||
-fs HFS+ \
|
||||
"$RW_DMG"
|
||||
|
||||
#rsync to TF DMG builds
|
||||
echo -e "\033[1;31mWould you like to upload MacOS packages "${APPNAME}"-"$VERSION"-"r$HEAD-arm64.zip", n/Y?.\033[m"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: hdiutil failed to create writable DMG."
|
||||
rm -rf "$STAGING"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mount the writable DMG
|
||||
rm -rf "$MOUNT_POINT"
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
hdiutil attach "$RW_DMG" -mountpoint "$MOUNT_POINT" -nobrowse -noverify
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: failed to mount writable DMG."
|
||||
rm -f "$RW_DMG"
|
||||
rm -rf "$STAGING"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Re-sign all binaries inside the mounted DMG
|
||||
echo "-- Re-signing dylibs inside DMG..."
|
||||
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
|
||||
done
|
||||
|
||||
find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
echo "-- Re-signing main executable inside DMG..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
|
||||
echo "-- Re-signing bundle inside DMG..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
"$MOUNT_POINT/$BUNDLE"
|
||||
|
||||
# Verify signature inside the mounted DMG
|
||||
echo "Verifying bundle signature inside DMG..."
|
||||
codesign --verify --deep --strict --verbose=2 "$MOUNT_POINT/$BUNDLE"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: bundle signature invalid inside DMG, aborting."
|
||||
hdiutil detach "$MOUNT_POINT"
|
||||
rm -f "$RW_DMG"
|
||||
rm -rf "$STAGING" "$MOUNT_POINT"
|
||||
exit 1
|
||||
fi
|
||||
echo "Bundle signature inside DMG OK."
|
||||
|
||||
# Detach the writable DMG
|
||||
hdiutil detach "$MOUNT_POINT"
|
||||
|
||||
### Convert UDRW to final compressed UDZO ###########################
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Convert to final compressed DMG (UDZO):"
|
||||
|
||||
mkdir -p "build-aux/mac-osx"
|
||||
rm -f "$DMG_PATH"
|
||||
|
||||
hdiutil convert "$RW_DMG" \
|
||||
-format UDZO \
|
||||
-o "$DMG_PATH"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: hdiutil convert failed."
|
||||
rm -f "$RW_DMG"
|
||||
rm -rf "$STAGING" "$MOUNT_POINT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$RW_DMG"
|
||||
rm -rf "$STAGING" "$MOUNT_POINT"
|
||||
|
||||
### Sign the final DMG ##############################################
|
||||
|
||||
echo "Signing final DMG..."
|
||||
codesign --sign "$IDENTITY" --timestamp "$DMG_PATH"
|
||||
|
||||
### Notarize and staple the final DMG ###############################
|
||||
|
||||
echo -e "\033[1;31mWould you like to notarize the DMG \"${DMG_NAME}\", n/Y?\033[m"
|
||||
read a
|
||||
if [[ $a == "Y" || $a == "y" ]]; then
|
||||
cp -Rf "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-arm64.zip" /Users/laurent/MAC_OS_X/
|
||||
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w --progress --exclude='.DS_Store' /Users/laurent/MAC_OS_X/ server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
|
||||
if [ $? != 0 ]; then
|
||||
{
|
||||
echo "RSYNC ERROR: problem syncing ${APPNAME}-$VERSION-r$HEAD-arm64.zip"
|
||||
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w --progress --exclude='.DS_Store' /Users/laurent/MAC_OS_X/ server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Notarizing DMG:"
|
||||
xcrun notarytool submit "$DMG_PATH" --keychain-profile "org.qelectrotech" --wait
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: DMG notarization failed. Check the log with:"
|
||||
echo " xcrun notarytool log <submission-id> --keychain-profile org.qelectrotech"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
} fi
|
||||
echo "Stapling DMG..."
|
||||
xcrun stapler staple "$DMG_PATH"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: stapling DMG failed."
|
||||
exit 1
|
||||
fi
|
||||
echo "DMG notarized and stapled OK."
|
||||
|
||||
echo "Verifying final DMG..."
|
||||
spctl -a -vv "$DMG_PATH"
|
||||
else
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
fi
|
||||
|
||||
### Clean up bundle #################################################
|
||||
|
||||
echo "Cleaning up bundle..."
|
||||
rm -rf "$BUNDLE"
|
||||
|
||||
### The end #########################################################
|
||||
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "The process is done."
|
||||
echo "DMG is in the folder 'build-aux/mac-osx'."
|
||||
|
||||
### Upload via rsync ################################################
|
||||
|
||||
echo -e "\033[1;31mWould you like to upload MacOS package \"${DMG_NAME}\", n/Y?\033[m"
|
||||
read a
|
||||
if [[ $a == "Y" || $a == "y" ]]; then
|
||||
cp -Rf "$DMG_PATH" /Users/laurent/MAC_OS_X/
|
||||
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
|
||||
--progress --exclude='.DS_Store' \
|
||||
/Users/laurent/MAC_OS_X/ \
|
||||
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
|
||||
if [ $? != 0 ]; then
|
||||
echo "RSYNC ERROR: problem syncing ${DMG_NAME}, retrying..."
|
||||
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
|
||||
--progress --exclude='.DS_Store' \
|
||||
/Users/laurent/MAC_OS_X/ \
|
||||
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
|
||||
fi
|
||||
else
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
fi
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "qeticons.h"
|
||||
#include "qetproject.h"
|
||||
#include "titleblock/templatescollection.h"
|
||||
#include <QApplication>
|
||||
|
||||
/*
|
||||
Lorsque le flag ENABLE_PANEL_DND_CHECKS est defini, le panel d'elements
|
||||
@@ -42,7 +43,7 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
|
||||
first_reload_(true)
|
||||
{
|
||||
// selection unique
|
||||
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
setColumnCount(1);
|
||||
setExpandsOnDoubleClick(true);
|
||||
setMouseTracking(true);
|
||||
@@ -299,11 +300,14 @@ void ElementsPanel::reload()
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementsPanel::slot_clicked
|
||||
handle click on qtwi
|
||||
@param qtwi item that was clickerd on
|
||||
*/
|
||||
* @brief ElementsPanel::slot_clicked
|
||||
* handle click on qtwi
|
||||
* @param qtwi item that was clickerd on
|
||||
*/
|
||||
void ElementsPanel::slot_clicked(QTreeWidgetItem *clickedItem, int) {
|
||||
if (QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestForItem(clickedItem);
|
||||
}
|
||||
@@ -553,3 +557,20 @@ void ElementsPanel::keyPressEvent(QKeyEvent *event)
|
||||
QTreeView::keyPressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ElementsPanel::selectedDiagrams
|
||||
* @return A list of all currently selected diagrams in the panel.
|
||||
*/
|
||||
QList<Diagram *> ElementsPanel::selectedDiagrams() const
|
||||
{
|
||||
QList<Diagram *> diagrams;
|
||||
foreach (QTreeWidgetItem *item, selectedItems()) {
|
||||
if (item->type() == QET::Diagram) {
|
||||
if (Diagram *diagram = valueForItem<Diagram *>(item)) {
|
||||
diagrams.append(diagram);
|
||||
}
|
||||
}
|
||||
}
|
||||
return diagrams;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ class ElementsPanel : public GenericPanel {
|
||||
// methods used to get what is represented by a particular visual item
|
||||
QString dirPathForItem(QTreeWidgetItem *);
|
||||
QString filePathForItem(QTreeWidgetItem *);
|
||||
QList<Diagram *> selectedDiagrams() const;
|
||||
|
||||
signals:
|
||||
void requestForProject(QETProject *);
|
||||
|
||||
+181
-122
@@ -25,6 +25,7 @@
|
||||
#include "qetproject.h"
|
||||
#include "titleblock/templatedeleter.h"
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
|
||||
/*
|
||||
When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel
|
||||
@@ -242,85 +243,134 @@ void ElementsPanelWidget::newDiagram()
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramDeletion avec le schema selectionne
|
||||
*/
|
||||
* Emet le signal requestForDiagramsDeletion avec les schemas selectionnes
|
||||
*/
|
||||
void ElementsPanelWidget::deleteDiagram()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramDeletion(selected_diagram));
|
||||
elements_panel->reload();
|
||||
QList<Diagram *> diagrams_to_delete = elements_panel->selectedDiagrams();
|
||||
|
||||
if (diagrams_to_delete.isEmpty()) return;
|
||||
|
||||
emit(requestForDiagramsDeletion(diagrams_to_delete));
|
||||
|
||||
elements_panel->reload();
|
||||
}
|
||||
/**
|
||||
* Emits the requestForDiagramMoveUpTop signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUpTop() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveUpTop(diagrams_to_move);
|
||||
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveUpTop avec le schema selectionne
|
||||
+*/
|
||||
void ElementsPanelWidget::moveDiagramUpTop()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveUpTop(selected_diagram));
|
||||
}
|
||||
}
|
||||
* Emits the requestForDiagramMoveUp signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUp() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveUp(diagrams_to_move);
|
||||
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveUp avec le schema selectionne
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUp()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveUp(selected_diagram));
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveDown avec le schema selectionne
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramDown()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveDown(selected_diagram));
|
||||
* Emits the requestForDiagramMoveDown signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramDown() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveDown(diagrams_to_move);
|
||||
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveUpx10 avec le schema selectionne
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUpx10()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveUpx10(selected_diagram));
|
||||
* Emits the requestForDiagramMoveUpx10 signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUpx10() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveUpx10(diagrams_to_move);
|
||||
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveUpx100 avec le schema selectionne
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUpx100()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveUpx100(selected_diagram));
|
||||
* Emits the requestForDiagramMoveUpx100 signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramUpx100() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveUpx100(diagrams_to_move);
|
||||
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveDownx10 avec le schema selectionne
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramDownx10()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveDownx10(selected_diagram));
|
||||
* Emits the requestForDiagramMoveDownx10 signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramDownx10() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveDownx10(diagrams_to_move);
|
||||
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Emet le signal requestForDiagramMoveDownx100 avec le schema selectionne
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramDownx100()
|
||||
{
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramMoveDownx100(selected_diagram));
|
||||
* Emits the requestForDiagramMoveDownx100 signal with all selected diagrams.
|
||||
*/
|
||||
void ElementsPanelWidget::moveDiagramDownx100() {
|
||||
QList<Diagram *> diagrams_to_move = elements_panel->selectedDiagrams();
|
||||
if (diagrams_to_move.isEmpty()) return;
|
||||
|
||||
// Emit the entire list at once
|
||||
emit requestForDiagramMoveDownx100(diagrams_to_move);
|
||||
|
||||
// Clear messy tree selection caused by moving items, then restore clean selection
|
||||
elements_panel->clearSelection();
|
||||
for (Diagram *d : diagrams_to_move) {
|
||||
if (auto item = elements_panel->getItemForDiagram(d)) item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,21 +428,35 @@ void ElementsPanelWidget::updateButtons()
|
||||
bool is_writable = !(elements_panel -> selectedProject() -> isReadOnly());
|
||||
prj_add_diagram -> setEnabled(is_writable);
|
||||
} else if (current_type == QET::Diagram) {
|
||||
Diagram *selected_diagram = elements_panel -> selectedDiagram();
|
||||
QETProject *selected_diagram_project = selected_diagram -> project();
|
||||
// Fetch ALL selected diagrams instead of just one
|
||||
QList<Diagram *> selected_diagrams = elements_panel -> selectedDiagrams();
|
||||
|
||||
bool is_writable = !(selected_diagram_project -> isReadOnly());
|
||||
int project_diagrams_count = selected_diagram_project -> diagrams().count();
|
||||
int diagram_position = selected_diagram_project -> diagrams().indexOf(selected_diagram);
|
||||
if (!selected_diagrams.isEmpty()) {
|
||||
QETProject *selected_diagram_project = selected_diagrams.first() -> project();
|
||||
bool is_writable = !(selected_diagram_project -> isReadOnly());
|
||||
int project_diagrams_count = selected_diagram_project -> diagrams().count();
|
||||
|
||||
prj_del_diagram -> setEnabled(is_writable);
|
||||
prj_move_diagram_up -> setEnabled(is_writable && diagram_position > 0);
|
||||
prj_move_diagram_down -> setEnabled(is_writable && diagram_position < project_diagrams_count - 1);
|
||||
prj_move_diagram_top -> setEnabled(is_writable && diagram_position > 0);
|
||||
prj_move_diagram_upx10 -> setEnabled(is_writable && diagram_position > 10);
|
||||
prj_move_diagram_upx100 -> setEnabled(is_writable && diagram_position > 100);
|
||||
prj_move_diagram_downx10 -> setEnabled(is_writable && diagram_position < project_diagrams_count - 10);
|
||||
prj_move_diagram_downx100 -> setEnabled(is_writable && diagram_position < project_diagrams_count - 100);
|
||||
// Find the highest (min) and lowest (max) index among the selection
|
||||
int min_position = project_diagrams_count;
|
||||
int max_position = -1;
|
||||
|
||||
for (Diagram *diagram : selected_diagrams) {
|
||||
int pos = selected_diagram_project -> diagrams().indexOf(diagram);
|
||||
if (pos < min_position) min_position = pos;
|
||||
if (pos > max_position) max_position = pos;
|
||||
}
|
||||
|
||||
prj_del_diagram -> setEnabled(is_writable);
|
||||
prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
|
||||
prj_move_diagram_down -> setEnabled(is_writable && max_position < project_diagrams_count - 1);
|
||||
prj_move_diagram_top -> setEnabled(is_writable && min_position > 0);
|
||||
|
||||
// Adjusted to >= to allow exactly 10 or 100 steps if space permits
|
||||
prj_move_diagram_upx10 -> setEnabled(is_writable && min_position > 10);
|
||||
prj_move_diagram_upx100 -> setEnabled(is_writable && min_position > 100);
|
||||
prj_move_diagram_downx10 -> setEnabled(is_writable && max_position < project_diagrams_count - 10);
|
||||
prj_move_diagram_downx100 -> setEnabled(is_writable && max_position < project_diagrams_count - 100);
|
||||
}
|
||||
} else if (current_type == QET::TitleBlockTemplatesCollection) {
|
||||
TitleBlockTemplateLocation location = elements_panel -> templateLocationForItem(current_item);
|
||||
tbt_add -> setEnabled(!location.isReadOnly());
|
||||
@@ -475,62 +539,57 @@ void ElementsPanelWidget::filterEdited(const QString &next_text) {
|
||||
}
|
||||
|
||||
/**
|
||||
Treat key press event inside elements panel widget
|
||||
*/
|
||||
void ElementsPanelWidget::keyPressEvent (QKeyEvent *e) {
|
||||
switch(e -> key()) {
|
||||
case Qt::Key_Delete: //delete diagram through elements panel widget
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
emit(requestForDiagramDeletion(selected_diagram));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F3:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveUp(selected_diagram));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F4:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveDown(selected_diagram));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F5:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveUpTop(selected_diagram));
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_F6:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveDownx10(selected_diagram));
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_F7:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveDownx100(selected_diagram));
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case Qt::Key_F8:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveUpx10(selected_diagram));
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_F9:
|
||||
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
|
||||
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
|
||||
emit(requestForDiagramMoveUpx100(selected_diagram));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
* Treat key press event inside elements panel widget
|
||||
*/
|
||||
/**
|
||||
* Treat key press event inside elements panel widget.
|
||||
* Respects the enabled/disabled state of the corresponding QActions.
|
||||
*/
|
||||
void ElementsPanelWidget::keyPressEvent(QKeyEvent *e) {
|
||||
switch(e->key()) {
|
||||
case Qt::Key_Delete:
|
||||
if (prj_del_diagram && prj_del_diagram->isEnabled()) {
|
||||
deleteDiagram();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F3:
|
||||
if (prj_move_diagram_up && prj_move_diagram_up->isEnabled()) {
|
||||
moveDiagramUp();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F4:
|
||||
if (prj_move_diagram_down && prj_move_diagram_down->isEnabled()) {
|
||||
moveDiagramDown();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F5:
|
||||
if (prj_move_diagram_top && prj_move_diagram_top->isEnabled()) {
|
||||
moveDiagramUpTop();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F6:
|
||||
if (prj_move_diagram_downx10 && prj_move_diagram_downx10->isEnabled()) {
|
||||
moveDiagramDownx10();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F7:
|
||||
if (prj_move_diagram_downx100 && prj_move_diagram_downx100->isEnabled()) {
|
||||
moveDiagramDownx100();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F8:
|
||||
if (prj_move_diagram_upx10 && prj_move_diagram_upx10->isEnabled()) {
|
||||
moveDiagramUpx10();
|
||||
}
|
||||
break;
|
||||
case Qt::Key_F9:
|
||||
if (prj_move_diagram_upx100 && prj_move_diagram_upx100->isEnabled()) {
|
||||
moveDiagramUpx100();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Pass unhandled key events to the base class
|
||||
QWidget::keyPressEvent(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +69,14 @@ class ElementsPanelWidget : public QWidget {
|
||||
void requestForProjectPropertiesEdition(QETProject *);
|
||||
void requestForDiagramPropertiesEdition(Diagram *);
|
||||
void requestForDiagramDeletion(Diagram *);
|
||||
void requestForDiagramMoveUp(Diagram *);
|
||||
void requestForDiagramMoveDown(Diagram *);
|
||||
void requestForDiagramMoveUpTop(Diagram *);
|
||||
void requestForDiagramMoveUpx10(Diagram *);
|
||||
void requestForDiagramMoveUpx100(Diagram *);
|
||||
void requestForDiagramMoveDownx10(Diagram *);
|
||||
void requestForDiagramMoveDownx100(Diagram *);
|
||||
void requestForDiagramsDeletion(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveUp(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveDown(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveUpTop(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveUpx10(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveUpx100(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveDownx10(const QList<Diagram *> &diagrams);
|
||||
void requestForDiagramMoveDownx100(const QList<Diagram *> &diagrams);
|
||||
|
||||
public slots:
|
||||
void openDirectoryForSelectedItem();
|
||||
|
||||
+74
-21
@@ -364,11 +364,12 @@ QETResult ProjectView::noProjectResult() const
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ProjectView::removeDiagram
|
||||
Remove a diagram (folio) of the project
|
||||
@param diagram_view : diagram view to remove
|
||||
*/
|
||||
void ProjectView::removeDiagram(DiagramView *diagram_view)
|
||||
* @brief ProjectView::removeDiagram
|
||||
* Remove a diagram (folio) of the project
|
||||
* @param diagram_view : diagram view to remove
|
||||
* @param silent : if true, bypasses the confirmation message box
|
||||
*/
|
||||
void ProjectView::removeDiagram(DiagramView *diagram_view, bool silent)
|
||||
{
|
||||
if (!diagram_view)
|
||||
return;
|
||||
@@ -377,17 +378,18 @@ void ProjectView::removeDiagram(DiagramView *diagram_view)
|
||||
if (!m_diagram_ids.values().contains(diagram_view))
|
||||
return;
|
||||
|
||||
|
||||
//Ask confirmation to user.
|
||||
int answer = QET::QetMessageBox::question(
|
||||
this,
|
||||
tr("Supprimer le folio ?", "message box title"),
|
||||
tr("Êtes-vous sûr de vouloir supprimer ce folio du projet ? Ce changement est irréversible.", "message box content"),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
||||
QMessageBox::No
|
||||
);
|
||||
if (answer != QMessageBox::Yes) {
|
||||
return;
|
||||
if (!silent) {
|
||||
//Ask confirmation to user.
|
||||
int answer = QET::QetMessageBox::question(
|
||||
this,
|
||||
tr("Supprimer le folio ?", "message box title"),
|
||||
tr("Êtes-vous sûr de vouloir supprimer ce folio du projet ? Ce changement est irréversible.", "message box content"),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
||||
QMessageBox::No
|
||||
);
|
||||
if (answer != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove the diagram view of the tabs widget
|
||||
@@ -405,14 +407,15 @@ void ProjectView::removeDiagram(DiagramView *diagram_view)
|
||||
}
|
||||
|
||||
/**
|
||||
Enleve un schema du ProjectView
|
||||
@param diagram Schema a enlever
|
||||
*/
|
||||
void ProjectView::removeDiagram(Diagram *diagram) {
|
||||
* Enleve un schema du ProjectView
|
||||
* @param diagram Schema a enlever
|
||||
* @param silent Si vrai, supprime sans demander confirmation
|
||||
*/
|
||||
void ProjectView::removeDiagram(Diagram *diagram, bool silent) {
|
||||
if (!diagram) return;
|
||||
|
||||
if (DiagramView *diagram_view = findDiagram(diagram)) {
|
||||
removeDiagram(diagram_view);
|
||||
removeDiagram(diagram_view, silent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +560,56 @@ void ProjectView::moveDiagramUpx10(Diagram *diagram) {
|
||||
moveDiagramUpx10(findDiagram(diagram));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ProjectView::moveDiagramUpx100
|
||||
* Moves the diagram_view up / left x100
|
||||
* @param diagram_view View to move
|
||||
*/
|
||||
void ProjectView::moveDiagramUpx100(DiagramView *diagram_view) {
|
||||
if (!diagram_view) return;
|
||||
|
||||
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
||||
if (!diagram_view_position) {
|
||||
// The diagram is the first of the project
|
||||
return;
|
||||
}
|
||||
m_tab->tabBar()->moveTab(diagram_view_position, diagram_view_position - 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ProjectView::moveDiagramUpx100
|
||||
* Moves the diagram up / left x100
|
||||
* @param diagram Diagram to move
|
||||
*/
|
||||
void ProjectView::moveDiagramUpx100(Diagram *diagram) {
|
||||
moveDiagramUpx100(findDiagram(diagram));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ProjectView::moveDiagramDownx100
|
||||
* Moves the diagram_view down / right x100
|
||||
* @param diagram_view View to move
|
||||
*/
|
||||
void ProjectView::moveDiagramDownx100(DiagramView *diagram_view) {
|
||||
if (!diagram_view) return;
|
||||
|
||||
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
||||
if (diagram_view_position + 1 == m_diagram_ids.count()) {
|
||||
// The diagram is the last of the project
|
||||
return;
|
||||
}
|
||||
m_tab->tabBar()->moveTab(diagram_view_position, diagram_view_position + 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ProjectView::moveDiagramDownx100
|
||||
* Moves the diagram down / right x100
|
||||
* @param diagram Diagram to move
|
||||
*/
|
||||
void ProjectView::moveDiagramDownx100(Diagram *diagram) {
|
||||
moveDiagramDownx100(findDiagram(diagram));
|
||||
}
|
||||
|
||||
/**
|
||||
Deplace le schema diagram_view vers le bas / la droite x10
|
||||
*/
|
||||
|
||||
@@ -104,8 +104,8 @@ class ProjectView : public QWidget
|
||||
void changeLastTab();
|
||||
|
||||
public slots:
|
||||
void removeDiagram(DiagramView *);
|
||||
void removeDiagram(Diagram *);
|
||||
void removeDiagram(DiagramView *diagram_view, bool silent = false);
|
||||
void removeDiagram(Diagram *diagram, bool silent = false);
|
||||
void showDiagram(DiagramView *);
|
||||
void showDiagram(Diagram *);
|
||||
void editProjectProperties();
|
||||
@@ -122,6 +122,10 @@ class ProjectView : public QWidget
|
||||
void moveDiagramUpx10(Diagram *);
|
||||
void moveDiagramDownx10(DiagramView *);
|
||||
void moveDiagramDownx10(Diagram *);
|
||||
void moveDiagramUpx100(DiagramView *);
|
||||
void moveDiagramUpx100(Diagram *);
|
||||
void moveDiagramDownx100(DiagramView *);
|
||||
void moveDiagramDownx100(Diagram *);
|
||||
void exportProject();
|
||||
QETResult save();
|
||||
QETResult saveAs();
|
||||
|
||||
+153
-95
@@ -16,7 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "qetdiagrameditor.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include "ElementsCollection/elementscollectionwidget.h"
|
||||
#include "QWidgetAnimation/qwidgetanimation.h"
|
||||
#include "autoNum/ui/autonumberingdockwidget.h"
|
||||
@@ -47,7 +47,7 @@
|
||||
#include "TerminalStrip/ui/addterminalstripitemdialog.h"
|
||||
#include "wiringlistexport.h"
|
||||
#include "ui/terminalnumberingdialog.h"
|
||||
|
||||
#include <QDebug>
|
||||
#ifdef BUILD_WITHOUT_KF5
|
||||
#else
|
||||
# include <KAutoSaveFile>
|
||||
@@ -176,12 +176,14 @@ void QETDiagramEditor::setUpElementsPanel()
|
||||
connect(pa, SIGNAL(requestForProjectPropertiesEdition (QETProject *)), this, SLOT(editProjectProperties(QETProject *)));
|
||||
connect(pa, SIGNAL(requestForNewDiagram (QETProject *)), this, SLOT(addDiagramToProject(QETProject *)));
|
||||
connect(pa, SIGNAL(requestForDiagramPropertiesEdition (Diagram *)), this, SLOT(editDiagramProperties(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramDeletion (Diagram *)), this, SLOT(removeDiagram(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUp (Diagram *)), this, SLOT(moveDiagramUp(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveDown (Diagram *)), this, SLOT(moveDiagramDown(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUpTop (Diagram *)), this, SLOT(moveDiagramUpTop(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUpx10 (Diagram *)), this, SLOT(moveDiagramUpx10(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveDownx10 (Diagram *)), this, SLOT(moveDiagramDownx10(Diagram *)));
|
||||
connect(pa, SIGNAL(requestForDiagramsDeletion (const QList<Diagram *> &)), this, SLOT(removeDiagrams(const QList<Diagram *> &)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUp (const QList<Diagram *> &)), this, SLOT(moveDiagramUp(const QList<Diagram *>&)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveDown (const QList<Diagram *> &)), this, SLOT(moveDiagramDown(const QList<Diagram *>&)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUpTop (const QList<Diagram *> &)), this, SLOT(moveDiagramUpTop(const QList<Diagram *>&)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUpx10 (const QList<Diagram *> &)), this, SLOT(moveDiagramUpx10(const QList<Diagram *>&)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveDownx10 (const QList<Diagram *> &)), this, SLOT(moveDiagramDownx10(const QList<Diagram *>&)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveUpx100 (const QList<Diagram *> &)), this, SLOT(moveDiagramUpx100(const QList<Diagram *>&)));
|
||||
connect(pa, SIGNAL(requestForDiagramMoveDownx100 (const QList<Diagram *> &)), this, SLOT(moveDiagramDownx100(const QList<Diagram *>&)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2183,126 +2185,182 @@ void QETDiagramEditor::addDiagramToProject(QETProject *project)
|
||||
project_view->project()->addNewDiagram();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief QETDiagramEditor::removeDiagram
|
||||
* Wrapper für einzelne Diagramme, um Abwärtskompatibilität zu erhalten.
|
||||
*/
|
||||
void QETDiagramEditor::removeDiagram(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
QList<Diagram *> list;
|
||||
list << diagram;
|
||||
removeDiagrams(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief QETDiagramEditor::removeDiagrams
|
||||
* Deletes a list of folios with a single query.
|
||||
*/
|
||||
void QETDiagramEditor::removeDiagrams(const QList<Diagram *> &diagrams)
|
||||
{
|
||||
if (diagrams.isEmpty()) return;
|
||||
|
||||
if (diagrams.count() == 1) {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(this, tr("Supprimer le folio"),
|
||||
tr("Êtes-vous sûr de vouloir supprimer ce folio ?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::No) return;
|
||||
} else {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(this, tr("Supprimer les folios"),
|
||||
tr("Êtes-vous sûr de vouloir supprimer les %1 folios sélectionnés ?").arg(diagrams.count()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::No) return;
|
||||
}
|
||||
|
||||
ProjectView *project_view = nullptr;
|
||||
if (QETProject *diagram_project = diagrams.first()->project()) {
|
||||
project_view = findProject(diagram_project);
|
||||
}
|
||||
|
||||
if (project_view) project_view->setUpdatesEnabled(false);
|
||||
if (pa) pa->setUpdatesEnabled(false);
|
||||
|
||||
foreach (Diagram *diagram, diagrams) {
|
||||
removeDiagramSilent(diagram);
|
||||
}
|
||||
|
||||
if (pa) pa->setUpdatesEnabled(true);
|
||||
if (project_view) project_view->setUpdatesEnabled(true);
|
||||
|
||||
emit syncElementsPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
Supprime un schema de son projet
|
||||
@param diagram Schema a supprimer
|
||||
*/
|
||||
void QETDiagramEditor::removeDiagram(Diagram *diagram)
|
||||
void QETDiagramEditor::removeDiagramSilent(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
|
||||
// recupere le projet contenant le schema
|
||||
if (QETProject *diagram_project = diagram -> project()) {
|
||||
// recupere la vue sur ce projet
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
|
||||
// affiche le schema en question
|
||||
project_view -> showDiagram(diagram);
|
||||
|
||||
// supprime le schema
|
||||
project_view -> removeDiagram(diagram);
|
||||
project_view -> removeDiagram(diagram, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void QETDiagramEditor::moveDiagramUp(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Forward loop for moving up
|
||||
for (int i = 0; i < safeDiagrams.size(); ++i) {
|
||||
project_view->moveDiagramUp(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Change l'ordre des schemas d'un projet, en decalant le schema vers le haut /
|
||||
la gauche
|
||||
@param diagram Schema a decaler vers le haut / la gauche
|
||||
*/
|
||||
void QETDiagramEditor::moveDiagramUp(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
|
||||
// recupere le projet contenant le schema
|
||||
if (QETProject *diagram_project = diagram -> project()) {
|
||||
if (diagram_project -> isReadOnly()) return;
|
||||
|
||||
// recupere la vue sur ce projet
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
project_view -> moveDiagramUp(diagram);
|
||||
void QETDiagramEditor::moveDiagramDown(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Backward loop for moving down
|
||||
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
||||
project_view->moveDiagramDown(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Change l'ordre des schemas d'un projet, en decalant le schema vers le bas /
|
||||
la droite
|
||||
@param diagram Schema a decaler vers le bas / la droite
|
||||
*/
|
||||
void QETDiagramEditor::moveDiagramDown(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
|
||||
// recupere le projet contenant le schema
|
||||
if (QETProject *diagram_project = diagram -> project()) {
|
||||
if (diagram_project -> isReadOnly()) return;
|
||||
|
||||
// recupere la vue sur ce projet
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
project_view -> moveDiagramDown(diagram);
|
||||
void QETDiagramEditor::moveDiagramUpTop(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Backward loop to preserve relative order of the selected items when moving to top
|
||||
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
||||
project_view->moveDiagramUpTop(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Change l'ordre des schemas d'un projet, en decalant le schema vers le haut /
|
||||
la gauche en position 0
|
||||
@param diagram Schema a decaler vers le haut / la gauche en position 0
|
||||
*/
|
||||
void QETDiagramEditor::moveDiagramUpTop(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
|
||||
// recupere le projet contenant le schema
|
||||
if (QETProject *diagram_project = diagram -> project()) {
|
||||
if (diagram_project -> isReadOnly()) return;
|
||||
|
||||
// recupere la vue sur ce projet
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
project_view -> moveDiagramUpTop(diagram);
|
||||
void QETDiagramEditor::moveDiagramUpx10(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Forward loop for moving up
|
||||
for (int i = 0; i < safeDiagrams.size(); ++i) {
|
||||
project_view->moveDiagramUpx10(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Change l'ordre des schemas d'un projet, en decalant le schema vers le haut /
|
||||
la gauche x10
|
||||
@param diagram Schema a decaler vers le haut / la gauche x10
|
||||
*/
|
||||
void QETDiagramEditor::moveDiagramUpx10(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
|
||||
// recupere le projet contenant le schema
|
||||
if (QETProject *diagram_project = diagram -> project()) {
|
||||
if (diagram_project -> isReadOnly()) return;
|
||||
|
||||
// recupere la vue sur ce projet
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
project_view -> moveDiagramUpx10(diagram);
|
||||
void QETDiagramEditor::moveDiagramDownx10(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Backward loop for moving down
|
||||
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
||||
project_view->moveDiagramDownx10(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Change l'ordre des schemas d'un projet, en decalant le schema vers le bas /
|
||||
la droite x10
|
||||
@param diagram Schema a decaler vers le bas / la droite x10
|
||||
*/
|
||||
void QETDiagramEditor::moveDiagramDownx10(Diagram *diagram)
|
||||
{
|
||||
if (!diagram) return;
|
||||
void QETDiagramEditor::moveDiagramUpx100(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Forward loop for moving up
|
||||
for (int i = 0; i < safeDiagrams.size(); ++i) {
|
||||
project_view->moveDiagramUpx100(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recupere le projet contenant le schema
|
||||
if (QETProject *diagram_project = diagram -> project()) {
|
||||
if (diagram_project -> isReadOnly()) return;
|
||||
|
||||
// recupere la vue sur ce projet
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
project_view -> moveDiagramDownx10(diagram);
|
||||
void QETDiagramEditor::moveDiagramDownx100(const QList<Diagram *> &diagrams) {
|
||||
if (diagrams.isEmpty()) return;
|
||||
QList<Diagram *> safeDiagrams = diagrams;
|
||||
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
||||
if (!diagram_project->isReadOnly()) {
|
||||
if (ProjectView *project_view = findProject(diagram_project)) {
|
||||
// Backward loop for moving down
|
||||
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
||||
project_view->moveDiagramDownx100(safeDiagrams.at(i));
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,12 +138,15 @@ class QETDiagramEditor : public QETMainWindow
|
||||
void editDiagramProperties(Diagram *);
|
||||
void addDiagramToProject(QETProject *);
|
||||
void removeDiagram(Diagram *);
|
||||
void removeDiagrams(const QList<Diagram *> &diagrams);
|
||||
void removeDiagramFromProject();
|
||||
void moveDiagramUp(Diagram *);
|
||||
void moveDiagramDown(Diagram *);
|
||||
void moveDiagramUpTop(Diagram *);
|
||||
void moveDiagramUpx10(Diagram *);
|
||||
void moveDiagramDownx10(Diagram *);
|
||||
void moveDiagramUp(const QList<Diagram *> &diagrams);
|
||||
void moveDiagramDown(const QList<Diagram *> &diagrams);
|
||||
void moveDiagramUpTop(const QList<Diagram *> &diagrams);
|
||||
void moveDiagramUpx10(const QList<Diagram *> &diagrams);
|
||||
void moveDiagramDownx10(const QList<Diagram *> &diagrams);
|
||||
void moveDiagramUpx100(const QList<Diagram *> &diagrams);
|
||||
void moveDiagramDownx100(const QList<Diagram *> &diagrams);
|
||||
void reloadOldElementPanel();
|
||||
void diagramWasAdded(DiagramView *);
|
||||
void findElementInPanel(const ElementsLocation &);
|
||||
@@ -222,6 +225,8 @@ class QETDiagramEditor : public QETMainWindow
|
||||
|
||||
QList <QAction *> m_zoom_action_toolBar; ///Only zoom action must displayed in the toolbar
|
||||
|
||||
void removeDiagramSilent(Diagram *diagram);
|
||||
|
||||
QMdiArea m_workspace;
|
||||
QSignalMapper windowMapper;
|
||||
QDir open_dialog_dir; /// Directory to use for file dialogs such as File > save
|
||||
|
||||
+162
-114
@@ -5,9 +5,7 @@
|
||||
#include <QTextStream>
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QQueue>
|
||||
#include <QSet>
|
||||
#include <algorithm>
|
||||
|
||||
WiringListExport::WiringListExport(QETProject *project, QWidget *parent) :
|
||||
QObject(parent),
|
||||
@@ -45,8 +43,27 @@ QDomElement WiringListExport::climbToDiagram(QDomNode node) const
|
||||
QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomElement &root) const
|
||||
{
|
||||
QMap<QString, ElementInfo> infoMap;
|
||||
QDomNodeList elements = root.elementsByTagName("element");
|
||||
|
||||
QSet<QString> placeholderTypes;
|
||||
QDomElement collection = root.firstChildElement("collection");
|
||||
if (!collection.isNull()) {
|
||||
QDomNodeList defs = collection.elementsByTagName("definition");
|
||||
for (int i = 0; i < defs.size(); ++i) {
|
||||
QDomElement def = defs.at(i).toElement();
|
||||
QString ltype = def.attribute("link_type");
|
||||
if (ltype == "next_report" || ltype == "previous_report") {
|
||||
QDomElement parentEl = def.parentNode().toElement();
|
||||
if (parentEl.tagName().toLower() == "element") {
|
||||
QString name = parentEl.attribute("name");
|
||||
if (!name.isEmpty()) {
|
||||
placeholderTypes.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QDomNodeList elements = root.elementsByTagName("element");
|
||||
for (int i = 0; i < elements.size(); ++i) {
|
||||
QDomElement el = elements.at(i).toElement();
|
||||
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
|
||||
@@ -75,13 +92,16 @@ QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomEleme
|
||||
}
|
||||
}
|
||||
|
||||
QString typeVal = el.attribute("type").toLower();
|
||||
if (typeVal.contains("naechste") || typeVal.contains("vorherige") ||
|
||||
typeVal.contains("next") || typeVal.contains("previous")) {
|
||||
info.isPlaceholder = true;
|
||||
QString typeVal = el.attribute("type");
|
||||
info.isPlaceholder = false;
|
||||
for (const QString &ptype : placeholderTypes) {
|
||||
if (typeVal.endsWith(ptype)) {
|
||||
info.isPlaceholder = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
infoMap.insert(uuid, info);
|
||||
infoMap.insert(uuid, info);
|
||||
}
|
||||
return infoMap;
|
||||
}
|
||||
@@ -102,7 +122,15 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
|
||||
data.el2_uuid = normalizeUuid(cond.attribute("element2", cond.attribute("element2id", "")));
|
||||
|
||||
data.element1_label = cond.attribute("element1_label");
|
||||
if (data.element1_label.isEmpty()) {
|
||||
data.element1_label = cond.attribute("element1_linked");
|
||||
}
|
||||
|
||||
data.element2_label = cond.attribute("element2_label");
|
||||
if (data.element2_label.isEmpty()) {
|
||||
data.element2_label = cond.attribute("element2_linked");
|
||||
}
|
||||
|
||||
data.terminalname1 = cond.attribute("terminalname1");
|
||||
data.terminalname2 = cond.attribute("terminalname2");
|
||||
data.tension_protocol = cond.attribute("tension_protocol");
|
||||
@@ -119,101 +147,6 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
|
||||
return conductors;
|
||||
}
|
||||
|
||||
void WiringListExport::resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const
|
||||
{
|
||||
QRegularExpression numericLabelRe("^\\d+(\\.\\d+)?$");
|
||||
|
||||
QMap<QString, QList<ConductorData>> el_to_cons;
|
||||
for (const ConductorData &c : conductors) {
|
||||
if (!c.el1_uuid.isEmpty()) el_to_cons[c.el1_uuid].append(c);
|
||||
if (!c.el2_uuid.isEmpty()) el_to_cons[c.el2_uuid].append(c);
|
||||
}
|
||||
|
||||
for (int i = 0; i < conductors.size(); ++i) {
|
||||
ConductorData &c = conductors[i];
|
||||
|
||||
auto resolveSide = [&](const QString &startUuid, QString &outLabel, QString &outTerminal) {
|
||||
if (startUuid.isEmpty() || !elementsInfo.contains(startUuid)) return;
|
||||
|
||||
const ElementInfo &startInfo = elementsInfo[startUuid];
|
||||
if (!startInfo.links.isEmpty() || startInfo.isPlaceholder) {
|
||||
QQueue<QString> q;
|
||||
QSet<QString> visited;
|
||||
q.enqueue(startUuid);
|
||||
visited.insert(startUuid);
|
||||
|
||||
int depth = 0;
|
||||
while (!q.isEmpty() && depth < 3) {
|
||||
int levelSize = q.size();
|
||||
for (int k = 0; k < levelSize; ++k) {
|
||||
QString curr = q.dequeue();
|
||||
|
||||
if (elementsInfo.contains(curr)) {
|
||||
const ElementInfo &currInfo = elementsInfo[curr];
|
||||
|
||||
if (!currInfo.isPlaceholder && !currInfo.label.isEmpty() && !numericLabelRe.match(currInfo.label).hasMatch()) {
|
||||
outLabel = currInfo.label;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QString &lnk : currInfo.links) {
|
||||
if (!visited.contains(lnk)) {
|
||||
visited.insert(lnk);
|
||||
q.enqueue(lnk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const ConductorData &cond : el_to_cons.value(curr)) {
|
||||
if (cond.index == c.index) continue;
|
||||
|
||||
QString other;
|
||||
QString terminalHint;
|
||||
if (cond.el1_uuid == curr) {
|
||||
other = cond.el2_uuid;
|
||||
terminalHint = cond.terminalname2;
|
||||
} else {
|
||||
other = cond.el1_uuid;
|
||||
terminalHint = cond.terminalname1;
|
||||
}
|
||||
|
||||
if (!other.isEmpty() && !visited.contains(other)) {
|
||||
if (elementsInfo.contains(other)) {
|
||||
const ElementInfo &oInfo = elementsInfo[other];
|
||||
if (!oInfo.isPlaceholder && !oInfo.label.isEmpty() && !numericLabelRe.match(oInfo.label).hasMatch()) {
|
||||
outLabel = oInfo.label;
|
||||
if (outTerminal.isEmpty()) outTerminal = terminalHint;
|
||||
return;
|
||||
}
|
||||
}
|
||||
visited.insert(other);
|
||||
q.enqueue(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
depth++;
|
||||
}
|
||||
} else {
|
||||
if (outLabel.isEmpty()) {
|
||||
outLabel = startInfo.label.isEmpty() ? startInfo.name : startInfo.label;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool p1 = elementsInfo.value(c.el1_uuid).isPlaceholder;
|
||||
bool p2 = elementsInfo.value(c.el2_uuid).isPlaceholder;
|
||||
|
||||
if (c.element1_label.isEmpty() || p1) {
|
||||
if (p1) c.element1_label = "";
|
||||
resolveSide(c.el1_uuid, c.element1_label, c.terminalname1);
|
||||
}
|
||||
if (c.element2_label.isEmpty() || p2) {
|
||||
if (p2) c.element2_label = "";
|
||||
resolveSide(c.el2_uuid, c.element2_label, c.terminalname2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiringListExport::toCsv()
|
||||
{
|
||||
if (!m_project) return;
|
||||
@@ -243,25 +176,140 @@ void WiringListExport::toCsv()
|
||||
QMap<QString, ElementInfo> elementsInfo = collectElementsInfo(doc.documentElement());
|
||||
QList<ConductorData> conductors = collectConductors(doc.documentElement());
|
||||
|
||||
resolveEndpoints(conductors, elementsInfo);
|
||||
|
||||
QList<ConductorData> uniqueConductors;
|
||||
QSet<QString> seenConnections;
|
||||
QMap<QString, ConductorData> partialWires;
|
||||
|
||||
for (const ConductorData &c : conductors) {
|
||||
if (c.element1_label.isEmpty() && c.element2_label.isEmpty()) continue;
|
||||
auto normalizePartial = [](ConductorData c, const QString &ph_uuid) {
|
||||
if (c.el1_uuid == ph_uuid) {
|
||||
std::swap(c.el1_uuid, c.el2_uuid);
|
||||
std::swap(c.element1_label, c.element2_label);
|
||||
std::swap(c.terminalname1, c.terminalname2);
|
||||
}
|
||||
return c;
|
||||
};
|
||||
|
||||
QString sideA = c.element1_label + ":" + c.terminalname1;
|
||||
QString sideB = c.element2_label + ":" + c.terminalname2;
|
||||
auto mergeField = [](const QString &a, const QString &b) {
|
||||
QString at = a.trimmed();
|
||||
QString bt = b.trimmed();
|
||||
if (at.isEmpty()) return bt;
|
||||
if (bt.isEmpty()) return at;
|
||||
if (at == bt) return at;
|
||||
return at + ", " + bt;
|
||||
};
|
||||
|
||||
QString key = (sideA < sideB) ? (sideA + "||" + sideB) : (sideB + "||" + sideA);
|
||||
for (int i = 0; i < conductors.size(); ++i) {
|
||||
ConductorData c = conductors[i];
|
||||
|
||||
if (!seenConnections.contains(key)) {
|
||||
seenConnections.insert(key);
|
||||
if (c.element1_label.isEmpty() && elementsInfo.contains(c.el1_uuid)) {
|
||||
c.element1_label = elementsInfo[c.el1_uuid].label;
|
||||
if (c.element1_label.isEmpty()) c.element1_label = elementsInfo[c.el1_uuid].name;
|
||||
}
|
||||
if (c.element2_label.isEmpty() && elementsInfo.contains(c.el2_uuid)) {
|
||||
c.element2_label = elementsInfo[c.el2_uuid].label;
|
||||
if (c.element2_label.isEmpty()) c.element2_label = elementsInfo[c.el2_uuid].name;
|
||||
}
|
||||
|
||||
bool el1_ph = elementsInfo.value(c.el1_uuid).isPlaceholder;
|
||||
bool el2_ph = elementsInfo.value(c.el2_uuid).isPlaceholder;
|
||||
|
||||
if (!el1_ph && !el2_ph) {
|
||||
uniqueConductors.append(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el1_ph && el2_ph) {
|
||||
uniqueConductors.append(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString ph_uuid = el1_ph ? c.el1_uuid : c.el2_uuid;
|
||||
ConductorData normC = normalizePartial(c, ph_uuid);
|
||||
|
||||
QString matching_ph_uuid;
|
||||
if (!elementsInfo[ph_uuid].links.isEmpty()) {
|
||||
matching_ph_uuid = elementsInfo[ph_uuid].links.first();
|
||||
}
|
||||
|
||||
if (!matching_ph_uuid.isEmpty() && partialWires.contains(matching_ph_uuid)) {
|
||||
ConductorData otherHalf = partialWires.take(matching_ph_uuid);
|
||||
|
||||
ConductorData merged;
|
||||
merged.folio = mergeField(otherHalf.folio, normC.folio);
|
||||
|
||||
merged.el1_uuid = otherHalf.el1_uuid;
|
||||
merged.element1_label = otherHalf.element1_label;
|
||||
merged.terminalname1 = otherHalf.terminalname1;
|
||||
|
||||
merged.el2_uuid = normC.el1_uuid;
|
||||
merged.element2_label = normC.element1_label;
|
||||
merged.terminalname2 = normC.terminalname1;
|
||||
|
||||
merged.tension_protocol = mergeField(otherHalf.tension_protocol, normC.tension_protocol);
|
||||
merged.conductor_color = mergeField(otherHalf.conductor_color, normC.conductor_color);
|
||||
merged.conductor_section = mergeField(otherHalf.conductor_section, normC.conductor_section);
|
||||
merged.function = mergeField(otherHalf.function, normC.function);
|
||||
|
||||
uniqueConductors.append(merged);
|
||||
} else {
|
||||
partialWires.insert(ph_uuid, normC);
|
||||
}
|
||||
}
|
||||
|
||||
for (const ConductorData &leftover : partialWires.values()) {
|
||||
uniqueConductors.append(leftover);
|
||||
}
|
||||
|
||||
for (ConductorData &c : uniqueConductors) {
|
||||
if (!c.element2_label.isEmpty() && (c.element1_label.isEmpty() || c.element2_label.toLower() < c.element1_label.toLower())) {
|
||||
std::swap(c.element1_label, c.element2_label);
|
||||
std::swap(c.terminalname1, c.terminalname2);
|
||||
std::swap(c.el1_uuid, c.el2_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(uniqueConductors.begin(), uniqueConductors.end(), [](const ConductorData &a, const ConductorData &b) {
|
||||
QStringList partsA = a.folio.split(',');
|
||||
QStringList partsB = b.folio.split(',');
|
||||
int minLen = std::min(partsA.size(), partsB.size());
|
||||
int folioCmp = 0;
|
||||
|
||||
for (int i = 0; i < minLen; ++i) {
|
||||
bool okA, okB;
|
||||
int numA = partsA[i].trimmed().toInt(&okA);
|
||||
int numB = partsB[i].trimmed().toInt(&okB);
|
||||
|
||||
if (okA && okB) {
|
||||
if (numA != numB) {
|
||||
folioCmp = (numA < numB) ? -1 : 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int strCmp = partsA[i].trimmed().compare(partsB[i].trimmed(), Qt::CaseInsensitive);
|
||||
if (strCmp != 0) {
|
||||
folioCmp = strCmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (folioCmp == 0 && partsA.size() != partsB.size()) {
|
||||
folioCmp = (partsA.size() < partsB.size()) ? -1 : 1;
|
||||
}
|
||||
|
||||
if (folioCmp != 0) return folioCmp < 0;
|
||||
|
||||
int el1Cmp = a.element1_label.toLower().compare(b.element1_label.toLower());
|
||||
if (el1Cmp != 0) return el1Cmp < 0;
|
||||
|
||||
int el2Cmp = a.element2_label.toLower().compare(b.element2_label.toLower());
|
||||
if (el2Cmp != 0) return el2Cmp < 0;
|
||||
|
||||
int term1Cmp = a.terminalname1.compare(b.terminalname1);
|
||||
if (term1Cmp != 0) return term1Cmp < 0;
|
||||
|
||||
return a.terminalname2 < b.terminalname2;
|
||||
});
|
||||
|
||||
QTextStream out(&file);
|
||||
out << tr("Page", "Wiring list CSV header") << ";"
|
||||
<< tr("Composant 1", "Wiring list CSV header") << ";"
|
||||
|
||||
@@ -12,7 +12,6 @@ class QWidget;
|
||||
class QDomElement;
|
||||
class QDomNode;
|
||||
|
||||
// Internal data structures for parsing the XML graph
|
||||
struct ElementInfo {
|
||||
QString folio;
|
||||
QStringList links;
|
||||
@@ -34,18 +33,11 @@ struct ConductorData {
|
||||
QString conductor_section;
|
||||
QString function;
|
||||
QString folio;
|
||||
|
||||
// Resolved endpoints
|
||||
QString chosen_a_uuid;
|
||||
QString chosen_a_label;
|
||||
QString chosen_b_uuid;
|
||||
QString chosen_b_label;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The WiringListExport class
|
||||
* Handles the export of the wiring list (Verdrahtungsplan) to a CSV file.
|
||||
* Automatically resolves links and placeholders to find physical endpoints.
|
||||
* Exports the wiring diagram from QElectroTech as a CSV file.
|
||||
*/
|
||||
class WiringListExport : public QObject
|
||||
{
|
||||
@@ -64,8 +56,6 @@ private:
|
||||
|
||||
QMap<QString, ElementInfo> collectElementsInfo(const QDomElement &root) const;
|
||||
QList<ConductorData> collectConductors(const QDomElement &root) const;
|
||||
|
||||
void resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const;
|
||||
};
|
||||
|
||||
#endif // WIRINGLISTEXPORT_H
|
||||
|
||||
Reference in New Issue
Block a user