mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-24 02:24:14 +02:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| f18eda845b | |||
| 73e9473db4 | |||
| 7307128fc3 | |||
| 799ff5573f | |||
| 6f2c66afef | |||
| c39c7414cf | |||
| c33f250910 | |||
| c265f0206c | |||
| 36dd1624d2 | |||
| a583b3c43c | |||
| 1738c3ad6c | |||
| 82026d8f7c | |||
| a494a8fd6c | |||
| 4c038a868b | |||
| 6208c7e5df | |||
| a48124a27a | |||
| e19d60ae55 | |||
| 030e6ebf00 | |||
| b855d760a8 | |||
| 5b8d05fc1e | |||
| b034b1a634 | |||
| 44ed01ff5d | |||
| 6061c63809 | |||
| 9285d12333 | |||
| fb0649ceee | |||
| d7c75ea5a5 | |||
| b9153269a4 | |||
| eab9603d8a | |||
| e3d11a4992 |
@@ -217,10 +217,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
|
||||
|
||||
+19
-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.
|
||||
@@ -163,7 +163,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 +189,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -85,6 +85,11 @@ parts:
|
||||
- qt6-tools-dev
|
||||
- qt6-base-private-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)"
|
||||
|
||||
@@ -174,6 +174,8 @@ 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/conductorprofile.cpp
|
||||
${QET_DIR}/sources/conductorprofile.h
|
||||
@@ -856,6 +858,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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <QDomDocument>
|
||||
#include <QDate>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
@@ -70,6 +71,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"},
|
||||
@@ -543,6 +545,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 +913,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.
|
||||
|
||||
@@ -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
|
||||
@@ -111,6 +111,47 @@ 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
|
||||
@@ -122,24 +163,12 @@ void projectDataBase::addElement(Element *element)
|
||||
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 {
|
||||
@@ -550,6 +579,7 @@ bool projectDataBase::createDataBase()
|
||||
|
||||
createElementNomenclatureView();
|
||||
createSummaryView();
|
||||
createWiringListView();
|
||||
prepareQuery();
|
||||
updateDB();
|
||||
return true;
|
||||
@@ -628,7 +658,13 @@ 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 (ei.exclude_from_bom IS NOT 'true')"
|
||||
//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.
|
||||
" AND e.type IN ('simple', 'terminal', 'master', 'thumbnail')");
|
||||
|
||||
QSqlQuery query(m_data_base);
|
||||
if (!query.exec(create_view)) {
|
||||
@@ -667,6 +703,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 +776,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 +812,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 +836,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 +1048,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());
|
||||
|
||||
@@ -49,6 +49,8 @@ class projectDataBase : public QObject
|
||||
void updateDB();
|
||||
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 +79,7 @@ class projectDataBase : public QObject
|
||||
bool createDataBase();
|
||||
void createElementNomenclatureView();
|
||||
void createSummaryView();
|
||||
void createWiringListView();
|
||||
void populateDiagramTable();
|
||||
void populateElementTable();
|
||||
void populateElementInfoTable();
|
||||
@@ -89,6 +92,8 @@ 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;
|
||||
|
||||
@@ -245,7 +245,8 @@ void DynamicTextFieldEditor::fillInfoComboBox()
|
||||
else {
|
||||
strl = QETInformation::elementInfoKeys();
|
||||
|
||||
bool is_plc_slave = (type == ElementData::Slave
|
||||
bool is_slave = (type == ElementData::Slave);
|
||||
bool is_plc_slave = (is_slave
|
||||
&& ed.m_slave_type == ElementData::PLCSlave);
|
||||
|
||||
if (is_plc_slave) {
|
||||
@@ -274,6 +275,10 @@ void DynamicTextFieldEditor::fillInfoComboBox()
|
||||
strl.removeAll(QETInformation::ELMT_PLC_T3);
|
||||
strl.removeAll(QETInformation::ELMT_PLC_T4);
|
||||
}
|
||||
|
||||
if (is_slave) {
|
||||
strl.prepend(QETInformation::ELMT_XREF);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<strl.size();++i) {
|
||||
|
||||
@@ -371,7 +371,7 @@ void ElementPropertiesEditorWidget::on_m_buttonBox_accepted()
|
||||
m_data.m_master_type = ui->m_master_type_cb->currentData().value<ElementData::MasterType>();
|
||||
|
||||
//If the checkbox is checked, save the number; otherwise, -1 (infinity)
|
||||
if (ui->max_slaves_checkbox->isVisible() && ui->max_slaves_checkbox->isChecked()) {
|
||||
if ((m_data.m_master_type == ElementData::Coil || m_data.m_master_type == ElementData::Protection || m_data.m_master_type == ElementData::Commutator) && ui->max_slaves_checkbox->isChecked()) {
|
||||
m_data.m_max_slaves = ui->max_slaves_spinbox->value();
|
||||
} else {
|
||||
m_data.m_max_slaves = -1;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "machine_info.h"
|
||||
#include "qet.h"
|
||||
#include "qetapp.h"
|
||||
#include "qetmessagebox.h"
|
||||
#include "qetproject.h"
|
||||
#include "singleapplication.h"
|
||||
#include "utils/qetsettings.h"
|
||||
@@ -127,6 +128,11 @@ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFacto
|
||||
// runs on a background thread referencing the project and races the
|
||||
// process exit (intermittent segfault in QET::writeToFile).
|
||||
QETProject::setBackupEnabled(false);
|
||||
// Answer message boxes instead of showing them: opening a project
|
||||
// saved by an older QElectroTech raises a warning from
|
||||
// QETProject::readProjectXml(), and with nobody able to dismiss it
|
||||
// QDialog::exec() would spin its event loop forever.
|
||||
QET::QetMessageBox::setNonInteractive(true);
|
||||
return CLIExport::run(export_app.arguments());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ XRefProperties::XRefProperties()
|
||||
m_master_label = "%f-%l%c";
|
||||
m_slave_label = "(%f-%l%c)";
|
||||
m_offset = 0;
|
||||
m_slave_offset = 0;
|
||||
m_xref_pos = Qt::AlignBottom;
|
||||
}
|
||||
|
||||
@@ -56,6 +57,8 @@ void XRefProperties::toSettings(QSettings &settings,
|
||||
settings.setValue(prefix % "snapto", snap);
|
||||
int offset = m_offset;
|
||||
settings.setValue(prefix % "offset", offset);
|
||||
int slave_offset = m_slave_offset;
|
||||
settings.setValue(prefix % "slave_offset", slave_offset);
|
||||
QString master_label = m_master_label;
|
||||
settings.setValue(prefix % "master_label", master_label);
|
||||
QString slave_label = m_slave_label;
|
||||
@@ -86,6 +89,7 @@ void XRefProperties::fromSettings(const QSettings &settings,
|
||||
QString snap = settings.value(prefix % "snapto", "label").toString();
|
||||
snap == "bottom"? m_snap_to = Bottom : m_snap_to = Label;
|
||||
m_offset = settings.value(prefix % "offset", "0").toInt();
|
||||
m_slave_offset = settings.value(prefix % "slave_offset", "0").toInt();
|
||||
m_master_label = settings.value(prefix % "master_label", "%f-%l%c").toString();
|
||||
m_slave_label = settings.value(prefix % "slave_label", "(%f-%l%c)").toString();
|
||||
|
||||
@@ -123,6 +127,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const
|
||||
|
||||
int offset = m_offset;
|
||||
xml_element.setAttribute("offset", QString::number(offset));
|
||||
xml_element.setAttribute("slave_offset", QString::number(m_slave_offset));
|
||||
QString master_label = m_master_label;
|
||||
xml_element.setAttribute("master_label", master_label);
|
||||
QString slave_label = m_slave_label;
|
||||
@@ -157,6 +162,7 @@ bool XRefProperties::fromXml(const QDomElement &xml_element) {
|
||||
m_xref_pos = Qt::AlignBottom;
|
||||
|
||||
m_offset = xml_element.attribute("offset", "0").toInt();
|
||||
m_slave_offset = xml_element.attribute("slave_offset", "0").toInt();
|
||||
m_master_label = xml_element.attribute("master_label", "%f-%l%c");
|
||||
m_slave_label = xml_element.attribute("slave_label","(%f-%l%c)");
|
||||
foreach (QString key, m_prefix_keys) {
|
||||
@@ -199,6 +205,7 @@ bool XRefProperties::operator ==(const XRefProperties &xrp) const{
|
||||
&& m_prefix == xrp.m_prefix
|
||||
&& m_master_label== xrp.m_master_label
|
||||
&& m_offset == xrp.m_offset
|
||||
&& m_slave_offset== xrp.m_slave_offset
|
||||
&& m_xref_pos == xrp.m_xref_pos
|
||||
&& m_slave_label == xrp.m_slave_label);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ class XRefProperties : public PropertiesInterface
|
||||
void setOffset(const int offset) {m_offset = offset;}
|
||||
int offset() const {return m_offset;}
|
||||
|
||||
void setSlaveOffset(const int offset) {m_slave_offset = offset;}
|
||||
int slaveOffset() const {return m_slave_offset;}
|
||||
|
||||
void setKey(QString& key) {m_key = key;}
|
||||
|
||||
private:
|
||||
@@ -93,6 +96,7 @@ class XRefProperties : public PropertiesInterface
|
||||
QString m_master_label;
|
||||
QString m_slave_label;
|
||||
int m_offset;
|
||||
int m_slave_offset;
|
||||
QString m_key;
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "ui/diagrameditorhandlersizewidget.h"
|
||||
#include "TerminalStrip/ui/addterminalstripitemdialog.h"
|
||||
#include "wiringlistexport.h"
|
||||
#include "ui/wiringlistdialog.h"
|
||||
#include "ui/terminalnumberingdialog.h"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
@@ -519,6 +520,17 @@ void QETDiagramEditor::setUpActions()
|
||||
}
|
||||
});
|
||||
|
||||
// Show the wiring list read from the project database
|
||||
m_project_wiring_list_view = new QAction(QET::Icons::DocumentSpreadsheet, tr("Liste de câblage (base de données)"), this);
|
||||
connect(m_project_wiring_list_view, &QAction::triggered, [this]() {
|
||||
QETProject *project = this->currentProject();
|
||||
if (project)
|
||||
{
|
||||
WiringListDialog dialog(project, this);
|
||||
dialog.exec();
|
||||
}
|
||||
});
|
||||
|
||||
// Terminal Numbering
|
||||
m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this);
|
||||
connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering);
|
||||
@@ -940,6 +952,7 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_project -> addAction(m_terminal_strip_dialog);
|
||||
menu_project -> addAction(m_project_terminalBloc);
|
||||
menu_project -> addAction(m_project_export_wiring_list);
|
||||
menu_project -> addAction(m_project_wiring_list_view);
|
||||
menu_project -> addAction(m_terminal_numbering);
|
||||
#ifdef QET_EXPORT_PROJECT_DB
|
||||
menu_project -> addSeparator();
|
||||
@@ -1789,6 +1802,7 @@ void QETDiagramEditor::slot_updateActions()
|
||||
m_project_export_conductor_num-> setEnabled(opened_project);
|
||||
m_terminal_strip_dialog -> setEnabled(editable_project);
|
||||
m_project_export_wiring_list -> setEnabled(opened_project);
|
||||
m_project_wiring_list_view -> setEnabled(opened_project);
|
||||
m_terminal_numbering -> setEnabled(editable_project);
|
||||
#ifdef QET_EXPORT_PROJECT_DB
|
||||
m_export_project_db -> setEnabled(editable_project);
|
||||
|
||||
@@ -209,6 +209,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_project_terminalBloc, ///< generate terminal block
|
||||
*m_project_export_conductor_num,///<Export the wire num to csv
|
||||
*m_project_export_wiring_list, ///< Action to export the wiring list
|
||||
*m_project_wiring_list_view, ///< Action to show the wiring list read from the project database
|
||||
*m_terminal_numbering, ///< Action to launch terminal numbering
|
||||
*m_export_project_db, ///Export to file the internal database of the current project
|
||||
*m_tile_window, ///< Show MDI subwindows as tile
|
||||
|
||||
@@ -1382,36 +1382,68 @@ void DynamicElementTextItem::updateXref()
|
||||
!m_parent_element.data()->linkedElements().isEmpty())
|
||||
{
|
||||
Element *master_elmt = m_parent_element.data()->linkedElements().first();
|
||||
if(master_elmt && !parentGroup() &&
|
||||
(
|
||||
if(master_elmt && !parentGroup())
|
||||
{
|
||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
|
||||
//Champ de texte: store xref in element informations
|
||||
if(xrp.getXrefPos() == Qt::AlignHCenter)
|
||||
{
|
||||
if(m_text_from == DynamicElementTextItem::ElementInfo && m_info_name == "xref")
|
||||
{
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
DiagramContext dc = m_parent_element->elementInformations();
|
||||
if(dc.value("xref").toString() != xref_label)
|
||||
{
|
||||
dc.addValue("xref", xref_label);
|
||||
m_parent_element->setElementInformations(dc);
|
||||
}
|
||||
|
||||
//Set up connections for future updates
|
||||
if(m_update_slave_Xref_connection.isEmpty())
|
||||
{
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram(), &Diagram::diagramInformationChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::projectDiagramsOrderChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::diagramRemoved, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::XRefPropertiesChanged, this, &DynamicElementTextItem::updateXref);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//For label/composite text: fall through to cleanup (delete m_slave_Xref_item)
|
||||
}
|
||||
else if(
|
||||
(m_text_from == DynamicElementTextItem::ElementInfo && m_info_name == "label") ||
|
||||
(m_text_from == DynamicElementTextItem::CompositeText && m_composite_text.contains("%{label}"))
|
||||
)
|
||||
)
|
||||
{
|
||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
if(!m_slave_Xref_item)
|
||||
{
|
||||
m_slave_Xref_item = new QGraphicsTextItem(xref_label, this);
|
||||
m_slave_Xref_item->setFont(QETApp::diagramTextsFont(5));
|
||||
m_slave_Xref_item->setDefaultTextColor(Qt::black);
|
||||
m_slave_Xref_item->installSceneEventFilter(this);
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram(), &Diagram::diagramInformationChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::projectDiagramsOrderChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::diagramRemoved, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::XRefPropertiesChanged, this, &DynamicElementTextItem::updateXref);
|
||||
if(!m_slave_Xref_item)
|
||||
{
|
||||
m_slave_Xref_item = new QGraphicsTextItem(xref_label, this);
|
||||
m_slave_Xref_item->setFont(QETApp::diagramTextsFont(5));
|
||||
m_slave_Xref_item->setDefaultTextColor(Qt::black);
|
||||
m_slave_Xref_item->installSceneEventFilter(this);
|
||||
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram(), &Diagram::diagramInformationChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::projectDiagramsOrderChanged, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::diagramRemoved, this, &DynamicElementTextItem::updateXref);
|
||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::XRefPropertiesChanged, this, &DynamicElementTextItem::updateXref);
|
||||
}
|
||||
else
|
||||
m_slave_Xref_item->setPlainText(xref_label);
|
||||
setXref_item(xrp.getXrefPos(), xrp.slaveOffset());
|
||||
return;
|
||||
}
|
||||
else
|
||||
m_slave_Xref_item->setPlainText(xref_label);
|
||||
setXref_item(xrp.getXrefPos());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1428,6 +1460,29 @@ void DynamicElementTextItem::updateXref()
|
||||
delete m_slave_Xref_item;
|
||||
m_slave_Xref_item = nullptr;
|
||||
m_update_slave_Xref_connection.clear();
|
||||
|
||||
//If position changed to Champ de texte, store xref in element info
|
||||
if(m_parent_element->linkType() == Element::Slave &&
|
||||
!m_parent_element->linkedElements().isEmpty())
|
||||
{
|
||||
Element *master_elmt = m_parent_element->linkedElements().first();
|
||||
if(master_elmt && diagram())
|
||||
{
|
||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
if(xrp.getXrefPos() == Qt::AlignHCenter)
|
||||
{
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
DiagramContext dc = m_parent_element->elementInformations();
|
||||
if(dc.value("xref").toString() != xref_label)
|
||||
{
|
||||
dc.addValue("xref", xref_label);
|
||||
m_parent_element->setElementInformations(dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1476,7 +1531,7 @@ void DynamicElementTextItem::setPlainText(const QString &text)
|
||||
? nullptr : m_parent_element.data()->linkedElements().first();
|
||||
if (master_elmt) {
|
||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
setXref_item(xrp.getXrefPos());
|
||||
setXref_item(xrp.getXrefPos(), xrp.slaveOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1488,44 +1543,44 @@ void DynamicElementTextItem::setTextWidth(qreal width)
|
||||
emit textWidthChanged(width);
|
||||
}
|
||||
|
||||
void DynamicElementTextItem::setXref_item(Qt::AlignmentFlag m_exHrefPos)
|
||||
void DynamicElementTextItem::setXref_item(Qt::AlignmentFlag m_exHrefPos, int slave_offset)
|
||||
{
|
||||
QRectF r = boundingRect();
|
||||
QPointF pos;
|
||||
//QPointF pos(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.top());
|
||||
if (m_exHrefPos == Qt::AlignBottom)
|
||||
{
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.bottom());
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.bottom() + slave_offset);
|
||||
}
|
||||
else if (m_exHrefPos == Qt::AlignTop)
|
||||
{
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.top() - m_slave_Xref_item->boundingRect().height());
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.top() - m_slave_Xref_item->boundingRect().height() - slave_offset);
|
||||
}
|
||||
else if (m_exHrefPos == Qt::AlignLeft) //
|
||||
{
|
||||
pos = QPointF(r.left() - m_slave_Xref_item->boundingRect().width(),r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
pos = QPointF(r.left() - m_slave_Xref_item->boundingRect().width() - slave_offset,r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
}
|
||||
else if (m_exHrefPos == Qt::AlignRight) //
|
||||
{
|
||||
pos = QPointF(r.right() ,r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
pos = QPointF(r.right() + slave_offset ,r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
}
|
||||
else if (m_exHrefPos == Qt::AlignBaseline) //
|
||||
{
|
||||
if(this->alignment() &Qt::AlignBottom)
|
||||
{
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.bottom());
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.bottom() + slave_offset);
|
||||
}
|
||||
else if(this->alignment() &Qt::AlignTop)
|
||||
{
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.top() - m_slave_Xref_item->boundingRect().height());
|
||||
pos = QPointF(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,r.top() - m_slave_Xref_item->boundingRect().height() - slave_offset);
|
||||
}
|
||||
else if(this->alignment() &Qt::AlignLeft)
|
||||
{
|
||||
pos = QPointF(r.left() - m_slave_Xref_item->boundingRect().width(),r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
pos = QPointF(r.left() - m_slave_Xref_item->boundingRect().width() - slave_offset,r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
}
|
||||
else if(this->alignment() &Qt::AlignRight)
|
||||
{
|
||||
pos = QPointF(r.right() ,r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
pos = QPointF(r.right() + slave_offset ,r.center().y() - m_slave_Xref_item->boundingRect().height()/2);
|
||||
}
|
||||
}
|
||||
m_slave_Xref_item->setPos(pos);
|
||||
|
||||
@@ -112,7 +112,7 @@ class DynamicElementTextItem : public DiagramTextItem
|
||||
void updateXref();
|
||||
void setPlainText(const QString &text);
|
||||
void setTextWidth(qreal width);
|
||||
void setXref_item(Qt::AlignmentFlag m_exHrefPos);
|
||||
void setXref_item(Qt::AlignmentFlag m_exHrefPos, int slave_offset = 0);
|
||||
|
||||
void setKeepVisualRotation(bool set);
|
||||
bool keepVisualRotation() const;
|
||||
|
||||
@@ -263,7 +263,16 @@ void ElementTextItemGroup::updateAlignment()
|
||||
if(m_Xref_item)
|
||||
m_Xref_item->autoPos();
|
||||
if(m_slave_Xref_item)
|
||||
adjustSlaveXrefPos();
|
||||
{
|
||||
int slave_offset = 0;
|
||||
Element *master_elmt = m_parent_element->linkedElements().isEmpty()
|
||||
? nullptr : m_parent_element->linkedElements().first();
|
||||
if (master_elmt && m_parent_element->diagram()) {
|
||||
XRefProperties xrp = m_parent_element->diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
slave_offset = xrp.slaveOffset();
|
||||
}
|
||||
adjustSlaveXrefPos(slave_offset);
|
||||
}
|
||||
if(m_hold_to_bottom_of_page)
|
||||
autoPos();
|
||||
}
|
||||
@@ -786,12 +795,46 @@ void ElementTextItemGroup::updateXref()
|
||||
!m_parent_element->linkedElements().isEmpty())
|
||||
{
|
||||
Element *master_elmt = m_parent_element->linkedElements().first();
|
||||
XRefProperties xrp = project->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
|
||||
//Champ de texte: store xref in element informations
|
||||
if(xrp.getXrefPos() == Qt::AlignHCenter)
|
||||
{
|
||||
for(DynamicElementTextItem *deti : texts())
|
||||
{
|
||||
if(deti->textFrom() == DynamicElementTextItem::ElementInfo && deti->infoName() == "xref")
|
||||
{
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
DiagramContext dc = m_parent_element->elementInformations();
|
||||
if(dc.value("xref").toString() != xref_label)
|
||||
{
|
||||
dc.addValue("xref", xref_label);
|
||||
m_parent_element->setElementInformations(dc);
|
||||
}
|
||||
|
||||
//Set up connections for future updates
|
||||
if(m_update_slave_Xref_connection.isEmpty())
|
||||
{
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &ElementTextItemGroup::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &ElementTextItemGroup::updateXref);
|
||||
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &ElementTextItemGroup::updateXref);
|
||||
m_update_slave_Xref_connection << connect(project, &QETProject::projectDiagramsOrderChanged, this, &ElementTextItemGroup::updateXref);
|
||||
m_update_slave_Xref_connection << connect(project, &QETProject::diagramRemoved, this, &ElementTextItemGroup::updateXref);
|
||||
m_update_slave_Xref_connection << connect(project, &QETProject::XRefPropertiesChanged, this, &ElementTextItemGroup::updateXref);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
//No "xref" text found: fall through to cleanup
|
||||
}
|
||||
|
||||
for(DynamicElementTextItem *deti : texts())
|
||||
{
|
||||
if((deti->textFrom() == DynamicElementTextItem::ElementInfo && deti->infoName() == "label") ||
|
||||
(deti->textFrom() == DynamicElementTextItem::CompositeText && deti->compositeText().contains("%{label")))
|
||||
{
|
||||
XRefProperties xrp = project->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
@@ -810,7 +853,7 @@ void ElementTextItemGroup::updateXref()
|
||||
else
|
||||
m_slave_Xref_item->setPlainText(xref_label);
|
||||
|
||||
adjustSlaveXrefPos();
|
||||
adjustSlaveXrefPos(xrp.slaveOffset());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -830,14 +873,38 @@ void ElementTextItemGroup::updateXref()
|
||||
delete m_slave_Xref_item;
|
||||
m_slave_Xref_item = nullptr;
|
||||
m_update_slave_Xref_connection.clear();
|
||||
|
||||
//If position changed to Champ de texte, store xref in element info
|
||||
if(m_parent_element->linkType() == Element::Slave &&
|
||||
!m_parent_element->linkedElements().isEmpty() &&
|
||||
m_parent_element->diagram())
|
||||
{
|
||||
Element *master_elmt = m_parent_element->linkedElements().first();
|
||||
if(master_elmt)
|
||||
{
|
||||
XRefProperties xrp = m_parent_element->diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||
if(xrp.getXrefPos() == Qt::AlignHCenter)
|
||||
{
|
||||
QString xref_label = xrp.slaveLabel();
|
||||
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
|
||||
|
||||
DiagramContext dc = m_parent_element->elementInformations();
|
||||
if(dc.value("xref").toString() != xref_label)
|
||||
{
|
||||
dc.addValue("xref", xref_label);
|
||||
m_parent_element->setElementInformations(dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ElementTextItemGroup::adjustSlaveXrefPos()
|
||||
void ElementTextItemGroup::adjustSlaveXrefPos(int slave_offset)
|
||||
{
|
||||
QRectF r = boundingRect();
|
||||
QPointF pos(r.center().x() - m_slave_Xref_item->boundingRect().width()/2,
|
||||
r.bottom());
|
||||
r.bottom() + slave_offset);
|
||||
m_slave_Xref_item->setPos(pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class ElementTextItemGroup : public QObject, public QGraphicsItemGroup
|
||||
|
||||
private:
|
||||
void updateXref();
|
||||
void adjustSlaveXrefPos();
|
||||
void adjustSlaveXrefPos(int slave_offset = 0);
|
||||
void autoPos();
|
||||
|
||||
private:
|
||||
|
||||
@@ -227,6 +227,73 @@ void MasterElement::aboutDeleteXref()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MasterElement::contactUsage
|
||||
* Count the slave contacts currently linked to this master, by type.
|
||||
* This is the single place where that count is worked out: the cross ref
|
||||
* item, the properties dialog and the link widgets all read it from here,
|
||||
* so they cannot disagree with each other.
|
||||
* @return the per type usage
|
||||
*/
|
||||
namespace {
|
||||
|
||||
/**
|
||||
Map the element data's contact type onto the tally's own, so that
|
||||
the used count and the declared capacity cannot classify the same
|
||||
contact type differently.
|
||||
*/
|
||||
ContactUsage::Type contactType(ElementData::SlaveState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ElementData::NO: return ContactUsage::NO;
|
||||
case ElementData::NC: return ContactUsage::NC;
|
||||
case ElementData::SW: return ContactUsage::SW;
|
||||
case ElementData::Other: break;
|
||||
}
|
||||
|
||||
return ContactUsage::Other;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContactUsage MasterElement::contactUsage() const
|
||||
{
|
||||
ContactUsage usage;
|
||||
|
||||
for (Element *elmt : connected_elements)
|
||||
{
|
||||
if (!elmt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ElementData &data = elmt->elementData();
|
||||
usage.addSlave(contactType(data.m_slave_state), data.m_contact_count);
|
||||
}
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MasterElement::contactCapacity
|
||||
* The contacts this master declares it provides, by type, summed over its
|
||||
* contact groups. A group stands for contactCount contacts of its type.
|
||||
* Returns an empty tally when the element declares no groups, which is the
|
||||
* case for every element in the standard collection today -- callers use
|
||||
* that to decide whether a capacity is worth showing at all.
|
||||
* @return the per type capacity
|
||||
*/
|
||||
ContactUsage MasterElement::contactCapacity() const
|
||||
{
|
||||
ContactUsage capacity;
|
||||
|
||||
for (const auto &group : m_data.m_slave_contact_groups) {
|
||||
capacity.addSlave(contactType(group.type), group.contactCount);
|
||||
}
|
||||
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MasterElement::isFull
|
||||
* @return true if the master has reached its maximum number of slaves
|
||||
@@ -247,7 +314,10 @@ bool MasterElement::isFull() const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if current connected elements reached or exceeded the limit
|
||||
// max_slaves is a number of slots, not of contacts: it sizes the
|
||||
// element's contact group table, and a slave occupies exactly one
|
||||
// group however many contacts that group stands for. So the slots
|
||||
// in use are the linked elements, not the contacts they carry.
|
||||
return connected_elements.size() >= max_slaves;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define MASTERELEMENT_H
|
||||
|
||||
#include "element.h"
|
||||
#include "../contactusage.h"
|
||||
#include <QHash>
|
||||
#include <QMetaObject>
|
||||
|
||||
@@ -47,6 +48,8 @@ class MasterElement : public Element
|
||||
void initLink (QETProject *project) override;
|
||||
QRectF XrefBoundingRect() const;
|
||||
|
||||
ContactUsage contactUsage() const;
|
||||
ContactUsage contactCapacity() const;
|
||||
bool isFull() const; // Check Slave-Limit
|
||||
|
||||
protected:
|
||||
|
||||
@@ -194,8 +194,9 @@ QStringList QETInformation::elementInfoKeys()
|
||||
ELMT_PLC_ADDRESS,
|
||||
ELMT_PLC_FUNCTION,
|
||||
ELMT_PLC_COMMENT,
|
||||
ELMT_PLC_CROSSREF,
|
||||
"exclude_from_bom" };
|
||||
ELMT_PLC_CROSSREF,
|
||||
ELMT_XREF,
|
||||
"exclude_from_bom" };
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -316,6 +317,7 @@ QString QETInformation::translatedInfoKey(const QString &info)
|
||||
else if (info == ELMT_PLC_FUNCTION) return QObject::tr("Fonction PLC");
|
||||
else if (info == ELMT_PLC_COMMENT) return QObject::tr("Commentaire PLC");
|
||||
else if (info == ELMT_PLC_CROSSREF) return QObject::tr("Réf. croisée PLC");
|
||||
else if (info == ELMT_XREF) return QObject::tr("Réf. croisée");
|
||||
else return QString();
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace QETInformation
|
||||
static QString ELMT_SUPPLIER_AUX4 = "supplier_auxiliary4";
|
||||
static QString ELMT_QUANTITY_AUX4 = "quantity_auxiliary4";
|
||||
static QString ELMT_UNITY_AUX4 = "unity_auxiliary4";
|
||||
static QString ELMT_XREF = "xref";
|
||||
|
||||
|
||||
/** Default information related to conductor **/
|
||||
|
||||
@@ -17,6 +17,84 @@
|
||||
*/
|
||||
#include "qetmessagebox.h"
|
||||
|
||||
#include <QTextStream>
|
||||
|
||||
namespace {
|
||||
bool g_non_interactive = false;
|
||||
|
||||
/**
|
||||
@brief autoAnswer
|
||||
Report a message box on stderr and pick an answer, for use when there
|
||||
is no user to click anything. @see QET::QetMessageBox::setNonInteractive
|
||||
@param severity : short word naming the kind of box, for the log line
|
||||
@param title
|
||||
@param text
|
||||
@param buttons : the buttons the caller offered
|
||||
@param defaultButton : the caller's preferred answer, may be NoButton
|
||||
@return the button to report as pressed
|
||||
*/
|
||||
QMessageBox::StandardButton autoAnswer(
|
||||
const char *severity,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
//Honour the caller's own default when it named one.
|
||||
if (defaultButton != QMessageBox::NoButton
|
||||
&& (buttons & defaultButton)) {
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: answered with the caller's default button)\n";
|
||||
return defaultButton;
|
||||
}
|
||||
|
||||
//Otherwise prefer a "carry on" answer over one that cancels, so a
|
||||
//batch run completes rather than silently doing nothing.
|
||||
static const QMessageBox::StandardButton preference[] = {
|
||||
QMessageBox::Ok, QMessageBox::Open, QMessageBox::Yes,
|
||||
QMessageBox::Save, QMessageBox::Apply, QMessageBox::YesToAll,
|
||||
QMessageBox::Retry, QMessageBox::Ignore, QMessageBox::Close
|
||||
};
|
||||
for (auto candidate : preference) {
|
||||
if (buttons & candidate) {
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: continuing)\n";
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
//Nothing affirmative on offer -- fall back to whatever is set.
|
||||
for (int bit = QMessageBox::Ok; bit <= QMessageBox::RestoreDefaults; bit <<= 1) {
|
||||
auto candidate = static_cast<QMessageBox::StandardButton>(bit);
|
||||
if (buttons & candidate) {
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: answered automatically)\n";
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: no button offered)\n";
|
||||
return QMessageBox::NoButton;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QET::QetMessageBox::setNonInteractive
|
||||
@param non_interactive
|
||||
*/
|
||||
void QET::QetMessageBox::setNonInteractive(bool non_interactive) {
|
||||
g_non_interactive = non_interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QET::QetMessageBox::isNonInteractive
|
||||
@return true when message boxes are answered without a user
|
||||
*/
|
||||
bool QET::QetMessageBox::isNonInteractive() {
|
||||
return g_non_interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
@see Documentation Qt pour QMessageBox::critical
|
||||
*/
|
||||
@@ -27,6 +105,9 @@ QMessageBox::StandardButton QET::QetMessageBox::critical (
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Critical", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Critical,
|
||||
@@ -59,6 +140,9 @@ QMessageBox::StandardButton QET::QetMessageBox::information(
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Information", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Information,
|
||||
@@ -91,6 +175,9 @@ QMessageBox::StandardButton QET::QetMessageBox::question (
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Question", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Question,
|
||||
@@ -123,6 +210,9 @@ QMessageBox::StandardButton QET::QetMessageBox::warning (
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Warning", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Warning,
|
||||
|
||||
@@ -27,6 +27,28 @@ namespace QET {
|
||||
Qt:Sheet flag, thus enabling a better MacOS integration.
|
||||
*/
|
||||
namespace QetMessageBox {
|
||||
/**
|
||||
Enable non-interactive mode.
|
||||
|
||||
In non-interactive mode the functions below never construct a
|
||||
dialog. They write the message to stderr and return an answer
|
||||
immediately, so a headless run cannot block on a modal box that
|
||||
nobody is there to dismiss.
|
||||
|
||||
This is needed because these are reachable from the command-line
|
||||
tools: opening a project written by an older QElectroTech raises
|
||||
a warning from QETProject::readProjectXml(), and with no display
|
||||
to click it, QDialog::exec() spins its event loop forever.
|
||||
|
||||
The answer is chosen as: the caller's defaultButton when it gave
|
||||
one, otherwise the first "carry on" button among those offered
|
||||
(Ok, Open, Yes, Save, Apply...), otherwise the first button set.
|
||||
So the two warnings above resolve to Open and the project loads,
|
||||
which is what a batch invocation wants.
|
||||
*/
|
||||
void setNonInteractive(bool non_interactive);
|
||||
bool isNonInteractive();
|
||||
|
||||
QMessageBox::StandardButton critical (
|
||||
QWidget *,
|
||||
const QString &,
|
||||
|
||||
@@ -1252,7 +1252,7 @@ ElementsLocation QETProject::importElement(ElementsLocation &location)
|
||||
// Warn if the new element introduces slave contact groups
|
||||
QDomElement new_kind = location.xml().firstChildElement("kindInformations");
|
||||
if (!new_kind.firstChildElement("slaveContactGroups").isNull()) {
|
||||
QMessageBox::StandardButton answer = QMessageBox::warning(nullptr,
|
||||
QMessageBox::StandardButton answer = QET::QetMessageBox::warning(nullptr,
|
||||
tr("Système de contacts modifié"),
|
||||
tr("Le nouvel élément définit des groupes de contacts esclaves.\n"
|
||||
"Les éléments esclaves existants ne seront pas automatiquement "
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/elementtextitemgroup.h"
|
||||
#include "../qetgraphicsitem/masterelement.h"
|
||||
#include "../qeticons.h"
|
||||
#include "dynamicelementtextitemeditor.h"
|
||||
#include "elementinfowidget.h"
|
||||
@@ -380,9 +381,48 @@ QWidget *ElementPropertiesWidget::generalWidget()
|
||||
description_string += QString(tr("Rotation : %1°\n")).arg(m_element.data()->rotation());
|
||||
description_string += QString(tr("Dimensions : %1*%2\n")).arg(m_element -> size().width()).arg(m_element -> size().height());
|
||||
description_string += QString(tr("Bornes : %1\n")).arg(m_element -> terminals().count());
|
||||
if (m_element->linkType() == Element::Master){
|
||||
description_string += QString(tr("Nombre maximum de contacts esclaves définis : %1\n")).arg(m_element -> elementData().m_max_slaves);
|
||||
description_string += QString(tr("Nombre de contacts esclaves utilisés : %1\n")).arg(m_element ->linkedElements().count());
|
||||
if (m_element->linkType() == Element::Master)
|
||||
{
|
||||
//The declared limit is optional: -1 means the element sets no
|
||||
//limit at all, which is worth saying rather than printing "-1".
|
||||
const int max_slaves = m_element->elementData().m_max_slaves;
|
||||
description_string += max_slaves == -1
|
||||
? QString(tr("Nombre maximum de contacts esclaves définis : non défini\n"))
|
||||
: QString(tr("Nombre maximum de contacts esclaves définis : %1\n")).arg(max_slaves);
|
||||
|
||||
//Left as a count of linked elements: the line above is a number
|
||||
//of slots, and a slave fills one slot however many contacts it
|
||||
//carries, so the two stay in the same unit.
|
||||
description_string += QString(tr("Nombre de contacts esclaves utilisés : %1\n")).arg(m_element->linkedElements().count());
|
||||
|
||||
//The breakdown below is in contacts, not slots: it answers how
|
||||
//many contacts an auxiliary block must provide.
|
||||
const MasterElement *master =
|
||||
static_cast<const MasterElement *>(m_element.data());
|
||||
const ContactUsage usage = master->contactUsage();
|
||||
const ContactUsage capacity = master->contactCapacity();
|
||||
|
||||
if (capacity.total() > 0)
|
||||
{
|
||||
//The element declares contact groups, so it can say not
|
||||
//only what has been used but what it has to offer. A type
|
||||
//used beyond what is declared shows as e.g. "1/0", which
|
||||
//is the point: it says this contact does not fit the part.
|
||||
description_string += QString(tr(" Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8\n"))
|
||||
.arg(usage.no).arg(capacity.no)
|
||||
.arg(usage.nc).arg(capacity.nc)
|
||||
.arg(usage.sw).arg(capacity.sw)
|
||||
.arg(usage.other).arg(capacity.other);
|
||||
}
|
||||
else if (usage.total() > 0)
|
||||
{
|
||||
//No declared groups, so a plain count of what is in use.
|
||||
description_string += QString(tr(" Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4\n"))
|
||||
.arg(usage.no)
|
||||
.arg(usage.nc)
|
||||
.arg(usage.sw)
|
||||
.arg(usage.other);
|
||||
}
|
||||
}
|
||||
description_string += QString(tr("Emplacement : %1\n")).arg(m_element.data()->location().toString());
|
||||
|
||||
|
||||
@@ -65,7 +65,17 @@ ClickableImageLabel::ClickableImageLabel(const QImage &sourceImage, QWidget *par
|
||||
*/
|
||||
void ClickableImageLabel::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton || pixmap().isNull())
|
||||
// QLabel::pixmap() returns a pointer in Qt5 and a value in Qt6.
|
||||
// Qt 5.15 offers the by-value form behind Qt::ReturnByValue; the
|
||||
// pointer overload is deprecated there, so take the by-value one
|
||||
// on both and the difference reduces to the argument.
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
const QPixmap label_pixmap = pixmap(Qt::ReturnByValue);
|
||||
#else
|
||||
const QPixmap label_pixmap = pixmap();
|
||||
#endif
|
||||
|
||||
if (event->button() != Qt::LeftButton || label_pixmap.isNull())
|
||||
return;
|
||||
|
||||
// The label may be larger than its pixmap (layout stretching); the
|
||||
@@ -73,9 +83,9 @@ void ClickableImageLabel::mousePressEvent(QMouseEvent *event)
|
||||
// so the click has to be re-based against the pixmap's own rect
|
||||
// within the label, not the label's own top-left.
|
||||
const QRect pixmapRect(
|
||||
(width() - pixmap().width()) / 2,
|
||||
(height() - pixmap().height()) / 2,
|
||||
pixmap().width(), pixmap().height());
|
||||
(width() - label_pixmap.width()) / 2,
|
||||
(height() - label_pixmap.height()) / 2,
|
||||
label_pixmap.width(), label_pixmap.height());
|
||||
if (!pixmapRect.contains(event->pos()))
|
||||
return;
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
#include "linksingleelementwidget.h"
|
||||
#include "contactgroupselectiondialog.h"
|
||||
#include "../qetgraphicsitem/masterelement.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../diagram.h"
|
||||
#include "../diagramposition.h"
|
||||
@@ -353,8 +352,16 @@ void LinkSingleElementWidget::buildTree()
|
||||
|
||||
QSettings settings;
|
||||
QVariant v = settings.value(QStringLiteral("link-element-widget/report-state"));
|
||||
if(!v.isNull())
|
||||
ui->m_tree_widget->header()->restoreState(v.toByteArray());
|
||||
auto *header = ui->m_tree_widget->header();
|
||||
if (v.isNull() || !header->restoreState(v.toByteArray()))
|
||||
{
|
||||
// Keep logical column IDs stable for saved layouts, but show the
|
||||
// folio identity first even when the candidate has no conductor.
|
||||
for (int column = 5; column < 8; ++column)
|
||||
header->moveSection(header->visualIndex(column), column - 5);
|
||||
ui->m_tree_widget->resizeColumnToContents(5);
|
||||
ui->m_tree_widget->resizeColumnToContents(6);
|
||||
}
|
||||
}
|
||||
|
||||
setUpCompleter();
|
||||
@@ -410,11 +417,12 @@ QVector <QPointer<Element>> LinkSingleElementWidget::availableElements()
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the master is full, we'll remove it from the list!
|
||||
MasterElement *master = static_cast<MasterElement*>(elmt);
|
||||
if (master->isFull()) {
|
||||
elmt_vector.removeAt(i);
|
||||
}
|
||||
// A master at its declared limit stays in the list. Removing
|
||||
// it made a full master indistinguishable from one that does
|
||||
// not exist: the candidate simply was not there, with nothing
|
||||
// to say why. The limit is advisory -- see the prompt in
|
||||
// MasterPropertiesWidget::on_link_button_clicked() -- so the
|
||||
// user decides, rather than the list deciding for them.
|
||||
}
|
||||
}
|
||||
return elmt_vector;
|
||||
|
||||
@@ -307,15 +307,24 @@ void MasterPropertiesWidget::on_link_button_clicked()
|
||||
int max_slaves = max_slaves_variant.toInt();
|
||||
int current_slaves = ui->m_link_tree_widget->topLevelItemCount();
|
||||
|
||||
// If a limit is set and reached
|
||||
// If a limit is set and reached, say so but let the user decide.
|
||||
// The limit records how many contacts the part is expected to
|
||||
// carry; it is not a rule the drawing has to obey, and refusing
|
||||
// the link obstructs drawing a schematic before the hardware has
|
||||
// been chosen.
|
||||
if (max_slaves != -1 && current_slaves >= max_slaves) {
|
||||
|
||||
|
||||
// Show a message box with the actual window as parent to ensure it's on top
|
||||
QMessageBox::warning(this->window(),
|
||||
tr("Nombre maximal d'esclaves atteint."),
|
||||
tr("Cet élément maître ne peut plus accepter aucun nouveau contact esclave, la limite fixée a été atteinte (Limite: %1).").arg(max_slaves));
|
||||
return;
|
||||
const auto answer = QMessageBox::warning(
|
||||
this->window(),
|
||||
tr("Nombre maximal d'esclaves atteint."),
|
||||
tr("La limite fixée pour cet élément maître est atteinte (Limite: %1).\n\n"
|
||||
"Voulez-vous tout de même lier ce contact esclave ?").arg(max_slaves),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::Yes);
|
||||
if (answer != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 "wiringlistdialog.h"
|
||||
|
||||
#include "../dataBase/projectdatabase.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QSqlQueryModel>
|
||||
#include <QTableView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief WiringListDialog::WiringListDialog
|
||||
@param project : project whose wiring list is shown
|
||||
@param parent : parent widget
|
||||
*/
|
||||
WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_project(project)
|
||||
{
|
||||
setWindowTitle(tr("Liste de câblage", "window title"));
|
||||
resize(900, 500);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
|
||||
//The wiring list reads the database rather than the diagrams, and a
|
||||
//conductor's row is only as fresh as the last thing that touched it.
|
||||
//Refresh before querying so the dialog cannot show a wire number that
|
||||
//was edited earlier in the session.
|
||||
m_project->dataBase()->updateDB();
|
||||
|
||||
auto *model = new QSqlQueryModel(this);
|
||||
model->setQuery(QStringLiteral(
|
||||
"SELECT wire_number, from_element_label, from_terminal,"
|
||||
" to_element_label, to_terminal, diagram_position"
|
||||
" 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"),
|
||||
m_project->dataBase()->database());
|
||||
|
||||
model->setHeaderData(0, Qt::Horizontal, tr("Fil", "column title"));
|
||||
model->setHeaderData(1, Qt::Horizontal, tr("Composant 1", "column title"));
|
||||
model->setHeaderData(2, Qt::Horizontal, tr("Borne 1", "column title"));
|
||||
model->setHeaderData(3, Qt::Horizontal, tr("Composant 2", "column title"));
|
||||
model->setHeaderData(4, Qt::Horizontal, tr("Borne 2", "column title"));
|
||||
model->setHeaderData(5, Qt::Horizontal, tr("Folio", "column title"));
|
||||
|
||||
const int excluded = m_project->dataBase()->excludedConductorCount();
|
||||
|
||||
//QSqlQueryModel fetches lazily, so rowCount() straight after
|
||||
//setQuery() reports the first batch (256) rather than the query's
|
||||
//size. Draining it first is what makes the count below true for a
|
||||
//project with more wires than that.
|
||||
while (model->canFetchMore()) {
|
||||
model->fetchMore();
|
||||
}
|
||||
const int listed = model->rowCount();
|
||||
|
||||
auto *summary = new QLabel(this);
|
||||
summary->setWordWrap(true);
|
||||
if (excluded > 0)
|
||||
{
|
||||
//Rare now that Terminal::stableUuid() gives every terminal an
|
||||
//identity: what is left is a conductor whose endpoint has no
|
||||
//parent element at all. Still worth saying out loud rather than
|
||||
//presenting a short list as if it were complete.
|
||||
summary->setText(tr("%n conducteur(s) listé(s).", "wiring list summary", listed)
|
||||
% QStringLiteral(" ")
|
||||
% tr("%n conducteur(s) exclu(s) : une extrémité n'est rattachée"
|
||||
" à aucun élément.",
|
||||
"wiring list exclusion warning", excluded));
|
||||
}
|
||||
else {
|
||||
summary->setText(tr("%n conducteur(s) listé(s).", "wiring list summary", listed));
|
||||
}
|
||||
layout->addWidget(summary);
|
||||
|
||||
auto *view = new QTableView(this);
|
||||
view->setModel(model);
|
||||
view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
view->setAlternatingRowColors(true);
|
||||
view->verticalHeader()->setVisible(false);
|
||||
view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
layout->addWidget(view);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 WIRINGLISTDIALOG_H
|
||||
#define WIRINGLISTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief The WiringListDialog class
|
||||
Read-only view of the project's from-to wiring list, read from the
|
||||
wiring_list_view of projectDataBase.
|
||||
|
||||
Deliberately not an exporter: QET already ships a wiring-list CSV export
|
||||
(Projet > Exporter le plan de câblage, and --export-cables), which walks
|
||||
the project XML and covers that need. This dialog exists to make the
|
||||
database view inspectable, and above all to state how many conductors
|
||||
are missing from it and why -- a count the CSV export cannot give,
|
||||
because it never excludes anything in the first place.
|
||||
*/
|
||||
class WiringListDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WiringListDialog(QETProject *project, QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
QETProject *m_project = nullptr;
|
||||
};
|
||||
|
||||
#endif // WIRINGLISTDIALOG_H
|
||||
@@ -111,6 +111,7 @@ void XRefPropertiesWidget::buildUi()
|
||||
ui -> m_xrefpos_cb -> addItem(tr("Left"),"left");
|
||||
ui -> m_xrefpos_cb -> addItem(tr("Right"),"right");
|
||||
ui -> m_xrefpos_cb -> addItem(tr("Text alignment"),"alignment");
|
||||
ui -> m_xrefpos_cb -> addItem(tr("Champ de texte"),"text_field");
|
||||
m_previous_type_index = ui -> m_type_cb -> currentIndex();
|
||||
}
|
||||
|
||||
@@ -139,6 +140,7 @@ void XRefPropertiesWidget::saveProperties(int index) {
|
||||
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "left") xrp.setXrefPos(Qt::AlignLeft);
|
||||
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "right") xrp.setXrefPos(Qt::AlignRight);
|
||||
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "alignment") xrp.setXrefPos(Qt::AlignBaseline);
|
||||
else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "text_field") xrp.setXrefPos(Qt::AlignHCenter);
|
||||
xrp.setShowPowerContac(ui->m_show_power_cb->isChecked());
|
||||
xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked());
|
||||
xrp.setPrefix("power", ui->m_power_prefix_le->text());
|
||||
@@ -147,6 +149,7 @@ void XRefPropertiesWidget::saveProperties(int index) {
|
||||
xrp.setMasterLabel(ui->m_master_le->text());
|
||||
xrp.setSlaveLabel(ui->m_slave_le->text());
|
||||
xrp.setOffset(ui->m_offset_sb->value());
|
||||
xrp.setSlaveOffset(ui->m_slave_offset_sb->value());
|
||||
|
||||
m_properties.insert(type, xrp);
|
||||
}
|
||||
@@ -177,6 +180,9 @@ void XRefPropertiesWidget::updateDisplay()
|
||||
int offset = xrp.offset();
|
||||
ui->m_offset_sb->setValue(offset);
|
||||
|
||||
int slave_offset = xrp.slaveOffset();
|
||||
ui->m_slave_offset_sb->setValue(slave_offset);
|
||||
|
||||
if (xrp.snapTo() == XRefProperties::Bottom){
|
||||
ui->m_snap_to_cb->setCurrentIndex(ui->m_snap_to_cb->findData("bottom"));
|
||||
ui->m_offset_sb->setEnabled(true);
|
||||
@@ -191,6 +197,7 @@ void XRefPropertiesWidget::updateDisplay()
|
||||
else if(xrp.getXrefPos() == Qt::AlignRight) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("right"));
|
||||
else if(xrp.getXrefPos() == Qt::AlignBaseline) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("alignment"));
|
||||
else if(xrp.getXrefPos() == Qt::AlignBottom) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("bottom"));
|
||||
else if(xrp.getXrefPos() == Qt::AlignHCenter) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("text_field"));
|
||||
ui->m_show_power_cb->setChecked(xrp.showPowerContact());
|
||||
ui->m_show_terminal_name_cb->setChecked(xrp.showTerminalName());
|
||||
ui->m_power_prefix_le-> setText(xrp.prefix("power"));
|
||||
|
||||
@@ -90,20 +90,56 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>XRef slave position</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="m_xrefpos_cb"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>XRef slave position</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="m_xrefpos_cb"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Distance label - slave :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="m_slave_offset_sb">
|
||||
<property name="toolTip">
|
||||
<string>Distance in pixels between the label and the slave cross reference</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true">px</string>
|
||||
</property>
|
||||
<property name="correctionMode">
|
||||
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-50</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
|
||||
@@ -85,3 +85,9 @@ add_test(NAME tst_diagramsortkeys COMMAND tst_diagramsortkeys)
|
||||
target_include_directories(tst_diagramsortkeys PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_diagramsortkeys PRIVATE Qt::Test)
|
||||
|
||||
# contactusage.h is a header-only helper holding the contact counting
|
||||
# rules, so this test builds independently of the rest of the QET sources.
|
||||
add_executable(tst_contactusage tst_contactusage.cpp)
|
||||
add_test(NAME tst_contactusage COMMAND tst_contactusage)
|
||||
target_include_directories(tst_contactusage PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_contactusage PRIVATE Qt::Test)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
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 <QtTest>
|
||||
|
||||
#include "contactusage.h"
|
||||
|
||||
class tst_contactusage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
// An empty master uses nothing.
|
||||
void emptyUsesNothing()
|
||||
{
|
||||
ContactUsage usage;
|
||||
|
||||
QCOMPARE(usage.no, 0);
|
||||
QCOMPARE(usage.nc, 0);
|
||||
QCOMPARE(usage.sw, 0);
|
||||
QCOMPARE(usage.other, 0);
|
||||
QCOMPARE(usage.total(), 0);
|
||||
}
|
||||
|
||||
// Each type accumulates into its own field only.
|
||||
void countsEachTypeSeparately()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NC, 1);
|
||||
usage.addSlave(ContactUsage::SW, 1);
|
||||
usage.addSlave(ContactUsage::Other, 1);
|
||||
|
||||
QCOMPARE(usage.no, 2);
|
||||
QCOMPARE(usage.nc, 1);
|
||||
QCOMPARE(usage.sw, 1);
|
||||
QCOMPARE(usage.other, 1);
|
||||
QCOMPARE(usage.total(), 5);
|
||||
}
|
||||
|
||||
// A slave standing for several contacts counts once per contact.
|
||||
// Counting elements rather than contacts made a 4 pole contact
|
||||
// consume a single contact from the master's budget.
|
||||
void countsContactsNotElements()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, 4);
|
||||
|
||||
QCOMPARE(usage.no, 4);
|
||||
QCOMPARE(usage.total(), 4);
|
||||
}
|
||||
|
||||
// A changeover is one contact of its own kind, never one NO plus
|
||||
// one NC. CrossRefItem::NOElements() and NCElements() both return
|
||||
// changeovers, so a count built by adding those two lists would
|
||||
// report a single changeover as two contacts.
|
||||
void changeoverIsCountedOnce()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::SW, 1);
|
||||
|
||||
QCOMPARE(usage.sw, 1);
|
||||
QCOMPARE(usage.no, 0);
|
||||
QCOMPARE(usage.nc, 0);
|
||||
QCOMPARE(usage.total(), 1);
|
||||
}
|
||||
|
||||
// An element which declares no contact count, or a nonsensical one,
|
||||
// is still a contact.
|
||||
void missingContactCountIsOneContact_data()
|
||||
{
|
||||
QTest::addColumn<int>("declared");
|
||||
|
||||
QTest::newRow("zero") << 0;
|
||||
QTest::newRow("negative") << -1;
|
||||
}
|
||||
|
||||
void missingContactCountIsOneContact()
|
||||
{
|
||||
QFETCH(int, declared);
|
||||
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, declared);
|
||||
|
||||
QCOMPARE(usage.no, 1);
|
||||
QCOMPARE(usage.total(), 1);
|
||||
}
|
||||
|
||||
// A declared capacity is summed across groups, so two NO groups of two
|
||||
// contacts each declare four NO contacts, not two groups.
|
||||
void capacitySumsAcrossGroups()
|
||||
{
|
||||
ContactUsage capacity;
|
||||
capacity.addSlave(ContactUsage::NO, 2);
|
||||
capacity.addSlave(ContactUsage::NO, 2);
|
||||
capacity.addSlave(ContactUsage::NC, 1);
|
||||
|
||||
QCOMPARE(capacity.no, 4);
|
||||
QCOMPARE(capacity.nc, 1);
|
||||
QCOMPARE(capacity.total(), 5);
|
||||
}
|
||||
|
||||
// The mix a coil would actually carry: two single NO, one 4 pole NO,
|
||||
// one NC and one changeover.
|
||||
void tallysARealisticMix()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NO, 4);
|
||||
usage.addSlave(ContactUsage::NC, 1);
|
||||
usage.addSlave(ContactUsage::SW, 1);
|
||||
|
||||
QCOMPARE(usage.no, 6);
|
||||
QCOMPARE(usage.nc, 1);
|
||||
QCOMPARE(usage.sw, 1);
|
||||
QCOMPARE(usage.total(), 8);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_contactusage)
|
||||
#include "tst_contactusage.moc"
|
||||
Reference in New Issue
Block a user