mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 15:24:14 +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-nsis
|
||||
mingw-w64-ucrt-x86_64-angleproject
|
||||
mingw-w64-ucrt-x86_64-qt6-declarative
|
||||
|
||||
- name: Cache ccache
|
||||
uses: actions/cache@v5
|
||||
@@ -217,10 +218,71 @@ jobs:
|
||||
cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true
|
||||
cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true
|
||||
|
||||
cp -r "$GITHUB_WORKSPACE/lang" "$FILES/lang" || true
|
||||
find "$GITHUB_WORKSPACE/build" -name "*.qm" -exec cp {} "$FILES/lang/" \; 2>/dev/null || true
|
||||
echo "=== .qm files in files/lang/ ==="
|
||||
ls "$FILES/lang/"*.qm 2>/dev/null | wc -l || echo "0 .qm files"
|
||||
# --- Translations ---
|
||||
# Since PR #751 the .qm files are no longer tracked in git: lrelease
|
||||
# generates them in build/lang/ (OUTPUT_LOCATION "lang", relative to
|
||||
# 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
|
||||
cp "$GITHUB_WORKSPACE/$f" "$FILES/$f" 2>/dev/null || true
|
||||
|
||||
+2
-1
@@ -6,4 +6,5 @@
|
||||
doc/*
|
||||
!doc/QElectroTech.qch
|
||||
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)
|
||||
|
||||
project(qelectrotech
|
||||
VERSION 0.100.1
|
||||
VERSION 0.200.1
|
||||
DESCRIPTION "QET is a CAD/CAE editor focusing on schematics drawing features."
|
||||
HOMEPAGE_URL "https://qelectrotech.org/"
|
||||
LANGUAGES C CXX)
|
||||
@@ -36,7 +36,7 @@ set(QET_DIR ${PROJECT_SOURCE_DIR})
|
||||
# includes below, so every subdirectory and every FetchContent dependency
|
||||
# sees a consistent, already-defined value.
|
||||
if(NOT DEFINED QT_VERSION_MAJOR)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
endif()
|
||||
|
||||
# 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()
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
if(PACKAGE_TESTS)
|
||||
message("Add sub directory tests")
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -163,7 +180,22 @@ else()
|
||||
endif()
|
||||
|
||||
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()
|
||||
|
||||
# The default build only compiles the tracked .ts files to .qm (lrelease).
|
||||
@@ -174,7 +206,7 @@ endif()
|
||||
# fail with "Premature end of document".
|
||||
set_source_files_properties(
|
||||
${TS_FILES}
|
||||
PROPERTIES OUTPUT_LOCATION "${QET_DIR}/lang"
|
||||
PROPERTIES OUTPUT_LOCATION "lang"
|
||||
)
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
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
|
||||
# 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
|
||||
# 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]
|
||||
The elements collection provided along with QElectroTech is provided as is and
|
||||
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
|
||||
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]
|
||||
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.
|
||||
|
||||
@@ -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)
|
||||
* GUI translations: [Qt Linguist](http://doc.qt.io/qt-5/qtlinguist-index.html)
|
||||
* Version control: [GIT](https://github.com/qelectrotech/qelectrotech-source-mirror.git)
|
||||
* Doxygen documentation :[Doxygen](https://qelectrotech.github.io/qelectrotech-source-mirror/)
|
||||
* QtCreator qch doxygen :[QElectroTech.qch](https://github.com/qelectrotech/qelectrotech-source-mirror/blob/master/doc/QElectroTech.qch)
|
||||
* Doxygen documentation :[Doxygen](https://download.qelectrotech.org/qet/doxygen/html/)
|
||||
* 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)
|
||||
* Main development platform: [GNU/Linux](http://getgnulinux.org/en/linux/)
|
||||
* Targeted platforms: Windows, GNU/Linux, Mac OS X, BSDs
|
||||
|
||||
@@ -73,6 +73,7 @@ parts:
|
||||
- git
|
||||
- sqlite3
|
||||
- xdg-user-dirs
|
||||
- libqt6qml6
|
||||
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
||||
# snap (issue #373). Not yet re-verified against kf6-core24 — remove this
|
||||
# if the Qt6 xcb platform plugin loads cleanly without it.
|
||||
@@ -84,7 +85,13 @@ parts:
|
||||
- libsqlite3-dev
|
||||
- qt6-tools-dev
|
||||
- qt6-base-private-dev
|
||||
- qt6-declarative-dev
|
||||
- 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: |
|
||||
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)"
|
||||
|
||||
@@ -26,7 +26,7 @@ if(BUILD_PUGIXML)
|
||||
pugixml
|
||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||
GIT_TAG v1.15)
|
||||
|
||||
set(PUGIXML_INSTALL OFF CACHE INTERNAL "")
|
||||
FetchContent_MakeAvailable(pugixml)
|
||||
else()
|
||||
find_package(pugixml REQUIRED)
|
||||
|
||||
@@ -174,7 +174,11 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/conductornumexport.cpp
|
||||
${QET_DIR}/sources/wiringlistexport.h
|
||||
${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/bomexport.cpp
|
||||
${QET_DIR}/sources/bomexport.h
|
||||
${QET_DIR}/sources/conductorprofile.cpp
|
||||
${QET_DIR}/sources/conductorprofile.h
|
||||
${QET_DIR}/sources/conductorproperties.cpp
|
||||
@@ -245,6 +249,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/qet.h
|
||||
${QET_DIR}/sources/qeticons.cpp
|
||||
${QET_DIR}/sources/qeticons.h
|
||||
${QET_DIR}/sources/qetpalette.cpp
|
||||
${QET_DIR}/sources/qetpalette.h
|
||||
${QET_DIR}/sources/qetinformation.cpp
|
||||
${QET_DIR}/sources/qetinformation.h
|
||||
${QET_DIR}/sources/qetmainwindow.cpp
|
||||
@@ -326,6 +332,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/diagramevent/diagrameventinterface.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.cpp
|
||||
${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.h
|
||||
@@ -473,6 +481,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/project/projectusagetracker.cpp
|
||||
${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.h
|
||||
${QET_DIR}/sources/properties/propertiesinterface.cpp
|
||||
@@ -841,6 +851,20 @@ if(QT_VERSION_MAJOR GREATER_EQUAL 6)
|
||||
)
|
||||
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
|
||||
${QET_DIR}/lang/qet_ar.ts
|
||||
${QET_DIR}/lang/qet_ca.ts
|
||||
@@ -856,6 +880,7 @@ set(TS_FILES
|
||||
${QET_DIR}/lang/qet_hu.ts
|
||||
${QET_DIR}/lang/qet_it.ts
|
||||
${QET_DIR}/lang/qet_ja.ts
|
||||
${QET_DIR}/lang/qet_ko.ts
|
||||
${QET_DIR}/lang/qet_mn.ts
|
||||
${QET_DIR}/lang/qet_nb.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>
|
||||
<string>qelectrotech</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.qelectrotech</string>
|
||||
<string>org.qelectrotech.QElectroTech</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<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 "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..."
|
||||
cp -R "$BUILD_DIR/qelectrotech.app" "./$BUNDLE"
|
||||
|
||||
@@ -147,6 +148,98 @@ fi
|
||||
|
||||
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 #################################
|
||||
# NOTE: this must run AFTER macdeployqt, not before. macdeployqt
|
||||
# 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_FONTS_DIR="${current_dir}/fonts/"
|
||||
QET_LICENSES_DIR="${current_dir}/licenses/"
|
||||
LANG_DIR="${current_dir}/lang1/"
|
||||
|
||||
if [ -d "${QET_ELMT_DIR}" ]; then
|
||||
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
|
||||
@@ -183,12 +275,85 @@ fi
|
||||
if [ -d "${QET_TBT_DIR}" ]; then
|
||||
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
|
||||
fi
|
||||
if [ -d "${QET_LANG_DIR}" ]; then
|
||||
mkdir $BUNDLE/Contents/Resources/lang
|
||||
cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang
|
||||
# Traductions : depuis la PR #751, les .qm ne sont plus versionnes ; lrelease
|
||||
# les genere dans $BUILD_DIR/lang/. Jeu attendu = les .ts listes dans TS_FILES
|
||||
# (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
|
||||
if [ -d "${LANG_DIR}" ]; then
|
||||
cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang
|
||||
find "${QET_LANG_DIR}" -maxdepth 1 -name 'qet_*.ts' -exec basename {} .ts \; | LC_ALL=C sort > "$QM_TMP/present"
|
||||
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
|
||||
if [ -d "${QET_EXAMPLES_DIR}" ]; then
|
||||
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->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);
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetxml.h"
|
||||
#include "../qetproject.h"
|
||||
#include <QDir>
|
||||
#include <QDomDocument>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <utility>
|
||||
@@ -703,6 +705,74 @@ namespace autonum
|
||||
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
|
||||
@param location
|
||||
@@ -716,114 +786,85 @@ namespace autonum
|
||||
if (!location.isProject())
|
||||
return QString();
|
||||
|
||||
QXmlStreamReader rxml;
|
||||
QString path[10];
|
||||
int i = -1;
|
||||
//Directory names from the element up to (not including) the
|
||||
//collection root, outermost last -- path[dirLevel] is the
|
||||
//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;
|
||||
int dirLevel = -1;
|
||||
|
||||
//Add location name to path array
|
||||
while((current_location.parent() != current_location) && (current_location.parent().fileName() != "import"))
|
||||
while ((current_location.parent() != current_location)
|
||||
&& (current_location.parent().fileName() != "import"))
|
||||
{
|
||||
i++;
|
||||
path[i]=current_location.fileName();
|
||||
path << current_location.fileName();
|
||||
current_location = current_location.parent();
|
||||
dirLevel++;
|
||||
}
|
||||
//User Element without folder treatment
|
||||
if (i == -1)
|
||||
{
|
||||
i = 0;
|
||||
path[i]=current_location.fileName();
|
||||
//User element without folder treatment
|
||||
if (path.isEmpty()) {
|
||||
path << current_location.fileName();
|
||||
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
|
||||
if (current_location.fileName() != "10_electric"){
|
||||
QString custom_labels = "qet_labels.xml";
|
||||
QString customfilepath = QETApp::customElementsDir().append(custom_labels);
|
||||
|
||||
QFile file(customfilepath);
|
||||
file.isReadable();
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text))
|
||||
return QString();
|
||||
rxml.setDevice(&file);
|
||||
rxml.readNext();
|
||||
/* Which collection an element actually came from is not
|
||||
* recoverable post-import (addElement() strips the protocol),
|
||||
* so custom and company labels files are tried against two
|
||||
* possible layouts: with the collection's top-level tree name
|
||||
* folded into the path (a custom/company file organised as a
|
||||
* mirror of the common collection, tree name included) and
|
||||
* without it (a file scoped to just this one tree, matching
|
||||
* how the common collection's own files are written). Custom
|
||||
* is tried before company, so a user override wins over a
|
||||
* shared one.
|
||||
*/
|
||||
QStringList path_from_root = path;
|
||||
path_from_root << collection_root;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
const QStringList candidate_dirs = {
|
||||
QETApp::customElementsDir(),
|
||||
QETApp::companyElementsDir()
|
||||
};
|
||||
for (const QString &dir : candidate_dirs) {
|
||||
const QString candidate =
|
||||
QDir(dir).filePath(QStringLiteral("qet_labels.xml"));
|
||||
for (const QStringList &segments : {path_from_root, path}) {
|
||||
const QString prefix = prefixFromLabelFile(
|
||||
candidate, segments, segments.size() - 1);
|
||||
if (!prefix.isNull()) {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ FolioAutonumberingW::~FolioAutonumberingW()
|
||||
*/
|
||||
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);}
|
||||
}
|
||||
|
||||
|
||||
@@ -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/>.
|
||||
*/
|
||||
#include "cli_export.h"
|
||||
#include "bomexport.h"
|
||||
|
||||
#include "bordertitleblock.h"
|
||||
#include "conductornumexport.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include <QDomDocument>
|
||||
#include <QDate>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
@@ -70,6 +72,7 @@ const QHash<QString, QString> &exportFlags()
|
||||
{"--export-cables", "cables"},
|
||||
{"--export-wires", "wires"},
|
||||
{"--export-bom", "bom"},
|
||||
{"--export-wiring", "wiring"},
|
||||
{"--export-nets", "nets"},
|
||||
{"--export-links", "links"},
|
||||
{"--info", "info"},
|
||||
@@ -302,61 +305,40 @@ int exportCsv(QETProject &project, const QString &format, const QString &output)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Quote a field for CSV output (RFC-4180 style, ';' delimiter).
|
||||
QString csvField(const QString &value)
|
||||
{
|
||||
if (value.contains(';') || value.contains('"')
|
||||
|| value.contains('\n') || value.contains('\r')) {
|
||||
QString v = value;
|
||||
v.replace('"', "\"\"");
|
||||
return '"' % v % '"';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Bill of materials: one row per element, key component-data fields.
|
||||
/// Pulls from QET's own project database (the same source as the GUI BOM
|
||||
/// export), so the output matches what the editor produces.
|
||||
/// Bill of materials from the same project database and default query as the
|
||||
/// GUI nomenclature export.
|
||||
int exportBom(QETProject &project, const QString &output)
|
||||
{
|
||||
// The project database is built lazily; force a (re)build before querying.
|
||||
project.dataBase()->updateDB();
|
||||
|
||||
static const QStringList columns {
|
||||
"label", "designation", "manufacturer", "manufacturer_reference",
|
||||
"quantity", "location", "function", "title", "folio"
|
||||
};
|
||||
|
||||
QSqlQuery query = project.dataBase()->newQuery(
|
||||
"SELECT " % columns.join(", ") %
|
||||
" FROM element_nomenclature_view ORDER BY label");
|
||||
QSqlQuery query = project.dataBase()->newQuery(BomExport::defaultQuery());
|
||||
if (!query.exec()) {
|
||||
err << "BOM query failed: " << query.lastError().text() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
QString csv = columns.join(";") % "\n";
|
||||
int rows = 0;
|
||||
while (query.next()) {
|
||||
QStringList values;
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
values << csvField(query.value(i).toString());
|
||||
csv += values.join(";") % "\n";
|
||||
++rows;
|
||||
}
|
||||
|
||||
QFile file(output);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
err << "Cannot open '" << output << "' for writing.\n";
|
||||
const auto csv = BomExport::toCsv(
|
||||
query, BomExport::defaultColumns(), true, &rows);
|
||||
QString error;
|
||||
if (!BomExport::writeCsv(output, csv, &error)) {
|
||||
err << "Cannot write '" << output << "': " << error << "\n";
|
||||
return 1;
|
||||
}
|
||||
QTextStream fout(&file);
|
||||
fout << csv;
|
||||
file.close();
|
||||
out << "Exported " << rows << " component(s) -> " << output << "\n";
|
||||
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.
|
||||
int freeTerminals(Element *element)
|
||||
{
|
||||
@@ -543,6 +525,70 @@ QHash<Element *, int> folioIndex(QETProject &project)
|
||||
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.
|
||||
/// Walks QET's own potential graph, so each net is a connected component
|
||||
/// of terminals across all folios. The ground truth for connectivity.
|
||||
@@ -847,6 +893,8 @@ int run(const QStringList &args)
|
||||
return exportCsv(project, format, output);
|
||||
if (format == "bom")
|
||||
return exportBom(project, output);
|
||||
if (format == "wiring")
|
||||
return exportWiring(project, output);
|
||||
if (format == "nets")
|
||||
return exportNets(project, output);
|
||||
if (format == "links")
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace CLIExport {
|
||||
qelectrotech --export-cables <project.qet> <output.csv>
|
||||
qelectrotech --export-wires <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-links <project.qet> <output.csv>
|
||||
qelectrotech --info <project.qet> [output.json]
|
||||
@@ -64,6 +65,11 @@ namespace CLIExport {
|
||||
cables: wiring list (one row per conductor) as CSV.
|
||||
wires: list of distinct wire numbers as CSV.
|
||||
bom: bill of materials (one row per element) as CSV.
|
||||
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.
|
||||
links: element cross-references (coil/contact) as CSV, with
|
||||
unresolved links flagged.
|
||||
|
||||
@@ -124,15 +124,15 @@ void ConductorNumExport::fillHash()
|
||||
continue;
|
||||
}
|
||||
|
||||
//We must define if the connected terminal is a folio report, if it is the case
|
||||
//we don't add the num to the hash because the terminal doesn't represent a real terminal.
|
||||
if(!(c->terminal1->parentElement()->linkType() & Element::AllReport))
|
||||
{
|
||||
int value = m_hash.value(num, 0);
|
||||
++value;
|
||||
m_hash.insert(num, value);
|
||||
}
|
||||
if(!(c->terminal2->parentElement()->linkType() & Element::AllReport))
|
||||
//We must define if the connected terminals are folio reports, if
|
||||
//both are the case we don't add the num to the hash because
|
||||
//neither terminal represents a real terminal. Otherwise the
|
||||
//conductor counts once, regardless of how many of its two ends
|
||||
//are real -- counting per terminal instead of per conductor
|
||||
//doubled every ordinary two-real-terminal conductor's tally.
|
||||
bool terminal1_is_report = c->terminal1->parentElement()->linkType() & Element::AllReport;
|
||||
bool terminal2_is_report = c->terminal2->parentElement()->linkType() & Element::AllReport;
|
||||
if (!(terminal1_is_report && terminal2_is_report))
|
||||
{
|
||||
int value = m_hash.value(num, 0);
|
||||
++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]()
|
||||
{
|
||||
m_content_changed = true;
|
||||
for (auto diagram : m_project->diagrams())
|
||||
{
|
||||
m_diagram_order_changed.bindValue(":pos", m_project->folioIndex(diagram)+1);
|
||||
@@ -85,14 +86,42 @@ projectDataBase::~projectDataBase()
|
||||
*/
|
||||
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();
|
||||
populateDiagramInfoTable();
|
||||
populateElementTable();
|
||||
populateElementInfoTable();
|
||||
populateConductorTable();
|
||||
m_content_changed = false;
|
||||
|
||||
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
|
||||
@return the project of this database
|
||||
@@ -111,35 +140,65 @@ QSqlQuery projectDataBase::newQuery(const QString &query) {
|
||||
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
|
||||
@param element
|
||||
*/
|
||||
void projectDataBase::addElement(Element *element)
|
||||
{
|
||||
m_content_changed = true;
|
||||
if (!element || !element->diagram()) {
|
||||
qDebug() << "projectDataBase::addElement: null element or diagram";
|
||||
return;
|
||||
}
|
||||
|
||||
m_insert_elements_query.bindValue(":uuid", element->uuid().toString());
|
||||
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());
|
||||
bindElementValues(m_insert_elements_query, element, element->diagram());
|
||||
if (!m_insert_elements_query.exec()) {
|
||||
qDebug() << "projectDataBase::addElement insert element error : " << m_insert_elements_query.lastError();
|
||||
}
|
||||
|
||||
m_insert_element_info_query.bindValue(":uuid", element->uuid().toString());
|
||||
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);
|
||||
}
|
||||
|
||||
bindElementInfoValues(m_insert_element_info_query, element);
|
||||
if (!m_insert_element_info_query.exec()) {
|
||||
qDebug() << "projectDataBase::addElement insert element info error : " << m_insert_element_info_query.lastError();
|
||||
} else {
|
||||
@@ -153,6 +212,7 @@ void projectDataBase::addElement(Element *element)
|
||||
*/
|
||||
void projectDataBase::removeElement(Element *element)
|
||||
{
|
||||
m_content_changed = true;
|
||||
m_remove_element_query.bindValue(":uuid", element->uuid().toString());
|
||||
if(!m_remove_element_query.exec()) {
|
||||
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
|
||||
@@ -167,6 +227,7 @@ void projectDataBase::removeElement(Element *element)
|
||||
*/
|
||||
void projectDataBase::elementInfoChanged(Element *element)
|
||||
{
|
||||
m_content_changed = true;
|
||||
auto hash = elementInfoToString(element);
|
||||
for (auto str : QETInformation::elementInfoKeys()) {
|
||||
m_update_element_query.bindValue(":" + str, hash.value(str));
|
||||
@@ -181,6 +242,7 @@ void projectDataBase::elementInfoChanged(Element *element)
|
||||
|
||||
void projectDataBase::elementInfoChanged(QList<Element *> elements)
|
||||
{
|
||||
m_content_changed = true;
|
||||
this->blockSignals(true);
|
||||
//Block signal for not emit dataBaseUpdated at
|
||||
//each call of the method elementInfoChanged(Element *element)
|
||||
@@ -197,6 +259,7 @@ void projectDataBase::elementInfoChanged(QList<Element *> elements)
|
||||
|
||||
void projectDataBase::addDiagram(Diagram *diagram)
|
||||
{
|
||||
m_content_changed = true;
|
||||
m_insert_diagram_query.bindValue(":uuid", diagram->uuid().toString());
|
||||
m_insert_diagram_query.bindValue(":pos", m_project->folioIndex(diagram)+1);
|
||||
if(!m_insert_diagram_query.exec()) {
|
||||
@@ -225,6 +288,7 @@ void projectDataBase::addDiagram(Diagram *diagram)
|
||||
|
||||
void projectDataBase::removeDiagram(Diagram *diagram)
|
||||
{
|
||||
m_content_changed = true;
|
||||
const QString uuid_str = diagram->uuid().toString();
|
||||
|
||||
//Order matters: element_info and terminal are scoped through a
|
||||
@@ -280,6 +344,7 @@ void projectDataBase::removeDiagram(Diagram *diagram)
|
||||
|
||||
void projectDataBase::diagramInfoChanged(Diagram *diagram)
|
||||
{
|
||||
m_content_changed = true;
|
||||
bindDiagramInfoValues(m_update_diagram_info_query, diagram);
|
||||
|
||||
if (!m_update_diagram_info_query.exec()) {
|
||||
@@ -291,6 +356,7 @@ void projectDataBase::diagramInfoChanged(Diagram *diagram)
|
||||
|
||||
void projectDataBase::diagramOrderChanged()
|
||||
{
|
||||
m_content_changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,6 +365,7 @@ void projectDataBase::diagramOrderChanged()
|
||||
*/
|
||||
void projectDataBase::addConductor(Conductor *conductor)
|
||||
{
|
||||
m_content_changed = true;
|
||||
if (!conductor || !conductor->diagram()) {
|
||||
qDebug() << "projectDataBase::addConductor: null conductor or diagram";
|
||||
return;
|
||||
@@ -332,6 +399,7 @@ void projectDataBase::addConductor(Conductor *conductor)
|
||||
*/
|
||||
void projectDataBase::removeConductor(Conductor *conductor)
|
||||
{
|
||||
m_content_changed = true;
|
||||
m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString());
|
||||
if (!m_remove_conductor_query.exec()) {
|
||||
qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError();
|
||||
@@ -352,6 +420,7 @@ void projectDataBase::removeConductor(Conductor *conductor)
|
||||
*/
|
||||
void projectDataBase::updateConductor(Conductor *conductor)
|
||||
{
|
||||
m_content_changed = true;
|
||||
if (!conductor) {
|
||||
return;
|
||||
}
|
||||
@@ -394,6 +463,7 @@ void projectDataBase::watchConductor(Conductor *conductor)
|
||||
*/
|
||||
void projectDataBase::conductorPropertiesChanged()
|
||||
{
|
||||
m_content_changed = true;
|
||||
if (auto *conductor = qobject_cast<Conductor *>(sender())) {
|
||||
updateConductor(conductor);
|
||||
}
|
||||
@@ -550,6 +620,7 @@ bool projectDataBase::createDataBase()
|
||||
|
||||
createElementNomenclatureView();
|
||||
createSummaryView();
|
||||
createWiringListView();
|
||||
prepareQuery();
|
||||
updateDB();
|
||||
return true;
|
||||
@@ -570,6 +641,11 @@ void projectDataBase::createElementNomenclatureView()
|
||||
"ei.designation AS designation,"
|
||||
"ei.manufacturer AS manufacturer,"
|
||||
"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.supplier AS supplier,"
|
||||
"ei.quantity AS quantity,"
|
||||
@@ -628,7 +704,21 @@ void projectDataBase::createElementNomenclatureView()
|
||||
"di.folio AS folio,"
|
||||
"e.pos AS position "
|
||||
" 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);
|
||||
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()
|
||||
{
|
||||
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
|
||||
Populate the element table
|
||||
@@ -694,16 +866,11 @@ void projectDataBase::populateElementTable()
|
||||
for (auto diagram : m_project->diagrams())
|
||||
{
|
||||
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
|
||||
for (const auto &elmt : elmt_vector)
|
||||
{
|
||||
const auto elmt_data = elmt->elementData();
|
||||
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());
|
||||
bindElementValues(m_insert_elements_query, elmt, diagram);
|
||||
if (!m_insert_elements_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateElementTable insert error : " << m_insert_elements_query.lastError();
|
||||
}
|
||||
@@ -723,20 +890,12 @@ void projectDataBase::populateElementInfoTable()
|
||||
for (const auto &diagram : m_project->diagrams())
|
||||
{
|
||||
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
|
||||
for (const auto &elmt : elmt_vector)
|
||||
{
|
||||
m_insert_element_info_query.bindValue(QStringLiteral(":uuid"), elmt->uuid().toString());
|
||||
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);
|
||||
}
|
||||
|
||||
bindElementInfoValues(m_insert_element_info_query, elmt);
|
||||
if (!m_insert_element_info_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateElementInfoTable insert error : " << m_insert_element_info_query.lastError();
|
||||
}
|
||||
@@ -943,6 +1102,52 @@ QHash<QString, QString> projectDataBase::elementInfoToString(Element *elmt)
|
||||
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)
|
||||
{
|
||||
query.bindValue(":uuid", diagram->uuid());
|
||||
|
||||
@@ -47,8 +47,21 @@ class projectDataBase : public QObject
|
||||
virtual ~projectDataBase() override;
|
||||
|
||||
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;
|
||||
QSqlQuery newQuery(const QString &query = QString());
|
||||
QSqlDatabase database() const {return m_data_base;}
|
||||
int excludedConductorCount() const;
|
||||
|
||||
void addElement (Element *element);
|
||||
void removeElement (Element *element);
|
||||
@@ -77,6 +90,7 @@ class projectDataBase : public QObject
|
||||
bool createDataBase();
|
||||
void createElementNomenclatureView();
|
||||
void createSummaryView();
|
||||
void createWiringListView();
|
||||
void populateDiagramTable();
|
||||
void populateElementTable();
|
||||
void populateElementInfoTable();
|
||||
@@ -89,9 +103,17 @@ class projectDataBase : public QObject
|
||||
static QHash<QString, QString> elementInfoToString(
|
||||
Element *elmt);
|
||||
void bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram);
|
||||
static void bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram);
|
||||
static void bindElementInfoValues(QSqlQuery &query, Element *element);
|
||||
|
||||
private:
|
||||
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;
|
||||
QSqlQuery m_insert_elements_query,
|
||||
m_insert_element_info_query,
|
||||
|
||||
@@ -160,6 +160,8 @@ void ElementQueryWidget::setQuery(const QString &query)
|
||||
{
|
||||
auto str_type = rxm.captured(1);
|
||||
where.remove(str_type);
|
||||
const bool all_master_types = str_type.contains(
|
||||
ElementData::typeToString(ElementData::Master));
|
||||
|
||||
int c=0;
|
||||
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()) {
|
||||
++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()) {
|
||||
++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()) {
|
||||
++c;
|
||||
}
|
||||
ui->m_protection_cb->setChecked(str_type.contains(ElementData::masterTypeToString(ElementData::Protection)) ? true : false);
|
||||
if (ui->m_protection_cb) {
|
||||
ui->m_protection_cb->setChecked(all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Protection)));
|
||||
if (ui->m_protection_cb->isChecked()) {
|
||||
++c;
|
||||
}
|
||||
ui->m_thumbnail_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Thumbnail)) ? true : false);
|
||||
if (ui->m_thumbnail_cb->isChecked()) {
|
||||
++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()) {
|
||||
++c;
|
||||
}
|
||||
@@ -352,22 +354,32 @@ QString ElementQueryWidget::queryStr() const
|
||||
where += QStringLiteral(" element_type = '") += ElementData::typeToString(ElementData::Simple) += "'";
|
||||
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";
|
||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Commutator) += "'";
|
||||
b = true;
|
||||
}
|
||||
if (ui->m_coil_cb->isChecked()) {
|
||||
if (!all_master_types && ui->m_coil_cb->isChecked()) {
|
||||
if (b) where +=" OR";
|
||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Coil) += "'";
|
||||
b = true;
|
||||
}
|
||||
if (ui->m_protection_cb->isChecked()) {
|
||||
if (!all_master_types && ui->m_protection_cb->isChecked()) {
|
||||
if (b) where +=" OR";
|
||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Protection) += "'";
|
||||
b = true;
|
||||
}
|
||||
if (ui->m_plc_cb->isChecked()) {
|
||||
if (!all_master_types && ui->m_plc_cb->isChecked()) {
|
||||
if (b) where +=" OR";
|
||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::PLC) += "'";
|
||||
b = true;
|
||||
|
||||
@@ -63,7 +63,6 @@ QString SummaryQueryWidget::queryStr() const
|
||||
QStringList keys = selectedKeys();
|
||||
|
||||
QString select ="SELECT ";
|
||||
QString order_by = " ORDER BY ";
|
||||
|
||||
QString column;
|
||||
bool first = true;
|
||||
@@ -72,14 +71,29 @@ QString SummaryQueryWidget::queryStr() const
|
||||
first = false;
|
||||
} else {
|
||||
column += ", ";
|
||||
order_by +=", ";
|
||||
}
|
||||
column += key;
|
||||
order_by += key;
|
||||
}
|
||||
|
||||
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);
|
||||
return q;
|
||||
}
|
||||
@@ -95,6 +109,7 @@ void SummaryQueryWidget::setQuery(const QString &query)
|
||||
if (query.startsWith("SELECT"))
|
||||
{
|
||||
reset();
|
||||
ui->m_edit_sql_query_cb->setChecked(false);
|
||||
ui->m_user_query_le->setText(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.
|
||||
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
|
||||
@@ -437,6 +450,34 @@ void Diagram::wheelEvent(QGraphicsSceneWheelEvent *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
|
||||
This event is managed by diagram event interface if any.
|
||||
@@ -650,6 +691,79 @@ QUuid Diagram::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
|
||||
Set event_interface has current interface.
|
||||
@@ -865,6 +979,11 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
|
||||
// schema properties
|
||||
// proprietes du schema
|
||||
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.borderToXml(dom_root);
|
||||
|
||||
@@ -1365,6 +1484,21 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
// Read attributes of this diagram
|
||||
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
|
||||
border_and_titleblock.titleBlockFromXml(root);
|
||||
border_and_titleblock.borderFromXml(root);
|
||||
@@ -1889,6 +2023,85 @@ void Diagram::invertSelection()
|
||||
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
|
||||
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_conductors_;
|
||||
QUuid m_uuid = QUuid::createUuid();
|
||||
|
||||
bool uuidUsedByOtherDiagram(const QUuid &uuid) const;
|
||||
QUuid derivedUuid(const QDomElement &root, const QString &reason) const;
|
||||
|
||||
// METHODS
|
||||
protected:
|
||||
@@ -150,8 +153,11 @@ class Diagram : public QGraphicsScene
|
||||
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
|
||||
void keyPressEvent (QKeyEvent *event) override;
|
||||
void keyReleaseEvent (QKeyEvent *) override;
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void selectNextItem(bool forward);
|
||||
|
||||
|
||||
public:
|
||||
void correctTextPos(Element* elmt);
|
||||
void restoreText(Element* elmt);
|
||||
@@ -287,6 +293,8 @@ class Diagram : public QGraphicsScene
|
||||
void selectAll();
|
||||
void deselectAll();
|
||||
void invertSelection();
|
||||
void selectAllConductors();
|
||||
void selectAllTextFields();
|
||||
|
||||
signals:
|
||||
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