mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 23:44:13 +02:00
Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c71839a291 | |||
| d9a2539702 | |||
| 22c1ec9321 | |||
| 4300a655bc | |||
| e7e2864641 | |||
| 2ed85f6fc7 | |||
| e62d43d024 | |||
| 6562498ca2 | |||
| 38e504fb7e | |||
| 4aef4d17d2 | |||
| c89243a36f | |||
| 4a83521306 | |||
| a7f406cd44 | |||
| dad6206386 | |||
| 451f8c3296 | |||
| 43a1f6a1a9 | |||
| b208a4f131 | |||
| 1c8e7ed052 | |||
| 9004db1d99 | |||
| edf483d88f | |||
| 0920e82188 | |||
| eba258f6cd | |||
| 2eed3acd3d | |||
| abd5e8978a | |||
| 43d27a9563 | |||
| d3424db23d | |||
| 0d722b6033 | |||
| 96fac96b89 | |||
| 1ec4f56a50 | |||
| 839324f231 | |||
| a756bc0722 | |||
| 2269a4641b | |||
| 7cd04f2a14 | |||
| 265aa44320 | |||
| 395c6f6602 | |||
| fd38f55724 | |||
| 85ed1b8a2a | |||
| 607eb4b1ea | |||
| 1e124450f2 | |||
| 4d70fcf35c | |||
| 55c2c0df9d | |||
| b94b244919 | |||
| bd6bed8d61 | |||
| 6ed26358e8 | |||
| 6a2b3973bc | |||
| d9db6e59e7 | |||
| 22469813fe | |||
| 88823ea35f | |||
| 9363e9bc2e | |||
| a5fe544415 | |||
| 9bfe18807f | |||
| 199444b6db | |||
| bdd52a0e2b | |||
| 86ce8fcd92 | |||
| 428687ee4b | |||
| 512d74c745 | |||
| 561b9c4eb1 | |||
| 00e75604b0 | |||
| ceda1e082a | |||
| ce890da342 | |||
| 2785e25569 | |||
| c8e4396b2a | |||
| 0147453494 | |||
| 181bbb7f21 | |||
| 3665ec1bcd | |||
| fcd2a4e0e0 | |||
| 3c1c29f6c3 | |||
| cafc5bcf6d | |||
| 3cbb930751 | |||
| 51209d30b6 | |||
| 9c55d36d55 | |||
| 3ac557570c | |||
| 0c2cf409f8 | |||
| 467a14df71 | |||
| 1b6768fcec | |||
| a6fd42ac5f | |||
| ce17b5c984 | |||
| b46a4cfc2a | |||
| 00d8822583 | |||
| 8edda68e96 | |||
| ad9a47602d | |||
| b034d3c5b3 | |||
| dd08b3daa5 | |||
| 2bdad43371 | |||
| c05033d44c | |||
| 15c893de89 | |||
| 3e7dd15392 | |||
| 061f07ef9e | |||
| cb0d8c8f63 | |||
| f9d063bd38 | |||
| fab5571ea2 | |||
| a0acf878a2 | |||
| 21588a9c10 | |||
| fcbea2dafd | |||
| 1272b9db06 | |||
| 6e8d821853 | |||
| e64c0618c8 | |||
| 3af551583c | |||
| b274170cff | |||
| bd7d60021c | |||
| 12f443dd89 | |||
| 9afba7091c | |||
| 3d5799773e | |||
| 9c3ab588a4 | |||
| 3a26cc4b86 | |||
| 88f5af08d7 | |||
| 29e4817254 | |||
| c8b5901296 | |||
| f8cb997f3b | |||
| 1e8f5d8a72 | |||
| ab8b85127f | |||
| 52d5c7572c | |||
| 30661c2e2d | |||
| d74317c87a | |||
| 256993e25b | |||
| b18904415e | |||
| 18477c22b5 | |||
| 128d9e8dff | |||
| 6eb5384048 | |||
| 4e9463ee8a | |||
| baa3a16614 | |||
| 32502bef2c | |||
| c13971c0fe | |||
| 4e8893929f | |||
| 59c51869b3 | |||
| f6f68871d1 | |||
| 20a0a888c7 | |||
| d2e0532b12 | |||
| a8df5e3df9 | |||
| 73250bd746 | |||
| 956836458a | |||
| 2c88f78f69 | |||
| f85ee19fc0 | |||
| 2f415117f7 | |||
| 7c0e867226 | |||
| 0b7197118a | |||
| 77f8e26ffc | |||
| 16dbc5c404 | |||
| 73f7ea8f50 | |||
| 99e9ce5aca | |||
| bc06f7d444 | |||
| 2620d34b59 | |||
| 4b675dda1b | |||
| e982778526 | |||
| 10e5cd00f4 | |||
| 6bc53277ec | |||
| 7d4cbc30f1 | |||
| 2fcf3540dd | |||
| a45a984b32 | |||
| 1f647fa229 | |||
| c4d2f0c4fb | |||
| ac47f52eb0 | |||
| d2e75b4195 | |||
| bbc995c91e | |||
| 2322e6fd12 | |||
| b5722c3f2a | |||
| 2926ca7306 | |||
| c7893c8229 | |||
| 412bc7f71f | |||
| c147e6562d | |||
| bee25a4ca9 | |||
| 6d09041dce | |||
| 1a12d440b2 | |||
| 85f46f2b48 | |||
| 9637420756 | |||
| 6c68c461b1 | |||
| c830101ba6 | |||
| a1c0907811 | |||
| ffd829bb69 | |||
| 957bbe5edb | |||
| ce0ba4a681 | |||
| 8295083f05 | |||
| a2441a6f81 | |||
| c1f9af8544 | |||
| 033c2f93a8 | |||
| 83623a0fa6 | |||
| c85f80bbcf | |||
| c6995a0e7e | |||
| f18eda845b | |||
| 73e9473db4 | |||
| 7307128fc3 | |||
| 799ff5573f | |||
| 6f2c66afef | |||
| c39c7414cf | |||
| c33f250910 | |||
| c265f0206c | |||
| cfa1e3ed27 | |||
| 36dd1624d2 | |||
| a583b3c43c | |||
| 1738c3ad6c | |||
| 82026d8f7c | |||
| a494a8fd6c | |||
| 95d7b74e9e | |||
| 68e507a522 | |||
| 6657b3de2b | |||
| 4c038a868b | |||
| dab4c6c424 | |||
| 6208c7e5df | |||
| 31feb7b7b0 | |||
| 622791f994 | |||
| 02e6eceb65 | |||
| 7c941e4697 | |||
| a48124a27a | |||
| e19d60ae55 | |||
| 030e6ebf00 | |||
| b855d760a8 | |||
| 5b8d05fc1e | |||
| b034b1a634 | |||
| 44ed01ff5d | |||
| 6061c63809 | |||
| 884ec4bbb9 | |||
| 9285d12333 | |||
| fb0649ceee | |||
| 6f9f5f84c2 | |||
| 5dbb9f28de | |||
| c26cf34397 | |||
| c6e7c5d371 | |||
| 7347322221 | |||
| c23999acd1 | |||
| 7afc1eb56b | |||
| 0c2027fd77 | |||
| f9cbf2365d | |||
| 52992b31eb | |||
| d7c75ea5a5 | |||
| b9153269a4 | |||
| eab9603d8a | |||
| e7cbb8f50e | |||
| e3d11a4992 |
@@ -56,6 +56,7 @@ jobs:
|
|||||||
mingw-w64-ucrt-x86_64-extra-cmake-modules
|
mingw-w64-ucrt-x86_64-extra-cmake-modules
|
||||||
mingw-w64-ucrt-x86_64-nsis
|
mingw-w64-ucrt-x86_64-nsis
|
||||||
mingw-w64-ucrt-x86_64-angleproject
|
mingw-w64-ucrt-x86_64-angleproject
|
||||||
|
mingw-w64-ucrt-x86_64-qt6-declarative
|
||||||
|
|
||||||
- name: Cache ccache
|
- name: Cache ccache
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
@@ -217,10 +218,71 @@ jobs:
|
|||||||
cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true
|
cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true
|
||||||
cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true
|
cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true
|
||||||
|
|
||||||
cp -r "$GITHUB_WORKSPACE/lang" "$FILES/lang" || true
|
# --- Translations ---
|
||||||
find "$GITHUB_WORKSPACE/build" -name "*.qm" -exec cp {} "$FILES/lang/" \; 2>/dev/null || true
|
# Since PR #751 the .qm files are no longer tracked in git: lrelease
|
||||||
echo "=== .qm files in files/lang/ ==="
|
# generates them in build/lang/ (OUTPUT_LOCATION "lang", relative to
|
||||||
ls "$FILES/lang/"*.qm 2>/dev/null | wc -l || echo "0 .qm files"
|
# the build dir). Expected set = the .ts listed in TS_FILES
|
||||||
|
# (cmake/qet_compilation_vars.cmake): a missing .qm fails the job
|
||||||
|
# instead of silently shipping an untranslated build; a .ts present
|
||||||
|
# in lang/ but not listed in TS_FILES only raises a warning.
|
||||||
|
# find/grep only, no shell glob: $GITHUB_WORKSPACE is a Windows path
|
||||||
|
# (D:\a\...) and its backslashes break glob patterns.
|
||||||
|
QM_SRC="$GITHUB_WORKSPACE/build/lang"
|
||||||
|
mkdir -p "$FILES/lang"
|
||||||
|
TS_LISTED=$(grep -o 'lang/qet_[A-Za-z_]*\.ts' "$GITHUB_WORKSPACE/cmake/qet_compilation_vars.cmake" \
|
||||||
|
| sed -e 's#^lang/##' -e 's#\.ts$##' | LC_ALL=C sort -u || true)
|
||||||
|
[ -n "$TS_LISTED" ] || { echo "ERROR: cannot read TS_FILES from cmake/qet_compilation_vars.cmake"; exit 1; }
|
||||||
|
TS_PRESENT=$(find "$GITHUB_WORKSPACE/lang" -maxdepth 1 -name 'qet_*.ts' -exec basename {} .ts \; | LC_ALL=C sort)
|
||||||
|
UNLISTED=$(LC_ALL=C comm -13 <(echo "$TS_LISTED") <(echo "$TS_PRESENT"))
|
||||||
|
if [ -n "$UNLISTED" ]; then
|
||||||
|
echo "::warning::.ts files not in TS_FILES, no .qm built:" $UNLISTED
|
||||||
|
fi
|
||||||
|
find "$QM_SRC" -maxdepth 1 -name 'qet_*.qm' -exec cp {} "$FILES/lang/" \; 2>/dev/null || true
|
||||||
|
QM_PRESENT=$(find "$FILES/lang" -maxdepth 1 -name 'qet_*.qm' -exec basename {} .qm \; | LC_ALL=C sort)
|
||||||
|
MISSING=$(LC_ALL=C comm -23 <(echo "$TS_LISTED") <(echo "$QM_PRESENT"))
|
||||||
|
QM_COUNT=$(printf '%s\n' "$QM_PRESENT" | grep -c . || true)
|
||||||
|
TS_COUNT=$(printf '%s\n' "$TS_LISTED" | grep -c .)
|
||||||
|
echo "=== $QM_COUNT .qm files copied to files/lang/ (expected: $TS_COUNT) ==="
|
||||||
|
if [ -n "$MISSING" ]; then
|
||||||
|
echo "ERROR: missing translations:" $MISSING
|
||||||
|
find "$GITHUB_WORKSPACE/build" -name '*.qm' || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Qt's own translations (OK/Cancel buttons, standard dialogs...):
|
||||||
|
# they come from qtbase_XX.qm, not from QET's .ts, and windeployqt
|
||||||
|
# runs with --no-translations. QETApp::setLanguage() falls back to
|
||||||
|
# lang/qt_XX.qm, so copy each qtbase_XX.qm there under that name
|
||||||
|
# (qtbase_XX.qm is standalone, unlike Qt's qt_XX.qm meta catalogs).
|
||||||
|
QT_TR_DIR=/ucrt64/share/qt6/translations
|
||||||
|
if [ ! -d "$QT_TR_DIR" ]; then
|
||||||
|
QT_TR_DIR=$(cygpath -u "$(/ucrt64/bin/qtpaths6 --query QT_INSTALL_TRANSLATIONS 2>/dev/null)" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
find "$QT_TR_DIR" -maxdepth 1 -name 'qtbase_*.qm' 2>/dev/null | while read -r f; do
|
||||||
|
l=$(basename "$f" .qm)
|
||||||
|
cp "$f" "$FILES/lang/qt_${l#qtbase_}.qm"
|
||||||
|
done
|
||||||
|
# QET languages Qt only ships with a region (pt -> pt_PT, zh -> zh_CN):
|
||||||
|
# QTranslator only shortens codes (fr_FR -> fr), it never extends them.
|
||||||
|
for q in $TS_LISTED; do
|
||||||
|
l=${q#qet_}
|
||||||
|
if [ ! -e "$FILES/lang/qt_$l.qm" ]; then
|
||||||
|
if [ -e "$QT_TR_DIR/qtbase_${l}_${l^^}.qm" ]; then
|
||||||
|
alt="$QT_TR_DIR/qtbase_${l}_${l^^}.qm"
|
||||||
|
else
|
||||||
|
alt=$(find "$QT_TR_DIR" -maxdepth 1 -name "qtbase_${l}_*.qm" 2>/dev/null | LC_ALL=C sort | head -1 || true)
|
||||||
|
fi
|
||||||
|
if [ -n "$alt" ]; then
|
||||||
|
cp "$alt" "$FILES/lang/qt_$l.qm"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
QT_QM_COUNT=$(find "$FILES/lang" -maxdepth 1 -name 'qt_*.qm' | wc -l)
|
||||||
|
echo "=== $QT_QM_COUNT Qt translation files (qt_*.qm) copied from $QT_TR_DIR ==="
|
||||||
|
if [ "$QT_QM_COUNT" -eq 0 ]; then
|
||||||
|
echo "ERROR: no qtbase_*.qm found in '$QT_TR_DIR' (mingw-w64-ucrt-x86_64-qt6-translations installed?)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE; do
|
for f in LICENSE ChangeLog CREDIT README ELEMENTS.LICENSE; do
|
||||||
cp "$GITHUB_WORKSPACE/$f" "$FILES/$f" 2>/dev/null || true
|
cp "$GITHUB_WORKSPACE/$f" "$FILES/$f" 2>/dev/null || true
|
||||||
|
|||||||
+2
-1
@@ -6,4 +6,5 @@
|
|||||||
doc/*
|
doc/*
|
||||||
!doc/QElectroTech.qch
|
!doc/QElectroTech.qch
|
||||||
QElectroTech.tag
|
QElectroTech.tag
|
||||||
!doc/doc-utils
|
!doc/doc-utils
|
||||||
|
lang/*.qm
|
||||||
|
|||||||
+36
-4
@@ -19,7 +19,7 @@ include(cmake/hoto_update_cmake_message.cmake)
|
|||||||
cmake_minimum_required(VERSION 3.5...4.2)
|
cmake_minimum_required(VERSION 3.5...4.2)
|
||||||
|
|
||||||
project(qelectrotech
|
project(qelectrotech
|
||||||
VERSION 0.100.1
|
VERSION 0.200.1
|
||||||
DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features."
|
DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features."
|
||||||
HOMEPAGE_URL "https://qelectrotech.org/"
|
HOMEPAGE_URL "https://qelectrotech.org/"
|
||||||
LANGUAGES C CXX)
|
LANGUAGES C CXX)
|
||||||
@@ -36,7 +36,7 @@ set(QET_DIR ${PROJECT_SOURCE_DIR})
|
|||||||
# includes below, so every subdirectory and every FetchContent dependency
|
# includes below, so every subdirectory and every FetchContent dependency
|
||||||
# sees a consistent, already-defined value.
|
# sees a consistent, already-defined value.
|
||||||
if(NOT DEFINED QT_VERSION_MAJOR)
|
if(NOT DEFINED QT_VERSION_MAJOR)
|
||||||
set(QT_VERSION_MAJOR 5)
|
set(QT_VERSION_MAJOR 6)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Some third-party CMake projects we pull in via FetchContent (e.g.
|
# Some third-party CMake projects we pull in via FetchContent (e.g.
|
||||||
@@ -109,6 +109,22 @@ if(QT_VERSION_MAJOR GREATER_EQUAL 6)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# JavaScript scripting (bugtracker #162: `--run script.js`, and later a
|
||||||
|
# "Run Script..." menu action) needs QJSEngine, in the Qml module. As with
|
||||||
|
# QtPdf above, this is not fatal when missing: some minimal Qt6 packagings
|
||||||
|
# may not ship it, and scripting is optional functionality nothing else in
|
||||||
|
# the application depends on. When it's missing the feature is silently
|
||||||
|
# disabled - see the QET_HAS_SCRIPTING guard in qetscripting.*.
|
||||||
|
set(QET_HAS_SCRIPTING FALSE)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS Qml)
|
||||||
|
if(TARGET Qt${QT_VERSION_MAJOR}::Qml)
|
||||||
|
set(QET_HAS_SCRIPTING TRUE)
|
||||||
|
list(APPEND QET_PRIVATE_LIBRARIES Qt::Qml)
|
||||||
|
add_compile_definitions(QET_HAS_SCRIPTING)
|
||||||
|
else()
|
||||||
|
message(STATUS "Qt Qml module not available: JavaScript scripting (--run) disabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(SQLite3 REQUIRED)
|
find_package(SQLite3 REQUIRED)
|
||||||
|
|
||||||
# CMake < 4.3 only creates the SQLite::SQLite3 target (no SQLite3::SQLite3
|
# CMake < 4.3 only creates the SQLite::SQLite3 target (no SQLite3::SQLite3
|
||||||
@@ -134,6 +150,7 @@ include(cmake/fetch_kdeaddons.cmake)
|
|||||||
option(PACKAGE_TESTS "Build the tests" ON)
|
option(PACKAGE_TESTS "Build the tests" ON)
|
||||||
if(PACKAGE_TESTS)
|
if(PACKAGE_TESTS)
|
||||||
message("Add sub directory tests")
|
message("Add sub directory tests")
|
||||||
|
enable_testing()
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -163,7 +180,22 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE TRUE)
|
# CFBundleIdentifier must not be empty. CMake's default Info.plist
|
||||||
|
# template fills it from MACOSX_BUNDLE_GUI_IDENTIFIER; with that unset
|
||||||
|
# the bundle ships an empty identifier, LaunchServices never registers
|
||||||
|
# the .app, and AppKit's open/save panel service (which is keyed on the
|
||||||
|
# client's bundle id) silently presents nothing -- every
|
||||||
|
# QFileDialog::getOpenFileName()/getSaveFileName() call returns an empty
|
||||||
|
# string without a panel ever appearing, so File > Open and File > Save
|
||||||
|
# as do nothing at all.
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
MACOSX_BUNDLE_GUI_IDENTIFIER "org.qelectrotech.QElectroTech"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME "QElectroTech"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
|
||||||
|
MACOSX_BUNDLE_COPYRIGHT "Copyright 2006-2026 The QElectroTech Team"
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The default build only compiles the tracked .ts files to .qm (lrelease).
|
# The default build only compiles the tracked .ts files to .qm (lrelease).
|
||||||
@@ -174,7 +206,7 @@ endif()
|
|||||||
# fail with "Premature end of document".
|
# fail with "Premature end of document".
|
||||||
set_source_files_properties(
|
set_source_files_properties(
|
||||||
${TS_FILES}
|
${TS_FILES}
|
||||||
PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang"
|
PROPERTIES OUTPUT_LOCATION "lang"
|
||||||
)
|
)
|
||||||
if(QT_VERSION_MAJOR EQUAL 6)
|
if(QT_VERSION_MAJOR EQUAL 6)
|
||||||
if(Qt6_VERSION VERSION_LESS "6.2")
|
if(Qt6_VERSION VERSION_LESS "6.2")
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ PROJECT_NAME = QElectroTech
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = " 0.100.0-dev"
|
PROJECT_NUMBER = " 0.200.1"
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
+17
-13
@@ -1,3 +1,20 @@
|
|||||||
|
[ca]
|
||||||
|
La col·lecció d'elements que s'inclou amb QElectrotech es proporciona tal com és
|
||||||
|
i sense cap garantia que sigui adequada per al vostre ús o que funcioni correctament.
|
||||||
|
L'ús, la modificació i la integració d'aquests elements en esquemes elèctrics
|
||||||
|
estan permesos sense restriccions, independentment de la llicència final que regeixi
|
||||||
|
els esquemes.
|
||||||
|
No es permet utilitzar aquest programari ni cap fitxer associat
|
||||||
|
com a dades de mostra per crear models d'aprenentatge automàtic.
|
||||||
|
|
||||||
|
Si redistribuïu la totalitat o una part de la col·lecció QElectroTech,
|
||||||
|
amb o sense modificacions, fora d'un esquema elèctric, heu de complir
|
||||||
|
les condicions de la llicència CC-BY:
|
||||||
|
Aquesta obra està subjecta a la llicència Reconeixement 3.0,
|
||||||
|
disponible en línia a http://creativecommons.org/licenses/by/3.0/ o bé
|
||||||
|
sol·licitant-la per correu a Creative Commons, 171 Second Street, Suite 300, San Francisco,
|
||||||
|
Califòrnia 94105, EUA.
|
||||||
|
|
||||||
[en]
|
[en]
|
||||||
The elements collection provided along with QElectroTech is provided as is and
|
The elements collection provided along with QElectroTech is provided as is and
|
||||||
without any warranty of fitness for your purpose or working.
|
without any warranty of fitness for your purpose or working.
|
||||||
@@ -96,19 +113,6 @@ Para ver una copia de esta licencia, visite
|
|||||||
http://creativecommons.org/licenses/by/3.0/ o envie una carta a Creative
|
http://creativecommons.org/licenses/by/3.0/ o envie una carta a Creative
|
||||||
Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
|
Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
|
||||||
|
|
||||||
[ca]
|
|
||||||
La col·lecció de símbols QElectrotech és distribuïda tal qual i sense cap
|
|
||||||
garantia d'idoneïtat d'ús ni de funcionament.
|
|
||||||
Es permet incondicionalment, amb independència de la llicència final, emprar,
|
|
||||||
editar, i incloure aquests símbols en esquemes elèctrics.
|
|
||||||
Si vostè redistribueix una part de la col·lecció de QElectrotech o tota ella,
|
|
||||||
amb condicions o sense, separadament d'un esquema elèctric, haurà de respectar
|
|
||||||
les condicions de la llicència CC-BY:
|
|
||||||
Aquesta obra es troba sota una llicència Reconeixement 3.0 de Creative Commons.
|
|
||||||
Per veure una còpia d'aquesta llicència visiti
|
|
||||||
http://creativecommons.org/licenses/by/3.0/ o enviï una carta a Creative
|
|
||||||
Commons, 171 Second Street, Suite 300, San Francisco, California 94105,
|
|
||||||
|
|
||||||
[cs]
|
[cs]
|
||||||
Sbírka prvků poskytovaná společně s QElectroTechem je poskytována tak, jak je,
|
Sbírka prvků poskytovaná společně s QElectroTechem je poskytována tak, jak je,
|
||||||
bez záruky nebo vhodnosti pro váš účal nebo práci.
|
bez záruky nebo vhodnosti pro váš účal nebo práci.
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ Here are the technical choices made for the software development:
|
|||||||
* Coding language: [C++](https://en.wikipedia.org/wiki/C%2B%2B)
|
* Coding language: [C++](https://en.wikipedia.org/wiki/C%2B%2B)
|
||||||
* GUI translations: [Qt Linguist](http://doc.qt.io/qt-5/qtlinguist-index.html)
|
* GUI translations: [Qt Linguist](http://doc.qt.io/qt-5/qtlinguist-index.html)
|
||||||
* Version control: [GIT](https://github.com/qelectrotech/qelectrotech-source-mirror.git)
|
* Version control: [GIT](https://github.com/qelectrotech/qelectrotech-source-mirror.git)
|
||||||
* Doxygen documentation :[Doxygen](https://qelectrotech.github.io/qelectrotech-source-mirror/)
|
* Doxygen documentation :[Doxygen](https://download.qelectrotech.org/qet/doxygen/html/)
|
||||||
* QtCreator qch doxygen :[QElectroTech.qch](https://github.com/qelectrotech/qelectrotech-source-mirror/blob/master/doc/QElectroTech.qch)
|
* QtCreator qch doxygen :[QElectroTech.qch](https://download.qelectrotech.org/qet/doxygen/QElectroTech.qch)
|
||||||
* File format for projects, elements and titleblocks: [XML](http://www.w3schools.com/xml/xml_whatis.asp)
|
* File format for projects, elements and titleblocks: [XML](http://www.w3schools.com/xml/xml_whatis.asp)
|
||||||
* Main development platform: [GNU/Linux](http://getgnulinux.org/en/linux/)
|
* Main development platform: [GNU/Linux](http://getgnulinux.org/en/linux/)
|
||||||
* Targeted platforms: Windows, GNU/Linux, Mac OS X, BSDs
|
* Targeted platforms: Windows, GNU/Linux, Mac OS X, BSDs
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ parts:
|
|||||||
- git
|
- git
|
||||||
- sqlite3
|
- sqlite3
|
||||||
- xdg-user-dirs
|
- xdg-user-dirs
|
||||||
|
- libqt6qml6
|
||||||
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
||||||
# snap (issue #373). Not yet re-verified against kf6-core24 — remove this
|
# snap (issue #373). Not yet re-verified against kf6-core24 — remove this
|
||||||
# if the Qt6 xcb platform plugin loads cleanly without it.
|
# if the Qt6 xcb platform plugin loads cleanly without it.
|
||||||
@@ -84,7 +85,13 @@ parts:
|
|||||||
- libsqlite3-dev
|
- libsqlite3-dev
|
||||||
- qt6-tools-dev
|
- qt6-tools-dev
|
||||||
- qt6-base-private-dev
|
- qt6-base-private-dev
|
||||||
|
- qt6-declarative-dev
|
||||||
- pkgconf
|
- pkgconf
|
||||||
|
# Qt6 PrintSupport records Cups::Cups as a third-party dependency
|
||||||
|
# (qprint_p.h includes <cups/ppd.h>), so find_package(Qt6 PrintSupport)
|
||||||
|
# runs FindCups at configure time and fails without the CUPS headers.
|
||||||
|
# Build-time only: nothing from it is staged into the snap.
|
||||||
|
- libcups2-dev
|
||||||
override-build: |
|
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')
|
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)"
|
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ if(BUILD_PUGIXML)
|
|||||||
pugixml
|
pugixml
|
||||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||||
GIT_TAG v1.15)
|
GIT_TAG v1.15)
|
||||||
|
set(PUGIXML_INSTALL OFF CACHE INTERNAL "")
|
||||||
FetchContent_MakeAvailable(pugixml)
|
FetchContent_MakeAvailable(pugixml)
|
||||||
else()
|
else()
|
||||||
find_package(pugixml REQUIRED)
|
find_package(pugixml REQUIRED)
|
||||||
|
|||||||
@@ -174,7 +174,11 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/conductornumexport.cpp
|
${QET_DIR}/sources/conductornumexport.cpp
|
||||||
${QET_DIR}/sources/wiringlistexport.h
|
${QET_DIR}/sources/wiringlistexport.h
|
||||||
${QET_DIR}/sources/wiringlistexport.cpp
|
${QET_DIR}/sources/wiringlistexport.cpp
|
||||||
|
${QET_DIR}/sources/ui/wiringlistdialog.h
|
||||||
|
${QET_DIR}/sources/ui/wiringlistdialog.cpp
|
||||||
${QET_DIR}/sources/conductornumexport.h
|
${QET_DIR}/sources/conductornumexport.h
|
||||||
|
${QET_DIR}/sources/bomexport.cpp
|
||||||
|
${QET_DIR}/sources/bomexport.h
|
||||||
${QET_DIR}/sources/conductorprofile.cpp
|
${QET_DIR}/sources/conductorprofile.cpp
|
||||||
${QET_DIR}/sources/conductorprofile.h
|
${QET_DIR}/sources/conductorprofile.h
|
||||||
${QET_DIR}/sources/conductorproperties.cpp
|
${QET_DIR}/sources/conductorproperties.cpp
|
||||||
@@ -245,6 +249,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/qet.h
|
${QET_DIR}/sources/qet.h
|
||||||
${QET_DIR}/sources/qeticons.cpp
|
${QET_DIR}/sources/qeticons.cpp
|
||||||
${QET_DIR}/sources/qeticons.h
|
${QET_DIR}/sources/qeticons.h
|
||||||
|
${QET_DIR}/sources/qetpalette.cpp
|
||||||
|
${QET_DIR}/sources/qetpalette.h
|
||||||
${QET_DIR}/sources/qetinformation.cpp
|
${QET_DIR}/sources/qetinformation.cpp
|
||||||
${QET_DIR}/sources/qetinformation.h
|
${QET_DIR}/sources/qetinformation.h
|
||||||
${QET_DIR}/sources/qetmainwindow.cpp
|
${QET_DIR}/sources/qetmainwindow.cpp
|
||||||
@@ -326,6 +332,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/diagramevent/diagrameventinterface.h
|
${QET_DIR}/sources/diagramevent/diagrameventinterface.h
|
||||||
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.cpp
|
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.cpp
|
||||||
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.h
|
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.h
|
||||||
|
${QET_DIR}/sources/diagramevent/diagrameventaddpaste.cpp
|
||||||
|
${QET_DIR}/sources/diagramevent/diagrameventaddpaste.h
|
||||||
|
|
||||||
${QET_DIR}/sources/dvevent/dveventinterface.cpp
|
${QET_DIR}/sources/dvevent/dveventinterface.cpp
|
||||||
${QET_DIR}/sources/dvevent/dveventinterface.h
|
${QET_DIR}/sources/dvevent/dveventinterface.h
|
||||||
@@ -473,6 +481,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/project/projectusagetracker.cpp
|
${QET_DIR}/sources/project/projectusagetracker.cpp
|
||||||
${QET_DIR}/sources/project/projectusagetracker.h
|
${QET_DIR}/sources/project/projectusagetracker.h
|
||||||
|
|
||||||
|
${QET_DIR}/sources/properties/deviceinformation.cpp
|
||||||
|
${QET_DIR}/sources/properties/deviceinformation.h
|
||||||
${QET_DIR}/sources/properties/elementdata.cpp
|
${QET_DIR}/sources/properties/elementdata.cpp
|
||||||
${QET_DIR}/sources/properties/elementdata.h
|
${QET_DIR}/sources/properties/elementdata.h
|
||||||
${QET_DIR}/sources/properties/propertiesinterface.cpp
|
${QET_DIR}/sources/properties/propertiesinterface.cpp
|
||||||
@@ -841,6 +851,20 @@ if(QT_VERSION_MAJOR GREATER_EQUAL 6)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# JavaScript scripting (bugtracker #162). Unconditionally in the source
|
||||||
|
# list, like the QtPdf files above: this file is included before the
|
||||||
|
# QET_HAS_SCRIPTING probe runs in the top-level CMakeLists.txt, so the
|
||||||
|
# variable isn't set yet here. Same pattern as QtPdf: always compiled, the
|
||||||
|
# actual Qt::Qml dependent code is behind #ifdef QET_HAS_SCRIPTING inside
|
||||||
|
# qetscripting.cpp/qetscriptapi.cpp themselves, compiling to a harmless
|
||||||
|
# stub when the module wasn't found.
|
||||||
|
list(APPEND QET_SRC_FILES
|
||||||
|
${QET_DIR}/sources/scripting/qetscriptapi.cpp
|
||||||
|
${QET_DIR}/sources/scripting/qetscriptapi.h
|
||||||
|
${QET_DIR}/sources/scripting/qetscripting.cpp
|
||||||
|
${QET_DIR}/sources/scripting/qetscripting.h
|
||||||
|
)
|
||||||
|
|
||||||
set(TS_FILES
|
set(TS_FILES
|
||||||
${QET_DIR}/lang/qet_ar.ts
|
${QET_DIR}/lang/qet_ar.ts
|
||||||
${QET_DIR}/lang/qet_ca.ts
|
${QET_DIR}/lang/qet_ca.ts
|
||||||
@@ -856,6 +880,7 @@ set(TS_FILES
|
|||||||
${QET_DIR}/lang/qet_hu.ts
|
${QET_DIR}/lang/qet_hu.ts
|
||||||
${QET_DIR}/lang/qet_it.ts
|
${QET_DIR}/lang/qet_it.ts
|
||||||
${QET_DIR}/lang/qet_ja.ts
|
${QET_DIR}/lang/qet_ja.ts
|
||||||
|
${QET_DIR}/lang/qet_ko.ts
|
||||||
${QET_DIR}/lang/qet_mn.ts
|
${QET_DIR}/lang/qet_mn.ts
|
||||||
${QET_DIR}/lang/qet_nb.ts
|
${QET_DIR}/lang/qet_nb.ts
|
||||||
${QET_DIR}/lang/qet_nl.ts
|
${QET_DIR}/lang/qet_nl.ts
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Smart Device Information and BOM CSV
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- A `.qet` project is XML. Placed component properties are held in
|
||||||
|
`ElementData::m_informations`, a `DiagramContext`, and are serialized by the
|
||||||
|
existing `Element::fromXml()` and `Element::toXml()` path.
|
||||||
|
- `DeviceInformation` is a typed view over that existing context. It does not
|
||||||
|
introduce a second data store. Updates made by the properties editor continue
|
||||||
|
to use `ChangeElementInformationCommand`, including undo/redo and dirty state.
|
||||||
|
- The project database copies element contexts into `element_info` and exposes
|
||||||
|
reporting fields through `element_nomenclature_view`.
|
||||||
|
- GUI and CLI BOM exports both use `BomExport::defaultQuery()` against that
|
||||||
|
view. The existing `BOMExportDialog` remains the only GUI export entry point;
|
||||||
|
users can still customize columns, filters, and grouped/ungrouped output.
|
||||||
|
|
||||||
|
## Device Properties
|
||||||
|
|
||||||
|
| Device field | XML/context key | Default BOM |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Device tag | `label` | Yes |
|
||||||
|
| Manufacturer | `manufacturer` | Yes |
|
||||||
|
| Part number | `manufacturer_reference` | Yes |
|
||||||
|
| Model | `model` | Yes |
|
||||||
|
| Description | `description` | Yes |
|
||||||
|
| Category | `category` | Yes |
|
||||||
|
| Quantity | `quantity` | Yes |
|
||||||
|
| Voltage rating | `voltage_rating` | Yes |
|
||||||
|
| Current rating | `current_rating` | Yes |
|
||||||
|
| Page | `folio` from the diagram | Yes |
|
||||||
|
| Notes | `notes` | Yes |
|
||||||
|
|
||||||
|
The five new context keys are `model`, `category`, `voltage_rating`,
|
||||||
|
`current_rating`, and `notes`. They are available in the existing information
|
||||||
|
editor for Simple and Master elements, in symbol definitions, in the SQL
|
||||||
|
nomenclature view, and in the export column picker.
|
||||||
|
|
||||||
|
No XML version bump or migration is required. Missing keys read as empty
|
||||||
|
strings, empty properties are omitted by the existing writer, and unknown
|
||||||
|
context keys remain preserved. Quantity and ratings stay free text so existing
|
||||||
|
unit conventions and decimal quantities are not changed implicitly.
|
||||||
|
|
||||||
|
## BOM Policy
|
||||||
|
|
||||||
|
The default BOM query exports one row per Simple or Master element. Slave
|
||||||
|
contacts, terminal-strip elements, reports, thumbnails, and conductor
|
||||||
|
definitions are excluded. A Master device is counted once and PLC I/O entries
|
||||||
|
are not expanded. The existing `exclude_from_bom` property is honored by the
|
||||||
|
SQL view, including common true representations (`true`, `1`, `yes`, `on`).
|
||||||
|
|
||||||
|
The default columns are:
|
||||||
|
|
||||||
|
`Device tag, Designation, Manufacturer, Part number, Model, Description,
|
||||||
|
Category, Quantity, Voltage rating, Current rating, Page, Notes`.
|
||||||
|
|
||||||
|
The existing **Format as bill of materials** checkbox provides grouped output;
|
||||||
|
it is off by default so the initial export remains ungrouped. Users may change
|
||||||
|
the query and column order through the existing dialog. The command-line
|
||||||
|
`--export-bom` operation uses the same default query and column order.
|
||||||
|
|
||||||
|
CSV output is UTF-8 with a BOM, semicolon-delimited, and quotes every field.
|
||||||
|
Embedded quotes are doubled and embedded newlines are retained. `QSaveFile`
|
||||||
|
provides atomic replacement; query, open, write, and commit failures are
|
||||||
|
reported without deleting an existing destination first.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
The smart-device test is registered explicitly in
|
||||||
|
`tests/qttest/CMakeLists.txt` and uses only Qt Test and Qt SQL. It is therefore
|
||||||
|
part of the normal cross-platform CMake/CTest flow:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build -DPACKAGE_TESTS=ON
|
||||||
|
cmake --build build
|
||||||
|
ctest --test-dir build --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
`tst_smart_device` checks the metadata column contract, Simple/Master filtering,
|
||||||
|
stable page ordering, UTF-8 with Latin and non-Latin samples, CSV escaping, and
|
||||||
|
atomic write success/failure. Existing project XML save/load tests cover the
|
||||||
|
generic `DiagramContext` persistence path used by the new keys.
|
||||||
|
|
||||||
|
Manual checks:
|
||||||
|
|
||||||
|
1. Open an existing project and edit a Simple or Master element's information.
|
||||||
|
2. Set all device fields, apply, undo, redo, save, close, and reopen.
|
||||||
|
3. Open Project > Export as CSV and verify the device columns are preselected.
|
||||||
|
4. Export with grouping both disabled and enabled.
|
||||||
|
5. Verify leading-zero part numbers, non-Latin text, quotes, and newlines in a
|
||||||
|
UTF-8 CSV reader.
|
||||||
|
|
||||||
|
Cable management, terminal-strip planning, automatic wire numbering, PLC I/O
|
||||||
|
expansion, panel layout, and manufacturer catalog integration are out of scope.
|
||||||
+1
-1
Submodule elements updated: 74bdb77844...60eacc28a6
File diff suppressed because one or more lines are too long
Binary file not shown.
+1120
-836
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+574
-543
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1095
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1092
-817
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1093
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1092
-817
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1099
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1116
-839
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+4891
-1856
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1389
-1112
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1109
-830
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1090
-817
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1093
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1089
-816
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1088
-813
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1092
-815
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1116
-839
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+4544
-1620
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1093
-818
File diff suppressed because it is too large
Load Diff
+1127
-850
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+4904
-1869
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+574
-511
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1107
-830
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1093
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1115
-838
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1129
-850
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1095
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1117
-838
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1119
-838
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1117
-838
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1092
-817
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1090
-817
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1095
-818
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1090
-817
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1
-1
@@ -42,7 +42,7 @@
|
|||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>qelectrotech</string>
|
<string>qelectrotech</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>org.qelectrotech</string>
|
<string>org.qelectrotech.QElectroTech</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
|
|||||||
@@ -1,337 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Copyright 2019 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/>.
|
|
||||||
# Need homebrew and coreutils installed see <http://brew.sh>.
|
|
||||||
|
|
||||||
|
|
||||||
#Force MacOSX12.3.sdk
|
|
||||||
#see: https://www.downtowndougbrown.com/2023/08/how-to-create-a-qt-5-arm-intel-universal-binary-for-mac/
|
|
||||||
|
|
||||||
export DEVELOPER_DIR=/Applications/Xcode_14.01.app/Contents/Developer
|
|
||||||
|
|
||||||
# configuration
|
|
||||||
APPNAME='qelectrotech'
|
|
||||||
BUNDLE=$APPNAME.app
|
|
||||||
APPBIN="$BUNDLE/Contents/MacOS/$APPNAME"
|
|
||||||
|
|
||||||
# Emplacement du script
|
|
||||||
current_dir=$(dirname "$0")
|
|
||||||
|
|
||||||
# On se remet au depart
|
|
||||||
cd "${current_dir}/../"
|
|
||||||
|
|
||||||
# Emplacement courant
|
|
||||||
current_dir=$(PWD)
|
|
||||||
|
|
||||||
|
|
||||||
### get system configuration ########################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "This script prepares a Qt application bundle for deployment."
|
|
||||||
echo "Please see the \"Deploying an Application on Qt/Mac\""
|
|
||||||
echo "page in the Qt documentation for more information."
|
|
||||||
echo
|
|
||||||
echo "This script :"
|
|
||||||
echo "\t - up date the svn depot"
|
|
||||||
echo "\t - built the application bundle,"
|
|
||||||
echo "\t - copy over required Qt frameworks,"
|
|
||||||
echo "\t - copy additional files: translations, titleblocks and elements,"
|
|
||||||
echo "\t - create image disk."
|
|
||||||
echo
|
|
||||||
echo "Enjoy ;-)"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# as long as we can find qmake, we don't need QTDIR
|
|
||||||
FWPATH=`qmake -query QT_INSTALL_LIBS`
|
|
||||||
if [ ! -d $FWPATH/QtGui.framework ] ; then
|
|
||||||
echo "ERROR: cannot find the Qt frameworks. Make sure Qt is installed"
|
|
||||||
echo "and qmake is in your environment path."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
### GIT ####################################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Run GIT:"
|
|
||||||
|
|
||||||
# recupere le numero de la dernière revision
|
|
||||||
# Si il y a ':' garde la 2e partie
|
|
||||||
# Remplace les lettres par 'rien'
|
|
||||||
#revAv=$(svnversion | cut -d : -f 2 | tr -d '[:alpha:]')
|
|
||||||
|
|
||||||
# Fait une mise à jour
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
git pull --recurse-submodules
|
|
||||||
git pull
|
|
||||||
#git checkout foliolist_position
|
|
||||||
|
|
||||||
# recupere le numero de la nouvelle revision
|
|
||||||
|
|
||||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
|
||||||
A=$(git rev-list HEAD --count)
|
|
||||||
HEAD=$(($A+473))
|
|
||||||
|
|
||||||
|
|
||||||
VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
|
||||||
#VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{ 0, "| head -n 1| cut -c25-35| sed -e 's/,/./g' -e 's/ //g') #Find major, minor, and micro version numbers in sources/qetversion.cp
|
|
||||||
|
|
||||||
# Dmg de la dernière revision déjà créé
|
|
||||||
if [ -e "build-aux/mac-osx/${APPNAME} $VERSION r$HEAD.dmg" ] ; 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
|
|
||||||
|
|
||||||
|
|
||||||
### make install ####################################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Run make install:"
|
|
||||||
|
|
||||||
# pour effacer l’ancienne compilation
|
|
||||||
if [ -d $BUNDLE ] ; then
|
|
||||||
echo "Removing hold bundle..."
|
|
||||||
rm -rf $BUNDLE
|
|
||||||
fi
|
|
||||||
if [ -e Makefile ] ; then
|
|
||||||
echo "Removing hold Makefile..."
|
|
||||||
make clean
|
|
||||||
fi
|
|
||||||
|
|
||||||
# genere le Makefile
|
|
||||||
echo "Generating new makefile..."
|
|
||||||
qmake -spec macx-clang
|
|
||||||
|
|
||||||
# compilation
|
|
||||||
if [ -e Makefile.Release ] ; then
|
|
||||||
START_TIME=$SECONDS
|
|
||||||
|
|
||||||
# arret du script si erreur de compilation
|
|
||||||
testSuccessBuild () {
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
cleanVerionTag
|
|
||||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
|
||||||
echo
|
|
||||||
echo "make failed - $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# utilise tout les coeurs pour une compilation plus rapide
|
|
||||||
coeur=$(sysctl hw.ncpu | awk '{print $2}')
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
make -f Makefile.Release
|
|
||||||
testSuccessBuild
|
|
||||||
else
|
|
||||||
make -j$(($coeur + 1)) -f Makefile.Release
|
|
||||||
testSuccessBuild
|
|
||||||
fi
|
|
||||||
|
|
||||||
cleanVerionTag
|
|
||||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
|
||||||
echo
|
|
||||||
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
|
||||||
else
|
|
||||||
echo "ERROR: Makefile not found. This script requires the macx-clang makespec"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp -R ${current_dir}/misc/Info.plist qelectrotech.app/Contents/
|
|
||||||
cp -R ${current_dir}/ico/mac_icon/*.icns qelectrotech.app/Contents/Resources/
|
|
||||||
# On rajoute le numero de version pour "cmd + i"
|
|
||||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist" # Version number
|
|
||||||
|
|
||||||
### copy over frameworks ############################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Copy Qt libraries and private frameworks:"
|
|
||||||
|
|
||||||
echo "Processing Mac deployment tool..."
|
|
||||||
if [ ! -d $BUNDLE ] ; then
|
|
||||||
echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
#~/Qt/5.5/clang_64/bin/macdeployqt $BUNDLE
|
|
||||||
macdeployqt $BUNDLE
|
|
||||||
|
|
||||||
### add file missing #######################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Copy file missing:"
|
|
||||||
|
|
||||||
# Dossier à ajouter
|
|
||||||
QET_ELMT_DIR="${current_dir}/elements/"
|
|
||||||
QET_TBT_DIR="${current_dir}/titleblocks/"
|
|
||||||
QET_LANG_DIR="${current_dir}/lang/"
|
|
||||||
QET_FONTS_DIR="${current_dir}/fonts/"
|
|
||||||
QET_LICENSES_DIR="${current_dir}/licenses/"
|
|
||||||
|
|
||||||
# Add new folder for Qt dialog translation see
|
|
||||||
## see <https://download.tuxfamily.org/qet/Qt_lang/>.
|
|
||||||
|
|
||||||
LANG_DIR="${current_dir}/lang1/"
|
|
||||||
|
|
||||||
if [ -d "${QET_ELMT_DIR}" ]; then
|
|
||||||
echo "Copying add elements in the bundle..."
|
|
||||||
#mkdir $BUNDLE/Contents/Resources/elements
|
|
||||||
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_TBT_DIR}" ]; then
|
|
||||||
echo "Copying titleblocks in the bundle..."
|
|
||||||
#mkdir $BUNDLE/Contents/Resources/titleblocks
|
|
||||||
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_LANG_DIR}" ]; then
|
|
||||||
echo "Copying translations in the bundle... "
|
|
||||||
mkdir $BUNDLE/Contents/Resources/lang
|
|
||||||
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${LANG_DIR}" ]; then
|
|
||||||
echo "Copying translations in the bundle... "
|
|
||||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_FONTS_DIR}" ]; then
|
|
||||||
echo "Copying fonts in the bundle... "
|
|
||||||
mkdir $BUNDLE/Contents/Resources/fonts
|
|
||||||
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_LICENSES_DIR}" ]; then
|
|
||||||
echo "Copying licenses in the bundle..."
|
|
||||||
mkdir $BUNDLE/Contents/Resources/licenses
|
|
||||||
cp -R -L ${QET_LICENSES_DIR} $BUNDLE/Contents/Resources/licenses
|
|
||||||
fi
|
|
||||||
### create disk image ###############################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Create disk image:"
|
|
||||||
|
|
||||||
# Direction
|
|
||||||
EX_Dir="examples"
|
|
||||||
DOC_Dir="docs/Documentation.webloc"
|
|
||||||
imagedir="/tmp/$APPNAME.$$"
|
|
||||||
|
|
||||||
# Remove any previous disk folder or DMG
|
|
||||||
echo 'Preparing (removing hold files)... '
|
|
||||||
if [ -e "/Volumes/${APPNAME}" ]; then
|
|
||||||
hdiutil detach -quiet "/Volumes/${APPNAME}"
|
|
||||||
fi
|
|
||||||
if [ -e "${APPNAME} $VERSION r$HEAD.dmg" ] ; then
|
|
||||||
rm -f "${APPNAME} $VERSION r$HEAD.dmg"
|
|
||||||
fi
|
|
||||||
if [ -e "build-aux/mac-osx/${APPNAME} $VERSION r$HEAD.dmg" ] ; then
|
|
||||||
rm -f "build-aux/mac-osx/${APPNAME} $VERSION r$HEAD.dmg"
|
|
||||||
fi
|
|
||||||
if [ -e $imagedir ] ; then
|
|
||||||
rm -rf $imagedir
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create disk folder
|
|
||||||
echo 'Copying application bundle and examples... '
|
|
||||||
mkdir $imagedir
|
|
||||||
cp -Rf $BUNDLE $imagedir
|
|
||||||
cp -Rf $EX_Dir $imagedir
|
|
||||||
if [ -e $DOC_Dir ] ; then
|
|
||||||
cp $DOC_Dir $imagedir
|
|
||||||
else
|
|
||||||
echo "Documentation in ${DOC_Dir} not find."
|
|
||||||
fi
|
|
||||||
ln -s /Applications $imagedir/Applications
|
|
||||||
|
|
||||||
# Elimine les accents, symbole ...
|
|
||||||
strip "$imagedir/$APPBIN"
|
|
||||||
|
|
||||||
# Creating a disk image from a folder
|
|
||||||
echo 'Creating disk image... '
|
|
||||||
hdiutil create -quiet -ov -srcfolder $imagedir -format UDBZ -volname "${APPNAME}" "${APPNAME} $VERSION r$HEAD.dmg"
|
|
||||||
hdiutil internet-enable -yes -quiet "${APPNAME} $VERSION r$HEAD.dmg"
|
|
||||||
|
|
||||||
# Clean up disk folder
|
|
||||||
echo 'Cleaning up... '
|
|
||||||
cp -Rf "${APPNAME} $VERSION r$HEAD.dmg" "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-intel_X86_64.dmg"
|
|
||||||
rm -f "${APPNAME} $VERSION r$HEAD.dmg"
|
|
||||||
rm -rf $imagedir
|
|
||||||
rm -rf $BUNDLE
|
|
||||||
|
|
||||||
|
|
||||||
### The end, process is done ##########################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "The process of creating deployable application bundle is done."
|
|
||||||
echo The disque image is in the folder \'build-aux/mac-osx\'.
|
|
||||||
# Affiche les mise à jour depuis l'ancienne revision
|
|
||||||
#if [ ! $(($HEAD - $revAv)) -eq 0 ] ; then
|
|
||||||
# echo
|
|
||||||
# echo "There are new updates. This numero of revision is $HEAD."
|
|
||||||
# svn log -l $(($HEAD - $revAv))
|
|
||||||
#else
|
|
||||||
# echo
|
|
||||||
# echo "There are not new updates. This numero of revision is $HEAD."
|
|
||||||
# fi
|
|
||||||
# echo
|
|
||||||
|
|
||||||
# La version en local n'est pas conforme à la dernière version svn
|
|
||||||
# svnversion | grep -q '[MS:]' ; if [ $? -eq 0 ] ; then
|
|
||||||
# echo Please note that the latest \local version is $(svnversion).
|
|
||||||
# echo This is not the same version as the deposit.
|
|
||||||
# echo You can use \'svn diff\' to see the differences.
|
|
||||||
# echo And use \'svn revert \<fichier\>\' to delete the difference.
|
|
||||||
# echo To go back, you can use svn update -r 360
|
|
||||||
# echo to go to revision number 360.
|
|
||||||
# echo
|
|
||||||
#fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#rsync to TF DMG builds
|
|
||||||
echo -e "\033[1;31mWould you like to upload MacOS packages "${APPNAME}"-"$VERSION"_"r$HEAD-intel.dmg", n/Y?.\033[m"
|
|
||||||
read a
|
|
||||||
if [[ $a == "Y" || $a == "y" ]]; then
|
|
||||||
cp -Rf "build-aux/mac-osx/${APPNAME}-$VERSION-r$HEAD-intel_X86_64.dmg" /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/intel_X86_64/
|
|
||||||
if [ $? != 0 ]; then
|
|
||||||
{
|
|
||||||
echo "RSYNC ERROR: problem syncing ${APPNAME}-$VERSION-r$HEAD-intel_X86_64.dmg"
|
|
||||||
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/intel_X86_64/
|
|
||||||
|
|
||||||
} fi
|
|
||||||
|
|
||||||
else
|
|
||||||
echo -e "\033[1;33mExit.\033[m"
|
|
||||||
|
|
||||||
fi
|
|
||||||
@@ -1,509 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Copyright 2023 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/>.
|
|
||||||
# Need homebrew and coreutils installed see <http://brew.sh>.
|
|
||||||
|
|
||||||
#Force MacOSX12.3.sdk
|
|
||||||
#see: https://www.downtowndougbrown.com/2023/08/how-to-create-a-qt-5-arm-intel-universal-binary-for-mac/
|
|
||||||
|
|
||||||
export DEVELOPER_DIR=/Applications/Xcode_14.01.app/Contents/Developer
|
|
||||||
|
|
||||||
# configuration
|
|
||||||
APPNAME='qelectrotech'
|
|
||||||
BUNDLE=$APPNAME.app
|
|
||||||
APPBIN="$BUNDLE/Contents/MacOS/$APPNAME"
|
|
||||||
IDENTITY="Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)"
|
|
||||||
|
|
||||||
# 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")
|
|
||||||
|
|
||||||
# Go back to repo root
|
|
||||||
cd "${current_dir}/../"
|
|
||||||
|
|
||||||
# Current directory
|
|
||||||
current_dir=$(PWD)
|
|
||||||
|
|
||||||
|
|
||||||
### get system configuration ########################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "This script prepares a Qt application bundle for deployment."
|
|
||||||
echo "Please see the \"Deploying an Application on Qt/Mac\""
|
|
||||||
echo "page in the Qt documentation for more information."
|
|
||||||
echo
|
|
||||||
echo "This script :"
|
|
||||||
echo "\t - update the git depot"
|
|
||||||
echo "\t - build the application bundle,"
|
|
||||||
echo "\t - copy over required Qt frameworks,"
|
|
||||||
echo "\t - copy additional files: translations, titleblocks and elements,"
|
|
||||||
echo "\t - notarize the .app, then create a signed DMG."
|
|
||||||
echo
|
|
||||||
echo "Enjoy ;-)"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# as long as we can find qmake, we don't need QTDIR
|
|
||||||
FWPATH=`qmake -query QT_INSTALL_LIBS`
|
|
||||||
if [ ! -d $FWPATH/QtGui.framework ] ; then
|
|
||||||
echo "ERROR: cannot find the Qt frameworks. Make sure Qt is installed"
|
|
||||||
echo "and qmake is in your environment path."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
### GIT ####################################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Run GIT:"
|
|
||||||
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
git pull --recurse-submodules
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# Get revision number and version
|
|
||||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
|
||||||
A=$(git rev-list HEAD --count)
|
|
||||||
HEAD=$(($A+473))
|
|
||||||
|
|
||||||
VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
|
||||||
|
|
||||||
DMG_NAME="${APPNAME}-$VERSION-r$HEAD-arm64.dmg"
|
|
||||||
DMG_PATH="build-aux/mac-osx/$DMG_NAME"
|
|
||||||
|
|
||||||
# Check if already built
|
|
||||||
if [ -e "$DMG_PATH" ] ; then
|
|
||||||
echo "There are not new updates, make disk image can"
|
|
||||||
echo "take a lot of time (5 min). Can you continu?"
|
|
||||||
echo "[y/n]"
|
|
||||||
read userinput
|
|
||||||
if [ "$userinput" == "n" ] ; then
|
|
||||||
echo
|
|
||||||
echo "Process is stopped."
|
|
||||||
echo
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
### make install ####################################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Run make install:"
|
|
||||||
|
|
||||||
# Remove old bundle
|
|
||||||
if [ -d $BUNDLE ] ; then
|
|
||||||
echo "Removing old bundle..."
|
|
||||||
rm -rf $BUNDLE
|
|
||||||
fi
|
|
||||||
if [ -e Makefile ] ; then
|
|
||||||
echo "Removing old Makefile..."
|
|
||||||
rm .qmake.stash
|
|
||||||
make clean
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate Makefile
|
|
||||||
echo "Generating new makefile..."
|
|
||||||
qmake -spec macx-clang
|
|
||||||
|
|
||||||
# Compile
|
|
||||||
if [ -e Makefile.Release ] ; then
|
|
||||||
START_TIME=$SECONDS
|
|
||||||
|
|
||||||
testSuccessBuild () {
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
cleanVerionTag
|
|
||||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
|
||||||
echo
|
|
||||||
echo "make failed - $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
coeur=$(sysctl hw.ncpu | awk '{print $2}')
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
make -f Makefile.Release
|
|
||||||
testSuccessBuild
|
|
||||||
else
|
|
||||||
make -j$(($coeur + 1)) -f Makefile.Release
|
|
||||||
testSuccessBuild
|
|
||||||
fi
|
|
||||||
|
|
||||||
ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
|
||||||
echo
|
|
||||||
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
|
||||||
else
|
|
||||||
echo "ERROR: Makefile not found. This script requires the macx-clang makespec"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp -R ${current_dir}/misc/Info.plist qelectrotech.app/Contents/
|
|
||||||
cp -R ${current_dir}/ico/mac_icon/*.icns qelectrotech.app/Contents/Resources/
|
|
||||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist"
|
|
||||||
|
|
||||||
|
|
||||||
### copy over frameworks ############################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Copy Qt libraries and private frameworks:"
|
|
||||||
|
|
||||||
echo "Processing Mac deployment tool..."
|
|
||||||
if [ ! -d $BUNDLE ] ; then
|
|
||||||
echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
macdeployqt $BUNDLE
|
|
||||||
|
|
||||||
### 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
|
|
||||||
echo "Copying elements in the bundle..."
|
|
||||||
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_TBT_DIR}" ]; then
|
|
||||||
echo "Copying titleblocks in the bundle..."
|
|
||||||
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_LANG_DIR}" ]; then
|
|
||||||
echo "Copying translations in the bundle..."
|
|
||||||
mkdir $BUNDLE/Contents/Resources/lang
|
|
||||||
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${LANG_DIR}" ]; then
|
|
||||||
echo "Copying extra translations in the bundle..."
|
|
||||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_EXAMPLES_DIR}" ]; then
|
|
||||||
echo "Copying examples in the bundle..."
|
|
||||||
mkdir $BUNDLE/Contents/Resources/examples
|
|
||||||
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_FONTS_DIR}" ]; then
|
|
||||||
echo "Copying fonts in the bundle..."
|
|
||||||
mkdir $BUNDLE/Contents/Resources/fonts
|
|
||||||
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${QET_LICENSES_DIR}" ]; then
|
|
||||||
echo "Copying licenses in the bundle..."
|
|
||||||
mkdir $BUNDLE/Contents/Resources/licenses
|
|
||||||
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):"
|
|
||||||
|
|
||||||
# 1. Sign all flat .dylib files in Frameworks/
|
|
||||||
echo "-- Signing dylibs in Frameworks/..."
|
|
||||||
find "$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
|
|
||||||
echo " $(basename $lib)"
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 2. Sign .framework bundles
|
|
||||||
echo "-- Signing .framework bundles..."
|
|
||||||
find "$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
|
|
||||||
echo " $(basename $fw)"
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 3. Sign plugins
|
|
||||||
echo "-- Signing plugins..."
|
|
||||||
find "$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
|
|
||||||
echo " $(basename $lib)"
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 4. Sign any dylibs in MacOS/
|
|
||||||
echo "-- Signing dylibs in MacOS/..."
|
|
||||||
find "$BUNDLE/Contents/MacOS" -name "*.dylib" | while read lib; do
|
|
||||||
echo " $(basename $lib)"
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 5. Sign the main executable explicitly
|
|
||||||
echo "-- Signing main executable..."
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
|
||||||
"$BUNDLE/Contents/MacOS/$APPNAME"
|
|
||||||
|
|
||||||
# 6. Sign the bundle itself last
|
|
||||||
echo "-- Signing bundle..."
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE"
|
|
||||||
|
|
||||||
# 7. Verify
|
|
||||||
echo
|
|
||||||
echo "Verifying bundle signature..."
|
|
||||||
codesign --verify --deep --strict --verbose=2 "$BUNDLE"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "ERROR: bundle signature verification failed, aborting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
spctl -a -vv "$BUNDLE"
|
|
||||||
echo "Bundle signature OK."
|
|
||||||
|
|
||||||
### Notarize the .app (via temporary ZIP) ###########################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Create temporary ZIP for notarization:"
|
|
||||||
|
|
||||||
NOTARIZE_ZIP="/tmp/${APPNAME}-$VERSION-r$HEAD-arm64-notarize.zip"
|
|
||||||
/usr/bin/ditto -c -k --keepParent "$BUNDLE" "$NOTARIZE_ZIP"
|
|
||||||
|
|
||||||
echo -e "\033[1;31mWould you like to notarize the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
|
|
||||||
read a
|
|
||||||
if [[ $a == "Y" || $a == "y" ]]; then
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "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
|
|
||||||
|
|
||||||
echo "Cleaning up temporary notarization ZIP..."
|
|
||||||
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
|
|
||||||
echo "Verifying staple on .app..."
|
|
||||||
xcrun stapler validate -v "$BUNDLE"
|
|
||||||
spctl -a -vv "$BUNDLE"
|
|
||||||
echo ".app stapled OK."
|
|
||||||
else
|
|
||||||
echo -e "\033[1;33mExit.\033[m"
|
|
||||||
fi
|
|
||||||
|
|
||||||
### Create staging folder with Applications symlink #################
|
|
||||||
# The staging folder contains the .app and a symlink to /Applications
|
|
||||||
# so the user can drag-and-drop to install directly from the DMG.
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Preparing DMG staging folder:"
|
|
||||||
|
|
||||||
rm -rf "$STAGING"
|
|
||||||
mkdir -p "$STAGING"
|
|
||||||
cp -R "$BUNDLE" "$STAGING/"
|
|
||||||
ln -s /Applications "$STAGING/Applications"
|
|
||||||
echo "Staging folder ready: $STAGING"
|
|
||||||
|
|
||||||
### Create writable DMG (UDRW) ######################################
|
|
||||||
# We use a writable DMG first so we can re-sign the .app inside
|
|
||||||
# after hdiutil copies it (hdiutil can invalidate Sealed Resources
|
|
||||||
# during the copy, so we must re-sign inside the mounted volume).
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Mount the writable DMG
|
|
||||||
rm -rf "$MOUNT_POINT"
|
|
||||||
mkdir -p "$MOUNT_POINT"
|
|
||||||
hdiutil attach "$RW_DMG" -mountpoint "$MOUNT_POINT" -nobrowse -noverify
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "ERROR: failed to mount writable DMG."
|
|
||||||
rm -f "$RW_DMG"
|
|
||||||
rm -rf "$STAGING"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Re-sign all binaries inside the mounted DMG
|
|
||||||
echo "-- Re-signing dylibs inside DMG..."
|
|
||||||
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
|
||||||
done
|
|
||||||
|
|
||||||
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
|
|
||||||
done
|
|
||||||
|
|
||||||
find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "-- Re-signing main executable inside DMG..."
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
|
||||||
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
|
||||||
|
|
||||||
echo "-- Re-signing bundle inside DMG..."
|
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
|
||||||
"$MOUNT_POINT/$BUNDLE"
|
|
||||||
|
|
||||||
# Verify signature inside the mounted DMG
|
|
||||||
echo "Verifying bundle signature inside DMG..."
|
|
||||||
codesign --verify --deep --strict --verbose=2 "$MOUNT_POINT/$BUNDLE"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "ERROR: bundle signature invalid inside DMG, aborting."
|
|
||||||
hdiutil detach "$MOUNT_POINT"
|
|
||||||
rm -f "$RW_DMG"
|
|
||||||
rm -rf "$STAGING" "$MOUNT_POINT"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Bundle signature inside DMG OK."
|
|
||||||
|
|
||||||
# Detach the writable DMG
|
|
||||||
hdiutil detach "$MOUNT_POINT"
|
|
||||||
|
|
||||||
### Convert UDRW to final compressed UDZO ###########################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "Convert to final compressed DMG (UDZO):"
|
|
||||||
|
|
||||||
mkdir -p "build-aux/mac-osx"
|
|
||||||
rm -f "$DMG_PATH"
|
|
||||||
|
|
||||||
hdiutil convert "$RW_DMG" \
|
|
||||||
-format UDZO \
|
|
||||||
-o "$DMG_PATH"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "ERROR: hdiutil convert failed."
|
|
||||||
rm -f "$RW_DMG"
|
|
||||||
rm -rf "$STAGING" "$MOUNT_POINT"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$RW_DMG"
|
|
||||||
rm -rf "$STAGING" "$MOUNT_POINT"
|
|
||||||
|
|
||||||
### Sign the final DMG ##############################################
|
|
||||||
|
|
||||||
echo "Signing final DMG..."
|
|
||||||
codesign --sign "$IDENTITY" --timestamp "$DMG_PATH"
|
|
||||||
|
|
||||||
### Notarize and staple the final DMG ###############################
|
|
||||||
|
|
||||||
echo -e "\033[1;31mWould you like to notarize the DMG \"${DMG_NAME}\", n/Y?\033[m"
|
|
||||||
read a
|
|
||||||
if [[ $a == "Y" || $a == "y" ]]; then
|
|
||||||
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."
|
|
||||||
|
|
||||||
echo "Verifying final DMG..."
|
|
||||||
spctl -a -vv "$DMG_PATH"
|
|
||||||
else
|
|
||||||
echo -e "\033[1;33mExit.\033[m"
|
|
||||||
fi
|
|
||||||
|
|
||||||
### Clean up bundle #################################################
|
|
||||||
|
|
||||||
echo "Cleaning up bundle..."
|
|
||||||
rm -rf "$BUNDLE"
|
|
||||||
|
|
||||||
### The end #########################################################
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "______________________________________________________________"
|
|
||||||
echo "The process is done."
|
|
||||||
echo "DMG is in the folder 'build-aux/mac-osx'."
|
|
||||||
|
|
||||||
### Upload via rsync ################################################
|
|
||||||
|
|
||||||
echo -e "\033[1;31mWould you like to upload MacOS package \"${DMG_NAME}\", n/Y?\033[m"
|
|
||||||
read a
|
|
||||||
if [[ $a == "Y" || $a == "y" ]]; then
|
|
||||||
cp -Rf "$DMG_PATH" /Users/laurent/MAC_OS_X/
|
|
||||||
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
|
|
||||||
--progress --exclude='.DS_Store' \
|
|
||||||
/Users/laurent/MAC_OS_X/ \
|
|
||||||
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
|
|
||||||
if [ $? != 0 ]; then
|
|
||||||
echo "RSYNC ERROR: problem syncing ${DMG_NAME}, retrying..."
|
|
||||||
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
|
|
||||||
--progress --exclude='.DS_Store' \
|
|
||||||
/Users/laurent/MAC_OS_X/ \
|
|
||||||
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "\033[1;33mExit.\033[m"
|
|
||||||
fi
|
|
||||||
@@ -126,7 +126,8 @@ ELAPSED_TIME=$(($SECONDS - $START_TIME))
|
|||||||
echo
|
echo
|
||||||
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
|
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
|
# Le .app sort a la racine de $BUILD_DIR : add_executable(... MACOSX_BUNDLE)
|
||||||
|
# sans RUNTIME_OUTPUT_DIRECTORY dans CMakeLists.txt.
|
||||||
echo "Copying built bundle into place..."
|
echo "Copying built bundle into place..."
|
||||||
cp -R "$BUILD_DIR/qelectrotech.app" "./$BUNDLE"
|
cp -R "$BUILD_DIR/qelectrotech.app" "./$BUNDLE"
|
||||||
|
|
||||||
@@ -147,6 +148,98 @@ fi
|
|||||||
|
|
||||||
macdeployqt $BUNDLE
|
macdeployqt $BUNDLE
|
||||||
|
|
||||||
|
### fix Homebrew dependencies macdeployqt could not handle ##########
|
||||||
|
# Recent Homebrew bottles (brotli, webp, sharpyuv...) reference their own
|
||||||
|
# dependencies as @rpath/libX.dylib. macdeployqt only resolves @rpath in
|
||||||
|
# Contents/lib and in Qt's lib dir (-libpath does not help): it prints
|
||||||
|
# "Cannot resolve rpath" and leaves some references untouched, either
|
||||||
|
# absolute /opt/homebrew paths or @rpath libs missing from the bundle.
|
||||||
|
# Fix both here: every /opt/homebrew reference is rewritten to
|
||||||
|
# @rpath/libX.dylib (install ids too), and every @rpath/libX.dylib is
|
||||||
|
# copied into Contents/Frameworks, where the executable's LC_RPATH
|
||||||
|
# (@executable_path/../Frameworks) lets dyld find it. Everything is signed
|
||||||
|
# below with the rest of Frameworks/.
|
||||||
|
echo
|
||||||
|
echo "______________________________________________________________"
|
||||||
|
echo "Fix Homebrew dependencies left by macdeployqt:"
|
||||||
|
|
||||||
|
FW="$BUNDLE/Contents/Frameworks"
|
||||||
|
chmod -R u+w "$BUNDLE/Contents/MacOS" "$FW" "$BUNDLE/Contents/PlugIns" 2>/dev/null
|
||||||
|
|
||||||
|
# Every Mach-O candidate of the bundle (otool silently ignores the others)
|
||||||
|
list_macho() {
|
||||||
|
find "$BUNDLE/Contents/MacOS" "$FW" "$BUNDLE/Contents/PlugIns" -type f \
|
||||||
|
\( -name '*.dylib' -o -perm -u+x \) 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dependencies of one binary, without its own install id (dylibs only)
|
||||||
|
list_deps() {
|
||||||
|
_id=$(otool -D "$1" 2>/dev/null | sed -n 2p)
|
||||||
|
otool -L "$1" 2>/dev/null | awk 'NR>1 { print $1 }' | while read _dep; do
|
||||||
|
[ "$_dep" = "$_id" ] || echo "$_dep"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# The @rpath/libX.dylib names referenced anywhere in the bundle
|
||||||
|
list_rpath_libs() {
|
||||||
|
list_macho | while read bin; do
|
||||||
|
list_deps "$bin" | sed -n 's#^@rpath/\([^/]*\.dylib\)$#\1#p'
|
||||||
|
done | LC_ALL=C sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! otool -l "$BUNDLE/Contents/MacOS/$APPNAME" | grep -q "@executable_path/../Frameworks" ; then
|
||||||
|
install_name_tool -add_rpath "@executable_path/../Frameworks" "$BUNDLE/Contents/MacOS/$APPNAME"
|
||||||
|
echo " Added LC_RPATH @executable_path/../Frameworks to $APPNAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3 passes, since each copied library can bring its own dependencies:
|
||||||
|
# a. rewrite absolute /opt/homebrew references (install ids included),
|
||||||
|
# copying the referenced library into Frameworks/ if needed
|
||||||
|
# b. copy the @rpath/libX.dylib still missing from Frameworks/
|
||||||
|
for PASS in 1 2 3; do
|
||||||
|
list_macho | while read bin; do
|
||||||
|
_id=$(otool -D "$bin" 2>/dev/null | sed -n 2p)
|
||||||
|
case "$_id" in
|
||||||
|
/opt/homebrew/*)
|
||||||
|
install_name_tool -id "@rpath/$(basename "$_id")" "$bin" 2>/dev/null
|
||||||
|
echo " Fixed id (pass $PASS): $(basename "$bin")"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
list_deps "$bin" | grep '^/opt/homebrew/' | while read ref; do
|
||||||
|
name=$(basename "$ref")
|
||||||
|
if [ ! -e "$FW/$name" ]; then
|
||||||
|
cp -L "$ref" "$FW/$name" && chmod u+w "$FW/$name"
|
||||||
|
echo " Copied (pass $PASS): $name"
|
||||||
|
fi
|
||||||
|
install_name_tool -change "$ref" "@rpath/$name" "$bin" 2>/dev/null
|
||||||
|
echo " Fixed ref (pass $PASS): $(basename "$bin") -> @rpath/$name"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
list_rpath_libs | while read lib; do
|
||||||
|
if [ ! -e "$FW/$lib" ] && [ -e "/opt/homebrew/lib/$lib" ]; then
|
||||||
|
cp -L "/opt/homebrew/lib/$lib" "$FW/$lib" && chmod u+w "$FW/$lib"
|
||||||
|
echo " Copied (pass $PASS): $lib"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. Checks
|
||||||
|
UNRESOLVED=$(list_rpath_libs | while read lib; do [ -e "$FW/$lib" ] || echo "$lib"; done)
|
||||||
|
if [ -n "$UNRESOLVED" ]; then
|
||||||
|
echo "ERROR: @rpath libraries still missing from Frameworks/:" $UNRESOLVED
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
HOMEBREW_REFS=$(list_macho | while read bin; do
|
||||||
|
otool -L "$bin" 2>/dev/null | awk 'NR>1 { print $1 }' | grep '^/opt/homebrew/' \
|
||||||
|
| sed "s#^# $(basename "$bin") -> #"
|
||||||
|
done)
|
||||||
|
if [ -n "$HOMEBREW_REFS" ]; then
|
||||||
|
echo "ERROR: bundle still references Homebrew paths:"
|
||||||
|
echo "$HOMEBREW_REFS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All dependencies resolved inside the bundle."
|
||||||
|
|
||||||
### install Info.plist and app icon #################################
|
### install Info.plist and app icon #################################
|
||||||
# NOTE: this must run AFTER macdeployqt, not before. macdeployqt
|
# NOTE: this must run AFTER macdeployqt, not before. macdeployqt
|
||||||
# rewrites/regenerates parts of Contents/Resources, and files copied
|
# rewrites/regenerates parts of Contents/Resources, and files copied
|
||||||
@@ -175,7 +268,6 @@ QET_LANG_DIR="${current_dir}/lang/"
|
|||||||
QET_EXAMPLES_DIR="${current_dir}/examples/"
|
QET_EXAMPLES_DIR="${current_dir}/examples/"
|
||||||
QET_FONTS_DIR="${current_dir}/fonts/"
|
QET_FONTS_DIR="${current_dir}/fonts/"
|
||||||
QET_LICENSES_DIR="${current_dir}/licenses/"
|
QET_LICENSES_DIR="${current_dir}/licenses/"
|
||||||
LANG_DIR="${current_dir}/lang1/"
|
|
||||||
|
|
||||||
if [ -d "${QET_ELMT_DIR}" ]; then
|
if [ -d "${QET_ELMT_DIR}" ]; then
|
||||||
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
||||||
@@ -183,12 +275,85 @@ fi
|
|||||||
if [ -d "${QET_TBT_DIR}" ]; then
|
if [ -d "${QET_TBT_DIR}" ]; then
|
||||||
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
||||||
fi
|
fi
|
||||||
if [ -d "${QET_LANG_DIR}" ]; then
|
# Traductions : depuis la PR #751, les .qm ne sont plus versionnes ; lrelease
|
||||||
mkdir $BUNDLE/Contents/Resources/lang
|
# les genere dans $BUILD_DIR/lang/. Jeu attendu = les .ts listes dans TS_FILES
|
||||||
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
|
# (cmake/qet_compilation_vars.cmake) : un .qm manquant arrete le script, un
|
||||||
|
# .ts present dans lang/ mais absent de TS_FILES donne seulement un WARNING.
|
||||||
|
# Fichiers temporaires plutot que <(...) : /bin/sh de macOS (bash 3.2 en mode
|
||||||
|
# POSIX) n'a pas la substitution de processus.
|
||||||
|
QM_SRC="${current_dir}/${BUILD_DIR}/lang"
|
||||||
|
QM_TMP=$(mktemp -d /tmp/qet_qm.XXXXXX)
|
||||||
|
grep -o 'lang/qet_[A-Za-z_]*\.ts' "${current_dir}/cmake/qet_compilation_vars.cmake" \
|
||||||
|
| sed -e 's#^lang/##' -e 's#\.ts$##' | LC_ALL=C sort -u > "$QM_TMP/listed"
|
||||||
|
if [ ! -s "$QM_TMP/listed" ]; then
|
||||||
|
echo "ERROR: cannot read TS_FILES from cmake/qet_compilation_vars.cmake"
|
||||||
|
rm -rf "$QM_TMP"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -d "${LANG_DIR}" ]; then
|
find "${QET_LANG_DIR}" -maxdepth 1 -name 'qet_*.ts' -exec basename {} .ts \; | LC_ALL=C sort > "$QM_TMP/present"
|
||||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
UNLISTED=$(LC_ALL=C comm -13 "$QM_TMP/listed" "$QM_TMP/present")
|
||||||
|
if [ -n "$UNLISTED" ]; then
|
||||||
|
echo "WARNING: .ts files not in TS_FILES, no .qm built:" $UNLISTED
|
||||||
|
fi
|
||||||
|
mkdir -p $BUNDLE/Contents/Resources/lang
|
||||||
|
find "${QM_SRC}" -maxdepth 1 -name 'qet_*.qm' -exec cp {} $BUNDLE/Contents/Resources/lang/ \; 2>/dev/null
|
||||||
|
find $BUNDLE/Contents/Resources/lang -maxdepth 1 -name 'qet_*.qm' -exec basename {} .qm \; | LC_ALL=C sort > "$QM_TMP/built"
|
||||||
|
MISSING=$(LC_ALL=C comm -23 "$QM_TMP/listed" "$QM_TMP/built")
|
||||||
|
echo "$(wc -l < "$QM_TMP/built" | tr -d ' ') .qm files copied to Contents/Resources/lang (expected: $(wc -l < "$QM_TMP/listed" | tr -d ' '))"
|
||||||
|
|
||||||
|
# Traductions de Qt lui-meme (boutons OK/Annuler, dialogues standard...) :
|
||||||
|
# elles viennent de qtbase_XX.qm, pas des .ts de QET, et macdeployqt ne les
|
||||||
|
# deploie pas. QETApp::setLanguage() charge "qt_XX" depuis le chemin de
|
||||||
|
# traductions de Qt (absent du bundle), puis depuis le dossier lang/ de QET :
|
||||||
|
# on y depose donc chaque qtbase_XX.qm sous le nom qt_XX.qm. qtbase_XX.qm est
|
||||||
|
# autonome, contrairement aux qt_XX.qm de Qt qui dependent de tous les modules.
|
||||||
|
# Premier dossier contenant des qtbase_*.qm : celui annonce par qtpaths, puis
|
||||||
|
# la formule Homebrew separee qttranslations, puis les autres emplacements
|
||||||
|
# Homebrew possibles. Homebrew fait de ces dossiers des liens symboliques
|
||||||
|
# (-> Cellar/qttranslations/...) : find doit donc les suivre (-L), sinon il
|
||||||
|
# ne voit que le lien lui-meme et ne trouve aucun fichier dedans.
|
||||||
|
QT_TR_DIR=""
|
||||||
|
for d in "$("$QT_PREFIX/bin/qtpaths" --query QT_INSTALL_TRANSLATIONS 2>/dev/null)" \
|
||||||
|
"$(brew --prefix qttranslations 2>/dev/null)/share/qt/translations" \
|
||||||
|
"$QT_PREFIX/share/qt/translations" \
|
||||||
|
/opt/homebrew/share/qt/translations \
|
||||||
|
/opt/homebrew/opt/*/share/qt/translations ; do
|
||||||
|
if ls "$d"/qtbase_*.qm >/dev/null 2>&1 ; then
|
||||||
|
QT_TR_DIR="$d"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
LANG_DST="$BUNDLE/Contents/Resources/lang"
|
||||||
|
find -L "$QT_TR_DIR" -maxdepth 1 -name 'qtbase_*.qm' 2>/dev/null | while read f; do
|
||||||
|
l=$(basename "$f" .qm | sed 's/^qtbase_//')
|
||||||
|
cp "$f" "$LANG_DST/qt_$l.qm"
|
||||||
|
done
|
||||||
|
# Langues QET sans equivalent Qt exact (pt -> pt_PT, zh -> zh_CN...) :
|
||||||
|
# QTranslator ne sait que raccourcir le code (fr_FR -> fr), pas l'allonger.
|
||||||
|
# On prefere la variante "principale" (pt_PT), sinon la premiere trouvee.
|
||||||
|
sed 's/^qet_//' "$QM_TMP/listed" | while read l; do
|
||||||
|
if [ ! -e "$LANG_DST/qt_$l.qm" ]; then
|
||||||
|
main="$QT_TR_DIR/qtbase_${l}_$(echo "$l" | tr 'a-z' 'A-Z').qm"
|
||||||
|
if [ -e "$main" ]; then
|
||||||
|
alt="$main"
|
||||||
|
else
|
||||||
|
alt=$(find -L "$QT_TR_DIR" -maxdepth 1 -name "qtbase_${l}_*.qm" 2>/dev/null | LC_ALL=C sort | head -1)
|
||||||
|
fi
|
||||||
|
[ -n "$alt" ] && cp "$alt" "$LANG_DST/qt_$l.qm"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
QT_QM_COUNT=$(find "$LANG_DST" -maxdepth 1 -name 'qt_*.qm' | wc -l | tr -d ' ')
|
||||||
|
echo "${QT_QM_COUNT} Qt translation files (qt_*.qm) copied from ${QT_TR_DIR:-<not found>}"
|
||||||
|
if [ "${QT_QM_COUNT}" -eq 0 ]; then
|
||||||
|
echo "ERROR: no qtbase_*.qm found (Qt translations not installed?)."
|
||||||
|
echo " Check with: find /opt/homebrew -name 'qtbase_fr.qm'"
|
||||||
|
rm -rf "$QM_TMP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -rf "$QM_TMP"
|
||||||
|
if [ -n "$MISSING" ]; then
|
||||||
|
echo "ERROR: missing translations:" $MISSING
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -d "${QET_EXAMPLES_DIR}" ]; then
|
if [ -d "${QET_EXAMPLES_DIR}" ]; then
|
||||||
mkdir $BUNDLE/Contents/Resources/examples
|
mkdir $BUNDLE/Contents/Resources/examples
|
||||||
|
|||||||
@@ -1,381 +0,0 @@
|
|||||||
######################################################################
|
|
||||||
# Fichier projet de QElectroTech #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
# Chemins utilises pour la compilation et l'installation de QET
|
|
||||||
unix {
|
|
||||||
# Chemins UNIX
|
|
||||||
COMPIL_PREFIX = '/usr/local/'
|
|
||||||
INSTALL_PREFIX = '/usr/local/'
|
|
||||||
QET_BINARY_PATH = 'bin/'
|
|
||||||
QET_COMMON_COLLECTION_PATH = 'share/qelectrotech/elements/'
|
|
||||||
QET_COMMON_TBT_PATH = 'share/qelectrotech/titleblocks/'
|
|
||||||
QET_LANG_PATH = 'share/qelectrotech/lang/'
|
|
||||||
QET_EXAMPLES_PATH = 'share/qelectrotech/examples/'
|
|
||||||
QET_LICENSE_PATH = 'doc/qelectrotech/'
|
|
||||||
QET_MIME_PACKAGE_PATH = '../share/mime/packages/'
|
|
||||||
QET_DESKTOP_PATH = 'share/applications/'
|
|
||||||
QET_ICONS_PATH = 'share/icons/hicolor/'
|
|
||||||
QET_MAN_PATH = 'man/'
|
|
||||||
QET_APPDATA_PATH = 'share/appdata'
|
|
||||||
}
|
|
||||||
win32 {
|
|
||||||
# Chemins Windows
|
|
||||||
COMPIL_PREFIX = './'
|
|
||||||
INSTALL_PREFIX = './'
|
|
||||||
QET_BINARY_PATH = './'
|
|
||||||
QET_COMMON_COLLECTION_PATH = 'elements/'
|
|
||||||
QET_COMMON_TBT_PATH = 'titleblocks/'
|
|
||||||
QET_LANG_PATH = 'lang/'
|
|
||||||
QET_LICENSE_PATH = './'
|
|
||||||
# Liste des ressources Windows
|
|
||||||
#RC_FILE = qelectrotech.rc
|
|
||||||
}
|
|
||||||
macx {
|
|
||||||
# Chemins MacOS X
|
|
||||||
COMPIL_PREFIX = './'
|
|
||||||
INSTALL_PREFIX = '/usr/local/'
|
|
||||||
QET_BINARY_PATH = 'bin/'
|
|
||||||
QET_COMMON_COLLECTION_PATH = '../Resources/elements/'
|
|
||||||
QET_COMMON_TBT_PATH = '../Resources/titleblocks/'
|
|
||||||
QET_LANG_PATH = '../Resources/lang/'
|
|
||||||
QET_EXAMPLES_PATH = 'share/qelectrotech/examples/'
|
|
||||||
QET_LICENSE_PATH = 'doc/qelectrotech/'
|
|
||||||
QET_DESKTOP_PATH = 'share/applications/'
|
|
||||||
QET_ICONS_PATH = 'share/icons/hicolor/'
|
|
||||||
QET_MAN_PATH = 'man/'
|
|
||||||
ICON = 'ico/mac_icon/qelectrotech.icns'
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# Commenter la ligne ci-dessous pour desactiver l'option --common-elements-dir
|
|
||||||
DEFINES += QET_ALLOW_OVERRIDE_CED_OPTION
|
|
||||||
|
|
||||||
# Comment the line below to disable the --common-tbt-dir option
|
|
||||||
DEFINES += QET_ALLOW_OVERRIDE_CTBTD_OPTION
|
|
||||||
|
|
||||||
# Commenter la ligne ci-dessous pour desactiver l'option --config-dir
|
|
||||||
DEFINES += QET_ALLOW_OVERRIDE_CD_OPTION
|
|
||||||
|
|
||||||
# Commenter la ligne ci-dessous pour desactiver l'option --data-dir
|
|
||||||
DEFINES += QET_ALLOW_OVERRIDE_DD_OPTION
|
|
||||||
|
|
||||||
#comment the line below to disable the project database export
|
|
||||||
DEFINES += QET_EXPORT_PROJECT_DB
|
|
||||||
|
|
||||||
# warn on *any* usage of deprecated APIs
|
|
||||||
#DEFINES += QT_DEPRECATED_WARNINGS
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
include(sources/PropertiesEditor/PropertiesEditor.pri)
|
|
||||||
include(sources/QetGraphicsItemModeler/QetGraphicsItemModeler.pri)
|
|
||||||
include(sources/QPropertyUndoCommand/QPropertyUndoCommand.pri)
|
|
||||||
include(SingleApplication/singleapplication.pri)
|
|
||||||
include(sources/QWidgetAnimation/QWidgetAnimation.pri)
|
|
||||||
|
|
||||||
DEFINES += QAPPLICATION_CLASS=QApplication
|
|
||||||
DEFINES += QT_MESSAGELOGCONTEXT
|
|
||||||
DEFINES += GIT_COMMIT_SHA="\\\"$(shell git -C \""$$_PRO_FILE_PWD_"\" rev-parse --verify HEAD 2>/dev/null || true)\\\""
|
|
||||||
|
|
||||||
# You can make your code fail to compile if it uses deprecated APIs.
|
|
||||||
# In order to do so, uncomment the following line.
|
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
|
||||||
|
|
||||||
# You can make your code warn on compile time for the TODO's
|
|
||||||
# In order to do so, uncomment the following line.
|
|
||||||
#DEFINES += TODO_LIST
|
|
||||||
|
|
||||||
TEMPLATE = app
|
|
||||||
|
|
||||||
#ToDo comment close the gap with Cmake
|
|
||||||
#this is my next problem now
|
|
||||||
INCLUDEPATH += sources/ui
|
|
||||||
|
|
||||||
#comment close the gap with Cmake (De-Backer)
|
|
||||||
#DEPENDPATH += .
|
|
||||||
#INCLUDEPATH += sources \
|
|
||||||
# sources/titleblock \
|
|
||||||
# sources/ui \
|
|
||||||
# sources/qetgraphicsitem \
|
|
||||||
# sources/qetgraphicsitem/ViewItem \
|
|
||||||
# sources/qetgraphicsitem/ViewItem/ui \
|
|
||||||
# sources/richtext \
|
|
||||||
# sources/factory \
|
|
||||||
# sources/properties \
|
|
||||||
# sources/dvevent \
|
|
||||||
# sources/editor \
|
|
||||||
# sources/editor/esevent \
|
|
||||||
# sources/editor/graphicspart \
|
|
||||||
# sources/editor/ui \
|
|
||||||
# sources/editor/UndoCommand \
|
|
||||||
# sources/undocommand \
|
|
||||||
# sources/diagramevent \
|
|
||||||
# sources/ElementsCollection \
|
|
||||||
# sources/ElementsCollection/ui \
|
|
||||||
# sources/autoNum \
|
|
||||||
# sources/autoNum/ui \
|
|
||||||
# sources/ui/configpage \
|
|
||||||
# sources/SearchAndReplace \
|
|
||||||
# sources/SearchAndReplace/ui \
|
|
||||||
# sources/NameList \
|
|
||||||
# sources/NameList/ui \
|
|
||||||
# sources/utils \
|
|
||||||
# pugixml \
|
|
||||||
# sources/dataBase \
|
|
||||||
# sources/dataBase/ui \
|
|
||||||
# sources/factory/ui \
|
|
||||||
# sources/print
|
|
||||||
|
|
||||||
# Fichiers sources
|
|
||||||
HEADERS += $$files(sources/*.h) \
|
|
||||||
$$files(sources/project/*.h) \
|
|
||||||
$$files(sources/ui/*.h) \
|
|
||||||
$$files(sources/editor/*.h) \
|
|
||||||
$$files(sources/titleblock/*.h) \
|
|
||||||
$$files(sources/richtext/*.h) \
|
|
||||||
$$files(sources/qetgraphicsitem/*.h) \
|
|
||||||
$$files(sources/qetgraphicsitem/ViewItem/*.h) \
|
|
||||||
$$files(sources/qetgraphicsitem/ViewItem/ui/*.h) \
|
|
||||||
$$files(sources/factory/*.h) \
|
|
||||||
$$files(sources/properties/*.h) \
|
|
||||||
$$files(sources/editor/ui/*.h) \
|
|
||||||
$$files(sources/editor/esevent/*.h) \
|
|
||||||
$$files(sources/editor/graphicspart/*.h) \
|
|
||||||
$$files(sources/editor/UndoCommand/*h) \
|
|
||||||
$$files(sources/dvevent/*.h) \
|
|
||||||
$$files(sources/undocommand/*.h) \
|
|
||||||
$$files(sources/diagramevent/*.h) \
|
|
||||||
$$files(sources/ElementsCollection/*.h) \
|
|
||||||
$$files(sources/ElementsCollection/ui/*.h) \
|
|
||||||
$$files(sources/autoNum/*.h) \
|
|
||||||
$$files(sources/autoNum/ui/*.h) \
|
|
||||||
$$files(sources/ui/configpage/*.h) \
|
|
||||||
$$files(sources/SearchAndReplace/*.h) \
|
|
||||||
$$files(sources/SearchAndReplace/ui/*.h) \
|
|
||||||
$$files(sources/NameList/*.h) \
|
|
||||||
$$files(sources/NameList/ui/*.h) \
|
|
||||||
$$files(sources/utils/*.h) \
|
|
||||||
$$files(pugixml/src/*.hpp) \
|
|
||||||
$$files(sources/dataBase/*.h) \
|
|
||||||
$$files(sources/dataBase/ui/*.h) \
|
|
||||||
$$files(sources/factory/ui/*.h) \
|
|
||||||
$$files(sources/print/*.h) \
|
|
||||||
$$files(sources/TerminalStrip/*.h) \
|
|
||||||
$$files(sources/TerminalStrip/ui/*.h) \
|
|
||||||
$$files(sources/TerminalStrip/ui/ConfigPage/*h) \
|
|
||||||
$$files(sources/TerminalStrip/UndoCommand/*.h) \
|
|
||||||
$$files(sources/TerminalStrip/GraphicsItem/*.h) \
|
|
||||||
$$files(sources/TerminalStrip/GraphicsItem/properties/*.h) \
|
|
||||||
$$files(sources/xml/*.h) \
|
|
||||||
$$files(sources/dxf/*.h) \
|
|
||||||
$$files(sources/qet_elementscaler/*.h) \
|
|
||||||
$$files(sources/svg/*.h) \
|
|
||||||
$$files(sources/import/edz/*.h) \
|
|
||||||
$$files(sources/import/edz/lzma/*.h) \
|
|
||||||
$$files(sources/logging/*.h) \
|
|
||||||
$$files(sources/logging/ui/*.h)
|
|
||||||
|
|
||||||
SOURCES += $$files(sources/*.cpp) \
|
|
||||||
$$files(sources/editor/*.cpp) \
|
|
||||||
$$files(sources/project/*.cpp) \
|
|
||||||
$$files(sources/titleblock/*.cpp) \
|
|
||||||
$$files(sources/richtext/*.cpp) \
|
|
||||||
$$files(sources/ui/*.cpp) \
|
|
||||||
$$files(sources/qetgraphicsitem/*.cpp) \
|
|
||||||
$$files(sources/qetgraphicsitem/ViewItem/*.cpp) \
|
|
||||||
$$files(sources/qetgraphicsitem/ViewItem/ui/*.cpp) \
|
|
||||||
$$files(sources/factory/*.cpp) \
|
|
||||||
$$files(sources/properties/*.cpp) \
|
|
||||||
$$files(sources/editor/ui/*.cpp) \
|
|
||||||
$$files(sources/editor/esevent/*.cpp) \
|
|
||||||
$$files(sources/editor/graphicspart/*.cpp) \
|
|
||||||
$$files(sources/editor/UndoCommand/*cpp) \
|
|
||||||
$$files(sources/dvevent/*.cpp) \
|
|
||||||
$$files(sources/undocommand/*.cpp) \
|
|
||||||
$$files(sources/diagramevent/*.cpp) \
|
|
||||||
$$files(sources/ElementsCollection/*.cpp) \
|
|
||||||
$$files(sources/ElementsCollection/ui/*.cpp) \
|
|
||||||
$$files(sources/autoNum/*.cpp) \
|
|
||||||
$$files(sources/autoNum/ui/*.cpp) \
|
|
||||||
$$files(sources/ui/configpage/*.cpp) \
|
|
||||||
$$files(sources/SearchAndReplace/*.cpp) \
|
|
||||||
$$files(sources/SearchAndReplace/ui/*.cpp) \
|
|
||||||
$$files(sources/NameList/*.cpp) \
|
|
||||||
$$files(sources/NameList/ui/*.cpp) \
|
|
||||||
$$files(sources/utils/*.cpp) \
|
|
||||||
$$files(pugixml/src/*.cpp) \
|
|
||||||
$$files(sources/dataBase/*.cpp) \
|
|
||||||
$$files(sources/dataBase/ui/*.cpp) \
|
|
||||||
$$files(sources/factory/ui/*.cpp) \
|
|
||||||
$$files(sources/print/*.cpp) \
|
|
||||||
$$files(sources/TerminalStrip/*.cpp) \
|
|
||||||
$$files(sources/TerminalStrip/ui/*.cpp) \
|
|
||||||
$$files(sources/TerminalStrip/ui/ConfigPage/*cpp) \
|
|
||||||
$$files(sources/TerminalStrip/UndoCommand/*.cpp) \
|
|
||||||
$$files(sources/TerminalStrip/GraphicsItem/*.cpp) \
|
|
||||||
$$files(sources/TerminalStrip/GraphicsItem/properties/*.cpp) \
|
|
||||||
$$files(sources/xml/*.cpp) \
|
|
||||||
$$files(sources/dxf/*.cpp) \
|
|
||||||
$$files(sources/qet_elementscaler/*.cpp) \
|
|
||||||
$$files(sources/svg/*.cpp) \
|
|
||||||
$$files(sources/import/edz/*.cpp) \
|
|
||||||
$$files(sources/import/edz/lzma/*.c) \
|
|
||||||
$$files(sources/logging/*.cpp) \
|
|
||||||
$$files(sources/logging/ui/*.cpp)
|
|
||||||
|
|
||||||
|
|
||||||
# Needed for use promote QTreeWidget in terminalstripeditor.ui
|
|
||||||
INCLUDEPATH += sources/TerminalStrip/ui
|
|
||||||
|
|
||||||
# Needed for the EPLAN .edz importer (PR #513) and its bundled lzma/7z sources
|
|
||||||
INCLUDEPATH += sources/import/edz
|
|
||||||
INCLUDEPATH += sources/import/edz/lzma
|
|
||||||
|
|
||||||
# Liste des fichiers qui seront incorpores au binaire en tant que ressources Qt
|
|
||||||
RESOURCES += qelectrotech.qrc
|
|
||||||
|
|
||||||
# Liste des ressources Windows
|
|
||||||
#RC_FILE = ico/windows_icon/qelectrotech.rc
|
|
||||||
|
|
||||||
# Fichiers de traduction qui seront installes
|
|
||||||
TRANSLATIONS += lang/*.ts
|
|
||||||
|
|
||||||
# Modules Qt utilises par l'application
|
|
||||||
QT += xml svg network sql widgets printsupport concurrent gui-private
|
|
||||||
|
|
||||||
# KDE Frameworks 5 (KWidgetsAddons, KCoreAddons) are optional.
|
|
||||||
#
|
|
||||||
# Pass CONFIG+=no_kf5 to qmake to build against the Qt-only replacements in
|
|
||||||
# sources/ui/nokde instead. This mirrors the CMake option BUILD_WITH_KF5=OFF
|
|
||||||
# and is the easiest route on Windows/MinGW, where KF5 is awkward to obtain.
|
|
||||||
#
|
|
||||||
# qmake CONFIG+=no_kf5 qelectrotech.pro
|
|
||||||
#
|
|
||||||
# sources/ui/nokde is only added to the include path in that configuration, so
|
|
||||||
# a normal KF5 build is unaffected.
|
|
||||||
no_kf5 {
|
|
||||||
DEFINES += BUILD_WITHOUT_KF5
|
|
||||||
INCLUDEPATH += sources/ui/nokde
|
|
||||||
HEADERS += $$files(sources/ui/nokde/*.h)
|
|
||||||
SOURCES += $$files(sources/ui/nokde/*.cpp)
|
|
||||||
} else {
|
|
||||||
QT += KWidgetsAddons KCoreAddons
|
|
||||||
}
|
|
||||||
|
|
||||||
# Private Qt GUI headers (needed for QPdfEngine::drawHyperlink)
|
|
||||||
# gui-private should add this automatically, but some distros need it explicit
|
|
||||||
INCLUDEPATH += $$[QT_INSTALL_HEADERS]/QtGui/$$[QT_VERSION]/QtGui
|
|
||||||
|
|
||||||
# UI DESIGNER FILES AND GENERATION SOURCES FILES
|
|
||||||
FORMS += $$files(sources/richtext/*.ui) \
|
|
||||||
$$files(sources/ui/*.ui) \
|
|
||||||
$$files(sources/editor/ui/*.ui) \
|
|
||||||
$$files(sources/ElementsCollection/ui/*.ui) \
|
|
||||||
$$files(sources/autoNum/ui/*.ui) \
|
|
||||||
$$files(sources/ui/configpage/*.ui) \
|
|
||||||
$$files(sources/SearchAndReplace/ui/*.ui) \
|
|
||||||
$$files(sources/NameList/ui/*.ui) \
|
|
||||||
$$files(sources/qetgraphicsitem/ViewItem/ui/*.ui) \
|
|
||||||
$$files(sources/dataBase/ui/*.ui) \
|
|
||||||
$$files(sources/factory/ui/*.ui) \
|
|
||||||
$$files(sources/print/*.ui) \
|
|
||||||
$$files(sources/TerminalStrip/ui/*.ui) \
|
|
||||||
$$files(sources/TerminalStrip/ui/ConfigPage/*.ui)
|
|
||||||
|
|
||||||
UI_SOURCES_DIR = sources/ui/
|
|
||||||
UI_HEADERS_DIR = sources/ui/
|
|
||||||
|
|
||||||
# Configuration de la compilation
|
|
||||||
CONFIG += c++17 debug_and_release warn_on link_pkgconfig
|
|
||||||
|
|
||||||
# Nom du binaire genere par la compilation
|
|
||||||
TARGET = qelectrotech
|
|
||||||
|
|
||||||
# Ajustement des bibliotheques utilisees lors de l'edition des liens
|
|
||||||
unix:QMAKE_LIBS_THREAD -= -lpthread
|
|
||||||
unix|win32: PKGCONFIG += sqlite3
|
|
||||||
|
|
||||||
# Enable C++17
|
|
||||||
QMAKE_CXXFLAGS += -std=c++17
|
|
||||||
|
|
||||||
# Description de l'installation
|
|
||||||
target.path = $$join(INSTALL_PREFIX,,,$${QET_BINARY_PATH})
|
|
||||||
|
|
||||||
elements.path = $$join(INSTALL_PREFIX,,,$${QET_COMMON_COLLECTION_PATH})
|
|
||||||
elements.files = elements/*
|
|
||||||
|
|
||||||
tbt.path = $$join(INSTALL_PREFIX,,,$${QET_COMMON_TBT_PATH})
|
|
||||||
tbt.files = titleblocks/*
|
|
||||||
|
|
||||||
lang.path = $$join(INSTALL_PREFIX,,,$${QET_LANG_PATH})
|
|
||||||
lang.files = $$replace(TRANSLATIONS, '.ts', '.qm')
|
|
||||||
|
|
||||||
examples.path = $$join(INSTALL_PREFIX,,,$${QET_EXAMPLES_PATH})
|
|
||||||
examples.files = examples/*
|
|
||||||
|
|
||||||
copyright.path = $$join(INSTALL_PREFIX,,,$${QET_LICENSE_PATH})
|
|
||||||
copyright.files = LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog
|
|
||||||
|
|
||||||
mime_package.path = $$join(INSTALL_PREFIX,,,$${QET_MIME_PACKAGE_PATH})
|
|
||||||
mime_package.files = misc/qelectrotech.xml
|
|
||||||
|
|
||||||
desktop.path = $$join(INSTALL_PREFIX,,,$${QET_DESKTOP_PATH})
|
|
||||||
desktop.files = misc/org.qelectrotech.qelectrotech.desktop
|
|
||||||
|
|
||||||
appdata.path = $$join(INSTALL_PREFIX,,,$${QET_APPDATA_PATH})
|
|
||||||
appdata.files = misc/qelectrotech.appdata.xml
|
|
||||||
|
|
||||||
icons.path = $$join(INSTALL_PREFIX,,,$${QET_ICONS_PATH})
|
|
||||||
icons.files = ico/breeze-icons/16x16 \
|
|
||||||
ico/breeze-icons/22x22 \
|
|
||||||
ico/breeze-icons/32x32 \
|
|
||||||
ico/breeze-icons/48x48 \
|
|
||||||
ico/breeze-icons/64x64 \
|
|
||||||
ico/breeze-icons/128x128 \
|
|
||||||
ico/breeze-icons/256x256
|
|
||||||
|
|
||||||
man.path = $$join(INSTALL_PREFIX,,,$${QET_MAN_PATH})
|
|
||||||
man.files = man/files/*
|
|
||||||
man.extra = sh man/compress_man_pages.sh
|
|
||||||
|
|
||||||
# L'installation comprend la copie du binaire, des elements, des fichiers de langue et du fichier LICENSE
|
|
||||||
INSTALLS += target elements tbt lang copyright
|
|
||||||
# Sous Unix, on installe egalement l'icone, un fichier .desktop, des fichiers mime et les pages de manuel
|
|
||||||
unix {
|
|
||||||
INSTALLS += desktop mime_package icons man examples appdata
|
|
||||||
}
|
|
||||||
|
|
||||||
# Options de compilation communes a Unix et MacOS X
|
|
||||||
unix {
|
|
||||||
# Chemin des fichiers de traduction ; par defaut : lang/ dans le repertoire d'execution
|
|
||||||
DEFINES += QET_LANG_PATH=$$join(COMPIL_PREFIX,,,$${QET_LANG_PATH})
|
|
||||||
|
|
||||||
# Chemin de la collection commune ; par defaut : elements/ dans le repertoire d'execution
|
|
||||||
DEFINES += QET_COMMON_COLLECTION_PATH=$$join(COMPIL_PREFIX,,,$${QET_COMMON_COLLECTION_PATH})
|
|
||||||
|
|
||||||
DEFINES += QET_COMMON_TBT_PATH=$$join(COMPIL_PREFIX,,,$${QET_COMMON_TBT_PATH})
|
|
||||||
}
|
|
||||||
|
|
||||||
# Options de compilation specifiques a MacOS X
|
|
||||||
macx {
|
|
||||||
# les chemins definis precedemment sont relatifs au dossier contenant le binaire executable
|
|
||||||
DEFINES += QET_LANG_PATH_RELATIVE_TO_BINARY_PATH
|
|
||||||
DEFINES += QET_COMMON_COLLECTION_PATH_RELATIVE_TO_BINARY_PATH
|
|
||||||
|
|
||||||
|
|
||||||
equals(QMAKE_TARGET.arch, x86) {
|
|
||||||
message( "It's x86" )
|
|
||||||
LIBS += /opt/digikam.org.x86_64/lib/libsqlite3.0.dylib
|
|
||||||
}
|
|
||||||
equals(QMAKE_TARGET.arch, arm64) {
|
|
||||||
message( "It's arm64" )
|
|
||||||
LIBS += /opt/digikam.org.arm64/lib/libsqlite3.0.dylib
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Compilers-specific options
|
|
||||||
unix {
|
|
||||||
QMAKE_COPY_DIR = 'cp -f -r --preserve=timestamps'
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,9 @@ TerminalStripLayoutEditor::TerminalStripLayoutEditor(QSharedPointer<TerminalStri
|
|||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->m_graphics_view->setScene(new QGraphicsScene{ this });
|
ui->m_graphics_view->setScene(new QGraphicsScene{ this });
|
||||||
|
// The preview is printed output: black ink on a white page, whatever
|
||||||
|
// the application palette.
|
||||||
|
ui->m_graphics_view->setBackgroundBrush(Qt::white);
|
||||||
ui->m_graphics_view->scene()->addItem(&m_preview_strip_item);
|
ui->m_graphics_view->scene()->addItem(&m_preview_strip_item);
|
||||||
updateUi();
|
updateUi();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#include "../qetgraphicsitem/element.h"
|
#include "../qetgraphicsitem/element.h"
|
||||||
#include "../qetxml.h"
|
#include "../qetxml.h"
|
||||||
#include "../qetproject.h"
|
#include "../qetproject.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDomDocument>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -703,6 +705,74 @@ namespace autonum
|
|||||||
return formula;
|
return formula;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief prefixFromLabelFile
|
||||||
|
Look up a prefix for @a path (path[dirLevel] outermost, path[1] the
|
||||||
|
deepest directory; path[0], the element's own file name, is never
|
||||||
|
matched) in the qet_labels.xml at @a filepath.
|
||||||
|
|
||||||
|
Descends through nested \<category name="..."\> elements matching
|
||||||
|
path[dirLevel], path[dirLevel-1], ..., path[1] in turn, considering
|
||||||
|
only *direct* children at each step -- unlike a flat token scan,
|
||||||
|
this cannot be fooled by a same-named category living elsewhere in
|
||||||
|
the document at the wrong nesting depth (bugtracker #671 item 5).
|
||||||
|
|
||||||
|
At each matched level, that category's own \<prefix\> child -- even
|
||||||
|
an empty one -- overrides whatever a shallower ancestor already
|
||||||
|
provided, so an explicit empty \<prefix/\> cancels inheritance
|
||||||
|
rather than silently falling back to it (the behaviour requested in
|
||||||
|
PR #686 review). A category with no \<prefix\> child at all leaves
|
||||||
|
the inherited value untouched, which is how a directory with no
|
||||||
|
prefix of its own comes to inherit its parent's, as the file's own
|
||||||
|
header comment documents.
|
||||||
|
|
||||||
|
@return the prefix that applies, or a null QString if the file
|
||||||
|
cannot be read, is not well-formed, or does not describe this
|
||||||
|
path at all (as opposed to describing it with no prefix
|
||||||
|
anywhere along it, which is a non-null empty string).
|
||||||
|
*/
|
||||||
|
static QString prefixFromLabelFile(const QString &filepath, const QStringList &path, int dirLevel)
|
||||||
|
{
|
||||||
|
QFile file(filepath);
|
||||||
|
if (!file.open(QFile::ReadOnly | QFile::Text))
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
QDomDocument document;
|
||||||
|
if (!document.setContent(&file))
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
QDomElement node = document.documentElement();
|
||||||
|
if (node.isNull())
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
QString prefix;
|
||||||
|
for (int i = dirLevel ; i >= 1 ; --i) {
|
||||||
|
QDomElement child = node.firstChildElement(QStringLiteral("category"));
|
||||||
|
while (!child.isNull()
|
||||||
|
&& child.attribute(QStringLiteral("name")) != path[i]) {
|
||||||
|
child = child.nextSiblingElement(QStringLiteral("category"));
|
||||||
|
}
|
||||||
|
if (child.isNull())
|
||||||
|
return QString();
|
||||||
|
node = child;
|
||||||
|
|
||||||
|
const QDomElement own = node.firstChildElement(QStringLiteral("prefix"));
|
||||||
|
if (!own.isNull()) {
|
||||||
|
//readElementText()'s null-vs-empty distinction that PR
|
||||||
|
//#686 needed for the old QXmlStreamReader-based lookup
|
||||||
|
//has a QDomElement equivalent: text() on an empty
|
||||||
|
//element can itself come back null depending on how the
|
||||||
|
//XML was written, so the same explicit fallback applies
|
||||||
|
//-- an empty QString here means "found, deliberately
|
||||||
|
//blank", not "not found".
|
||||||
|
prefix = own.text();
|
||||||
|
if (prefix.isNull())
|
||||||
|
prefix = QString("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief elementPrefixForLocation
|
@brief elementPrefixForLocation
|
||||||
@param location
|
@param location
|
||||||
@@ -716,114 +786,85 @@ namespace autonum
|
|||||||
if (!location.isProject())
|
if (!location.isProject())
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
QXmlStreamReader rxml;
|
//Directory names from the element up to (not including) the
|
||||||
QString path[10];
|
//collection root, outermost last -- path[dirLevel] is the
|
||||||
int i = -1;
|
//top-level category, path[1] the element's immediate parent
|
||||||
|
//directory, path[0] the element's own file name (never matched
|
||||||
|
//against a category: the search stops descending once it has
|
||||||
|
//matched path[1], the deepest real directory). An unbounded
|
||||||
|
//QStringList rather than a fixed-size array, because a custom
|
||||||
|
//collection can nest deeper than the shipped one -- see
|
||||||
|
//bugtracker #671 item 3.
|
||||||
|
QStringList path;
|
||||||
ElementsLocation current_location = location;
|
ElementsLocation current_location = location;
|
||||||
int dirLevel = -1;
|
while ((current_location.parent() != current_location)
|
||||||
|
&& (current_location.parent().fileName() != "import"))
|
||||||
//Add location name to path array
|
|
||||||
while((current_location.parent() != current_location) && (current_location.parent().fileName() != "import"))
|
|
||||||
{
|
{
|
||||||
i++;
|
path << current_location.fileName();
|
||||||
path[i]=current_location.fileName();
|
|
||||||
current_location = current_location.parent();
|
current_location = current_location.parent();
|
||||||
dirLevel++;
|
|
||||||
}
|
}
|
||||||
//User Element without folder treatment
|
//User element without folder treatment
|
||||||
if (i == -1)
|
if (path.isEmpty()) {
|
||||||
{
|
path << current_location.fileName();
|
||||||
i = 0;
|
|
||||||
path[i]=current_location.fileName();
|
|
||||||
current_location = current_location.parent();
|
current_location = current_location.parent();
|
||||||
dirLevel = 0;
|
}
|
||||||
|
const int dirLevel = path.size() - 1;
|
||||||
|
//Name of the top-level tree the element's path was found
|
||||||
|
//under, e.g. "10_electric" -- or, for a custom/company
|
||||||
|
//collection not organised that way, whatever its top-level
|
||||||
|
//folder happens to be called.
|
||||||
|
const QString collection_root = current_location.fileName();
|
||||||
|
|
||||||
|
//Every top-level common-collection tree (10_electric,
|
||||||
|
//20_logic, 30_hydraulic, ...) may carry its own
|
||||||
|
//qet_labels.xml, with categories relative to that tree, the
|
||||||
|
//same way 10_electric/qet_labels.xml already does -- not just
|
||||||
|
//10_electric, which is all the hardcoded check this replaces
|
||||||
|
//used to allow (bugtracker #671 item 2). commonElementsDir()
|
||||||
|
//-- unlike customElementsDir(), which normalises this itself
|
||||||
|
//-- returns whatever path the user configured verbatim, with
|
||||||
|
//no guaranteed trailing separator; concatenating a suffix onto
|
||||||
|
//it directly used to silently mangle the path (and so the
|
||||||
|
//prefix lookup) for any install relocated to a directory
|
||||||
|
//without a trailing slash (#671 item 1). QDir::filePath()
|
||||||
|
//joins correctly either way.
|
||||||
|
{
|
||||||
|
const QString common_file = QDir(QETApp::commonElementsDir())
|
||||||
|
.filePath(collection_root + QStringLiteral("/qet_labels.xml"));
|
||||||
|
const QString prefix = prefixFromLabelFile(common_file, path, dirLevel);
|
||||||
|
if (!prefix.isNull()) {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Custom labels if qet_labels.xml exits in customElementsDir
|
/* Which collection an element actually came from is not
|
||||||
if (current_location.fileName() != "10_electric"){
|
* recoverable post-import (addElement() strips the protocol),
|
||||||
QString custom_labels = "qet_labels.xml";
|
* so custom and company labels files are tried against two
|
||||||
QString customfilepath = QETApp::customElementsDir().append(custom_labels);
|
* possible layouts: with the collection's top-level tree name
|
||||||
|
* folded into the path (a custom/company file organised as a
|
||||||
QFile file(customfilepath);
|
* mirror of the common collection, tree name included) and
|
||||||
file.isReadable();
|
* without it (a file scoped to just this one tree, matching
|
||||||
if (!file.open(QFile::ReadOnly | QFile::Text))
|
* how the common collection's own files are written). Custom
|
||||||
return QString();
|
* is tried before company, so a user override wins over a
|
||||||
rxml.setDevice(&file);
|
* shared one.
|
||||||
rxml.readNext();
|
*/
|
||||||
|
QStringList path_from_root = path;
|
||||||
|
path_from_root << collection_root;
|
||||||
|
|
||||||
while(!rxml.atEnd())
|
const QStringList candidate_dirs = {
|
||||||
{
|
QETApp::customElementsDir(),
|
||||||
if (rxml.attributes().value("name").toString() == path[i])
|
QETApp::companyElementsDir()
|
||||||
{
|
};
|
||||||
rxml.readNext();
|
for (const QString &dir : candidate_dirs) {
|
||||||
i=i-1;
|
const QString candidate =
|
||||||
//reached element directory
|
QDir(dir).filePath(QStringLiteral("qet_labels.xml"));
|
||||||
if (i==0)
|
for (const QStringList &segments : {path_from_root, path}) {
|
||||||
{
|
const QString prefix = prefixFromLabelFile(
|
||||||
for (int j=i; j<= dirLevel; j = j +1)
|
candidate, segments, segments.size() - 1);
|
||||||
{
|
if (!prefix.isNull()) {
|
||||||
//if there is a prefix available apply prefix
|
return prefix;
|
||||||
if(rxml.name().toString()=="prefix")
|
|
||||||
{
|
|
||||||
return rxml.readElementText();
|
|
||||||
}
|
|
||||||
//if there isn't a prefix available, find parent prefix in parent folder
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (rxml.readNextStartElement() && rxml.name().toString()!="prefix")
|
|
||||||
{
|
|
||||||
rxml.skipCurrentElement();
|
|
||||||
rxml.readNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rxml.readNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QString qet_labels = "10_electric/qet_labels.xml";
|
|
||||||
QString filepath = QETApp::commonElementsDir().append(qet_labels);
|
|
||||||
QFile file(filepath);
|
|
||||||
file.isReadable();
|
|
||||||
if (!file.open(QFile::ReadOnly | QFile::Text))
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
rxml.setDevice(&file);
|
|
||||||
rxml.readNext();
|
|
||||||
|
|
||||||
while(!rxml.atEnd())
|
|
||||||
{
|
|
||||||
if (rxml.attributes().value("name").toString() == path[i])
|
|
||||||
{
|
|
||||||
rxml.readNext();
|
|
||||||
i=i-1;
|
|
||||||
//reached element directory
|
|
||||||
if (i==0)
|
|
||||||
{
|
|
||||||
for (int j=i; j<= dirLevel; j = j +1)
|
|
||||||
{
|
|
||||||
//if there is a prefix available apply prefix
|
|
||||||
if(rxml.name().toString()=="prefix")
|
|
||||||
{
|
|
||||||
return rxml.readElementText();
|
|
||||||
}
|
|
||||||
//if there isn't a prefix available, find parent prefix in parent folder
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (rxml.readNextStartElement() && rxml.name().toString()!="prefix")
|
|
||||||
{
|
|
||||||
rxml.skipCurrentElement();
|
|
||||||
rxml.readNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rxml.readNext();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ FolioAutonumberingW::~FolioAutonumberingW()
|
|||||||
*/
|
*/
|
||||||
void FolioAutonumberingW::setContext(QList <QString> autonums)
|
void FolioAutonumberingW::setContext(QList <QString> autonums)
|
||||||
{
|
{
|
||||||
|
// Replace the list rather than append to it: this is called again
|
||||||
|
// whenever the project's numberings change (import, for instance),
|
||||||
|
// and appending gave a second copy of every name.
|
||||||
|
ui->m_autonums_cb->clear();
|
||||||
foreach (QString str, autonums) { ui->m_autonums_cb->addItem(str);}
|
foreach (QString str, autonums) { ui->m_autonums_cb->addItem(str);}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
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 "bomexport.h"
|
||||||
|
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlRecord>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString csvField(QString value)
|
||||||
|
{
|
||||||
|
value.replace(QLatin1Char('"'), QStringLiteral("\"\""));
|
||||||
|
return QLatin1Char('"') + value + QLatin1Char('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray csvRecord(const QStringList &values)
|
||||||
|
{
|
||||||
|
QStringList escaped;
|
||||||
|
for (const auto &value : values) {
|
||||||
|
escaped.append(csvField(value));
|
||||||
|
}
|
||||||
|
return (escaped.join(QLatin1Char(';')) + QLatin1Char('\n')).toUtf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList BomExport::defaultColumns()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
QStringLiteral("label"),
|
||||||
|
QStringLiteral("designation"),
|
||||||
|
QStringLiteral("manufacturer"),
|
||||||
|
QStringLiteral("manufacturer_reference"),
|
||||||
|
QStringLiteral("model"),
|
||||||
|
QStringLiteral("description"),
|
||||||
|
QStringLiteral("category"),
|
||||||
|
QStringLiteral("quantity"),
|
||||||
|
QStringLiteral("voltage_rating"),
|
||||||
|
QStringLiteral("current_rating"),
|
||||||
|
QStringLiteral("folio"),
|
||||||
|
QStringLiteral("notes")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BomExport::defaultQuery()
|
||||||
|
{
|
||||||
|
//Slaves and terminals are included because both are routinely
|
||||||
|
//separately orderable hardware. A circuit breaker can carry ten or
|
||||||
|
//twenty auxiliary blocks, each with its own order code, and a
|
||||||
|
//terminal block is a purchased part in its own right. Neither shares
|
||||||
|
//a line with its master: the query is ungrouped, one row per element,
|
||||||
|
//so each appears as the distinct item it is.
|
||||||
|
//
|
||||||
|
//Anything that should not be ordered is kept out by setting
|
||||||
|
//exclude_from_bom on the element, which the view already honours --
|
||||||
|
//a relay's own auxiliary contact, say.
|
||||||
|
//
|
||||||
|
//Thumbnails are deliberately left out for now even though ten of
|
||||||
|
//them in the shipped examples carry manufacturer and reference data
|
||||||
|
//(the assembly-plan mounting-plate symbols), because that has not
|
||||||
|
//been asked for and is a separate question. The folio report arrows
|
||||||
|
//and the conductor definition stay out because they are not hardware.
|
||||||
|
//
|
||||||
|
//See discussion #847.
|
||||||
|
return QStringLiteral("SELECT %1 FROM element_nomenclature_view "
|
||||||
|
"WHERE element_type IN "
|
||||||
|
"('simple', 'master', 'slave', 'terminal') "
|
||||||
|
"ORDER BY diagram_position, position, label")
|
||||||
|
.arg(defaultColumns().join(QStringLiteral(", ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BomExport::toCsv(QSqlQuery &query, const QStringList &headers,
|
||||||
|
bool include_headers, int *row_count)
|
||||||
|
{
|
||||||
|
QByteArray csv("\xEF\xBB\xBF");
|
||||||
|
if (include_headers) {
|
||||||
|
csv += csvRecord(headers);
|
||||||
|
}
|
||||||
|
int rows = 0;
|
||||||
|
while (query.next())
|
||||||
|
{
|
||||||
|
QStringList values;
|
||||||
|
for (int i = 0; i < query.record().count(); ++i) {
|
||||||
|
values.append(query.value(i).toString());
|
||||||
|
}
|
||||||
|
csv += csvRecord(values);
|
||||||
|
++rows;
|
||||||
|
}
|
||||||
|
if (row_count) {
|
||||||
|
*row_count = rows;
|
||||||
|
}
|
||||||
|
return csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BomExport::writeCsv(const QString &file_path, const QByteArray &csv,
|
||||||
|
QString *error)
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
error->clear();
|
||||||
|
}
|
||||||
|
QSaveFile file(file_path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly) || file.write(csv) != csv.size())
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
*error = file.errorString();
|
||||||
|
}
|
||||||
|
file.cancelWriting();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!file.commit())
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
*error = file.errorString();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
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 BOMEXPORT_H
|
||||||
|
#define BOMEXPORT_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QSqlQuery;
|
||||||
|
|
||||||
|
namespace BomExport
|
||||||
|
{
|
||||||
|
QStringList defaultColumns();
|
||||||
|
QString defaultQuery();
|
||||||
|
QByteArray toCsv(QSqlQuery &query, const QStringList &headers,
|
||||||
|
bool include_headers = true, int *row_count = nullptr);
|
||||||
|
bool writeCsv(const QString &file_path, const QByteArray &csv,
|
||||||
|
QString *error = nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOMEXPORT_H
|
||||||
+89
-41
@@ -16,6 +16,7 @@
|
|||||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "cli_export.h"
|
#include "cli_export.h"
|
||||||
|
#include "bomexport.h"
|
||||||
|
|
||||||
#include "bordertitleblock.h"
|
#include "bordertitleblock.h"
|
||||||
#include "conductornumexport.h"
|
#include "conductornumexport.h"
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QSaveFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@@ -70,6 +72,7 @@ const QHash<QString, QString> &exportFlags()
|
|||||||
{"--export-cables", "cables"},
|
{"--export-cables", "cables"},
|
||||||
{"--export-wires", "wires"},
|
{"--export-wires", "wires"},
|
||||||
{"--export-bom", "bom"},
|
{"--export-bom", "bom"},
|
||||||
|
{"--export-wiring", "wiring"},
|
||||||
{"--export-nets", "nets"},
|
{"--export-nets", "nets"},
|
||||||
{"--export-links", "links"},
|
{"--export-links", "links"},
|
||||||
{"--info", "info"},
|
{"--info", "info"},
|
||||||
@@ -302,61 +305,40 @@ int exportCsv(QETProject &project, const QString &format, const QString &output)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Quote a field for CSV output (RFC-4180 style, ';' delimiter).
|
/// Bill of materials from the same project database and default query as the
|
||||||
QString csvField(const QString &value)
|
/// GUI nomenclature export.
|
||||||
{
|
|
||||||
if (value.contains(';') || value.contains('"')
|
|
||||||
|| value.contains('\n') || value.contains('\r')) {
|
|
||||||
QString v = value;
|
|
||||||
v.replace('"', "\"\"");
|
|
||||||
return '"' % v % '"';
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bill of materials: one row per element, key component-data fields.
|
|
||||||
/// Pulls from QET's own project database (the same source as the GUI BOM
|
|
||||||
/// export), so the output matches what the editor produces.
|
|
||||||
int exportBom(QETProject &project, const QString &output)
|
int exportBom(QETProject &project, const QString &output)
|
||||||
{
|
{
|
||||||
// The project database is built lazily; force a (re)build before querying.
|
|
||||||
project.dataBase()->updateDB();
|
project.dataBase()->updateDB();
|
||||||
|
QSqlQuery query = project.dataBase()->newQuery(BomExport::defaultQuery());
|
||||||
static const QStringList columns {
|
|
||||||
"label", "designation", "manufacturer", "manufacturer_reference",
|
|
||||||
"quantity", "location", "function", "title", "folio"
|
|
||||||
};
|
|
||||||
|
|
||||||
QSqlQuery query = project.dataBase()->newQuery(
|
|
||||||
"SELECT " % columns.join(", ") %
|
|
||||||
" FROM element_nomenclature_view ORDER BY label");
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
err << "BOM query failed: " << query.lastError().text() << "\n";
|
err << "BOM query failed: " << query.lastError().text() << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString csv = columns.join(";") % "\n";
|
|
||||||
int rows = 0;
|
int rows = 0;
|
||||||
while (query.next()) {
|
const auto csv = BomExport::toCsv(
|
||||||
QStringList values;
|
query, BomExport::defaultColumns(), true, &rows);
|
||||||
for (int i = 0; i < columns.size(); ++i)
|
QString error;
|
||||||
values << csvField(query.value(i).toString());
|
if (!BomExport::writeCsv(output, csv, &error)) {
|
||||||
csv += values.join(";") % "\n";
|
err << "Cannot write '" << output << "': " << error << "\n";
|
||||||
++rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile file(output);
|
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
||||||
err << "Cannot open '" << output << "' for writing.\n";
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
QTextStream fout(&file);
|
|
||||||
fout << csv;
|
|
||||||
file.close();
|
|
||||||
out << "Exported " << rows << " component(s) -> " << output << "\n";
|
out << "Exported " << rows << " component(s) -> " << output << "\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString csvField(const QString &value)
|
||||||
|
{
|
||||||
|
if (value.contains(QLatin1Char(';')) || value.contains(QLatin1Char('"'))
|
||||||
|
|| value.contains(QLatin1Char('\n')) || value.contains(QLatin1Char('\r')))
|
||||||
|
{
|
||||||
|
QString escaped = value;
|
||||||
|
escaped.replace(QLatin1Char('"'), QStringLiteral("\"\""));
|
||||||
|
return QLatin1Char('"') % escaped % QLatin1Char('"');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/// Count terminals on @p element that no conductor connects to.
|
/// Count terminals on @p element that no conductor connects to.
|
||||||
int freeTerminals(Element *element)
|
int freeTerminals(Element *element)
|
||||||
{
|
{
|
||||||
@@ -543,6 +525,70 @@ QHash<Element *, int> folioIndex(QETProject &project)
|
|||||||
return folio;
|
return folio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// From-to wiring list: one row per conductor, each endpoint resolved to its
|
||||||
|
/// element label and terminal name.
|
||||||
|
///
|
||||||
|
/// Reads wiring_list_view out of the project database. --export-cables produces
|
||||||
|
/// the same logical list from the document XML instead, and the two are meant
|
||||||
|
/// to agree: running both and diffing them is a direct check that the database
|
||||||
|
/// still describes the project, which is otherwise only observable through the
|
||||||
|
/// GUI.
|
||||||
|
int exportWiring(QETProject &project, const QString &output)
|
||||||
|
{
|
||||||
|
// The project database is built lazily; force a (re)build before querying.
|
||||||
|
project.dataBase()->updateDB();
|
||||||
|
|
||||||
|
static const QStringList columns {
|
||||||
|
"wire_number", "from_element_label", "from_terminal",
|
||||||
|
"to_element_label", "to_terminal", "diagram_position", "conductor_uuid"
|
||||||
|
};
|
||||||
|
|
||||||
|
QSqlQuery query = project.dataBase()->newQuery(
|
||||||
|
"SELECT " % columns.join(", ") %
|
||||||
|
" FROM wiring_list_view"
|
||||||
|
//Wire numbers are text, so a plain sort puts "10" before "9".
|
||||||
|
//Numeric ones first, ordered by value; anything non-numeric after,
|
||||||
|
//ordered as text. The trailing wire_number keeps ties stable.
|
||||||
|
" ORDER BY diagram_position,"
|
||||||
|
" CASE WHEN wire_number GLOB '[0-9]*' THEN 0 ELSE 1 END,"
|
||||||
|
" CAST(wire_number AS INTEGER),"
|
||||||
|
" wire_number");
|
||||||
|
if (!query.exec()) {
|
||||||
|
err << "Wiring list query failed: " << query.lastError().text() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString csv = columns.join(";") % "\n";
|
||||||
|
int rows = 0;
|
||||||
|
while (query.next()) {
|
||||||
|
QStringList values;
|
||||||
|
for (int i = 0; i < columns.size(); ++i)
|
||||||
|
values << csvField(query.value(i).toString());
|
||||||
|
csv += values.join(";") % "\n";
|
||||||
|
++rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Written through QSaveFile so a failure part-way leaves the previous
|
||||||
|
//file intact rather than a truncated one, and with a UTF-8 byte order
|
||||||
|
//mark: without it Excel opens a .csv as the local 8-bit codepage and
|
||||||
|
//mangles any accented element label. Qt writes UTF-8 by default, so
|
||||||
|
//the bytes were already right -- the mark is what tells Excel so.
|
||||||
|
QSaveFile file(output);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
err << "Cannot open '" << output << "' for writing.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static const char utf8_bom[] = "\xEF\xBB\xBF";
|
||||||
|
file.write(utf8_bom, 3);
|
||||||
|
file.write(csv.toUtf8());
|
||||||
|
if (!file.commit()) {
|
||||||
|
err << "Cannot write '" << output << "': " << file.errorString() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
out << "Exported " << rows << " conductor(s) -> " << output << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Electrical nets: groups of terminals joined into one potential.
|
/// Electrical nets: groups of terminals joined into one potential.
|
||||||
/// Walks QET's own potential graph, so each net is a connected component
|
/// Walks QET's own potential graph, so each net is a connected component
|
||||||
/// of terminals across all folios. The ground truth for connectivity.
|
/// of terminals across all folios. The ground truth for connectivity.
|
||||||
@@ -847,6 +893,8 @@ int run(const QStringList &args)
|
|||||||
return exportCsv(project, format, output);
|
return exportCsv(project, format, output);
|
||||||
if (format == "bom")
|
if (format == "bom")
|
||||||
return exportBom(project, output);
|
return exportBom(project, output);
|
||||||
|
if (format == "wiring")
|
||||||
|
return exportWiring(project, output);
|
||||||
if (format == "nets")
|
if (format == "nets")
|
||||||
return exportNets(project, output);
|
return exportNets(project, output);
|
||||||
if (format == "links")
|
if (format == "links")
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ namespace CLIExport {
|
|||||||
qelectrotech --export-cables <project.qet> <output.csv>
|
qelectrotech --export-cables <project.qet> <output.csv>
|
||||||
qelectrotech --export-wires <project.qet> <output.csv>
|
qelectrotech --export-wires <project.qet> <output.csv>
|
||||||
qelectrotech --export-bom <project.qet> <output.csv>
|
qelectrotech --export-bom <project.qet> <output.csv>
|
||||||
|
qelectrotech --export-wiring <project.qet> <output.csv>
|
||||||
qelectrotech --export-nets <project.qet> <output.json>
|
qelectrotech --export-nets <project.qet> <output.json>
|
||||||
qelectrotech --export-links <project.qet> <output.csv>
|
qelectrotech --export-links <project.qet> <output.csv>
|
||||||
qelectrotech --info <project.qet> [output.json]
|
qelectrotech --info <project.qet> [output.json]
|
||||||
@@ -64,6 +65,11 @@ namespace CLIExport {
|
|||||||
cables: wiring list (one row per conductor) as CSV.
|
cables: wiring list (one row per conductor) as CSV.
|
||||||
wires: list of distinct wire numbers as CSV.
|
wires: list of distinct wire numbers as CSV.
|
||||||
bom: bill of materials (one row per element) as CSV.
|
bom: bill of materials (one row per element) as CSV.
|
||||||
|
wiring: from-to wiring list (one row per conductor) as CSV, read from
|
||||||
|
the project database. Same logical list as `cables`, which
|
||||||
|
reads the document XML instead; the two are meant to agree,
|
||||||
|
so diffing them checks that the database still describes the
|
||||||
|
project.
|
||||||
nets: electrical nets (connected-terminal groups) as JSON.
|
nets: electrical nets (connected-terminal groups) as JSON.
|
||||||
links: element cross-references (coil/contact) as CSV, with
|
links: element cross-references (coil/contact) as CSV, with
|
||||||
unresolved links flagged.
|
unresolved links flagged.
|
||||||
|
|||||||
@@ -124,15 +124,15 @@ void ConductorNumExport::fillHash()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//We must define if the connected terminal is a folio report, if it is the case
|
//We must define if the connected terminals are folio reports, if
|
||||||
//we don't add the num to the hash because the terminal doesn't represent a real terminal.
|
//both are the case we don't add the num to the hash because
|
||||||
if(!(c->terminal1->parentElement()->linkType() & Element::AllReport))
|
//neither terminal represents a real terminal. Otherwise the
|
||||||
{
|
//conductor counts once, regardless of how many of its two ends
|
||||||
int value = m_hash.value(num, 0);
|
//are real -- counting per terminal instead of per conductor
|
||||||
++value;
|
//doubled every ordinary two-real-terminal conductor's tally.
|
||||||
m_hash.insert(num, value);
|
bool terminal1_is_report = c->terminal1->parentElement()->linkType() & Element::AllReport;
|
||||||
}
|
bool terminal2_is_report = c->terminal2->parentElement()->linkType() & Element::AllReport;
|
||||||
if(!(c->terminal2->parentElement()->linkType() & Element::AllReport))
|
if (!(terminal1_is_report && terminal2_is_report))
|
||||||
{
|
{
|
||||||
int value = m_hash.value(num, 0);
|
int value = m_hash.value(num, 0);
|
||||||
++value;
|
++value;
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
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 CONTACTUSAGE_H
|
||||||
|
#define CONTACTUSAGE_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief The ContactUsage struct
|
||||||
|
How many slave contacts a master element currently uses, broken down
|
||||||
|
by contact type.
|
||||||
|
|
||||||
|
Header-only and free of any graphics dependency so that the counting
|
||||||
|
rules can be unit tested on their own. MasterElement::contactUsage()
|
||||||
|
is the thin wrapper that feeds it the linked elements.
|
||||||
|
|
||||||
|
This counts contacts, which is what tells you how many contacts an
|
||||||
|
auxiliary block must provide. It is deliberately not the count that
|
||||||
|
MasterElement::isFull() uses: a master's max_slaves is a number of
|
||||||
|
slots, and a slave fills exactly one slot however many contacts it
|
||||||
|
carries.
|
||||||
|
|
||||||
|
Two rules are easy to get wrong, and both live here so that every
|
||||||
|
caller gets them right:
|
||||||
|
- a slave stands for as many contacts as its "number" kind
|
||||||
|
information says, so a 4 pole contact counts as 4, not as 1
|
||||||
|
- a changeover contact is counted once, as sw. CrossRefItem's
|
||||||
|
NOElements() and NCElements() both return it, so adding those two
|
||||||
|
lists together would count it twice.
|
||||||
|
*/
|
||||||
|
struct ContactUsage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Contact types a slave can declare. Mirrors
|
||||||
|
ElementData::SlaveState, which is not used directly so that this
|
||||||
|
header stays free of the element data dependencies and can be
|
||||||
|
unit tested on its own. MasterElement::contactUsage() maps
|
||||||
|
between the two.
|
||||||
|
*/
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
NO, ///< Normally open
|
||||||
|
NC, ///< Normally closed
|
||||||
|
SW, ///< Changeover
|
||||||
|
Other ///< Neither of the above
|
||||||
|
};
|
||||||
|
|
||||||
|
int no = 0; ///< Normally open
|
||||||
|
int nc = 0; ///< Normally closed
|
||||||
|
int sw = 0; ///< Changeover
|
||||||
|
int other = 0; ///< Neither of the above
|
||||||
|
|
||||||
|
int total() const { return no + nc + sw + other; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add one slave element to the tally.
|
||||||
|
@param type the contact type the slave declares
|
||||||
|
@param contacts how many contacts it stands for. Values below 1
|
||||||
|
are treated as 1: an element which declares no
|
||||||
|
contact count is still one contact.
|
||||||
|
*/
|
||||||
|
void addSlave(Type type, int contacts)
|
||||||
|
{
|
||||||
|
const int n = std::max(1, contacts);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case NO: no += n; break;
|
||||||
|
case NC: nc += n; break;
|
||||||
|
case SW: sw += n; break;
|
||||||
|
case Other: other += n; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTACTUSAGE_H
|
||||||
@@ -53,6 +53,7 @@ projectDataBase::projectDataBase(QETProject *project, QObject *parent) :
|
|||||||
});
|
});
|
||||||
connect(m_project, &QETProject::projectDiagramsOrderChanged, [this]()
|
connect(m_project, &QETProject::projectDiagramsOrderChanged, [this]()
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
for (auto diagram : m_project->diagrams())
|
for (auto diagram : m_project->diagrams())
|
||||||
{
|
{
|
||||||
m_diagram_order_changed.bindValue(":pos", m_project->folioIndex(diagram)+1);
|
m_diagram_order_changed.bindValue(":pos", m_project->folioIndex(diagram)+1);
|
||||||
@@ -85,14 +86,42 @@ projectDataBase::~projectDataBase()
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::updateDB()
|
void projectDataBase::updateDB()
|
||||||
{
|
{
|
||||||
|
//A bulk operation is in progress and updates the database itself once
|
||||||
|
//it is done : rebuilding now would only be thrown away by that final
|
||||||
|
//rebuild. @see setUpdateBlocked().
|
||||||
|
if (m_update_blocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Nothing in the project has changed since the last rebuild, so
|
||||||
|
//repopulating would insert exactly the rows that are already there.
|
||||||
|
//The signal is still emitted : callers and models rely on it to
|
||||||
|
//refresh, and what they read back is unchanged either way.
|
||||||
|
if (!m_content_changed)
|
||||||
|
{
|
||||||
|
emit dataBaseUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
populateDiagramTable();
|
populateDiagramTable();
|
||||||
populateDiagramInfoTable();
|
populateDiagramInfoTable();
|
||||||
populateElementTable();
|
populateElementTable();
|
||||||
populateElementInfoTable();
|
populateElementInfoTable();
|
||||||
populateConductorTable();
|
populateConductorTable();
|
||||||
|
m_content_changed = false;
|
||||||
|
|
||||||
emit dataBaseUpdated();
|
emit dataBaseUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief projectDataBase::setUpdateBlocked
|
||||||
|
@param blocked : whether updateDB() should skip the full rebuild
|
||||||
|
*/
|
||||||
|
void projectDataBase::setUpdateBlocked(bool blocked)
|
||||||
|
{
|
||||||
|
m_update_blocked = blocked;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief projectDataBase::project
|
@brief projectDataBase::project
|
||||||
@return the project of this database
|
@return the project of this database
|
||||||
@@ -111,35 +140,65 @@ QSqlQuery projectDataBase::newQuery(const QString &query) {
|
|||||||
return QSqlQuery(query, m_data_base);
|
return QSqlQuery(query, m_data_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief projectDataBase::excludedConductorCount
|
||||||
|
@return how many conductors of the project are absent from the conductor
|
||||||
|
table because an endpoint has no parent element to key on.
|
||||||
|
|
||||||
|
Counted from the live scene rather than from the database, precisely
|
||||||
|
because the database is where these conductors are *not*.
|
||||||
|
|
||||||
|
This used to count conductors whose terminals had no uuid, which was most
|
||||||
|
of them on most projects. Terminal::stableUuid() now derives an identity
|
||||||
|
from the terminal's geometry when the definition provides no uuid, so that
|
||||||
|
is no longer a reason to exclude anything, and this counts only the case
|
||||||
|
that remains genuinely unkeyable.
|
||||||
|
|
||||||
|
This is what lets a caller tell the user "N wires are missing and here
|
||||||
|
is why", instead of silently presenting a short list as if it were
|
||||||
|
complete.
|
||||||
|
*/
|
||||||
|
int projectDataBase::excludedConductorCount() const
|
||||||
|
{
|
||||||
|
if (!m_project) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (auto *diagram : m_project->diagrams())
|
||||||
|
{
|
||||||
|
const auto conductor_list = diagram->conductors();
|
||||||
|
for (auto *conductor : conductor_list)
|
||||||
|
{
|
||||||
|
//Must match addConductor()'s guard exactly, or this reports
|
||||||
|
//wires as missing that the list is in fact showing.
|
||||||
|
if (!conductor->terminal1->parentElement()
|
||||||
|
|| !conductor->terminal2->parentElement()) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief projectDataBase::addElement
|
@brief projectDataBase::addElement
|
||||||
@param element
|
@param element
|
||||||
*/
|
*/
|
||||||
void projectDataBase::addElement(Element *element)
|
void projectDataBase::addElement(Element *element)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
if (!element || !element->diagram()) {
|
if (!element || !element->diagram()) {
|
||||||
qDebug() << "projectDataBase::addElement: null element or diagram";
|
qDebug() << "projectDataBase::addElement: null element or diagram";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_insert_elements_query.bindValue(":uuid", element->uuid().toString());
|
bindElementValues(m_insert_elements_query, element, element->diagram());
|
||||||
m_insert_elements_query.bindValue(":diagram_uuid", element->diagram()->uuid().toString());
|
|
||||||
m_insert_elements_query.bindValue(":pos", element->diagram()->convertPosition(element->scenePos()).toString());
|
|
||||||
m_insert_elements_query.bindValue(":type", element->elementData().typeToString());
|
|
||||||
m_insert_elements_query.bindValue(":sub_type", element->kindInformations()["type"].toString());
|
|
||||||
if (!m_insert_elements_query.exec()) {
|
if (!m_insert_elements_query.exec()) {
|
||||||
qDebug() << "projectDataBase::addElement insert element error : " << m_insert_elements_query.lastError();
|
qDebug() << "projectDataBase::addElement insert element error : " << m_insert_elements_query.lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_insert_element_info_query.bindValue(":uuid", element->uuid().toString());
|
bindElementInfoValues(m_insert_element_info_query, element);
|
||||||
auto hash = elementInfoToString(element);
|
|
||||||
for (auto key : hash.keys())
|
|
||||||
{
|
|
||||||
QString value = hash.value(key);
|
|
||||||
QString bind = key.prepend(":");
|
|
||||||
m_insert_element_info_query.bindValue(bind, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_insert_element_info_query.exec()) {
|
if (!m_insert_element_info_query.exec()) {
|
||||||
qDebug() << "projectDataBase::addElement insert element info error : " << m_insert_element_info_query.lastError();
|
qDebug() << "projectDataBase::addElement insert element info error : " << m_insert_element_info_query.lastError();
|
||||||
} else {
|
} else {
|
||||||
@@ -153,6 +212,7 @@ void projectDataBase::addElement(Element *element)
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::removeElement(Element *element)
|
void projectDataBase::removeElement(Element *element)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
m_remove_element_query.bindValue(":uuid", element->uuid().toString());
|
m_remove_element_query.bindValue(":uuid", element->uuid().toString());
|
||||||
if(!m_remove_element_query.exec()) {
|
if(!m_remove_element_query.exec()) {
|
||||||
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
|
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
|
||||||
@@ -167,6 +227,7 @@ void projectDataBase::removeElement(Element *element)
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::elementInfoChanged(Element *element)
|
void projectDataBase::elementInfoChanged(Element *element)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
auto hash = elementInfoToString(element);
|
auto hash = elementInfoToString(element);
|
||||||
for (auto str : QETInformation::elementInfoKeys()) {
|
for (auto str : QETInformation::elementInfoKeys()) {
|
||||||
m_update_element_query.bindValue(":" + str, hash.value(str));
|
m_update_element_query.bindValue(":" + str, hash.value(str));
|
||||||
@@ -181,6 +242,7 @@ void projectDataBase::elementInfoChanged(Element *element)
|
|||||||
|
|
||||||
void projectDataBase::elementInfoChanged(QList<Element *> elements)
|
void projectDataBase::elementInfoChanged(QList<Element *> elements)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
this->blockSignals(true);
|
this->blockSignals(true);
|
||||||
//Block signal for not emit dataBaseUpdated at
|
//Block signal for not emit dataBaseUpdated at
|
||||||
//each call of the method elementInfoChanged(Element *element)
|
//each call of the method elementInfoChanged(Element *element)
|
||||||
@@ -197,6 +259,7 @@ void projectDataBase::elementInfoChanged(QList<Element *> elements)
|
|||||||
|
|
||||||
void projectDataBase::addDiagram(Diagram *diagram)
|
void projectDataBase::addDiagram(Diagram *diagram)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
m_insert_diagram_query.bindValue(":uuid", diagram->uuid().toString());
|
m_insert_diagram_query.bindValue(":uuid", diagram->uuid().toString());
|
||||||
m_insert_diagram_query.bindValue(":pos", m_project->folioIndex(diagram)+1);
|
m_insert_diagram_query.bindValue(":pos", m_project->folioIndex(diagram)+1);
|
||||||
if(!m_insert_diagram_query.exec()) {
|
if(!m_insert_diagram_query.exec()) {
|
||||||
@@ -225,6 +288,7 @@ void projectDataBase::addDiagram(Diagram *diagram)
|
|||||||
|
|
||||||
void projectDataBase::removeDiagram(Diagram *diagram)
|
void projectDataBase::removeDiagram(Diagram *diagram)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
const QString uuid_str = diagram->uuid().toString();
|
const QString uuid_str = diagram->uuid().toString();
|
||||||
|
|
||||||
//Order matters: element_info and terminal are scoped through a
|
//Order matters: element_info and terminal are scoped through a
|
||||||
@@ -280,6 +344,7 @@ void projectDataBase::removeDiagram(Diagram *diagram)
|
|||||||
|
|
||||||
void projectDataBase::diagramInfoChanged(Diagram *diagram)
|
void projectDataBase::diagramInfoChanged(Diagram *diagram)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
bindDiagramInfoValues(m_update_diagram_info_query, diagram);
|
bindDiagramInfoValues(m_update_diagram_info_query, diagram);
|
||||||
|
|
||||||
if (!m_update_diagram_info_query.exec()) {
|
if (!m_update_diagram_info_query.exec()) {
|
||||||
@@ -291,6 +356,7 @@ void projectDataBase::diagramInfoChanged(Diagram *diagram)
|
|||||||
|
|
||||||
void projectDataBase::diagramOrderChanged()
|
void projectDataBase::diagramOrderChanged()
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -299,6 +365,7 @@ void projectDataBase::diagramOrderChanged()
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::addConductor(Conductor *conductor)
|
void projectDataBase::addConductor(Conductor *conductor)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
if (!conductor || !conductor->diagram()) {
|
if (!conductor || !conductor->diagram()) {
|
||||||
qDebug() << "projectDataBase::addConductor: null conductor or diagram";
|
qDebug() << "projectDataBase::addConductor: null conductor or diagram";
|
||||||
return;
|
return;
|
||||||
@@ -332,6 +399,7 @@ void projectDataBase::addConductor(Conductor *conductor)
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::removeConductor(Conductor *conductor)
|
void projectDataBase::removeConductor(Conductor *conductor)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString());
|
m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString());
|
||||||
if (!m_remove_conductor_query.exec()) {
|
if (!m_remove_conductor_query.exec()) {
|
||||||
qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError();
|
qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError();
|
||||||
@@ -352,6 +420,7 @@ void projectDataBase::removeConductor(Conductor *conductor)
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::updateConductor(Conductor *conductor)
|
void projectDataBase::updateConductor(Conductor *conductor)
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
if (!conductor) {
|
if (!conductor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -394,6 +463,7 @@ void projectDataBase::watchConductor(Conductor *conductor)
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::conductorPropertiesChanged()
|
void projectDataBase::conductorPropertiesChanged()
|
||||||
{
|
{
|
||||||
|
m_content_changed = true;
|
||||||
if (auto *conductor = qobject_cast<Conductor *>(sender())) {
|
if (auto *conductor = qobject_cast<Conductor *>(sender())) {
|
||||||
updateConductor(conductor);
|
updateConductor(conductor);
|
||||||
}
|
}
|
||||||
@@ -550,6 +620,7 @@ bool projectDataBase::createDataBase()
|
|||||||
|
|
||||||
createElementNomenclatureView();
|
createElementNomenclatureView();
|
||||||
createSummaryView();
|
createSummaryView();
|
||||||
|
createWiringListView();
|
||||||
prepareQuery();
|
prepareQuery();
|
||||||
updateDB();
|
updateDB();
|
||||||
return true;
|
return true;
|
||||||
@@ -570,6 +641,11 @@ void projectDataBase::createElementNomenclatureView()
|
|||||||
"ei.designation AS designation,"
|
"ei.designation AS designation,"
|
||||||
"ei.manufacturer AS manufacturer,"
|
"ei.manufacturer AS manufacturer,"
|
||||||
"ei.manufacturer_reference AS manufacturer_reference,"
|
"ei.manufacturer_reference AS manufacturer_reference,"
|
||||||
|
"ei.model AS model,"
|
||||||
|
"ei.category AS category,"
|
||||||
|
"ei.voltage_rating AS voltage_rating,"
|
||||||
|
"ei.current_rating AS current_rating,"
|
||||||
|
"ei.notes AS notes,"
|
||||||
"ei.machine_manufacturer_reference AS machine_manufacturer_reference,"
|
"ei.machine_manufacturer_reference AS machine_manufacturer_reference,"
|
||||||
"ei.supplier AS supplier,"
|
"ei.supplier AS supplier,"
|
||||||
"ei.quantity AS quantity,"
|
"ei.quantity AS quantity,"
|
||||||
@@ -628,7 +704,21 @@ void projectDataBase::createElementNomenclatureView()
|
|||||||
"di.folio AS folio,"
|
"di.folio AS folio,"
|
||||||
"e.pos AS position "
|
"e.pos AS position "
|
||||||
" FROM element_info ei, diagram_info di, element e, diagram d"
|
" FROM element_info ei, diagram_info di, element e, diagram d"
|
||||||
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid AND (ei.exclude_from_bom IS NOT 'true')");
|
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid"
|
||||||
|
" AND COALESCE(LOWER(TRIM(ei.exclude_from_bom)), '') NOT IN ('true', '1', 'yes', 'on')"
|
||||||
|
//The element table holds every element of the project; which
|
||||||
|
//kinds belong in a nomenclature is this view's business, not
|
||||||
|
//the table's. Kept identical to the mask populateElementTable()
|
||||||
|
//used to apply, so what this view returns does not change --
|
||||||
|
//a slave element (a relay contact) is still not a line item.
|
||||||
|
//Slave is here because an auxiliary contact block is
|
||||||
|
//separately orderable hardware with its own part
|
||||||
|
//number, even though it shares its master's BMK.
|
||||||
|
//Anything that should not be ordered -- a relay's
|
||||||
|
//own auxiliary contact, say -- is kept out by
|
||||||
|
//exclude_from_bom above, not by its base type.
|
||||||
|
//See discussion #847.
|
||||||
|
" AND e.type IN ('simple', 'terminal', 'master', 'slave', 'thumbnail')");
|
||||||
|
|
||||||
QSqlQuery query(m_data_base);
|
QSqlQuery query(m_data_base);
|
||||||
if (!query.exec(create_view)) {
|
if (!query.exec(create_view)) {
|
||||||
@@ -667,6 +757,64 @@ void projectDataBase::createSummaryView()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief projectDataBase::createWiringListView
|
||||||
|
A from-to wiring list: one row per conductor, each endpoint resolved to
|
||||||
|
its element label and terminal name.
|
||||||
|
|
||||||
|
Two deliberate differences from an ordinary inner-join view like
|
||||||
|
element_nomenclature_view:
|
||||||
|
|
||||||
|
- No join to the element table. A terminal row already carries its
|
||||||
|
element_uuid, so joining element back just to read the same uuid adds
|
||||||
|
nothing -- and would actively drop rows, because populateElementTable()
|
||||||
|
only inserts elements matching Simple|Terminal|Master|Thumbnail. Slave
|
||||||
|
elements (relay contacts and the like, extremely common at the end of a
|
||||||
|
wire) and report elements are absent from that table after a project
|
||||||
|
load, so an inner join through it silently loses their conductors.
|
||||||
|
- element_info is LEFT joined for the same reason. A wire whose endpoint
|
||||||
|
element carries no info row still belongs in a wiring list; it comes
|
||||||
|
back with an empty label rather than vanishing. Losing a wire from a
|
||||||
|
wiring list is a worse failure than showing one with a blank end.
|
||||||
|
|
||||||
|
- diagram is LEFT joined for the same reason. It should
|
||||||
|
always match, since QETProject::diagramAdded is wired to addDiagram()
|
||||||
|
and a conductor cannot exist before its folio -- but an inner join here
|
||||||
|
would make that an assumption the view silently enforces, and a wire
|
||||||
|
missing from a wiring list is the one failure this view must not have.
|
||||||
|
|
||||||
|
The result is that this view returns exactly as many rows as the
|
||||||
|
conductor table holds -- what is already excluded upstream (conductors
|
||||||
|
on legacy terminals without uuids) stays excluded, and nothing new is
|
||||||
|
dropped here. Only the terminal joins are inner, and both are guaranteed
|
||||||
|
by insertTerminal() running for each endpoint before the conductor row
|
||||||
|
is written.
|
||||||
|
*/
|
||||||
|
void projectDataBase::createWiringListView()
|
||||||
|
{
|
||||||
|
QString create_view ("CREATE VIEW wiring_list_view AS SELECT "
|
||||||
|
"c.uuid AS conductor_uuid,"
|
||||||
|
"c.text AS wire_number,"
|
||||||
|
"t1.element_uuid AS from_element_uuid,"
|
||||||
|
"ei1.label AS from_element_label,"
|
||||||
|
"t1.name AS from_terminal,"
|
||||||
|
"t2.element_uuid AS to_element_uuid,"
|
||||||
|
"ei2.label AS to_element_label,"
|
||||||
|
"t2.name AS to_terminal,"
|
||||||
|
"d.pos AS diagram_position"
|
||||||
|
" FROM conductor c"
|
||||||
|
" JOIN terminal t1 ON c.terminal1_uuid = t1.uuid AND c.terminal1_element_uuid = t1.element_uuid"
|
||||||
|
" JOIN terminal t2 ON c.terminal2_uuid = t2.uuid AND c.terminal2_element_uuid = t2.element_uuid"
|
||||||
|
" LEFT JOIN element_info ei1 ON t1.element_uuid = ei1.element_uuid"
|
||||||
|
" LEFT JOIN element_info ei2 ON t2.element_uuid = ei2.element_uuid"
|
||||||
|
" LEFT JOIN diagram d ON c.diagram_uuid = d.uuid");
|
||||||
|
|
||||||
|
QSqlQuery query(m_data_base);
|
||||||
|
if (!query.exec(create_view)) {
|
||||||
|
qDebug() << query.lastError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void projectDataBase::populateDiagramTable()
|
void projectDataBase::populateDiagramTable()
|
||||||
{
|
{
|
||||||
QSqlQuery query_(m_data_base);
|
QSqlQuery query_(m_data_base);
|
||||||
@@ -682,6 +830,30 @@ void projectDataBase::populateDiagramTable()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief allElementTypes
|
||||||
|
Every ElementData::Type, i.e. no filtering at all.
|
||||||
|
|
||||||
|
The element table used to be populated with only
|
||||||
|
Simple|Terminal|Master|Thumbnail, which quietly made it "the elements a
|
||||||
|
nomenclature cares about" rather than "the elements of the project".
|
||||||
|
Anything else reading the table -- the wiring list, and terminal plans
|
||||||
|
later -- then could not see slave elements (relay contacts) or report
|
||||||
|
elements, which are ordinary conductor endpoints. The filter now lives in
|
||||||
|
element_nomenclature_view, where it belongs; see createElementNomenclatureView().
|
||||||
|
*/
|
||||||
|
static ElementData::Types allElementTypes()
|
||||||
|
{
|
||||||
|
return ElementData::Simple
|
||||||
|
| ElementData::NextReport
|
||||||
|
| ElementData::PreviousReport
|
||||||
|
| ElementData::Master
|
||||||
|
| ElementData::Slave
|
||||||
|
| ElementData::Terminal
|
||||||
|
| ElementData::Thumbnail
|
||||||
|
| ElementData::ConductorDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief projectDataBase::populateElementTable
|
@brief projectDataBase::populateElementTable
|
||||||
Populate the element table
|
Populate the element table
|
||||||
@@ -694,16 +866,11 @@ void projectDataBase::populateElementTable()
|
|||||||
for (auto diagram : m_project->diagrams())
|
for (auto diagram : m_project->diagrams())
|
||||||
{
|
{
|
||||||
const ElementProvider ep(diagram);
|
const ElementProvider ep(diagram);
|
||||||
const auto elmt_vector = ep.find(ElementData::Simple | ElementData::Terminal | ElementData::Master | ElementData::Thumbnail);
|
const auto elmt_vector = ep.find(allElementTypes());
|
||||||
//Insert all values into the database
|
//Insert all values into the database
|
||||||
for (const auto &elmt : elmt_vector)
|
for (const auto &elmt : elmt_vector)
|
||||||
{
|
{
|
||||||
const auto elmt_data = elmt->elementData();
|
bindElementValues(m_insert_elements_query, elmt, diagram);
|
||||||
m_insert_elements_query.bindValue(":uuid", elmt->uuid().toString());
|
|
||||||
m_insert_elements_query.bindValue(":diagram_uuid", diagram->uuid().toString());
|
|
||||||
m_insert_elements_query.bindValue(":pos", diagram->convertPosition(elmt->scenePos()).toString());
|
|
||||||
m_insert_elements_query.bindValue(":type", elmt_data.typeToString());
|
|
||||||
m_insert_elements_query.bindValue(":sub_type", elmt_data.masterTypeToString());
|
|
||||||
if (!m_insert_elements_query.exec()) {
|
if (!m_insert_elements_query.exec()) {
|
||||||
qDebug() << "projectDataBase::populateElementTable insert error : " << m_insert_elements_query.lastError();
|
qDebug() << "projectDataBase::populateElementTable insert error : " << m_insert_elements_query.lastError();
|
||||||
}
|
}
|
||||||
@@ -723,20 +890,12 @@ void projectDataBase::populateElementInfoTable()
|
|||||||
for (const auto &diagram : m_project->diagrams())
|
for (const auto &diagram : m_project->diagrams())
|
||||||
{
|
{
|
||||||
const ElementProvider ep(diagram);
|
const ElementProvider ep(diagram);
|
||||||
const auto elmt_vector = ep.find(ElementData::Simple | ElementData::Terminal | ElementData::Master | ElementData::Thumbnail);
|
const auto elmt_vector = ep.find(allElementTypes());
|
||||||
|
|
||||||
//Insert all values into the database
|
//Insert all values into the database
|
||||||
for (const auto &elmt : elmt_vector)
|
for (const auto &elmt : elmt_vector)
|
||||||
{
|
{
|
||||||
m_insert_element_info_query.bindValue(QStringLiteral(":uuid"), elmt->uuid().toString());
|
bindElementInfoValues(m_insert_element_info_query, elmt);
|
||||||
const auto hash = elementInfoToString(elmt);
|
|
||||||
for (const auto &key : hash.keys())
|
|
||||||
{
|
|
||||||
QString value = hash.value(key);
|
|
||||||
QString bind = QStringLiteral(":") + key;
|
|
||||||
m_insert_element_info_query.bindValue(bind, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_insert_element_info_query.exec()) {
|
if (!m_insert_element_info_query.exec()) {
|
||||||
qDebug() << "projectDataBase::populateElementInfoTable insert error : " << m_insert_element_info_query.lastError();
|
qDebug() << "projectDataBase::populateElementInfoTable insert error : " << m_insert_element_info_query.lastError();
|
||||||
}
|
}
|
||||||
@@ -943,6 +1102,52 @@ QHash<QString, QString> projectDataBase::elementInfoToString(Element *elmt)
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief projectDataBase::bindElementValues
|
||||||
|
Bind one element's row for the element table.
|
||||||
|
|
||||||
|
Shared by addElement() (a single element added to a live diagram) and
|
||||||
|
populateElementTable() (a full rebuild), because those two used to bind
|
||||||
|
the same row differently: the incremental path wrote
|
||||||
|
kindInformations()["type"] into sub_type while the bulk path wrote
|
||||||
|
elementData().masterTypeToString(). The element table therefore held
|
||||||
|
different values depending on whether the project had been reloaded
|
||||||
|
since the element was placed. One binder means live and reloaded agree
|
||||||
|
by construction rather than by coincidence.
|
||||||
|
|
||||||
|
The bulk path's values are the ones kept: they are what every already
|
||||||
|
saved project contains, so nothing a reload produces changes.
|
||||||
|
@param query : prepared insert query to bind into
|
||||||
|
@param element : element to bind
|
||||||
|
@param diagram : diagram holding @element
|
||||||
|
*/
|
||||||
|
void projectDataBase::bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram)
|
||||||
|
{
|
||||||
|
const auto element_data = element->elementData();
|
||||||
|
query.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
|
||||||
|
query.bindValue(QStringLiteral(":diagram_uuid"), diagram->uuid().toString());
|
||||||
|
query.bindValue(QStringLiteral(":pos"), diagram->convertPosition(element->scenePos()).toString());
|
||||||
|
query.bindValue(QStringLiteral(":type"), element_data.typeToString());
|
||||||
|
query.bindValue(QStringLiteral(":sub_type"), element_data.masterTypeToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief projectDataBase::bindElementInfoValues
|
||||||
|
Bind one element's row for the element info table.
|
||||||
|
Shared by addElement() and populateElementInfoTable() for the same
|
||||||
|
reason as bindElementValues().
|
||||||
|
@param query : prepared insert query to bind into
|
||||||
|
@param element : element to bind
|
||||||
|
*/
|
||||||
|
void projectDataBase::bindElementInfoValues(QSqlQuery &query, Element *element)
|
||||||
|
{
|
||||||
|
query.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
|
||||||
|
const auto hash = elementInfoToString(element);
|
||||||
|
for (const auto &key : hash.keys()) {
|
||||||
|
query.bindValue(QStringLiteral(":") + key, hash.value(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram)
|
void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram)
|
||||||
{
|
{
|
||||||
query.bindValue(":uuid", diagram->uuid());
|
query.bindValue(":uuid", diagram->uuid());
|
||||||
|
|||||||
@@ -47,8 +47,21 @@ class projectDataBase : public QObject
|
|||||||
virtual ~projectDataBase() override;
|
virtual ~projectDataBase() override;
|
||||||
|
|
||||||
void updateDB();
|
void updateDB();
|
||||||
|
/**
|
||||||
|
Suppress the full rebuild performed by updateDB().
|
||||||
|
|
||||||
|
While blocked, updateDB() returns immediately instead of
|
||||||
|
repopulating every table. Meant for bulk operations -- notably
|
||||||
|
loading a project, where each table model re-queries the
|
||||||
|
database as it is built and would otherwise trigger one
|
||||||
|
complete rebuild of it. The caller unblocks and calls
|
||||||
|
updateDB() once when done; @see QETProject::readProjectXml().
|
||||||
|
*/
|
||||||
|
void setUpdateBlocked(bool blocked);
|
||||||
QETProject *project() const;
|
QETProject *project() const;
|
||||||
QSqlQuery newQuery(const QString &query = QString());
|
QSqlQuery newQuery(const QString &query = QString());
|
||||||
|
QSqlDatabase database() const {return m_data_base;}
|
||||||
|
int excludedConductorCount() const;
|
||||||
|
|
||||||
void addElement (Element *element);
|
void addElement (Element *element);
|
||||||
void removeElement (Element *element);
|
void removeElement (Element *element);
|
||||||
@@ -77,6 +90,7 @@ class projectDataBase : public QObject
|
|||||||
bool createDataBase();
|
bool createDataBase();
|
||||||
void createElementNomenclatureView();
|
void createElementNomenclatureView();
|
||||||
void createSummaryView();
|
void createSummaryView();
|
||||||
|
void createWiringListView();
|
||||||
void populateDiagramTable();
|
void populateDiagramTable();
|
||||||
void populateElementTable();
|
void populateElementTable();
|
||||||
void populateElementInfoTable();
|
void populateElementInfoTable();
|
||||||
@@ -89,9 +103,17 @@ class projectDataBase : public QObject
|
|||||||
static QHash<QString, QString> elementInfoToString(
|
static QHash<QString, QString> elementInfoToString(
|
||||||
Element *elmt);
|
Element *elmt);
|
||||||
void bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram);
|
void bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram);
|
||||||
|
static void bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram);
|
||||||
|
static void bindElementInfoValues(QSqlQuery &query, Element *element);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<QETProject> m_project;
|
QPointer<QETProject> m_project;
|
||||||
|
bool m_update_blocked = false;
|
||||||
|
//Starts true : the database is empty until the first rebuild.
|
||||||
|
//Set by every method of this class that writes rows, cleared by
|
||||||
|
//updateDB(). Callers reach the database from outside only through
|
||||||
|
//newQuery(), and every such call site reads.
|
||||||
|
bool m_content_changed = true;
|
||||||
QSqlDatabase m_data_base;
|
QSqlDatabase m_data_base;
|
||||||
QSqlQuery m_insert_elements_query,
|
QSqlQuery m_insert_elements_query,
|
||||||
m_insert_element_info_query,
|
m_insert_element_info_query,
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ void ElementQueryWidget::setQuery(const QString &query)
|
|||||||
{
|
{
|
||||||
auto str_type = rxm.captured(1);
|
auto str_type = rxm.captured(1);
|
||||||
where.remove(str_type);
|
where.remove(str_type);
|
||||||
|
const bool all_master_types = str_type.contains(
|
||||||
|
ElementData::typeToString(ElementData::Master));
|
||||||
|
|
||||||
int c=0;
|
int c=0;
|
||||||
ui->m_simple_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Simple)) ? true : false);
|
ui->m_simple_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Simple)) ? true : false);
|
||||||
@@ -170,23 +172,23 @@ void ElementQueryWidget::setQuery(const QString &query)
|
|||||||
if (ui->m_terminal_cb->isChecked()) {
|
if (ui->m_terminal_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_coil_cb->setChecked (str_type.contains(ElementData::masterTypeToString(ElementData::Coil)) ? true : false);
|
ui->m_coil_cb->setChecked (all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Coil)));
|
||||||
if (ui->m_coil_cb->isChecked()) {
|
if (ui->m_coil_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_button_cb->setChecked (str_type.contains(ElementData::masterTypeToString(ElementData::Commutator)) ? true : false);
|
ui->m_button_cb->setChecked (all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Commutator)));
|
||||||
if (ui->m_button_cb->isChecked()) {
|
if (ui->m_button_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_protection_cb->setChecked(str_type.contains(ElementData::masterTypeToString(ElementData::Protection)) ? true : false);
|
ui->m_protection_cb->setChecked(all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Protection)));
|
||||||
if (ui->m_protection_cb) {
|
if (ui->m_protection_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_thumbnail_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Thumbnail)) ? true : false);
|
ui->m_thumbnail_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Thumbnail)) ? true : false);
|
||||||
if (ui->m_thumbnail_cb->isChecked()) {
|
if (ui->m_thumbnail_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_plc_cb->setChecked (str_type.contains(ElementData::masterTypeToString(ElementData::PLC)) ? true : false);
|
ui->m_plc_cb->setChecked (all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::PLC)));
|
||||||
if (ui->m_plc_cb->isChecked()) {
|
if (ui->m_plc_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
@@ -352,22 +354,32 @@ QString ElementQueryWidget::queryStr() const
|
|||||||
where += QStringLiteral(" element_type = '") += ElementData::typeToString(ElementData::Simple) += "'";
|
where += QStringLiteral(" element_type = '") += ElementData::typeToString(ElementData::Simple) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_button_cb->isChecked()) {
|
const bool all_master_types = ui->m_button_cb->isChecked()
|
||||||
|
&& ui->m_coil_cb->isChecked()
|
||||||
|
&& ui->m_protection_cb->isChecked()
|
||||||
|
&& ui->m_plc_cb->isChecked();
|
||||||
|
if (all_master_types) {
|
||||||
|
if (b) where += " OR";
|
||||||
|
where += QStringLiteral(" element_type = '")
|
||||||
|
+= ElementData::typeToString(ElementData::Master) += "'";
|
||||||
|
b = true;
|
||||||
|
}
|
||||||
|
if (!all_master_types && ui->m_button_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Commutator) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Commutator) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_coil_cb->isChecked()) {
|
if (!all_master_types && ui->m_coil_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Coil) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Coil) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_protection_cb->isChecked()) {
|
if (!all_master_types && ui->m_protection_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Protection) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Protection) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_plc_cb->isChecked()) {
|
if (!all_master_types && ui->m_plc_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::PLC) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::PLC) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ QString SummaryQueryWidget::queryStr() const
|
|||||||
QStringList keys = selectedKeys();
|
QStringList keys = selectedKeys();
|
||||||
|
|
||||||
QString select ="SELECT ";
|
QString select ="SELECT ";
|
||||||
QString order_by = " ORDER BY ";
|
|
||||||
|
|
||||||
QString column;
|
QString column;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
@@ -72,14 +71,29 @@ QString SummaryQueryWidget::queryStr() const
|
|||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
column += ", ";
|
column += ", ";
|
||||||
order_by +=", ";
|
|
||||||
}
|
}
|
||||||
column += key;
|
column += key;
|
||||||
order_by += key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString from = " FROM project_summary_view";
|
QString from = " FROM project_summary_view";
|
||||||
|
|
||||||
|
//Always ordered by the folio's own position, never by the columns
|
||||||
|
//the user happened to choose to display. This is a table of
|
||||||
|
//contents: it lists the folios of the project and its order is the
|
||||||
|
//project's order. Ordering by the displayed columns instead meant
|
||||||
|
//that choosing, say, Title as the first column silently sorted the
|
||||||
|
//summary alphabetically (bugtracker #238).
|
||||||
|
//
|
||||||
|
//project_summary_view exposes the position as "pos", from
|
||||||
|
//diagram.pos, which is an INTEGER -- so this sorts numerically and
|
||||||
|
//folio 10 does not land between folio 1 and folio 2. One row per
|
||||||
|
//folio means pos fully determines the order and no secondary key is
|
||||||
|
//needed.
|
||||||
|
//
|
||||||
|
//A user who wants a different order can still write the query by
|
||||||
|
//hand; that path returns above, untouched.
|
||||||
|
QString order_by = " ORDER BY pos";
|
||||||
|
|
||||||
QString q(select + column + from + order_by);
|
QString q(select + column + from + order_by);
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
@@ -95,6 +109,7 @@ void SummaryQueryWidget::setQuery(const QString &query)
|
|||||||
if (query.startsWith("SELECT"))
|
if (query.startsWith("SELECT"))
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
|
ui->m_edit_sql_query_cb->setChecked(false);
|
||||||
ui->m_user_query_le->setText(query);
|
ui->m_user_query_le->setText(query);
|
||||||
|
|
||||||
QString select = query;
|
QString select = query;
|
||||||
@@ -115,6 +130,17 @@ void SummaryQueryWidget::setQuery(const QString &query)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If the query this widget would build from the columns just
|
||||||
|
//parsed above does not match the query as loaded byte-for-byte,
|
||||||
|
//the user wrote it by hand (a join, a subquery, a different
|
||||||
|
//view) and accepting the dialog unmodified must not silently
|
||||||
|
//replace it with a generated one (bugtracker #885).
|
||||||
|
const bool custom_query = query != queryStr();
|
||||||
|
m_custom_query = custom_query ? query : QString();
|
||||||
|
ui->m_edit_sql_query_cb->setChecked(custom_query);
|
||||||
|
ui->m_user_query_le->setEnabled(custom_query);
|
||||||
|
ui->m_info_widget->setDisabled(custom_query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+214
-1
@@ -56,7 +56,20 @@ namespace {
|
|||||||
/// non-deterministic across process runs for any legacy file.
|
/// non-deterministic across process runs for any legacy file.
|
||||||
QString elementSortKey(Element *elmt)
|
QString elementSortKey(Element *elmt)
|
||||||
{
|
{
|
||||||
return positionKey(elmt->pos());
|
//Position alone is not a total order: two elements can sit at the
|
||||||
|
//same x/y (lmdg.qet has a pair of text elements both at 780,350).
|
||||||
|
//With equal keys std::stable_sort falls back to the order the
|
||||||
|
//scene handed us, which varies per run, so those two swapped
|
||||||
|
//places on every save. The uuid breaks the tie.
|
||||||
|
//
|
||||||
|
//For an element with a persisted uuid attribute this is fully
|
||||||
|
//deterministic. For a legacy element without one, fromXml()
|
||||||
|
//invents a fresh uuid per load, so a collision between two such
|
||||||
|
//elements is no better ordered than before -- but no worse
|
||||||
|
//either, and the tiebreaker is only consulted when the positions
|
||||||
|
//are equal.
|
||||||
|
return positionKey(elmt->pos())
|
||||||
|
+ QLatin1Char(':') + elmt->uuid().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort key for a terminal: its parent element's position, then the
|
/// Sort key for a terminal: its parent element's position, then the
|
||||||
@@ -437,6 +450,34 @@ void Diagram::wheelEvent(QGraphicsSceneWheelEvent *event)
|
|||||||
QGraphicsScene::wheelEvent(event);
|
QGraphicsScene::wheelEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::event
|
||||||
|
QGraphicsScene has its own Tab/Shift+Tab item-focus-chain traversal
|
||||||
|
(mirroring QWidget's), checked before keyPressEvent() is ever reached:
|
||||||
|
by default it would silently consume Tab/Backtab to move focus among
|
||||||
|
the scene's own focusable items. Intercepting the key press here,
|
||||||
|
ahead of that, is the only way to reliably override it: unlike
|
||||||
|
QWidget::focusNextPrevChild(), QGraphicsScene::focusNextPrevChild() is
|
||||||
|
only virtual starting in Qt 6 (guarded by the QT6_VIRTUAL macro), so a
|
||||||
|
Diagram:: override of it would silently do nothing on a Qt 5 build.
|
||||||
|
@param event
|
||||||
|
*/
|
||||||
|
bool Diagram::event(QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::KeyPress) {
|
||||||
|
auto *key_event = static_cast<QKeyEvent *>(event);
|
||||||
|
if ((key_event->key() == Qt::Key_Tab || key_event->key() == Qt::Key_Backtab)
|
||||||
|
&& !isReadOnly() && !focusItem()) {
|
||||||
|
bool forward = key_event->key() == Qt::Key_Tab
|
||||||
|
&& !(key_event->modifiers() & Qt::ShiftModifier);
|
||||||
|
selectNextItem(forward);
|
||||||
|
event->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QGraphicsScene::event(event);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Diagram::keyPressEvent
|
@brief Diagram::keyPressEvent
|
||||||
This event is managed by diagram event interface if any.
|
This event is managed by diagram event interface if any.
|
||||||
@@ -650,6 +691,79 @@ QUuid Diagram::uuid()
|
|||||||
return m_uuid;
|
return m_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::uuidUsedByOtherDiagram
|
||||||
|
A hand-edited or merged project file can contain two folios with the same
|
||||||
|
uuid. The uuid is used as a key (e.g. in the project database), so the
|
||||||
|
second one must get another one.
|
||||||
|
@param uuid
|
||||||
|
@return true if another diagram of the parent project already uses @p uuid
|
||||||
|
*/
|
||||||
|
bool Diagram::uuidUsedByOtherDiagram(const QUuid &uuid) const
|
||||||
|
{
|
||||||
|
if (!m_project) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto diagrams = m_project->diagrams();
|
||||||
|
for (const Diagram *diagram : diagrams) {
|
||||||
|
if (diagram != this && diagram->m_uuid == uuid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::derivedUuid
|
||||||
|
Name-based (version 5) uuid for a folio that has no usable uuid in the
|
||||||
|
file it is loaded from : either the file predates the uuid attribute, or
|
||||||
|
the uuid it carries is already taken by another folio of the project.
|
||||||
|
|
||||||
|
A random uuid would do as an identity, but it would make saving an
|
||||||
|
unmodified legacy project non-reproducible : every load would invent a
|
||||||
|
different one and write it out (see #754). The name is therefore built
|
||||||
|
only from data read from the file itself -- the project title, the
|
||||||
|
position of the folio in the file and its title -- so the same input file
|
||||||
|
always yields the same uuid. It is computed once, when the folio is
|
||||||
|
loaded, and saved from then on : renaming or moving the folio later does
|
||||||
|
not change it.
|
||||||
|
|
||||||
|
@param root : the <diagram> element being loaded
|
||||||
|
@param reason : distinguishes the two cases above, so that they can not
|
||||||
|
produce the same name
|
||||||
|
@return a uuid not used by any other diagram of the project
|
||||||
|
*/
|
||||||
|
QUuid Diagram::derivedUuid(const QDomElement &root, const QString &reason) const
|
||||||
|
{
|
||||||
|
//Fixed namespace for QElectroTech folio uuids, never change it :
|
||||||
|
//doing so would change the uuid given to every legacy folio.
|
||||||
|
static const QUuid folio_namespace(
|
||||||
|
QStringLiteral("{d5951240-154d-44d6-8277-0092a31d1920}"));
|
||||||
|
|
||||||
|
const int index = m_project
|
||||||
|
? m_project->diagrams().indexOf(const_cast<Diagram *>(this))
|
||||||
|
: -1;
|
||||||
|
const QString project_title = root.ownerDocument()
|
||||||
|
.documentElement()
|
||||||
|
.attribute(QStringLiteral("title"));
|
||||||
|
|
||||||
|
const QString base = QStringLiteral("qet-folio\n%1\n%2\n%3\n%4")
|
||||||
|
.arg(reason,
|
||||||
|
project_title,
|
||||||
|
QString::number(index),
|
||||||
|
root.attribute(QStringLiteral("title")));
|
||||||
|
|
||||||
|
//A clash is only possible with a hand-crafted file, but the uuid is a
|
||||||
|
//key : salt the name until it is free. Still deterministic.
|
||||||
|
QUuid uuid = QUuid::createUuidV5(folio_namespace, base);
|
||||||
|
for (int salt = 1 ; uuidUsedByOtherDiagram(uuid) ; ++salt) {
|
||||||
|
uuid = QUuid::createUuidV5(folio_namespace,
|
||||||
|
base + QStringLiteral("\n")
|
||||||
|
+ QString::number(salt));
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Diagram::setEventInterface
|
@brief Diagram::setEventInterface
|
||||||
Set event_interface has current interface.
|
Set event_interface has current interface.
|
||||||
@@ -865,6 +979,11 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
|
|||||||
// schema properties
|
// schema properties
|
||||||
// proprietes du schema
|
// proprietes du schema
|
||||||
if (whole_content) {
|
if (whole_content) {
|
||||||
|
//Persist the folio identity, so that a folio keeps the same uuid
|
||||||
|
//across save/load. Without it every load invents a new one, and
|
||||||
|
//nothing outside the running instance (version control, a lock,
|
||||||
|
//a diff tool...) can tell which folio is which.
|
||||||
|
dom_root.setAttribute(QStringLiteral("uuid"), m_uuid.toString());
|
||||||
border_and_titleblock.titleBlockToXml(dom_root);
|
border_and_titleblock.titleBlockToXml(dom_root);
|
||||||
border_and_titleblock.borderToXml(dom_root);
|
border_and_titleblock.borderToXml(dom_root);
|
||||||
|
|
||||||
@@ -1365,6 +1484,21 @@ bool Diagram::fromXml(QDomElement &document,
|
|||||||
// Read attributes of this diagram
|
// Read attributes of this diagram
|
||||||
if (consider_informations)
|
if (consider_informations)
|
||||||
{
|
{
|
||||||
|
// Restore the persisted folio uuid. Done first, before any item is
|
||||||
|
// loaded, so that everything created below sees the final uuid.
|
||||||
|
// A folio without a usable one (file written before the uuid was
|
||||||
|
// persisted, or uuid already taken by another folio) gets a
|
||||||
|
// deterministic one instead, see derivedUuid().
|
||||||
|
const QUuid persisted_uuid(root.attribute(QStringLiteral("uuid")));
|
||||||
|
if (persisted_uuid.isNull()) {
|
||||||
|
m_uuid = derivedUuid(root, QStringLiteral("legacy"));
|
||||||
|
} else if (uuidUsedByOtherDiagram(persisted_uuid)) {
|
||||||
|
m_uuid = derivedUuid(root, QStringLiteral("duplicate ")
|
||||||
|
+ persisted_uuid.toString());
|
||||||
|
} else {
|
||||||
|
m_uuid = persisted_uuid;
|
||||||
|
}
|
||||||
|
|
||||||
// Load border and titleblock
|
// Load border and titleblock
|
||||||
border_and_titleblock.titleBlockFromXml(root);
|
border_and_titleblock.titleBlockFromXml(root);
|
||||||
border_and_titleblock.borderFromXml(root);
|
border_and_titleblock.borderFromXml(root);
|
||||||
@@ -1889,6 +2023,85 @@ void Diagram::invertSelection()
|
|||||||
emit selectionChanged();
|
emit selectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::selectAllConductors
|
||||||
|
Select every conductor on this diagram, deselecting anything else.
|
||||||
|
*/
|
||||||
|
void Diagram::selectAllConductors()
|
||||||
|
{
|
||||||
|
if (items().isEmpty()) return;
|
||||||
|
|
||||||
|
blockSignals(true);
|
||||||
|
for (auto item : items()) {
|
||||||
|
item -> setSelected(dynamic_cast<Conductor *>(item) != nullptr);
|
||||||
|
}
|
||||||
|
blockSignals(false);
|
||||||
|
emit selectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::selectAllTextFields
|
||||||
|
Select every text field on this diagram (independent/static text,
|
||||||
|
conductor labels, and dynamic element texts), deselecting anything else.
|
||||||
|
*/
|
||||||
|
void Diagram::selectAllTextFields()
|
||||||
|
{
|
||||||
|
if (items().isEmpty()) return;
|
||||||
|
|
||||||
|
blockSignals(true);
|
||||||
|
for (auto item : items()) {
|
||||||
|
item -> setSelected(dynamic_cast<DiagramTextItem *>(item) != nullptr);
|
||||||
|
}
|
||||||
|
blockSignals(false);
|
||||||
|
emit selectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::selectNextItem
|
||||||
|
Select the next (or, if @a forward is false, the previous) selectable
|
||||||
|
item on this diagram, cycling through items() (z-order) and wrapping
|
||||||
|
around at either end. If nothing is currently selected, selects the
|
||||||
|
first (or last) item. Uses the same "what counts as a real selectable
|
||||||
|
diagram item" filter as invertSelection(), so the candidate list and
|
||||||
|
its order always match what the user could reach by clicking.
|
||||||
|
@param forward true to select the next item, false for the previous one
|
||||||
|
*/
|
||||||
|
void Diagram::selectNextItem(bool forward)
|
||||||
|
{
|
||||||
|
QList<QGraphicsItem *> candidates;
|
||||||
|
for (auto item : items()) {
|
||||||
|
if (dynamic_cast<QetGraphicsItem *>(item) ||
|
||||||
|
dynamic_cast<DiagramTextItem *>(item) ||
|
||||||
|
dynamic_cast<Conductor *>(item)) {
|
||||||
|
candidates << item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (candidates.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int current_index = -1;
|
||||||
|
for (int i = 0; i < candidates.size(); ++i) {
|
||||||
|
if (candidates.at(i) -> isSelected()) {
|
||||||
|
current_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int next_index;
|
||||||
|
if (current_index == -1) {
|
||||||
|
next_index = forward ? 0 : candidates.size() - 1;
|
||||||
|
} else {
|
||||||
|
next_index = forward
|
||||||
|
? (current_index + 1) % candidates.size()
|
||||||
|
: (current_index - 1 + candidates.size()) % candidates.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection();
|
||||||
|
QGraphicsItem *next_item = candidates.at(next_index);
|
||||||
|
next_item -> setSelected(true);
|
||||||
|
next_item -> ensureVisible();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Diagram::insertFolioSeqHash
|
@brief Diagram::insertFolioSeqHash
|
||||||
This class inserts a stringlist containing all
|
This class inserts a stringlist containing all
|
||||||
|
|||||||
+9
-1
@@ -136,6 +136,9 @@ class Diagram : public QGraphicsScene
|
|||||||
bool m_freeze_new_elements;
|
bool m_freeze_new_elements;
|
||||||
bool m_freeze_new_conductors_;
|
bool m_freeze_new_conductors_;
|
||||||
QUuid m_uuid = QUuid::createUuid();
|
QUuid m_uuid = QUuid::createUuid();
|
||||||
|
|
||||||
|
bool uuidUsedByOtherDiagram(const QUuid &uuid) const;
|
||||||
|
QUuid derivedUuid(const QDomElement &root, const QString &reason) const;
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
protected:
|
protected:
|
||||||
@@ -150,8 +153,11 @@ class Diagram : public QGraphicsScene
|
|||||||
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
|
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
|
||||||
void keyPressEvent (QKeyEvent *event) override;
|
void keyPressEvent (QKeyEvent *event) override;
|
||||||
void keyReleaseEvent (QKeyEvent *) override;
|
void keyReleaseEvent (QKeyEvent *) override;
|
||||||
|
bool event(QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void selectNextItem(bool forward);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void correctTextPos(Element* elmt);
|
void correctTextPos(Element* elmt);
|
||||||
void restoreText(Element* elmt);
|
void restoreText(Element* elmt);
|
||||||
@@ -287,6 +293,8 @@ class Diagram : public QGraphicsScene
|
|||||||
void selectAll();
|
void selectAll();
|
||||||
void deselectAll();
|
void deselectAll();
|
||||||
void invertSelection();
|
void invertSelection();
|
||||||
|
void selectAllConductors();
|
||||||
|
void selectAllTextFields();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void showDiagram (Diagram *);
|
void showDiagram (Diagram *);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user