mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 23:44:13 +02:00
Compare commits
259 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70c665199f | |||
| 6b577ee757 | |||
| 74e878207a | |||
| 3e66d403c8 | |||
| a7fa4dfdcf | |||
| b98589d3c7 | |||
| f997550e07 | |||
| d9f148ab2f | |||
| 9fdd7b5fc0 | |||
| 823468826d | |||
| b659c4acad | |||
| c69e5747a1 | |||
| 899f10533d | |||
| 5b2fdaeb00 | |||
| 5027ffda9b | |||
| 3ca5d4ab29 | |||
| e47dcec7cb | |||
| a32aef2246 | |||
| a5d9cc37e1 | |||
| 0dde1eaf73 | |||
| e3fe633267 | |||
| 3fa73e945e | |||
| 4038122f99 | |||
| 860a12b970 | |||
| 0a98cce8de | |||
| cceddba44e | |||
| 487b22f483 | |||
| da1d7df662 | |||
| 617619c92c | |||
| 61fa130d6d | |||
| c9899372d3 | |||
| e6f1a18844 | |||
| b8b9fa086a | |||
| 34270a961d | |||
| 48715051bb | |||
| efab108709 | |||
| ea6f0f893a | |||
| bfe2aa26a0 | |||
| 4bde81937d | |||
| 91116f7044 | |||
| 8246c8aaba | |||
| 8ca62c4a17 | |||
| 0394503c0b | |||
| e5935c75d1 | |||
| 2e49f4588a | |||
| ccf545d30c | |||
| 26d7c03a76 | |||
| bf23967470 | |||
| 8f6f41ed19 | |||
| 5e898c4661 | |||
| 79251c8ed6 | |||
| 03b552c601 | |||
| 1195474f88 | |||
| 7bfa2a56df | |||
| 47558f8b0a | |||
| e44219778e | |||
| 8d175754cd | |||
| 9d590eaa6f | |||
| 759b197dc2 | |||
| e542724953 | |||
| 61a160fa62 | |||
| 8af4bc8eee | |||
| 45afc7e6aa | |||
| ff25a77159 | |||
| 4edd4932bb | |||
| baf95338a3 | |||
| 56464629bf | |||
| b7f8086166 | |||
| f6afd87522 | |||
| c536dcf30f | |||
| 5db77fe40c | |||
| bf816cbc44 | |||
| 315889baf4 | |||
| 2f6fb7808a | |||
| f951c20586 | |||
| 033c3fc112 | |||
| 480d2757e7 | |||
| 92db329f40 | |||
| c488d8437e | |||
| 7f7185172f | |||
| dbe2bcb599 | |||
| ab159404a3 | |||
| bc79a5df7a | |||
| 43da912aad | |||
| 0fdcd1e1e8 | |||
| f172e0740b | |||
| 5263d78dce | |||
| 265ec12351 | |||
| 4572fca31f | |||
| b8df298953 | |||
| 9d81a9788f | |||
| 2803dcf8b8 | |||
| 5f83679756 | |||
| bb1f457763 | |||
| e2e0df784b | |||
| cfc64dfad0 | |||
| eb095f9a10 | |||
| e9ee8b600b | |||
| 4b981668ca | |||
| 91380b0695 | |||
| dc1f0ffa4d | |||
| 5b9da15982 | |||
| e18d5b1aa0 | |||
| f8634a83fc | |||
| 99fa186d30 | |||
| c8ff06459f | |||
| d5c75ad19d | |||
| 17eac03ca3 | |||
| e90cb66686 | |||
| 610001a847 | |||
| 2419faf931 | |||
| ddbd1d8d75 | |||
| afb7442e8a | |||
| 1780fde458 | |||
| f0b16fe650 | |||
| d988054aca | |||
| 1743f342ce | |||
| 2c69bf0dad | |||
| 8bda0a1821 | |||
| fe6191f26d | |||
| 3077527601 | |||
| 8a71649e87 | |||
| 46aed59b8b | |||
| 5acf201067 | |||
| 4c7d4e7c53 | |||
| f0348a7cd8 | |||
| 6326cb3789 | |||
| 39ac5716c7 | |||
| f83ed508e3 | |||
| ab88937ac9 | |||
| 1cf58e3caf | |||
| 26f6f6d36e | |||
| 293dc92b41 | |||
| 6d05bc2f21 | |||
| 7ba295a339 | |||
| 458c9921c2 | |||
| deba8d0e4e | |||
| 2e1ba46430 | |||
| 67b1665b36 | |||
| 61a509e5d8 | |||
| 2186d2733f | |||
| b7efbdc323 | |||
| b00e053dab | |||
| b0172f4dd7 | |||
| 1de9f7eaf0 | |||
| b72794bea1 | |||
| 11357eb504 | |||
| 85346305e5 | |||
| d2a921ea80 | |||
| a4d23fe312 | |||
| 8cfb777fe1 | |||
| 8d08c3fd56 | |||
| 766a6b981d | |||
| 8b40134a47 | |||
| cc032c9c76 | |||
| 92b2608bfb | |||
| af87d28e33 | |||
| c5d714089f | |||
| 206c37f620 | |||
| c14d6a6dd6 | |||
| e32e5595c0 | |||
| 1684d2cfe6 | |||
| 6f66745ce3 | |||
| 762bd7febf | |||
| 162d43e1c9 | |||
| 10cc162c4e | |||
| 3a3182862c | |||
| 0d432f6159 | |||
| 45458c2f2a | |||
| 472786fd23 | |||
| 0732bf64a7 | |||
| 2d889568a5 | |||
| 55250a6de9 | |||
| 325b895d0b | |||
| 6ad2a63575 | |||
| 99dac17327 | |||
| 13e245b104 | |||
| 6bf6a17dc9 | |||
| 550e085b82 | |||
| 9fd951152a | |||
| 7debaa5504 | |||
| 6c76b1f6a8 | |||
| 31ab7538a4 | |||
| b0b5345e15 | |||
| c25c400c93 | |||
| d9638ad746 | |||
| ea8eeb02c3 | |||
| 5e4c423a7d | |||
| 7f75500023 | |||
| cdf5cad1e5 | |||
| 3a3f23b7a8 | |||
| 1f7107a7b6 | |||
| 7cb1e7394e | |||
| b91aaacad9 | |||
| 1f3c28992c | |||
| 5e02111600 | |||
| a3d348fdcd | |||
| c1d892c496 | |||
| 5b33c044c5 | |||
| c7ed3229d0 | |||
| 79cd91bd1c | |||
| b7fc0c79cb | |||
| fca1e0993b | |||
| 9f283322a2 | |||
| 09983efe05 | |||
| 76ff69e912 | |||
| 779602372a | |||
| d95e744494 | |||
| 1317f137c0 | |||
| 201bd4c5f6 | |||
| c12137c5a0 | |||
| 5adf61936b | |||
| 39b1e836bf | |||
| f076df77da | |||
| b3de01d171 | |||
| 90950075bf | |||
| a668ccfa90 | |||
| ad200e28c7 | |||
| 3424ba50bd | |||
| 79da321ddc | |||
| 293e61abeb | |||
| 438e2ade3a | |||
| bfee5b1cdb | |||
| 3eafe840f1 | |||
| e38493c308 | |||
| 5abf890b24 | |||
| 037d5a13cd | |||
| cd7388985e | |||
| 5ba08284f5 | |||
| 4a95efbfe6 | |||
| bab5bf58f8 | |||
| fca945f90e | |||
| 615e40dee2 | |||
| 4bd9b6b212 | |||
| d2b2a2eadb | |||
| e5e2f2efbd | |||
| 5c39518921 | |||
| 6d2995ad68 | |||
| 1198cf73a9 | |||
| e8f80697f3 | |||
| 62ad49a6d3 | |||
| 41e83bbc09 | |||
| b6e43f78d0 | |||
| 016bb3636e | |||
| 57d0b4b5a3 | |||
| 99151b9c04 | |||
| 43e4603c55 | |||
| 3cf5cb945e | |||
| b3a4a41ad9 | |||
| 979df376d1 | |||
| 484ab9ed87 | |||
| ee36073428 | |||
| ab61e00cdf | |||
| fb51489436 | |||
| 1216855cfa | |||
| 32dd686144 | |||
| 29ba7c9636 | |||
| a3511e8694 | |||
| 76fda3f0e6 |
@@ -2,7 +2,7 @@ name: Windows Build
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * 1' # Every Monday at 2:00 UTC
|
||||
- cron: '0 2 1 * *' #
|
||||
workflow_dispatch: # Manual trigger available at any time
|
||||
|
||||
concurrency:
|
||||
@@ -11,356 +11,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
# =============================================================================
|
||||
# Job 1: Qt5 build (stable track)
|
||||
# =============================================================================
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
cache: true
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-ucrt-x86_64-ccache
|
||||
mingw-w64-ucrt-x86_64-gcc
|
||||
mingw-w64-ucrt-x86_64-cmake
|
||||
mingw-w64-ucrt-x86_64-ninja
|
||||
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-qt5-pdf
|
||||
mingw-w64-ucrt-x86_64-sqlite3
|
||||
mingw-w64-ucrt-x86_64-pkg-config
|
||||
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
||||
mingw-w64-ucrt-x86_64-kcoreaddons
|
||||
mingw-w64-ucrt-x86_64-extra-cmake-modules
|
||||
mingw-w64-ucrt-x86_64-nsis
|
||||
mingw-w64-ucrt-x86_64-angleproject
|
||||
|
||||
- name: Cache ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: C:\Users\runneradmin\AppData\Local\ccache
|
||||
key: ccache-windows-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-windows-${{ github.ref_name }}-
|
||||
ccache-windows-
|
||||
|
||||
- name: Configure ccache
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
/ucrt64/bin/ccache --set-config=max_size=500M
|
||||
/ucrt64/bin/ccache --set-config=compression=true
|
||||
/ucrt64/bin/ccache -z
|
||||
echo "=== ccache config ==="
|
||||
/ucrt64/bin/ccache -p
|
||||
|
||||
- name: Patch NSIS Welcome page — fix title font size
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
WELCOME_NSH=$(find /ucrt64 -path "*/Modern UI 2/Pages/Welcome.nsh" | head -1)
|
||||
if [ -z "$WELCOME_NSH" ]; then
|
||||
echo "WARNING: Welcome.nsh not found, skipping font patch"
|
||||
else
|
||||
echo "Patching: $WELCOME_NSH"
|
||||
sed -i '/WelcomePage\.Title\.Font/s/"[0-9]\+" "700"/"10" "700"/' "$WELCOME_NSH"
|
||||
grep 'WelcomePage.Title.Font' "$WELCOME_NSH"
|
||||
echo " OK font size patched to 10"
|
||||
fi
|
||||
|
||||
FINISH_NSH=$(find /ucrt64 -path "*/Modern UI 2/Pages/Finish.nsh" | head -1)
|
||||
if [ -z "$FINISH_NSH" ]; then
|
||||
echo "WARNING: Finish.nsh not found, skipping font patch"
|
||||
else
|
||||
echo "Patching: $FINISH_NSH"
|
||||
sed -i '/FinishPage\.Title\.Font/s/"[0-9]\+" "700"/"10" "700"/' "$FINISH_NSH"
|
||||
grep 'FinishPage.Title.Font' "$FINISH_NSH"
|
||||
echo " OK font size patched to 10"
|
||||
fi
|
||||
|
||||
- name: Force Qt5 — remove Qt6 cmake + tools
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf /ucrt64/lib/cmake/Qt6
|
||||
pacman -R --noconfirm mingw-w64-ucrt-x86_64-qt6-tools 2>/dev/null || true
|
||||
echo "=== windeployqt binaries ==="
|
||||
ls /ucrt64/bin/windeployqt* || echo "NO windeployqt found!"
|
||||
|
||||
- name: Build with cmake
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
mkdir build && cd build
|
||||
NPROC=$(nproc)
|
||||
echo "Available CPUs: $NPROC"
|
||||
|
||||
cmake -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/ucrt64 \
|
||||
-DQt5_DIR=/ucrt64/lib/cmake/Qt5 \
|
||||
-DQT_VERSION_MAJOR=5 \
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_Qt6=ON \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DCMAKE_POLICY_DEFAULT_CMP0077=NEW \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_CXX_FLAGS="-DQET_EXPORT_PROJECT_DB" \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
|
||||
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
|
||||
..
|
||||
ninja -j"$NPROC"
|
||||
|
||||
- name: Show ccache stats
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
echo "=== ccache statistics ==="
|
||||
/ucrt64/bin/ccache -s
|
||||
|
||||
- name: Verify exe was built
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXE=$(find "$GITHUB_WORKSPACE/build" -maxdepth 3 -iname "qelectrotech.exe" | head -1)
|
||||
if [ -z "$EXE" ]; then
|
||||
echo "ERROR: no qelectrotech.exe found in build/"
|
||||
find "$GITHUB_WORKSPACE/build" -maxdepth 3 -name "*.exe" || true
|
||||
exit 1
|
||||
fi
|
||||
SIZE=$(stat -c%s "$EXE")
|
||||
echo "Exe found: $EXE ($SIZE bytes)"
|
||||
[ "$SIZE" -gt 100000 ] || { echo "ERROR: exe too small"; exit 1; }
|
||||
|
||||
- name: Deploy — copy exe + windeployqt + DLLs
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NSIS_ROOT="$GITHUB_WORKSPACE/nsis_root"
|
||||
FILES="$NSIS_ROOT/files"
|
||||
BIN="$FILES/bin"
|
||||
mkdir -p "$BIN"
|
||||
|
||||
EXE=$(find "$GITHUB_WORKSPACE/build" -maxdepth 3 -iname "qelectrotech.exe" | head -1)
|
||||
echo "Copying exe: $EXE -> $BIN/QElectroTech.exe"
|
||||
cp "$EXE" "$BIN/QElectroTech.exe"
|
||||
|
||||
cd "$BIN"
|
||||
/ucrt64/bin/windeployqt-qt5 \
|
||||
--release \
|
||||
--no-translations \
|
||||
--no-compiler-runtime \
|
||||
./QElectroTech.exe || true
|
||||
|
||||
echo "=== 3-pass transitive DLL scan ==="
|
||||
set +e
|
||||
for PASS in 1 2 3; do
|
||||
echo "-- Pass $PASS --"
|
||||
for bin_file in "$BIN"/*.dll "$BIN"/*.exe "$BIN"/sqldrivers/*.dll "$BIN"/platforms/*.dll "$BIN"/imageformats/*.dll; do
|
||||
[ -f "$bin_file" ] || continue
|
||||
while IFS= read -r line; do
|
||||
dll_path=$(echo "$line" | awk '{print $3}')
|
||||
[ -f "$dll_path" ] || continue
|
||||
dll_name=$(basename "$dll_path")
|
||||
dst="$BIN/$dll_name"
|
||||
if [ ! -f "$dst" ]; then
|
||||
cp "$dll_path" "$dst"
|
||||
echo " Copied (pass $PASS): $dll_name"
|
||||
fi
|
||||
done < <(ldd "$bin_file" 2>/dev/null | grep -i '/ucrt64/bin/')
|
||||
done
|
||||
done
|
||||
set -e
|
||||
|
||||
DLL_COUNT=$(find "$BIN" -name "*.dll" | wc -l)
|
||||
echo "=== $DLL_COUNT DLLs present after scan ==="
|
||||
ls -lh "$BIN/QElectroTech.exe" || { echo "ERROR: exe missing from bin/"; exit 1; }
|
||||
[ "$DLL_COUNT" -gt 5 ] || { echo "ERROR: too few DLLs"; exit 1; }
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
|
||||
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
|
||||
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
|
||||
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
|
||||
SQLITE=$(find /ucrt64/bin -name "libsqlite3*.dll" | head -1)
|
||||
if [ -n "$SQLITE" ]; then
|
||||
cp "$SQLITE" "$BIN/"
|
||||
echo "SQLite3 copied: $(basename $SQLITE)"
|
||||
else
|
||||
echo "WARNING: libsqlite3 not found in /ucrt64/bin/"
|
||||
fi
|
||||
|
||||
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
|
||||
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
|
||||
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra_fr.nsh" "$NSIS_ROOT/"
|
||||
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra_missing.nsh" "$NSIS_ROOT/"
|
||||
cp -r "$GITHUB_WORKSPACE/build-aux/windows/nsis_base/." "$NSIS_ROOT/"
|
||||
|
||||
curl -fsSL \
|
||||
"https://raw.githubusercontent.com/qelectrotech/qelectrotech-source-mirror/refs/heads/master/misc/Lancer%20QET.bat" \
|
||||
-o "$FILES/Lancer QET.bat"
|
||||
cp -r "$GITHUB_WORKSPACE/elements" "$FILES/elements" || true
|
||||
cp -r "$GITHUB_WORKSPACE/titleblocks" "$FILES/titleblocks" || true
|
||||
cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true
|
||||
cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true
|
||||
|
||||
cp -r "$GITHUB_WORKSPACE/lang" "$FILES/lang" || true
|
||||
find "$GITHUB_WORKSPACE/build" -name "*.qm" -exec cp {} "$FILES/lang/" \; 2>/dev/null || true
|
||||
echo "=== .qm files in files/lang/ ==="
|
||||
ls "$FILES/lang/"*.qm 2>/dev/null | wc -l || echo "0 .qm files"
|
||||
|
||||
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE; do
|
||||
cp "$GITHUB_WORKSPACE/$f" "$FILES/$f" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "=== Verification of key files in files/ ==="
|
||||
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE \
|
||||
qet_uninstall_file_associations.reg register_filetypes.bat "Lancer QET.bat"; do
|
||||
[ -f "$FILES/$f" ] \
|
||||
&& echo " OK : $f" \
|
||||
|| echo " MISSING: $f"
|
||||
done
|
||||
for d in ico elements lang titleblocks fonts examples bin; do
|
||||
[ -d "$FILES/$d" ] \
|
||||
&& echo " OK : $d/" \
|
||||
|| echo " MISSING: $d/"
|
||||
done
|
||||
|
||||
- name: Extract version for installer name
|
||||
shell: msys2 {0}
|
||||
id: qet_version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
GITCOMMIT=$(git -C "$GITHUB_WORKSPACE" rev-parse --short HEAD)
|
||||
|
||||
A=$(git -C "$GITHUB_WORKSPACE" rev-list HEAD --count)
|
||||
HEAD=$(( A + 473 ))
|
||||
|
||||
VERSION=$(grep 'return QVersionNumber{' "$GITHUB_WORKSPACE/sources/qetversion.cpp" \
|
||||
| head -1 \
|
||||
| awk -F '{' '{ print $2 }' \
|
||||
| awk -F '}' '{ print $1 }' \
|
||||
| sed -e 's/,/./g' -e 's/ //g')
|
||||
[ -z "$VERSION" ] && VERSION="dev"
|
||||
|
||||
FULL_VERSION="${VERSION}-r${HEAD}-${GITCOMMIT}_x86_64-win64"
|
||||
echo "version=$FULL_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "base_version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "gitcommit=$GITCOMMIT" >> "$GITHUB_OUTPUT"
|
||||
echo "head=$HEAD" >> "$GITHUB_OUTPUT"
|
||||
echo "VERSION : $VERSION"
|
||||
echo "GITCOMMIT : $GITCOMMIT"
|
||||
echo "HEAD (rev) : $HEAD"
|
||||
echo "FULL : $FULL_VERSION"
|
||||
|
||||
- name: Patch QET64.nsi — version + exe name + absolute paths
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.qet_version.outputs.version }}"
|
||||
NSI="$GITHUB_WORKSPACE/nsis_root/QET64.nsi"
|
||||
FILES_WIN=$(cygpath -w "$GITHUB_WORKSPACE/nsis_root/files")
|
||||
SCRIPT="$GITHUB_WORKSPACE/build-aux/windows/patch_nsi.py"
|
||||
|
||||
python3 "$SCRIPT" "$NSI" "$VERSION" "$FILES_WIN"
|
||||
|
||||
echo "=== Verification ==="
|
||||
grep 'SOFT_VERSION' "$NSI" | head -1
|
||||
grep -m2 'nsis_root' "$NSI" | head -2
|
||||
echo "=== Contents of nsis_root/files/ ==="
|
||||
ls "$GITHUB_WORKSPACE/nsis_root/files/"
|
||||
|
||||
- name: Build NSIS installer
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NSIS_ROOT="$GITHUB_WORKSPACE/nsis_root"
|
||||
cd "$NSIS_ROOT"
|
||||
echo "=== CWD : $(pwd) ==="
|
||||
MSYS2_ARG_CONV_EXCL="*" makensis /V4 QET64.nsi
|
||||
RC=$?
|
||||
echo "=== Contents of nsis_root after makensis ==="
|
||||
ls "$NSIS_ROOT/"
|
||||
[ $RC -eq 0 ] || { echo "ERROR: makensis failed (exit $RC)"; exit 1; }
|
||||
|
||||
- name: Move installer to dist/
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$GITHUB_WORKSPACE/dist"
|
||||
INSTALLER=$(find "$GITHUB_WORKSPACE/nsis_root" -maxdepth 1 -iname "installer_*.exe" | head -1)
|
||||
if [ -z "$INSTALLER" ]; then
|
||||
echo "ERROR: no installer .exe found in nsis_root/"
|
||||
ls "$GITHUB_WORKSPACE/nsis_root/"
|
||||
exit 1
|
||||
fi
|
||||
echo "Moving: $INSTALLER -> dist/"
|
||||
mv "$INSTALLER" "$GITHUB_WORKSPACE/dist/"
|
||||
|
||||
- name: Upload build logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-logs
|
||||
path: |
|
||||
build/CMakeFiles/*.log
|
||||
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@v7
|
||||
with:
|
||||
name: qelectrotech-${{ steps.qet_version.outputs.base_version }}+git${{ steps.qet_version.outputs.head }}-x86-win64-readytouse
|
||||
path: dist/${{ steps.zip_portable.outputs.zip_name }}
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload NSIS installer
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: qelectrotech-windows-installer
|
||||
path: dist/Installer_*.exe
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload portable (nom fixe pour le workflow MSI)
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: qelectrotech-windows-portable
|
||||
path: nsis_root/files/
|
||||
retention-days: 14
|
||||
|
||||
# =============================================================================
|
||||
# Job 2: Qt6 build (EXPERIMENTAL track)
|
||||
# Job 1: Windows build (Qt6/KF6 — sole track since the Qt5 track was removed)
|
||||
#
|
||||
# Version label: the C++ source (sources/qetversion.cpp) intentionally stays
|
||||
# Qt-agnostic — QT_VERSION-based detection inside the binary proved unreliable
|
||||
@@ -368,6 +19,9 @@ jobs:
|
||||
# certainty which Qt major version it configured (-DQT_VERSION_MAJOR=6), so it
|
||||
# is safe to label the *package* "0.200.1" at this level, without touching
|
||||
# QetVersion::currentVersion() or making the binary self-detect its Qt build.
|
||||
#
|
||||
# Job id kept as "build-windows-qt6" (not renamed to "build-windows") in case
|
||||
# branch protection / required status checks reference this exact job name.
|
||||
# =============================================================================
|
||||
build-windows-qt6:
|
||||
runs-on: windows-latest
|
||||
@@ -397,6 +51,9 @@ jobs:
|
||||
mingw-w64-ucrt-x86_64-qt6-pdf
|
||||
mingw-w64-ucrt-x86_64-sqlite3
|
||||
mingw-w64-ucrt-x86_64-pkg-config
|
||||
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
||||
mingw-w64-ucrt-x86_64-kcoreaddons
|
||||
mingw-w64-ucrt-x86_64-extra-cmake-modules
|
||||
mingw-w64-ucrt-x86_64-nsis
|
||||
mingw-w64-ucrt-x86_64-angleproject
|
||||
|
||||
@@ -456,11 +113,12 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH=/ucrt64 \
|
||||
-DQt6_DIR=/ucrt64/lib/cmake/Qt6 \
|
||||
-DQT_VERSION_MAJOR=6 \
|
||||
-DBUILD_WITH_KF5=OFF \
|
||||
-DBUILD_WITH_KF=ON \
|
||||
-DBUILD_KF=OFF \
|
||||
-DPACKAGE_TESTS=OFF \
|
||||
-DCMAKE_POLICY_DEFAULT_CMP0077=NEW \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_CXX_FLAGS="-DQET_EXPORT_PROJECT_DB" \
|
||||
-DQET_EXPORT_PROJECT_DB=ON \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
|
||||
@@ -594,8 +252,8 @@ jobs:
|
||||
|
||||
# Deliberately NOT parsed from sources/qetversion.cpp: the CI job
|
||||
# already knows for certain it configured Qt6 (-DQT_VERSION_MAJOR=6),
|
||||
# so the "0.200.1" experimental-track label is set here explicitly
|
||||
# rather than relying on in-binary Qt version detection.
|
||||
# so the "0.200.1" version label is set here explicitly rather than
|
||||
# relying on in-binary Qt version detection.
|
||||
VERSION="0.200.1"
|
||||
|
||||
FULL_VERSION="${VERSION}-qt6-r${HEAD}-${GITCOMMIT}_x86_64-win64"
|
||||
@@ -687,60 +345,64 @@ jobs:
|
||||
with:
|
||||
name: qelectrotech-${{ steps.qet_version.outputs.base_version }}+git${{ steps.qet_version.outputs.head }}-x86-win64-readytouse
|
||||
path: dist/${{ steps.zip_portable.outputs.zip_name }}
|
||||
retention-days: 14
|
||||
retention-days: 40
|
||||
|
||||
- name: Upload NSIS installer
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: qelectrotech-windows-installer-qt6
|
||||
path: dist/Installer_*.exe
|
||||
retention-days: 14
|
||||
retention-days: 40
|
||||
|
||||
- name: Upload portable (nom fixe pour le workflow MSI)
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: qelectrotech-windows-portable-qt6
|
||||
path: nsis_root/files/
|
||||
retention-days: 14
|
||||
retention-days: 40
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 3 : Publie les assets nightly (exe + zip, Qt5 et Qt6) sur la release
|
||||
# Job 2 : Publie les assets nightly (exe + zip) sur la release
|
||||
# Ne tourne que sur push master (pas sur les PRs)
|
||||
# ---------------------------------------------------------------------------
|
||||
publish-nightly-assets:
|
||||
needs: [build-windows, build-windows-qt6]
|
||||
needs: [build-windows-qt6]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download installer artifacts (Qt5 + Qt6)
|
||||
- name: Download installer artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: qelectrotech-windows-installer*
|
||||
path: downloaded/installer/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download portable artifacts (Qt5 + Qt6)
|
||||
- name: Download portable artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: qelectrotech-*-readytouse
|
||||
path: downloaded/portable/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Delete old nightly assets (.exe and .zip)
|
||||
# Only Qt6-tagged assets are deleted/replaced here. Assets without "qt6"
|
||||
# in the name are the frozen Qt5 legacy build (last one ever published,
|
||||
# before the Qt5 CI job was removed) — deliberately left untouched so
|
||||
# they stay downloadable indefinitely instead of disappearing.
|
||||
- name: Delete old nightly Qt6 assets (.exe and .zip)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
gh release view nightly --repo "$REPO" --json assets \
|
||||
--jq '.assets[] | select(.name | test("\\.(exe|zip)$")) | .name' \
|
||||
--jq '.assets[] | select(.name | test("qt6")) | select(.name | test("\\.(exe|zip)$")) | .name' \
|
||||
| while read -r name; do
|
||||
echo "Deleting old asset: $name"
|
||||
echo "Deleting old Qt6 asset: $name"
|
||||
gh release delete-asset nightly "$name" --repo "$REPO" --yes
|
||||
done
|
||||
echo "Old .exe and .zip assets deleted."
|
||||
echo "Old Qt6 .exe and .zip assets deleted (legacy Qt5 assets left untouched)."
|
||||
|
||||
- name: Update nightly release
|
||||
uses: softprops/action-gh-release@v3
|
||||
@@ -759,8 +421,7 @@ jobs:
|
||||
> ⚠️ 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).
|
||||
|
||||
> 🧪 **Qt6 builds are experimental.** Files tagged `-qt6-` are built against Qt6 and are not yet
|
||||
> as tested as the Qt5 track. Expect rough edges; report issues clearly labelled "Qt6".
|
||||
> 🗄️ Files without `-qt6-` in the name are the last Qt5 build ever published (frozen, unmaintained). Files tagged `-qt6-` are the actively maintained build.
|
||||
prerelease: true
|
||||
make_latest: false
|
||||
files: |
|
||||
@@ -769,4 +430,4 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# GitHub Pages is generated and deployed by windows-msi.yml
|
||||
# after the MSI upload, so that all URLs (exe/zip/msi, Qt5+Qt6) are known.
|
||||
# after the MSI upload, so that all URLs (exe/zip/msi) are known.
|
||||
|
||||
@@ -25,18 +25,17 @@ jobs:
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Single-entry matrix kept on purpose (rather than flattening the job):
|
||||
# matrix.flavor is used as part of the MSI ProductCode seed, so changing
|
||||
# it would generate a new ProductCode and break upgrade detection for
|
||||
# existing installs. Keeping "qt6" here preserves continuity.
|
||||
matrix:
|
||||
include:
|
||||
- flavor: qt5
|
||||
portable_artifact: qelectrotech-windows-portable
|
||||
version_source: qetversion # parsed from sources/qetversion.cpp
|
||||
label_suffix: ""
|
||||
experimental: false
|
||||
- flavor: qt6
|
||||
portable_artifact: qelectrotech-windows-portable-qt6
|
||||
version_source: hardcoded # see note in "Extract version" step
|
||||
label_suffix: "-qt6-EXPERIMENTAL"
|
||||
experimental: true
|
||||
label_suffix: "-qt6"
|
||||
experimental: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -44,7 +43,6 @@ jobs:
|
||||
id-token: write # Required by SignPath
|
||||
|
||||
outputs:
|
||||
qt5_msi: ${{ steps.export.outputs.msi_name_qt5 }}
|
||||
qt6_msi: ${{ steps.export.outputs.msi_name_qt6 }}
|
||||
|
||||
steps:
|
||||
@@ -72,14 +70,12 @@ jobs:
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3. Extract version
|
||||
# Qt5: parsed from sources/qetversion.cpp (single source of truth
|
||||
# for the software's own reported version).
|
||||
# Qt6: hardcoded "0.200.1" here — deliberately NOT parsed from the
|
||||
# binary/source, since Qt-version self-detection inside the
|
||||
# compiled code proved unreliable (see commit e1aa65f). The CI
|
||||
# matrix entry already knows for certain which flavor it is
|
||||
# packaging, so the experimental-track label is set explicitly
|
||||
# at the packaging level instead.
|
||||
# packaging, so the version label is set explicitly at the
|
||||
# packaging level instead.
|
||||
# ----------------------------------------------------------------
|
||||
- name: Extract version
|
||||
id: version
|
||||
@@ -125,10 +121,9 @@ jobs:
|
||||
Write-Host "Version MSI : $verMsi"
|
||||
Write-Host "Version display : $verDisplay"
|
||||
|
||||
# Qt platform argument: only Qt5 needs the QTBUG-83161 font-rendering
|
||||
# workaround (GDI backend). Qt6 uses DirectWrite by default and does
|
||||
# not need it. Computed once here so both the bundled "Lancer QET.bat"
|
||||
# and the MSI shortcuts (wix build -d QtPlatformArgs=...) stay in sync.
|
||||
# Qt platform argument: kept from the Qt5 era (QTBUG-83161
|
||||
# font-rendering workaround, GDI backend). Qt6 uses DirectWrite by
|
||||
# default and does not need it, so this always resolves empty now.
|
||||
if ("${{ matrix.flavor }}" -eq "qt5") {
|
||||
$qtArgs = "-platform windows:fontengine=freetype"
|
||||
} else {
|
||||
@@ -228,10 +223,10 @@ jobs:
|
||||
Write-Host "Lancer QET.bat replaced for MSI installation (qtArgs: '$qtArgs')."
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 9. Build the MSI (unsigned)
|
||||
# Qt6 (experimental) uses a distinct ProductCode seed so it never
|
||||
# collides with / upgrades over the Qt5 MSI — they must be able to
|
||||
# coexist as clearly separate installs.
|
||||
# 9. Build the MSI (unsigned at this stage — signing happens below)
|
||||
# Qt6 keeps its own ProductCode seed (distinct from the retired Qt5
|
||||
# MSI), so a machine that still has the old Qt5 MSI installed gets a
|
||||
# separate, coexisting install rather than an unexpected upgrade.
|
||||
# ----------------------------------------------------------------
|
||||
- name: Build MSI
|
||||
shell: pwsh
|
||||
@@ -289,12 +284,13 @@ jobs:
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
# Qt6 stays on the unsigned/experimental track (no signing request for
|
||||
# this flavor), and forks never have the SignPath secrets, so guard on
|
||||
# both: only qt5, and only in the upstream repo.
|
||||
# Qt6 is now signed too (previously excluded while Qt5 was the stable
|
||||
# track and Qt6 was experimental-only). The remaining guard is the fork
|
||||
# check: forks never have the SignPath secrets, and a fork-originated
|
||||
# PR/run must never attempt a signing request.
|
||||
# (cf. DieterMayerOSS:fix/msi-signing-fork-guard, d3f60c88)
|
||||
- name: Sign MSI via SignPath
|
||||
if: matrix.flavor == 'qt5' && github.repository == 'qelectrotech/qelectrotech-source-mirror'
|
||||
if: github.repository == 'qelectrotech/qelectrotech-source-mirror'
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
@@ -311,18 +307,17 @@ jobs:
|
||||
with:
|
||||
name: qelectrotech-windows-msi-${{ matrix.flavor }}
|
||||
path: dist\*.msi
|
||||
retention-days: 14
|
||||
retention-days: 40
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Delete old nightly .msi asset for this flavor
|
||||
- name: Delete old nightly .msi asset
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
$pattern = if ("${{ matrix.flavor }}" -eq "qt6") { "-qt6-EXPERIMENTAL.*\\.msi$" } else { "\\.msi$" }
|
||||
$pattern = "-qt6.*\\.msi$"
|
||||
$names = gh release view nightly --repo $env:REPO --json assets --jq ".assets[] | select(.name | test(`"$pattern`")) | .name"
|
||||
foreach ($name in ($names -split "`n" | Where-Object { $_ })) {
|
||||
if ("${{ matrix.flavor }}" -eq "qt5" -and $name -match "-qt6-EXPERIMENTAL") { continue }
|
||||
Write-Host "Deleting old asset: $name"
|
||||
gh release delete-asset nightly $name --repo $env:REPO --yes
|
||||
}
|
||||
@@ -342,11 +337,7 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
$name = "$env:MSI_NAME"
|
||||
if ("${{ matrix.flavor }}" -eq "qt6") {
|
||||
echo "msi_name_qt6=$name" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "msi_name_qt5=$name" >> $env:GITHUB_OUTPUT
|
||||
}
|
||||
echo "msi_name_qt6=$name" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
@@ -354,11 +345,11 @@ jobs:
|
||||
run: |
|
||||
Write-Host "=== MSI build summary (${{ matrix.flavor }}) ==="
|
||||
Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}"
|
||||
Write-Host "Experimental : ${{ matrix.experimental }}"
|
||||
Write-Host "Signed : true"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 2 : Génère et déploie la page GitHub Pages une fois les DEUX MSI
|
||||
# (Qt5 + Qt6) publiés, pour que toutes les URLs soient connues.
|
||||
# Job 2 : Génère et déploie la page GitHub Pages une fois le MSI publié,
|
||||
# pour que toutes les URLs soient connues.
|
||||
# ---------------------------------------------------------------------------
|
||||
deploy-pages:
|
||||
needs: build-msi
|
||||
@@ -388,13 +379,17 @@ jobs:
|
||||
|
||||
ASSETS=$(gh release view nightly --repo "$REPO" --json assets --jq '.assets[].name')
|
||||
|
||||
EXE_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.exe$' | head -1)
|
||||
ZIP_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.zip$' | head -1)
|
||||
MSI_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.msi$' | head -1 || echo "")
|
||||
EXE_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.exe$' | head -1)
|
||||
ZIP_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.zip$' | head -1)
|
||||
MSI_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.msi$' | head -1 || echo "")
|
||||
|
||||
EXE_QT6_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.exe$' | head -1 || echo "")
|
||||
ZIP_QT6_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.zip$' | head -1 || echo "")
|
||||
MSI_QT6_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.msi$' | head -1 || echo "")
|
||||
# Legacy Qt5 assets (no "qt6" in the name): last ever published,
|
||||
# frozen — windows-build.yml/windows-msi.yml no longer delete or
|
||||
# replace these, so they keep pointing at the same files release
|
||||
# after release. Rendered as a separate "legacy" section if present.
|
||||
LEGACY_EXE_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.exe$' | head -1 || echo "")
|
||||
LEGACY_ZIP_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.zip$' | head -1 || echo "")
|
||||
LEGACY_MSI_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.msi$' | head -1 || echo "")
|
||||
|
||||
BASE="https://github.com/$REPO/releases/download/nightly"
|
||||
INSTALLER_URL="$BASE/$EXE_NAME"
|
||||
@@ -402,12 +397,12 @@ jobs:
|
||||
MSI_URL=""
|
||||
[ -n "$MSI_NAME" ] && MSI_URL="$BASE/$MSI_NAME"
|
||||
|
||||
INSTALLER_QT6_URL=""
|
||||
PORTABLE_QT6_URL=""
|
||||
MSI_QT6_URL=""
|
||||
[ -n "$EXE_QT6_NAME" ] && INSTALLER_QT6_URL="$BASE/$EXE_QT6_NAME"
|
||||
[ -n "$ZIP_QT6_NAME" ] && PORTABLE_QT6_URL="$BASE/$ZIP_QT6_NAME"
|
||||
[ -n "$MSI_QT6_NAME" ] && MSI_QT6_URL="$BASE/$MSI_QT6_NAME"
|
||||
LEGACY_INSTALLER_URL=""
|
||||
LEGACY_PORTABLE_URL=""
|
||||
LEGACY_MSI_URL=""
|
||||
[ -n "$LEGACY_EXE_NAME" ] && LEGACY_INSTALLER_URL="$BASE/$LEGACY_EXE_NAME"
|
||||
[ -n "$LEGACY_ZIP_NAME" ] && LEGACY_PORTABLE_URL="$BASE/$LEGACY_ZIP_NAME"
|
||||
[ -n "$LEGACY_MSI_NAME" ] && LEGACY_MSI_URL="$BASE/$LEGACY_MSI_NAME"
|
||||
|
||||
SHA="${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
SHORT="${SHA:0:7}"
|
||||
@@ -417,7 +412,7 @@ jobs:
|
||||
|
||||
export DATE SHORT REPO SHA RUN_URL RUN_NUMBER
|
||||
export INSTALLER_URL PORTABLE_URL MSI_URL
|
||||
export INSTALLER_QT6_URL PORTABLE_QT6_URL MSI_QT6_URL
|
||||
export LEGACY_INSTALLER_URL LEGACY_PORTABLE_URL LEGACY_MSI_URL
|
||||
|
||||
python3 source/build-aux/generate-page.py
|
||||
|
||||
|
||||
+138
-31
@@ -48,19 +48,11 @@ endif()
|
||||
# INTERFACE_QT_MAJOR_VERSION mismatch at generate time.
|
||||
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (5 or 6)" FORCE)
|
||||
|
||||
# Add sub directories
|
||||
option(PACKAGE_TESTS "Build the tests" ON)
|
||||
if(PACKAGE_TESTS)
|
||||
message("Add sub directory tests")
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
include(cmake/paths_compilation_installation.cmake)
|
||||
include(cmake/start_options.cmake)
|
||||
include(cmake/developer_options.cmake)
|
||||
include(cmake/git_update_submodules.cmake)
|
||||
include(cmake/git_last_commit_sha.cmake)
|
||||
include(cmake/fetch_kdeaddons.cmake)
|
||||
include(cmake/fetch_singleapplication.cmake)
|
||||
include(cmake/fetch_pugixml.cmake)
|
||||
include(cmake/qet_compilation_vars.cmake)
|
||||
@@ -78,12 +70,43 @@ find_package(
|
||||
${QET_COMPONENTS}
|
||||
REQUIRED)
|
||||
|
||||
# Qt6 only creates the Qt::GuiPrivate target (used for QPdfEngine::drawHyperlink)
|
||||
# when the GuiPrivate component is explicitly requested. Qt5 has no such
|
||||
# component package and creates the target implicitly with Gui, so only
|
||||
# request it on Qt6 - requesting it on Qt5 fails the whole configure.
|
||||
# <private/qpdf_p.h> (QPdfEngine::drawHyperlink) needs Qt's private GUI module.
|
||||
# Qt >= 6.7 ships it as a proper find_package component, but some distro
|
||||
# packages (e.g. Ubuntu's qt6-base-private-dev) omit Qt6GuiPrivateConfig.cmake
|
||||
# and only provide the implicit Qt6::GuiPrivate target created alongside
|
||||
# Qt6::Gui. Try the component quietly, then verify the target below so a
|
||||
# missing private-headers package fails here instead of at compile time.
|
||||
# Qt5 has no such component as its GuiPrivate target always exists once Gui is found.
|
||||
if(QT_VERSION_MAJOR GREATER_EQUAL 6)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS GuiPrivate)
|
||||
find_package(Qt6 QUIET COMPONENTS GuiPrivate)
|
||||
endif()
|
||||
|
||||
if(QT_VERSION_MAJOR GREATER_EQUAL 6 AND NOT TARGET Qt6::GuiPrivate)
|
||||
message(FATAL_ERROR
|
||||
"Qt6::GuiPrivate was not found. It is required for PDF hyperlink "
|
||||
"support (<private/qpdf_p.h>). Install the Qt6 private headers "
|
||||
"(e.g. 'qt6-base-private-dev' on Debian/Ubuntu) or use a Qt build "
|
||||
"that provides the GuiPrivate component.")
|
||||
endif()
|
||||
|
||||
# PDF page import (toolbar "Ajouter un PDF") needs the QtPdf module and,
|
||||
# specifically, QPdfDocument::pagePointSize() which only exists since Qt 6.4.
|
||||
# Unlike GuiPrivate above, a missing QtPdf module is NOT fatal here: some
|
||||
# Qt6 distributions (e.g. the Flatpak org.kde.Platform runtime) don't ship
|
||||
# it at all, since it lives in the qtwebengine source tree rather than Qt6
|
||||
# core. When it's missing, or too old, the feature is silently disabled -
|
||||
# see the QT_VERSION_CHECK / QET_HAS_QTPDF guards in diagrameventaddpdf.*
|
||||
# and pdfpagesdialog.*.
|
||||
set(QET_HAS_QTPDF FALSE)
|
||||
if(QT_VERSION_MAJOR GREATER_EQUAL 6)
|
||||
find_package(Qt6 QUIET COMPONENTS Pdf)
|
||||
if(TARGET Qt6::Pdf AND NOT Qt6_VERSION VERSION_LESS 6.4.0)
|
||||
set(QET_HAS_QTPDF TRUE)
|
||||
list(APPEND QET_PRIVATE_LIBRARIES Qt::Pdf)
|
||||
add_compile_definitions(QET_HAS_QTPDF)
|
||||
else()
|
||||
message(STATUS "QtPdf module not available (or Qt < 6.4): PDF page import feature disabled")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(SQLite3 REQUIRED)
|
||||
@@ -100,25 +123,19 @@ endif()
|
||||
|
||||
set(CMAKE_AUTOUIC_SEARCH_PATHS ${QET_DIR}/sources/ui)
|
||||
|
||||
# The default build only compiles the tracked .ts files to .qm (lrelease).
|
||||
# Refreshing the .ts from the sources (lupdate) is a developer action behind
|
||||
# the explicit "update_translations" target below: running lupdate on every
|
||||
# build rewrote tracked files as a side effect, and under high parallelism
|
||||
# lupdate rewriting a .ts while lrelease read the same file made the build
|
||||
# fail with "Premature end of document".
|
||||
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang")
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
qt6_add_translation(QM_FILES ${TS_FILES})
|
||||
set(KF_MAJOR_VERSION 6)
|
||||
else()
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
set(KF_MAJOR_VERSION 5)
|
||||
endif()
|
||||
include(cmake/fetch_kdeaddons.cmake)
|
||||
|
||||
add_custom_target(update_translations
|
||||
COMMAND $<TARGET_FILE:Qt${QT_VERSION_MAJOR}::lupdate> ${CMAKE_SOURCE_DIR}/sources -ts ${TS_FILES}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMENT "Updating .ts files from sources/ (lupdate) - developer target, run explicitly"
|
||||
VERBATIM
|
||||
)
|
||||
# Add sub directories
|
||||
option(PACKAGE_TESTS "Build the tests" ON)
|
||||
if(PACKAGE_TESTS)
|
||||
message("Add sub directory tests")
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# als laatse
|
||||
include(cmake/define_definitions.cmake)
|
||||
@@ -145,6 +162,94 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
endif()
|
||||
|
||||
# The default build only compiles the tracked .ts files to .qm (lrelease).
|
||||
# Refreshing the .ts from the sources (lupdate) is a developer action behind
|
||||
# the explicit "update_translations" target below: running lupdate on every
|
||||
# build rewrote tracked files as a side effect, and under high parallelism
|
||||
# lupdate rewriting a .ts while lrelease read the same file made the build
|
||||
# fail with "Premature end of document".
|
||||
set_source_files_properties(
|
||||
${TS_FILES}
|
||||
PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang"
|
||||
)
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
if(Qt6_VERSION VERSION_LESS "6.2")
|
||||
# Qt 6.0–6.1
|
||||
qt6_add_translation(QM_FILES ${TS_FILES})
|
||||
|
||||
# qt6_add_translation() only creates custom commands. Something must
|
||||
# depend on their outputs for them to run during the default build.
|
||||
add_custom_target(${PROJECT_NAME}_lrelease ALL
|
||||
DEPENDS ${QM_FILES}
|
||||
)
|
||||
|
||||
add_custom_target(update_translations
|
||||
COMMAND
|
||||
$<TARGET_FILE:Qt6::lupdate>
|
||||
"${CMAKE_SOURCE_DIR}/sources"
|
||||
-ts ${TS_FILES}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT
|
||||
"Updating .ts files from sources/ (lupdate) - developer target"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
elseif(Qt6_VERSION VERSION_LESS "6.7")
|
||||
# Qt 6.2–6.6: old target-based signature
|
||||
qt_add_lrelease(
|
||||
${PROJECT_NAME}
|
||||
TS_FILES ${TS_FILES}
|
||||
QM_FILES_OUTPUT_VARIABLE QM_FILES
|
||||
)
|
||||
|
||||
# Automatically creates the update_translations umbrella target.
|
||||
qt_add_lupdate(
|
||||
${PROJECT_NAME}
|
||||
TS_FILES ${TS_FILES}
|
||||
)
|
||||
|
||||
else()
|
||||
# Qt 6.7+: new signature
|
||||
qt_add_lrelease(
|
||||
TS_FILES ${TS_FILES}
|
||||
LRELEASE_TARGET ${PROJECT_NAME}_lrelease
|
||||
QM_FILES_OUTPUT_VARIABLE QM_FILES
|
||||
)
|
||||
|
||||
qt_add_lupdate(
|
||||
SOURCE_TARGETS ${PROJECT_NAME}
|
||||
TS_FILES ${TS_FILES}
|
||||
LUPDATE_TARGET update_translations
|
||||
NO_GLOBAL_TARGET
|
||||
)
|
||||
endif()
|
||||
|
||||
else()
|
||||
# Qt 5
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
|
||||
# Likewise, qt5_add_translation() needs a target depending on its outputs,
|
||||
# unless QM_FILES are already consumed elsewhere.
|
||||
add_custom_target(${PROJECT_NAME}_lrelease ALL
|
||||
DEPENDS ${QM_FILES}
|
||||
)
|
||||
|
||||
add_custom_target(update_translations
|
||||
COMMAND
|
||||
$<TARGET_FILE:Qt5::lupdate>
|
||||
"${CMAKE_SOURCE_DIR}/sources"
|
||||
-ts ${TS_FILES}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT
|
||||
"Updating .ts files from sources/ (lupdate) - developer target"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
# Optional precompiled headers -- see QET_ENABLE_PCH in
|
||||
# cmake/developer_options.cmake for what this trades away.
|
||||
#
|
||||
@@ -180,7 +285,7 @@ target_link_libraries(
|
||||
pugixml::pugixml
|
||||
SingleApplication::SingleApplication
|
||||
SQLite3::SQLite3
|
||||
${KF5_PRIVATE_LIBRARIES}
|
||||
${KF_PRIVATE_LIBRARIES}
|
||||
${QET_PRIVATE_LIBRARIES}
|
||||
)
|
||||
|
||||
@@ -221,7 +326,7 @@ target_include_directories(
|
||||
${QET_DIR}/sources/svg
|
||||
)
|
||||
|
||||
if(NOT BUILD_WITH_KF5)
|
||||
if(NOT BUILD_WITH_KF)
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
@@ -229,7 +334,9 @@ if(NOT BUILD_WITH_KF5)
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROJECT_NAME})
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
BUNDLE DESTINATION .
|
||||
)
|
||||
|
||||
if (NOT MINGW)
|
||||
install(DIRECTORY ico/breeze-icons/16x16 DESTINATION ${QET_ICONS_PATH})
|
||||
|
||||
@@ -20,6 +20,7 @@ All notable changes to QElectroTech are documented here.
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fix #798: clamp element-editor and diagram-view zoom to prevent view-transform overflow crash on scroll-wheel zoom ([3ca5d4a](../../commit/3ca5d4ab2))
|
||||
- Fix(windows-msi): inject rev into MSI Version Build field ([e19f523](../../commit/e19f5232277efb37435cb65a83563d73333d62ec))
|
||||
- Fix #391: use wide-char path for pugixml on Windows to handle Unicode paths ([31edf30](../../commit/31edf30c619213368e9b592b51be6ca8190db831))
|
||||
- Fix(#283): restore center alignment when loading table config ([f55ba56](../../commit/f55ba568f68293e06436899bcc831431e9d27295))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "org.qelectrotech.QElectroTech",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "5.15-25.08",
|
||||
"runtime-version": "6.11",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "qelectrotech",
|
||||
"rename-desktop-file": "org.qelectrotech.qelectrotech.desktop",
|
||||
@@ -30,7 +30,20 @@
|
||||
"pypi-dependencies.json",
|
||||
{
|
||||
"name": "qelectrotech",
|
||||
"buildsystem": "qmake",
|
||||
"//qt6-private-headers-note": "qt6-base-private-dev has no Flatpak build-depends equivalent — private Qt6 headers ship inside org.kde.Sdk itself. If the cmake build fails looking for QtCore/private/*.h, the SDK/runtime branch is mismatched with the source tree, not a missing package.",
|
||||
"//sqlite-driver-note": "libqt6sql6-sqlite / libsqlite3-dev have no Flatpak build-depends equivalent either. QET_EXPORT_PROJECT_DB=ON assumes the KDE runtime ships the Qt6 SQLite plugin (libqsqlite.so) already built into QtSql — verified at Debian packaging time this needed an explicit runtime dep there (see debian/control). Test the in-app database export after building; if it silently fails, the runtime's QtSql plugin set needs checking, not a package to add here.",
|
||||
"buildsystem": "cmake",
|
||||
"config-opts": [
|
||||
"-DCMAKE_INSTALL_PREFIX=/app",
|
||||
"-DQT_VERSION_MAJOR=6",
|
||||
"-DBUILD_WITH_KF=ON",
|
||||
"-DBUILD_KF=OFF",
|
||||
"-DPACKAGE_TESTS=OFF",
|
||||
"-DBUILD_PUGIXML=ON",
|
||||
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON",
|
||||
"-DFETCHCONTENT_SOURCE_DIR_PUGIXML=/run/build/qelectrotech/pugixml",
|
||||
"-DQET_EXPORT_PROJECT_DB=ON"
|
||||
],
|
||||
"post-install": [
|
||||
"mv ${FLATPAK_DEST}/share/mime/packages/qelectrotech.xml ${FLATPAK_DEST}/share/mime/packages/org.qelectrotech.QElectroTech.xml"
|
||||
],
|
||||
@@ -38,12 +51,6 @@
|
||||
{
|
||||
"type": "dir",
|
||||
"path": "../.."
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"paths": [
|
||||
"patches/fix-the-installation-paths.patch"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+42
-33
@@ -2,11 +2,19 @@
|
||||
"""
|
||||
generate-page.py — Generates gh-pages/index.html for QElectroTech nightly builds.
|
||||
Called from windows-msi.yml deploy-pages job.
|
||||
|
||||
Environment variables required:
|
||||
DATE, SHORT, REPO, SHA, RUN_URL, RUN_NUMBER,
|
||||
INSTALLER_URL, PORTABLE_URL, MSI_URL (optional)
|
||||
Optional (Qt6 experimental track — omitted entirely if empty):
|
||||
INSTALLER_QT6_URL, PORTABLE_QT6_URL, MSI_QT6_URL
|
||||
|
||||
Optional (frozen Qt5 legacy build — omitted entirely if empty):
|
||||
LEGACY_INSTALLER_URL, LEGACY_PORTABLE_URL, LEGACY_MSI_URL
|
||||
|
||||
NOTE: Windows Qt5 CI build removed (Qt6 is now the sole track built and
|
||||
signed going forward). INSTALLER_URL / PORTABLE_URL / MSI_URL point to the
|
||||
Qt6 build artifacts. The LEGACY_* variables, when set, point to the last
|
||||
Qt5 nightly assets that were ever published — kept downloadable but frozen
|
||||
(windows-build.yml no longer regenerates or deletes them).
|
||||
"""
|
||||
import os
|
||||
|
||||
@@ -20,9 +28,9 @@ installer_url = os.environ.get("INSTALLER_URL", "")
|
||||
portable_url = os.environ.get("PORTABLE_URL", "")
|
||||
msi_url = os.environ.get("MSI_URL", "")
|
||||
|
||||
installer_qt6_url = os.environ.get("INSTALLER_QT6_URL", "")
|
||||
portable_qt6_url = os.environ.get("PORTABLE_QT6_URL", "")
|
||||
msi_qt6_url = os.environ.get("MSI_QT6_URL", "")
|
||||
legacy_installer_url = os.environ.get("LEGACY_INSTALLER_URL", "")
|
||||
legacy_portable_url = os.environ.get("LEGACY_PORTABLE_URL", "")
|
||||
legacy_msi_url = os.environ.get("LEGACY_MSI_URL", "")
|
||||
|
||||
msi_block = ""
|
||||
if msi_url:
|
||||
@@ -32,43 +40,44 @@ if msi_url:
|
||||
<span class="btn-text">Windows Installer .msi<small>.msi — for enterprise / GPO deployment</small></span>
|
||||
</a>"""
|
||||
|
||||
# Qt6 experimental section — only rendered if at least one Qt6 asset exists.
|
||||
qt6_block = ""
|
||||
if installer_qt6_url or portable_qt6_url or msi_qt6_url:
|
||||
qt6_msi_btn = ""
|
||||
if msi_qt6_url:
|
||||
qt6_msi_btn = f"""
|
||||
<a class="btn btn-msi" href="{msi_qt6_url}">
|
||||
# Legacy Qt5 section — only rendered if at least one legacy asset exists.
|
||||
legacy_block = ""
|
||||
if legacy_installer_url or legacy_portable_url or legacy_msi_url:
|
||||
legacy_installer_btn = ""
|
||||
if legacy_installer_url:
|
||||
legacy_installer_btn = f"""
|
||||
<a class="btn btn-secondary" href="{legacy_installer_url}">
|
||||
<span class="btn-icon">⬇</span>
|
||||
<span class="btn-text">Windows Installer .msi (Qt6)<small>.msi — experimental, for enterprise / GPO deployment</small></span>
|
||||
<span class="btn-text">Windows Installer (Qt5, legacy)<small>.exe — frozen, no longer updated</small></span>
|
||||
</a>"""
|
||||
qt6_installer_btn = ""
|
||||
if installer_qt6_url:
|
||||
qt6_installer_btn = f"""
|
||||
<a class="btn btn-primary" href="{installer_qt6_url}">
|
||||
legacy_msi_btn = ""
|
||||
if legacy_msi_url:
|
||||
legacy_msi_btn = f"""
|
||||
<a class="btn btn-secondary" href="{legacy_msi_url}">
|
||||
<span class="btn-icon">⬇</span>
|
||||
<span class="btn-text">Windows Installer (Qt6)<small>.exe — experimental, includes all dependencies</small></span>
|
||||
<span class="btn-text">Windows Installer .msi (Qt5, legacy)<small>.msi — frozen, no longer updated</small></span>
|
||||
</a>"""
|
||||
qt6_portable_btn = ""
|
||||
if portable_qt6_url:
|
||||
qt6_portable_btn = f"""
|
||||
<a class="btn btn-secondary" href="{portable_qt6_url}">
|
||||
legacy_portable_btn = ""
|
||||
if legacy_portable_url:
|
||||
legacy_portable_btn = f"""
|
||||
<a class="btn btn-secondary" href="{legacy_portable_url}">
|
||||
<span class="btn-icon">📦</span>
|
||||
<span class="btn-text">Windows Portable (Qt6)<small>.zip — experimental, no installation required</small></span>
|
||||
<span class="btn-text">Windows Portable (Qt5, legacy)<small>.zip — frozen, no longer updated</small></span>
|
||||
</a>"""
|
||||
|
||||
qt6_block = f"""
|
||||
legacy_block = f"""
|
||||
<div class="card">
|
||||
<h2>🧪 Windows — x86_64 — Qt6 track</h2>
|
||||
<h2>🗃 Windows — x86_64 — Qt5 (legacy, unmaintained)</h2>
|
||||
<div class="warning">
|
||||
⚠️ <strong>Experimental.</strong> These Qt6-based installer, portable and MSI
|
||||
builds are new and not as thoroughly tested as the Qt5 track above. Expect rough
|
||||
edges; please report issues and mention "Qt6" explicitly.
|
||||
🗃 <strong>Legacy build — frozen, no longer updated.</strong> This is the
|
||||
last Qt5 build published before Windows CI switched to Qt6 only. Kept available for
|
||||
anyone who still needs it, but it will not receive further fixes or security updates.
|
||||
Please migrate to the Qt6 build above when you can.
|
||||
</div>
|
||||
<div class="downloads">
|
||||
{qt6_installer_btn}
|
||||
{qt6_msi_btn}
|
||||
{qt6_portable_btn}
|
||||
{legacy_installer_btn}
|
||||
{legacy_msi_btn}
|
||||
{legacy_portable_btn}
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
@@ -126,7 +135,7 @@ For production use, download a <a href="https://github.com/{repo}/releases">stab
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>🏹 Windows — x86_64 — Qt5 track</h2>
|
||||
<h2>🏹 Windows — x86_64</h2>
|
||||
<div class="downloads">
|
||||
<a class="btn btn-primary" href="{installer_url}">
|
||||
<span class="btn-icon">⬇</span>
|
||||
@@ -143,7 +152,7 @@ For production use, download a <a href="https://github.com/{repo}/releases">stab
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{qt6_block}
|
||||
{legacy_block}
|
||||
</main>
|
||||
<footer>
|
||||
Auto-generated by GitHub Actions ·
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
name: qelectrotech
|
||||
title: QElectroTech
|
||||
base: core22
|
||||
base: core24
|
||||
adopt-info: qelectrotech
|
||||
license: GPL-2.0
|
||||
summary: Electrical diagram editor
|
||||
description: |
|
||||
QElectroTech, or QET in short, is a libre and open source desktop application
|
||||
QElectroTech, or QET in short, is a libre and open source desktop application
|
||||
to create diagrams and schematics.
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
compression: lzo
|
||||
|
||||
architectures:
|
||||
- build-on: amd64
|
||||
build-for: amd64
|
||||
- build-on: arm64
|
||||
build-for: arm64
|
||||
|
||||
layout:
|
||||
/usr/local/share/qelectrotech:
|
||||
symlink: $SNAP/usr/local/share/qelectrotech
|
||||
|
||||
platforms:
|
||||
amd64:
|
||||
arm64:
|
||||
|
||||
apps:
|
||||
qelectrotech:
|
||||
command: usr/local/bin/qelectrotech
|
||||
command: usr/bin/qelectrotech
|
||||
common-id: qelectrotech.desktop
|
||||
extensions:
|
||||
- kde-neon
|
||||
extensions:
|
||||
- kde-neon-6
|
||||
plugs: &plugs [opengl, unity7, home, removable-media, gsettings, network, cups-control, wayland, x11]
|
||||
environment: &env
|
||||
TCL_LIBRARY: $SNAP/usr/share/tcltk/tcl8.6
|
||||
HOME: $SNAP_USER_COMMON
|
||||
PYTHONPATH: $SNAP:$SNAP/lib/python3.10/site-packages:$SNAP/usr/lib/python3.10:$SNAP/usr/lib/python3.10/lib-dynload
|
||||
PYTHONPATH: $SNAP:$SNAP/lib/python3.12/site-packages:$SNAP/usr/lib/python3.12:$SNAP/usr/lib/python3.12/lib-dynload
|
||||
|
||||
qet-tb-generator:
|
||||
command: bin/qet_tb_generator
|
||||
extensions:
|
||||
- kde-neon
|
||||
extensions:
|
||||
- kde-neon-6
|
||||
plugs: *plugs
|
||||
environment: *env
|
||||
|
||||
|
||||
|
||||
parts:
|
||||
launchers:
|
||||
plugin: dump
|
||||
@@ -55,7 +46,7 @@ parts:
|
||||
source: https://github.com/raulroda/qet_tb_generator-plugin.git
|
||||
source-tag: v1.31
|
||||
python-packages: [PySimpleGUI]
|
||||
stage-packages:
|
||||
stage-packages:
|
||||
- python3-lxml
|
||||
- python3-tk
|
||||
- libtk8.6
|
||||
@@ -63,16 +54,17 @@ parts:
|
||||
kde-sdk-setup:
|
||||
plugin: nil
|
||||
build-snaps:
|
||||
- kf5-5-110-qt-5-15-11-core22-sdk
|
||||
- kf6-core24-sdk
|
||||
- kde-qt6-core24-sdk
|
||||
build-packages:
|
||||
- g++
|
||||
- mesa-common-dev
|
||||
- libglvnd-dev
|
||||
- rsync
|
||||
override-build: |
|
||||
rsync -a --ignore-existing /snap/kf5-5-110-qt-5-15-11-core22-sdk/current/ /
|
||||
rsync -a --ignore-existing /snap/kf6-core24-sdk/current/ /
|
||||
rsync -a --ignore-existing /snap/kde-qt6-core24-sdk/current/ /
|
||||
|
||||
|
||||
qelectrotech:
|
||||
after: [kde-sdk-setup]
|
||||
plugin: nil
|
||||
@@ -81,37 +73,48 @@ parts:
|
||||
- git
|
||||
- sqlite3
|
||||
- xdg-user-dirs
|
||||
# libxcb-cursor0 was added as a hard dependency of the Qt5 xcb platform
|
||||
# plugin in Qt 5.15.x. The kf5-5-110-qt-5-15-11-core22 content snap does
|
||||
# not bundle it, so dlopen() of libqxcb.so fails on Ubuntu 24.04 hosts
|
||||
# with "found but could not load" (issue #373). Staging the 22.04 build
|
||||
# of this library satisfies the dep without changing base or Qt version.
|
||||
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
||||
# snap (issue #373). Not yet re-verified against kf6-core24 — remove this
|
||||
# if the Qt6 xcb platform plugin loads cleanly without it.
|
||||
- libxcb-cursor0
|
||||
build-packages:
|
||||
- git
|
||||
- cmake
|
||||
- ninja-build
|
||||
- libsqlite3-dev
|
||||
- qt6-tools-dev
|
||||
- qt6-base-private-dev
|
||||
- pkgconf
|
||||
override-build: |
|
||||
displayed_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')
|
||||
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
|
||||
modified_displayed_version="${snap_version}.snap"
|
||||
sed -i -E "s|const QString displayedVersion =.*|const QString displayedVersion =\"$modified_displayed_version\";|" sources/qet.h
|
||||
craftctl set version="$snap_version"
|
||||
qmake "$CRAFT_PART_SRC/qelectrotech.pro"
|
||||
make -j${CRAFT_PARALLEL_BUILD_COUNT}
|
||||
make install INSTALL_ROOT="$CRAFT_PART_INSTALL"
|
||||
cmake -G Ninja -B build -S "$CRAFT_PART_SRC" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DQT_VERSION_MAJOR=6 \
|
||||
-DBUILD_WITH_KF=ON \
|
||||
-DBUILD_KF=OFF \
|
||||
-DPACKAGE_TESTS=OFF \
|
||||
-DBUILD_PUGIXML=ON \
|
||||
-DQET_EXPORT_PROJECT_DB=ON
|
||||
ninja -C build -j${CRAFT_PARALLEL_BUILD_COUNT}
|
||||
DESTDIR="$CRAFT_PART_INSTALL" ninja -C build install
|
||||
override-stage: |
|
||||
craftctl default
|
||||
# patch desktop file with correct icon path
|
||||
SED_CMD="sed -i -E s|^Icon=(.*)|Icon=\${SNAP}/usr/local/share/icons/hicolor/128x128/apps/\1.png|g"
|
||||
$SED_CMD usr/local/share/applications/org.qelectrotech.qelectrotech.desktop
|
||||
SED_CMD="sed -i -E s|^Icon=(.*)|Icon=\${SNAP}/usr/share/icons/hicolor/128x128/apps/\1.png|g"
|
||||
$SED_CMD usr/share/applications/org.qelectrotech.qelectrotech.desktop
|
||||
|
||||
cleanup:
|
||||
after: [qelectrotech, qet-tb-generator]
|
||||
plugin: nil
|
||||
build-snaps: [kf5-5-110-qt-5-15-11-core22]
|
||||
build-snaps: [kf6-core24]
|
||||
override-prime: |
|
||||
set -eux
|
||||
for snap in "kf5-5-110-qt-5-15-11-core22"; do # List all content-snaps you're using here
|
||||
for snap in "kf6-core24"; do # List all content-snaps you're using here
|
||||
cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$CRAFT_PRIME/{}" "$CRAFT_PRIME/usr/{}" \;
|
||||
done
|
||||
for cruft in bug lintian man; do
|
||||
|
||||
@@ -40,8 +40,8 @@ git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror
|
||||
Here are the technical choices made for the software development:
|
||||
|
||||
* Integrated development environment: [Qt Framework](https://www.qt.io/ide/)
|
||||
* Libraries: Qt 5.x
|
||||
* [KF5 Framework](https://github.com/KDE)
|
||||
* Libraries: Qt 5.x / Qt 6.x
|
||||
* [KF5/6 Framework](https://github.com/KDE)
|
||||
[Cmake](https://cmake.org/install/)
|
||||
[kcoreaddons](https://github.com/KDE/kcoreaddons/tree/kf5)
|
||||
[kwidgetsaddons](https://github.com/KDE/kwidgetsaddons/tree/kf5).
|
||||
|
||||
@@ -25,24 +25,24 @@ message("INSTALL_PREFIX " ${INSTALL_PREFIX})
|
||||
message("QET_BINARY_PATH " ${QET_BINARY_PATH})
|
||||
|
||||
if(${QET_COMMON_COLLECTION_PATH} STRGREATER "")
|
||||
message("QET_COMMON_COLLECTION_PATH " ${INSTALL_PREFIX}${QET_COMMON_COLLECTION_PATH})
|
||||
add_definitions(-DQET_COMMON_COLLECTION_PATH=${INSTALL_PREFIX}${QET_COMMON_COLLECTION_PATH})
|
||||
message("QET_COMMON_COLLECTION_PATH " ${COMPIL_PREFIX}${QET_COMMON_COLLECTION_PATH})
|
||||
add_definitions(-DQET_COMMON_COLLECTION_PATH=${COMPIL_PREFIX}${QET_COMMON_COLLECTION_PATH})
|
||||
endif()
|
||||
if(${QET_COMMON_TBT_PATH} STRGREATER "")
|
||||
message("QET_COMMON_TBT_PATH " ${INSTALL_PREFIX}${QET_COMMON_TBT_PATH})
|
||||
add_definitions(-DQET_COMMON_TBT_PATH=${INSTALL_PREFIX}${QET_COMMON_TBT_PATH})
|
||||
message("QET_COMMON_TBT_PATH " ${COMPIL_PREFIX}${QET_COMMON_TBT_PATH})
|
||||
add_definitions(-DQET_COMMON_TBT_PATH=${COMPIL_PREFIX}${QET_COMMON_TBT_PATH})
|
||||
endif()
|
||||
if(${QET_LANG_PATH_RELATIVE_TO_BINARY_PATH})
|
||||
add_definitions(-DQET_LANG_PATH_RELATIVE_TO_BINARY_PATH)
|
||||
endif()
|
||||
if(${QET_LANG_PATH} STRGREATER "")
|
||||
message("QET_LANG_PATH " ${INSTALL_PREFIX}${QET_LANG_PATH})
|
||||
add_definitions(-DQET_LANG_PATH=${INSTALL_PREFIX}${QET_LANG_PATH})
|
||||
message("QET_LANG_PATH " ${COMPIL_PREFIX}${QET_LANG_PATH})
|
||||
add_definitions(-DQET_LANG_PATH=${COMPIL_PREFIX}${QET_LANG_PATH})
|
||||
endif()
|
||||
if (NOT MINGW)
|
||||
if(${QET_EXAMPLES_PATH} STRGREATER "")
|
||||
message("QET_EXAMPLES_PATH " ${INSTALL_PREFIX}${QET_EXAMPLES_PATH})
|
||||
add_definitions(-DQET_EXAMPLES_PATH=${INSTALL_PREFIX}${QET_EXAMPLES_PATH})
|
||||
message("QET_EXAMPLES_PATH " ${COMPIL_PREFIX}${QET_EXAMPLES_PATH})
|
||||
add_definitions(-DQET_EXAMPLES_PATH=${COMPIL_PREFIX}${QET_EXAMPLES_PATH})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -62,10 +62,10 @@ message("PROJECT_SOURCE_DIR :" ${PROJECT_SOURCE_DIR})
|
||||
message("QET_DIR :" ${QET_DIR})
|
||||
message("GIT_COMMIT_SHA :" ${GIT_COMMIT_SHA})
|
||||
|
||||
if(BUILD_WITH_KF5)
|
||||
message("KF5_GIT_TAG :" ${KF5_GIT_TAG})
|
||||
if(BUILD_WITH_KF)
|
||||
message("KF_GIT_TAG :" ${KF_GIT_TAG})
|
||||
else()
|
||||
add_definitions(-DBUILD_WITHOUT_KF5)
|
||||
add_definitions(-DBUILD_WITHOUT_KF)
|
||||
endif()
|
||||
message("QET_COMPONENTS :" ${QET_COMPONENTS})
|
||||
message("QT_VERSION_MAJOR :" ${QT_VERSION_MAJOR})
|
||||
|
||||
@@ -31,8 +31,11 @@ add_definitions(-DQT_MESSAGELOGCONTEXT)
|
||||
# In order to do so, uncomment the following line.
|
||||
#add_definitions(-DTODO_LIST)
|
||||
|
||||
# Build with KF5
|
||||
option(BUILD_WITH_KF5 "Build with KF5" ON)
|
||||
# Build with KDE Frameworks. The major version (KF5/KF6) is derived
|
||||
# automatically from QT_VERSION_MAJOR -- KDE Frameworks deliberately
|
||||
# mirrors Qt's own major version numbering, so there is no independent
|
||||
# choice to make here. See cmake/fetch_kdeaddons.cmake.
|
||||
option(BUILD_WITH_KF "Build with KDE Frameworks" ON)
|
||||
|
||||
# Precompiled headers for the Qt umbrella headers.
|
||||
#
|
||||
|
||||
+58
-36
@@ -16,52 +16,74 @@
|
||||
|
||||
message(" - fetch_kdeaddons")
|
||||
|
||||
if(BUILD_WITH_KF5)
|
||||
# TODO remove path as soon as Qt5 gets retired
|
||||
if(BUILD_WITH_KF)
|
||||
Include(FetchContent)
|
||||
|
||||
option(BUILD_KF5 "Build KF5 libraries, use system ones otherwise" YES)
|
||||
option(BUILD_KF "Build KF libraries, use system ones otherwise" YES)
|
||||
|
||||
if(BUILD_KF5)
|
||||
if(BUILD_KF)
|
||||
|
||||
if(NOT DEFINED KF5_GIT_TAG)
|
||||
#https://qelectrotech.org/forum/viewtopic.php?pid=13924#p13924
|
||||
set(KF5_GIT_TAG v5.77.0)
|
||||
if(KF_MAJOR_VERSION EQUAL 5)
|
||||
if(NOT DEFINED KF_GIT_TAG)
|
||||
#https://qelectrotech.org/forum/viewtopic.php?pid=13924#p13924
|
||||
set(KF_GIT_TAG v5.77.0)
|
||||
endif()
|
||||
else()
|
||||
if(NOT DEFINED KF_GIT_TAG)
|
||||
# this is a more or less random version, taken as an conservative approach
|
||||
set(KF_GIT_TAG v6.10.0)
|
||||
endif()
|
||||
endif()
|
||||
# using a function in order to limit the scope of the variables
|
||||
# with CMake >=3.25 we could use a block()
|
||||
function(qet_make_kf_available)
|
||||
# Fix stop the run autotests of kcoreaddons
|
||||
# see
|
||||
# https://invent.kde.org/frameworks/kcoreaddons/-/blob/master/CMakeLists.txt#L98
|
||||
# issue:
|
||||
# CMake Error at /usr/share/ECM/modules/ECMAddTests.cmake:89 (add_executable):
|
||||
# Cannot find source file:
|
||||
# see
|
||||
# https://qelectrotech.org/forum/viewtopic.php?pid=13929#p13929
|
||||
set(KDE_SKIP_TEST_SETTINGS ON)
|
||||
set(BUILD_TESTING OFF)
|
||||
|
||||
# QElectroTech is a plain QtWidgets application with no QML anywhere in
|
||||
# it; these disable optional features of the fetched KF modules that
|
||||
# would otherwise pull in extra Qt6 components (e.g. Qt6Qml) we don't
|
||||
# have and don't need.
|
||||
set(BUILD_DESIGNERPLUGIN OFF)
|
||||
set(KCOREADDONS_USE_QML OFF)
|
||||
set(BUILD_QCH OFF)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
# Fix stop the run autotests of kcoreaddons
|
||||
# see
|
||||
# https://invent.kde.org/frameworks/kcoreaddons/-/blob/master/CMakeLists.txt#L98
|
||||
# issue:
|
||||
# CMake Error at /usr/share/ECM/modules/ECMAddTests.cmake:89 (add_executable):
|
||||
# Cannot find source file:
|
||||
# see
|
||||
# https://qelectrotech.org/forum/viewtopic.php?pid=13929#p13929
|
||||
set(KDE_SKIP_TEST_SETTINGS "TRUE")
|
||||
set(BUILD_TESTING "0")
|
||||
FetchContent_Declare(
|
||||
ecm
|
||||
GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git
|
||||
GIT_TAG ${KF5_GIT_TAG})
|
||||
FetchContent_MakeAvailable(ecm)
|
||||
FetchContent_Declare(
|
||||
ecm
|
||||
GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git
|
||||
GIT_TAG ${KF_GIT_TAG})
|
||||
FetchContent_MakeAvailable(ecm)
|
||||
|
||||
FetchContent_Declare(
|
||||
kcoreaddons
|
||||
GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git
|
||||
GIT_TAG ${KF5_GIT_TAG})
|
||||
FetchContent_MakeAvailable(kcoreaddons)
|
||||
FetchContent_Declare(
|
||||
kcoreaddons
|
||||
GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git
|
||||
GIT_TAG ${KF_GIT_TAG})
|
||||
FetchContent_MakeAvailable(kcoreaddons)
|
||||
|
||||
FetchContent_Declare(
|
||||
kwidgetsaddons
|
||||
GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git
|
||||
GIT_TAG ${KF5_GIT_TAG})
|
||||
FetchContent_MakeAvailable(kwidgetsaddons)
|
||||
FetchContent_Declare(
|
||||
kwidgetsaddons
|
||||
GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git
|
||||
GIT_TAG ${KF_GIT_TAG})
|
||||
FetchContent_MakeAvailable(kwidgetsaddons)
|
||||
endfunction()
|
||||
qet_make_kf_available()
|
||||
else()
|
||||
find_package(KF5CoreAddons REQUIRED)
|
||||
find_package(KF5WidgetsAddons REQUIRED)
|
||||
find_package(KF${KF_MAJOR_VERSION}CoreAddons REQUIRED)
|
||||
find_package(KF${KF_MAJOR_VERSION}WidgetsAddons REQUIRED)
|
||||
endif()
|
||||
|
||||
set(KF5_PRIVATE_LIBRARIES
|
||||
KF5::WidgetsAddons
|
||||
KF5::CoreAddons
|
||||
set(KF_PRIVATE_LIBRARIES
|
||||
KF${KF_MAJOR_VERSION}::WidgetsAddons
|
||||
KF${KF_MAJOR_VERSION}::CoreAddons
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -23,6 +23,14 @@ set(QAPPLICATION_CLASS QApplication)
|
||||
|
||||
Include(FetchContent)
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/SingleApplication/CMakeLists.txt")
|
||||
# Submodule deja present dans l'arbre source (clone --recursive, tarball
|
||||
# de distro deja peuple via "git submodule update", etc.) : on l'utilise
|
||||
# tel quel, sans acces reseau. Necessaire pour les builds hors-ligne
|
||||
# (pbuilder/sbuild avec FETCHCONTENT_FULLY_DISCONNECTED=ON, Launchpad PPA...).
|
||||
set(FETCHCONTENT_SOURCE_DIR_SINGLEAPPLICATION "${CMAKE_SOURCE_DIR}/SingleApplication")
|
||||
endif()
|
||||
|
||||
FetchContent_Declare(
|
||||
SingleApplication
|
||||
GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git
|
||||
|
||||
@@ -20,15 +20,15 @@ message(" - paths_compilation_installation")
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
# for Linux, BSD, Solaris, Minix
|
||||
set(COMPIL_PREFIX "/usr/local/")
|
||||
set(INSTALL_PREFIX "/usr/local/")
|
||||
set(COMPIL_PREFIX "${CMAKE_INSTALL_PREFIX}/")
|
||||
set(INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/")
|
||||
set(QET_BINARY_PATH "bin/")
|
||||
set(QET_COMMON_COLLECTION_PATH "share/qelectrotech/elements/")
|
||||
set(QET_COMMON_TBT_PATH "share/qelectrotech/titleblocks/")
|
||||
set(QET_LANG_PATH "share/qelectrotech/lang/")
|
||||
set(QET_EXAMPLES_PATH "share/qelectrotech/examples/")
|
||||
set(QET_LICENSE_PATH "doc/qelectrotech/")
|
||||
set(QET_MIME_PACKAGE_PATH "../share/mime/packages/")
|
||||
set(QET_MIME_PACKAGE_PATH "share/mime/packages/")
|
||||
set(QET_DESKTOP_PATH "share/applications/")
|
||||
set(QET_ICONS_PATH "share/icons/hicolor/")
|
||||
set(QET_MAN_PATH "man/")
|
||||
|
||||
@@ -33,6 +33,13 @@ set(QET_COMPONENTS
|
||||
Widgets
|
||||
Concurrent)
|
||||
|
||||
# Note: Pdf is intentionally NOT in this list. Some Qt6 distributions
|
||||
# (notably the Flatpak org.kde.Platform runtime) don't ship the QtPdf
|
||||
# module at all - it lives in the qtwebengine source tree, not Qt6 core.
|
||||
# Requesting it here as a REQUIRED component would fail the whole
|
||||
# configure on those setups. It is probed separately, QUIET and
|
||||
# non-fatal, right after the main find_package() call below.
|
||||
|
||||
set(QET_PRIVATE_LIBRARIES
|
||||
Qt::PrintSupport
|
||||
Qt::Gui
|
||||
@@ -45,6 +52,9 @@ set(QET_PRIVATE_LIBRARIES
|
||||
Qt::Concurrent
|
||||
)
|
||||
|
||||
# Qt::Pdf is appended conditionally in CMakeLists.txt, once we know
|
||||
# whether the module was actually found (see QET_HAS_QTPDF).
|
||||
|
||||
set(QET_RES_FILES
|
||||
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.ui
|
||||
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.ui
|
||||
@@ -91,7 +101,6 @@ set(QET_RES_FILES
|
||||
${QET_DIR}/sources/ui/configsaveloaderwidget.ui
|
||||
${QET_DIR}/sources/ui/diagramcontextwidget.ui
|
||||
${QET_DIR}/sources/ui/diagrameditorhandlersizewidget.ui
|
||||
${QET_DIR}/sources/ui/diagramselection.ui
|
||||
${QET_DIR}/sources/ui/dialogwaiting.ui
|
||||
${QET_DIR}/sources/ui/dynamicelementtextitemeditor.ui
|
||||
${QET_DIR}/sources/ui/elementinfopartwidget.ui
|
||||
@@ -177,6 +186,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/configdialog.h
|
||||
${QET_DIR}/sources/createdxf.cpp
|
||||
${QET_DIR}/sources/createdxf.h
|
||||
${QET_DIR}/sources/dxfpaintdevice.cpp
|
||||
${QET_DIR}/sources/dxfpaintdevice.h
|
||||
${QET_DIR}/sources/diagramcommands.cpp
|
||||
${QET_DIR}/sources/diagramcommands.h
|
||||
${QET_DIR}/sources/diagramcontent.cpp
|
||||
@@ -215,6 +226,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/exportpropertieswidget.h
|
||||
${QET_DIR}/sources/genericpanel.cpp
|
||||
${QET_DIR}/sources/genericpanel.h
|
||||
${QET_DIR}/sources/lastusedstyle.cpp
|
||||
${QET_DIR}/sources/lastusedstyle.h
|
||||
${QET_DIR}/sources/machine_info.cpp
|
||||
${QET_DIR}/sources/machine_info.h
|
||||
${QET_DIR}/sources/main.cpp
|
||||
@@ -296,12 +309,17 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/dataBase/ui/summaryquerywidget.cpp
|
||||
${QET_DIR}/sources/dataBase/ui/summaryquerywidget.h
|
||||
|
||||
${QET_DIR}/sources/autobreakconductor.cpp
|
||||
${QET_DIR}/sources/autobreakconductor.h
|
||||
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddelement.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddelement.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddimage.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddimage.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddshape.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddshape.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddpath.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddpath.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddtext.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddtext.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventinterface.cpp
|
||||
@@ -502,6 +520,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/qetgraphicsitem/qetgraphicsitem.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/qetshapeitem.cpp
|
||||
${QET_DIR}/sources/qetgraphicsitem/qetshapeitem.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/shapetransform.cpp
|
||||
${QET_DIR}/sources/qetgraphicsitem/shapetransform.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/qgraphicsitemutility.cpp
|
||||
${QET_DIR}/sources/qetgraphicsitem/qgraphicsitemutility.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/reportelement.cpp
|
||||
@@ -682,8 +702,6 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/diagrampropertiesdialog.h
|
||||
${QET_DIR}/sources/ui/diagrampropertieseditordockwidget.cpp
|
||||
${QET_DIR}/sources/ui/diagrampropertieseditordockwidget.h
|
||||
${QET_DIR}/sources/ui/diagramselection.cpp
|
||||
${QET_DIR}/sources/ui/diagramselection.h
|
||||
${QET_DIR}/sources/ui/backupdialog.cpp
|
||||
${QET_DIR}/sources/ui/backupdialog.h
|
||||
${QET_DIR}/sources/ui/dialogwaiting.cpp
|
||||
@@ -704,8 +722,12 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/elementpropertieswidget.h
|
||||
${QET_DIR}/sources/ui/formulaassistantdialog.cpp
|
||||
${QET_DIR}/sources/ui/formulaassistantdialog.h
|
||||
${QET_DIR}/sources/ui/imagecropdialog.cpp
|
||||
${QET_DIR}/sources/ui/imagecropdialog.h
|
||||
${QET_DIR}/sources/ui/imagepropertieswidget.cpp
|
||||
${QET_DIR}/sources/ui/imagepropertieswidget.h
|
||||
${QET_DIR}/sources/ui/imagetransparentcolordialog.cpp
|
||||
${QET_DIR}/sources/ui/imagetransparentcolordialog.h
|
||||
${QET_DIR}/sources/ui/importelementdialog.cpp
|
||||
${QET_DIR}/sources/ui/importelementdialog.h
|
||||
${QET_DIR}/sources/ui/importelementtextpatterndialog.cpp
|
||||
@@ -776,6 +798,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/undocommand/setautonumcontextcommand.h
|
||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp
|
||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.h
|
||||
${QET_DIR}/sources/undocommand/promoteshapecommand.cpp
|
||||
${QET_DIR}/sources/undocommand/promoteshapecommand.h
|
||||
${QET_DIR}/sources/undocommand/rotatetextscommand.cpp
|
||||
${QET_DIR}/sources/undocommand/rotatetextscommand.h
|
||||
${QET_DIR}/sources/undocommand/movegraphicsitemcommand.cpp
|
||||
@@ -796,7 +820,7 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/xml/terminalstriplayoutpatternxml.h
|
||||
)
|
||||
|
||||
if(NOT BUILD_WITH_KF5)
|
||||
if(NOT BUILD_WITH_KF)
|
||||
list(APPEND QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/nokde/kautosavefile.cpp
|
||||
${QET_DIR}/sources/ui/nokde/kautosavefile.h
|
||||
@@ -807,6 +831,16 @@ if(NOT BUILD_WITH_KF5)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Qt6-only: PDF page import files
|
||||
if(QT_VERSION_MAJOR GREATER_EQUAL 6)
|
||||
list(APPEND QET_SRC_FILES
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddpdf.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddpdf.h
|
||||
${QET_DIR}/sources/ui/pdfpagesdialog.cpp
|
||||
${QET_DIR}/sources/ui/pdfpagesdialog.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(TS_FILES
|
||||
${QET_DIR}/lang/qet_ar.ts
|
||||
${QET_DIR}/lang/qet_ca.ts
|
||||
|
||||
@@ -28,5 +28,9 @@ add_definitions(-DQET_ALLOW_OVERRIDE_CD_OPTION)
|
||||
# Comment the line below to deactivate the --data-dir option
|
||||
add_definitions(-DQET_ALLOW_OVERRIDE_DD_OPTION)
|
||||
|
||||
#comment the line below to disable the project database export
|
||||
#add_definitions(-DQET_EXPORT_PROJECT_DB) #error Todo
|
||||
# Enable project database export when requested by the build system.
|
||||
option(QET_EXPORT_PROJECT_DB "Enable project database export" OFF)
|
||||
|
||||
if(QET_EXPORT_PROJECT_DB)
|
||||
add_compile_definitions(QET_EXPORT_PROJECT_DB)
|
||||
endif()
|
||||
|
||||
+1
-1
Submodule elements updated: 9cc2f069f7...74bdb77844
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="m18.5 4c-.65424 0-1.202197.418077-1.408203 1h-13.0918v1c3.601 0 6.5 2.899 6.5 6.5 0 1.905514-.822115 3.601777-2.121094 4.787109-.245727-.180239-.548776-.287109-.878906-.287109-.831 0-1.5.669-1.5 1.5 0 .831.669 1.5 1.5 1.5.65424 0 1.202197-.418077 1.408203-1h4.091797v1h3v-3h-3v1h-3.912109c1.479147-1.367931 2.412109-3.317097 2.412109-5.5 0-2.788667-1.510668-5.207469-3.757813-6.5h9.349609c.206006.581923.753963 1 1.408203 1 .831 0 1.5-.669 1.5-1.5 0-.831-.669-1.5-1.5-1.5m0 1c.277 0 .5.223.5.5 0 .277-.223.5-.5.5-.06925 0-.135453-.013828-.195312-.039063-.179579-.075703-.304688-.253188-.304688-.460938 0-.06925.013828-.135453.039062-.195313.075704-.179578.253188-.304688.460938-.304688m-11 13c.277 0 .5.223.5.5 0 .277-.223.5-.5.5-.277 0-.5-.223-.5-.5 0-.277.223-.5.5-.5m6.5 0h1v1h-1v-1z" transform="translate(-.99999-.99999)" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
+1234
-998
File diff suppressed because it is too large
Load Diff
+1226
-999
File diff suppressed because it is too large
Load Diff
+1228
-998
File diff suppressed because it is too large
Load Diff
+1226
-999
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1553
-1320
File diff suppressed because it is too large
Load Diff
+1226
-999
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1231
-1000
File diff suppressed because it is too large
Load Diff
+1226
-999
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1226
-997
File diff suppressed because it is too large
Load Diff
+1227
-996
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1763
-1482
File diff suppressed because it is too large
Load Diff
+1226
-998
File diff suppressed because it is too large
Load Diff
+1224
-999
File diff suppressed because it is too large
Load Diff
+1223
-996
File diff suppressed because it is too large
Load Diff
+1226
-999
File diff suppressed because it is too large
Load Diff
+1226
-998
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1612
-1348
File diff suppressed because it is too large
Load Diff
+1225
-996
File diff suppressed because it is too large
Load Diff
+1228
-999
File diff suppressed because it is too large
Load Diff
+1225
-999
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1613
-1354
File diff suppressed because it is too large
Load Diff
+1227
-996
File diff suppressed because it is too large
Load Diff
+1227
-996
File diff suppressed because it is too large
Load Diff
+1228
-1001
File diff suppressed because it is too large
Load Diff
+1227
-996
File diff suppressed because it is too large
Load Diff
+1229
-996
File diff suppressed because it is too large
Load Diff
+1227
-996
File diff suppressed because it is too large
Load Diff
+1226
-999
File diff suppressed because it is too large
Load Diff
+1224
-999
File diff suppressed because it is too large
Load Diff
+1228
-999
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1490
-1244
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -40,7 +40,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>qelectrotech</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>qelectrotech.icns</string>
|
||||
<string>qelectrotech</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.qelectrotech</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright 2026 The QElectroTech Team
|
||||
# This file is part of QElectroTech.
|
||||
# QElectroTech is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# Suppose que l'environnement a ete prepare via macos_homebrew_setup.sh
|
||||
# (Homebrew, Qt6, cmake, ninja, kf6-kwidgetsaddons, kf6-kcoreaddons).
|
||||
#
|
||||
# Remplace l'ancien misc/MacQetDeploy_arm64.sh (qmake/Qt5) par un
|
||||
# build CMake/Qt6/KF6. La chaine de signature/notarization/DMG est
|
||||
# reprise a l'identique de l'ancien script (eprouvee, ne pas y toucher
|
||||
# sans raison).
|
||||
|
||||
# configuration
|
||||
APPNAME='qelectrotech'
|
||||
BUNDLE=$APPNAME.app
|
||||
IDENTITY="Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)"
|
||||
|
||||
QT_MAJOR="${QT_MAJOR:-6}"
|
||||
BUILD_WITH_KF="${BUILD_WITH_KF:-ON}"
|
||||
BUILD_DIR="build-macos-arm64"
|
||||
|
||||
# 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")
|
||||
cd "${current_dir}/../"
|
||||
current_dir=$(PWD)
|
||||
|
||||
### get system configuration ########################################
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "This script prepares a Qt6/KF6 application bundle for deployment."
|
||||
echo "This script :"
|
||||
echo "\t - update the git depot"
|
||||
echo "\t - configure and build via CMake,"
|
||||
echo "\t - copy over required Qt frameworks,"
|
||||
echo "\t - copy additional files: translations, titleblocks and elements,"
|
||||
echo "\t - notarize the .app, then create a signed DMG."
|
||||
echo
|
||||
|
||||
QT_PREFIX=$(brew --prefix qt 2>/dev/null || true)
|
||||
if [ -z "$QT_PREFIX" ] || [ ! -d "$QT_PREFIX/lib/QtCore.framework" ] ; then
|
||||
echo "ERROR: cannot find Qt6 via Homebrew. Run macos_homebrew_setup.sh first."
|
||||
exit 1
|
||||
fi
|
||||
export CMAKE_PREFIX_PATH="$QT_PREFIX:$CMAKE_PREFIX_PATH"
|
||||
|
||||
### GIT ####################################################
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Run GIT:"
|
||||
git submodule init
|
||||
git submodule update
|
||||
git pull --recurse-submodules
|
||||
git pull
|
||||
|
||||
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')
|
||||
DMG_NAME="${APPNAME}-$VERSION-r$HEAD-arm64.dmg"
|
||||
DMG_PATH="build-aux/mac-osx/$DMG_NAME"
|
||||
|
||||
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]"
|
||||
read userinput
|
||||
if [ "$userinput" == "n" ] ; then
|
||||
echo
|
||||
echo "Process is stopped."
|
||||
echo
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
|
||||
### build with CMake #################################################
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Run CMake configure + build (Qt${QT_MAJOR}, BUILD_WITH_KF=${BUILD_WITH_KF}):"
|
||||
|
||||
if [ -d $BUNDLE ] ; then
|
||||
echo "Removing old bundle..."
|
||||
rm -rf $BUNDLE
|
||||
fi
|
||||
|
||||
if [ -d "$BUILD_DIR" ] ; then
|
||||
echo "Removing old build directory..."
|
||||
rm -rf "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
cmake -S . -B "$BUILD_DIR" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DQT_VERSION_MAJOR=$QT_MAJOR \
|
||||
-DBUILD_WITH_KF=$BUILD_WITH_KF \
|
||||
-DBUILD_KF=OFF \
|
||||
-DQET_EXPORT_PROJECT_DB=ON \
|
||||
-DPACKAGE_TESTS=OFF
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: cmake configure failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
START_TIME=$SECONDS
|
||||
coeur=$(sysctl hw.ncpu | awk '{print $2}')
|
||||
|
||||
cmake --build "$BUILD_DIR" -j$(($coeur + 1))
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
||||
echo
|
||||
echo "cmake build failed - $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
||||
echo
|
||||
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
||||
|
||||
# TODO: confirmer le chemin exact de sortie du .app selon CMakeLists.txt
|
||||
echo "Copying built bundle into place..."
|
||||
cp -R "$BUILD_DIR/qelectrotech.app" "./$BUNDLE"
|
||||
|
||||
if [ ! -d "$BUNDLE" ] ; then
|
||||
echo "ERROR: expected bundle \"$BUNDLE\" not found after cmake build."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
### copy over frameworks ############################################
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Copy Qt libraries and private frameworks:"
|
||||
|
||||
if [ ! -d $BUNDLE ] ; then
|
||||
echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
macdeployqt $BUNDLE
|
||||
|
||||
### install Info.plist and app icon #################################
|
||||
# NOTE: this must run AFTER macdeployqt, not before. macdeployqt
|
||||
# rewrites/regenerates parts of Contents/Resources, and files copied
|
||||
# there beforehand (e.g. the .icns icons) do not reliably survive it
|
||||
# and end up missing from the final bundle, causing the app to show
|
||||
# the generic placeholder icon instead of the real one. Info.plist
|
||||
# itself lives directly in Contents/ and happens to survive either
|
||||
# way, but keep it here too so this whole "final metadata" step stays
|
||||
# in one place, after macdeployqt is done touching the bundle.
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Install Info.plist and app icon:"
|
||||
|
||||
cp -R ${current_dir}/misc/Info.plist $BUNDLE/Contents/
|
||||
cp -R ${current_dir}/ico/mac_icon/*.icns $BUNDLE/Contents/Resources/
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "$BUNDLE/Contents/Info.plist"
|
||||
|
||||
### add missing files ###############################################
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Copy missing files:"
|
||||
|
||||
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/"
|
||||
LANG_DIR="${current_dir}/lang1/"
|
||||
|
||||
if [ -d "${QET_ELMT_DIR}" ]; then
|
||||
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
||||
fi
|
||||
if [ -d "${QET_TBT_DIR}" ]; then
|
||||
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
||||
fi
|
||||
if [ -d "${QET_LANG_DIR}" ]; then
|
||||
mkdir $BUNDLE/Contents/Resources/lang
|
||||
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
|
||||
fi
|
||||
if [ -d "${LANG_DIR}" ]; then
|
||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
||||
fi
|
||||
if [ -d "${QET_EXAMPLES_DIR}" ]; then
|
||||
mkdir $BUNDLE/Contents/Resources/examples
|
||||
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
|
||||
fi
|
||||
if [ -d "${QET_FONTS_DIR}" ]; then
|
||||
mkdir $BUNDLE/Contents/Resources/fonts
|
||||
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
|
||||
fi
|
||||
if [ -d "${QET_LICENSES_DIR}" ]; then
|
||||
cp -R -L ${QET_LICENSES_DIR} $BUNDLE/Contents/Resources/licenses
|
||||
fi
|
||||
|
||||
### 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 "Code signing (dylibs -> frameworks -> plugins -> bundle):"
|
||||
|
||||
echo "-- Signing dylibs in Frameworks/..."
|
||||
find "$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
echo "-- Signing .framework bundles..."
|
||||
find "$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
|
||||
done
|
||||
|
||||
echo "-- Signing plugins..."
|
||||
find "$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
echo "-- Signing dylibs in MacOS/..."
|
||||
find "$BUNDLE/Contents/MacOS" -name "*.dylib" | while read lib; do
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
|
||||
echo "-- Signing main executable..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
"$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
|
||||
echo "-- Signing bundle..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE"
|
||||
|
||||
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 "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"
|
||||
fi
|
||||
rm -f "$NOTARIZE_ZIP"
|
||||
|
||||
### Staple the .app #################################################
|
||||
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"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: stapling .app failed."
|
||||
exit 1
|
||||
fi
|
||||
xcrun stapler validate -v "$BUNDLE"
|
||||
spctl -a -vv "$BUNDLE"
|
||||
echo ".app stapled OK."
|
||||
else
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
fi
|
||||
|
||||
### Create staging folder with Applications symlink #################
|
||||
echo
|
||||
echo "______________________________________________________________"
|
||||
echo "Preparing DMG staging folder:"
|
||||
rm -rf "$STAGING"
|
||||
mkdir -p "$STAGING"
|
||||
cp -R "$BUNDLE" "$STAGING/"
|
||||
ln -s /Applications "$STAGING/Applications"
|
||||
|
||||
### Create writable DMG (UDRW) ######################################
|
||||
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"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: hdiutil failed to create writable DMG."
|
||||
rm -rf "$STAGING"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
"$MOUNT_POINT/$BUNDLE"
|
||||
|
||||
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."
|
||||
|
||||
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
|
||||
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
|
||||
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."
|
||||
spctl -a -vv "$DMG_PATH"
|
||||
else
|
||||
echo -e "\033[1;33mExit.\033[m"
|
||||
fi
|
||||
|
||||
### Clean up bundle #################################################
|
||||
rm -rf "$BUNDLE"
|
||||
|
||||
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
|
||||
@@ -140,6 +140,7 @@
|
||||
<file>ico/22x22/go-up.png</file>
|
||||
<file>ico/22x22/hotspot.png</file>
|
||||
<file>ico/22x22/insert-image.png</file>
|
||||
<file>ico/22x22/pdf-import.png</file>
|
||||
<file>ico/22x22/label.png</file>
|
||||
<file>ico/22x22/landscape.png</file>
|
||||
<file>ico/22x22/line.png</file>
|
||||
@@ -547,6 +548,7 @@
|
||||
<file>ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-element.svgz</file>
|
||||
<file>ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-project.svgz</file>
|
||||
<file>ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-titleblock.svgz</file>
|
||||
<file>ico/breeze-icons/scalable/apps/hidef/draw-bezier-curves.svg</file>
|
||||
<file>ico/16x16/object-group.png</file>
|
||||
<file>ico/mac_icon/elmt.icns</file>
|
||||
<file>ico/mac_icon/qelectrotech.icns</file>
|
||||
|
||||
@@ -38,6 +38,20 @@ ElementsCollectionModel::ElementsCollectionModel(QObject *parent) :
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementsCollectionModel::~ElementsCollectionModel
|
||||
Destructor. loadCollections() may still have background threads
|
||||
(via QtConcurrent::map()) running setUpData() on this model's items
|
||||
when the model is destroyed (e.g. the user cancels the dialog before
|
||||
loading finishes). Wait for them here so QStandardItemModel's
|
||||
destructor doesn't free items out from under them, which used to
|
||||
crash the whole application (bugtracker #291).
|
||||
*/
|
||||
ElementsCollectionModel::~ElementsCollectionModel()
|
||||
{
|
||||
m_future.waitForFinished();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementsCollectionModel::data
|
||||
Reimplemented from QStandardItemModel
|
||||
@@ -49,6 +63,7 @@ QVariant ElementsCollectionModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == Qt::DecorationRole) {
|
||||
QStandardItem *item = itemFromIndex(index);
|
||||
if (!item) return QStandardItemModel::data(index, role);
|
||||
|
||||
if (item->type() == FileElementCollectionItem::Type)
|
||||
static_cast<FileElementCollectionItem*>(item)->setUpIcon();
|
||||
@@ -316,7 +331,6 @@ void ElementsCollectionModel::loadMacrosCollection()
|
||||
void ElementsCollectionModel::addMacrosCollection(bool set_data)
|
||||
{
|
||||
QString macrosPath = QETApp::userMacrosDir();
|
||||
qDebug() << "=== MAKRO PFAD CHECK ===" << macrosPath;
|
||||
if (macrosPath.endsWith("/")) {
|
||||
macrosPath.remove(macrosPath.length() - 1, 1);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class ElementsCollectionModel : public QStandardItemModel
|
||||
|
||||
public:
|
||||
ElementsCollectionModel(QObject *parent = Q_NULLPTR);
|
||||
~ElementsCollectionModel() override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
|
||||
@@ -949,14 +949,7 @@ void ElementsCollectionWidget::search()
|
||||
}
|
||||
|
||||
hideCollection(true);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
|
||||
const QStringList text_list = text.split("+", QString::SkipEmptyParts);
|
||||
#else
|
||||
#if TODO_LIST
|
||||
#pragma message("@TODO remove code for QT 5.14 or later")
|
||||
#endif
|
||||
const QStringList text_list = text.split("+", Qt::SkipEmptyParts);
|
||||
#endif
|
||||
QModelIndexList match_index;
|
||||
for (QString txt : text_list) {
|
||||
match_index << m_model->match(m_showed_index.isValid()
|
||||
|
||||
@@ -143,11 +143,7 @@ void ElementsTreeView::startElementDrag(const ElementsLocation &location)
|
||||
QString file_name = (last_slash != -1) ? path.mid(last_slash + 1) : path;
|
||||
|
||||
if (!dir_path.isEmpty()) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList parts = dir_path.split('/', QString::SkipEmptyParts);
|
||||
#else
|
||||
QStringList parts = dir_path.split('/', Qt::SkipEmptyParts);
|
||||
#endif
|
||||
QString current_path = "";
|
||||
for (const QString &part : parts) {
|
||||
QString parent_path = current_path;
|
||||
|
||||
@@ -430,7 +430,7 @@ void FileElementCollectionItem::setUpData()
|
||||
*/
|
||||
void FileElementCollectionItem::setUpIcon()
|
||||
{
|
||||
// Must return unconditionally once an icon is set: setIcon() calls
|
||||
// Must return unconditionally once setUpIcon has run: setIcon() calls
|
||||
// setData(), which emits dataChanged() regardless of whether the new
|
||||
// icon differs from the old one (QIcon has no meaningful equality).
|
||||
// QTreeView responds to dataChanged() by recomputing the row's size
|
||||
@@ -438,13 +438,18 @@ void FileElementCollectionItem::setUpIcon()
|
||||
// guard, any repeated setIcon() here recurses until the stack
|
||||
// overflows. Confirmed by crash report on PR #633.
|
||||
//
|
||||
// We use a dedicated bool instead of icon().isNull() because
|
||||
// some items intentionally keep a null icon (e.g. .qetmak),
|
||||
// which would bypass a null-based guard and still recurse.
|
||||
//
|
||||
// This item's m_qet_directory_unreadable is already final by the time
|
||||
// this can run at all: ElementsCollectionModel only attaches itself
|
||||
// to the tree view (making data() reachable) from loadingFinished(),
|
||||
// which fires after the QtConcurrent::map over every item -- this one
|
||||
// included -- has completed. So there is no race to work around here.
|
||||
if (!icon().isNull())
|
||||
if (m_icon_initialized)
|
||||
return;
|
||||
m_icon_initialized = true;
|
||||
|
||||
if (isCollectionRoot()) {
|
||||
QString macrosPath = QETApp::userMacrosDir();
|
||||
|
||||
@@ -69,6 +69,7 @@ class FileElementCollectionItem : public ElementCollectionItem
|
||||
/// rather than acted on in localName(), because setUpData() resets
|
||||
/// the tooltip afterwards and would otherwise discard it.
|
||||
bool m_qet_directory_unreadable = false;
|
||||
bool m_icon_initialized = false;
|
||||
};
|
||||
|
||||
#endif // FILEELEMENTCOLLECTIONITEM2_H
|
||||
|
||||
@@ -46,6 +46,18 @@ class PropertiesEditorDialog : public QDialog
|
||||
PropertiesEditorDialog(T editor, QWidget *parent = nullptr) :
|
||||
QDialog (parent)
|
||||
{
|
||||
// Without this, a QWidget-modal QDialog with a parent falls
|
||||
// back to macOS's automatic sheet presentation, which some
|
||||
// Qt/Cocoa versions render stuck centered on screen and
|
||||
// non-draggable rather than as a proper attached sheet
|
||||
// (bugtracker #275). Other dialogs in this codebase already
|
||||
// opt in explicitly (see ElementDialog, DiagramPropertiesDialog)
|
||||
// -- this one was missing it.
|
||||
setWindowModality(Qt::WindowModal);
|
||||
#ifdef Q_OS_MACOS
|
||||
setWindowFlags(Qt::Sheet);
|
||||
#endif
|
||||
|
||||
//Set dialog title
|
||||
setWindowTitle(editor->title());
|
||||
// Reparent the editor,
|
||||
|
||||
@@ -1020,6 +1020,7 @@ void SearchAndReplaceWidget::on_m_tree_widget_currentItemChanged(
|
||||
{
|
||||
m_highlighted_element = elmt;
|
||||
elmt.data()->setHighlighted(true);
|
||||
elmt.data()->ensureVisible();
|
||||
}
|
||||
}
|
||||
else if (m_text_hash.contains(current))
|
||||
@@ -1029,6 +1030,7 @@ void SearchAndReplaceWidget::on_m_tree_widget_currentItemChanged(
|
||||
{
|
||||
text.data()->setSelected(true);
|
||||
m_last_selected = text;
|
||||
text.data()->ensureVisible();
|
||||
}
|
||||
}
|
||||
else if (m_conductor_hash.contains(current))
|
||||
@@ -1038,6 +1040,7 @@ void SearchAndReplaceWidget::on_m_tree_widget_currentItemChanged(
|
||||
{
|
||||
cond.data()->setSelected(true);
|
||||
m_last_selected = cond;
|
||||
cond.data()->ensureVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define TERMINALSTRIPDRAWER_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QUuid>
|
||||
|
||||
#include "properties/terminalstriplayoutpattern.h"
|
||||
|
||||
|
||||
@@ -130,12 +130,7 @@ bool PhysicalTerminal::setLevelOf(const QSharedPointer<RealTerminal> &terminal,
|
||||
const int i = m_real_terminal.indexOf(terminal);
|
||||
if (i >= 0)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
|
||||
m_real_terminal.swapItemsAt(i, std::min<int>(level, static_cast<int>(m_real_terminal.size())-1));
|
||||
#else
|
||||
auto j = std::min(level, m_real_terminal.size()-1);
|
||||
std::swap(m_real_terminal.begin()[i], m_real_terminal.begin()[j]);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -64,11 +64,8 @@ bool TerminalStripData::fromXml(const QDomElement &xml_element)
|
||||
"due to wrong tag name. Expected " << this->xmlTagName() << " used " << xml_element.tagName();
|
||||
return false;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
|
||||
m_uuid = QUuid::fromString(xml_element.attribute(QStringLiteral("uuid")));
|
||||
#else
|
||||
m_uuid = QUuid(xml_element.attribute(QStringLiteral("uuid")));
|
||||
#endif
|
||||
|
||||
for (auto &xml_info :
|
||||
QETXML::findInDomElement(xml_element.firstChildElement(QStringLiteral("informations")),
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "../terminalstrip.h"
|
||||
#include "../../qetinformation.h"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
TerminalStripTreeDockWidget::TerminalStripTreeDockWidget(QETProject *project, QWidget *parent) :
|
||||
QDockWidget(parent),
|
||||
ui(new Ui::TerminalStripTreeDockWidget)
|
||||
@@ -35,11 +37,7 @@ TerminalStripTreeDockWidget::TerminalStripTreeDockWidget(QETProject *project, QW
|
||||
ui->setupUi(this);
|
||||
setProject(project);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
|
||||
ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex());
|
||||
#else
|
||||
ui->m_tree_view->expandAll();
|
||||
#endif
|
||||
}
|
||||
|
||||
TerminalStripTreeDockWidget::~TerminalStripTreeDockWidget()
|
||||
@@ -93,11 +91,7 @@ void TerminalStripTreeDockWidget::reload()
|
||||
|
||||
buildTree();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
|
||||
ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex());
|
||||
#else
|
||||
ui->m_tree_view->expandAll();
|
||||
#endif
|
||||
|
||||
//Reselect the tree widget item of the current edited strip
|
||||
auto item = m_item_strip_H.key(current_);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QPointer>
|
||||
#include <QHash>
|
||||
|
||||
class QETProject;
|
||||
class QTreeWidgetItem;
|
||||
|
||||
@@ -220,6 +220,24 @@ void NumerotationContext::replaceValue(int index, QString content) {
|
||||
content_[index] = type + "|" + value + "|" + increase + "|" + initvalue + "|" + modulus + "|" + format;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief NumerotationContext::replaceIncrease
|
||||
Change how much this part advances per step, leaving its current value,
|
||||
initial value, modulus and format untouched. Sibling to replaceValue(),
|
||||
which deliberately never touches this field.
|
||||
@param index of NC item
|
||||
@param increase new increase for that item
|
||||
*/
|
||||
void NumerotationContext::replaceIncrease(int index, int increase) {
|
||||
QStringList strl = content_[index].split("|");
|
||||
QString type = strl.at(0);
|
||||
QString value = strl.at(1);
|
||||
QString initvalue = strl.at(3);
|
||||
QString modulus = strl.size() > 4 ? strl.at(4) : QStringLiteral("0");
|
||||
QString format = strl.size() > 5 ? strl.at(5) : QString();
|
||||
content_[index] = type + "|" + value + "|" + QString::number(increase) + "|" + initvalue + "|" + modulus + "|" + format;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief NumerotationContext::formatOf
|
||||
@param item : a context item as returned by itemAt()
|
||||
@@ -230,3 +248,30 @@ QString NumerotationContext::formatOf(const QStringList &item)
|
||||
{
|
||||
return item.size() > 5 ? item.at(5) : QString();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief NumerotationContext::formatValue
|
||||
@param item : a context item as returned by itemAt()
|
||||
@return the part's value, zero-padded exactly as
|
||||
autonum::setSequentialToList() pads it when composing a real label: an
|
||||
explicit format mask wins, then "ten"/"hundred" parts get their
|
||||
implicit 2/3-digit width, "alpha" is used as-is, everything else is a
|
||||
plain number. Kept in step with that function by hand since the two
|
||||
cannot share code without exposing an assignvariables.cpp-local helper.
|
||||
*/
|
||||
QString NumerotationContext::formatValue(const QStringList &item)
|
||||
{
|
||||
const QString &type = item.at(0);
|
||||
const QString &value = item.at(1);
|
||||
if (type == QLatin1String("alpha"))
|
||||
return value;
|
||||
|
||||
const QString mask = formatOf(item);
|
||||
if (!mask.isEmpty())
|
||||
return QString("%1").arg(value.toInt(), mask.length(), 10, QChar('0'));
|
||||
if (type == QLatin1String("ten") || type == QLatin1String("tenfolio"))
|
||||
return QString("%1").arg(value.toInt(), 2, 10, QChar('0'));
|
||||
if (type == QLatin1String("hundred") || type == QLatin1String("hundredfolio"))
|
||||
return QString("%1").arg(value.toInt(), 3, 10, QChar('0'));
|
||||
return QString::number(value.toInt());
|
||||
}
|
||||
|
||||
@@ -54,6 +54,11 @@ class NumerotationContext
|
||||
QDomElement toXml(QDomDocument &, const QString&);
|
||||
void fromXml(QDomElement &);
|
||||
void replaceValue(int, QString);
|
||||
void replaceIncrease(int, int);
|
||||
/// Zero-pad a part's value the same way the real numbering engine
|
||||
/// does (autonum::setSequentialToList in assignvariables.cpp), so a
|
||||
/// UI preview of a part's value matches what actually gets rendered.
|
||||
static QString formatValue(const QStringList &item);
|
||||
|
||||
private:
|
||||
QStringList content_;
|
||||
|
||||
@@ -24,10 +24,14 @@
|
||||
#include "../../titleblockproperties.h"
|
||||
#include "../../ui/projectpropertiesdialog.h"
|
||||
#include "../numerotationcontext.h"
|
||||
#include "../numerotationcontextcommands.h"
|
||||
#include "ui_autonumberingdockwidget.h"
|
||||
#include "../../undocommand/changetitleblockcommand.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSpinBox>
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::AutoNumberingDockWidget
|
||||
@@ -64,6 +68,29 @@ void AutoNumberingDockWidget::clear()
|
||||
ui->m_conductor_value_le->clear();
|
||||
ui->m_element_value_le->clear();
|
||||
ui->m_folio_value_le->clear();
|
||||
ui->m_conductor_next_le->clear();
|
||||
ui->m_element_next_le->clear();
|
||||
ui->m_folio_next_le->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::rowFor
|
||||
@return the combo/value/increase/next widgets that make up category's row.
|
||||
*/
|
||||
AutoNumberingDockWidget::Row AutoNumberingDockWidget::rowFor(AutoNumCategory category) const
|
||||
{
|
||||
switch (category) {
|
||||
case AutoNumCategory::Conductor:
|
||||
return {ui->m_conductor_cb, ui->m_conductor_value_le,
|
||||
ui->m_conductor_increase_sb, ui->m_conductor_next_le};
|
||||
case AutoNumCategory::Element:
|
||||
return {ui->m_element_cb, ui->m_element_value_le,
|
||||
ui->m_element_increase_sb, ui->m_element_next_le};
|
||||
case AutoNumCategory::Folio:
|
||||
return {ui->m_folio_cb, ui->m_folio_value_le,
|
||||
ui->m_folio_increase_sb, ui->m_folio_next_le};
|
||||
}
|
||||
return {nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
|
||||
void AutoNumberingDockWidget::projectClosed()
|
||||
@@ -86,33 +113,19 @@ void AutoNumberingDockWidget::setProject(QETProject *project,
|
||||
//Disconnect previous project
|
||||
if (m_project && m_project_view)
|
||||
{
|
||||
//Conductor Signals
|
||||
disconnect(m_project, SIGNAL(conductorAutoNumChanged()),
|
||||
this,SLOT(conductorAutoNumChanged()));
|
||||
disconnect (m_project,SIGNAL(conductorAutoNumRemoved()),
|
||||
this,SLOT(conductorAutoNumChanged()));
|
||||
disconnect (m_project,SIGNAL(conductorAutoNumAdded()),
|
||||
this,SLOT(conductorAutoNumChanged()));
|
||||
disconnect(m_project_view,SIGNAL(diagramActivated(DiagramView*)),
|
||||
this,SLOT(setConductorActive(DiagramView*)));
|
||||
|
||||
//Element Signals
|
||||
disconnect (m_project,SIGNAL(elementAutoNumRemoved(QString)),
|
||||
this,SLOT(elementAutoNumChanged()));
|
||||
disconnect (m_project,SIGNAL(elementAutoNumAdded(QString)),
|
||||
this,SLOT(elementAutoNumChanged()));
|
||||
disconnect(m_project, &QETProject::conductorAutoNumChanged, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
|
||||
disconnect(m_project, &QETProject::conductorAutoNumRemoved, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
|
||||
disconnect(m_project, &QETProject::conductorAutoNumAdded, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
|
||||
disconnect(m_project_view, &ProjectView::diagramActivated, this, &AutoNumberingDockWidget::setConductorActive);
|
||||
|
||||
//Element Signals
|
||||
disconnect(m_project, &QETProject::elementAutoNumRemoved, this, &AutoNumberingDockWidget::elementAutoNumChanged);
|
||||
disconnect(m_project, &QETProject::elementAutoNumAdded, this, &AutoNumberingDockWidget::elementAutoNumChanged);
|
||||
|
||||
//Folio Signals
|
||||
disconnect (m_project,SIGNAL(folioAutoNumRemoved()),
|
||||
this,SLOT(folioAutoNumChanged()));
|
||||
disconnect (m_project,SIGNAL(folioAutoNumAdded()),
|
||||
this,SLOT(folioAutoNumChanged()));
|
||||
disconnect (this,
|
||||
SIGNAL(folioAutoNumChanged(QString)),
|
||||
&m_project_view->currentDiagram()->diagram()->border_and_titleblock,
|
||||
SLOT (slot_setAutoPageNum(QString)));
|
||||
disconnect(m_project, SIGNAL(defaultTitleBlockPropertiesChanged()),
|
||||
this,SLOT(setActive()));
|
||||
disconnect(m_project, &QETProject::folioAutoNumRemoved, this, qOverload<>(&AutoNumberingDockWidget::folioAutoNumChanged));
|
||||
disconnect(m_project, &QETProject::folioAutoNumAdded, this, qOverload<>(&AutoNumberingDockWidget::folioAutoNumChanged));
|
||||
disconnect(m_project, &QETProject::defaultTitleBlockPropertiesChanged, this, &AutoNumberingDockWidget::setActive);
|
||||
|
||||
//Conductor, Element and Folio Signals
|
||||
disconnect(m_project, &QETProject::autoNumContextUpdated,
|
||||
@@ -125,33 +138,19 @@ void AutoNumberingDockWidget::setProject(QETProject *project,
|
||||
m_project_view = projectview;
|
||||
this->setEnabled(true);
|
||||
|
||||
//Conductor Signals
|
||||
connect(m_project, SIGNAL(conductorAutoNumChanged()),
|
||||
this,SLOT(conductorAutoNumChanged()));
|
||||
connect(m_project,SIGNAL(conductorAutoNumRemoved()),
|
||||
this,SLOT(conductorAutoNumChanged()));
|
||||
connect(m_project,SIGNAL(conductorAutoNumAdded()),
|
||||
this,SLOT(conductorAutoNumChanged()));
|
||||
connect(m_project_view,SIGNAL(diagramActivated(DiagramView*)),
|
||||
this,SLOT(setConductorActive(DiagramView*)));
|
||||
connect(m_project, &QETProject::conductorAutoNumChanged, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
|
||||
connect(m_project, &QETProject::conductorAutoNumRemoved, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
|
||||
connect(m_project, &QETProject::conductorAutoNumAdded, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
|
||||
connect(m_project_view, &ProjectView::diagramActivated, this, &AutoNumberingDockWidget::setConductorActive);
|
||||
|
||||
//Element Signals
|
||||
connect (m_project,SIGNAL(elementAutoNumRemoved(QString)),
|
||||
this,SLOT(elementAutoNumChanged()));
|
||||
connect (m_project,SIGNAL(elementAutoNumAdded(QString)),
|
||||
this,SLOT(elementAutoNumChanged()));
|
||||
//Element Signals
|
||||
connect(m_project, &QETProject::elementAutoNumRemoved, this, &AutoNumberingDockWidget::elementAutoNumChanged);
|
||||
connect(m_project, &QETProject::elementAutoNumAdded, this, &AutoNumberingDockWidget::elementAutoNumChanged);
|
||||
|
||||
//Folio Signals
|
||||
connect (m_project,SIGNAL(folioAutoNumRemoved()),
|
||||
this,SLOT(folioAutoNumChanged()));
|
||||
connect (m_project,SIGNAL(folioAutoNumAdded()),
|
||||
this,SLOT(folioAutoNumChanged()));
|
||||
connect (this,
|
||||
SIGNAL(folioAutoNumChanged(QString)),
|
||||
&m_project_view->currentDiagram()->diagram()->border_and_titleblock,
|
||||
SLOT (slot_setAutoPageNum(QString)));
|
||||
connect(m_project, SIGNAL(defaultTitleBlockPropertiesChanged()),
|
||||
this,SLOT(setActive()));
|
||||
connect(m_project, &QETProject::folioAutoNumRemoved, this, qOverload<>(&AutoNumberingDockWidget::folioAutoNumChanged));
|
||||
connect(m_project, &QETProject::folioAutoNumAdded, this, qOverload<>(&AutoNumberingDockWidget::folioAutoNumChanged));
|
||||
connect(m_project, &QETProject::defaultTitleBlockPropertiesChanged, this, &AutoNumberingDockWidget::setActive);
|
||||
|
||||
//Conductor, Element and Folio Signals
|
||||
connect(m_project, &QETProject::autoNumContextUpdated,
|
||||
@@ -201,9 +200,9 @@ void AutoNumberingDockWidget::setContext()
|
||||
|
||||
//The combo boxes have just been repopulated, so the value fields next
|
||||
//to them are showing whatever the previous project left there.
|
||||
refreshValueField(ui->m_conductor_cb, ui->m_conductor_value_le, AutoNumCategory::Conductor);
|
||||
refreshValueField(ui->m_element_cb, ui->m_element_value_le, AutoNumCategory::Element);
|
||||
refreshValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
|
||||
refreshRow(AutoNumCategory::Conductor);
|
||||
refreshRow(AutoNumCategory::Element);
|
||||
refreshRow(AutoNumCategory::Folio);
|
||||
|
||||
this->setActive();
|
||||
}
|
||||
@@ -279,7 +278,7 @@ void AutoNumberingDockWidget::on_m_conductor_cb_activated(int)
|
||||
m_project->setCurrentConductorAutoNum(current_autonum);
|
||||
m_project_view->currentDiagram()->diagram()->setConductorsAutonumName(current_autonum);
|
||||
m_project_view->currentDiagram()->diagram()->loadCndFolioSeq();
|
||||
refreshValueField(ui->m_conductor_cb, ui->m_conductor_value_le, AutoNumCategory::Conductor);
|
||||
refreshRow(AutoNumCategory::Conductor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,7 +307,7 @@ void AutoNumberingDockWidget::on_m_element_cb_activated(int)
|
||||
{
|
||||
m_project->setCurrrentElementAutonum(ui->m_element_cb->currentText());
|
||||
m_project_view->currentDiagram()->diagram()->loadElmtFolioSeq();
|
||||
refreshValueField(ui->m_element_cb, ui->m_element_value_le, AutoNumCategory::Element);
|
||||
refreshRow(AutoNumCategory::Element);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,8 +344,17 @@ void AutoNumberingDockWidget::on_m_folio_cb_activated(int) {
|
||||
ip.folio = "%id/%total";
|
||||
m_project->setDefaultTitleBlockProperties(ip);
|
||||
}
|
||||
emit(folioAutoNumChanged(current_autonum));
|
||||
refreshValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
|
||||
|
||||
if (m_project_view && m_project_view->currentDiagram()) {
|
||||
Diagram *diagram = m_project_view->currentDiagram()->diagram();
|
||||
TitleBlockProperties old_properties = diagram->border_and_titleblock.exportTitleBlock();
|
||||
TitleBlockProperties new_properties = old_properties;
|
||||
new_properties.auto_page_num = ip.auto_page_num;
|
||||
new_properties.folio = ip.folio;
|
||||
if (new_properties != old_properties)
|
||||
diagram->undoStack().push(new ChangeTitleBlockCommand(diagram, old_properties, new_properties));
|
||||
}
|
||||
refreshRow(AutoNumCategory::Folio);
|
||||
}
|
||||
|
||||
void AutoNumberingDockWidget::on_m_configure_pb_clicked()
|
||||
@@ -389,6 +397,21 @@ void AutoNumberingDockWidget::on_m_folio_value_le_editingFinished()
|
||||
applyValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
|
||||
}
|
||||
|
||||
void AutoNumberingDockWidget::on_m_conductor_increase_sb_valueChanged(int)
|
||||
{
|
||||
applyIncreaseField(ui->m_conductor_cb, ui->m_conductor_increase_sb, AutoNumCategory::Conductor);
|
||||
}
|
||||
|
||||
void AutoNumberingDockWidget::on_m_element_increase_sb_valueChanged(int)
|
||||
{
|
||||
applyIncreaseField(ui->m_element_cb, ui->m_element_increase_sb, AutoNumCategory::Element);
|
||||
}
|
||||
|
||||
void AutoNumberingDockWidget::on_m_folio_increase_sb_valueChanged(int)
|
||||
{
|
||||
applyIncreaseField(ui->m_folio_cb, ui->m_folio_increase_sb, AutoNumCategory::Folio);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::contextFor
|
||||
@return the numerotation context named by combo_box, for category
|
||||
@@ -455,17 +478,21 @@ int AutoNumberingDockWidget::counterIndex(const NumerotationContext &context)
|
||||
*/
|
||||
void AutoNumberingDockWidget::refreshValueFields()
|
||||
{
|
||||
//Leave alone a field the user is typing in: numbering an element
|
||||
//refreshes all three, and overwriting a half-typed value under the
|
||||
//cursor is worse than showing it a moment out of date. Only this
|
||||
//automatic path skips; an explicit refresh after a reset or an edit
|
||||
//still writes, so the field always ends up canonical.
|
||||
if (!ui->m_conductor_value_le->hasFocus())
|
||||
refreshValueField(ui->m_conductor_cb, ui->m_conductor_value_le, AutoNumCategory::Conductor);
|
||||
if (!ui->m_element_value_le->hasFocus())
|
||||
refreshValueField(ui->m_element_cb, ui->m_element_value_le, AutoNumCategory::Element);
|
||||
if (!ui->m_folio_value_le->hasFocus())
|
||||
refreshValueField(ui->m_folio_cb, ui->m_folio_value_le, AutoNumCategory::Folio);
|
||||
//Leave alone a row the user is typing in: numbering an element
|
||||
//refreshes all three rows, and overwriting a half-typed value or
|
||||
//increment under the cursor is worse than showing it a moment out
|
||||
//of date. Only this automatic path skips; an explicit refresh after
|
||||
//a reset or an edit still writes, so the row always ends up
|
||||
//canonical. The next-value preview has no such guard: it is
|
||||
//read-only, so there is nothing a refresh could clobber.
|
||||
for (AutoNumCategory category : {AutoNumCategory::Conductor,
|
||||
AutoNumCategory::Element,
|
||||
AutoNumCategory::Folio})
|
||||
{
|
||||
const Row row = rowFor(category);
|
||||
if (!row.value->hasFocus() && !row.increase->hasFocus())
|
||||
refreshRow(category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,13 +534,114 @@ void AutoNumberingDockWidget::applyValueField(QComboBox *combo_box, QLineEdit *l
|
||||
const QString typed = line_edit->text();
|
||||
if (typed.isEmpty() || typed == context.itemAt(index).at(1))
|
||||
{
|
||||
refreshValueField(combo_box, line_edit, category);
|
||||
refreshRow(category);
|
||||
return;
|
||||
}
|
||||
|
||||
context.replaceValue(index, typed);
|
||||
storeContext(combo_box, category, context);
|
||||
refreshValueField(combo_box, line_edit, category);
|
||||
refreshRow(category);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::refreshIncreaseField
|
||||
Show the counter's current step size (bug #331: previously only
|
||||
reachable from the full configuration dialog, via "Configurer").
|
||||
*/
|
||||
void AutoNumberingDockWidget::refreshIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category)
|
||||
{
|
||||
//QSpinBox::setValue() emits valueChanged() even when called
|
||||
//programmatically. Without blocking it, this refresh would
|
||||
//immediately re-trigger on_..._increase_sb_valueChanged() ->
|
||||
//applyIncreaseField() -> storeContext() -> the project's
|
||||
//autoNumContextUpdated signal -> refreshValueFields() -> back here.
|
||||
const QSignalBlocker blocker(increase_sb);
|
||||
if (!m_project || combo_box->currentText().isEmpty())
|
||||
{
|
||||
increase_sb->setEnabled(false);
|
||||
increase_sb->setValue(increase_sb->minimum());
|
||||
return;
|
||||
}
|
||||
|
||||
const NumerotationContext context = contextFor(combo_box, category);
|
||||
const int index = counterIndex(context);
|
||||
increase_sb->setEnabled(index >= 0);
|
||||
increase_sb->setValue(index >= 0 ? context.itemAt(index).at(2).toInt()
|
||||
: increase_sb->minimum());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::applyIncreaseField
|
||||
Write the spin box's step size to the counter it displays (bug #331).
|
||||
*/
|
||||
void AutoNumberingDockWidget::applyIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category)
|
||||
{
|
||||
if (!m_project || combo_box->currentText().isEmpty())
|
||||
return;
|
||||
|
||||
NumerotationContext context = contextFor(combo_box, category);
|
||||
const int index = counterIndex(context);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
if (increase_sb->value() == context.itemAt(index).at(2).toInt())
|
||||
return;
|
||||
|
||||
context.replaceIncrease(index, increase_sb->value());
|
||||
storeContext(combo_box, category, context);
|
||||
refreshRow(category);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::refreshNextField
|
||||
Show what this counter will read after one more step (bug #331: "visualiser
|
||||
la prochaine numérotation qui sera appliquée"). Advances a copy of the
|
||||
whole context through NumerotationContextCommands -- the same engine the
|
||||
Suivant button in the full configuration dialog uses to step a context --
|
||||
so wrap-and-carry into this part from a following part, or out of it into
|
||||
a preceding one, comes out identical to what will actually happen when the
|
||||
number is next consumed.
|
||||
*/
|
||||
void AutoNumberingDockWidget::refreshNextField(QComboBox *combo_box, QLineEdit *next_edit, AutoNumCategory category)
|
||||
{
|
||||
if (!m_project || combo_box->currentText().isEmpty())
|
||||
{
|
||||
next_edit->clear();
|
||||
next_edit->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const NumerotationContext context = contextFor(combo_box, category);
|
||||
const int index = counterIndex(context);
|
||||
if (index < 0)
|
||||
{
|
||||
next_edit->clear();
|
||||
next_edit->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Diagram *diagram = (m_project_view && m_project_view->currentDiagram())
|
||||
? m_project_view->currentDiagram()->diagram()
|
||||
: nullptr;
|
||||
NumerotationContextCommands ncc(context, diagram);
|
||||
const NumerotationContext next_context = ncc.next();
|
||||
|
||||
next_edit->setEnabled(true);
|
||||
next_edit->setText(NumerotationContext::formatValue(next_context.itemAt(index)));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AutoNumberingDockWidget::refreshRow
|
||||
Refresh a category's value, increment and next-value preview together --
|
||||
every call site that used to refresh just the value field needs the
|
||||
other two kept in step with it as well.
|
||||
*/
|
||||
void AutoNumberingDockWidget::refreshRow(AutoNumCategory category)
|
||||
{
|
||||
const Row row = rowFor(category);
|
||||
refreshValueField(row.combo, row.value, category);
|
||||
refreshIncreaseField(row.combo, row.increase, category);
|
||||
refreshNextField(row.combo, row.next, category);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,10 +685,5 @@ void AutoNumberingDockWidget::resetAutoNum(QComboBox *combo_box, AutoNumCategory
|
||||
}
|
||||
|
||||
storeContext(combo_box, category, context);
|
||||
|
||||
switch (category) {
|
||||
case AutoNumCategory::Conductor: refreshValueField(combo_box, ui->m_conductor_value_le, category); break;
|
||||
case AutoNumCategory::Element: refreshValueField(combo_box, ui->m_element_value_le, category); break;
|
||||
case AutoNumCategory::Folio: refreshValueField(combo_box, ui->m_folio_value_le, category); break;
|
||||
}
|
||||
refreshRow(category);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
class QSpinBox;
|
||||
|
||||
namespace Ui {
|
||||
class AutoNumberingDockWidget;
|
||||
@@ -66,12 +67,27 @@ class AutoNumberingDockWidget : public QDockWidget
|
||||
void on_m_element_value_le_editingFinished();
|
||||
void on_m_folio_value_le_editingFinished();
|
||||
|
||||
void on_m_conductor_increase_sb_valueChanged(int);
|
||||
void on_m_element_increase_sb_valueChanged(int);
|
||||
void on_m_folio_increase_sb_valueChanged(int);
|
||||
|
||||
signals:
|
||||
void folioAutoNumChanged(QString);
|
||||
|
||||
private:
|
||||
enum class AutoNumCategory { Conductor, Element, Folio };
|
||||
|
||||
/// The four widgets that make up one category's row, bundled so
|
||||
/// refreshRow() can be called with just a category instead of
|
||||
/// four pointers that must always be passed in matching sets.
|
||||
struct Row
|
||||
{
|
||||
QComboBox *combo;
|
||||
QLineEdit *value;
|
||||
QSpinBox *increase;
|
||||
QLineEdit *next;
|
||||
};
|
||||
Row rowFor(AutoNumCategory category) const;
|
||||
|
||||
/**
|
||||
@brief resetAutoNum
|
||||
Reset the numerotation context currently selected in combo_box
|
||||
@@ -93,6 +109,23 @@ class AutoNumberingDockWidget : public QDockWidget
|
||||
void refreshValueField(QComboBox *combo_box, QLineEdit *line_edit, AutoNumCategory category);
|
||||
void applyValueField(QComboBox *combo_box, QLineEdit *line_edit, AutoNumCategory category);
|
||||
|
||||
/// Refresh/apply the increment spin box the same way.
|
||||
void refreshIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category);
|
||||
void applyIncreaseField(QComboBox *combo_box, QSpinBox *increase_sb, AutoNumCategory category);
|
||||
|
||||
/// Show what the counter will read after one more step, using
|
||||
/// the same NumerotationContextCommands engine the Suivant
|
||||
/// button in the full configuration dialog already advances
|
||||
/// the whole context with -- so the preview can never disagree
|
||||
/// with what actually happens when the number is next consumed.
|
||||
void refreshNextField(QComboBox *combo_box, QLineEdit *next_edit, AutoNumCategory category);
|
||||
|
||||
/// Refresh a whole row -- value, increment and next-value
|
||||
/// preview -- in one call. Every refresh call site needs the
|
||||
/// increment and preview kept in step with the value now, so
|
||||
/// this replaces refreshValueField() at each of them.
|
||||
void refreshRow(AutoNumCategory category);
|
||||
|
||||
Ui::AutoNumberingDockWidget *ui;
|
||||
QETProject* m_project = nullptr;
|
||||
ProjectView* m_project_view = nullptr;
|
||||
|
||||
@@ -15,6 +15,36 @@
|
||||
</property>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="value_header_label">
|
||||
<property name="text">
|
||||
<string>Valeur</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QLabel" name="increase_header_label">
|
||||
<property name="text">
|
||||
<string>Incrément</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<widget class="QLabel" name="next_header_label">
|
||||
<property name="text">
|
||||
<string>Suivant</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="m_element_cb"/>
|
||||
</item>
|
||||
@@ -61,6 +91,47 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="4">
|
||||
<widget class="QSpinBox" name="m_conductor_increase_sb">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Incrément : valeur ajoutée au compteur à chaque nouvelle numérotation</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="accelerated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="5">
|
||||
<widget class="QLineEdit" name="m_conductor_next_le">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Prochaine valeur qui sera appliquée avec cet incrément</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -108,6 +179,47 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QSpinBox" name="m_element_increase_sb">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Incrément : valeur ajoutée au compteur à chaque nouvelle numérotation</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="accelerated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="5">
|
||||
<widget class="QLineEdit" name="m_element_next_le">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Prochaine valeur qui sera appliquée avec cet incrément</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="m_folio_cb"/>
|
||||
</item>
|
||||
@@ -144,6 +256,47 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="4">
|
||||
<widget class="QSpinBox" name="m_folio_increase_sb">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Incrément : valeur ajoutée au compteur à chaque nouvelle numérotation</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="accelerated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="5">
|
||||
<widget class="QLineEdit" name="m_folio_next_le">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Prochaine valeur qui sera appliquée avec cet incrément</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
@@ -163,7 +162,6 @@
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
@@ -238,7 +236,6 @@
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
@@ -328,7 +325,6 @@
|
||||
<widget class="QWidget" name="folioWidget" native="true">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
@@ -338,7 +334,6 @@
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
||||
@@ -112,7 +112,7 @@ void SelectAutonumW::setContext(const NumerotationContext &context)
|
||||
else {
|
||||
for (int i=0; i<m_context.size(); ++i) { //build with the content of @context
|
||||
NumPartEditorW *part= new NumPartEditorW(m_context, i, m_edited_type, this);
|
||||
connect (part, SIGNAL(changed()), this, SLOT(applyEnable()));
|
||||
connect(part, &NumPartEditorW::changed, this, [this]() { applyEnable(); });
|
||||
num_part_list_ << part;
|
||||
ui -> editor_layout -> addWidget(part);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ void SelectAutonumW::on_add_button_clicked()
|
||||
{
|
||||
applyEnable(false);
|
||||
NumPartEditorW *part = new NumPartEditorW(m_edited_type, this);
|
||||
connect (part, SIGNAL(changed()), this, SLOT(applyEnable()));
|
||||
connect(part, &NumPartEditorW::changed, this, [this]() { applyEnable(); });
|
||||
num_part_list_ << part;
|
||||
ui -> editor_layout -> addWidget(part);
|
||||
ui -> remove_button -> setEnabled(true);
|
||||
@@ -160,7 +160,7 @@ void SelectAutonumW::on_remove_button_clicked()
|
||||
//remove if @num_part_list contains more than one item
|
||||
if (num_part_list_.size() > 1) {
|
||||
NumPartEditorW *part = num_part_list_.takeLast();
|
||||
disconnect(part, SIGNAL(changed()), this, SLOT(applyEnable()));
|
||||
// deliberately not disconnecting as not possible to resolve with lambda and will happen automatically when the object "part" is destroyed.
|
||||
delete part;
|
||||
if (num_part_list_.size() == 1) {
|
||||
ui -> remove_button -> setDisabled(true);
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "autobreakconductor.h"
|
||||
|
||||
#include "diagram.h"
|
||||
#include "qetproject.h"
|
||||
#include "conductorautonumerotation.h"
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetgraphicsitem/terminal.h"
|
||||
#include "qetgraphicsitem/conductor.h"
|
||||
#include "undocommand/addgraphicsobjectcommand.h"
|
||||
#include "undocommand/deleteqgraphicsitemcommand.h"
|
||||
|
||||
#include <QPainterPath>
|
||||
#include <QMap>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief distanceToSegment
|
||||
@param point : point to measure from
|
||||
@param segment : the finite segment (not the infinite line through it)
|
||||
@return the distance from @a point to the closest point actually on
|
||||
@a segment. Unlike QET::orthogonalProjection(), which reports a hit
|
||||
for any point on the segment's infinite extension, this clamps the
|
||||
projection to the segment itself: a point collinear with a wire but
|
||||
past its actual drawn end is correctly reported as far away, not "on"
|
||||
the wire.
|
||||
*/
|
||||
qreal distanceToSegment(const QPointF &point, const QLineF &segment)
|
||||
{
|
||||
const QPointF a = segment.p1();
|
||||
const QPointF b = segment.p2();
|
||||
const QPointF ab = b - a;
|
||||
const qreal len2 = QPointF::dotProduct(ab, ab);
|
||||
|
||||
if (len2 <= 0.0)
|
||||
return QLineF(point, a).length();
|
||||
|
||||
qreal t = QPointF::dotProduct(point - a, ab) / len2;
|
||||
t = qBound(0.0, t, 1.0);
|
||||
return QLineF(point, a + t * ab).length();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/**
|
||||
@brief autoBreakConductors
|
||||
For each terminal of @a element, check if its dock point lies on an
|
||||
existing conductor. If so, break the conductor and reconnect through
|
||||
the element's terminal. Broken conductors from the same circuit
|
||||
(sharing the same far-end endpoint) are broken together; independent
|
||||
crossing conductors are left untouched.
|
||||
*/
|
||||
QSet<Terminal *> autoBreakConductors(
|
||||
Diagram *diagram,
|
||||
Element *element,
|
||||
QUndoCommand *parent,
|
||||
QList<Conductor *> &conductors_handled,
|
||||
QSet<Terminal *> &used_terminals)
|
||||
{
|
||||
QSet<Terminal *> broken_endpoints;
|
||||
|
||||
if (!diagram->project()->autoBreakConductor())
|
||||
return broken_endpoints;
|
||||
|
||||
foreach (Terminal *t, element->terminals())
|
||||
{
|
||||
if (used_terminals.contains(t))
|
||||
continue;
|
||||
|
||||
QPointF t_dock = t->dockConductor();
|
||||
|
||||
struct ConductorMatch {
|
||||
Conductor *conductor;
|
||||
Terminal *connect_to;
|
||||
Terminal *other;
|
||||
};
|
||||
QList<ConductorMatch> all_matches;
|
||||
|
||||
foreach (Conductor *c, diagram->conductors())
|
||||
{
|
||||
if (conductors_handled.contains(c) ||
|
||||
c->terminal1->parentElement() == element ||
|
||||
c->terminal2->parentElement() == element)
|
||||
continue;
|
||||
|
||||
QPointF local_dock = c->mapFromScene(t_dock);
|
||||
bool point_on_conductor = false;
|
||||
QPainterPath path = c->path();
|
||||
for (int i = 0; i < path.elementCount() - 1; ++i)
|
||||
{
|
||||
const QPainterPath::Element &e1 = path.elementAt(i);
|
||||
const QPainterPath::Element &e2 = path.elementAt(i + 1);
|
||||
QLineF segment(QPointF(e1.x, e1.y), QPointF(e2.x, e2.y));
|
||||
if (distanceToSegment(local_dock, segment) < 5.0)
|
||||
{
|
||||
point_on_conductor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!point_on_conductor)
|
||||
continue;
|
||||
|
||||
Terminal *c1 = c->terminal1;
|
||||
Terminal *c2 = c->terminal2;
|
||||
QPointF c1_dock = c1->dockConductor();
|
||||
QPointF c2_dock = c2->dockConductor();
|
||||
|
||||
Terminal *connect_to = nullptr;
|
||||
Terminal *other = nullptr;
|
||||
|
||||
switch (t->orientation()) {
|
||||
case Qet::North:
|
||||
if (c1_dock.y() < t_dock.y()) { connect_to = c1; other = c2; }
|
||||
else if (c2_dock.y() < t_dock.y()) { connect_to = c2; other = c1; }
|
||||
break;
|
||||
case Qet::South:
|
||||
if (c1_dock.y() > t_dock.y()) { connect_to = c1; other = c2; }
|
||||
else if (c2_dock.y() > t_dock.y()) { connect_to = c2; other = c1; }
|
||||
break;
|
||||
case Qet::East:
|
||||
if (c1_dock.x() > t_dock.x()) { connect_to = c1; other = c2; }
|
||||
else if (c2_dock.x() > t_dock.x()) { connect_to = c2; other = c1; }
|
||||
break;
|
||||
case Qet::West:
|
||||
if (c1_dock.x() < t_dock.x()) { connect_to = c1; other = c2; }
|
||||
else if (c2_dock.x() < t_dock.x()) { connect_to = c2; other = c1; }
|
||||
break;
|
||||
}
|
||||
|
||||
if (!connect_to) {
|
||||
qreal d1 = QLineF(t_dock, c1_dock).length();
|
||||
qreal d2 = QLineF(t_dock, c2_dock).length();
|
||||
if (d1 <= d2) { connect_to = c1; other = c2; }
|
||||
else { connect_to = c2; other = c1; }
|
||||
}
|
||||
|
||||
all_matches.append({c, connect_to, other});
|
||||
}
|
||||
|
||||
if (all_matches.isEmpty())
|
||||
continue;
|
||||
|
||||
QMap<Terminal *, QList<ConductorMatch>> groups;
|
||||
for (const auto &m : all_matches) {
|
||||
groups[m.other].append(m);
|
||||
}
|
||||
Terminal *best_other = nullptr;
|
||||
int best_count = 0;
|
||||
for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) {
|
||||
if (it.value().size() > best_count) {
|
||||
best_count = it.value().size();
|
||||
best_other = it.key();
|
||||
}
|
||||
}
|
||||
|
||||
const QList<ConductorMatch> &matches = groups[best_other];
|
||||
|
||||
used_terminals.insert(t);
|
||||
|
||||
DiagramContent content;
|
||||
for (const auto &m : matches) {
|
||||
content.m_other_conductors.append(m.conductor);
|
||||
conductors_handled.append(m.conductor);
|
||||
}
|
||||
new DeleteQGraphicsItemCommand(diagram, content, parent);
|
||||
|
||||
for (const auto &m : matches) {
|
||||
Conductor *new_c = new Conductor(m.connect_to, t);
|
||||
new AddGraphicsObjectCommand(new_c, diagram, QPointF(), parent);
|
||||
ConductorAutoNumerotation can(new_c, diagram, parent);
|
||||
can.numerate();
|
||||
if (diagram->freezeNewConductors() || diagram->project()->isFreezeNewConductors())
|
||||
new_c->setFreezeLabel(true);
|
||||
|
||||
broken_endpoints.insert(m.connect_to);
|
||||
}
|
||||
|
||||
QPointF other_dock = best_other->dockConductor();
|
||||
Terminal *other_terminal = nullptr;
|
||||
qreal best_dist = std::numeric_limits<qreal>::max();
|
||||
foreach (Terminal *ot, element->terminals())
|
||||
{
|
||||
if (used_terminals.contains(ot))
|
||||
continue;
|
||||
|
||||
QPointF ot_dock = ot->dockConductor();
|
||||
bool orientation_ok = false;
|
||||
switch (ot->orientation()) {
|
||||
case Qet::North: orientation_ok = other_dock.y() < ot_dock.y(); break;
|
||||
case Qet::South: orientation_ok = other_dock.y() > ot_dock.y(); break;
|
||||
case Qet::East: orientation_ok = other_dock.x() > ot_dock.x(); break;
|
||||
case Qet::West: orientation_ok = other_dock.x() < ot_dock.x(); break;
|
||||
}
|
||||
if (!orientation_ok)
|
||||
continue;
|
||||
|
||||
qreal dist = QLineF(ot_dock, other_dock).length();
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
other_terminal = ot;
|
||||
}
|
||||
}
|
||||
|
||||
if (other_terminal) {
|
||||
Conductor *new_c2 = new Conductor(best_other, other_terminal);
|
||||
new AddGraphicsObjectCommand(new_c2, diagram, QPointF(), parent);
|
||||
ConductorAutoNumerotation can2(new_c2, diagram, parent);
|
||||
can2.numerate();
|
||||
if (diagram->freezeNewConductors() || diagram->project()->isFreezeNewConductors())
|
||||
new_c2->setFreezeLabel(true);
|
||||
|
||||
broken_endpoints.insert(best_other);
|
||||
used_terminals.insert(other_terminal);
|
||||
}
|
||||
}
|
||||
|
||||
return broken_endpoints;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef AUTOBREAKCONDUCTOR_H
|
||||
#define AUTOBREAKCONDUCTOR_H
|
||||
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
|
||||
class Diagram;
|
||||
class Element;
|
||||
class Terminal;
|
||||
class Conductor;
|
||||
class QUndoCommand;
|
||||
|
||||
/**
|
||||
@brief autoBreakConductors
|
||||
For each terminal of @a element, check if its dock point lies on an
|
||||
existing conductor. If so, break the conductor and reconnect through the
|
||||
element's terminal. Broken conductors from the same circuit (sharing the
|
||||
same far-end endpoint) are broken together; independent crossing
|
||||
conductors are left untouched.
|
||||
@param diagram the diagram containing the conductors
|
||||
@param element the element whose terminals trigger breaks
|
||||
@param parent undo command under which break/reconnect
|
||||
sub-commands are created
|
||||
@param conductors_handled shared list of conductors already claimed by
|
||||
a previous call in the same batch (prevents
|
||||
double-processing when multiple elements are
|
||||
broken in one pass)
|
||||
@param used_terminals shared set of terminals already used as
|
||||
connect_to or other_terminal in the same batch
|
||||
@return set of terminals that were connected to a broken conductor's
|
||||
far-end (useful for preventing duplicate auto-connect)
|
||||
*/
|
||||
QSet<Terminal *> autoBreakConductors(
|
||||
Diagram *diagram,
|
||||
Element *element,
|
||||
QUndoCommand *parent,
|
||||
QList<Conductor *> &conductors_handled,
|
||||
QSet<Terminal *> &used_terminals);
|
||||
|
||||
#endif // AUTOBREAKCONDUCTOR_H
|
||||
@@ -73,6 +73,8 @@ class BorderTitleBlock : public QObject
|
||||
return(rows_count_ * rows_height_); }
|
||||
/// @return la rows header width, in pixels
|
||||
qreal rowsHeaderWidth() const { return(rows_header_width_); }
|
||||
/// @return the edge where title block is docked
|
||||
Qt::Edge titleBlockEdge() const { return(m_edge); }
|
||||
|
||||
// border - title block = diagram
|
||||
/**
|
||||
|
||||
+37
-13
@@ -109,7 +109,16 @@ QString diagramStem(Diagram *diagram, int index)
|
||||
}
|
||||
|
||||
/// Render @p diagram into @p painter, fitting @p target to the page rect.
|
||||
void renderDiagram(Diagram *diagram, QPainter &painter, const QRectF &target)
|
||||
/// @p showTerminals: paint terminal markers (red stroke + blue docking
|
||||
/// dot) and terminal names, as the interactive editor does. Off by
|
||||
/// default — Terminal::paint() draws them whenever the diagram's
|
||||
/// drawTerminals()/drawTerminalNames() flags are set (default true, so
|
||||
/// headless export used to ship them as editor UI); the GUI export
|
||||
/// dialog already defaults to clearing them via
|
||||
/// Diagram::applyProperties(). Pass true (--show-terminals) to keep
|
||||
/// them, e.g. to visually debug an unconnected pin.
|
||||
void renderDiagram(Diagram *diagram, QPainter &painter, const QRectF &target,
|
||||
bool showTerminals = false)
|
||||
{
|
||||
const QRect source = diagramRect(diagram);
|
||||
// Export without the editor grid: drawBackground() only paints it when
|
||||
@@ -117,14 +126,21 @@ void renderDiagram(Diagram *diagram, QPainter &painter, const QRectF &target)
|
||||
// and restore it afterwards.
|
||||
const bool was_drawing_grid = diagram->displayGrid();
|
||||
const bool was_drawing_guides = diagram->displayGuides();
|
||||
const bool was_drawing_terminals = diagram->drawTerminals();
|
||||
const bool was_drawing_terminal_names = diagram->drawTerminalNames();
|
||||
diagram->setDisplayGrid(false);
|
||||
diagram->setDisplayGuides(false);
|
||||
diagram->setDrawTerminals(showTerminals);
|
||||
diagram->setDrawTerminalNames(showTerminals);
|
||||
diagram->render(&painter, target, source, Qt::KeepAspectRatio);
|
||||
diagram->setDisplayGrid(was_drawing_grid);
|
||||
diagram->setDisplayGuides(was_drawing_guides);
|
||||
diagram->setDrawTerminals(was_drawing_terminals);
|
||||
diagram->setDrawTerminalNames(was_drawing_terminal_names);
|
||||
}
|
||||
|
||||
int exportPdf(QETProject &project, const QString &output)
|
||||
int exportPdf(QETProject &project, const QString &output,
|
||||
bool showTerminals = false)
|
||||
{
|
||||
const QList<Diagram *> diagrams = project.diagrams();
|
||||
if (diagrams.isEmpty()) {
|
||||
@@ -164,7 +180,7 @@ int exportPdf(QETProject &project, const QString &output)
|
||||
}
|
||||
const QRectF target(0, 0,
|
||||
writer.width(), writer.height());
|
||||
renderDiagram(diagram, painter, target);
|
||||
renderDiagram(diagram, painter, target, showTerminals);
|
||||
|
||||
// Inject clickable cross-reference / folio-report hyperlinks for this
|
||||
// page. The geometry is rebuilt from the QPdfWriter (not a QPrinter):
|
||||
@@ -214,7 +230,7 @@ int exportPdf(QETProject &project, const QString &output)
|
||||
}
|
||||
|
||||
int exportImages(QETProject &project, const QString &format,
|
||||
const QString &out_dir)
|
||||
const QString &out_dir, bool showTerminals = false)
|
||||
{
|
||||
const QList<Diagram *> diagrams = project.diagrams();
|
||||
if (diagrams.isEmpty()) {
|
||||
@@ -237,14 +253,16 @@ int exportImages(QETProject &project, const QString &format,
|
||||
gen.setViewBox(QRect(0, 0, r.width(), r.height()));
|
||||
gen.setTitle(diagram->title());
|
||||
QPainter painter(&gen);
|
||||
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()));
|
||||
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()),
|
||||
showTerminals);
|
||||
painter.end();
|
||||
} else { // png
|
||||
QImage image(r.size(), QImage::Format_ARGB32);
|
||||
image.fill(Qt::white);
|
||||
QPainter painter(&image);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()));
|
||||
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()),
|
||||
showTerminals);
|
||||
painter.end();
|
||||
if (!image.save(path)) {
|
||||
err << "Failed to write '" << path << "'.\n";
|
||||
@@ -768,13 +786,19 @@ bool isExportRequest(const QStringList &args)
|
||||
|
||||
int run(const QStringList &args)
|
||||
{
|
||||
// --show-terminals is a standalone switch (not tied to a position),
|
||||
// so pull it out before the positional project/output arguments are
|
||||
// collected below.
|
||||
QStringList filtered = args;
|
||||
const bool showTerminals = filtered.removeAll("--show-terminals") > 0;
|
||||
|
||||
QString flag;
|
||||
QStringList rest;
|
||||
for (int i = 0; i < args.size(); ++i) {
|
||||
if (exportFlags().contains(args.at(i))) {
|
||||
flag = args.at(i);
|
||||
for (int j = i + 1; j < args.size(); ++j)
|
||||
rest << args.at(j);
|
||||
for (int i = 0; i < filtered.size(); ++i) {
|
||||
if (exportFlags().contains(filtered.at(i))) {
|
||||
flag = filtered.at(i);
|
||||
for (int j = i + 1; j < filtered.size(); ++j)
|
||||
rest << filtered.at(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -818,7 +842,7 @@ int run(const QStringList &args)
|
||||
return 2;
|
||||
}
|
||||
if (format == "pdf")
|
||||
return exportPdf(project, output);
|
||||
return exportPdf(project, output, showTerminals);
|
||||
if (format == "cables" || format == "wires")
|
||||
return exportCsv(project, format, output);
|
||||
if (format == "bom")
|
||||
@@ -831,7 +855,7 @@ int run(const QStringList &args)
|
||||
return resaveProject(project, output);
|
||||
if (format == "settb")
|
||||
return setTitleBlock(project, output, rest.mid(2));
|
||||
return exportImages(project, format, output);
|
||||
return exportImages(project, format, output, showTerminals);
|
||||
}
|
||||
|
||||
} // namespace CLIExport
|
||||
|
||||
@@ -42,9 +42,9 @@ namespace CLIExport {
|
||||
@return process exit code (0 on success).
|
||||
|
||||
Usage:
|
||||
qelectrotech --export-pdf <project.qet> <output.pdf>
|
||||
qelectrotech --export-png <project.qet> <output_dir>
|
||||
qelectrotech --export-svg <project.qet> <output_dir>
|
||||
qelectrotech --export-pdf <project.qet> <output.pdf> [--show-terminals]
|
||||
qelectrotech --export-png <project.qet> <output_dir> [--show-terminals]
|
||||
qelectrotech --export-svg <project.qet> <output_dir> [--show-terminals]
|
||||
qelectrotech --export-cables <project.qet> <output.csv>
|
||||
qelectrotech --export-wires <project.qet> <output.csv>
|
||||
qelectrotech --export-bom <project.qet> <output.csv>
|
||||
@@ -57,6 +57,10 @@ namespace CLIExport {
|
||||
|
||||
PDF: one multi-page document (one diagram per page).
|
||||
PNG/SVG: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
|
||||
--show-terminals: also paint terminal markers (red stroke + blue
|
||||
docking dot) and terminal names, as the interactive editor
|
||||
does; off by default, matching the GUI export dialog's
|
||||
default. Has no effect on the non-image export modes.
|
||||
cables: wiring list (one row per conductor) as CSV.
|
||||
wires: list of distinct wire numbers as CSV.
|
||||
bom: bill of materials (one row per element) as CSV.
|
||||
|
||||
@@ -72,14 +72,8 @@ bool ConductorNumExport::toCsv()
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
QTextStream stream(&file);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // ### Qt 6: remove
|
||||
stream << wiresNum() << endl;
|
||||
#else
|
||||
#if TODO_LIST
|
||||
#pragma message("@TODO remove code for QT 5.15 or later")
|
||||
#endif
|
||||
|
||||
stream << wiresNum() << &Qt::endl(stream);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
|
||||
@@ -811,14 +811,7 @@ void ConductorProperties::readStyle(const QString &style_string) {
|
||||
if (style_string.isEmpty()) return;
|
||||
|
||||
// recupere la liste des couples style / valeur
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
|
||||
QStringList styles = style_string.split(";", QString::SkipEmptyParts);
|
||||
#else
|
||||
#if TODO_LIST
|
||||
#pragma message("@TODO remove code QString::SkipEmptyParts for QT 5.14 or later")
|
||||
#endif
|
||||
QStringList styles = style_string.split(";", Qt::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
QRegularExpression Rx("^(?<name>[a-z-]+): (?<value>[a-z-]+)$");
|
||||
if (!Rx.isValid())
|
||||
|
||||
@@ -74,10 +74,9 @@ ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent) {
|
||||
setLayout(dialog_layout);
|
||||
|
||||
// connexion signaux / slots
|
||||
connect(buttons, SIGNAL(accepted()), this, SLOT(applyConf()));
|
||||
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
connect(pages_list, SIGNAL(currentRowChanged(int)),
|
||||
pages_widget, SLOT(setCurrentIndex(int)));
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ConfigDialog::applyConf);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ConfigDialog::reject);
|
||||
connect(pages_list, &QListWidget::currentRowChanged, pages_widget, &QStackedWidget::setCurrentIndex);
|
||||
|
||||
// set maximum a bit smaller than available size = (screen-size - Task-Bar):
|
||||
setMaximumSize((int)(0.94 * MachineInfo::instance()->i_max_available_width()),
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include "../diagramposition.h"
|
||||
#include "../elementprovider.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qetinformation.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
@@ -87,6 +89,7 @@ void projectDataBase::updateDB()
|
||||
populateDiagramInfoTable();
|
||||
populateElementTable();
|
||||
populateElementInfoTable();
|
||||
populateConductorTable();
|
||||
emit dataBaseUpdated();
|
||||
}
|
||||
|
||||
@@ -245,6 +248,132 @@ void projectDataBase::diagramOrderChanged()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::addConductor
|
||||
@param conductor
|
||||
*/
|
||||
void projectDataBase::addConductor(Conductor *conductor)
|
||||
{
|
||||
if (!conductor || !conductor->diagram()) {
|
||||
qDebug() << "projectDataBase::addConductor: null conductor or diagram";
|
||||
return;
|
||||
}
|
||||
|
||||
//Both endpoints must belong to an element: the terminal table is keyed
|
||||
//on (terminal, element) and a terminal with no parent has no identity
|
||||
//to key on. Terminals whose *definition* predates terminal uuids are
|
||||
//fine -- Terminal::stableUuid() derives one from the terminal's local
|
||||
//position, which is what the project format itself matches on.
|
||||
if (!conductor->terminal1->parentElement()
|
||||
|| !conductor->terminal2->parentElement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
insertTerminal(conductor->terminal1);
|
||||
insertTerminal(conductor->terminal2);
|
||||
|
||||
watchConductor(conductor);
|
||||
bindConductorValues(m_insert_conductor_query, conductor, conductor->diagram());
|
||||
if (!m_insert_conductor_query.exec()) {
|
||||
qDebug() << "projectDataBase::addConductor insert error : " << m_insert_conductor_query.lastError();
|
||||
} else {
|
||||
emit dataBaseUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::removeConductor
|
||||
@param conductor
|
||||
*/
|
||||
void projectDataBase::removeConductor(Conductor *conductor)
|
||||
{
|
||||
m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString());
|
||||
if (!m_remove_conductor_query.exec()) {
|
||||
qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError();
|
||||
} else {
|
||||
emit dataBaseUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::updateConductor
|
||||
Refresh the mutable columns of an already-inserted conductor.
|
||||
|
||||
Only the text (the wire number) can change without the conductor being
|
||||
removed and re-added: its endpoints are fixed for its lifetime. Without
|
||||
this, renaming a wire left the database holding the old number and the
|
||||
wiring list showed a stale value until the next full repopulate.
|
||||
@param conductor
|
||||
*/
|
||||
void projectDataBase::updateConductor(Conductor *conductor)
|
||||
{
|
||||
if (!conductor) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_update_conductor_query.bindValue(QStringLiteral(":uuid"), conductor->uuid().toString());
|
||||
m_update_conductor_query.bindValue(QStringLiteral(":text"), conductor->properties().text);
|
||||
if (!m_update_conductor_query.exec()) {
|
||||
qDebug() << "projectDataBase::updateConductor update error : " << m_update_conductor_query.lastError();
|
||||
}
|
||||
|
||||
//Deliberately no dataBaseUpdated() here, unlike add/remove. The only
|
||||
//column this touches is the wire text, which no view watched by
|
||||
//ProjectDBModel displays -- the nomenclature shows elements, and its
|
||||
//wire_count changes when a conductor appears or disappears, not when
|
||||
//it is renamed. Emitting would make every ProjectDBModel re-run its
|
||||
//query, and auto-numbering renames every conductor in the project in
|
||||
//one pass.
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::watchConductor
|
||||
Keep this conductor's row in step with its properties.
|
||||
|
||||
Conductor::setProperties() has a dozen call sites (auto-numbering, the
|
||||
properties dialog, element moves, deletion re-links...), so listening to
|
||||
the signal it already emits is the only way to catch them all -- and the
|
||||
only way to catch the ones added later. Qt::UniqueConnection makes a
|
||||
repeated insert or a full repopulate harmless.
|
||||
@param conductor
|
||||
*/
|
||||
void projectDataBase::watchConductor(Conductor *conductor)
|
||||
{
|
||||
connect(conductor, &Conductor::propertiesChange,
|
||||
this, &projectDataBase::conductorPropertiesChanged,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::conductorPropertiesChanged
|
||||
*/
|
||||
void projectDataBase::conductorPropertiesChanged()
|
||||
{
|
||||
if (auto *conductor = qobject_cast<Conductor *>(sender())) {
|
||||
updateConductor(conductor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::bindConductorValues
|
||||
One binder for both insert paths, so a conductor added to a live diagram
|
||||
and one read from a file can never drift apart -- the same reason
|
||||
bindElementValues() exists for elements.
|
||||
@param query
|
||||
@param conductor
|
||||
@param diagram : the diagram the conductor belongs to
|
||||
*/
|
||||
void projectDataBase::bindConductorValues(QSqlQuery &query, Conductor *conductor, Diagram *diagram)
|
||||
{
|
||||
query.bindValue(QStringLiteral(":uuid"), conductor->uuid().toString());
|
||||
query.bindValue(QStringLiteral(":diagram_uuid"), diagram->uuid().toString());
|
||||
query.bindValue(QStringLiteral(":terminal1_uuid"), conductor->terminal1->stableUuid().toString());
|
||||
query.bindValue(QStringLiteral(":terminal1_element_uuid"), conductor->terminal1->parentElement()->uuid().toString());
|
||||
query.bindValue(QStringLiteral(":terminal2_uuid"), conductor->terminal2->stableUuid().toString());
|
||||
query.bindValue(QStringLiteral(":terminal2_element_uuid"), conductor->terminal2->parentElement()->uuid().toString());
|
||||
query.bindValue(QStringLiteral(":text"), conductor->properties().text);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::createDataBase
|
||||
Create the data base
|
||||
@@ -323,6 +452,57 @@ bool projectDataBase::createDataBase()
|
||||
qDebug() << " element_info_table query : " << query_.lastError();
|
||||
}
|
||||
|
||||
//Create the terminal table.
|
||||
//Terminal::uuid() is the terminal-position id baked into the catalog
|
||||
//.elmt definition (e.g. "the top terminal") -- identical across every
|
||||
//placed instance of that catalog element, not a per-instance id. A
|
||||
//terminal instance is only uniquely identified by (uuid, element_uuid)
|
||||
//together, so that pair is the primary key here, not uuid alone.
|
||||
QString terminal_table("CREATE TABLE terminal"
|
||||
"( "
|
||||
"uuid VARCHAR(50) NOT NULL, "
|
||||
"element_uuid VARCHAR(50) NOT NULL,"
|
||||
"name VARCHAR(50),"
|
||||
"PRIMARY KEY (uuid, element_uuid),"
|
||||
"FOREIGN KEY (element_uuid) REFERENCES element (uuid)"
|
||||
")");
|
||||
if (!query_.exec(terminal_table)) {
|
||||
qDebug() << "terminal_table query : "<< query_.lastError();
|
||||
}
|
||||
|
||||
//Create the conductor table
|
||||
QString conductor_table("CREATE TABLE conductor"
|
||||
"( "
|
||||
"uuid VARCHAR(50) PRIMARY KEY NOT NULL, "
|
||||
"diagram_uuid VARCHAR(50) NOT NULL,"
|
||||
"terminal1_uuid VARCHAR(50) NOT NULL,"
|
||||
"terminal1_element_uuid VARCHAR(50) NOT NULL,"
|
||||
"terminal2_uuid VARCHAR(50) NOT NULL,"
|
||||
"terminal2_element_uuid VARCHAR(50) NOT NULL,"
|
||||
"text VARCHAR(100),"
|
||||
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid),"
|
||||
"FOREIGN KEY (terminal1_uuid, terminal1_element_uuid) REFERENCES terminal (uuid, element_uuid),"
|
||||
"FOREIGN KEY (terminal2_uuid, terminal2_element_uuid) REFERENCES terminal (uuid, element_uuid)"
|
||||
")");
|
||||
if (!query_.exec(conductor_table)) {
|
||||
qDebug() << "conductor_table query : "<< query_.lastError();
|
||||
}
|
||||
|
||||
//The element-facing columns are looked up per element row, not per
|
||||
//conductor row: element_nomenclature_view carries a correlated
|
||||
//subquery counting the wires touching each element. Without these
|
||||
//indexes each element row full-scans the conductor table, which grows
|
||||
//as elements x conductors.
|
||||
for (const QString &index_ : {
|
||||
QStringLiteral("CREATE INDEX idx_conductor_terminal1_element ON conductor (terminal1_element_uuid)"),
|
||||
QStringLiteral("CREATE INDEX idx_conductor_terminal2_element ON conductor (terminal2_element_uuid)"),
|
||||
QStringLiteral("CREATE INDEX idx_conductor_diagram ON conductor (diagram_uuid)") })
|
||||
{
|
||||
if (!query_.exec(index_)) {
|
||||
qDebug() << "conductor index query : " << query_.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
createElementNomenclatureView();
|
||||
createSummaryView();
|
||||
prepareQuery();
|
||||
@@ -534,6 +714,58 @@ void projectDataBase::populateDiagramInfoTable()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::populateConductorTable
|
||||
Populate the terminal and conductor tables. Terminals only matter here
|
||||
in the context of a conductor referencing them, so their population is
|
||||
folded into this method rather than tracked independently.
|
||||
*/
|
||||
void projectDataBase::populateConductorTable()
|
||||
{
|
||||
QSqlQuery query(m_data_base);
|
||||
query.exec(QStringLiteral("DELETE FROM conductor"));
|
||||
query.exec(QStringLiteral("DELETE FROM terminal"));
|
||||
|
||||
for (auto *diagram : m_project->diagrams())
|
||||
{
|
||||
const auto conductor_list = diagram->conductors();
|
||||
for (auto *conductor : conductor_list)
|
||||
{
|
||||
//See addConductor(): only a terminal with no parent element is
|
||||
//skipped. A missing terminal uuid is handled by stableUuid().
|
||||
if (!conductor->terminal1->parentElement()
|
||||
|| !conductor->terminal2->parentElement()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
insertTerminal(conductor->terminal1);
|
||||
insertTerminal(conductor->terminal2);
|
||||
|
||||
watchConductor(conductor);
|
||||
bindConductorValues(m_insert_conductor_query, conductor, diagram);
|
||||
if (!m_insert_conductor_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateConductorTable insert error : " << m_insert_conductor_query.lastError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::insertTerminal
|
||||
Insert (or, if already present -- e.g. a junction shared by several
|
||||
conductors -- silently keep) @terminal in the terminal table.
|
||||
@param terminal
|
||||
*/
|
||||
void projectDataBase::insertTerminal(Terminal *terminal)
|
||||
{
|
||||
m_insert_terminal_query.bindValue(":uuid", terminal->stableUuid().toString());
|
||||
m_insert_terminal_query.bindValue(":element_uuid", terminal->parentElement()->uuid().toString());
|
||||
m_insert_terminal_query.bindValue(":name", terminal->name());
|
||||
if (!m_insert_terminal_query.exec()) {
|
||||
qDebug() << "projectDataBase::insertTerminal insert error : " << m_insert_terminal_query.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
void projectDataBase::prepareQuery()
|
||||
{
|
||||
//INSERT DIAGRAM
|
||||
@@ -606,6 +838,23 @@ void projectDataBase::prepareQuery()
|
||||
update_str.append(" WHERE element_uuid = :uuid");
|
||||
m_update_element_query = QSqlQuery(m_data_base);
|
||||
m_update_element_query.prepare(update_str);
|
||||
|
||||
//INSERT TERMINAL
|
||||
m_insert_terminal_query = QSqlQuery(m_data_base);
|
||||
m_insert_terminal_query.prepare("INSERT OR IGNORE INTO terminal (uuid, element_uuid, name) VALUES (:uuid, :element_uuid, :name)");
|
||||
|
||||
//INSERT CONDUCTOR
|
||||
m_insert_conductor_query = QSqlQuery(m_data_base);
|
||||
m_insert_conductor_query.prepare("INSERT INTO conductor (uuid, diagram_uuid, terminal1_uuid, terminal1_element_uuid, terminal2_uuid, terminal2_element_uuid, text) "
|
||||
"VALUES (:uuid, :diagram_uuid, :terminal1_uuid, :terminal1_element_uuid, :terminal2_uuid, :terminal2_element_uuid, :text)");
|
||||
|
||||
//UPDATE CONDUCTOR
|
||||
m_update_conductor_query = QSqlQuery(m_data_base);
|
||||
m_update_conductor_query.prepare(QStringLiteral("UPDATE conductor SET text = :text WHERE uuid = :uuid"));
|
||||
|
||||
//REMOVE CONDUCTOR
|
||||
m_remove_conductor_query = QSqlQuery(m_data_base);
|
||||
m_remove_conductor_query.prepare("DELETE FROM conductor WHERE uuid=:uuid");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
class Element;
|
||||
class QETProject;
|
||||
class Diagram;
|
||||
class Conductor;
|
||||
class Terminal;
|
||||
class sqlite3;
|
||||
|
||||
/**
|
||||
@@ -58,6 +60,16 @@ class projectDataBase : public QObject
|
||||
void diagramInfoChanged (Diagram *diagram);
|
||||
void diagramOrderChanged();
|
||||
|
||||
void addConductor (Conductor *conductor);
|
||||
void removeConductor (Conductor *conductor);
|
||||
void updateConductor (Conductor *conductor);
|
||||
|
||||
private slots:
|
||||
//Refresh the sender()'s row after Conductor::setProperties().
|
||||
void conductorPropertiesChanged();
|
||||
|
||||
public:
|
||||
|
||||
signals:
|
||||
void dataBaseUpdated();
|
||||
|
||||
@@ -69,6 +81,10 @@ class projectDataBase : public QObject
|
||||
void populateElementTable();
|
||||
void populateElementInfoTable();
|
||||
void populateDiagramInfoTable();
|
||||
void populateConductorTable();
|
||||
void bindConductorValues(QSqlQuery &query, Conductor *conductor, Diagram *diagram);
|
||||
void watchConductor(Conductor *conductor);
|
||||
void insertTerminal(Terminal *terminal);
|
||||
void prepareQuery();
|
||||
static QHash<QString, QString> elementInfoToString(
|
||||
Element *elmt);
|
||||
@@ -86,7 +102,11 @@ class projectDataBase : public QObject
|
||||
m_insert_diagram_info_query,
|
||||
m_update_diagram_info_query,
|
||||
m_diagram_order_changed,
|
||||
m_diagram_info_order_changed;
|
||||
m_diagram_info_order_changed,
|
||||
m_insert_terminal_query,
|
||||
m_insert_conductor_query,
|
||||
m_update_conductor_query,
|
||||
m_remove_conductor_query;
|
||||
|
||||
#ifdef QET_EXPORT_PROJECT_DB
|
||||
public:
|
||||
|
||||
@@ -48,14 +48,8 @@ ElementQueryWidget::ElementQueryWidget(QWidget *parent) :
|
||||
m_button_group.addButton(ui->m_protection_cb, 5);
|
||||
m_button_group.addButton(ui->m_thumbnail_cb, 6);
|
||||
m_button_group.addButton(ui->m_plc_cb, 7);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) // ### Qt 6: remove
|
||||
connect(&m_button_group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), [this](int id)
|
||||
#else
|
||||
#if TODO_LIST
|
||||
#pragma message("@TODO remove code for QT 5.15 or later")
|
||||
#endif
|
||||
connect(&m_button_group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::idClicked), [this](int id)
|
||||
#endif
|
||||
|
||||
{
|
||||
auto check_box = static_cast<QCheckBox *>(m_button_group.button(0));
|
||||
if (id == 0)
|
||||
@@ -384,10 +378,10 @@ QString ElementQueryWidget::queryStr() const
|
||||
where.clear();
|
||||
}
|
||||
|
||||
QString exclude_condition = "(exclude_from_bom IS NULL OR exclude_from_bom != '1')";
|
||||
|
||||
filter_ += " AND " + exclude_condition;
|
||||
// -------------------------------------------------------------
|
||||
// exclude_from_bom is already filtered by element_nomenclature_view
|
||||
// (see createElementNomenclatureView() in projectdatabase.cpp); this
|
||||
// widget's query reads FROM that view, so a flagged element never
|
||||
// reaches this point in the first place.
|
||||
|
||||
if (where.isEmpty() && !filter_.isEmpty()) {
|
||||
filter_.remove(0, 4); //Remove the first " AND" of filter.
|
||||
|
||||
+21
-10
@@ -67,6 +67,7 @@ Diagram::Diagram(QETProject *project) :
|
||||
m_project (project),
|
||||
use_border_ (true),
|
||||
draw_terminals_ (true),
|
||||
draw_terminal_names_ (true),
|
||||
draw_colored_conductors_ (true),
|
||||
m_event_interface (nullptr),
|
||||
m_freeze_new_elements (false),
|
||||
@@ -1555,14 +1556,6 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
if (content_ptr) {
|
||||
content_ptr -> m_elements = added_elements;
|
||||
content_ptr -> m_conductors_to_move = added_conductors;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
|
||||
content_ptr -> m_text_fields = added_texts.toSet();
|
||||
content_ptr -> m_images = added_images.toSet();
|
||||
content_ptr -> m_shapes = added_shapes.toSet();
|
||||
#else
|
||||
#if TODO_LIST
|
||||
#pragma message("@TODO remove code for QT 5.14 or later")
|
||||
#endif
|
||||
content_ptr -> m_text_fields = QSet<IndependentTextItem *>(
|
||||
added_texts.begin(),
|
||||
added_texts.end());
|
||||
@@ -1573,7 +1566,6 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
added_shapes.begin(),
|
||||
added_shapes.end());
|
||||
content_ptr->m_terminal_strip.swap(added_strips);
|
||||
#endif
|
||||
content_ptr->m_tables.swap(added_tables);
|
||||
}
|
||||
|
||||
@@ -1674,6 +1666,7 @@ void Diagram::addItem(QGraphicsItem *item)
|
||||
conductor->terminal1->addConductor(conductor);
|
||||
conductor->terminal2->addConductor(conductor);
|
||||
conductor->calculateTextItemPosition();
|
||||
m_project->dataBase()->addConductor(conductor);
|
||||
break;
|
||||
}
|
||||
default: {break;}
|
||||
@@ -1704,6 +1697,7 @@ void Diagram::removeItem(QGraphicsItem *item)
|
||||
Conductor *conductor = static_cast<Conductor *>(item);
|
||||
conductor->terminal1->removeConductor(conductor);
|
||||
conductor->terminal2->removeConductor(conductor);
|
||||
m_project->dataBase()->removeConductor(conductor);
|
||||
break;
|
||||
}
|
||||
default: {break;}
|
||||
@@ -2319,6 +2313,7 @@ ExportProperties Diagram::applyProperties(
|
||||
old_properties.draw_border = border_and_titleblock.borderIsDisplayed();
|
||||
old_properties.draw_titleblock = border_and_titleblock.titleBlockIsDisplayed();
|
||||
old_properties.draw_terminals = drawTerminals();
|
||||
old_properties.draw_terminal_names = drawTerminalNames();
|
||||
old_properties.draw_colored_conductors = drawColoredConductors();
|
||||
old_properties.exported_area = useBorder() ? QET::BorderArea
|
||||
: QET::ElementsArea;
|
||||
@@ -2327,6 +2322,7 @@ ExportProperties Diagram::applyProperties(
|
||||
// applique les nouvelles options de rendu
|
||||
setUseBorder (new_properties.exported_area == QET::BorderArea);
|
||||
setDrawTerminals (new_properties.draw_terminals);
|
||||
setDrawTerminalNames (new_properties.draw_terminal_names);
|
||||
setDrawColoredConductors (new_properties.draw_colored_conductors);
|
||||
setDisplayGrid (new_properties.draw_grid);
|
||||
setDisplayGuides (new_properties.draw_guides);
|
||||
@@ -2397,9 +2393,24 @@ QPointF Diagram::snapToGrid(const QPointF &p)
|
||||
\~French true pour afficher les bornes, false sinon
|
||||
*/
|
||||
void Diagram::setDrawTerminals(bool dt) {
|
||||
draw_terminals_ = dt;
|
||||
foreach(QGraphicsItem *qgi, items()) {
|
||||
if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
|
||||
t -> setVisible(dt);
|
||||
t -> update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::setDrawTerminalNames
|
||||
Defines whether or not to display the terminal names/labels
|
||||
@param dt : true to display the terminal names, false otherwise
|
||||
*/
|
||||
void Diagram::setDrawTerminalNames(bool dt) {
|
||||
draw_terminal_names_ = dt;
|
||||
foreach(QGraphicsItem *qgi, items()) {
|
||||
if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
|
||||
t -> update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ class Diagram : public QGraphicsScene
|
||||
bool draw_guides_;
|
||||
QList<Diagram::Guide> m_guides_list;
|
||||
bool draw_terminals_;
|
||||
bool draw_terminal_names_;
|
||||
bool draw_colored_conductors_;
|
||||
|
||||
QString m_conductors_autonum_name;
|
||||
@@ -226,6 +227,8 @@ class Diagram : public QGraphicsScene
|
||||
|
||||
bool drawTerminals() const;
|
||||
void setDrawTerminals(bool);
|
||||
bool drawTerminalNames() const;
|
||||
void setDrawTerminalNames(bool);
|
||||
bool drawColoredConductors() const;
|
||||
void setDrawColoredConductors(bool);
|
||||
|
||||
@@ -426,6 +429,15 @@ inline bool Diagram::drawTerminals() const
|
||||
return(draw_terminals_);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::drawTerminalNames
|
||||
@return true if terminal names are rendered, false otherwise
|
||||
*/
|
||||
inline bool Diagram::drawTerminalNames() const
|
||||
{
|
||||
return(draw_terminal_names_);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::drawColoredConductors
|
||||
@return true if conductors colors are rendered, false otherwise.
|
||||
|
||||
@@ -75,6 +75,13 @@ void PasteDiagramCommand::redo()
|
||||
{
|
||||
first_redo = false;
|
||||
|
||||
//make new uuid for every pasted conductor, because old uuid are
|
||||
//the uuid of the copied conductor
|
||||
const QList <Conductor *> all_pasted_conductors = content.conductors();
|
||||
for (Conductor *c : all_pasted_conductors) {
|
||||
c -> newUuid();
|
||||
}
|
||||
|
||||
//this is the first paste, we do some actions for the new element
|
||||
const QList <Element *> elmts_list = content.m_elements;
|
||||
for (Element *e : elmts_list)
|
||||
@@ -92,12 +99,18 @@ void PasteDiagramCommand::redo()
|
||||
dc.addValue("location", "");
|
||||
e->setElementInformations(dc);
|
||||
|
||||
//Reset the text of conductors
|
||||
//Reset the text of conductors, the same way the label/comment/
|
||||
//location above are reset to "" rather than to some other
|
||||
//value - "erase on copy" means erase, not "replace with the
|
||||
//project's default new-conductor text" (which happens to
|
||||
//default to a literal "_" character, unrelated to whether the
|
||||
//user wanted this copy's old label kept or cleared; see
|
||||
//issue #413).
|
||||
const QList <Conductor *> conductors_list = content.m_conductors_to_move;
|
||||
for (Conductor *c : conductors_list)
|
||||
{
|
||||
ConductorProperties cp = c -> properties();
|
||||
cp.text = c->diagram() ? c -> diagram() -> defaultConductorProperties.text : "_";
|
||||
cp.text = "";
|
||||
c -> setProperties(cp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,17 @@
|
||||
#include "../conductorautonumerotation.h"
|
||||
#include "../diagram.h"
|
||||
#include "../undocommand/addgraphicsobjectcommand.h"
|
||||
#include "../undocommand/deleteqgraphicsitemcommand.h"
|
||||
#include "../factory/elementfactory.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetdiagrameditor.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qet.h"
|
||||
#include "../autobreakconductor.h"
|
||||
#include <QPainterPath>
|
||||
#include <limits>
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddElement::DiagramEventAddElement
|
||||
@@ -248,16 +254,32 @@ void DiagramEventAddElement::addElement()
|
||||
QUndoCommand *undo_object = new QUndoCommand(tr("Ajouter %1").arg(element->name()));
|
||||
new AddGraphicsObjectCommand(element, m_diagram, m_element -> pos(), undo_object);
|
||||
|
||||
//When we search for free aligned terminal we temporally remove m_element to
|
||||
//avoid any interaction with the function Element::AlignedFreeTerminals
|
||||
//This is useful when an element has two (or more) terminals on opposite sides,
|
||||
//because m_element is exactly at the same pos of the new element
|
||||
//added to the scene so new conductor are created between terminal of the new element
|
||||
//and the opposite terminal of m_element.
|
||||
//When we search for free aligned terminal we temporally remove m_element to
|
||||
//avoid any interaction with the function Element::AlignedFreeTerminals
|
||||
//This is useful when an element has two (or more) terminals on opposite sides,
|
||||
//because m_element is exactly at the same pos of the new element
|
||||
//added to the scene so new conductor are created between terminal of the new element
|
||||
//and the opposite terminal of m_element.
|
||||
m_diagram->removeItem(m_element);
|
||||
while (!element -> AlignedFreeTerminals().isEmpty() && m_diagram -> project() -> autoConductor())
|
||||
|
||||
//Auto break conductor: if a terminal of the new element lies on an existing
|
||||
//conductor, break the conductor and reconnect through the new element's terminal.
|
||||
//Track the endpoints of broken conductors so auto-connect doesn't create duplicates.
|
||||
QList<Conductor *> conductors_handled;
|
||||
QSet<Terminal *> used_terminals;
|
||||
QSet<Terminal *> broken_endpoints = autoBreakConductors(m_diagram, element, undo_object,
|
||||
conductors_handled, used_terminals);
|
||||
|
||||
//Auto-connect: collect all aligned pairs first, then filter and process.
|
||||
QList<QPair<Terminal *, Terminal *>> aligned_pairs;
|
||||
if (m_diagram->project()->autoConductor())
|
||||
aligned_pairs = element->AlignedFreeTerminals();
|
||||
|
||||
for (const QPair<Terminal *, Terminal *> &pair : aligned_pairs)
|
||||
{
|
||||
QPair <Terminal *, Terminal *> pair = element -> AlignedFreeTerminals().takeFirst();
|
||||
//Skip if the other terminal was an endpoint of a broken conductor
|
||||
if (broken_endpoints.contains(pair.second))
|
||||
continue;
|
||||
|
||||
Conductor *conductor = new Conductor(pair.first, pair.second);
|
||||
new AddGraphicsObjectCommand(conductor, m_diagram, QPointF(), undo_object);
|
||||
|
||||
@@ -19,10 +19,14 @@
|
||||
#include "diagrameventaddimage.h"
|
||||
|
||||
#include "../qetapp.h"
|
||||
#include "../qetdiagrameditor.h"
|
||||
#include "../diagram.h"
|
||||
#include "../undocommand/addgraphicsobjectcommand.h"
|
||||
#include "../qetgraphicsitem/diagramimageitem.h"
|
||||
|
||||
#include <QStatusBar>
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddImage::DiagramEventAddImage
|
||||
Default constructor
|
||||
@@ -34,6 +38,16 @@ DiagramEventAddImage::DiagramEventAddImage(Diagram *diagram) :
|
||||
m_is_added (false)
|
||||
{
|
||||
openDialog();
|
||||
if (m_running)
|
||||
{
|
||||
// Deferred for the same reason as the shape tools' own
|
||||
// constructor-time hint: Diagram::setEventInterface() destroys
|
||||
// whatever tool was previously active *after* this constructor
|
||||
// returns, and that tool's own destructor clears the status bar
|
||||
// -- an immediate show here would just get wiped out moments
|
||||
// later by that cleanup.
|
||||
QTimer::singleShot(0, this, [this]() { showHint(); });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,33 +61,61 @@ DiagramEventAddImage::~DiagramEventAddImage()
|
||||
delete m_image;
|
||||
}
|
||||
|
||||
if (!m_diagram->views().isEmpty())
|
||||
{
|
||||
if (auto *editor = QETApp::diagramEditorAncestorOf(m_diagram->views().constFirst()))
|
||||
editor->statusBar()->clearMessage();
|
||||
}
|
||||
|
||||
foreach (QGraphicsView *view, m_diagram->views())
|
||||
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddImage::showHint
|
||||
Re-asserted on every move (see mouseMoveEvent), not just once at
|
||||
activation: Qt's own built-in "show an action's statusTip on hover"
|
||||
has its own internal "restore whatever was there before" logic for
|
||||
when the hover ends. Since this message is first shown *during* that
|
||||
same hover session (the user is still over the toolbar icon when the
|
||||
deferred constructor-time call above fires), Qt's hover-tracking has
|
||||
no idea this code changed the status bar in the meantime -- the
|
||||
moment the mouse leaves the icon for the canvas, it silently
|
||||
restores whatever it remembers being there before its own tip
|
||||
started, overwriting this one. Re-showing it on every move within
|
||||
the canvas simply outlasts that one-time restore -- the exact same
|
||||
issue already found and fixed for the shape tools.
|
||||
*/
|
||||
void DiagramEventAddImage::showHint() const
|
||||
{
|
||||
if (m_diagram->views().isEmpty())
|
||||
return;
|
||||
if (auto *editor = QETApp::diagramEditorAncestorOf(m_diagram->views().constFirst()))
|
||||
editor->statusBar()->showMessage(tr("Clic : positionner à la taille d'origine. "
|
||||
"Cliquer-glisser : positionner et redimensionner. "
|
||||
"Clic droit : pivoter de 90°. Ctrl+molette : ajuster la taille."));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddImage::mousePressEvent
|
||||
Action when mouse is pressed
|
||||
Left button: starts a potential drag-to-resize, anchored here -- but
|
||||
doesn't commit to anything yet. A quick click-release (see
|
||||
mouseMoveEvent's threshold check) still places the image at its
|
||||
original size, matching the previous behavior exactly; only an
|
||||
actual drag switches to resizing. Right button still rotates in 90
|
||||
degree steps, unchanged, and only while not already left-dragging.
|
||||
@param event : event of mouse pressed
|
||||
*/
|
||||
void DiagramEventAddImage::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (m_image && event -> button() == Qt::LeftButton)
|
||||
if (m_image && event->button() == Qt::LeftButton)
|
||||
{
|
||||
QPointF pos = event->scenePos();
|
||||
pos.rx() -= m_image->boundingRect().width()/2;
|
||||
pos.ry() -= m_image->boundingRect().height()/2;
|
||||
m_diagram -> undoStack().push (new AddGraphicsObjectCommand(m_image, m_diagram, pos));
|
||||
|
||||
for (QGraphicsView *view : m_diagram->views()) {
|
||||
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
emit finish();
|
||||
m_pressed = true;
|
||||
m_resize_engaged = false;
|
||||
m_press_pos = event->scenePos();
|
||||
event->setAccepted(true);
|
||||
}
|
||||
else if (m_image && event -> button() == Qt::RightButton)
|
||||
else if (m_image && !m_pressed && event->button() == Qt::RightButton)
|
||||
{
|
||||
m_image->setRotation(m_image->rotation() + 90);
|
||||
event->setAccepted(true);
|
||||
@@ -87,26 +129,95 @@ void DiagramEventAddImage::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
*/
|
||||
void DiagramEventAddImage::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!m_image || event->buttons() != Qt::NoButton) {
|
||||
if (!m_image) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
showHint();
|
||||
|
||||
QPointF pos = event->scenePos();
|
||||
|
||||
|
||||
if (!m_is_added)
|
||||
{
|
||||
for (QGraphicsView *view : m_diagram->views()) {
|
||||
view->setContextMenuPolicy((Qt::NoContextMenu));
|
||||
}
|
||||
|
||||
|
||||
m_diagram->addItem(m_image);
|
||||
m_is_added = true;
|
||||
}
|
||||
|
||||
m_image->setPos(pos - m_image->boundingRect().center());
|
||||
|
||||
if (m_pressed)
|
||||
{
|
||||
// Anchored on m_press_pos, not the item's own current position:
|
||||
// dragging in any direction has to visibly grow the image from
|
||||
// where the click started, not from wherever the "no button
|
||||
// held" preview phase happened to leave it centered.
|
||||
const QPointF delta = pos - m_press_pos;
|
||||
if (!m_resize_engaged && QLineF(m_press_pos, pos).length() >= 4.0)
|
||||
m_resize_engaged = true; // latched: crossing back within the threshold afterward must not un-engage it
|
||||
|
||||
if (!m_resize_engaged)
|
||||
{
|
||||
// Still just a (so far) plain click -- keep behaving like
|
||||
// the pre-drag preview: original size, centered here, so
|
||||
// releasing right now reproduces the old click-to-place
|
||||
// behavior exactly.
|
||||
m_image->setPos(m_press_pos - m_image->boundingRect().center());
|
||||
}
|
||||
else
|
||||
{
|
||||
const QSizeF naturalSize = m_image->boundingRect().size();
|
||||
if (naturalSize.width() > 0 && naturalSize.height() > 0)
|
||||
{
|
||||
const qreal scaleX = qAbs(delta.x()) / naturalSize.width();
|
||||
const qreal scaleY = qAbs(delta.y()) / naturalSize.height();
|
||||
// The larger of the two, not a per-axis stretch: images
|
||||
// only support a single uniform scale today (see
|
||||
// boundingRect()/paint(), which never touch aspect
|
||||
// ratio), so this is a diagonal-drag size, not a
|
||||
// free-form one -- breaking aspect ratio on purpose is
|
||||
// its own, separate, larger piece of work.
|
||||
const qreal newScale = qBound(0.01, qMax(scaleX, scaleY), 50.0);
|
||||
m_image->setScale(newScale);
|
||||
}
|
||||
m_image->setPos(qMin(m_press_pos.x(), pos.x()), qMin(m_press_pos.y(), pos.y()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_image->setPos(pos - m_image->boundingRect().center());
|
||||
}
|
||||
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddImage::mouseReleaseEvent
|
||||
Left button release commits whatever mouseMoveEvent last set --
|
||||
original size and centered if the press never turned into a real
|
||||
drag, or the dragged-out size and position otherwise. Either way,
|
||||
this is the only place placement is actually finalized now; a plain
|
||||
click no longer finishes inside mousePressEvent itself, since it has
|
||||
to wait and see whether a drag follows.
|
||||
@param event : event of mouse release
|
||||
*/
|
||||
void DiagramEventAddImage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (m_image && m_pressed && event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_diagram->undoStack().push(new AddGraphicsObjectCommand(m_image, m_diagram, m_image->pos()));
|
||||
|
||||
for (QGraphicsView *view : m_diagram->views()) {
|
||||
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
emit finish();
|
||||
event->setAccepted(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddImage::mouseDoubleClickEvent
|
||||
This method is used only to overwrite double click.
|
||||
@@ -124,7 +235,14 @@ void DiagramEventAddImage::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event
|
||||
*/
|
||||
void DiagramEventAddImage::wheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
if (!m_is_added || !m_image || event -> modifiers() != Qt::CTRL) {
|
||||
// !m_pressed added alongside the modifier fix: without it, wheel
|
||||
// scaling could fight with an active drag-resize, both trying to
|
||||
// set scale() from different sources in the same gesture.
|
||||
// event->modifiers() & Qt::ControlModifier, not != Qt::CTRL: the
|
||||
// same exact-equality bug already found and fixed several times
|
||||
// this session elsewhere -- Ctrl held together with any other
|
||||
// modifier would silently fail to register as Ctrl at all.
|
||||
if (!m_is_added || !m_image || m_pressed || !(event->modifiers() & Qt::ControlModifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "diagrameventinterface.h"
|
||||
|
||||
#include <QPointF>
|
||||
|
||||
class Diagram;
|
||||
class DiagramImageItem;
|
||||
|
||||
@@ -37,15 +39,20 @@ class DiagramEventAddImage : public DiagramEventInterface
|
||||
|
||||
void mousePressEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
|
||||
|
||||
bool isNull () const;
|
||||
private:
|
||||
void openDialog();
|
||||
void showHint() const;
|
||||
|
||||
DiagramImageItem *m_image;
|
||||
bool m_is_added;
|
||||
bool m_pressed = false; // left button held: dragging out a size, not just positioning
|
||||
bool m_resize_engaged = false; // latched once the drag threshold is crossed, matching the pen tool's own curve-drag threshold convention -- so dragging out and back near the start point doesn't "snap back" to original size before release
|
||||
QPointF m_press_pos; // scene position of the left-button press, the resize anchor
|
||||
};
|
||||
|
||||
#endif // DIAGRAMEVENTADDIMAGE_H
|
||||
|
||||
@@ -43,11 +43,7 @@ m_preview_item(nullptr)
|
||||
QString file_name = (last_slash != -1) ? path.mid(last_slash + 1) : path;
|
||||
|
||||
if (!dir_path.isEmpty()) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList parts = dir_path.split('/', QString::SkipEmptyParts);
|
||||
#else
|
||||
QStringList parts = dir_path.split('/', Qt::SkipEmptyParts);
|
||||
#endif
|
||||
QString current_path = "";
|
||||
for (const QString &part : parts) {
|
||||
QString parent_path = current_path;
|
||||
@@ -202,11 +198,7 @@ bool DiagramEventAddMacro::loadMacro()
|
||||
QString file_name = (last_slash != -1) ? path.mid(last_slash + 1) : path;
|
||||
|
||||
if (!dir_path.isEmpty()) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList parts = dir_path.split('/', QString::SkipEmptyParts);
|
||||
#else
|
||||
QStringList parts = dir_path.split('/', Qt::SkipEmptyParts);
|
||||
#endif
|
||||
QString current_path = "";
|
||||
for (const QString &part : parts) {
|
||||
QString parent_path = current_path;
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "diagrameventaddpath.h"
|
||||
|
||||
#include "../diagram.h"
|
||||
#include "../lastusedstyle.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetdiagrameditor.h"
|
||||
#include "../undocommand/addgraphicsobjectcommand.h"
|
||||
|
||||
#include <QGraphicsLineItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineF>
|
||||
#include <QStatusBar>
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::DiagramEventAddPath
|
||||
@param diagram : the diagram where this event must operate
|
||||
*/
|
||||
DiagramEventAddPath::DiagramEventAddPath(Diagram *diagram) :
|
||||
DiagramEventInterface(diagram),
|
||||
m_shape_item (nullptr),
|
||||
m_help_horiz (nullptr),
|
||||
m_help_verti (nullptr)
|
||||
{
|
||||
m_running = true;
|
||||
init();
|
||||
// Deferred for the same reason as DiagramEventAddShape's own
|
||||
// constructor-time hint: Diagram::setEventInterface() destroys
|
||||
// whatever tool was previously active *after* this constructor
|
||||
// returns, and that tool's own destructor clears the status bar --
|
||||
// an immediate show here would just get wiped out moments later.
|
||||
QTimer::singleShot(0, this, [this]() { showHint(); });
|
||||
}
|
||||
|
||||
DiagramEventAddPath::~DiagramEventAddPath()
|
||||
{
|
||||
if ((m_running || m_abort) && m_shape_item)
|
||||
{
|
||||
m_diagram->removeItem(m_shape_item);
|
||||
delete m_shape_item;
|
||||
}
|
||||
delete m_help_horiz;
|
||||
delete m_help_verti;
|
||||
|
||||
if (m_diagram && !m_diagram->views().isEmpty())
|
||||
{
|
||||
if (auto *editor = QETApp::diagramEditorAncestorOf(m_diagram->views().constFirst()))
|
||||
editor->statusBar()->clearMessage();
|
||||
}
|
||||
|
||||
foreach (QGraphicsView *v, m_diagram->views())
|
||||
v->setContextMenuPolicy(Qt::DefaultContextMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::showHint
|
||||
Re-asserted on every move within the canvas (see mouseMoveEvent), not
|
||||
just once at activation: Qt's own built-in "show an action's
|
||||
statusTip on hover" has its own internal "restore whatever was there
|
||||
before" logic for when the hover ends. Since this message is first
|
||||
shown *during* that same hover session (the user is still over the
|
||||
toolbar icon when the deferred constructor-time call above fires),
|
||||
Qt's hover-tracking has no idea this code changed the status bar in
|
||||
the meantime -- the moment the mouse leaves the icon for the canvas,
|
||||
it silently restores whatever it remembers being there before its
|
||||
own tip started, overwriting this one. Re-showing it on every move
|
||||
within the canvas simply outlasts that one-time restore.
|
||||
*/
|
||||
void DiagramEventAddPath::showHint() const
|
||||
{
|
||||
if (!m_diagram || m_diagram->views().isEmpty())
|
||||
return;
|
||||
if (auto *editor = QETApp::diagramEditorAncestorOf(m_diagram->views().constFirst()))
|
||||
editor->statusBar()->showMessage(tr("Clic: point anguleux. Cliquer-glisser: point courbe. "
|
||||
"Clic sur le premier point: fermer. Échap/Entrée: terminer. "
|
||||
"Clic droit: annuler le dernier point."));
|
||||
}
|
||||
|
||||
void DiagramEventAddPath::init()
|
||||
{
|
||||
foreach (QGraphicsView *v, m_diagram->views())
|
||||
v->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
}
|
||||
|
||||
QPointF DiagramEventAddPath::snapped(const QPointF &scenePos, Qt::KeyboardModifiers mods) const
|
||||
{
|
||||
return mods == Qt::ControlModifier ? scenePos : Diagram::snapToGrid(scenePos);
|
||||
}
|
||||
|
||||
int DiagramEventAddPath::confirmedNodeCount() const
|
||||
{
|
||||
// The trailing element is always the live preview while m_shape_item
|
||||
// exists; with no shape yet there are no nodes of any kind.
|
||||
return m_shape_item ? qMax(0, m_nodes.size() - 1) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::mousePressEvent
|
||||
Left click: on the very first click, creates the shape with a real
|
||||
node *and* an immediate preview node at the same spot, so a segment
|
||||
exists (even if zero-length) from the start rather than requiring a
|
||||
second click before anything is visible. On later clicks: either
|
||||
confirms the live preview into a real point and appends a fresh one
|
||||
for the next segment, or -- if close enough to the first node --
|
||||
closes the path.
|
||||
*/
|
||||
void DiagramEventAddPath::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (Q_UNLIKELY(m_diagram->isReadOnly()))
|
||||
return;
|
||||
|
||||
if (event->button() != Qt::LeftButton)
|
||||
{
|
||||
// Accept every button while this tool is running, not just the
|
||||
// one it actually acts on -- Diagram::mousePressEvent falls
|
||||
// through to Qt's own default scene handling for anything left
|
||||
// unaccepted, which is exactly the kind of competing control
|
||||
// this tool can't afford while it's supposed to have exclusive
|
||||
// ownership of input.
|
||||
event->setAccepted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const QPointF pos = snapped(event->scenePos(), event->modifiers());
|
||||
|
||||
if (!m_shape_item)
|
||||
{
|
||||
m_shape_item = new QetShapeItem(pos, pos, QetShapeItem::Path);
|
||||
if (LastUsedStyle::hasShapePen())
|
||||
m_shape_item->setPen(LastUsedStyle::shapePen());
|
||||
if (LastUsedStyle::hasShapeBrush())
|
||||
m_shape_item->setBrush(LastUsedStyle::shapeBrush());
|
||||
m_diagram->addItem(m_shape_item);
|
||||
// Handles only ever get built for a selected item.
|
||||
m_shape_item->setSelected(true);
|
||||
|
||||
QetShapeItem::PathNode node;
|
||||
node.anchor = pos;
|
||||
m_nodes << node;
|
||||
m_nodes << node; // live preview, tracks the mouse from here on
|
||||
m_shape_item->setPathNodes(m_nodes);
|
||||
m_shape_item->enableNodeEditMode();
|
||||
|
||||
m_dragging_node = 0;
|
||||
event->setAccepted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirmedNodeCount() >= 2 && nearFirstNode(pos))
|
||||
{
|
||||
finishPath(true);
|
||||
event->setAccepted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm the preview node as a real point, then append a fresh
|
||||
// preview (a plain Corner, not a copy of the just-confirmed node's
|
||||
// kind/handles) for the segment after it.
|
||||
m_dragging_node = m_nodes.size() - 1;
|
||||
m_nodes[m_dragging_node].anchor = pos;
|
||||
|
||||
QetShapeItem::PathNode preview;
|
||||
preview.anchor = pos;
|
||||
m_nodes << preview;
|
||||
|
||||
m_shape_item->setPathNodes(m_nodes);
|
||||
m_shape_item->enableNodeEditMode();
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::mouseMoveEvent
|
||||
Two mutually exclusive behaviours, matching whether a button is held:
|
||||
with the left button down on a just-placed node, dragging shapes that
|
||||
node's handles (same convention as editing an existing node -- see
|
||||
QetShapeItem::dragPathControlHandle()). With no button held, the
|
||||
trailing preview node instead tracks the mouse, giving the live
|
||||
rubber-band segment.
|
||||
*/
|
||||
void DiagramEventAddPath::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
updateHelpCross(event->scenePos());
|
||||
showHint();
|
||||
|
||||
if (m_shape_item)
|
||||
{
|
||||
const QPointF pos = snapped(event->scenePos(), event->modifiers());
|
||||
|
||||
if (m_dragging_node >= 0 && (event->buttons() & Qt::LeftButton))
|
||||
{
|
||||
QetShapeItem::PathNode &node = m_nodes[m_dragging_node];
|
||||
const QPointF delta = pos - node.anchor;
|
||||
|
||||
// A small threshold so an accidental few-pixel wobble on
|
||||
// what was meant to be a plain click doesn't silently add
|
||||
// curve handles the user never intended.
|
||||
if (QLineF(QPointF(), delta).length() > 3.0)
|
||||
{
|
||||
node.kind = QetShapeItem::NodeKind::Smooth;
|
||||
node.outHandle = delta;
|
||||
node.inHandle = -delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
node.kind = QetShapeItem::NodeKind::Corner;
|
||||
node.outHandle.reset();
|
||||
node.inHandle.reset();
|
||||
}
|
||||
m_shape_item->setPathNodes(m_nodes);
|
||||
}
|
||||
else if (!(event->buttons() & Qt::LeftButton) && !m_nodes.isEmpty())
|
||||
{
|
||||
m_nodes.last().anchor = pos;
|
||||
m_shape_item->setPathNodes(m_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
// Ours unconditionally while running: a stray, unaccepted move event
|
||||
// falling through to Qt's default handling risks it dragging our
|
||||
// selected, movable in-progress shape out from under the tool.
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::mouseReleaseEvent
|
||||
Left release just ends the current node's drag phase (the trailing
|
||||
preview resumes tracking the mouse on the next move). Right release
|
||||
steps back one *confirmed* point (the preview is left alone), or
|
||||
cancels outright once only one remains, or exits the tool entirely if
|
||||
nothing is in progress at all.
|
||||
*/
|
||||
void DiagramEventAddPath::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_dragging_node = -1;
|
||||
}
|
||||
else if (event->button() == Qt::RightButton)
|
||||
{
|
||||
if (m_shape_item)
|
||||
{
|
||||
if (confirmedNodeCount() > 1)
|
||||
{
|
||||
m_nodes.remove(m_nodes.size() - 2); // the last *confirmed* node; keep the trailing preview
|
||||
m_shape_item->setPathNodes(m_nodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
cancelPath();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_running = false;
|
||||
emit finish();
|
||||
}
|
||||
}
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::mouseDoubleClickEvent
|
||||
A double-click is a press, release, press, release, doubleclick
|
||||
sequence -- the second press already confirmed the preview into a
|
||||
duplicate point (mousePressEvent can't distinguish a double-click
|
||||
from two single clicks in the same place) and appended a fresh
|
||||
preview after it. Dropping the last node here removes that fresh
|
||||
preview; finishPath()'s own trailing-preview removal then removes the
|
||||
duplicate underneath it, leaving only the genuinely-placed points.
|
||||
*/
|
||||
void DiagramEventAddPath::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (m_shape_item && event->button() == Qt::LeftButton && !m_nodes.isEmpty())
|
||||
{
|
||||
m_nodes.removeLast();
|
||||
finishPath(false);
|
||||
}
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::keyPressEvent
|
||||
Escape or Enter finish the path open once at least two real points
|
||||
exist; Escape with fewer (or none placed at all) cancels/exits
|
||||
instead, since there's nothing meaningful to keep.
|
||||
*/
|
||||
void DiagramEventAddPath::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Escape)
|
||||
{
|
||||
if (m_shape_item && confirmedNodeCount() >= 2)
|
||||
finishPath(false);
|
||||
else if (m_shape_item)
|
||||
cancelPath();
|
||||
else
|
||||
{
|
||||
m_running = false;
|
||||
emit finish();
|
||||
}
|
||||
event->accept();
|
||||
}
|
||||
else if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
|
||||
&& m_shape_item && confirmedNodeCount() >= 2)
|
||||
{
|
||||
finishPath(false);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::finishPath
|
||||
Strips the trailing live-preview node, commits the in-progress path
|
||||
onto the undo stack, and resets so the tool is ready to draw another
|
||||
one immediately -- matching every other shape tool's own behaviour
|
||||
after finishing a shape.
|
||||
*/
|
||||
void DiagramEventAddPath::finishPath(bool closed)
|
||||
{
|
||||
if (!m_shape_item)
|
||||
return;
|
||||
|
||||
if (!m_nodes.isEmpty())
|
||||
m_nodes.removeLast();
|
||||
|
||||
if (m_nodes.size() < 2)
|
||||
{
|
||||
cancelPath();
|
||||
return;
|
||||
}
|
||||
|
||||
if (closed)
|
||||
m_shape_item->setClosed(true);
|
||||
|
||||
m_shape_item->setPathNodes(m_nodes);
|
||||
m_diagram->undoStack().push(new AddGraphicsObjectCommand(m_shape_item, m_diagram));
|
||||
m_shape_item = nullptr;
|
||||
m_nodes.clear();
|
||||
m_dragging_node = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::cancelPath
|
||||
Discards the in-progress path entirely -- nothing worth keeping (an
|
||||
empty or single-point path isn't a usable shape).
|
||||
*/
|
||||
void DiagramEventAddPath::cancelPath()
|
||||
{
|
||||
if (m_shape_item)
|
||||
{
|
||||
m_diagram->removeItem(m_shape_item);
|
||||
delete m_shape_item;
|
||||
m_shape_item = nullptr;
|
||||
}
|
||||
m_nodes.clear();
|
||||
m_dragging_node = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::nearFirstNode
|
||||
m_shape_item's pos()/transform() stay at their identity defaults for
|
||||
its entire construction here -- nothing during drawing ever touches
|
||||
them -- so the first node's anchor, stored in local coordinates, is
|
||||
directly comparable to a scene position without any mapping.
|
||||
*/
|
||||
bool DiagramEventAddPath::nearFirstNode(const QPointF &scenePos) const
|
||||
{
|
||||
if (m_nodes.isEmpty())
|
||||
return false;
|
||||
return QLineF(m_nodes.first().anchor, scenePos).length() <= CLOSE_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPath::updateHelpCross
|
||||
Same crosshair guide as every other shape tool (see
|
||||
DiagramEventAddShape::updateHelpCross) -- duplicated rather than
|
||||
shared, since the two classes don't otherwise share a common base
|
||||
beyond DiagramEventInterface.
|
||||
*/
|
||||
void DiagramEventAddPath::updateHelpCross(const QPointF &p)
|
||||
{
|
||||
if (!m_help_horiz || !m_help_verti)
|
||||
{
|
||||
QPen pen;
|
||||
pen.setWidthF(0.4);
|
||||
pen.setCosmetic(true);
|
||||
pen.setColor(Diagram::background_color == Qt::darkGray ? Qt::lightGray : Qt::darkGray);
|
||||
|
||||
QRectF rect = m_diagram->border_and_titleblock.insideBorderRect();
|
||||
|
||||
if (!m_help_horiz)
|
||||
{
|
||||
m_help_horiz = new QGraphicsLineItem(rect.topLeft().x(), 0, rect.topRight().x(), 0);
|
||||
m_help_horiz->setPen(pen);
|
||||
m_diagram->addItem(m_help_horiz);
|
||||
}
|
||||
|
||||
if (!m_help_verti)
|
||||
{
|
||||
m_help_verti = new QGraphicsLineItem(0, rect.topLeft().y(), 0, rect.bottomLeft().y());
|
||||
m_help_verti->setPen(pen);
|
||||
m_diagram->addItem(m_help_verti);
|
||||
}
|
||||
}
|
||||
|
||||
QPointF point = Diagram::snapToGrid(p);
|
||||
|
||||
m_help_horiz->setY(point.y());
|
||||
m_help_verti->setX(point.x());
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef DIAGRAMEVENTADDPATH_H
|
||||
#define DIAGRAMEVENTADDPATH_H
|
||||
|
||||
#include "../qetgraphicsitem/qetshapeitem.h"
|
||||
#include "diagrameventinterface.h"
|
||||
|
||||
class QGraphicsLineItem;
|
||||
|
||||
/**
|
||||
@brief The DiagramEventAddPath class
|
||||
Pen tool: interactively draw a new Path (Bezier) shape, following the
|
||||
same vocabulary every vector editor's pen tool uses:
|
||||
- click places a Corner node;
|
||||
- press-drag-release places a Smooth node, with the drag defining a
|
||||
pair of mirrored handles (same convention as
|
||||
QetShapeItem::dragPathControlHandle's "Smooth" mirroring);
|
||||
- clicking back on the first node closes the path;
|
||||
- double-click, Enter, or Escape (once 2+ nodes exist) finishes it
|
||||
open;
|
||||
- right-click steps back one node;
|
||||
- right-click or Escape with nothing placed yet cancels the tool.
|
||||
While running, m_nodes always carries one extra "preview" node at the
|
||||
end, tracking the mouse so a live rubber-band segment is always
|
||||
visible -- confirmedNodeCount() excludes it; every public gesture
|
||||
handler is responsible for stripping it before treating the list as
|
||||
"the path so far" (see finishPath(), which does this once for every
|
||||
finishing gesture).
|
||||
*/
|
||||
class DiagramEventAddPath : public DiagramEventInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DiagramEventAddPath(Diagram *diagram);
|
||||
~DiagramEventAddPath() override;
|
||||
|
||||
void mousePressEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
void keyPressEvent (QKeyEvent *event) override;
|
||||
void init() override;
|
||||
|
||||
private:
|
||||
void updateHelpCross (const QPointF &p);
|
||||
void showHint () const;
|
||||
void finishPath (bool closed);
|
||||
void cancelPath ();
|
||||
bool nearFirstNode (const QPointF &scenePos) const;
|
||||
int confirmedNodeCount () const; // m_nodes always carries one trailing "preview" node while running; this excludes it
|
||||
QPointF snapped (const QPointF &scenePos, Qt::KeyboardModifiers mods) const;
|
||||
|
||||
QetShapeItem *m_shape_item;
|
||||
QVector<QetShapeItem::PathNode> m_nodes;
|
||||
int m_dragging_node = -1;
|
||||
QGraphicsLineItem *m_help_horiz, *m_help_verti;
|
||||
|
||||
// Scene units within which a click on an existing path is
|
||||
// treated as "on the first node" and closes the shape, rather
|
||||
// than adding yet another node right next to it.
|
||||
static constexpr qreal CLOSE_THRESHOLD = 12.0;
|
||||
};
|
||||
|
||||
#endif // DIAGRAMEVENTADDPATH_H
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "diagrameventaddpdf.h"
|
||||
|
||||
// Whole file is a no-op unless QtPdf is available (see diagrameventaddpdf.h).
|
||||
#ifdef QET_HAS_QTPDF
|
||||
|
||||
#include "../qetapp.h"
|
||||
#include "../diagram.h"
|
||||
#include "../undocommand/addgraphicsobjectcommand.h"
|
||||
#include "../qetgraphicsitem/diagramimageitem.h"
|
||||
#include "../ui/pdfpagesdialog.h"
|
||||
|
||||
#include <QPdfDocument>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::DiagramEventAddPdf
|
||||
Constructor
|
||||
@param diagram the diagram where this event operates
|
||||
*/
|
||||
DiagramEventAddPdf::DiagramEventAddPdf(Diagram *diagram) :
|
||||
DiagramEventInterface(diagram),
|
||||
m_image(nullptr),
|
||||
m_is_added(false)
|
||||
{
|
||||
openDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::~DiagramEventAddPdf
|
||||
Destructor
|
||||
*/
|
||||
DiagramEventAddPdf::~DiagramEventAddPdf()
|
||||
{
|
||||
if (m_running || m_abort)
|
||||
{
|
||||
if (m_is_added) m_diagram->removeItem(m_image);
|
||||
delete m_image;
|
||||
}
|
||||
|
||||
foreach (QGraphicsView *view, m_diagram->views())
|
||||
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::mousePressEvent
|
||||
Action when mouse is pressed
|
||||
@param event event of mouse pressed
|
||||
*/
|
||||
void DiagramEventAddPdf::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (m_image && event->button() == Qt::LeftButton)
|
||||
{
|
||||
QPointF pos = event->scenePos();
|
||||
pos.rx() -= m_image->boundingRect().width()/2;
|
||||
pos.ry() -= m_image->boundingRect().height()/2;
|
||||
m_diagram->undoStack().push(new AddGraphicsObjectCommand(m_image, m_diagram, pos));
|
||||
|
||||
for (QGraphicsView *view : m_diagram->views()) {
|
||||
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
emit finish();
|
||||
event->setAccepted(true);
|
||||
}
|
||||
else if (m_image && event->button() == Qt::RightButton)
|
||||
{
|
||||
m_image->setRotation(m_image->rotation() + 90);
|
||||
event->setAccepted(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::mouseMoveEvent
|
||||
Action when mouse moves
|
||||
@param event event of mouse move
|
||||
*/
|
||||
void DiagramEventAddPdf::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!m_image || event->buttons() != Qt::NoButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPointF pos = event->scenePos();
|
||||
|
||||
if (!m_is_added)
|
||||
{
|
||||
for (QGraphicsView *view : m_diagram->views()) {
|
||||
view->setContextMenuPolicy((Qt::NoContextMenu));
|
||||
}
|
||||
|
||||
m_diagram->addItem(m_image);
|
||||
m_is_added = true;
|
||||
}
|
||||
|
||||
m_image->setPos(pos - m_image->boundingRect().center());
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::mouseDoubleClickEvent
|
||||
Overwrite double click to prevent opening properties dialog.
|
||||
@param event event of mouse double click
|
||||
*/
|
||||
void DiagramEventAddPdf::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::wheelEvent
|
||||
Action when mouse wheel is rotated (CTRL+wheel to scale)
|
||||
@param event event of mouse wheel
|
||||
*/
|
||||
void DiagramEventAddPdf::wheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
if (!m_is_added || !m_image || event->modifiers() != Qt::CTRL) {
|
||||
return;
|
||||
}
|
||||
|
||||
qreal scaling = m_image->scale();
|
||||
event->delta() > 1 ? scaling += 0.01 : scaling -= 0.01;
|
||||
if (scaling > 0.01 && scaling <= 2) {
|
||||
m_image->setScale(scaling);
|
||||
}
|
||||
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::isNull
|
||||
@return true if the PDF image couldn't be loaded, false otherwise
|
||||
*/
|
||||
bool DiagramEventAddPdf::isNull() const
|
||||
{
|
||||
if (!m_image) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramEventAddPdf::openDialog
|
||||
Opens a file dialog to select a PDF file, then opens a page selection
|
||||
dialog with DPI options. The selected page is rendered to a QImage at
|
||||
the chosen DPI and converted to a DiagramImageItem.
|
||||
*/
|
||||
void DiagramEventAddPdf::openDialog()
|
||||
{
|
||||
if (m_diagram->isReadOnly()) return;
|
||||
|
||||
// Open file dialog to select a PDF file
|
||||
QString pathPDFs = QETApp::documentDir();
|
||||
QString fileName = QFileDialog::getOpenFileName(
|
||||
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
|
||||
QObject::tr("Sélectionner un fichier PDF..."),
|
||||
pathPDFs,
|
||||
QObject::tr("Fichiers PDF (*.pdf)")
|
||||
);
|
||||
|
||||
if (fileName.isEmpty()) return;
|
||||
|
||||
// Load the PDF document
|
||||
QPdfDocument document;
|
||||
document.load(fileName);
|
||||
|
||||
if (document.status() != QPdfDocument::Status::Ready)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
|
||||
QObject::tr("Erreur"),
|
||||
QObject::tr("Impossible de charger le fichier PDF.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
int pageCount = document.pageCount();
|
||||
if (pageCount <= 0)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
|
||||
QObject::tr("Erreur"),
|
||||
QObject::tr("Le fichier PDF ne contient aucune page.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always show the dialog so the user can choose page and DPI
|
||||
PdfPagesDialog dialog(document,
|
||||
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first());
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
int pageIndex = dialog.selectedPage() - 1; // Convert to 0-based index
|
||||
int dpi = dialog.selectedDpi();
|
||||
|
||||
// Calculate pixel size from PDF points at the user-selected DPI
|
||||
// PDF point = 1/72 inch
|
||||
QSizeF pageSize = document.pagePointSize(pageIndex);
|
||||
int pixelWidth = qRound((pageSize.width() / 72.0) * dpi);
|
||||
int pixelHeight = qRound((pageSize.height() / 72.0) * dpi);
|
||||
|
||||
if (pixelWidth <= 0 || pixelHeight <= 0)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
|
||||
QObject::tr("Erreur"),
|
||||
QObject::tr("Impossible de déterminer la taille de la page PDF.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Render PDF page to QImage
|
||||
QImage image = document.render(pageIndex, QSize(pixelWidth, pixelHeight));
|
||||
if (image.isNull())
|
||||
{
|
||||
QMessageBox::critical(
|
||||
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
|
||||
QObject::tr("Erreur"),
|
||||
QObject::tr("Impossible de rendre la page PDF.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill white background to handle transparent PDFs
|
||||
QImage background(image.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
background.fill(Qt::white);
|
||||
QPainter painter(&background);
|
||||
painter.drawImage(0, 0, image);
|
||||
painter.end();
|
||||
|
||||
m_image = new DiagramImageItem(QPixmap::fromImage(background));
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
#endif // QET_HAS_QTPDF
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user