mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-27 20:44:13 +02:00
Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1331ed6677 | |||
| ae400d4da6 | |||
| 7fdd4417a4 | |||
| 6637127910 | |||
| 38d7ecd67a | |||
| 27b60ec30e | |||
| 4bd5a6e5ac | |||
| 58cf62c782 | |||
| 83bb7e14e0 | |||
| 239065af7d | |||
| adcf11270c | |||
| 539708da3c | |||
| f62a44827b | |||
| b350a2dbaa | |||
| e1152ba570 | |||
| 8184ee1a18 | |||
| d56e99702c | |||
| 08325061fd | |||
| e9610d3f96 | |||
| d5027eadbb | |||
| da09206826 | |||
| 9812166071 | |||
| a399923575 | |||
| 1d980b164f | |||
| 20a3e4f636 | |||
| 4278ef864e | |||
| f98d5d00e9 | |||
| 166ee34f52 | |||
| 4efe28ce03 | |||
| 7c4355338b | |||
| f7591e0c9d | |||
| 4c3e86c07e | |||
| 44cabbcfe4 | |||
| a2a30ef7c4 | |||
| 0b4f480dfe | |||
| 05260de87b | |||
| db154a8d06 | |||
| 7dc1d345b0 | |||
| ba1811a0cf | |||
| a3dc8d3ad4 | |||
| b1fe1ef7a7 | |||
| 9584fc4b7c | |||
| 25baf48153 | |||
| 7f51a3e10a | |||
| 503823bfce | |||
| 30c97612a2 | |||
| 8cdf51405a | |||
| 4a5755e552 | |||
| ef8b3866ba | |||
| f6f53e3f52 | |||
| 5120feb417 | |||
| cd38f6f2a2 | |||
| 29fe731728 | |||
| b740508b7f | |||
| 76eea09192 | |||
| 30e83d8c52 | |||
| 3a50cd9b0e | |||
| acb770909e | |||
| a9ad6e2f34 | |||
| ff795258c4 | |||
| 0cb424bd06 | |||
| 47e5a8072b | |||
| 5bd55801e9 | |||
| 5ccb089cdd | |||
| 416ababc41 | |||
| 63771a41b5 | |||
| 57905a5789 | |||
| 451cccd615 | |||
| af28a49b72 | |||
| 926bbcf75f | |||
| 6bcdb87524 | |||
| f850d0bee8 | |||
| d7036ad807 | |||
| 09fb81b99a | |||
| 501379def2 | |||
| 52a831e1fa | |||
| c1bb3b33f4 | |||
| f91ec74dde | |||
| b08d0692b7 | |||
| 627b818a30 | |||
| 0d2befbc00 | |||
| db387525e2 | |||
| 6763103d57 | |||
| 852c32fc07 | |||
| e82548bee9 | |||
| 60ab39b0f6 | |||
| b57c37ca62 | |||
| 329831d986 | |||
| b80d6e7cd3 | |||
| 6d42556d9f | |||
| 2da101ca90 | |||
| 07501d604e | |||
| 73ad43bd8f | |||
| c1d9e60656 | |||
| 92cbc8fd2a | |||
| db56262924 | |||
| 86cb7e430e | |||
| 3d260edcdb | |||
| 53706a80d8 | |||
| 1e74bcd9de | |||
| 45aec7735b | |||
| 923367d2a8 | |||
| 6dd260a7b2 | |||
| 132c564aef | |||
| e9c4969af7 | |||
| 040c4b5e29 | |||
| 71d708e2da | |||
| b490720652 | |||
| 519f2245f6 | |||
| b61193f66f | |||
| e6291eb8e8 | |||
| 0ca4b9f6c3 | |||
| a08cdf7272 | |||
| 2fdb225936 | |||
| 95f3ea2f6d | |||
| 0e02190eb2 | |||
| 2807771200 | |||
| 4cffdf93b6 | |||
| 9738345b45 | |||
| 286002fe25 | |||
| ce0e7e6867 | |||
| 5f0b015186 | |||
| e5738c5b7d | |||
| c3c264cd78 | |||
| d392ab4b8f | |||
| ebd4a57563 | |||
| 23d83ec153 | |||
| d43c535cc9 | |||
| 71782aec20 | |||
| 59dc59cf2f | |||
| 469c8cbc70 | |||
| 1330dbb435 | |||
| e7f19de2da | |||
| 4c9a307cb9 | |||
| 4381c6caa3 | |||
| 07d7aedc26 | |||
| 5622308ba2 | |||
| 149ae961cc | |||
| ac40d110fb | |||
| 4f309770f0 | |||
| 72cf86ece0 | |||
| 8aefdcc481 | |||
| 3a45625d69 | |||
| 988357057f | |||
| e9676ef87e | |||
| ca507dc0f3 | |||
| 1932150ea6 | |||
| 23241fba69 | |||
| f2f1051914 | |||
| 313aafc95b | |||
| aaeaff55cc | |||
| bb037ee3ff | |||
| 17ebbffbca | |||
| 04e2279a7f | |||
| 9987f05835 | |||
| a94f30b256 | |||
| 73075a5154 | |||
| 9875de85d7 | |||
| 48883b320e | |||
| d1256872c2 | |||
| b93d8d8baa | |||
| ae41dd7686 | |||
| 800189debb | |||
| 522e008c26 | |||
| 7fbbb71aac | |||
| 232724bf82 | |||
| 12f826e969 | |||
| 245aafb0d1 | |||
| 0d231ef548 | |||
| 3513f692fd | |||
| 5a27f3cf80 | |||
| d3a9adbe44 | |||
| 8ab4e24b61 | |||
| 2d2bf5ca2b | |||
| 2f6ab8f63b | |||
| 2266497b0b | |||
| dcb79b6553 | |||
| 745b4a936c | |||
| 7ac3719c37 | |||
| ddfa234943 | |||
| 9af562fcbd | |||
| c85276f61b | |||
| c1cc9a5b98 | |||
| f3a31c291f | |||
| a06220e8dc | |||
| 79990e40ee | |||
| e1e7ea4888 | |||
| 54b8fe5df4 | |||
| be0d214b75 | |||
| 2f988e6e2c | |||
| 9786006ca4 | |||
| 91e8956734 | |||
| 6fad3390df | |||
| 231ab990c3 | |||
| f10275cb22 | |||
| 3d0e7a27a3 | |||
| f14278324c | |||
| d5318eb7b4 | |||
| f10ef3ded7 | |||
| 6f7ea63589 | |||
| 9a6029a2bb | |||
| bc0ca56c7e | |||
| 1d4c0326d4 | |||
| 3457a88e81 | |||
| 4e361f93fe | |||
| 6a8838e719 | |||
| 774f803626 | |||
| 5b0785fcce | |||
| de9b3eae06 | |||
| 032f2249a3 | |||
| 1890cee801 | |||
| 3eea3059bf | |||
| bf7f49f334 | |||
| 2ec413a578 | |||
| 61ce75e380 | |||
| 83b9f32bd0 | |||
| e35cab33f0 | |||
| 2e4bb486bb | |||
| 4e754cfac1 | |||
| 151b16ba76 | |||
| 0902725a59 | |||
| 280051745a | |||
| e3ede85efd | |||
| 95c2f6cf6d |
@@ -28,7 +28,7 @@ jobs:
|
|||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
ca-certificates build-essential cmake ninja-build git pkg-config \
|
ca-certificates build-essential cmake ninja-build git pkg-config \
|
||||||
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||||
libqt6svg6-dev libqt6sql6-sqlite libcups2-dev \
|
libqt6svg6-dev libqt6sql6-sqlite libcups2-dev libsqlite3-dev \
|
||||||
libxkbcommon-x11-0 \
|
libxkbcommon-x11-0 \
|
||||||
xvfb openbox xdotool x11-utils
|
xvfb openbox xdotool x11-utils
|
||||||
# extra-cmake-modules and the KF6 libraries are installed rather than
|
# extra-cmake-modules and the KF6 libraries are installed rather than
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ jobs:
|
|||||||
mingw-w64-ucrt-x86_64-qt6-tools
|
mingw-w64-ucrt-x86_64-qt6-tools
|
||||||
mingw-w64-ucrt-x86_64-qt6-translations
|
mingw-w64-ucrt-x86_64-qt6-translations
|
||||||
mingw-w64-ucrt-x86_64-qt6-pdf
|
mingw-w64-ucrt-x86_64-qt6-pdf
|
||||||
|
mingw-w64-ucrt-x86_64-sqlite3
|
||||||
mingw-w64-ucrt-x86_64-pkg-config
|
mingw-w64-ucrt-x86_64-pkg-config
|
||||||
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
||||||
mingw-w64-ucrt-x86_64-kcoreaddons
|
mingw-w64-ucrt-x86_64-kcoreaddons
|
||||||
@@ -56,6 +57,7 @@ jobs:
|
|||||||
mingw-w64-ucrt-x86_64-nsis
|
mingw-w64-ucrt-x86_64-nsis
|
||||||
mingw-w64-ucrt-x86_64-angleproject
|
mingw-w64-ucrt-x86_64-angleproject
|
||||||
mingw-w64-ucrt-x86_64-qt6-declarative
|
mingw-w64-ucrt-x86_64-qt6-declarative
|
||||||
|
mingw-w64-ucrt-x86_64-hidapi
|
||||||
|
|
||||||
- name: Cache ccache
|
- name: Cache ccache
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
@@ -119,9 +121,12 @@ jobs:
|
|||||||
-DCMAKE_POLICY_DEFAULT_CMP0077=NEW \
|
-DCMAKE_POLICY_DEFAULT_CMP0077=NEW \
|
||||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||||
-DQET_EXPORT_PROJECT_DB=ON \
|
-DQET_EXPORT_PROJECT_DB=ON \
|
||||||
|
-DQET_ENABLE_SPACEMOUSE=ON \
|
||||||
|
-DQET_SPACEMOUSE_BACKEND=hid \
|
||||||
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||||
|
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
|
||||||
|
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
|
||||||
..
|
..
|
||||||
ninja -j"$NPROC"
|
ninja -j"$NPROC"
|
||||||
|
|
||||||
@@ -144,6 +149,11 @@ jobs:
|
|||||||
SIZE=$(stat -c%s "$EXE")
|
SIZE=$(stat -c%s "$EXE")
|
||||||
echo "Exe found: $EXE ($SIZE bytes)"
|
echo "Exe found: $EXE ($SIZE bytes)"
|
||||||
[ "$SIZE" -gt 100000 ] || { echo "ERROR: exe too small"; exit 1; }
|
[ "$SIZE" -gt 100000 ] || { echo "ERROR: exe too small"; exit 1; }
|
||||||
|
# 3D mouse support (discussion #599): CMake only warns when hidapi
|
||||||
|
# is missing, which would ship a package without it -- fail here.
|
||||||
|
ldd "$EXE" | grep -qi 'libhidapi' \
|
||||||
|
|| { echo "ERROR: exe not linked against hidapi -- 3D mouse support missing"; exit 1; }
|
||||||
|
echo "3D mouse support: linked against $(ldd "$EXE" | grep -io 'libhidapi[^ ]*' | head -1)"
|
||||||
|
|
||||||
- name: Deploy — copy exe + windeployqt + DLLs
|
- name: Deploy — copy exe + windeployqt + DLLs
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
@@ -189,11 +199,20 @@ jobs:
|
|||||||
echo "=== $DLL_COUNT DLLs present after scan ==="
|
echo "=== $DLL_COUNT DLLs present after scan ==="
|
||||||
ls -lh "$BIN/QElectroTech.exe" || { echo "ERROR: exe missing from bin/"; exit 1; }
|
ls -lh "$BIN/QElectroTech.exe" || { echo "ERROR: exe missing from bin/"; exit 1; }
|
||||||
[ "$DLL_COUNT" -gt 5 ] || { echo "ERROR: too few DLLs"; exit 1; }
|
[ "$DLL_COUNT" -gt 5 ] || { echo "ERROR: too few DLLs"; exit 1; }
|
||||||
|
# Without it QElectroTech.exe would not start at all.
|
||||||
|
[ -f "$BIN/libhidapi-0.dll" ] || { echo "ERROR: libhidapi-0.dll not deployed"; exit 1; }
|
||||||
cd "$GITHUB_WORKSPACE"
|
cd "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
|
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
|
||||||
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
|
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
|
||||||
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
|
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
|
||||||
|
SQLITE=$(find /ucrt64/bin -name "libsqlite3*.dll" | head -1)
|
||||||
|
if [ -n "$SQLITE" ]; then
|
||||||
|
cp "$SQLITE" "$BIN/"
|
||||||
|
echo "SQLite3 copied: $(basename $SQLITE)"
|
||||||
|
else
|
||||||
|
echo "WARNING: libsqlite3 not found in /ucrt64/bin/"
|
||||||
|
fi
|
||||||
|
|
||||||
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
|
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
|
||||||
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
|
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
|
||||||
|
|||||||
+1
-9
@@ -9,12 +9,4 @@ doc/*
|
|||||||
!doc/QElectroTech.qch
|
!doc/QElectroTech.qch
|
||||||
QElectroTech.tag
|
QElectroTech.tag
|
||||||
!doc/doc-utils
|
!doc/doc-utils
|
||||||
lang/*.qm
|
ui_*.h
|
||||||
|
|
||||||
# -- IDE settings/intermediate files --
|
|
||||||
# zed
|
|
||||||
.zed
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# VS Code
|
|
||||||
.vscode
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ include(cmake/git_update_submodules.cmake)
|
|||||||
include(cmake/git_last_commit_sha.cmake)
|
include(cmake/git_last_commit_sha.cmake)
|
||||||
include(cmake/fetch_singleapplication.cmake)
|
include(cmake/fetch_singleapplication.cmake)
|
||||||
include(cmake/fetch_pugixml.cmake)
|
include(cmake/fetch_pugixml.cmake)
|
||||||
|
include(cmake/find_spacemouse.cmake)
|
||||||
include(cmake/qet_compilation_vars.cmake)
|
include(cmake/qet_compilation_vars.cmake)
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
@@ -169,6 +170,13 @@ else()
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED)
|
||||||
|
list(APPEND QET_SPACEMOUSE_LIBRARIES PkgConfig::SPNAV)
|
||||||
|
endif()
|
||||||
|
if(QET_SPACEMOUSE_BACKEND_HID_ENABLED)
|
||||||
|
list(APPEND QET_SPACEMOUSE_LIBRARIES PkgConfig::HIDAPI)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
# CFBundleIdentifier must not be empty. CMake's default Info.plist
|
# CFBundleIdentifier must not be empty. CMake's default Info.plist
|
||||||
# template fills it from MACOSX_BUNDLE_GUI_IDENTIFIER; with that unset
|
# template fills it from MACOSX_BUNDLE_GUI_IDENTIFIER; with that unset
|
||||||
@@ -287,6 +295,7 @@ target_link_libraries(
|
|||||||
SQLite3::SQLite3
|
SQLite3::SQLite3
|
||||||
${KF_PRIVATE_LIBRARIES}
|
${KF_PRIVATE_LIBRARIES}
|
||||||
${QET_PRIVATE_LIBRARIES}
|
${QET_PRIVATE_LIBRARIES}
|
||||||
|
${QET_SPACEMOUSE_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
|
|||||||
+21
-7
@@ -27,12 +27,18 @@ git submodule update --init --recursive
|
|||||||
| C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) |
|
| C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) |
|
||||||
| Qt6 base + widgets | required | |
|
| Qt6 base + widgets | required | |
|
||||||
| Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below |
|
| Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below |
|
||||||
|
| SQLite3 | required | used by the nomenclature/summary database |
|
||||||
| Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build |
|
| Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build |
|
||||||
| pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below |
|
| pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below |
|
||||||
| Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed |
|
| Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed |
|
||||||
| KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) |
|
| KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) |
|
||||||
| QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) |
|
| QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) |
|
||||||
|
|
||||||
|
A note on CMake versions: the project declares a minimum of 3.5 but is
|
||||||
|
routinely built with much newer releases; if your CMake is older than 4.3 it
|
||||||
|
simply won't have the newer `SQLite3::SQLite3` target name, which the build
|
||||||
|
script compensates for automatically. There is nothing you need to do either
|
||||||
|
way.
|
||||||
|
|
||||||
## 3. Building (out-of-source build)
|
## 3. Building (out-of-source build)
|
||||||
|
|
||||||
@@ -100,7 +106,7 @@ the closest match.
|
|||||||
sudo apt install \
|
sudo apt install \
|
||||||
build-essential cmake ninja-build git \
|
build-essential cmake ninja-build git \
|
||||||
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||||
|
libsqlite3-dev \
|
||||||
libkf6coreaddons-dev libkf6widgetsaddons-dev
|
libkf6coreaddons-dev libkf6widgetsaddons-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -132,6 +138,7 @@ sudo apt install libpugixml-dev
|
|||||||
sudo dnf install \
|
sudo dnf install \
|
||||||
cmake gcc-c++ git \
|
cmake gcc-c++ git \
|
||||||
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
|
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
|
||||||
|
sqlite-devel \
|
||||||
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
|
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -148,10 +155,11 @@ Optional, for a system pugixml: `sudo dnf install pugixml-devel`.
|
|||||||
pkg install \
|
pkg install \
|
||||||
cmake git \
|
cmake git \
|
||||||
qt6-base qt6-tools \
|
qt6-base qt6-tools \
|
||||||
|
sqlite3 \
|
||||||
kf6-kcoreaddons kf6-kwidgetsaddons
|
kf6-kcoreaddons kf6-kwidgetsaddons
|
||||||
```
|
```
|
||||||
|
|
||||||
(from ports: `devel/qt6-base`, `devel/qt6-tools`,
|
(from ports: `devel/qt6-base`, `devel/qt6-tools`, `databases/sqlite3`,
|
||||||
`devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's
|
`devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's
|
||||||
`GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate
|
`GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate
|
||||||
package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing
|
package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing
|
||||||
@@ -167,7 +175,7 @@ Optional, for a system pugixml: `pkg install pugixml` (`devel/pugixml`).
|
|||||||
Using [Homebrew](https://brew.sh):
|
Using [Homebrew](https://brew.sh):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew install cmake qt ninja
|
brew install cmake qt sqlite ninja
|
||||||
```
|
```
|
||||||
|
|
||||||
Homebrew's `qt` formula is Qt6 and includes the private headers, so no
|
Homebrew's `qt` formula is Qt6 and includes the private headers, so no
|
||||||
@@ -185,7 +193,7 @@ Optional, for a system pugixml: `brew install pugixml`.
|
|||||||
|
|
||||||
See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the
|
See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the
|
||||||
package sources differ enough from the Unix-like platforms above (no system
|
package sources differ enough from the Unix-like platforms above (no system
|
||||||
package manager and Qt aren't provided the same way) that it gets
|
package manager, SQLite3 and Qt aren't provided the same way) that it gets
|
||||||
its own section.
|
its own section.
|
||||||
|
|
||||||
## 5. pugixml
|
## 5. pugixml
|
||||||
@@ -207,7 +215,7 @@ on Debian/Ubuntu, `pugixml-devel` on Fedora).
|
|||||||
Both toolchains QET's CMake build targets on Windows are covered here:
|
Both toolchains QET's CMake build targets on Windows are covered here:
|
||||||
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
|
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
|
||||||
Unix-like platforms above, there's no single system package manager, so
|
Unix-like platforms above, there's no single system package manager, so
|
||||||
Qt and (optionally) KDE Frameworks each need to be sourced
|
Qt, SQLite3 and (optionally) KDE Frameworks each need to be sourced
|
||||||
separately per toolchain.
|
separately per toolchain.
|
||||||
|
|
||||||
One piece of good news either way: unlike Debian/Fedora, the official Qt
|
One piece of good news either way: unlike Debian/Fedora, the official Qt
|
||||||
@@ -228,7 +236,11 @@ normally use `-DBUILD_WITH_KF=OFF` (see the
|
|||||||
1. Install Visual Studio with the "Desktop development with C++" workload,
|
1. Install Visual Studio with the "Desktop development with C++" workload,
|
||||||
and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit)
|
and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit)
|
||||||
via the [Qt Online Installer](https://www.qt.io/download-qt-installer).
|
via the [Qt Online Installer](https://www.qt.io/download-qt-installer).
|
||||||
2. Configure and build from an "x64 Native Tools Command Prompt for VS":
|
2. Get SQLite3 — the simplest route is [vcpkg](https://vcpkg.io):
|
||||||
|
```bat
|
||||||
|
vcpkg install sqlite3:x64-windows
|
||||||
|
```
|
||||||
|
3. Configure and build from an "x64 Native Tools Command Prompt for VS":
|
||||||
```bat
|
```bat
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. -G "Visual Studio 17 2022" -A x64 ^
|
cmake .. -G "Visual Studio 17 2022" -A x64 ^
|
||||||
@@ -281,7 +293,9 @@ Using the Qt Online Installer's bundled MinGW kit instead: point
|
|||||||
`CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure
|
`CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure
|
||||||
its bundled `g++.exe` comes first on `PATH`, or pass
|
its bundled `g++.exe` comes first on `PATH`, or pass
|
||||||
`-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick
|
`-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick
|
||||||
up a different MinGW installation.
|
up a different MinGW installation. SQLite3 still has to come from elsewhere
|
||||||
|
in this path — vcpkg with a `mingw`-flavoured triplet, or MSYS2's package as
|
||||||
|
above.
|
||||||
|
|
||||||
## 7. Qt6 private headers (mandatory)
|
## 7. Qt6 private headers (mandatory)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"finish-args": [
|
"finish-args": [
|
||||||
"--socket=wayland",
|
"--socket=wayland",
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--device=dri",
|
"--device=all",
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--share=network",
|
"--share=network",
|
||||||
"--socket=cups",
|
"--socket=cups",
|
||||||
@@ -33,6 +33,23 @@
|
|||||||
"modules": [
|
"modules": [
|
||||||
"tkinter.json",
|
"tkinter.json",
|
||||||
"pypi-dependencies.json",
|
"pypi-dependencies.json",
|
||||||
|
{
|
||||||
|
"name": "hidapi",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"config-opts": [
|
||||||
|
"-DCMAKE_INSTALL_PREFIX=/app",
|
||||||
|
"-DCMAKE_BUILD_TYPE=Release",
|
||||||
|
"-DHIDAPI_BUILD_HIDTEST=OFF",
|
||||||
|
"-DHIDAPI_WITH_TESTS=OFF"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/libusb/hidapi.git",
|
||||||
|
"tag": "hidapi-0.15.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "qelectrotech",
|
"name": "qelectrotech",
|
||||||
"buildsystem": "cmake",
|
"buildsystem": "cmake",
|
||||||
@@ -45,7 +62,9 @@
|
|||||||
"-DBUILD_PUGIXML=ON",
|
"-DBUILD_PUGIXML=ON",
|
||||||
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON",
|
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON",
|
||||||
"-DFETCHCONTENT_SOURCE_DIR_PUGIXML=/run/build/qelectrotech/pugixml",
|
"-DFETCHCONTENT_SOURCE_DIR_PUGIXML=/run/build/qelectrotech/pugixml",
|
||||||
"-DQET_EXPORT_PROJECT_DB=ON"
|
"-DQET_EXPORT_PROJECT_DB=ON",
|
||||||
|
"-DQET_ENABLE_SPACEMOUSE=ON",
|
||||||
|
"-DQET_SPACEMOUSE_BACKEND=hid"
|
||||||
],
|
],
|
||||||
"post-install": [
|
"post-install": [
|
||||||
"mv ${FLATPAK_DEST}/share/mime/packages/qelectrotech.xml ${FLATPAK_DEST}/share/mime/packages/org.qelectrotech.QElectroTech.xml"
|
"mv ${FLATPAK_DEST}/share/mime/packages/qelectrotech.xml ${FLATPAK_DEST}/share/mime/packages/org.qelectrotech.QElectroTech.xml"
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ parts:
|
|||||||
- python3-lxml
|
- python3-lxml
|
||||||
- python3-tk
|
- python3-tk
|
||||||
- libtk8.6
|
- libtk8.6
|
||||||
|
- libspnav0
|
||||||
|
|
||||||
kde-sdk-setup:
|
kde-sdk-setup:
|
||||||
plugin: nil
|
plugin: nil
|
||||||
@@ -71,6 +72,7 @@ parts:
|
|||||||
source: .
|
source: .
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- git
|
- git
|
||||||
|
- sqlite3
|
||||||
- xdg-user-dirs
|
- xdg-user-dirs
|
||||||
- libqt6qml6
|
- libqt6qml6
|
||||||
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
||||||
@@ -81,7 +83,7 @@ parts:
|
|||||||
- git
|
- git
|
||||||
- cmake
|
- cmake
|
||||||
- ninja-build
|
- ninja-build
|
||||||
|
- libsqlite3-dev
|
||||||
- qt6-tools-dev
|
- qt6-tools-dev
|
||||||
- qt6-base-private-dev
|
- qt6-base-private-dev
|
||||||
- qt6-declarative-dev
|
- qt6-declarative-dev
|
||||||
@@ -91,6 +93,7 @@ parts:
|
|||||||
# runs FindCups at configure time and fails without the CUPS headers.
|
# runs FindCups at configure time and fails without the CUPS headers.
|
||||||
# Build-time only: nothing from it is staged into the snap.
|
# Build-time only: nothing from it is staged into the snap.
|
||||||
- libcups2-dev
|
- libcups2-dev
|
||||||
|
- libspnav-dev
|
||||||
override-build: |
|
override-build: |
|
||||||
displayed_version=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
displayed_version=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
||||||
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
|
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
|
||||||
@@ -105,7 +108,8 @@ parts:
|
|||||||
-DBUILD_KF=OFF \
|
-DBUILD_KF=OFF \
|
||||||
-DPACKAGE_TESTS=OFF \
|
-DPACKAGE_TESTS=OFF \
|
||||||
-DBUILD_PUGIXML=ON \
|
-DBUILD_PUGIXML=ON \
|
||||||
-DQET_EXPORT_PROJECT_DB=ON
|
-DQET_EXPORT_PROJECT_DB=ON \
|
||||||
|
-DQET_ENABLE_SPACEMOUSE=ON
|
||||||
ninja -C build -j${CRAFT_PARALLEL_BUILD_COUNT}
|
ninja -C build -j${CRAFT_PARALLEL_BUILD_COUNT}
|
||||||
DESTDIR="$CRAFT_PART_INSTALL" ninja -C build install
|
DESTDIR="$CRAFT_PART_INSTALL" ninja -C build install
|
||||||
override-stage: |
|
override-stage: |
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ add_definitions(-DQT_MESSAGELOGCONTEXT)
|
|||||||
# In order to do so, uncomment the following line.
|
# In order to do so, uncomment the following line.
|
||||||
#add_definitions(-DTODO_LIST)
|
#add_definitions(-DTODO_LIST)
|
||||||
|
|
||||||
# Build with KDE Frameworks.
|
# Build with KDE Frameworks.
|
||||||
option(BUILD_WITH_KF "Build with KDE Frameworks" ON)
|
option(BUILD_WITH_KF "Build with KDE Frameworks" ON)
|
||||||
|
|
||||||
# Precompiled headers for the Qt umbrella headers.
|
# Precompiled headers for the Qt umbrella headers.
|
||||||
@@ -47,3 +47,8 @@ option(BUILD_WITH_KF "Build with KDE Frameworks" ON)
|
|||||||
# compile for everyone else. Leaving it off keeps CI and contributors on the
|
# compile for everyone else. Leaving it off keeps CI and contributors on the
|
||||||
# strict behaviour, and only developers who opt in trade that for the speed.
|
# strict behaviour, and only developers who opt in trade that for the speed.
|
||||||
option(QET_ENABLE_PCH "Use precompiled headers (developer build speed; may mask missing #includes)" OFF)
|
option(QET_ENABLE_PCH "Use precompiled headers (developer build speed; may mask missing #includes)" OFF)
|
||||||
|
|
||||||
|
# Discussion #599: 3Dconnexion SpaceMouse/SpacePilot pan/zoom support. Off by
|
||||||
|
# default -- see cmake/find_spacemouse.cmake for the backends and what happens
|
||||||
|
# when it is on but no library is found.
|
||||||
|
option(QET_ENABLE_SPACEMOUSE "Build with 3D mouse (3Dconnexion SpaceMouse) support for pan/zoom; needs libspnav or hidapi, see QET_SPACEMOUSE_BACKEND" OFF)
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Copyright 2006 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/>.
|
||||||
|
|
||||||
|
message(" - find_spacemouse")
|
||||||
|
|
||||||
|
# QET_ENABLE_SPACEMOUSE (cmake/developer_options.cmake) is off by default,
|
||||||
|
# so none of this runs, and the default build is entirely unaffected: no new
|
||||||
|
# dependency, no new source files, no new symbols.
|
||||||
|
#
|
||||||
|
# When it is on, QET_SPACEMOUSE_BACKEND picks how the device is read -- see
|
||||||
|
# sources/spacemouse/spacemousebackend.h for what a backend is:
|
||||||
|
# spnav Linux, through the spacenavd daemon (libspnav)
|
||||||
|
# hid any platform, directly over USB (hidapi), no 3Dconnexion driver
|
||||||
|
# auto spnav on Linux when libspnav is found, hid otherwise
|
||||||
|
# On macOS the 3DxWare backend is added to whichever of these is chosen.
|
||||||
|
# A backend whose library is not found downgrades the option to off with a
|
||||||
|
# warning, rather than failing configure for an opt-in feature.
|
||||||
|
set(QET_SPACEMOUSE_BACKEND "auto" CACHE STRING "3D mouse backend: auto, spnav or hid")
|
||||||
|
set_property(CACHE QET_SPACEMOUSE_BACKEND PROPERTY STRINGS auto spnav hid)
|
||||||
|
|
||||||
|
set(QET_SPACEMOUSE_ENABLED FALSE)
|
||||||
|
set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED FALSE)
|
||||||
|
set(QET_SPACEMOUSE_BACKEND_HID_ENABLED FALSE)
|
||||||
|
set(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED FALSE)
|
||||||
|
|
||||||
|
if(QET_ENABLE_SPACEMOUSE)
|
||||||
|
find_package(PkgConfig)
|
||||||
|
|
||||||
|
set(_qet_spacemouse_try_spnav FALSE)
|
||||||
|
set(_qet_spacemouse_try_hid FALSE)
|
||||||
|
if(QET_SPACEMOUSE_BACKEND STREQUAL "spnav")
|
||||||
|
set(_qet_spacemouse_try_spnav TRUE)
|
||||||
|
elseif(QET_SPACEMOUSE_BACKEND STREQUAL "hid")
|
||||||
|
set(_qet_spacemouse_try_hid TRUE)
|
||||||
|
else()
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
set(_qet_spacemouse_try_spnav TRUE)
|
||||||
|
endif()
|
||||||
|
set(_qet_spacemouse_try_hid TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# libspnav: Debian/Ubuntu's libspnav-dev ships spnav.pc.
|
||||||
|
if(_qet_spacemouse_try_spnav AND PkgConfig_FOUND)
|
||||||
|
pkg_check_modules(SPNAV IMPORTED_TARGET spnav)
|
||||||
|
if(SPNAV_FOUND)
|
||||||
|
set(QET_SPACEMOUSE_ENABLED TRUE)
|
||||||
|
set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED TRUE)
|
||||||
|
add_definitions(-DQET_SPACEMOUSE_BACKEND_SPNAV)
|
||||||
|
message("QET_ENABLE_SPACEMOUSE ON (backend: libspnav ${SPNAV_VERSION})")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# hidapi: hidapi-hidraw.pc on Linux (libhidapi-dev), hidapi.pc from
|
||||||
|
# MSYS2 (mingw-w64-ucrt-x86_64-hidapi) and Homebrew (hidapi).
|
||||||
|
if(NOT QET_SPACEMOUSE_ENABLED AND _qet_spacemouse_try_hid AND PkgConfig_FOUND)
|
||||||
|
pkg_search_module(HIDAPI IMPORTED_TARGET hidapi-hidraw hidapi)
|
||||||
|
if(HIDAPI_FOUND)
|
||||||
|
set(QET_SPACEMOUSE_ENABLED TRUE)
|
||||||
|
set(QET_SPACEMOUSE_BACKEND_HID_ENABLED TRUE)
|
||||||
|
add_definitions(-DQET_SPACEMOUSE_BACKEND_HID)
|
||||||
|
message("QET_ENABLE_SPACEMOUSE ON (backend: hidapi ${HIDAPI_VERSION})")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# macOS: with 3DxWare installed the device can only be read through
|
||||||
|
# 3DxWare, so its backend comes too, whichever backend was asked for.
|
||||||
|
# It loads 3DxWare's library at run time and needs nothing to build.
|
||||||
|
if(APPLE)
|
||||||
|
set(QET_SPACEMOUSE_ENABLED TRUE)
|
||||||
|
set(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED TRUE)
|
||||||
|
add_definitions(-DQET_SPACEMOUSE_BACKEND_CONNEXION)
|
||||||
|
message("QET_ENABLE_SPACEMOUSE ON (backend: 3DxWare when installed)")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(QET_SPACEMOUSE_ENABLED)
|
||||||
|
add_definitions(-DQET_SPACEMOUSE_SUPPORT)
|
||||||
|
else()
|
||||||
|
message(WARNING "QET_ENABLE_SPACEMOUSE is ON but no library was found for the "
|
||||||
|
"'${QET_SPACEMOUSE_BACKEND}' backend (libspnav-dev for spnav, "
|
||||||
|
"hidapi for hid, via pkg-config) -- building WITHOUT 3D mouse support.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
@@ -168,6 +168,9 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/borderproperties.h
|
${QET_DIR}/sources/borderproperties.h
|
||||||
${QET_DIR}/sources/bordertitleblock.cpp
|
${QET_DIR}/sources/bordertitleblock.cpp
|
||||||
${QET_DIR}/sources/bordertitleblock.h
|
${QET_DIR}/sources/bordertitleblock.h
|
||||||
|
${QET_DIR}/sources/bordercelllabels.h
|
||||||
|
${QET_DIR}/sources/cellruler.cpp
|
||||||
|
${QET_DIR}/sources/cellruler.h
|
||||||
${QET_DIR}/sources/conductorautonumerotation.cpp
|
${QET_DIR}/sources/conductorautonumerotation.cpp
|
||||||
${QET_DIR}/sources/conductorautonumerotation.h
|
${QET_DIR}/sources/conductorautonumerotation.h
|
||||||
${QET_DIR}/sources/conductornumexport.cpp
|
${QET_DIR}/sources/conductornumexport.cpp
|
||||||
@@ -189,6 +192,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/configdialog.h
|
${QET_DIR}/sources/configdialog.h
|
||||||
${QET_DIR}/sources/createdxf.cpp
|
${QET_DIR}/sources/createdxf.cpp
|
||||||
${QET_DIR}/sources/createdxf.h
|
${QET_DIR}/sources/createdxf.h
|
||||||
|
${QET_DIR}/sources/dxfexport.cpp
|
||||||
|
${QET_DIR}/sources/dxfexport.h
|
||||||
${QET_DIR}/sources/dxfpaintdevice.cpp
|
${QET_DIR}/sources/dxfpaintdevice.cpp
|
||||||
${QET_DIR}/sources/dxfpaintdevice.h
|
${QET_DIR}/sources/dxfpaintdevice.h
|
||||||
${QET_DIR}/sources/diagramcommands.cpp
|
${QET_DIR}/sources/diagramcommands.cpp
|
||||||
@@ -229,6 +234,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/exportpropertieswidget.h
|
${QET_DIR}/sources/exportpropertieswidget.h
|
||||||
${QET_DIR}/sources/genericpanel.cpp
|
${QET_DIR}/sources/genericpanel.cpp
|
||||||
${QET_DIR}/sources/genericpanel.h
|
${QET_DIR}/sources/genericpanel.h
|
||||||
|
${QET_DIR}/sources/itemgroups.cpp
|
||||||
|
${QET_DIR}/sources/itemgroups.h
|
||||||
${QET_DIR}/sources/lastusedstyle.cpp
|
${QET_DIR}/sources/lastusedstyle.cpp
|
||||||
${QET_DIR}/sources/lastusedstyle.h
|
${QET_DIR}/sources/lastusedstyle.h
|
||||||
${QET_DIR}/sources/machine_info.cpp
|
${QET_DIR}/sources/machine_info.cpp
|
||||||
@@ -282,6 +289,14 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/recentfiles.h
|
${QET_DIR}/sources/recentfiles.h
|
||||||
${QET_DIR}/sources/shortcutmanager.cpp
|
${QET_DIR}/sources/shortcutmanager.cpp
|
||||||
${QET_DIR}/sources/shortcutmanager.h
|
${QET_DIR}/sources/shortcutmanager.h
|
||||||
|
${QET_DIR}/sources/shortcutbarsettings.cpp
|
||||||
|
${QET_DIR}/sources/shortcutbarsettings.h
|
||||||
|
${QET_DIR}/sources/diagramcontexttoolbar.cpp
|
||||||
|
${QET_DIR}/sources/diagramcontexttoolbar.h
|
||||||
|
${QET_DIR}/sources/diagramgestureoverlay.cpp
|
||||||
|
${QET_DIR}/sources/diagramgestureoverlay.h
|
||||||
|
${QET_DIR}/sources/commandsearchpopup.cpp
|
||||||
|
${QET_DIR}/sources/commandsearchpopup.h
|
||||||
${QET_DIR}/sources/titleblockcell.cpp
|
${QET_DIR}/sources/titleblockcell.cpp
|
||||||
${QET_DIR}/sources/titleblockcell.h
|
${QET_DIR}/sources/titleblockcell.h
|
||||||
${QET_DIR}/sources/titleblockproperties.cpp
|
${QET_DIR}/sources/titleblockproperties.cpp
|
||||||
@@ -297,6 +312,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/autoNum/numerotationcontextcommands.h
|
${QET_DIR}/sources/autoNum/numerotationcontextcommands.h
|
||||||
${QET_DIR}/sources/autoNum/numerotationcontext.cpp
|
${QET_DIR}/sources/autoNum/numerotationcontext.cpp
|
||||||
${QET_DIR}/sources/autoNum/numerotationcontext.h
|
${QET_DIR}/sources/autoNum/numerotationcontext.h
|
||||||
|
${QET_DIR}/sources/autoNum/renumberelementscommand.cpp
|
||||||
|
${QET_DIR}/sources/autoNum/renumberelementscommand.h
|
||||||
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp
|
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp
|
||||||
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h
|
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h
|
||||||
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp
|
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp
|
||||||
@@ -307,9 +324,12 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/autoNum/ui/formulaautonumberingw.h
|
${QET_DIR}/sources/autoNum/ui/formulaautonumberingw.h
|
||||||
${QET_DIR}/sources/autoNum/ui/numparteditorw.cpp
|
${QET_DIR}/sources/autoNum/ui/numparteditorw.cpp
|
||||||
${QET_DIR}/sources/autoNum/ui/numparteditorw.h
|
${QET_DIR}/sources/autoNum/ui/numparteditorw.h
|
||||||
|
${QET_DIR}/sources/autoNum/ui/renumberelementsdialog.cpp
|
||||||
|
${QET_DIR}/sources/autoNum/ui/renumberelementsdialog.h
|
||||||
${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp
|
${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp
|
||||||
${QET_DIR}/sources/autoNum/ui/selectautonumw.h
|
${QET_DIR}/sources/autoNum/ui/selectautonumw.h
|
||||||
|
|
||||||
|
${QET_DIR}/sources/dataBase/legacyelementtypes.h
|
||||||
${QET_DIR}/sources/dataBase/projectdatabase.cpp
|
${QET_DIR}/sources/dataBase/projectdatabase.cpp
|
||||||
${QET_DIR}/sources/dataBase/projectdatabase.h
|
${QET_DIR}/sources/dataBase/projectdatabase.h
|
||||||
${QET_DIR}/sources/dataBase/sqlreadonly.cpp
|
${QET_DIR}/sources/dataBase/sqlreadonly.cpp
|
||||||
@@ -444,6 +464,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h
|
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
|
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
|
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
|
||||||
|
${QET_DIR}/sources/ElementsCollection/elementpickerpopup.cpp
|
||||||
|
${QET_DIR}/sources/ElementsCollection/elementpickerpopup.h
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
|
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.h
|
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.h
|
||||||
${QET_DIR}/sources/ElementsCollection/elementslocation.cpp
|
${QET_DIR}/sources/ElementsCollection/elementslocation.cpp
|
||||||
@@ -699,6 +721,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ui/borderpropertieswidget.h
|
${QET_DIR}/sources/ui/borderpropertieswidget.h
|
||||||
${QET_DIR}/sources/ui/compositetexteditdialog.cpp
|
${QET_DIR}/sources/ui/compositetexteditdialog.cpp
|
||||||
${QET_DIR}/sources/ui/compositetexteditdialog.h
|
${QET_DIR}/sources/ui/compositetexteditdialog.h
|
||||||
|
${QET_DIR}/sources/ui/conductorpropertieseditorwidget.cpp
|
||||||
|
${QET_DIR}/sources/ui/conductorpropertieseditorwidget.h
|
||||||
${QET_DIR}/sources/ui/contactgroupselectiondialog.cpp
|
${QET_DIR}/sources/ui/contactgroupselectiondialog.cpp
|
||||||
${QET_DIR}/sources/ui/contactgroupselectiondialog.h
|
${QET_DIR}/sources/ui/contactgroupselectiondialog.h
|
||||||
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
|
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
|
||||||
@@ -790,6 +814,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ui/configpage/guidespropertieswidget.h
|
${QET_DIR}/sources/ui/configpage/guidespropertieswidget.h
|
||||||
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.cpp
|
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.cpp
|
||||||
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.h
|
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.h
|
||||||
|
${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.cpp
|
||||||
|
${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.h
|
||||||
|
|
||||||
${QET_DIR}/sources/undocommand/addelementtextcommand.cpp
|
${QET_DIR}/sources/undocommand/addelementtextcommand.cpp
|
||||||
${QET_DIR}/sources/undocommand/addelementtextcommand.h
|
${QET_DIR}/sources/undocommand/addelementtextcommand.h
|
||||||
@@ -815,6 +841,10 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/undocommand/removediagramcommand.h
|
${QET_DIR}/sources/undocommand/removediagramcommand.h
|
||||||
${QET_DIR}/sources/undocommand/setautonumcontextcommand.cpp
|
${QET_DIR}/sources/undocommand/setautonumcontextcommand.cpp
|
||||||
${QET_DIR}/sources/undocommand/setautonumcontextcommand.h
|
${QET_DIR}/sources/undocommand/setautonumcontextcommand.h
|
||||||
|
${QET_DIR}/sources/undocommand/groupitemscommand.cpp
|
||||||
|
${QET_DIR}/sources/undocommand/groupitemscommand.h
|
||||||
|
${QET_DIR}/sources/undocommand/alignselectioncommand.cpp
|
||||||
|
${QET_DIR}/sources/undocommand/alignselectioncommand.h
|
||||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp
|
${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp
|
||||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.h
|
${QET_DIR}/sources/undocommand/rotateselectioncommand.h
|
||||||
${QET_DIR}/sources/undocommand/promoteshapecommand.cpp
|
${QET_DIR}/sources/undocommand/promoteshapecommand.cpp
|
||||||
@@ -871,6 +901,43 @@ list(APPEND QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/scripting/qetscripting.h
|
${QET_DIR}/sources/scripting/qetscripting.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(QET_SPACEMOUSE_ENABLED)
|
||||||
|
list(APPEND QET_SRC_FILES
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousebackend.h
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousebuttonmap.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousebuttonmap.h
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemouselistener.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemouselistener.h
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousemotion.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousemotion.h
|
||||||
|
${QET_DIR}/sources/ui/configpage/spacemouseconfigpage.cpp
|
||||||
|
${QET_DIR}/sources/ui/configpage/spacemouseconfigpage.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED)
|
||||||
|
list(APPEND QET_SRC_FILES
|
||||||
|
${QET_DIR}/sources/spacemouse/spnavbackend.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/spnavbackend.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(QET_SPACEMOUSE_BACKEND_HID_ENABLED)
|
||||||
|
list(APPEND QET_SRC_FILES
|
||||||
|
${QET_DIR}/sources/spacemouse/hidbackend.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/hidbackend.h
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousehid.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/spacemousehid.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED)
|
||||||
|
list(APPEND QET_SRC_FILES
|
||||||
|
${QET_DIR}/sources/spacemouse/connexionbackend.cpp
|
||||||
|
${QET_DIR}/sources/spacemouse/connexionbackend.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(TS_FILES
|
set(TS_FILES
|
||||||
${QET_DIR}/lang/qet_ar.ts
|
${QET_DIR}/lang/qet_ar.ts
|
||||||
${QET_DIR}/lang/qet_ca.ts
|
${QET_DIR}/lang/qet_ca.ts
|
||||||
|
|||||||
+1
-1
Submodule elements updated: 60eacc28a6...08e438a919
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,222 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="fr_FR" language="ca">
|
||||||
|
<phrase>
|
||||||
|
<source>Réf. croisée</source>
|
||||||
|
<target>Ref. creuada</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>armoire</source>
|
||||||
|
<target>armari</target>
|
||||||
|
<definition>cabinet / enclosure</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bobine</source>
|
||||||
|
<target>bobina</target>
|
||||||
|
<definition>coil</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>borne</source>
|
||||||
|
<target>born</target>
|
||||||
|
<definition>terminal - a connection point on a symbol</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bornier</source>
|
||||||
|
<target>bornes</target>
|
||||||
|
<definition>terminal strip</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cable</source>
|
||||||
|
<target>cable</target>
|
||||||
|
<definition>cable</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>calibre</source>
|
||||||
|
<target>calibre</target>
|
||||||
|
<definition>rating - the current rating of a device</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cartouche</source>
|
||||||
|
<target>cartutx</target>
|
||||||
|
<definition>title block - the framed information panel</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>champ de texte</source>
|
||||||
|
<target>camp de text</target>
|
||||||
|
<definition>text field</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>collection</source>
|
||||||
|
<target>col·lecció</target>
|
||||||
|
<definition>collection - the symbol library</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>conducteur</source>
|
||||||
|
<target>conductor</target>
|
||||||
|
<definition>conductor - the drawn wire between terminals</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contact</source>
|
||||||
|
<target>contacte</target>
|
||||||
|
<definition>contact</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contacteur</source>
|
||||||
|
<target>contactor</target>
|
||||||
|
<definition>contactor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>courant</source>
|
||||||
|
<target>corrent</target>
|
||||||
|
<definition>current</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>disjoncteur</source>
|
||||||
|
<target>disjuntor</target>
|
||||||
|
<definition>circuit breaker</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>dossier</source>
|
||||||
|
<target>carpeta</target>
|
||||||
|
<definition>folder</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>element</source>
|
||||||
|
<target>element</target>
|
||||||
|
<definition>element - a symbol placed on a sheet</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>esclave</source>
|
||||||
|
<target>esclau</target>
|
||||||
|
<definition>slave - the contact block belonging to a master</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fil</source>
|
||||||
|
<target>fil</target>
|
||||||
|
<definition>wire</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>folio</source>
|
||||||
|
<target>plec</target>
|
||||||
|
<definition>sheet - one drawing of a set</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fusible</source>
|
||||||
|
<target>fusible</target>
|
||||||
|
<definition>fuse</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>grille</source>
|
||||||
|
<target>graella</target>
|
||||||
|
<definition>grid</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>interrupteur</source>
|
||||||
|
<target>interruptor</target>
|
||||||
|
<definition>switch</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>maitre</source>
|
||||||
|
<target>mestre</target>
|
||||||
|
<definition>master - the element a cross-reference points from</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>modele</source>
|
||||||
|
<target>plantilla</target>
|
||||||
|
<definition>template</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>moteur</source>
|
||||||
|
<target>motor</target>
|
||||||
|
<definition>motor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>neutre</source>
|
||||||
|
<target>neutre</target>
|
||||||
|
<definition>neutral</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>nomenclature</source>
|
||||||
|
<target>nomenclatura</target>
|
||||||
|
<definition>parts list / bill of materials</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>numerotation</source>
|
||||||
|
<target>numeració</target>
|
||||||
|
<definition>numbering - automatic wire and part numbering</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>phase</source>
|
||||||
|
<target>fase</target>
|
||||||
|
<definition>phase</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>potentiel</source>
|
||||||
|
<target>potencial</target>
|
||||||
|
<definition>potential - conductors electrically joined as one node</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>projet</source>
|
||||||
|
<target>projecte</target>
|
||||||
|
<definition>project - the .qet file holding the sheets</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>puissance</source>
|
||||||
|
<target>potència</target>
|
||||||
|
<definition>power</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>relais</source>
|
||||||
|
<target>relé</target>
|
||||||
|
<definition>relay</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>renvoi de folio</source>
|
||||||
|
<target>referència de plec</target>
|
||||||
|
<definition>sheet cross-reference</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>repere</source>
|
||||||
|
<target>referència</target>
|
||||||
|
<definition>designation - the identifying mark on a part</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>report de folio</source>
|
||||||
|
<target>remissió de plec</target>
|
||||||
|
<definition>sheet cross-reference (the arrow symbol)</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>schema</source>
|
||||||
|
<target>esquema</target>
|
||||||
|
<definition>diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>section</source>
|
||||||
|
<target>secció</target>
|
||||||
|
<definition>cross-section - conductor size in mm2</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>sommaire</source>
|
||||||
|
<target>sumari</target>
|
||||||
|
<definition>summary / table of contents</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>tension</source>
|
||||||
|
<target>tensió</target>
|
||||||
|
<definition>voltage</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>terre</source>
|
||||||
|
<target>terra</target>
|
||||||
|
<definition>earth / ground</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>texte</source>
|
||||||
|
<target>text</target>
|
||||||
|
<definition>text</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>transformateur</source>
|
||||||
|
<target>transformador</target>
|
||||||
|
<definition>transformer</definition>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -100,4 +100,8 @@
|
|||||||
<source>traduction</source>
|
<source>traduction</source>
|
||||||
<target>Übersetzung</target>
|
<target>Übersetzung</target>
|
||||||
</phrase>
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier le nom de la borne</source>
|
||||||
|
<target>Klemmenname ändern</target>
|
||||||
|
</phrase>
|
||||||
</QPH>
|
</QPH>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,918 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="fr_FR" language="en">
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : Blue</source>
|
||||||
|
<target>Blue : Blue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : CornflowerBlue</source>
|
||||||
|
<target>Blue : CornflowerBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : DarkBlue</source>
|
||||||
|
<target>Blue : DarkBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : DeepSkyBlue</source>
|
||||||
|
<target>Blue : DeepSkyBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : DodgerBlue</source>
|
||||||
|
<target>Blue : DodgerBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : LightBlue</source>
|
||||||
|
<target>Blue : LightBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : LightSkyBlue</source>
|
||||||
|
<target>Blue : LightSkyBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : LightSteelBlue</source>
|
||||||
|
<target>Blue : LightSteelBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : MediumBlue</source>
|
||||||
|
<target>Blue : MediumBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : MidnightBlue</source>
|
||||||
|
<target>Blue : MidnightBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : Navy</source>
|
||||||
|
<target>Blue : Navy</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : PowderBlue</source>
|
||||||
|
<target>Blue : PowderBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : RoyalBlue</source>
|
||||||
|
<target>Blue : RoyalBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : SkyBlue</source>
|
||||||
|
<target>Blue : SkyBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Blue : SteelBlue</source>
|
||||||
|
<target>Blue : SteelBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Bisque</source>
|
||||||
|
<target>Brown : Bisque</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : BlanchedAlmond</source>
|
||||||
|
<target>Brown : BlanchedAlmond</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Brown</source>
|
||||||
|
<target>Brown : Brown</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Burlywood</source>
|
||||||
|
<target>Brown : Burlywood</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Chocolate</source>
|
||||||
|
<target>Brown : Chocolate</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Cornsilk</source>
|
||||||
|
<target>Brown : Cornsilk</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : DarkGoldenrod</source>
|
||||||
|
<target>Brown : DarkGoldenrod</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Goldenrod</source>
|
||||||
|
<target>Brown : Goldenrod</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Maroon</source>
|
||||||
|
<target>Brown : Maroon</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : NavajoWhite</source>
|
||||||
|
<target>Brown : NavajoWhite</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Peru</source>
|
||||||
|
<target>Brown : Peru</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : RosyBrown</source>
|
||||||
|
<target>Brown : RosyBrown</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : SaddleBrown</source>
|
||||||
|
<target>Brown : SaddleBrown</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : SandyBrown</source>
|
||||||
|
<target>Brown : SandyBrown</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Sienna</source>
|
||||||
|
<target>Brown : Sienna</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Tan</source>
|
||||||
|
<target>Brown : Tan</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Brown : Wheat</source>
|
||||||
|
<target>Brown : Wheat</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : Aqua</source>
|
||||||
|
<target>Cyan : Aqua</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : Aquamarine</source>
|
||||||
|
<target>Cyan : Aquamarine</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : CadetBlue</source>
|
||||||
|
<target>Cyan : CadetBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : Cyan</source>
|
||||||
|
<target>Cyan : Cyan</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : DarkCyan</source>
|
||||||
|
<target>Cyan : DarkCyan</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : DarkTurquoise</source>
|
||||||
|
<target>Cyan : DarkTurquoise</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : LightCyan</source>
|
||||||
|
<target>Cyan : LightCyan</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : LightSeaGreen</source>
|
||||||
|
<target>Cyan : LightSeaGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : MediumTurquoise</source>
|
||||||
|
<target>Cyan : MediumTurquoise</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : PaleTurquoise</source>
|
||||||
|
<target>Cyan : PaleTurquoise</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : Teal</source>
|
||||||
|
<target>Cyan : Teal</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cyan : Turquoise</source>
|
||||||
|
<target>Cyan : Turquoise</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : Black</source>
|
||||||
|
<target>Gray : Black</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : DarkGray</source>
|
||||||
|
<target>Gray : DarkGray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : DarkSlateGray</source>
|
||||||
|
<target>Gray : DarkSlateGray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : DimGray</source>
|
||||||
|
<target>Gray : DimGray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : Gainsboro</source>
|
||||||
|
<target>Gray : Gainsboro</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : Gray</source>
|
||||||
|
<target>Gray : Gray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : LightGray</source>
|
||||||
|
<target>Gray : LightGray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : LightSlateGray</source>
|
||||||
|
<target>Gray : LightSlateGray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : Silver</source>
|
||||||
|
<target>Gray : Silver</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Gray : SlateGray</source>
|
||||||
|
<target>Gray : SlateGray</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : Chartreuse</source>
|
||||||
|
<target>Green : Chartreuse</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : DarkGreen</source>
|
||||||
|
<target>Green : DarkGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : DarkOliveGreen</source>
|
||||||
|
<target>Green : DarkOliveGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : DarkSeaGreen</source>
|
||||||
|
<target>Green : DarkSeaGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : ForestGreen</source>
|
||||||
|
<target>Green : ForestGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : Green</source>
|
||||||
|
<target>Green : Green</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : GreenYellow</source>
|
||||||
|
<target>Green : GreenYellow</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : LawnGreen</source>
|
||||||
|
<target>Green : LawnGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : LightGreen</source>
|
||||||
|
<target>Green : LightGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : Lime</source>
|
||||||
|
<target>Green : Lime</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : LimeGreen</source>
|
||||||
|
<target>Green : LimeGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : MediumAquamarine</source>
|
||||||
|
<target>Green : MediumAquamarine</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : MediumSeaGreen</source>
|
||||||
|
<target>Green : MediumSeaGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : MediumSpringGreen</source>
|
||||||
|
<target>Green : MediumSpringGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : Olive</source>
|
||||||
|
<target>Green : Olive</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : OliveDrab</source>
|
||||||
|
<target>Green : OliveDrab</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : PaleGreen</source>
|
||||||
|
<target>Green : PaleGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : SeaGreen</source>
|
||||||
|
<target>Green : SeaGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : SpringGreen</source>
|
||||||
|
<target>Green : SpringGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Green : YellowGreen</source>
|
||||||
|
<target>Green : YellowGreen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Orange : Coral</source>
|
||||||
|
<target>Orange : Coral</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Orange : DarkOrange</source>
|
||||||
|
<target>Orange : DarkOrange</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Orange : Orange</source>
|
||||||
|
<target>Orange : Orange</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Orange : OrangeRed</source>
|
||||||
|
<target>Orange : OrangeRed</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Orange : Tomato</source>
|
||||||
|
<target>Orange : Tomato</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Pink : DeepPink</source>
|
||||||
|
<target>Pink: DeepPink</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Pink : HotPink</source>
|
||||||
|
<target>Pink: HotPink</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Pink : LightPink</source>
|
||||||
|
<target>Pink: LightPink</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Pink : MediumVioletRed</source>
|
||||||
|
<target>Pink: MediumVioletRed</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Pink : PaleVioletRed</source>
|
||||||
|
<target>Pink: PaleVioletRed</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Pink : Pink</source>
|
||||||
|
<target>Pink: Pink</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : BlueViolet</source>
|
||||||
|
<target>Purple : BlueViolet</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : DarkMagenta</source>
|
||||||
|
<target>Purple : DarkMagenta</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : DarkOrchid</source>
|
||||||
|
<target>Purple : DarkOrchid</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : DarkSlateBlue</source>
|
||||||
|
<target>Purple : DarkSlateBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : DarkViolet</source>
|
||||||
|
<target>Purple : DarkViolet</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Fuchsia</source>
|
||||||
|
<target>Purple : Fuchsia</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Indigo</source>
|
||||||
|
<target>Purple : Indigo</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Lavender</source>
|
||||||
|
<target>Purple : Lavender</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Magenta</source>
|
||||||
|
<target>Purple : Magenta</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : MediumOrchid</source>
|
||||||
|
<target>Purple : MediumOrchid</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : MediumPurple</source>
|
||||||
|
<target>Purple : MediumPurple</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : MediumSlateBlue</source>
|
||||||
|
<target>Purple : MediumSlateBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Orchid</source>
|
||||||
|
<target>Purple : Orchid</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Plum</source>
|
||||||
|
<target>Purple : Plum</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Purple</source>
|
||||||
|
<target>Purple : Purple</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : SlateBlue</source>
|
||||||
|
<target>Purple : SlateBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Thistle</source>
|
||||||
|
<target>Purple : Thistle</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Purple : Violet</source>
|
||||||
|
<target>Purple : Violet</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : Crimson</source>
|
||||||
|
<target>Red : Crimson</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : DarkRed</source>
|
||||||
|
<target>Red : DarkRed</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : DarkSalmon</source>
|
||||||
|
<target>Red: DarkSalmon</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : Firebrick</source>
|
||||||
|
<target>Red : Firebrick</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : IndianRed</source>
|
||||||
|
<target>Red: IndianRed</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : LightCoral</source>
|
||||||
|
<target>Red: LightCoral</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : LightSalmon</source>
|
||||||
|
<target>Red: LightSalmon</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : Red</source>
|
||||||
|
<target>Red : Red</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Red : Salmon</source>
|
||||||
|
<target>Red: Salmon</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : AliceBlue</source>
|
||||||
|
<target>White : AliceBlue</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : AntiqueWhite</source>
|
||||||
|
<target>White : AntiqueWhite</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Azure</source>
|
||||||
|
<target>White : Azure</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Beige</source>
|
||||||
|
<target>White : Beige</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : FloralWhite</source>
|
||||||
|
<target>White : FloralWhite</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : GhostWhite</source>
|
||||||
|
<target>White : GhostWhite</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Honeydew</source>
|
||||||
|
<target>White : Honeydew</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Ivory</source>
|
||||||
|
<target>White : Ivory</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : LavenderBlush</source>
|
||||||
|
<target>White : LavenderBlush</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Linen</source>
|
||||||
|
<target>White : Linen</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : MintCream</source>
|
||||||
|
<target>White : MintCream</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : MistyRose</source>
|
||||||
|
<target>White : MistyRose</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : OldLace</source>
|
||||||
|
<target>White : OldLace</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Seashell</source>
|
||||||
|
<target>White : Seashell</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : Snow</source>
|
||||||
|
<target>White : Snow</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : White</source>
|
||||||
|
<target>White : White</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>White : WhiteSmoke</source>
|
||||||
|
<target>White : WhiteSmoke</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : DarkKhaki</source>
|
||||||
|
<target>Yellow : DarkKhaki</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : Gold</source>
|
||||||
|
<target>Yellow : Gold</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : Khaki</source>
|
||||||
|
<target>Yellow : Khaki</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : LemonChiffon</source>
|
||||||
|
<target>Yellow : LemonChiffon</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : LightGoldenrodYellow</source>
|
||||||
|
<target>Yellow : LightGoldenrodYellow</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : LightYellow</source>
|
||||||
|
<target>Yellow : LightYellow</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : Moccasin</source>
|
||||||
|
<target>Yellow : Moccasin</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : PaleGoldenrod</source>
|
||||||
|
<target>Yellow : PaleGoldenrod</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : PapayaWhip</source>
|
||||||
|
<target>Yellow : PapayaWhip</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : PeachPuff</source>
|
||||||
|
<target>Yellow : PeachPuff</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yellow : Yellow</source>
|
||||||
|
<target>Yellow : Yellow</target>
|
||||||
|
<definition>element part filling</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>armoire</source>
|
||||||
|
<target>cabinet</target>
|
||||||
|
<definition>cabinet / enclosure</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bobine</source>
|
||||||
|
<target>coil</target>
|
||||||
|
<definition>coil</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>borne</source>
|
||||||
|
<target>terminal</target>
|
||||||
|
<definition>terminal - a connection point on a symbol</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bornier</source>
|
||||||
|
<target>terminal strip</target>
|
||||||
|
<definition>terminal strip</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cable</source>
|
||||||
|
<target>cable</target>
|
||||||
|
<definition>cable</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>calibre</source>
|
||||||
|
<target>rating</target>
|
||||||
|
<definition>rating - the current rating of a device</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cartouche</source>
|
||||||
|
<target>title block</target>
|
||||||
|
<definition>title block - the framed information panel</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>champ de texte</source>
|
||||||
|
<target>text field</target>
|
||||||
|
<definition>text field</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>collection</source>
|
||||||
|
<target>collection</target>
|
||||||
|
<definition>collection - the symbol library</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>conducteur</source>
|
||||||
|
<target>conductor</target>
|
||||||
|
<definition>conductor - the drawn wire between terminals</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contact</source>
|
||||||
|
<target>contact</target>
|
||||||
|
<definition>contact</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contacteur</source>
|
||||||
|
<target>contactor</target>
|
||||||
|
<definition>contactor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>courant</source>
|
||||||
|
<target>current</target>
|
||||||
|
<definition>current</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>disjoncteur</source>
|
||||||
|
<target>circuit breaker</target>
|
||||||
|
<definition>circuit breaker</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>dossier</source>
|
||||||
|
<target>folder</target>
|
||||||
|
<definition>folder</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>element</source>
|
||||||
|
<target>element</target>
|
||||||
|
<definition>element - a symbol placed on a sheet</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>esclave</source>
|
||||||
|
<target>slave</target>
|
||||||
|
<definition>slave - the contact block belonging to a master</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fil</source>
|
||||||
|
<target>wire</target>
|
||||||
|
<definition>wire</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>folio</source>
|
||||||
|
<target>sheet</target>
|
||||||
|
<definition>sheet - one drawing of a set</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fusible</source>
|
||||||
|
<target>fuse</target>
|
||||||
|
<definition>fuse</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>grille</source>
|
||||||
|
<target>grid</target>
|
||||||
|
<definition>grid</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>interrupteur</source>
|
||||||
|
<target>switch</target>
|
||||||
|
<definition>switch</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>maitre</source>
|
||||||
|
<target>master</target>
|
||||||
|
<definition>master - the element a cross-reference points from</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>modele</source>
|
||||||
|
<target>template</target>
|
||||||
|
<definition>template</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>moteur</source>
|
||||||
|
<target>motor</target>
|
||||||
|
<definition>motor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>neutre</source>
|
||||||
|
<target>neutral</target>
|
||||||
|
<definition>neutral</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>nomenclature</source>
|
||||||
|
<target>parts list</target>
|
||||||
|
<definition>parts list / bill of materials</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>numerotation</source>
|
||||||
|
<target>numbering</target>
|
||||||
|
<definition>numbering - automatic wire and part numbering</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>phase</source>
|
||||||
|
<target>phase</target>
|
||||||
|
<definition>phase</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>potentiel</source>
|
||||||
|
<target>potential</target>
|
||||||
|
<definition>potential - conductors electrically joined as one node</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>projet</source>
|
||||||
|
<target>project</target>
|
||||||
|
<definition>project - the .qet file holding the sheets</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>puissance</source>
|
||||||
|
<target>power</target>
|
||||||
|
<definition>power</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>relais</source>
|
||||||
|
<target>relay</target>
|
||||||
|
<definition>relay</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>renvoi de folio</source>
|
||||||
|
<target>sheet cross-reference</target>
|
||||||
|
<definition>sheet cross-reference</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>repere</source>
|
||||||
|
<target>designation</target>
|
||||||
|
<definition>designation - the identifying mark on a part</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>report de folio</source>
|
||||||
|
<target>sheet cross-reference</target>
|
||||||
|
<definition>sheet cross-reference (the arrow symbol)</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>schema</source>
|
||||||
|
<target>diagram</target>
|
||||||
|
<definition>diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>section</source>
|
||||||
|
<target>cross-section</target>
|
||||||
|
<definition>cross-section - conductor size in mm2</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>sommaire</source>
|
||||||
|
<target>summary</target>
|
||||||
|
<definition>summary / table of contents</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>tension</source>
|
||||||
|
<target>voltage</target>
|
||||||
|
<definition>voltage</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>terre</source>
|
||||||
|
<target>earth</target>
|
||||||
|
<definition>earth / ground</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>texte</source>
|
||||||
|
<target>text</target>
|
||||||
|
<definition>text</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>transformateur</source>
|
||||||
|
<target>transformer</target>
|
||||||
|
<definition>transformer</definition>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,941 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="fr_FR" language="nl_BE">
|
||||||
|
<phrase>
|
||||||
|
<source> Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4
|
||||||
|
</source>
|
||||||
|
<target>Contacten: NO: %1, NC: %2, wisselcontacten: %3, andere: %4
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8
|
||||||
|
</source>
|
||||||
|
<target>Contacten: NO: %1/%2, NC: %3/%4, wisselcontacten: %5/%6, andere: %7/%8
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> %</source>
|
||||||
|
<target>%</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> (Ctrl pendant le glissement = position libre, sans accrochage à la grille)</source>
|
||||||
|
<target>(Ctrl tijdens het slepen = vrije positie, zonder vastklikken aan het raster)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> -- %1 : mode %2</source>
|
||||||
|
<target>-- %1 : modus %2</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> ; point rouge : glisser pour repositionner le centre de rotation</source>
|
||||||
|
<target>; rood punt: slepen om het draaipunt te verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> ; point turquoise : arc</source>
|
||||||
|
<target>; turquoise punt: boog</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> ; un bord : inclinaison</source>
|
||||||
|
<target>; een rand: kanteling</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> °</source>
|
||||||
|
<target>°</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> — Cliquer : mode %1</source>
|
||||||
|
<target>— Klikken: modus %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>, Alt = créer des poignées</source>
|
||||||
|
<target>, Alt = handgrepen creëren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>, Alt = détacher en polyligne</source>
|
||||||
|
<target>, Alt = losmaken als polylijn</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajoute une courbe de Bézier sur le folio actuel</source>
|
||||||
|
<target>Voegt een Béziercurve toe op het huidige blad</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajouter la borne %1</source>
|
||||||
|
<target>Klem %1 toevoegen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajouter un point à une courbe</source>
|
||||||
|
<target>Een punt aan een curve toevoegen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajouter une courbe</source>
|
||||||
|
<target>Een curve toevoegen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Angle</source>
|
||||||
|
<target>Hoek</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Anguleux</source>
|
||||||
|
<target>Hoekig</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Aperçu</source>
|
||||||
|
<target>Voorbeeld</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Arrondir les coins d'%1</source>
|
||||||
|
<target>Hoeken van %1 afronden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Borne 1</source>
|
||||||
|
<target>Klem 1</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Borne 2</source>
|
||||||
|
<target>Klem 2</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Catégorie</source>
|
||||||
|
<target>Categorie</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ce format ne prend pas en charge la transparence : l'image sera enregistrée telle qu'elle était avant l'application de la couleur transparente. Continuer ?</source>
|
||||||
|
<target>Dit formaat ondersteunt geen transparantie: de afbeelding wordt opgeslagen zoals ze was vóór het toepassen van de transparante kleur. Doorgaan?</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Champ de texte</source>
|
||||||
|
<target>Tekstveld</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic : positionner à la taille d'origine. Cliquer-glisser : positionner et redimensionner. Clic droit : pivoter de 90°. Ctrl+molette : ajuster la taille.</source>
|
||||||
|
<target>Klik: op oorspronkelijke grootte plaatsen. Klikken en slepen: plaatsen en formaat wijzigen. Rechtsklik: 90° draaien. Ctrl+muiswiel: formaat aanpassen.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : point suivant ; double-clic ou Entrée : terminer ; clic droit : annuler le dernier point</source>
|
||||||
|
<target>Linkerklik: volgend punt; dubbelklik of Enter: beëindigen; rechtsklik: laatste punt annuleren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le coin opposé (Maj = carré, Ctrl = depuis le centre + position libre, Ctrl+Maj = carré centré) ; clic droit : annuler</source>
|
||||||
|
<target>Linkerklik: tegenoverliggende hoek plaatsen (Shift = vierkant, Ctrl = vanuit het midden + vrije positie, Ctrl+Shift = gecentreerd vierkant); rechtsklik: annuleren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le coin opposé (Maj = cercle, Ctrl = depuis le centre + position libre, Ctrl+Maj = cercle centré) ; clic droit : annuler</source>
|
||||||
|
<target>Linkerklik: tegenoverliggende hoek plaatsen (Shift = cirkel, Ctrl = vanuit het midden + vrije positie, Ctrl+Shift = gecentreerde cirkel); rechtsklik: annuleren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le point de départ (Ctrl = position libre)</source>
|
||||||
|
<target>Linkerklik: startpunt plaatsen (Ctrl = vrije positie)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le point final (Ctrl = position libre) ; clic droit : annuler</source>
|
||||||
|
<target>Linkerklik: eindpunt plaatsen (Ctrl = vrije positie); rechtsklik: annuleren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le premier coin (Ctrl = point central, position libre)</source>
|
||||||
|
<target>Linkerklik: eerste hoek plaatsen (Ctrl = middelpunt, vrije positie)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le premier point (Ctrl = position libre)</source>
|
||||||
|
<target>Linkerklik: eerste punt plaatsen (Ctrl = vrije positie)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic: point anguleux. Cliquer-glisser: point courbe. Clic sur le premier point: fermer. Échap/Entrée: terminer. Clic droit: annuler le dernier point.</source>
|
||||||
|
<target>Klik: hoekpunt. Klikken en slepen: curvepunt. Klik op het eerste punt: sluiten. Esc/Enter: beëindigen. Rechtsklik: laatste punt annuleren.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquer</source>
|
||||||
|
<target>Klikken</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquer : mode %1</source>
|
||||||
|
<target>Klikken: modus %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquez pour choisir une couleur</source>
|
||||||
|
<target>Klik om een kleur te kiezen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquez sur l'image pour ajouter une couleur. Ajustez la tolérance de chaque couleur avec son curseur, ou cliquez sur × pour la retirer.</source>
|
||||||
|
<target>Klik op de afbeelding om een kleur toe te voegen. Pas de tolerantie van elke kleur aan met de schuifregelaar, of klik op × om ze te verwijderen.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquez sur l'image pour choisir une couleur</source>
|
||||||
|
<target>Klik op de afbeelding om een kleur te kiezen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Coins arrondis</source>
|
||||||
|
<target>Afgeronde hoeken</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Composant 1</source>
|
||||||
|
<target>Component 1</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Composant 2</source>
|
||||||
|
<target>Component 2</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir %1 en courbe de Bézier</source>
|
||||||
|
<target>%1 omzetten naar Béziercurve</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir %1 en polyligne</source>
|
||||||
|
<target>%1 omzetten naar polylijn</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir en courbe de Bézier</source>
|
||||||
|
<target>Omzetten naar Béziercurve</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir en polyligne</source>
|
||||||
|
<target>Omzetten naar polylijn</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Couleur transparente</source>
|
||||||
|
<target>Transparante kleur</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Couleur transparente...</source>
|
||||||
|
<target>Transparante kleur...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Courant nominal</source>
|
||||||
|
<target>Nominale stroom</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Deplacer le centre de rotation</source>
|
||||||
|
<target>Draaipunt verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Description</source>
|
||||||
|
<target>Beschrijving</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Distance in pixels between the label and the slave cross reference</source>
|
||||||
|
<target>Afstand in pixels tussen het label en de slave-kruisverwijzing</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Distance label - slave :</source>
|
||||||
|
<target>Afstand label - slave:</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Définir une couleur transparente</source>
|
||||||
|
<target>Een transparante kleur instellen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Déformer une courbe</source>
|
||||||
|
<target>Een curve vervormen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Déplacer le centre de rotation d'une image</source>
|
||||||
|
<target>Het draaipunt van een afbeelding verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Déverrouillé : largeur et hauteur peuvent être modifiées indépendamment. Cliquer pour verrouiller.</source>
|
||||||
|
<target>Ontgrendeld: breedte en hoogte kunnen onafhankelijk worden gewijzigd. Klik om te vergrendelen.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Enregistrer l'image d'origine sous...</source>
|
||||||
|
<target>Oorspronkelijke afbeelding opslaan als...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Enregistrer l'image sous...</source>
|
||||||
|
<target>Afbeelding opslaan als...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Erreur</source>
|
||||||
|
<target>Fout</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Exclure de la nomenclature</source>
|
||||||
|
<target>Uitsluiten van de stuklijst</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faire pivoter %1</source>
|
||||||
|
<target>%1 roteren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faire pivoter une image</source>
|
||||||
|
<target>Een afbeelding draaien</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faites glisser les poignées, ou l'intérieur du cadre, pour ajuster la zone à conserver.</source>
|
||||||
|
<target>Sleep de handgrepen, of het binnenste van het kader, om het te behouden gebied aan te passen.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Fil</source>
|
||||||
|
<target>Draad</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Folio</source>
|
||||||
|
<target>Blad</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Forme fermée</source>
|
||||||
|
<target>Gesloten vorm</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser %1 : rotation (Maj = 15°)</source>
|
||||||
|
<target>Sleep %1: rotatie (Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : ajuster l'arc (Ctrl = position libre, Maj = 15°)</source>
|
||||||
|
<target>Slepen: de boog aanpassen (Ctrl = vrije positie, Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : arrondir les coins (Ctrl = position libre)</source>
|
||||||
|
<target>Slepen: hoeken afronden (Ctrl = vrije positie)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déformer la courbe (Ctrl = position libre, Alt = briser la tangente)</source>
|
||||||
|
<target>Slepen: de curve vervormen (Ctrl = vrije positie, Alt = de raaklijn breken)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déplacer ce point</source>
|
||||||
|
<target>Slepen: dit punt verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déplacer le centre de rotation</source>
|
||||||
|
<target>Slepen: draaipunt verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déplacer le point (Ctrl = position libre</source>
|
||||||
|
<target>Slepen: het punt verplaatsen (Ctrl = vrije positie</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : inclinaison (Ctrl = position libre, Maj = 15°)</source>
|
||||||
|
<target>Slepen: kanteling (Ctrl = vrije positie, Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : incliner (Maj = par pas de 15°)</source>
|
||||||
|
<target>Slepen: kantelen (Shift = stappen van 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : pivoter (Maj = par pas de 15°)</source>
|
||||||
|
<target>Slepen: draaien (Shift = stappen van 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : redimensionner (Ctrl = depuis le centre + position libre, Maj = proportions</source>
|
||||||
|
<target>Slepen: formaat wijzigen (Ctrl = vanuit het midden + vrije positie, Shift = verhoudingen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : redimensionner (Maj = conserver les proportions, Ctrl = depuis le centre)</source>
|
||||||
|
<target>Slepen: formaat wijzigen (Shift = verhoudingen behouden, Ctrl = vanuit het midden)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : repositionner le centre de rotation (Ctrl = position libre)</source>
|
||||||
|
<target>Slepen: het draaipunt verplaatsen (Ctrl = vrije positie)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : rotation (Ctrl = position libre, Maj = 15°)</source>
|
||||||
|
<target>Slepen: rotatie (Ctrl = vrije positie, Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser le point violet : arrondir les coins</source>
|
||||||
|
<target>Sleep het paarse punt: hoeken afronden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un coin : pivoter (Maj = par pas de 15°) ; glisser un bord : incliner (Maj = par pas de 15°) ; point rouge : déplacer le centre de rotation</source>
|
||||||
|
<target>Sleep een hoek: draaien (Shift = stappen van 15°); sleep een rand: kantelen (Shift = stappen van 15°); rood punt: draaipunt verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = conserver les proportions)</source>
|
||||||
|
<target>Sleep een hoek/rand: formaat wijzigen (Ctrl = vanuit het midden, Shift = verhoudingen behouden)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = proportions, Alt = détacher en polyligne)</source>
|
||||||
|
<target>Sleep een hoek/rand: formaat wijzigen (Ctrl = vanuit het midden, Shift = verhoudingen, Alt = losmaken als polylijn)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un point : le déplacer</source>
|
||||||
|
<target>Sleep een punt: het verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser une extrémité : la déplacer</source>
|
||||||
|
<target>Sleep een uiteinde: het verplaatsen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser une poignée ou la courbe : déformer (Alt = briser la tangente) ; Alt+glisser un point anguleux : créer des poignées ; clic droit : menu du nœud le plus proche</source>
|
||||||
|
<target>Sleep een handgreep of de curve: vervormen (Alt = de raaklijn breken); Alt+sleep een hoekpunt: handgrepen creëren; rechtsklik: menu van het dichtstbijzijnde knooppunt</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Géométrie</source>
|
||||||
|
<target>Geometrie</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Hauteur</source>
|
||||||
|
<target>Hoogte</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image BMP (*.bmp)</source>
|
||||||
|
<target>BMP-afbeelding (*.bmp)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image Files (*.png *.jpg *.jpeg *.bmp *.svg)</source>
|
||||||
|
<target>Afbeeldingsbestanden (*.png *.jpg *.jpeg *.bmp *.svg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image JPEG (*.jpg *.jpeg)</source>
|
||||||
|
<target>JPEG-afbeelding (*.jpg *.jpeg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image PNG (*.png)</source>
|
||||||
|
<target>PNG-afbeelding (*.png)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image source</source>
|
||||||
|
<target>Bronafbeelding</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image SVG (*.svg)</source>
|
||||||
|
<target>SVG-afbeelding (*.svg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Images non incluses dans l'export DXF</source>
|
||||||
|
<target>Afbeeldingen niet inbegrepen in de DXF-export</target>
|
||||||
|
<definition>message box title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible d'enregistrer l'image à cet emplacement.</source>
|
||||||
|
<target>Kan de afbeelding niet op die locatie opslaan.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible d'enregistrer la nomenclature dans %1.
|
||||||
|
%2</source>
|
||||||
|
<target>Kan de stuklijst niet opslaan in %1.
|
||||||
|
%2</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible de charger l'image.</source>
|
||||||
|
<target>Kan de afbeelding niet laden.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Inclinaison X</source>
|
||||||
|
<target>Kanteling X</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Inclinaison Y</source>
|
||||||
|
<target>Kanteling Y</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Incliner %1</source>
|
||||||
|
<target>%1 kantelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Incliner une image</source>
|
||||||
|
<target>Een afbeelding kantelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>La limite fixée pour cet élément maître est atteinte (Limite: %1).
|
||||||
|
|
||||||
|
Voulez-vous tout de même lier ce contact esclave ?</source>
|
||||||
|
<target>De limiet voor dit hoofdelement is bereikt (Limiet: %1).
|
||||||
|
|
||||||
|
Wilt u dit hulpcontact toch koppelen?</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Largeur</source>
|
||||||
|
<target>Breedte</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Le format DXF utilisé ici (AC1006) ne permet pas d'inclure d'image. Les images seront représentées uniquement par un rectangle de contour (position, taille, rotation et inclinaison conservées), sans le contenu de l'image.</source>
|
||||||
|
<target>Het hier gebruikte DXF-formaat (AC1006) laat niet toe afbeeldingen op te nemen. Afbeeldingen worden enkel voorgesteld door een omtrekrechthoek (positie, grootte, rotatie en kanteling behouden), zonder de inhoud van de afbeelding.</target>
|
||||||
|
<definition>message box content</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Lisse</source>
|
||||||
|
<target>Vloeiend</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Liste de câblage</source>
|
||||||
|
<target>Bekabelingslijst</target>
|
||||||
|
<definition>window title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Liste de câblage (base de données)</source>
|
||||||
|
<target>Bekabelingslijst (database)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Localisation :</source>
|
||||||
|
<target>Locatie:</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Longueur</source>
|
||||||
|
<target>Lengte</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>margin: 5px; font-weight: bold;</source>
|
||||||
|
<target>margin: 5px; font-weight: bold;</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>max:</source>
|
||||||
|
<target>max:</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>min:</source>
|
||||||
|
<target>min:</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir horizontal</source>
|
||||||
|
<target>Horizontaal spiegelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir horizontal d'une image</source>
|
||||||
|
<target>Horizontaal spiegelen van een afbeelding</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir horizontal de %1</source>
|
||||||
|
<target>Horizontaal spiegelen van %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir impossible : inclinaison trop extrême pour cette forme</source>
|
||||||
|
<target>Spiegelen onmogelijk: kanteling te extreem voor deze vorm</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir vertical</source>
|
||||||
|
<target>Verticaal spiegelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir vertical d'une image</source>
|
||||||
|
<target>Verticaal spiegelen van een afbeelding</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir vertical de %1</source>
|
||||||
|
<target>Verticaal spiegelen van %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'un arc</source>
|
||||||
|
<target>Hoek van een boog wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'une forme</source>
|
||||||
|
<target>Hoek van een vorm wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'une image</source>
|
||||||
|
<target>De hoek van een afbeelding wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'inclinaison d'une image</source>
|
||||||
|
<target>De kanteling van een afbeelding wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la courbure d'%1</source>
|
||||||
|
<target>Kromming van %1 wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la forme d'%1</source>
|
||||||
|
<target>Vorm van %1 wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la hauteur d'une image</source>
|
||||||
|
<target>De hoogte van een afbeelding wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la largeur d'une image</source>
|
||||||
|
<target>De breedte van een afbeelding wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la longueur d'une ligne</source>
|
||||||
|
<target>Lengte van een lijn wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la taille d'une forme</source>
|
||||||
|
<target>Grootte van een vorm wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier le type d'un nœud</source>
|
||||||
|
<target>Het type van een knooppunt wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier une image</source>
|
||||||
|
<target>Een afbeelding wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modèle</source>
|
||||||
|
<target>Model</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Nombre maximum de contacts esclaves définis : non défini
|
||||||
|
</source>
|
||||||
|
<target>Maximumaantal gedefinieerde hulpcontacten: niet gedefinieerd
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Notes</source>
|
||||||
|
<target>Notities</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Nœud le plus proche</source>
|
||||||
|
<target>Dichtstbijzijnde knooppunt</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>pivoter/incliner</source>
|
||||||
|
<target>draaien/kantelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ponter des bornes entre-elles</source>
|
||||||
|
<target>Klemmen onderling doorverbinden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rayon X</source>
|
||||||
|
<target>Straal X</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rayon Y</source>
|
||||||
|
<target>Straal Y</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>redimensionner</source>
|
||||||
|
<target>formaat wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Redimensionner %1</source>
|
||||||
|
<target>%1 vergroten/verkleinen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Redimensionner une image</source>
|
||||||
|
<target>Formaat van een afbeelding wijzigen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Remplacer l'image...</source>
|
||||||
|
<target>Afbeelding vervangen...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Remplacer une image</source>
|
||||||
|
<target>Een afbeelding vervangen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Restaurer les proportions</source>
|
||||||
|
<target>Verhoudingen herstellen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Restaurer les proportions d'une image</source>
|
||||||
|
<target>De verhoudingen van een afbeelding herstellen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Retirer cette couleur</source>
|
||||||
|
<target>Deze kleur verwijderen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Revenir à l'image complète, sans rognage</source>
|
||||||
|
<target>Terug naar de volledige afbeelding, zonder bijsnijden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>rgb(%1, %2, %3)</source>
|
||||||
|
<target>rgb(%1, %2, %3)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rogner l'image</source>
|
||||||
|
<target>Afbeelding bijsnijden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rogner une image</source>
|
||||||
|
<target>Een afbeelding bijsnijden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rogner...</source>
|
||||||
|
<target>Bijsnijden...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rotation/Inclinaison</source>
|
||||||
|
<target>Rotatie/kanteling</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Réinitialiser</source>
|
||||||
|
<target>Herinitialiseren</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Selectionner une image...</source>
|
||||||
|
<target>Een afbeelding selecteren...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Supprimer le nœud le plus proche</source>
|
||||||
|
<target>Het dichtstbijzijnde knooppunt verwijderen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Supprimer un point d'une courbe</source>
|
||||||
|
<target>Een punt van een curve verwijderen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Symétrique</source>
|
||||||
|
<target>Symmetrisch</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Séparation de potentiel</source>
|
||||||
|
<target>Potentiaalscheiding</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Taille</source>
|
||||||
|
<target>Grootte</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tension nominale</source>
|
||||||
|
<target>Nominale spanning</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tolérance pour cette couleur</source>
|
||||||
|
<target>Tolerantie voor deze kleur</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tous les fichiers (*)</source>
|
||||||
|
<target>Alle bestanden (*)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Transformer %1</source>
|
||||||
|
<target>%1 omvormen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Transparence non conservée</source>
|
||||||
|
<target>Transparantie niet behouden</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>un arc</source>
|
||||||
|
<target>een boog</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>un coin</source>
|
||||||
|
<target>een hoek</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>un point</source>
|
||||||
|
<target>een punt</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une courbe</source>
|
||||||
|
<target>een curve</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une extrémité</source>
|
||||||
|
<target>een uiteinde</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une image</source>
|
||||||
|
<target>een afbeelding</target>
|
||||||
|
<definition>part of a sentence listing the content of a diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouiller la numérotation automatique</source>
|
||||||
|
<target>Automatische nummering vergrendelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouiller la position</source>
|
||||||
|
<target>Positie vergrendelen</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouillé : modifier la largeur ou la hauteur ajuste l'autre pour conserver les proportions. Cliquer pour déverrouiller.</source>
|
||||||
|
<target>Vergrendeld: het wijzigen van de breedte of hoogte past de andere aan om de verhoudingen te behouden. Klik om te ontgrendelen.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Échec de l'enregistrement</source>
|
||||||
|
<target>Opslaan mislukt</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Édition des nœuds</source>
|
||||||
|
<target>Knooppunten bewerken</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>armoire</source>
|
||||||
|
<target>kast</target>
|
||||||
|
<definition>cabinet / enclosure</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bobine</source>
|
||||||
|
<target>spoel</target>
|
||||||
|
<definition>coil</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>borne</source>
|
||||||
|
<target>klem</target>
|
||||||
|
<definition>terminal - a connection point on a symbol</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bornier</source>
|
||||||
|
<target>klemmenstrook</target>
|
||||||
|
<definition>terminal strip</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cable</source>
|
||||||
|
<target>kabel</target>
|
||||||
|
<definition>cable</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>calibre</source>
|
||||||
|
<target>kaliber</target>
|
||||||
|
<definition>rating - the current rating of a device</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cartouche</source>
|
||||||
|
<target>titelblok</target>
|
||||||
|
<definition>title block - the framed information panel</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>collection</source>
|
||||||
|
<target>collectie</target>
|
||||||
|
<definition>collection - the symbol library</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>conducteur</source>
|
||||||
|
<target>geleider</target>
|
||||||
|
<definition>conductor - the drawn wire between terminals</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contact</source>
|
||||||
|
<target>contact</target>
|
||||||
|
<definition>contact</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contacteur</source>
|
||||||
|
<target>contacteur</target>
|
||||||
|
<definition>contactor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>courant</source>
|
||||||
|
<target>stroom</target>
|
||||||
|
<definition>current</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>disjoncteur</source>
|
||||||
|
<target>automaat</target>
|
||||||
|
<definition>circuit breaker</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>dossier</source>
|
||||||
|
<target>map</target>
|
||||||
|
<definition>folder</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>element</source>
|
||||||
|
<target>element</target>
|
||||||
|
<definition>element - a symbol placed on a sheet</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>esclave</source>
|
||||||
|
<target>hulpcontact</target>
|
||||||
|
<definition>slave - the contact block belonging to a master</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fusible</source>
|
||||||
|
<target>zekering</target>
|
||||||
|
<definition>fuse</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>grille</source>
|
||||||
|
<target>raster</target>
|
||||||
|
<definition>grid</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>interrupteur</source>
|
||||||
|
<target>schakelaar</target>
|
||||||
|
<definition>switch</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>maitre</source>
|
||||||
|
<target>hoofd</target>
|
||||||
|
<definition>master - the element a cross-reference points from</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>modele</source>
|
||||||
|
<target>sjabloon</target>
|
||||||
|
<definition>template</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>moteur</source>
|
||||||
|
<target>motor</target>
|
||||||
|
<definition>motor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>neutre</source>
|
||||||
|
<target>nul</target>
|
||||||
|
<definition>neutral</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>nomenclature</source>
|
||||||
|
<target>stuklijst</target>
|
||||||
|
<definition>parts list / bill of materials</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>numerotation</source>
|
||||||
|
<target>nummering</target>
|
||||||
|
<definition>numbering - automatic wire and part numbering</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>phase</source>
|
||||||
|
<target>fase</target>
|
||||||
|
<definition>phase</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>potentiel</source>
|
||||||
|
<target>potentiaal</target>
|
||||||
|
<definition>potential - conductors electrically joined as one node</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>projet</source>
|
||||||
|
<target>project</target>
|
||||||
|
<definition>project - the .qet file holding the sheets</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>puissance</source>
|
||||||
|
<target>vermogen</target>
|
||||||
|
<definition>power</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>relais</source>
|
||||||
|
<target>relais</target>
|
||||||
|
<definition>relay</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>renvoi de folio</source>
|
||||||
|
<target>bladverwijzing</target>
|
||||||
|
<definition>sheet cross-reference</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>repere</source>
|
||||||
|
<target>aanduiding</target>
|
||||||
|
<definition>designation - the identifying mark on a part</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>report de folio</source>
|
||||||
|
<target>bladverwijzing</target>
|
||||||
|
<definition>sheet cross-reference (the arrow symbol)</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>schema</source>
|
||||||
|
<target>schema</target>
|
||||||
|
<definition>diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>section</source>
|
||||||
|
<target>doorsnede</target>
|
||||||
|
<definition>cross-section - conductor size in mm2</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>sommaire</source>
|
||||||
|
<target>inhoudstafel</target>
|
||||||
|
<definition>summary / table of contents</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>tension</source>
|
||||||
|
<target>spanning</target>
|
||||||
|
<definition>voltage</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>terre</source>
|
||||||
|
<target>aarde</target>
|
||||||
|
<definition>earth / ground</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>texte</source>
|
||||||
|
<target>tekst</target>
|
||||||
|
<definition>text</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>transformateur</source>
|
||||||
|
<target>transformator</target>
|
||||||
|
<definition>transformer</definition>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="fr_FR" language="pl">
|
||||||
|
<phrase>
|
||||||
|
<source>armoire</source>
|
||||||
|
<target>Szafa</target>
|
||||||
|
<definition>cabinet / enclosure</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bobine</source>
|
||||||
|
<target>Cewka</target>
|
||||||
|
<definition>coil</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>borne</source>
|
||||||
|
<target>Zacisk</target>
|
||||||
|
<definition>terminal - a connection point on a symbol</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bornier</source>
|
||||||
|
<target>Listwa zaciskowa</target>
|
||||||
|
<definition>terminal strip</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cable</source>
|
||||||
|
<target>Kabel</target>
|
||||||
|
<definition>cable</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>calibre</source>
|
||||||
|
<target>Prąd znamionowy</target>
|
||||||
|
<definition>rating - the current rating of a device</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cartouche</source>
|
||||||
|
<target>Tabliczka rysunkowa</target>
|
||||||
|
<definition>title block - the framed information panel</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>champ de texte</source>
|
||||||
|
<target>Pole tekstowe</target>
|
||||||
|
<definition>text field</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>collection</source>
|
||||||
|
<target>Biblioteka</target>
|
||||||
|
<definition>collection - the symbol library</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>conducteur</source>
|
||||||
|
<target>Przewód</target>
|
||||||
|
<definition>conductor - the drawn wire between terminals</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contact</source>
|
||||||
|
<target>Styk</target>
|
||||||
|
<definition>contact</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contacteur</source>
|
||||||
|
<target>Stycznik</target>
|
||||||
|
<definition>contactor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>courant</source>
|
||||||
|
<target>Prąd</target>
|
||||||
|
<definition>current</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>disjoncteur</source>
|
||||||
|
<target>Wyłącznik nadprądowy</target>
|
||||||
|
<definition>circuit breaker</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>dossier</source>
|
||||||
|
<target>Folder</target>
|
||||||
|
<definition>folder</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>element</source>
|
||||||
|
<target>Element</target>
|
||||||
|
<definition>element - a symbol placed on a sheet</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>esclave</source>
|
||||||
|
<target>Element podrzędny</target>
|
||||||
|
<definition>slave - the contact block belonging to a master</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fil</source>
|
||||||
|
<target>Przewód</target>
|
||||||
|
<definition>wire</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>folio</source>
|
||||||
|
<target>Arkusz</target>
|
||||||
|
<definition>sheet - one drawing of a set</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fusible</source>
|
||||||
|
<target>Bezpiecznik</target>
|
||||||
|
<definition>fuse</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>grille</source>
|
||||||
|
<target>Siatka</target>
|
||||||
|
<definition>grid</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>interrupteur</source>
|
||||||
|
<target>Wyłącznik</target>
|
||||||
|
<definition>switch</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>maitre</source>
|
||||||
|
<target>Element główny</target>
|
||||||
|
<definition>master - the element a cross-reference points from</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>modele</source>
|
||||||
|
<target>Szablon</target>
|
||||||
|
<definition>template</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>moteur</source>
|
||||||
|
<target>Silnik</target>
|
||||||
|
<definition>motor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>neutre</source>
|
||||||
|
<target>Neutralny</target>
|
||||||
|
<definition>neutral</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>nomenclature</source>
|
||||||
|
<target>Zestawienie materiałów</target>
|
||||||
|
<definition>parts list / bill of materials</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>numerotation</source>
|
||||||
|
<target>Numeracja</target>
|
||||||
|
<definition>numbering - automatic wire and part numbering</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>phase</source>
|
||||||
|
<target>Faza</target>
|
||||||
|
<definition>phase</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>potentiel</source>
|
||||||
|
<target>Potencjał</target>
|
||||||
|
<definition>potential - conductors electrically joined as one node</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>projet</source>
|
||||||
|
<target>Projekt</target>
|
||||||
|
<definition>project - the .qet file holding the sheets</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>puissance</source>
|
||||||
|
<target>Moc</target>
|
||||||
|
<definition>power</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>relais</source>
|
||||||
|
<target>Przekaźnik</target>
|
||||||
|
<definition>relay</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>renvoi de folio</source>
|
||||||
|
<target>Odnośnik do arkusza</target>
|
||||||
|
<definition>sheet cross-reference</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>repere</source>
|
||||||
|
<target>Oznaczenie</target>
|
||||||
|
<definition>designation - the identifying mark on a part</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>report de folio</source>
|
||||||
|
<target>Odnośnik do arkusza</target>
|
||||||
|
<definition>sheet cross-reference (the arrow symbol)</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>schema</source>
|
||||||
|
<target>Schemat</target>
|
||||||
|
<definition>diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>section</source>
|
||||||
|
<target>Przekrój</target>
|
||||||
|
<definition>cross-section - conductor size in mm2</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>sommaire</source>
|
||||||
|
<target>Spis treści</target>
|
||||||
|
<definition>summary / table of contents</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>tension</source>
|
||||||
|
<target>Napięcie</target>
|
||||||
|
<definition>voltage</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>terre</source>
|
||||||
|
<target>Uziemienie</target>
|
||||||
|
<definition>earth / ground</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>texte</source>
|
||||||
|
<target>Tekst</target>
|
||||||
|
<definition>text</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>transformateur</source>
|
||||||
|
<target>Transformator</target>
|
||||||
|
<definition>transformer</definition>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,465 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="fr_FR" language="pt_BR">
|
||||||
|
<phrase>
|
||||||
|
<source> Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4
|
||||||
|
</source>
|
||||||
|
<target>Contatos: NA: %1, NF: %2, reversíveis: %3, outros: %4
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8
|
||||||
|
</source>
|
||||||
|
<target>Contatos: NA: %1/%2, NF: %3/%4, reversíveis: %5/%6, outros: %7/%8
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Aperçu</source>
|
||||||
|
<target>Pré-visualização</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Arrondir les coins d'%1</source>
|
||||||
|
<target>Arredondar os cantos de %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Borne 1</source>
|
||||||
|
<target>Borne 1</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Borne 2</source>
|
||||||
|
<target>Borne 2</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Catégorie</source>
|
||||||
|
<target>Categoria</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ce format ne prend pas en charge la transparence : l'image sera enregistrée telle qu'elle était avant l'application de la couleur transparente. Continuer ?</source>
|
||||||
|
<target>Este formato não suporta transparência: a imagem será salva como estava antes da aplicação da cor transparente. Continuar?</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Champ de texte</source>
|
||||||
|
<target>Campo de texto</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Composant 1</source>
|
||||||
|
<target>Componente 1</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Composant 2</source>
|
||||||
|
<target>Componente 2</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir %1 en courbe de Bézier</source>
|
||||||
|
<target>Converter %1 em curva de Bézier</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir en courbe de Bézier</source>
|
||||||
|
<target>Converter em curva de Bézier</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Courant nominal</source>
|
||||||
|
<target>Corrente nominal</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Distance in pixels between the label and the slave cross reference</source>
|
||||||
|
<target>Distância em pixels entre o rótulo e a referência cruzada do escravo</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Distance label - slave :</source>
|
||||||
|
<target>Distância do rótulo - escravo:</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Enregistrer l'image d'origine sous...</source>
|
||||||
|
<target>Salvar a imagem original como...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Enregistrer l'image sous...</source>
|
||||||
|
<target>Salvar a imagem como...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Erreur</source>
|
||||||
|
<target>Erro</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Exclure de la nomenclature</source>
|
||||||
|
<target>Excluir da lista de materiais</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faire pivoter %1</source>
|
||||||
|
<target>Girar %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faire pivoter une image</source>
|
||||||
|
<target>Girar uma imagem</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Fil</source>
|
||||||
|
<target>Fio</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Folio</source>
|
||||||
|
<target>Folha</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Hauteur</source>
|
||||||
|
<target>Altura</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image BMP (*.bmp)</source>
|
||||||
|
<target>Imagem BMP (*.bmp)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image JPEG (*.jpg *.jpeg)</source>
|
||||||
|
<target>Imagem JPEG (*.jpg *.jpeg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image PNG (*.png)</source>
|
||||||
|
<target>Imagem PNG (*.png)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image SVG (*.svg)</source>
|
||||||
|
<target>Imagem SVG (*.svg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Images non incluses dans l'export DXF</source>
|
||||||
|
<target>Imagens não incluídas na exportação DXF</target>
|
||||||
|
<definition>message box title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible d'enregistrer l'image à cet emplacement.</source>
|
||||||
|
<target>Não foi possível salvar a imagem neste local.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible d'enregistrer la nomenclature dans %1.
|
||||||
|
%2</source>
|
||||||
|
<target>Não foi possível salvar a lista de materiais em %1.
|
||||||
|
%2</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible de charger l'image.</source>
|
||||||
|
<target>Não foi possível carregar a imagem.</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Incliner %1</source>
|
||||||
|
<target>Inclinar %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Incliner une image</source>
|
||||||
|
<target>Inclinar uma imagem</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>La limite fixée pour cet élément maître est atteinte (Limite: %1).
|
||||||
|
|
||||||
|
Voulez-vous tout de même lier ce contact esclave ?</source>
|
||||||
|
<target>O limite definido para este elemento mestre foi atingido (Limite: %1).
|
||||||
|
|
||||||
|
Deseja vincular este contato escravo mesmo assim?</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Largeur</source>
|
||||||
|
<target>Largura</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Le format DXF utilisé ici (AC1006) ne permet pas d'inclure d'image. Les images seront représentées uniquement par un rectangle de contour (position, taille, rotation et inclinaison conservées), sans le contenu de l'image.</source>
|
||||||
|
<target>O formato DXF usado aqui (AC1006) não permite incluir imagens. As imagens serão representadas apenas por um retângulo de contorno (posição, tamanho, rotação e inclinação preservados), sem o conteúdo da imagem.</target>
|
||||||
|
<definition>message box content</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Liste de câblage</source>
|
||||||
|
<target>Lista de cabeamento</target>
|
||||||
|
<definition>window title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Liste de câblage (base de données)</source>
|
||||||
|
<target>Lista de cabeamento (banco de dados)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>margin: 5px; font-weight: bold;</source>
|
||||||
|
<target>margin: 5px; font-weight: bold;</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'un arc</source>
|
||||||
|
<target>Modificar o ângulo de um arco</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la courbure d'%1</source>
|
||||||
|
<target>Modificar a curvatura de %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la forme d'%1</source>
|
||||||
|
<target>Modificar a forma de %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modèle</source>
|
||||||
|
<target>Modelo</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Nombre maximum de contacts esclaves définis : non défini
|
||||||
|
</source>
|
||||||
|
<target>Número máximo de contatos escravos definidos: não definido
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Notes</source>
|
||||||
|
<target>Observações</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Redimensionner %1</source>
|
||||||
|
<target>Redimensionar %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Redimensionner une image</source>
|
||||||
|
<target>Redimensionar uma imagem</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Réinitialiser</source>
|
||||||
|
<target>Redefinir</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Selectionner une image...</source>
|
||||||
|
<target>Selecionar uma imagem...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Séparation de potentiel</source>
|
||||||
|
<target>Separação de potencial</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Taille</source>
|
||||||
|
<target>Tamanho</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tension nominale</source>
|
||||||
|
<target>Tensão nominal</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tous les fichiers (*)</source>
|
||||||
|
<target>Todos os arquivos (*)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Transparence non conservée</source>
|
||||||
|
<target>Transparência não preservada</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une image</source>
|
||||||
|
<target>uma imagem</target>
|
||||||
|
<definition>part of a sentence listing the content of a diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouiller la numérotation automatique</source>
|
||||||
|
<target>Bloquear a numeração automática</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouiller la position</source>
|
||||||
|
<target>Bloquear a posição</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Échec de l'enregistrement</source>
|
||||||
|
<target>Falha ao salvar</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>armoire</source>
|
||||||
|
<target>armário</target>
|
||||||
|
<definition>cabinet / enclosure</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bobine</source>
|
||||||
|
<target>bobina</target>
|
||||||
|
<definition>coil</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>borne</source>
|
||||||
|
<target>borne</target>
|
||||||
|
<definition>terminal - a connection point on a symbol</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bornier</source>
|
||||||
|
<target>borneira</target>
|
||||||
|
<definition>terminal strip</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cable</source>
|
||||||
|
<target>cabo</target>
|
||||||
|
<definition>cable</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>calibre</source>
|
||||||
|
<target>calibre</target>
|
||||||
|
<definition>rating - the current rating of a device</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cartouche</source>
|
||||||
|
<target>bloco de título</target>
|
||||||
|
<definition>title block - the framed information panel</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>collection</source>
|
||||||
|
<target>coleção</target>
|
||||||
|
<definition>collection - the symbol library</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>conducteur</source>
|
||||||
|
<target>condutor</target>
|
||||||
|
<definition>conductor - the drawn wire between terminals</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contact</source>
|
||||||
|
<target>contato</target>
|
||||||
|
<definition>contact</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contacteur</source>
|
||||||
|
<target>contator</target>
|
||||||
|
<definition>contactor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>courant</source>
|
||||||
|
<target>corrente</target>
|
||||||
|
<definition>current</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>disjoncteur</source>
|
||||||
|
<target>disjuntor</target>
|
||||||
|
<definition>circuit breaker</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>dossier</source>
|
||||||
|
<target>pasta</target>
|
||||||
|
<definition>folder</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>element</source>
|
||||||
|
<target>elemento</target>
|
||||||
|
<definition>element - a symbol placed on a sheet</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>esclave</source>
|
||||||
|
<target>escravo</target>
|
||||||
|
<definition>slave - the contact block belonging to a master</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fusible</source>
|
||||||
|
<target>fusível</target>
|
||||||
|
<definition>fuse</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>grille</source>
|
||||||
|
<target>grade</target>
|
||||||
|
<definition>grid</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>interrupteur</source>
|
||||||
|
<target>interruptor</target>
|
||||||
|
<definition>switch</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>maitre</source>
|
||||||
|
<target>mestre</target>
|
||||||
|
<definition>master - the element a cross-reference points from</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>modele</source>
|
||||||
|
<target>modelo</target>
|
||||||
|
<definition>template</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>moteur</source>
|
||||||
|
<target>motor</target>
|
||||||
|
<definition>motor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>neutre</source>
|
||||||
|
<target>neutro</target>
|
||||||
|
<definition>neutral</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>nomenclature</source>
|
||||||
|
<target>lista de materiais</target>
|
||||||
|
<definition>parts list / bill of materials</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>numerotation</source>
|
||||||
|
<target>numeração</target>
|
||||||
|
<definition>numbering - automatic wire and part numbering</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>phase</source>
|
||||||
|
<target>fase</target>
|
||||||
|
<definition>phase</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>potentiel</source>
|
||||||
|
<target>potencial</target>
|
||||||
|
<definition>potential - conductors electrically joined as one node</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>projet</source>
|
||||||
|
<target>projeto</target>
|
||||||
|
<definition>project - the .qet file holding the sheets</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>puissance</source>
|
||||||
|
<target>potência</target>
|
||||||
|
<definition>power</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>relais</source>
|
||||||
|
<target>relé</target>
|
||||||
|
<definition>relay</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>renvoi de folio</source>
|
||||||
|
<target>referência de folha</target>
|
||||||
|
<definition>sheet cross-reference</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>repere</source>
|
||||||
|
<target>designação</target>
|
||||||
|
<definition>designation - the identifying mark on a part</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>report de folio</source>
|
||||||
|
<target>remissão de folha</target>
|
||||||
|
<definition>sheet cross-reference (the arrow symbol)</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>schema</source>
|
||||||
|
<target>diagrama</target>
|
||||||
|
<definition>diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>section</source>
|
||||||
|
<target>seção</target>
|
||||||
|
<definition>cross-section - conductor size in mm2</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>sommaire</source>
|
||||||
|
<target>sumário</target>
|
||||||
|
<definition>summary / table of contents</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>tension</source>
|
||||||
|
<target>tensão</target>
|
||||||
|
<definition>voltage</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>terre</source>
|
||||||
|
<target>terra</target>
|
||||||
|
<definition>earth / ground</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>texte</source>
|
||||||
|
<target>texto</target>
|
||||||
|
<definition>text</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>transformateur</source>
|
||||||
|
<target>transformador</target>
|
||||||
|
<definition>transformer</definition>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,917 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="fr_FR" language="zh">
|
||||||
|
<phrase>
|
||||||
|
<source> Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4
|
||||||
|
</source>
|
||||||
|
<target>触点:常开:%1,常闭:%2,转换:%3,其他:%4
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8
|
||||||
|
</source>
|
||||||
|
<target>触点:常开:%1/%2,常闭:%3/%4,转换:%5/%6,其他:%7/%8
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> %</source>
|
||||||
|
<target>%</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> (Ctrl pendant le glissement = position libre, sans accrochage à la grille)</source>
|
||||||
|
<target>(拖动时按 Ctrl = 自由位置,不吸附到网格)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> -- %1 : mode %2</source>
|
||||||
|
<target>-- %1:%2 模式</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> ; point rouge : glisser pour repositionner le centre de rotation</source>
|
||||||
|
<target>;红点:拖动以重新定位旋转中心</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> ; point turquoise : arc</source>
|
||||||
|
<target>;青色点:圆弧</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> ; un bord : inclinaison</source>
|
||||||
|
<target>;边:倾斜</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> °</source>
|
||||||
|
<target>°</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source> — Cliquer : mode %1</source>
|
||||||
|
<target>— 单击:%1 模式</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>, Alt = créer des poignées</source>
|
||||||
|
<target>,Alt = 创建控制柄</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>, Alt = détacher en polyligne</source>
|
||||||
|
<target>,Alt = 分离为多段线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajoute une courbe de Bézier sur le folio actuel</source>
|
||||||
|
<target>在当前图纸上添加贝塞尔曲线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajouter un point à une courbe</source>
|
||||||
|
<target>向曲线添加点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ajouter une courbe</source>
|
||||||
|
<target>添加曲线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Angle</source>
|
||||||
|
<target>角度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Anguleux</source>
|
||||||
|
<target>角点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Aperçu</source>
|
||||||
|
<target>预览</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Arrondir les coins d'%1</source>
|
||||||
|
<target>将%1的角变圆</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Borne 1</source>
|
||||||
|
<target>端子1</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Borne 2</source>
|
||||||
|
<target>端子2</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Catégorie</source>
|
||||||
|
<target>类别</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Ce format ne prend pas en charge la transparence : l'image sera enregistrée telle qu'elle était avant l'application de la couleur transparente. Continuer ?</source>
|
||||||
|
<target>此格式不支持透明度:图像将按应用透明色之前的状态保存。是否继续?</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Champ de texte</source>
|
||||||
|
<target>文本字段</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic : positionner à la taille d'origine. Cliquer-glisser : positionner et redimensionner. Clic droit : pivoter de 90°. Ctrl+molette : ajuster la taille.</source>
|
||||||
|
<target>单击:按原始尺寸放置。单击拖动:放置并调整大小。右键:旋转 90°。Ctrl+滚轮:调整大小。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : point suivant ; double-clic ou Entrée : terminer ; clic droit : annuler le dernier point</source>
|
||||||
|
<target>左键单击:下一个点;双击或 Enter:结束;右键单击:撤销上一个点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le coin opposé (Maj = carré, Ctrl = depuis le centre + position libre, Ctrl+Maj = carré centré) ; clic droit : annuler</source>
|
||||||
|
<target>左键单击:放置对角(Shift = 正方形,Ctrl = 从中心 + 自由位置,Ctrl+Shift = 居中正方形);右键单击:取消</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le coin opposé (Maj = cercle, Ctrl = depuis le centre + position libre, Ctrl+Maj = cercle centré) ; clic droit : annuler</source>
|
||||||
|
<target>左键单击:放置对角(Shift = 圆形,Ctrl = 从中心 + 自由位置,Ctrl+Shift = 居中圆形);右键单击:取消</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le point de départ (Ctrl = position libre)</source>
|
||||||
|
<target>左键单击:放置起点(Ctrl = 自由位置)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le point final (Ctrl = position libre) ; clic droit : annuler</source>
|
||||||
|
<target>左键单击:放置终点(Ctrl = 自由位置);右键单击:取消</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le premier coin (Ctrl = point central, position libre)</source>
|
||||||
|
<target>左键单击:放置第一个角(Ctrl = 中心点,自由位置)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic gauche : positionner le premier point (Ctrl = position libre)</source>
|
||||||
|
<target>左键单击:放置第一个点(Ctrl = 自由位置)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Clic: point anguleux. Cliquer-glisser: point courbe. Clic sur le premier point: fermer. Échap/Entrée: terminer. Clic droit: annuler le dernier point.</source>
|
||||||
|
<target>单击:角点。单击拖动:曲线点。单击第一个点:闭合。Esc/Enter:结束。右键:撤销上一个点。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquer</source>
|
||||||
|
<target>单击</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquer : mode %1</source>
|
||||||
|
<target>单击:%1 模式</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquez pour choisir une couleur</source>
|
||||||
|
<target>点击选择颜色</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquez sur l'image pour ajouter une couleur. Ajustez la tolérance de chaque couleur avec son curseur, ou cliquez sur × pour la retirer.</source>
|
||||||
|
<target>单击图像以添加颜色。使用滑块调整每种颜色的容差,或单击 × 将其移除。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cliquez sur l'image pour choisir une couleur</source>
|
||||||
|
<target>单击图像选择颜色</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Coins arrondis</source>
|
||||||
|
<target>圆角</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Composant 1</source>
|
||||||
|
<target>元件1</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Composant 2</source>
|
||||||
|
<target>元件2</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir %1 en courbe de Bézier</source>
|
||||||
|
<target>将 %1 转换为贝塞尔曲线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir %1 en polyligne</source>
|
||||||
|
<target>将 %1 转换为多段线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir en courbe de Bézier</source>
|
||||||
|
<target>转换为贝塞尔曲线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Convertir en polyligne</source>
|
||||||
|
<target>转换为多段线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Couleur transparente</source>
|
||||||
|
<target>透明色</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Couleur transparente...</source>
|
||||||
|
<target>透明色...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Courant nominal</source>
|
||||||
|
<target>额定电流</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Deplacer le centre de rotation</source>
|
||||||
|
<target>移动旋转中心</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Distance in pixels between the label and the slave cross reference</source>
|
||||||
|
<target>标签与从站交叉引用之间的像素距离</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Distance label - slave :</source>
|
||||||
|
<target>标签距离 - 从站:</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Définir une couleur transparente</source>
|
||||||
|
<target>设置透明色</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Déformer une courbe</source>
|
||||||
|
<target>变形曲线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Déplacer le centre de rotation d'une image</source>
|
||||||
|
<target>移动图像的旋转中心</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Déverrouillé : largeur et hauteur peuvent être modifiées indépendamment. Cliquer pour verrouiller.</source>
|
||||||
|
<target>已解锁:宽度和高度可独立修改。单击锁定。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Enregistrer l'image d'origine sous...</source>
|
||||||
|
<target>原始图像另存为...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Enregistrer l'image sous...</source>
|
||||||
|
<target>图像另存为...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Erreur</source>
|
||||||
|
<target>错误</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Exclure de la nomenclature</source>
|
||||||
|
<target>从物料清单中排除</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faire pivoter %1</source>
|
||||||
|
<target>旋转 %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faire pivoter une image</source>
|
||||||
|
<target>旋转图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Faites glisser les poignées, ou l'intérieur du cadre, pour ajuster la zone à conserver.</source>
|
||||||
|
<target>拖动控制柄或框内区域,调整要保留的区域。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Fil</source>
|
||||||
|
<target>导线</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Folio</source>
|
||||||
|
<target>图页</target>
|
||||||
|
<definition>column title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Forme fermée</source>
|
||||||
|
<target>闭合形状</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser %1 : rotation (Maj = 15°)</source>
|
||||||
|
<target>拖动 %1:旋转(Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : ajuster l'arc (Ctrl = position libre, Maj = 15°)</source>
|
||||||
|
<target>拖动:调整圆弧(Ctrl = 自由位置,Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : arrondir les coins (Ctrl = position libre)</source>
|
||||||
|
<target>拖动:圆角(Ctrl = 自由位置)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déformer la courbe (Ctrl = position libre, Alt = briser la tangente)</source>
|
||||||
|
<target>拖动:变形曲线(Ctrl = 自由位置,Alt = 断开切线)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déplacer ce point</source>
|
||||||
|
<target>拖动:移动此点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déplacer le centre de rotation</source>
|
||||||
|
<target>拖动:移动旋转中心</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : déplacer le point (Ctrl = position libre</source>
|
||||||
|
<target>拖动:移动点(Ctrl = 自由位置</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : inclinaison (Ctrl = position libre, Maj = 15°)</source>
|
||||||
|
<target>拖动:倾斜(Ctrl = 自由位置,Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : incliner (Maj = par pas de 15°)</source>
|
||||||
|
<target>拖动:倾斜(Shift = 以 15° 为步长)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : pivoter (Maj = par pas de 15°)</source>
|
||||||
|
<target>拖动:旋转(Shift = 以 15° 为步长)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : redimensionner (Ctrl = depuis le centre + position libre, Maj = proportions</source>
|
||||||
|
<target>拖动:调整大小(Ctrl = 从中心 + 自由位置,Shift = 比例</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : redimensionner (Maj = conserver les proportions, Ctrl = depuis le centre)</source>
|
||||||
|
<target>拖动:调整大小(Shift = 保持比例,Ctrl = 从中心)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : repositionner le centre de rotation (Ctrl = position libre)</source>
|
||||||
|
<target>拖动:重新定位旋转中心(Ctrl = 自由位置)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser : rotation (Ctrl = position libre, Maj = 15°)</source>
|
||||||
|
<target>拖动:旋转(Ctrl = 自由位置,Shift = 15°)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser le point violet : arrondir les coins</source>
|
||||||
|
<target>拖动紫色点:圆角</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un coin : pivoter (Maj = par pas de 15°) ; glisser un bord : incliner (Maj = par pas de 15°) ; point rouge : déplacer le centre de rotation</source>
|
||||||
|
<target>拖动角:旋转(Shift = 以 15° 为步长);拖动边:倾斜(Shift = 以 15° 为步长);红点:移动旋转中心</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = conserver les proportions)</source>
|
||||||
|
<target>拖动角/边:调整大小(Ctrl = 从中心,Shift = 保持比例)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = proportions, Alt = détacher en polyligne)</source>
|
||||||
|
<target>拖动角/边:调整大小(Ctrl = 从中心,Shift = 比例,Alt = 分离为多段线)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser un point : le déplacer</source>
|
||||||
|
<target>拖动点:移动它</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser une extrémité : la déplacer</source>
|
||||||
|
<target>拖动端点:移动它</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Glisser une poignée ou la courbe : déformer (Alt = briser la tangente) ; Alt+glisser un point anguleux : créer des poignées ; clic droit : menu du nœud le plus proche</source>
|
||||||
|
<target>拖动控制柄或曲线:变形(Alt = 断开切线);Alt+拖动角点:创建控制柄;右键单击:最近节点菜单</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Géométrie</source>
|
||||||
|
<target>几何</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Hauteur</source>
|
||||||
|
<target>高度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image BMP (*.bmp)</source>
|
||||||
|
<target>BMP 图像 (*.bmp)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image Files (*.png *.jpg *.jpeg *.bmp *.svg)</source>
|
||||||
|
<target>图像文件 (*.png *.jpg *.jpeg *.bmp *.svg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image JPEG (*.jpg *.jpeg)</source>
|
||||||
|
<target>JPEG 图像 (*.jpg *.jpeg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image PNG (*.png)</source>
|
||||||
|
<target>PNG 图像 (*.png)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image source</source>
|
||||||
|
<target>源图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Image SVG (*.svg)</source>
|
||||||
|
<target>SVG 图像 (*.svg)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Images non incluses dans l'export DXF</source>
|
||||||
|
<target>图像未包含在 DXF 导出中</target>
|
||||||
|
<definition>message box title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible d'enregistrer l'image à cet emplacement.</source>
|
||||||
|
<target>无法将图像保存到该位置。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible d'enregistrer la nomenclature dans %1.
|
||||||
|
%2</source>
|
||||||
|
<target>无法将物料清单保存到 %1。
|
||||||
|
%2</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Impossible de charger l'image.</source>
|
||||||
|
<target>无法加载图像。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Inclinaison X</source>
|
||||||
|
<target>X 倾斜</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Inclinaison Y</source>
|
||||||
|
<target>Y 倾斜</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Incliner %1</source>
|
||||||
|
<target>倾斜 %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Incliner une image</source>
|
||||||
|
<target>倾斜图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>La limite fixée pour cet élément maître est atteinte (Limite: %1).
|
||||||
|
|
||||||
|
Voulez-vous tout de même lier ce contact esclave ?</source>
|
||||||
|
<target>已达到此主元件的限制(限制:%1)。
|
||||||
|
|
||||||
|
是否仍要链接此从属触点?</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Largeur</source>
|
||||||
|
<target>宽度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Le format DXF utilisé ici (AC1006) ne permet pas d'inclure d'image. Les images seront représentées uniquement par un rectangle de contour (position, taille, rotation et inclinaison conservées), sans le contenu de l'image.</source>
|
||||||
|
<target>此处使用的 DXF 格式 (AC1006) 无法包含图像。图像将仅以轮廓矩形表示(保留位置、大小、旋转和倾斜),不包含图像内容。</target>
|
||||||
|
<definition>message box content</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Lisse</source>
|
||||||
|
<target>平滑</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Liste de câblage</source>
|
||||||
|
<target>接线列表</target>
|
||||||
|
<definition>window title</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Liste de câblage (base de données)</source>
|
||||||
|
<target>接线列表(数据库)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Longueur</source>
|
||||||
|
<target>长度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>margin: 5px; font-weight: bold;</source>
|
||||||
|
<target>margin: 5px; font-weight: bold;</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir horizontal</source>
|
||||||
|
<target>水平镜像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir horizontal d'une image</source>
|
||||||
|
<target>图像水平镜像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir horizontal de %1</source>
|
||||||
|
<target>%1 的水平镜像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir impossible : inclinaison trop extrême pour cette forme</source>
|
||||||
|
<target>无法镜像:此形状的倾斜过于极端</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir vertical</source>
|
||||||
|
<target>垂直镜像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir vertical d'une image</source>
|
||||||
|
<target>图像垂直镜像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Miroir vertical de %1</source>
|
||||||
|
<target>%1 的垂直镜像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'un arc</source>
|
||||||
|
<target>修改弧的角度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'une forme</source>
|
||||||
|
<target>修改形状角度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'angle d'une image</source>
|
||||||
|
<target>修改图像角度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier l'inclinaison d'une image</source>
|
||||||
|
<target>修改图像倾斜</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la courbure d'%1</source>
|
||||||
|
<target>修改%1的曲率</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la forme d'%1</source>
|
||||||
|
<target>修改%1的形状</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la hauteur d'une image</source>
|
||||||
|
<target>修改图像高度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la largeur d'une image</source>
|
||||||
|
<target>修改图像宽度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la longueur d'une ligne</source>
|
||||||
|
<target>修改线条长度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier la taille d'une forme</source>
|
||||||
|
<target>修改形状大小</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier le type d'un nœud</source>
|
||||||
|
<target>修改节点类型</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modifier une image</source>
|
||||||
|
<target>修改图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Modèle</source>
|
||||||
|
<target>型号</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Nombre maximum de contacts esclaves définis : non défini
|
||||||
|
</source>
|
||||||
|
<target>已定义的从属触点最大数量:未定义
|
||||||
|
</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Notes</source>
|
||||||
|
<target>备注</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Nœud le plus proche</source>
|
||||||
|
<target>最近节点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>pivoter/incliner</source>
|
||||||
|
<target>旋转/倾斜</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rayon X</source>
|
||||||
|
<target>X半径</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rayon Y</source>
|
||||||
|
<target>Y半径</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>redimensionner</source>
|
||||||
|
<target>调整大小</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Redimensionner %1</source>
|
||||||
|
<target>调整 %1 大小</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Redimensionner une image</source>
|
||||||
|
<target>调整图像大小</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Remplacer l'image...</source>
|
||||||
|
<target>替换图像...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Remplacer une image</source>
|
||||||
|
<target>替换图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Restaurer les proportions</source>
|
||||||
|
<target>恢复比例</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Restaurer les proportions d'une image</source>
|
||||||
|
<target>恢复图像比例</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Retirer cette couleur</source>
|
||||||
|
<target>移除此颜色</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Revenir à l'image complète, sans rognage</source>
|
||||||
|
<target>恢复为完整图像,不裁剪</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>rgb(%1, %2, %3)</source>
|
||||||
|
<target>rgb(%1, %2, %3)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rogner l'image</source>
|
||||||
|
<target>裁剪图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rogner une image</source>
|
||||||
|
<target>裁剪图像</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rogner...</source>
|
||||||
|
<target>裁剪...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Rotation/Inclinaison</source>
|
||||||
|
<target>旋转/倾斜</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Réinitialiser</source>
|
||||||
|
<target>重置</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Selectionner une image...</source>
|
||||||
|
<target>选择图像...</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Supprimer le nœud le plus proche</source>
|
||||||
|
<target>删除最近节点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Supprimer un point d'une courbe</source>
|
||||||
|
<target>从曲线删除点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Symétrique</source>
|
||||||
|
<target>对称</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Séparation de potentiel</source>
|
||||||
|
<target>电位分隔</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Taille</source>
|
||||||
|
<target>大小</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tension nominale</source>
|
||||||
|
<target>额定电压</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tolérance pour cette couleur</source>
|
||||||
|
<target>此颜色的容差</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Tous les fichiers (*)</source>
|
||||||
|
<target>所有文件 (*)</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Transformer %1</source>
|
||||||
|
<target>变换 %1</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Transparence non conservée</source>
|
||||||
|
<target>不保留透明度</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>un arc</source>
|
||||||
|
<target>一段弧</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>un coin</source>
|
||||||
|
<target>一个角</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>un point</source>
|
||||||
|
<target>一个点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une courbe</source>
|
||||||
|
<target>一条曲线</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une extrémité</source>
|
||||||
|
<target>一个端点</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>une image</source>
|
||||||
|
<target>一张图像</target>
|
||||||
|
<definition>part of a sentence listing the content of a diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouiller la numérotation automatique</source>
|
||||||
|
<target>锁定自动编号</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouiller la position</source>
|
||||||
|
<target>锁定位置</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Verrouillé : modifier la largeur ou la hauteur ajuste l'autre pour conserver les proportions. Cliquer pour déverrouiller.</source>
|
||||||
|
<target>已锁定:修改宽度或高度会调整另一个以保持比例。单击解锁。</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Échec de l'enregistrement</source>
|
||||||
|
<target>保存失败</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Édition des nœuds</source>
|
||||||
|
<target>节点编辑</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>armoire</source>
|
||||||
|
<target>机柜</target>
|
||||||
|
<definition>cabinet / enclosure</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bobine</source>
|
||||||
|
<target>线圈</target>
|
||||||
|
<definition>coil</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>borne</source>
|
||||||
|
<target>端子</target>
|
||||||
|
<definition>terminal - a connection point on a symbol</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>bornier</source>
|
||||||
|
<target>端子排</target>
|
||||||
|
<definition>terminal strip</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cable</source>
|
||||||
|
<target>电缆</target>
|
||||||
|
<definition>cable</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>calibre</source>
|
||||||
|
<target>额定值</target>
|
||||||
|
<definition>rating - the current rating of a device</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>cartouche</source>
|
||||||
|
<target>标题栏</target>
|
||||||
|
<definition>title block - the framed information panel</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>collection</source>
|
||||||
|
<target>符号库</target>
|
||||||
|
<definition>collection - the symbol library</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>conducteur</source>
|
||||||
|
<target>导线</target>
|
||||||
|
<definition>conductor - the drawn wire between terminals</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contact</source>
|
||||||
|
<target>触点</target>
|
||||||
|
<definition>contact</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>contacteur</source>
|
||||||
|
<target>接触器</target>
|
||||||
|
<definition>contactor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>courant</source>
|
||||||
|
<target>电流</target>
|
||||||
|
<definition>current</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>disjoncteur</source>
|
||||||
|
<target>断路器</target>
|
||||||
|
<definition>circuit breaker</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>dossier</source>
|
||||||
|
<target>文件夹</target>
|
||||||
|
<definition>folder</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>element</source>
|
||||||
|
<target>元件</target>
|
||||||
|
<definition>element - a symbol placed on a sheet</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>esclave</source>
|
||||||
|
<target>从</target>
|
||||||
|
<definition>slave - the contact block belonging to a master</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>fusible</source>
|
||||||
|
<target>熔断器</target>
|
||||||
|
<definition>fuse</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>grille</source>
|
||||||
|
<target>网格</target>
|
||||||
|
<definition>grid</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>interrupteur</source>
|
||||||
|
<target>开关</target>
|
||||||
|
<definition>switch</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>maitre</source>
|
||||||
|
<target>主</target>
|
||||||
|
<definition>master - the element a cross-reference points from</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>modele</source>
|
||||||
|
<target>模板</target>
|
||||||
|
<definition>template</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>moteur</source>
|
||||||
|
<target>电动机</target>
|
||||||
|
<definition>motor</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>neutre</source>
|
||||||
|
<target>中性线</target>
|
||||||
|
<definition>neutral</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>nomenclature</source>
|
||||||
|
<target>物料清单</target>
|
||||||
|
<definition>parts list / bill of materials</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>numerotation</source>
|
||||||
|
<target>编号</target>
|
||||||
|
<definition>numbering - automatic wire and part numbering</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>phase</source>
|
||||||
|
<target>相</target>
|
||||||
|
<definition>phase</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>potentiel</source>
|
||||||
|
<target>电位</target>
|
||||||
|
<definition>potential - conductors electrically joined as one node</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>projet</source>
|
||||||
|
<target>项目</target>
|
||||||
|
<definition>project - the .qet file holding the sheets</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>puissance</source>
|
||||||
|
<target>功率</target>
|
||||||
|
<definition>power</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>relais</source>
|
||||||
|
<target>继电器</target>
|
||||||
|
<definition>relay</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>renvoi de folio</source>
|
||||||
|
<target>图纸交叉引用</target>
|
||||||
|
<definition>sheet cross-reference</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>repere</source>
|
||||||
|
<target>标识</target>
|
||||||
|
<definition>designation - the identifying mark on a part</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>report de folio</source>
|
||||||
|
<target>图纸交叉引用</target>
|
||||||
|
<definition>sheet cross-reference (the arrow symbol)</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>schema</source>
|
||||||
|
<target>原理图</target>
|
||||||
|
<definition>diagram</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>section</source>
|
||||||
|
<target>截面积</target>
|
||||||
|
<definition>cross-section - conductor size in mm2</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>sommaire</source>
|
||||||
|
<target>目录</target>
|
||||||
|
<definition>summary / table of contents</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>tension</source>
|
||||||
|
<target>电压</target>
|
||||||
|
<definition>voltage</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>terre</source>
|
||||||
|
<target>接地</target>
|
||||||
|
<definition>earth / ground</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>texte</source>
|
||||||
|
<target>文本</target>
|
||||||
|
<definition>text</definition>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>transformateur</source>
|
||||||
|
<target>变压器</target>
|
||||||
|
<definition>transformer</definition>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
+2968
-1648
File diff suppressed because it is too large
Load Diff
+3200
-1489
File diff suppressed because it is too large
Load Diff
+2954
-1527
File diff suppressed because it is too large
Load Diff
+2915
-1522
File diff suppressed because it is too large
Load Diff
+2801
-1380
File diff suppressed because it is too large
Load Diff
+2915
-1522
File diff suppressed because it is too large
Load Diff
+3097
-1666
File diff suppressed because it is too large
Load Diff
+2896
-1571
File diff suppressed because it is too large
Load Diff
+2813
-1735
File diff suppressed because it is too large
Load Diff
+2860
-1714
File diff suppressed because it is too large
Load Diff
+2867
-1706
File diff suppressed because it is too large
Load Diff
+2773
-1385
File diff suppressed because it is too large
Load Diff
+2903
-1566
File diff suppressed because it is too large
Load Diff
+2873
-1584
File diff suppressed because it is too large
Load Diff
+2900
-1529
File diff suppressed because it is too large
Load Diff
+2879
-1562
File diff suppressed because it is too large
Load Diff
+2900
-1579
File diff suppressed because it is too large
Load Diff
+2926
-1525
File diff suppressed because it is too large
Load Diff
+2798
-1389
File diff suppressed because it is too large
Load Diff
+2777
-1699
File diff suppressed because it is too large
Load Diff
+3228
-1489
File diff suppressed because it is too large
Load Diff
+2842
-1685
File diff suppressed because it is too large
Load Diff
+2799
-1386
File diff suppressed because it is too large
Load Diff
+2878
-1678
File diff suppressed because it is too large
Load Diff
+2862
-1766
File diff suppressed because it is too large
Load Diff
+2943
-1526
File diff suppressed because it is too large
Load Diff
+2838
-1742
File diff suppressed because it is too large
Load Diff
+2859
-1745
File diff suppressed because it is too large
Load Diff
+2838
-1742
File diff suppressed because it is too large
Load Diff
+2927
-1526
File diff suppressed because it is too large
Load Diff
+2897
-1527
File diff suppressed because it is too large
Load Diff
+2943
-1526
File diff suppressed because it is too large
Load Diff
+2773
-1385
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -62,6 +62,6 @@
|
|||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>12.3.0</string>
|
<string>14.0.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -99,11 +99,14 @@ fi
|
|||||||
|
|
||||||
cmake -S . -B "$BUILD_DIR" -G Ninja \
|
cmake -S . -B "$BUILD_DIR" -G Ninja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \
|
||||||
-DQT_VERSION_MAJOR=$QT_MAJOR \
|
-DQT_VERSION_MAJOR=$QT_MAJOR \
|
||||||
-DBUILD_WITH_KF=$BUILD_WITH_KF \
|
-DBUILD_WITH_KF=$BUILD_WITH_KF \
|
||||||
-DBUILD_KF=OFF \
|
-DBUILD_KF=OFF \
|
||||||
-DQET_EXPORT_PROJECT_DB=ON \
|
-DQET_EXPORT_PROJECT_DB=ON \
|
||||||
-DPACKAGE_TESTS=OFF
|
-DPACKAGE_TESTS=OFF \
|
||||||
|
-DQET_ENABLE_SPACEMOUSE=ON \
|
||||||
|
-DQET_SPACEMOUSE_BACKEND=hid
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "ERROR: cmake configure failed."
|
echo "ERROR: cmake configure failed."
|
||||||
@@ -256,6 +259,7 @@ echo "Install Info.plist and app icon:"
|
|||||||
cp -R ${current_dir}/misc/Info.plist $BUNDLE/Contents/
|
cp -R ${current_dir}/misc/Info.plist $BUNDLE/Contents/
|
||||||
cp -R ${current_dir}/ico/mac_icon/*.icns $BUNDLE/Contents/Resources/
|
cp -R ${current_dir}/ico/mac_icon/*.icns $BUNDLE/Contents/Resources/
|
||||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "$BUNDLE/Contents/Info.plist"
|
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "$BUNDLE/Contents/Info.plist"
|
||||||
|
/usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion 14.0.0" "$BUNDLE/Contents/Info.plist"
|
||||||
|
|
||||||
### add missing files ###############################################
|
### add missing files ###############################################
|
||||||
echo
|
echo
|
||||||
@@ -396,10 +400,12 @@ done
|
|||||||
|
|
||||||
echo "-- Signing main executable..."
|
echo "-- Signing main executable..."
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||||
|
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||||
"$BUNDLE/Contents/MacOS/$APPNAME"
|
"$BUNDLE/Contents/MacOS/$APPNAME"
|
||||||
|
|
||||||
echo "-- Signing bundle..."
|
echo "-- Signing bundle..."
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE"
|
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||||
|
--entitlements "${current_dir}/misc/qelectrotech.entitlements" "$BUNDLE"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Verifying bundle signature..."
|
echo "Verifying bundle signature..."
|
||||||
@@ -500,8 +506,10 @@ find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so"
|
|||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||||
done
|
done
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||||
|
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||||
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
||||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||||
|
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||||
"$MOUNT_POINT/$BUNDLE"
|
"$MOUNT_POINT/$BUNDLE"
|
||||||
|
|
||||||
echo "Verifying bundle signature inside DMG..."
|
echo "Verifying bundle signature inside DMG..."
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- 3D mouse with 3DxWare installed: lets QElectroTech load
|
||||||
|
3Dconnexion's client library, which 3Dconnexion signs, at run time
|
||||||
|
(sources/spacemouse/connexionbackend.h). The hardened runtime
|
||||||
|
refuses it otherwise, and the 3D mouse does nothing. -->
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+14
-6
@@ -33,7 +33,7 @@ here read the model.
|
|||||||
| `qet_diff` | **what an edit actually changed** — element moves, adds, removes, relabels; conductor changes; and folio fields, texts, shapes, images, symbol text fields and terminal strips |
|
| `qet_diff` | **what an edit actually changed** — element moves, adds, removes, relabels; conductor changes; and folio fields, texts, shapes, images, symbol text fields and terminal strips |
|
||||||
| `qet_scan` | sweep a directory of projects, counting nodes carrying an attribute |
|
| `qet_scan` | sweep a directory of projects, counting nodes carrying an attribute |
|
||||||
| `qet_element_info` | a `.elmt`: translated names, terminals, info fields, part counts |
|
| `qet_element_info` | a `.elmt`: translated names, terminals, info fields, part counts |
|
||||||
| `qet_export` | run a headless export (pdf, png, svg, bom, cables, wires, wiring, nets, links, info) |
|
| `qet_export` | run a headless export (pdf, png, svg, dxf, bom, cables, wires, wiring, nets, links, info) |
|
||||||
| `qet_edit` | **change a project** — place, move, rotate, label, wire, number, cross-reference, add text, shapes and images, restyle a symbol's text fields, delete; then diff the result |
|
| `qet_edit` | **change a project** — place, move, rotate, label, wire, number, cross-reference, add text, shapes and images, restyle a symbol's text fields, delete; then diff the result |
|
||||||
| `qet_element_build` | **author a `.elmt`** — draw a new symbol, with terminals to wire it by |
|
| `qet_element_build` | **author a `.elmt`** — draw a new symbol, with terminals to wire it by |
|
||||||
| `qet_project_new` | **start from nothing** — an empty project with a title and folios |
|
| `qet_project_new` | **start from nothing** — an empty project with a title and folios |
|
||||||
@@ -271,6 +271,13 @@ Python, plus the hang guard on `addConductor` and the database refresh in
|
|||||||
QElectroTech applies to its own custom-query box, not one invented here.
|
QElectroTech applies to its own custom-query box, not one invented here.
|
||||||
An empty result and a failed query are told apart: `row_count` 0 with no
|
An empty result and a failed query are told apart: `row_count` 0 with no
|
||||||
`error` means nothing matched, and a typo'd column name says so.
|
`error` means nothing matched, and a typo'd column name says so.
|
||||||
|
- **Texts, shapes and images are in the database too**, one row each in
|
||||||
|
`drawing_item_view` (`uuid`, `kind`, `folio`, position and size, and a
|
||||||
|
`description`: the shape type or the text). The uuid is the one saved in
|
||||||
|
the file, and every `qet_edit` op that takes a text, shape or image
|
||||||
|
`index` also takes that uuid, which does not shift the way an index
|
||||||
|
does. `qet_element_build` gives every part of a symbol a uuid as well,
|
||||||
|
returned in `part_uuids`; `qet_element_info` lists them in `part_list`.
|
||||||
- **`qet_export` isolates its launch.** SingleApplication keys its socket
|
- **`qet_export` isolates its launch.** SingleApplication keys its socket
|
||||||
on `applicationFilePath()`, so a second launch of the same binary path
|
on `applicationFilePath()`, so a second launch of the same binary path
|
||||||
forwards its request to an already-running instance and returns *that*
|
forwards its request to an already-running instance and returns *that*
|
||||||
@@ -295,11 +302,12 @@ Python, plus the hang guard on `addConductor` and the database refresh in
|
|||||||
no-op edit. Where an element predates persisted uuids the end cannot be
|
no-op edit. Where an element predates persisted uuids the end cannot be
|
||||||
resolved and keeps a `#`-marked unstable key; the diff then reports
|
resolved and keeps a `#`-marked unstable key; the diff then reports
|
||||||
`unstable_keys` and says so rather than pretending to be comparable.
|
`unstable_keys` and says so rather than pretending to be comparable.
|
||||||
- **Texts, shapes and images have no uuid**, so `qet_diff` cannot say "the same
|
- **Texts, shapes and images are keyed by uuid** when every one on both sides
|
||||||
text, edited": an edited text reads as the old one removed and a new one
|
has one, so an edit or a move reads as a change to that item. A file saved
|
||||||
added, both shown. Shapes and images are keyed by position, so a restyle
|
before they carried a uuid has none; for such a pair (including a legacy
|
||||||
or rescale *is* reported as a change to that item, but a move reads as a
|
file against its first re-save) that kind falls back to position, where an
|
||||||
removal plus an addition. The folio `version` attribute is left out of the
|
edited text reads as removed plus added and a move as a removal plus an
|
||||||
|
addition. Each section says which it used in `keyed_by`. The folio `version` attribute is left out of the
|
||||||
comparison on purpose: QElectroTech rewrites it on every save, and
|
comparison on purpose: QElectroTech rewrites it on every save, and
|
||||||
including it made every folio of any re-saved project look edited.
|
including it made every folio of any re-saved project look edited.
|
||||||
- **Elements** written before persisted uuids fall back to a positional key,
|
- **Elements** written before persisted uuids fall back to a positional key,
|
||||||
|
|||||||
+139
-54
@@ -69,6 +69,7 @@ EXPORT_FORMATS = {
|
|||||||
"pdf": "--export-pdf",
|
"pdf": "--export-pdf",
|
||||||
"png": "--export-png",
|
"png": "--export-png",
|
||||||
"svg": "--export-svg",
|
"svg": "--export-svg",
|
||||||
|
"dxf": "--export-dxf",
|
||||||
"bom": "--export-bom",
|
"bom": "--export-bom",
|
||||||
"cables": "--export-cables",
|
"cables": "--export-cables",
|
||||||
"wires": "--export-wires",
|
"wires": "--export-wires",
|
||||||
@@ -314,32 +315,42 @@ def _plain_text(html: str) -> str:
|
|||||||
def _extras(root: ET.Element) -> dict:
|
def _extras(root: ET.Element) -> dict:
|
||||||
"""Everything a folio holds besides elements and conductors.
|
"""Everything a folio holds besides elements and conductors.
|
||||||
|
|
||||||
Independent texts, shapes and images have no uuid, so nothing here can
|
Independent texts, shapes and images are records, not a keyed dict:
|
||||||
say "this is the same text, edited". A change to one therefore reads as
|
files written since they carry a uuid are compared by it (see
|
||||||
the old one removed and a new one added, with both shown -- accurate,
|
_diff_items), so an edited or moved text reads as that text, changed.
|
||||||
if less tidy than a move. Position is the identity they have.
|
Older files have only position to go on, and there a change reads as
|
||||||
|
the old one removed and a new one added, with both shown.
|
||||||
"""
|
"""
|
||||||
folios, texts, shapes, images = {}, {}, {}, {}
|
folios, texts, shapes, images = {}, [], [], []
|
||||||
|
|
||||||
|
def record(el, label, value):
|
||||||
|
return {"uuid": el.get("uuid", ""), "key": tuple(label.values()),
|
||||||
|
"label": label, "value": value}
|
||||||
|
|
||||||
for n, d in _folios(root):
|
for n, d in _folios(root):
|
||||||
folios[n] = {f: d.get(f, "") for f in _FOLIO_FIELDS}
|
folios[n] = {f: d.get(f, "") for f in _FOLIO_FIELDS}
|
||||||
for t in d.iter("input"):
|
for t in d.iter("input"):
|
||||||
key = (n, t.get("x", ""), t.get("y", ""), _plain_text(t.get("text", "")))
|
texts.append(record(
|
||||||
texts[key] = {"rotation": t.get("rotation", "0"),
|
t, {"folio": n, "x": t.get("x", ""), "y": t.get("y", ""),
|
||||||
"font": t.get("font", ""), "color": t.get("color", "")}
|
"text": _plain_text(t.get("text", ""))},
|
||||||
|
{"rotation": t.get("rotation", "0"),
|
||||||
|
"font": t.get("font", ""), "color": t.get("color", "")}))
|
||||||
for sh in d.iter("shape"):
|
for sh in d.iter("shape"):
|
||||||
pen, brush = sh.find("pen"), sh.find("brush")
|
pen, brush = sh.find("pen"), sh.find("brush")
|
||||||
key = (n, sh.get("type", ""), sh.get("x1", ""), sh.get("y1", ""),
|
shapes.append(record(
|
||||||
sh.get("x2", ""), sh.get("y2", ""))
|
sh, {"folio": n, "type": sh.get("type", ""),
|
||||||
shapes[key] = {
|
"from": [sh.get("x1", ""), sh.get("y1", "")],
|
||||||
"line_color": pen.get("color", "") if pen is not None else "",
|
"to": [sh.get("x2", ""), sh.get("y2", "")]},
|
||||||
"line_style": pen.get("style", "") if pen is not None else "",
|
{"line_color": pen.get("color", "") if pen is not None else "",
|
||||||
"line_width": pen.get("widthF", "") if pen is not None else "",
|
"line_style": pen.get("style", "") if pen is not None else "",
|
||||||
"fill": (brush.get("color", "") if brush is not None and
|
"line_width": pen.get("widthF", "") if pen is not None else "",
|
||||||
brush.get("style", "") != "NoBrush" else "none"),
|
"fill": (brush.get("color", "") if brush is not None and
|
||||||
"rotation": sh.get("rotation", "0")}
|
brush.get("style", "") != "NoBrush" else "none"),
|
||||||
|
"rotation": sh.get("rotation", "0")}))
|
||||||
for im in d.iter("image"):
|
for im in d.iter("image"):
|
||||||
key = (n, im.get("x", ""), im.get("y", ""))
|
images.append(record(
|
||||||
images[key] = {"scale": im.get("size", ""), "rotation": im.get("rotation", "")}
|
im, {"folio": n, "x": im.get("x", ""), "y": im.get("y", "")},
|
||||||
|
{"scale": im.get("size", ""), "rotation": im.get("rotation", "")}))
|
||||||
|
|
||||||
element_texts = {}
|
element_texts = {}
|
||||||
for n, d in _folios(root):
|
for n, d in _folios(root):
|
||||||
@@ -388,6 +399,32 @@ def _diff_keyed(a: dict, b: dict, label) -> dict:
|
|||||||
"changed": changed[:50]}
|
"changed": changed[:50]}
|
||||||
|
|
||||||
|
|
||||||
|
def _diff_items(a: list, b: list) -> dict:
|
||||||
|
"""_diff_keyed() over _extras() records of one kind.
|
||||||
|
|
||||||
|
Keyed on uuid only when every item on both sides has one. A file saved
|
||||||
|
before these items carried a uuid has none, and the first save by a
|
||||||
|
current QElectroTech gives them one, so a mixed pair falls back to
|
||||||
|
position for the whole kind rather than reading as everything removed
|
||||||
|
and re-added. On uuid, position is part of what is compared, so a move
|
||||||
|
is a change to that item.
|
||||||
|
"""
|
||||||
|
by_uuid = all(r["uuid"] for r in a + b)
|
||||||
|
|
||||||
|
def key(r):
|
||||||
|
return r["uuid"] if by_uuid else str(r["key"])
|
||||||
|
|
||||||
|
def keyed(rs):
|
||||||
|
return {key(r): ({**r["label"], **r["value"]} if by_uuid else r["value"])
|
||||||
|
for r in rs}
|
||||||
|
|
||||||
|
labels = {key(r): ({**r["label"], "uuid": r["uuid"]} if by_uuid else r["label"])
|
||||||
|
for r in a + b}
|
||||||
|
out = _diff_keyed(keyed(a), keyed(b), lambda k: labels[k])
|
||||||
|
out["keyed_by"] = "uuid" if by_uuid else "position"
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _diff_extras(before: ET.Element, after: ET.Element) -> dict:
|
def _diff_extras(before: ET.Element, after: ET.Element) -> dict:
|
||||||
a, b = _extras(before), _extras(after)
|
a, b = _extras(before), _extras(after)
|
||||||
out = {}
|
out = {}
|
||||||
@@ -407,13 +444,9 @@ def _diff_extras(before: ET.Element, after: ET.Element) -> dict:
|
|||||||
if len(a["folios"]) != len(b["folios"]) and folio_changes:
|
if len(a["folios"]) != len(b["folios"]) and folio_changes:
|
||||||
out["folios"]["note"] = ("the folio count changed, so changes listed here may be "
|
out["folios"]["note"] = ("the folio count changed, so changes listed here may be "
|
||||||
"later folios shifting position rather than edits")
|
"later folios shifting position rather than edits")
|
||||||
out["texts"] = _diff_keyed(a["texts"], b["texts"],
|
out["texts"] = _diff_items(a["texts"], b["texts"])
|
||||||
lambda k: {"folio": k[0], "x": k[1], "y": k[2], "text": k[3]})
|
out["shapes"] = _diff_items(a["shapes"], b["shapes"])
|
||||||
out["shapes"] = _diff_keyed(a["shapes"], b["shapes"],
|
out["images"] = _diff_items(a["images"], b["images"])
|
||||||
lambda k: {"folio": k[0], "type": k[1],
|
|
||||||
"from": [k[2], k[3]], "to": [k[4], k[5]]})
|
|
||||||
out["images"] = _diff_keyed(a["images"], b["images"],
|
|
||||||
lambda k: {"folio": k[0], "x": k[1], "y": k[2]})
|
|
||||||
# Keyed by element, what the field is bound to, and the nth such field.
|
# Keyed by element, what the field is bound to, and the nth such field.
|
||||||
# A field's own text is also compared ("shows"), so relabelling an
|
# A field's own text is also compared ("shows"), so relabelling an
|
||||||
# element shows up here as well as in the element's information.
|
# element shows up here as well as in the element's information.
|
||||||
@@ -594,14 +627,20 @@ def tool_element_info(path: str) -> dict:
|
|||||||
ordered, ambiguous = _terminals_in_index_order(list(root.iter("terminal")))
|
ordered, ambiguous = _terminals_in_index_order(list(root.iter("terminal")))
|
||||||
terminals = [{"index": i, "x": t.get("x"), "y": t.get("y"),
|
terminals = [{"index": i, "x": t.get("x"), "y": t.get("y"),
|
||||||
"orientation": t.get("orientation"),
|
"orientation": t.get("orientation"),
|
||||||
"name": t.get("name", ""), "type": t.get("type", "")}
|
"name": t.get("name", ""), "type": t.get("type", ""),
|
||||||
|
"uuid": t.get("uuid", "")}
|
||||||
for i, t in enumerate(ordered)]
|
for i, t in enumerate(ordered)]
|
||||||
info_fields = sorted({(i.text or "").strip()
|
info_fields = sorted({(i.text or "").strip()
|
||||||
for i in root.iter("info_name") if (i.text or "").strip()})
|
for i in root.iter("info_name") if (i.text or "").strip()})
|
||||||
parts = {}
|
parts = {}
|
||||||
|
part_list = []
|
||||||
desc = root.find("description")
|
desc = root.find("description")
|
||||||
for child in (desc if desc is not None else []):
|
for child in (desc if desc is not None else []):
|
||||||
parts[child.tag] = parts.get(child.tag, 0) + 1
|
parts[child.tag] = parts.get(child.tag, 0) + 1
|
||||||
|
if child.tag != "terminal":
|
||||||
|
# uuid is empty for a part saved before parts carried one; the
|
||||||
|
# element editor gives it one on the next save.
|
||||||
|
part_list.append({"type": child.tag, "uuid": child.get("uuid", "")})
|
||||||
return {
|
return {
|
||||||
"file": str(Path(path).expanduser()),
|
"file": str(Path(path).expanduser()),
|
||||||
"type": root.get("type", ""), "link_type": root.get("link_type", ""),
|
"type": root.get("type", ""), "link_type": root.get("link_type", ""),
|
||||||
@@ -614,6 +653,7 @@ def tool_element_info(path: str) -> dict:
|
|||||||
"index is undefined" if ambiguous else ""),
|
"index is undefined" if ambiguous else ""),
|
||||||
"info_fields": info_fields,
|
"info_fields": info_fields,
|
||||||
"parts": parts,
|
"parts": parts,
|
||||||
|
"part_list": part_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -851,11 +891,15 @@ def _validate_part(index: int, part) -> str:
|
|||||||
for key in spec["required"]:
|
for key in spec["required"]:
|
||||||
if key not in part:
|
if key not in part:
|
||||||
raise ValueError(f"part {index} ({kind}) is missing {key!r}")
|
raise ValueError(f"part {index} ({kind}) is missing {key!r}")
|
||||||
allowed = set(spec["required"]) | set(spec["optional"]) | {"type", "style", "antialias"}
|
allowed = set(spec["required"]) | set(spec["optional"]) | {"type", "style", "antialias", "uuid"}
|
||||||
for key in part:
|
for key in part:
|
||||||
if key not in allowed:
|
if key not in allowed:
|
||||||
raise ValueError(f"part {index} ({kind}): unexpected {key!r}; "
|
raise ValueError(f"part {index} ({kind}): unexpected {key!r}; "
|
||||||
f"allowed: {', '.join(sorted(allowed))}")
|
f"allowed: {', '.join(sorted(allowed))}")
|
||||||
|
if "uuid" in part and not (isinstance(part["uuid"], str)
|
||||||
|
and _UUID_RE.fullmatch(part["uuid"])):
|
||||||
|
raise ValueError(f"part {index} ({kind}): uuid must look like "
|
||||||
|
f"{{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}}, got {part['uuid']!r}")
|
||||||
if kind == "polygon":
|
if kind == "polygon":
|
||||||
pts = part["points"]
|
pts = part["points"]
|
||||||
if not isinstance(pts, list) or len(pts) < 2:
|
if not isinstance(pts, list) or len(pts) < 2:
|
||||||
@@ -866,9 +910,19 @@ def _validate_part(index: int, part) -> str:
|
|||||||
return kind
|
return kind
|
||||||
|
|
||||||
|
|
||||||
def _part_element(part: dict) -> ET.Element:
|
def _part_uuid(part: dict) -> str:
|
||||||
|
"""The caller's uuid for this part, braced as QElectroTech writes it, or
|
||||||
|
a new one. Every part carries one, as the element editor saves them."""
|
||||||
|
given = part.get("uuid")
|
||||||
|
if given:
|
||||||
|
return given if given.startswith("{") else "{" + given + "}"
|
||||||
|
return "{" + str(__import__("uuid").uuid4()) + "}"
|
||||||
|
|
||||||
|
|
||||||
|
def _part_element(part: dict, uuid: str) -> ET.Element:
|
||||||
kind = part["type"]
|
kind = part["type"]
|
||||||
node = ET.Element(kind)
|
node = ET.Element(kind)
|
||||||
|
node.set("uuid", uuid)
|
||||||
if kind == "polygon":
|
if kind == "polygon":
|
||||||
for n, (px, py) in enumerate(part["points"], start=1):
|
for n, (px, py) in enumerate(part["points"], start=1):
|
||||||
node.set(f"x{n}", _fmt(px))
|
node.set(f"x{n}", _fmt(px))
|
||||||
@@ -960,8 +1014,11 @@ def tool_element_build(output: str, names: dict, parts: list,
|
|||||||
ET.SubElement(kind, "kindInformation", {"name": key}).text = str(informations[key])
|
ET.SubElement(kind, "kindInformation", {"name": key}).text = str(informations[key])
|
||||||
ET.SubElement(root, "informations")
|
ET.SubElement(root, "informations")
|
||||||
description = ET.SubElement(root, "description")
|
description = ET.SubElement(root, "description")
|
||||||
for part in parts:
|
part_uuids = [_part_uuid(part) for part in parts]
|
||||||
description.append(_part_element(part))
|
if len(set(part_uuids)) != len(part_uuids):
|
||||||
|
raise ValueError("two parts were given the same uuid")
|
||||||
|
for part, part_uuid in zip(parts, part_uuids):
|
||||||
|
description.append(_part_element(part, part_uuid))
|
||||||
for t in terminals:
|
for t in terminals:
|
||||||
attrs = {"x": _fmt(t["x"]), "y": _fmt(t["y"]),
|
attrs = {"x": _fmt(t["x"]), "y": _fmt(t["y"]),
|
||||||
"orientation": t["orientation"],
|
"orientation": t["orientation"],
|
||||||
@@ -985,6 +1042,8 @@ def tool_element_build(output: str, names: dict, parts: list,
|
|||||||
# caller listed them in.
|
# caller listed them in.
|
||||||
"terminal_index_order": [t["name"] or f"({t['x']},{t['y']})"
|
"terminal_index_order": [t["name"] or f"({t['x']},{t['y']})"
|
||||||
for t in check["terminals"]],
|
for t in check["terminals"]],
|
||||||
|
# In the order the parts were given.
|
||||||
|
"part_uuids": part_uuids,
|
||||||
"verified": check}
|
"verified": check}
|
||||||
|
|
||||||
|
|
||||||
@@ -1034,8 +1093,9 @@ OPS = {
|
|||||||
("value", "str")]),
|
("value", "str")]),
|
||||||
"remove_plc_io": ("removePlcIO", [("folio", "folio"), ("element", "elmt"),
|
"remove_plc_io": ("removePlcIO", [("folio", "folio"), ("element", "elmt"),
|
||||||
("index", "folio")]),
|
("index", "folio")]),
|
||||||
# Texts and shapes have no uuid; they are addressed by index into a
|
# Texts, shapes and images are addressed by "index": their index in a
|
||||||
# position-sorted listing, and add_text/add_shape return that index so
|
# position-sorted listing, which add_text/add_shape return, or their
|
||||||
|
# uuid, which does not shift when another one is added. So
|
||||||
# it can be named as "$id". Indexes shift when one is added or deleted.
|
# it can be named as "$id". Indexes shift when one is added or deleted.
|
||||||
"delete_conductor": ("deleteConductor", [("folio", "folio"), ("element", "elmt"),
|
"delete_conductor": ("deleteConductor", [("folio", "folio"), ("element", "elmt"),
|
||||||
("terminal", "num")]),
|
("terminal", "num")]),
|
||||||
@@ -1091,38 +1151,38 @@ OPS = {
|
|||||||
("to_folio", "folio"), ("x", "num"), ("y", "num")]),
|
("to_folio", "folio"), ("x", "num"), ("y", "num")]),
|
||||||
"add_text": ("addText", [("folio", "folio"), ("text", "str"),
|
"add_text": ("addText", [("folio", "folio"), ("text", "str"),
|
||||||
("x", "num"), ("y", "num")]),
|
("x", "num"), ("y", "num")]),
|
||||||
"set_text": ("setTextContent", [("folio", "folio"), ("index", "folio"),
|
"set_text": ("setTextContent", [("folio", "folio"), ("index", "text"),
|
||||||
("text", "str")]),
|
("text", "str")]),
|
||||||
"set_text_color": ("setTextColor", [("folio", "folio"), ("index", "folio"),
|
"set_text_color": ("setTextColor", [("folio", "folio"), ("index", "text"),
|
||||||
("color", "str")]),
|
("color", "str")]),
|
||||||
"rotate_text": ("setTextRotation", [("folio", "folio"), ("index", "folio"),
|
"rotate_text": ("setTextRotation", [("folio", "folio"), ("index", "text"),
|
||||||
("angle", "num")]),
|
("angle", "num")]),
|
||||||
"delete_text": ("deleteText", [("folio", "folio"), ("index", "folio")]),
|
"delete_text": ("deleteText", [("folio", "folio"), ("index", "text")]),
|
||||||
"add_shape": ("addShape", [("folio", "folio"), ("shape", "str"),
|
"add_shape": ("addShape", [("folio", "folio"), ("shape", "str"),
|
||||||
("x1", "num"), ("y1", "num"),
|
("x1", "num"), ("y1", "num"),
|
||||||
("x2", "num"), ("y2", "num")]),
|
("x2", "num"), ("y2", "num")]),
|
||||||
"set_shape": ("setShapeProperty", [("folio", "folio"), ("index", "folio"),
|
"set_shape": ("setShapeProperty", [("folio", "folio"), ("index", "shape"),
|
||||||
("property", "str"), ("value", "str")]),
|
("property", "str"), ("value", "str")]),
|
||||||
"add_image": ("addImage", [("folio", "folio"), ("file", "str"),
|
"add_image": ("addImage", [("folio", "folio"), ("file", "str"),
|
||||||
("x", "num"), ("y", "num")]),
|
("x", "num"), ("y", "num")]),
|
||||||
"add_pdf_page": ("addPdfPage", [("folio", "folio"), ("file", "str"),
|
"add_pdf_page": ("addPdfPage", [("folio", "folio"), ("file", "str"),
|
||||||
("page", "num"), ("dpi", "num"),
|
("page", "num"), ("dpi", "num"),
|
||||||
("x", "num"), ("y", "num")]),
|
("x", "num"), ("y", "num")]),
|
||||||
"scale_image": ("setImageScale", [("folio", "folio"), ("index", "folio"),
|
"scale_image": ("setImageScale", [("folio", "folio"), ("index", "image"),
|
||||||
("factor", "num")]),
|
("factor", "num")]),
|
||||||
"rotate_image": ("setImageRotation", [("folio", "folio"), ("index", "folio"),
|
"rotate_image": ("setImageRotation", [("folio", "folio"), ("index", "image"),
|
||||||
("angle", "num")]),
|
("angle", "num")]),
|
||||||
"delete_image": ("deleteImage", [("folio", "folio"), ("index", "folio")]),
|
"delete_image": ("deleteImage", [("folio", "folio"), ("index", "image")]),
|
||||||
"delete_shape": ("deleteShape", [("folio", "folio"), ("index", "folio")]),
|
"delete_shape": ("deleteShape", [("folio", "folio"), ("index", "shape")]),
|
||||||
"add_polygon": ("addPolygon", [("folio", "folio"), ("points", "points"),
|
"add_polygon": ("addPolygon", [("folio", "folio"), ("points", "points"),
|
||||||
("closed", "bool")]),
|
("closed", "bool")]),
|
||||||
"set_shape_polygon": ("setShapePolygon", [("folio", "folio"), ("index", "folio"),
|
"set_shape_polygon": ("setShapePolygon", [("folio", "folio"), ("index", "shape"),
|
||||||
("points", "points")]),
|
("points", "points")]),
|
||||||
"add_path": ("addPath", [("folio", "folio"), ("nodes", "nodes"),
|
"add_path": ("addPath", [("folio", "folio"), ("nodes", "nodes"),
|
||||||
("closed", "bool")]),
|
("closed", "bool")]),
|
||||||
"set_shape_path_nodes": ("setShapePathNodes", [("folio", "folio"), ("index", "folio"),
|
"set_shape_path_nodes": ("setShapePathNodes", [("folio", "folio"), ("index", "shape"),
|
||||||
("nodes", "nodes")]),
|
("nodes", "nodes")]),
|
||||||
"set_shape_closed": ("setShapeClosed", [("folio", "folio"), ("index", "folio"),
|
"set_shape_closed": ("setShapeClosed", [("folio", "folio"), ("index", "shape"),
|
||||||
("closed", "bool")]),
|
("closed", "bool")]),
|
||||||
"add_table": ("addTable", [("folio", "folio"), ("kind", "str"),
|
"add_table": ("addTable", [("folio", "folio"), ("kind", "str"),
|
||||||
("name", "str"), ("query", "str")]),
|
("name", "str"), ("query", "str")]),
|
||||||
@@ -1161,6 +1221,9 @@ _REQUIRED_METHODS = sorted({m for m, _ in OPS.values() if m} |
|
|||||||
|
|
||||||
_MARKER = "QETEDIT "
|
_MARKER = "QETEDIT "
|
||||||
|
|
||||||
|
_UUID_RE = re.compile(r"\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
|
||||||
|
r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}?")
|
||||||
|
|
||||||
|
|
||||||
def _js(value) -> str:
|
def _js(value) -> str:
|
||||||
"""A JSON literal is a JavaScript literal for every type used here."""
|
"""A JSON literal is a JavaScript literal for every type used here."""
|
||||||
@@ -1175,11 +1238,15 @@ def _build_script(operations: list, output: str) -> str:
|
|||||||
JavaScript exception.
|
JavaScript exception.
|
||||||
"""
|
"""
|
||||||
refs: set[str] = set()
|
refs: set[str] = set()
|
||||||
|
# Resolvers a uuid reference needs; required only when one is used, so
|
||||||
|
# an index-only edit still runs on a build that predates them.
|
||||||
|
uuid_methods: set[str] = set()
|
||||||
|
folio_js = "0"
|
||||||
lines = [
|
lines = [
|
||||||
"// generated by qet-mcp; do not edit",
|
"// generated by qet-mcp; do not edit",
|
||||||
"var R = {};", # $name -> value from an earlier op
|
"var R = {};", # $name -> value from an earlier op
|
||||||
"var missing = [];",
|
"var missing = [];",
|
||||||
f"var need = {_js(_REQUIRED_METHODS)};",
|
"var need = @NEED@;",
|
||||||
"for (var i = 0; i < need.length; i++) {",
|
"for (var i = 0; i < need.length; i++) {",
|
||||||
" if (typeof qet[need[i]] !== 'function') missing.push(need[i]);",
|
" if (typeof qet[need[i]] !== 'function') missing.push(need[i]);",
|
||||||
"}",
|
"}",
|
||||||
@@ -1228,6 +1295,18 @@ def _build_script(operations: list, output: str) -> str:
|
|||||||
raise ValueError(f"operation {op_index}: {key!r} must be a non-empty list of "
|
raise ValueError(f"operation {op_index}: {key!r} must be a non-empty list of "
|
||||||
f"integer indices, got {value!r}")
|
f"integer indices, got {value!r}")
|
||||||
return _js(value)
|
return _js(value)
|
||||||
|
if kind in ("text", "shape", "image"):
|
||||||
|
# A uuid names the item for good; it is turned into the index
|
||||||
|
# the call takes at run time, by the item's own folio.
|
||||||
|
if isinstance(value, str) and _UUID_RE.fullmatch(value):
|
||||||
|
method = {"text": "textIndex", "shape": "shapeIndex",
|
||||||
|
"image": "imageIndex"}[kind]
|
||||||
|
uuid_methods.add(method)
|
||||||
|
return f"qet.{method}({folio_js}, {_js(value)})"
|
||||||
|
if not isinstance(value, int) or isinstance(value, bool):
|
||||||
|
raise ValueError(f"operation {op_index}: {key!r} must be a {kind} index, "
|
||||||
|
f"its uuid, or a \"$name\" reference, got {value!r}")
|
||||||
|
return _js(value)
|
||||||
if kind == "folio":
|
if kind == "folio":
|
||||||
if not isinstance(value, int) or isinstance(value, bool):
|
if not isinstance(value, int) or isinstance(value, bool):
|
||||||
raise ValueError(f"operation {op_index}: {key!r} must be a folio index "
|
raise ValueError(f"operation {op_index}: {key!r} must be a folio index "
|
||||||
@@ -1314,6 +1393,7 @@ def _build_script(operations: list, output: str) -> str:
|
|||||||
for key, kind in spec:
|
for key, kind in spec:
|
||||||
if key not in op:
|
if key not in op:
|
||||||
raise ValueError(f"operation {i} ({name}) is missing {key!r}")
|
raise ValueError(f"operation {i} ({name}) is missing {key!r}")
|
||||||
|
folio_js = args[0] if args else "0"
|
||||||
args.append(ref_or(op[key], kind, i, key))
|
args.append(ref_or(op[key], kind, i, key))
|
||||||
|
|
||||||
ident = op.get("id")
|
ident = op.get("id")
|
||||||
@@ -1359,7 +1439,8 @@ def _build_script(operations: list, output: str) -> str:
|
|||||||
lines.append(f" qet.log({_js(_MARKER)} + JSON.stringify("
|
lines.append(f" qet.log({_js(_MARKER)} + JSON.stringify("
|
||||||
"{kind: 'save', result: saved, stopped_early: stop}));")
|
"{kind: 'save', result: saved, stopped_early: stop}));")
|
||||||
lines.append("}")
|
lines.append("}")
|
||||||
return "\n".join(lines) + "\n"
|
need = _js(sorted(set(_REQUIRED_METHODS) | uuid_methods))
|
||||||
|
return "\n".join(line.replace("@NEED@", need) for line in lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def _parse_script_output(text: str) -> dict:
|
def _parse_script_output(text: str) -> dict:
|
||||||
@@ -2106,7 +2187,7 @@ TOOLS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "qet_export",
|
"name": "qet_export",
|
||||||
"description": "Run a QElectroTech export headlessly (pdf, png, svg, bom, "
|
"description": "Run a QElectroTech export headlessly (pdf, png, svg, dxf, bom, "
|
||||||
"cables, wires, wiring, nets, links, info). Launches the "
|
"cables, wires, wiring, nets, links, info). Launches the "
|
||||||
"binary in an isolated sandbox so it cannot be captured by, "
|
"binary in an isolated sandbox so it cannot be captured by, "
|
||||||
"or capture, a running QElectroTech.",
|
"or capture, a running QElectroTech.",
|
||||||
@@ -2268,10 +2349,12 @@ TOOLS = [
|
|||||||
"that element; set_element_text takes " +
|
"that element; set_element_text takes " +
|
||||||
", ".join(ELEMENT_TEXT_PROPERTIES) + ". A field bound with source "
|
", ".join(ELEMENT_TEXT_PROPERTIES) + ". A field bound with source "
|
||||||
"\"info\" follows set_label/set_info. Indexes shift on delete. "
|
"\"info\" follows set_label/set_info. Indexes shift on delete. "
|
||||||
"Texts and shapes have no uuid: add_text/add_shape return an "
|
"Texts, shapes and images: add_text/add_shape/add_image return an "
|
||||||
"index you can name as \"$id\", and the other text/shape ops "
|
"index you can name as \"$id\", and the other text/shape/image "
|
||||||
"take it as \"index\". Indexes shift when one is added or "
|
"ops take it as \"index\". Indexes shift when one is added or "
|
||||||
"deleted. Shapes: " + ", ".join(SHAPES) + "; set_shape takes " + ", ".join(SHAPE_PROPERTIES) +
|
"deleted; \"index\" also accepts the item's uuid (from the "
|
||||||
|
"project database's drawing_item_view, or a saved file), which "
|
||||||
|
"does not. Shapes: " + ", ".join(SHAPES) + "; set_shape takes " + ", ".join(SHAPE_PROPERTIES) +
|
||||||
" (fill accepts a colour or \"none\"). "
|
" (fill accepts a colour or \"none\"). "
|
||||||
"add_shape's own \"polygon\" is always the degenerate two-point "
|
"add_shape's own \"polygon\" is always the degenerate two-point "
|
||||||
"form (it shares add_shape's p1/p2 shape); add_polygon takes as "
|
"form (it shares add_shape's p1/p2 shape); add_polygon takes as "
|
||||||
@@ -2291,7 +2374,7 @@ TOOLS = [
|
|||||||
"shapes are listed by current on-folio position, so changing one "
|
"shapes are listed by current on-folio position, so changing one "
|
||||||
"shape's points can reorder it relative to the others -- re-list "
|
"shape's points can reorder it relative to the others -- re-list "
|
||||||
"before addressing one by index again if more than one is being "
|
"before addressing one by index again if more than one is being "
|
||||||
"edited in the same run. "
|
"edited in the same run, or address it by uuid. "
|
||||||
"Tables: add_table places a BOM/nomenclature or summary table "
|
"Tables: add_table places a BOM/nomenclature or summary table "
|
||||||
"(kind is \"nomenclature\" or \"summary\") built from a query "
|
"(kind is \"nomenclature\" or \"summary\") built from a query "
|
||||||
"against a project database view -- run qet.query() (the "
|
"against a project database view -- run qet.query() (the "
|
||||||
@@ -2512,7 +2595,9 @@ TOOLS = [
|
|||||||
'line x1,y1,x2,y2; rect/ellipse/arc x,y,width,height '
|
'line x1,y1,x2,y2; rect/ellipse/arc x,y,width,height '
|
||||||
"(arc also start,angle); circle x,y,diameter; polygon "
|
"(arc also start,angle); circle x,y,diameter; polygon "
|
||||||
'points:[[x,y],...] and closed; text x,y,text with optional '
|
'points:[[x,y],...] and closed; text x,y,text with optional '
|
||||||
"size, rotation, color. Any part may carry style and antialias. "
|
"size, rotation, color. Any part may carry style and antialias, "
|
||||||
|
"and a uuid to name it by; parts without one get a new uuid, "
|
||||||
|
"returned in part_uuids. "
|
||||||
"Coordinates are the element's own, with (0,0) at its origin.",
|
"Coordinates are the element's own, with (0,0) at its origin.",
|
||||||
"items": {"type": "object"},
|
"items": {"type": "object"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ needs_examples = unittest.skipUnless(have_binary and have_examples,
|
|||||||
COIL = "common://10_electric/10_allpole/310_relays_contactors_contacts/01_coils/bobine_ka_a_remanence.elmt"
|
COIL = "common://10_electric/10_allpole/310_relays_contactors_contacts/01_coils/bobine_ka_a_remanence.elmt"
|
||||||
SLAVE = ("common://10_electric/10_allpole/310_relays_contactors_contacts/"
|
SLAVE = ("common://10_electric/10_allpole/310_relays_contactors_contacts/"
|
||||||
"02_contacts_cross_referencing/15_protection_contacts/contact_relais_nf_esclave.elmt")
|
"02_contacts_cross_referencing/15_protection_contacts/contact_relais_nf_esclave.elmt")
|
||||||
|
SHARED_UUID = "{11111111-2222-3333-4444-555555555555}"
|
||||||
# The shipped "going/coming arrow" pair -- a folio-jump link, next_report on
|
# The shipped "going/coming arrow" pair -- a folio-jump link, next_report on
|
||||||
# one folio linked to previous_report on the next, one terminal each. Same
|
# one folio linked to previous_report on the next, one terminal each. Same
|
||||||
# category of element as issue #974's custom "naechste_folie_rechts.elmt" /
|
# category of element as issue #974's custom "naechste_folie_rechts.elmt" /
|
||||||
@@ -328,6 +329,29 @@ class EditValidation(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError, "list of strings"):
|
with self.assertRaisesRegex(ValueError, "list of strings"):
|
||||||
self.build([{"op": "add_autonum", "kind": "conductor", "name": "a", "parts": "string:W"}])
|
self.build([{"op": "add_autonum", "kind": "conductor", "name": "a", "parts": "string:W"}])
|
||||||
|
|
||||||
|
def test_a_uuid_index_is_resolved_at_run_time(self):
|
||||||
|
u = "{11111111-1111-4111-8111-111111111111}"
|
||||||
|
script = self.build([{"op": "set_shape", "folio": 2, "index": u,
|
||||||
|
"property": "fill", "value": "none"},
|
||||||
|
{"op": "delete_text", "folio": 1, "index": u},
|
||||||
|
{"op": "delete_image", "folio": 0, "index": u}])
|
||||||
|
self.assertIn(f'qet.setShapeProperty(2, qet.shapeIndex(2, "{u}"), ', script)
|
||||||
|
self.assertIn(f'qet.deleteText(1, qet.textIndex(1, "{u}"))', script)
|
||||||
|
self.assertIn(f'qet.deleteImage(0, qet.imageIndex(0, "{u}"))', script)
|
||||||
|
need = json.loads(re.search(r"var need = (\[.*?\]);", script).group(1))
|
||||||
|
self.assertTrue({"shapeIndex", "textIndex", "imageIndex"} <= set(need))
|
||||||
|
|
||||||
|
def test_an_index_only_edit_does_not_need_the_uuid_resolvers(self):
|
||||||
|
script = self.build([{"op": "delete_shape", "folio": 0, "index": 3}])
|
||||||
|
need = json.loads(re.search(r"var need = (\[.*?\]);", script).group(1))
|
||||||
|
self.assertFalse({"shapeIndex", "textIndex", "imageIndex"} & set(need))
|
||||||
|
|
||||||
|
def test_a_string_index_that_is_not_a_uuid_is_refused(self):
|
||||||
|
for bad in ("3", "{nope}", "11111111-1111"):
|
||||||
|
with self.subTest(index=bad):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.build([{"op": "delete_shape", "folio": 0, "index": bad}])
|
||||||
|
|
||||||
def test_indexed_references_and_element_lists(self):
|
def test_indexed_references_and_element_lists(self):
|
||||||
script = self.build([
|
script = self.build([
|
||||||
{"op": "add_folio", "id": "f"},
|
{"op": "add_folio", "id": "f"},
|
||||||
@@ -536,6 +560,30 @@ class ElementBuild(unittest.TestCase):
|
|||||||
{"x": 0, "y": -20, "orientation": "n", "name": "high"}])
|
{"x": 0, "y": -20, "orientation": "n", "name": "high"}])
|
||||||
self.assertEqual(r["terminal_index_order"], ["high", "low"])
|
self.assertEqual(r["terminal_index_order"], ["high", "low"])
|
||||||
|
|
||||||
|
def test_every_part_gets_its_own_uuid(self):
|
||||||
|
parts = [{"type": "line", "x1": 0, "y1": 0, "x2": 10, "y2": 0},
|
||||||
|
{"type": "rect", "x": 0, "y": 0, "width": 5, "height": 5},
|
||||||
|
{"type": "text", "x": 0, "y": 0, "text": "K"}]
|
||||||
|
r = self.build(parts=parts)
|
||||||
|
desc = ET.parse(self.out).getroot().find("description")
|
||||||
|
written = [e.get("uuid") for e in desc if e.tag != "terminal"]
|
||||||
|
self.assertEqual(written, r["part_uuids"])
|
||||||
|
self.assertEqual(len(set(written)), 3)
|
||||||
|
self.assertEqual([p["uuid"] for p in r["verified"]["part_list"]], written)
|
||||||
|
|
||||||
|
def test_a_given_part_uuid_is_kept_and_braced(self):
|
||||||
|
r = self.build(parts=[{"type": "line", "x1": 0, "y1": 0, "x2": 1, "y2": 0,
|
||||||
|
"uuid": "11111111-1111-4111-8111-111111111111"}])
|
||||||
|
self.assertEqual(r["part_uuids"], ["{11111111-1111-4111-8111-111111111111}"])
|
||||||
|
|
||||||
|
def test_bad_or_repeated_part_uuids_are_refused(self):
|
||||||
|
line = {"type": "line", "x1": 0, "y1": 0, "x2": 1, "y2": 0}
|
||||||
|
u = "{11111111-1111-4111-8111-111111111111}"
|
||||||
|
for parts in ([{**line, "uuid": "nope"}], [{**line, "uuid": u}, {**line, "uuid": u}]):
|
||||||
|
with self.subTest(parts=parts):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.build(parts=parts)
|
||||||
|
|
||||||
def test_output_reads_back(self):
|
def test_output_reads_back(self):
|
||||||
r = self.build(names={"en": "Coil", "fr": "Bobine"})
|
r = self.build(names={"en": "Coil", "fr": "Bobine"})
|
||||||
self.assertEqual(r["verified"]["names"], {"en": "Coil", "fr": "Bobine"})
|
self.assertEqual(r["verified"]["names"], {"en": "Coil", "fr": "Bobine"})
|
||||||
@@ -632,14 +680,17 @@ class Diff(unittest.TestCase):
|
|||||||
|
|
||||||
def project(self, name, *, texts=(), shapes=(), images=(), author="", version="1",
|
def project(self, name, *, texts=(), shapes=(), images=(), author="", version="1",
|
||||||
strips=(), conductors=()):
|
strips=(), conductors=()):
|
||||||
inputs = "".join(f'<input x="{x}" y="{y}" rotation="0" font="f" '
|
# An optional last member of each tuple is the item's uuid.
|
||||||
|
uid = lambda u: f' uuid="{u[0]}"' if u else ""
|
||||||
|
inputs = "".join(f'<input x="{x}" y="{y}" rotation="0" font="f"{uid(u)} '
|
||||||
f'text="<html><body><p>{t}</p></body></html>"/>'
|
f'text="<html><body><p>{t}</p></body></html>"/>'
|
||||||
for x, y, t in texts)
|
for x, y, t, *u in texts)
|
||||||
shp = "".join(f'<shape type="Rectangle" x1="{a}" y1="{b}" x2="{c}" y2="{d}">'
|
shp = "".join(f'<shape type="Rectangle" x1="{a}" y1="{b}" x2="{c}" y2="{d}"{uid(u)}>'
|
||||||
f'<pen color="{pc}" style="SolidLine" widthF="1"/>'
|
f'<pen color="{pc}" style="SolidLine" widthF="1"/>'
|
||||||
f'<brush color="#fff" style="SolidPattern"/></shape>'
|
f'<brush color="#fff" style="SolidPattern"/></shape>'
|
||||||
for a, b, c, d, pc in shapes)
|
for a, b, c, d, pc, *u in shapes)
|
||||||
img = "".join(f'<image x="{x}" y="{y}" size="{s}" rotation="0"/>' for x, y, s in images)
|
img = "".join(f'<image x="{x}" y="{y}" size="{s}" rotation="0"{uid(u)}/>'
|
||||||
|
for x, y, s, *u in images)
|
||||||
st = "".join(f'<terminal_strip><terminal_strip_data uuid="{u}"><informations>'
|
st = "".join(f'<terminal_strip><terminal_strip_data uuid="{u}"><informations>'
|
||||||
f'<information name="name">{n}</information></informations>'
|
f'<information name="name">{n}</information></informations>'
|
||||||
f'</terminal_strip_data><layout/></terminal_strip>' for u, n in strips)
|
f'</terminal_strip_data><layout/></terminal_strip>' for u, n in strips)
|
||||||
@@ -669,6 +720,39 @@ class Diff(unittest.TestCase):
|
|||||||
self.assertEqual([t["text"] for t in d["removed"]], ["note"])
|
self.assertEqual([t["text"] for t in d["removed"]], ["note"])
|
||||||
self.assertEqual([t["text"] for t in d["added"]], ["note EDITED"])
|
self.assertEqual([t["text"] for t in d["added"]], ["note EDITED"])
|
||||||
|
|
||||||
|
U1 = "{11111111-1111-4111-8111-111111111111}"
|
||||||
|
U2 = "{22222222-2222-4222-8222-222222222222}"
|
||||||
|
|
||||||
|
def test_edited_text_with_a_uuid_is_a_change_to_that_text(self):
|
||||||
|
a = self.project("a.qet", texts=[(1, 2, "note", self.U1)])
|
||||||
|
b = self.project("b.qet", texts=[(1, 2, "note EDITED", self.U1)])
|
||||||
|
d = m.tool_diff(a, b)["texts"]
|
||||||
|
self.assertEqual(d["keyed_by"], "uuid")
|
||||||
|
self.assertEqual((d["added"], d["removed"]), ([], []))
|
||||||
|
self.assertEqual(d["changed"][0]["changed"]["text"], ["note", "note EDITED"])
|
||||||
|
|
||||||
|
def test_moved_shape_with_a_uuid_is_a_change_to_that_shape(self):
|
||||||
|
a = self.project("a.qet", shapes=[(0, 0, 5, 5, "#000000", self.U1),
|
||||||
|
(9, 9, 12, 12, "#000000", self.U2)])
|
||||||
|
b = self.project("b.qet", shapes=[(100, 0, 105, 5, "#000000", self.U1),
|
||||||
|
(9, 9, 12, 12, "#000000", self.U2)])
|
||||||
|
d = m.tool_diff(a, b)["shapes"]
|
||||||
|
self.assertEqual((d["added"], d["removed"]), ([], []))
|
||||||
|
self.assertEqual(len(d["changed"]), 1)
|
||||||
|
self.assertEqual(d["changed"][0]["item"]["uuid"], self.U1)
|
||||||
|
self.assertEqual(d["changed"][0]["changed"]["from"], [["0", "0"], ["100", "0"]])
|
||||||
|
|
||||||
|
def test_a_file_without_uuids_against_its_resave_is_keyed_by_position(self):
|
||||||
|
"""The first save by a current QElectroTech adds uuids to a legacy
|
||||||
|
file: that must not read as every item removed and re-added."""
|
||||||
|
a = self.project("a.qet", texts=[(1, 2, "note")], images=[(3, 3, 1)])
|
||||||
|
b = self.project("b.qet", texts=[(1, 2, "note", self.U1)], images=[(3, 3, 1, self.U2)])
|
||||||
|
d = m.tool_diff(a, b)
|
||||||
|
for k in ("texts", "images"):
|
||||||
|
with self.subTest(section=k):
|
||||||
|
self.assertEqual(d[k]["keyed_by"], "position")
|
||||||
|
self.assertFalse(d[k]["added"] or d[k]["removed"] or d[k]["changed"])
|
||||||
|
|
||||||
def test_shape_restyle_is_a_change_to_that_item(self):
|
def test_shape_restyle_is_a_change_to_that_item(self):
|
||||||
a = self.project("a.qet", shapes=[(0, 0, 5, 5, "#000000")])
|
a = self.project("a.qet", shapes=[(0, 0, 5, 5, "#000000")])
|
||||||
b = self.project("b.qet", shapes=[(0, 0, 5, 5, "#ff0000")])
|
b = self.project("b.qet", shapes=[(0, 0, 5, 5, "#ff0000")])
|
||||||
@@ -1975,6 +2059,85 @@ class Integration(unittest.TestCase):
|
|||||||
self.assertEqual(len(d["shapes"]["added"]), 1)
|
self.assertEqual(len(d["shapes"]["added"]), 1)
|
||||||
self.assertEqual(len(d["images"]["added"]), 1)
|
self.assertEqual(len(d["images"]["added"]), 1)
|
||||||
|
|
||||||
|
def test_drawing_items_are_in_the_database_and_addressable_by_uuid(self):
|
||||||
|
base = self.sb.new()
|
||||||
|
r = self.ok(self.sb.edit(base, [
|
||||||
|
{"op": "add_text", "folio": 0, "text": "note", "x": 50, "y": 50},
|
||||||
|
{"op": "add_shape", "folio": 0, "shape": "rectangle", "x1": 10, "y1": 10, "x2": 200, "y2": 120}]))
|
||||||
|
root = ET.parse(r["output"]).getroot()
|
||||||
|
shape_uuid = next(root.iter("shape")).get("uuid")
|
||||||
|
text_uuid = next(root.iter("input")).get("uuid")
|
||||||
|
rows = m.tool_query(BINARY, r["output"],
|
||||||
|
"SELECT uuid, kind, folio, description FROM drawing_item_view "
|
||||||
|
"ORDER BY kind")["rows"]
|
||||||
|
self.assertEqual([(x["uuid"], x["kind"], x["folio"]) for x in rows],
|
||||||
|
[(shape_uuid, "shape", 1), (text_uuid, "text", 1)])
|
||||||
|
self.assertEqual(rows[1]["description"], "note")
|
||||||
|
|
||||||
|
# A second shape placed above the first shifts its index to 1; the
|
||||||
|
# uuid still names it.
|
||||||
|
r2 = self.ok(self.sb.edit(r["output"], [
|
||||||
|
{"op": "add_shape", "folio": 0, "shape": "line", "x1": 0, "y1": 0, "x2": 5, "y2": 0},
|
||||||
|
{"op": "set_shape", "folio": 0, "index": shape_uuid, "property": "fill", "value": "#00ff00"}],
|
||||||
|
out="out2.qet"))
|
||||||
|
d = r2["diff"]["shapes"]
|
||||||
|
self.assertEqual(d["keyed_by"], "uuid")
|
||||||
|
self.assertEqual([c["item"]["uuid"] for c in d["changed"]], [shape_uuid])
|
||||||
|
self.assertEqual(len(d["added"]), 1)
|
||||||
|
|
||||||
|
def _old_file(self, same_shape_uuid=False):
|
||||||
|
"""A folio with a text and two shapes, as a version without
|
||||||
|
drawing-item uuids wrote it -- or, with same_shape_uuid, hand-edited
|
||||||
|
so that both shapes claim the same one."""
|
||||||
|
r = self.ok(self.sb.edit(self.sb.new(), [
|
||||||
|
{"op": "add_text", "folio": 0, "text": "note", "x": 50, "y": 50},
|
||||||
|
{"op": "add_shape", "folio": 0, "shape": "rectangle", "x1": 10, "y1": 10, "x2": 200, "y2": 120},
|
||||||
|
{"op": "add_shape", "folio": 0, "shape": "line", "x1": 0, "y1": 0, "x2": 5, "y2": 0}]))
|
||||||
|
tree = ET.parse(r["output"])
|
||||||
|
shapes = list(tree.getroot().iter("shape"))
|
||||||
|
for item in [*tree.getroot().iter("input"), *shapes]:
|
||||||
|
del item.attrib["uuid"]
|
||||||
|
if same_shape_uuid:
|
||||||
|
for shape in shapes:
|
||||||
|
shape.set("uuid", SHARED_UUID)
|
||||||
|
tree.write(self.sb.p("old.qet"), encoding="utf-8", xml_declaration=True)
|
||||||
|
return self.sb.p("old.qet")
|
||||||
|
|
||||||
|
def _db_uuids(self, project):
|
||||||
|
rows = m.tool_query(BINARY, project, "SELECT uuid, kind FROM drawing_item_view")["rows"]
|
||||||
|
return sorted((x["uuid"], x["kind"]) for x in rows)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _file_uuids(project):
|
||||||
|
root = ET.parse(project).getroot()
|
||||||
|
return sorted((item.get("uuid"), kind)
|
||||||
|
for tag, kind in (("shape", "shape"), ("input", "text"))
|
||||||
|
for item in root.iter(tag))
|
||||||
|
|
||||||
|
def test_a_file_without_drawing_uuids_gets_the_same_ones_on_every_open(self):
|
||||||
|
old = self._old_file()
|
||||||
|
first = self._db_uuids(old)
|
||||||
|
self.assertEqual(len(first), 3)
|
||||||
|
self.assertEqual(len({u for u, _ in first}), 3, "each item gets its own")
|
||||||
|
self.assertEqual(self._db_uuids(old), first)
|
||||||
|
|
||||||
|
def test_the_first_save_writes_those_uuids_and_a_reopen_keeps_them(self):
|
||||||
|
old = self._old_file()
|
||||||
|
derived = self._db_uuids(old)
|
||||||
|
saved = self.ok(self.sb.edit(old, [{"op": "add_folio"}], out="saved.qet"))["output"]
|
||||||
|
self.assertEqual(self._file_uuids(saved), derived)
|
||||||
|
self.assertEqual(self._db_uuids(saved), derived)
|
||||||
|
# Saving the same old file again writes the same uuids (#754).
|
||||||
|
again = self.ok(self.sb.edit(old, [{"op": "add_folio"}], out="again.qet"))["output"]
|
||||||
|
self.assertEqual(self._file_uuids(again), derived)
|
||||||
|
|
||||||
|
def test_a_uuid_repeated_in_a_hand_edited_file_names_one_item_only(self):
|
||||||
|
old = self._old_file(same_shape_uuid=True)
|
||||||
|
shapes = [u for u, kind in self._db_uuids(old) if kind == "shape"]
|
||||||
|
self.assertEqual(len(set(shapes)), 2)
|
||||||
|
self.assertIn(SHARED_UUID, shapes)
|
||||||
|
self.assertEqual([u for u, kind in self._db_uuids(old) if kind == "shape"], shapes)
|
||||||
|
|
||||||
# ---- polygon and path shapes ----
|
# ---- polygon and path shapes ----
|
||||||
|
|
||||||
def test_add_polygon_with_more_than_two_points(self):
|
def test_add_polygon_with_more_than_two_points(self):
|
||||||
|
|||||||
Executable
+659
@@ -0,0 +1,659 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# 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/>.
|
||||||
|
r"""Record raw USB reports from a 3Dconnexion 3D mouse, for QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech's Windows/macOS 3D mouse support reads the device directly
|
||||||
|
over USB, so it has to decode each device model's raw reports itself.
|
||||||
|
This records what your device sends while you make a few guided
|
||||||
|
movements, and saves it to one file you can attach to discussion #599.
|
||||||
|
Nothing is sent anywhere. One recording per device model is enough, from
|
||||||
|
any of the three systems: the device sends the same reports on all of them.
|
||||||
|
|
||||||
|
It takes about two minutes. Each step says what to do: press Enter, make
|
||||||
|
the movement and hold it until the next prompt. Push firmly.
|
||||||
|
|
||||||
|
Linux
|
||||||
|
Python 3 is already installed.
|
||||||
|
1. Download: curl -LO https://raw.githubusercontent.com/qelectrotech/qelectrotech-source-mirror/master/misc/spacemouse-capture.py
|
||||||
|
2. List: sudo python3 spacemouse-capture.py --list
|
||||||
|
3. Record: sudo python3 spacemouse-capture.py --seconds 3
|
||||||
|
If --list shows several devices (a Logitech receiver shows up as
|
||||||
|
several), add --device /dev/hidrawN with the 3D mouse's line.
|
||||||
|
sudo is needed because /dev/hidraw* is readable by root only.
|
||||||
|
spacenavd can keep running; if the recording comes out empty, stop it
|
||||||
|
(sudo systemctl stop spacenavd) and try again.
|
||||||
|
|
||||||
|
macOS
|
||||||
|
Python 3 comes with the Xcode command line tools; if "python3" asks
|
||||||
|
to install them, accept. Open Terminal, then:
|
||||||
|
1. Download: curl -LO https://raw.githubusercontent.com/qelectrotech/qelectrotech-source-mirror/master/misc/spacemouse-capture.py
|
||||||
|
2. Record: python3 spacemouse-capture.py --seconds 3
|
||||||
|
No sudo. If it says another program holds the device, quit 3DxWare
|
||||||
|
(or uninstall it) and try again. If it says macOS refused access,
|
||||||
|
allow Terminal in System Settings > Privacy & Security > Input
|
||||||
|
Monitoring, and try again. (Rather not use Terminal? The SpaceMouse
|
||||||
|
Check app does the same with a window: see discussion #599.)
|
||||||
|
|
||||||
|
Windows
|
||||||
|
Install Python 3 from https://www.python.org/downloads/ (tick "Add
|
||||||
|
python.exe to PATH") or from the Microsoft Store. Open a Command
|
||||||
|
Prompt (Windows key, type cmd, Enter), then:
|
||||||
|
1. cd %USERPROFILE%\Downloads
|
||||||
|
2. Download: curl -LO https://raw.githubusercontent.com/qelectrotech/qelectrotech-source-mirror/master/misc/spacemouse-capture.py
|
||||||
|
3. Record: python spacemouse-capture.py --seconds 3
|
||||||
|
No administrator rights needed, and 3DxWare can keep running.
|
||||||
|
Windows does not give out the device's report descriptor, so a
|
||||||
|
recording made on Linux or macOS is a little more complete.
|
||||||
|
|
||||||
|
Every system
|
||||||
|
--list only show the 3D mice found
|
||||||
|
--device PATH pick one, if several are found (a path from --list)
|
||||||
|
--seconds N multiply the time per step (3 triples it)
|
||||||
|
-o FILE where to save (default: spacemouse-capture-<id>.json)
|
||||||
|
|
||||||
|
The file is saved in the current folder: attach it to discussion #599.
|
||||||
|
Only Python's standard library is used.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import select
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
VENDORS = {0x046D: 'Logitech (older 3Dconnexion)', 0x256F: '3Dconnexion'}
|
||||||
|
|
||||||
|
STEPS = [
|
||||||
|
('rest', 'Do not touch the device.', 3),
|
||||||
|
('right', 'Push the cap to the RIGHT and hold it, then let go.', 4),
|
||||||
|
('left', 'Push the cap to the LEFT and hold it, then let go.', 4),
|
||||||
|
('away', 'Push the cap AWAY from you and hold it, then let go.', 4),
|
||||||
|
('toward', 'Pull the cap TOWARDS you and hold it, then let go.', 4),
|
||||||
|
('down', 'Press the cap DOWN and hold it, then let go.', 4),
|
||||||
|
('up', 'Lift the cap UP and hold it, then let go.', 4),
|
||||||
|
('twist_cw', 'TWIST the cap CLOCKWISE (seen from above) and hold, then let go.', 4),
|
||||||
|
('twist_ccw', 'TWIST the cap ANTICLOCKWISE and hold, then let go.', 4),
|
||||||
|
('tilt_away', 'TILT the cap AWAY from you and hold, then let go.', 4),
|
||||||
|
('tilt_right', 'TILT the cap to the RIGHT and hold, then let go.', 4),
|
||||||
|
('buttons', 'Press each button once, slowly, one at a time, in any order.', 15),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Processes that may also be reading the device.
|
||||||
|
OTHER_READERS = ('3dconnexion', '3dx', 'spacenavd') # 3DxWare: 3DconnexionHelper, 3DxNLServer...
|
||||||
|
|
||||||
|
|
||||||
|
def other_readers():
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
try:
|
||||||
|
out = subprocess.run(['tasklist', '/fo', 'csv', '/nh'], capture_output=True,
|
||||||
|
text=True, timeout=10).stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return ['unknown']
|
||||||
|
names = {line.split('","')[0].strip('"') for line in out.splitlines() if line}
|
||||||
|
return sorted(n for n in names if any(r in n.lower() for r in OTHER_READERS))
|
||||||
|
try:
|
||||||
|
out = subprocess.run(['ps', '-A', '-o', 'comm='], capture_output=True,
|
||||||
|
text=True, timeout=5).stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return ['unknown']
|
||||||
|
names = {os.path.basename(line.strip()) for line in out.splitlines()}
|
||||||
|
return sorted(n for n in names if any(r in n.lower() for r in OTHER_READERS))
|
||||||
|
|
||||||
|
|
||||||
|
# --- Linux: /dev/hidraw ----------------------------------------------------
|
||||||
|
|
||||||
|
class HidrawDevice:
|
||||||
|
backend = 'hidraw'
|
||||||
|
|
||||||
|
def __init__(self, path, name='?', vendor='?', product='?', sysfs=None):
|
||||||
|
self.path, self.name, self.vendor, self.product = path, name, vendor, product
|
||||||
|
self.sysfs = sysfs
|
||||||
|
self.fd = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find():
|
||||||
|
found = []
|
||||||
|
for sysdir in sorted(glob.glob('/sys/class/hidraw/hidraw*')):
|
||||||
|
try:
|
||||||
|
with open(os.path.join(sysdir, 'device', 'uevent')) as f:
|
||||||
|
uevent = dict(line.strip().split('=', 1) for line in f if '=' in line)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
# HID_ID=0003:0000256F:0000C635 (bus:vendor:product)
|
||||||
|
try:
|
||||||
|
_bus, vendor, product = (int(x, 16) for x in uevent.get('HID_ID', '').split(':'))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if vendor not in VENDORS:
|
||||||
|
continue
|
||||||
|
found.append(HidrawDevice('/dev/' + os.path.basename(sysdir),
|
||||||
|
uevent.get('HID_NAME', '?'),
|
||||||
|
'%04x' % vendor, '%04x' % product, sysdir))
|
||||||
|
return found
|
||||||
|
|
||||||
|
def descriptor(self):
|
||||||
|
if not self.sysfs:
|
||||||
|
return 'unknown'
|
||||||
|
try:
|
||||||
|
with open(os.path.join(self.sysfs, 'device', 'report_descriptor'), 'rb') as f:
|
||||||
|
return f.read().hex()
|
||||||
|
except OSError as e:
|
||||||
|
return 'unreadable: %s' % e
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
try:
|
||||||
|
self.fd = os.open(self.path, os.O_RDONLY | os.O_NONBLOCK)
|
||||||
|
except PermissionError:
|
||||||
|
sys.exit('Permission denied on %s -- run with sudo.' % self.path)
|
||||||
|
|
||||||
|
def record(self, seconds):
|
||||||
|
"""Read every report arriving within `seconds`; return [[ms, hex], ...]."""
|
||||||
|
reports = []
|
||||||
|
start = time.monotonic()
|
||||||
|
while True:
|
||||||
|
left = seconds - (time.monotonic() - start)
|
||||||
|
if left <= 0:
|
||||||
|
return reports
|
||||||
|
ready, _, _ = select.select([self.fd], [], [], left)
|
||||||
|
if not ready:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
data = os.read(self.fd, 64)
|
||||||
|
except BlockingIOError:
|
||||||
|
continue
|
||||||
|
if not data: # only a test FIFO with no writer does this
|
||||||
|
time.sleep(0.01)
|
||||||
|
continue
|
||||||
|
reports.append([round((time.monotonic() - start) * 1000, 1), data.hex()])
|
||||||
|
|
||||||
|
def drain(self):
|
||||||
|
"""Drop reports queued while waiting for Enter."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if not os.read(self.fd, 64):
|
||||||
|
return
|
||||||
|
except BlockingIOError:
|
||||||
|
return
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
os.close(self.fd)
|
||||||
|
|
||||||
|
def empty_hint(self):
|
||||||
|
return 'Try stopping spacenavd first: sudo systemctl stop spacenavd'
|
||||||
|
|
||||||
|
|
||||||
|
# --- macOS: IOKit, the same calls hidapi's mac backend makes ----------------
|
||||||
|
|
||||||
|
class MacHid:
|
||||||
|
"""ctypes bindings to the few CoreFoundation/IOKit calls needed."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import ctypes as c
|
||||||
|
self.c = c
|
||||||
|
cf = c.CDLL('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
|
||||||
|
io = c.CDLL('/System/Library/Frameworks/IOKit.framework/IOKit')
|
||||||
|
vp, i32, u32, idx = c.c_void_p, c.c_int32, c.c_uint32, c.c_long
|
||||||
|
|
||||||
|
def fn(lib, name, res, *args):
|
||||||
|
f = getattr(lib, name)
|
||||||
|
f.restype, f.argtypes = res, list(args)
|
||||||
|
return f
|
||||||
|
|
||||||
|
self.CFStringCreateWithCString = fn(cf, 'CFStringCreateWithCString', vp, vp, c.c_char_p, u32)
|
||||||
|
self.CFStringGetCString = fn(cf, 'CFStringGetCString', c.c_bool, vp, c.c_char_p, idx, u32)
|
||||||
|
self.CFGetTypeID = fn(cf, 'CFGetTypeID', c.c_ulong, vp)
|
||||||
|
self.CFNumberGetTypeID = fn(cf, 'CFNumberGetTypeID', c.c_ulong)
|
||||||
|
self.CFStringGetTypeID = fn(cf, 'CFStringGetTypeID', c.c_ulong)
|
||||||
|
self.CFDataGetTypeID = fn(cf, 'CFDataGetTypeID', c.c_ulong)
|
||||||
|
self.CFNumberGetValue = fn(cf, 'CFNumberGetValue', c.c_bool, vp, idx, vp)
|
||||||
|
self.CFDataGetLength = fn(cf, 'CFDataGetLength', idx, vp)
|
||||||
|
self.CFDataGetBytePtr = fn(cf, 'CFDataGetBytePtr', vp, vp)
|
||||||
|
self.CFSetGetCount = fn(cf, 'CFSetGetCount', idx, vp)
|
||||||
|
self.CFSetGetValues = fn(cf, 'CFSetGetValues', None, vp, c.POINTER(vp))
|
||||||
|
self.CFRunLoopGetCurrent = fn(cf, 'CFRunLoopGetCurrent', vp)
|
||||||
|
self.CFRunLoopRunInMode = fn(cf, 'CFRunLoopRunInMode', i32, vp, c.c_double, c.c_bool)
|
||||||
|
self.kCFRunLoopDefaultMode = vp.in_dll(cf, 'kCFRunLoopDefaultMode').value
|
||||||
|
|
||||||
|
self.IOHIDManagerCreate = fn(io, 'IOHIDManagerCreate', vp, vp, u32)
|
||||||
|
self.IOHIDManagerSetDeviceMatching = fn(io, 'IOHIDManagerSetDeviceMatching', None, vp, vp)
|
||||||
|
self.IOHIDManagerScheduleWithRunLoop = fn(io, 'IOHIDManagerScheduleWithRunLoop', None, vp, vp, vp)
|
||||||
|
self.IOHIDManagerCopyDevices = fn(io, 'IOHIDManagerCopyDevices', vp, vp)
|
||||||
|
self.IOHIDDeviceGetProperty = fn(io, 'IOHIDDeviceGetProperty', vp, vp, vp)
|
||||||
|
self.IOHIDDeviceOpen = fn(io, 'IOHIDDeviceOpen', i32, vp, u32)
|
||||||
|
self.IOHIDDeviceClose = fn(io, 'IOHIDDeviceClose', i32, vp, u32)
|
||||||
|
self.IOHIDDeviceScheduleWithRunLoop = fn(io, 'IOHIDDeviceScheduleWithRunLoop', None, vp, vp, vp)
|
||||||
|
self.IOHIDDeviceUnscheduleFromRunLoop = fn(io, 'IOHIDDeviceUnscheduleFromRunLoop', None, vp, vp, vp)
|
||||||
|
# void (*)(void *ctx, IOReturn, void *sender, IOHIDReportType, uint32_t id, uint8_t *, CFIndex)
|
||||||
|
self.ReportCallback = c.CFUNCTYPE(None, vp, i32, vp, c.c_int, u32, c.POINTER(c.c_uint8), idx)
|
||||||
|
self.IOHIDDeviceRegisterInputReportCallback = fn(
|
||||||
|
io, 'IOHIDDeviceRegisterInputReportCallback', None, vp, vp, idx, self.ReportCallback, vp)
|
||||||
|
|
||||||
|
def cfstr(self, s):
|
||||||
|
return self.CFStringCreateWithCString(None, s.encode(), 0x08000100) # UTF-8
|
||||||
|
|
||||||
|
def prop(self, dev, key):
|
||||||
|
"""A device property as int, str or bytes, or None."""
|
||||||
|
c = self.c
|
||||||
|
ref = self.IOHIDDeviceGetProperty(dev, self.cfstr(key))
|
||||||
|
if not ref:
|
||||||
|
return None
|
||||||
|
t = self.CFGetTypeID(ref)
|
||||||
|
if t == self.CFNumberGetTypeID():
|
||||||
|
v = c.c_int64()
|
||||||
|
self.CFNumberGetValue(ref, 4, c.byref(v)) # kCFNumberSInt64Type
|
||||||
|
return v.value
|
||||||
|
if t == self.CFStringGetTypeID():
|
||||||
|
buf = c.create_string_buffer(256)
|
||||||
|
return buf.value.decode('utf-8', 'replace') if self.CFStringGetCString(
|
||||||
|
ref, buf, len(buf), 0x08000100) else None
|
||||||
|
if t == self.CFDataGetTypeID():
|
||||||
|
return c.string_at(self.CFDataGetBytePtr(ref), self.CFDataGetLength(ref))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class MacDevice:
|
||||||
|
backend = 'iokit'
|
||||||
|
_hid = None
|
||||||
|
|
||||||
|
def __init__(self, ref, name, vendor, product, location):
|
||||||
|
self.ref, self.name, self.vendor, self.product = ref, name, vendor, product
|
||||||
|
self.path = 'iokit:%08x' % location
|
||||||
|
self.reports = []
|
||||||
|
self.start = time.monotonic()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def hid(cls):
|
||||||
|
if cls._hid is None:
|
||||||
|
cls._hid = MacHid()
|
||||||
|
return cls._hid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find(cls):
|
||||||
|
h = cls.hid()
|
||||||
|
mgr = h.IOHIDManagerCreate(None, 0)
|
||||||
|
h.IOHIDManagerSetDeviceMatching(mgr, None)
|
||||||
|
h.IOHIDManagerScheduleWithRunLoop(mgr, h.CFRunLoopGetCurrent(), h.kCFRunLoopDefaultMode)
|
||||||
|
devset = h.IOHIDManagerCopyDevices(mgr)
|
||||||
|
if not devset:
|
||||||
|
return []
|
||||||
|
n = h.CFSetGetCount(devset)
|
||||||
|
refs = (h.c.c_void_p * n)()
|
||||||
|
h.CFSetGetValues(devset, refs)
|
||||||
|
found = []
|
||||||
|
for ref in refs:
|
||||||
|
vendor = h.prop(ref, 'VendorID')
|
||||||
|
if vendor not in VENDORS:
|
||||||
|
continue
|
||||||
|
# The same test as QET's SpaceMouseHid::isSpaceMouse(): Generic
|
||||||
|
# Desktop / Multi-axis Controller, or no usage at all. This also
|
||||||
|
# skips Logitech's ordinary mice and keyboards.
|
||||||
|
usage = (h.prop(ref, 'PrimaryUsagePage') or 0, h.prop(ref, 'PrimaryUsage') or 0)
|
||||||
|
if usage not in ((1, 8), (0, 0)):
|
||||||
|
continue
|
||||||
|
found.append(cls(ref, h.prop(ref, 'Product') or '?', '%04x' % vendor,
|
||||||
|
'%04x' % (h.prop(ref, 'ProductID') or 0),
|
||||||
|
h.prop(ref, 'LocationID') or 0))
|
||||||
|
return sorted(found, key=lambda d: d.path)
|
||||||
|
|
||||||
|
def descriptor(self):
|
||||||
|
d = self.hid().prop(self.ref, 'ReportDescriptor')
|
||||||
|
return d.hex() if d else 'unreadable'
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
h = self.hid()
|
||||||
|
ret = h.IOHIDDeviceOpen(self.ref, 0) & 0xFFFFFFFF # kIOHIDOptionsTypeNone: shared
|
||||||
|
if ret == 0xE00002C5:
|
||||||
|
sys.exit('The device is held exclusively by another program '
|
||||||
|
'(kIOReturnExclusiveAccess). Quit 3DxWare and try again.')
|
||||||
|
if ret in (0xE00002E2, 0xE00002C1):
|
||||||
|
sys.exit('macOS refused access (0x%08X). Allow Terminal under System Settings > '
|
||||||
|
'Privacy & Security > Input Monitoring, then try again.' % ret)
|
||||||
|
if ret:
|
||||||
|
sys.exit('Could not open the device (IOReturn 0x%08X).' % ret)
|
||||||
|
size = h.prop(self.ref, 'MaxInputReportSize') or 64
|
||||||
|
self.buf = h.c.create_string_buffer(size)
|
||||||
|
|
||||||
|
def on_report(_ctx, _result, _sender, _type, _id, data, length):
|
||||||
|
self.reports.append([round((time.monotonic() - self.start) * 1000, 1),
|
||||||
|
h.c.string_at(data, length).hex()])
|
||||||
|
self.callback = h.ReportCallback(on_report) # must outlive the device
|
||||||
|
h.IOHIDDeviceRegisterInputReportCallback(self.ref, self.buf, size, self.callback, None)
|
||||||
|
h.IOHIDDeviceScheduleWithRunLoop(self.ref, h.CFRunLoopGetCurrent(), h.kCFRunLoopDefaultMode)
|
||||||
|
|
||||||
|
def record(self, seconds):
|
||||||
|
h = self.hid()
|
||||||
|
self.reports, self.start = [], time.monotonic()
|
||||||
|
while True:
|
||||||
|
left = seconds - (time.monotonic() - self.start)
|
||||||
|
if left <= 0:
|
||||||
|
return self.reports
|
||||||
|
h.CFRunLoopRunInMode(h.kCFRunLoopDefaultMode, left, False)
|
||||||
|
|
||||||
|
def drain(self):
|
||||||
|
self.hid().CFRunLoopRunInMode(self.hid().kCFRunLoopDefaultMode, 0.05, False)
|
||||||
|
self.reports = []
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
h = self.hid()
|
||||||
|
h.IOHIDDeviceUnscheduleFromRunLoop(self.ref, h.CFRunLoopGetCurrent(), h.kCFRunLoopDefaultMode)
|
||||||
|
h.IOHIDDeviceClose(self.ref, 0)
|
||||||
|
|
||||||
|
def empty_hint(self):
|
||||||
|
return ('Quit 3DxWare if it is running, or allow Terminal under System Settings > '
|
||||||
|
'Privacy & Security > Input Monitoring, and try again.')
|
||||||
|
|
||||||
|
|
||||||
|
# --- Windows: hid.dll, the same calls hidapi's Windows backend makes ---------
|
||||||
|
|
||||||
|
class WinHid:
|
||||||
|
"""ctypes bindings to the SetupAPI/HID/kernel32 calls needed."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import ctypes as c
|
||||||
|
from ctypes import wintypes as w
|
||||||
|
self.c, self.w = c, w
|
||||||
|
self.hid = c.WinDLL('hid')
|
||||||
|
self.setupapi = c.WinDLL('setupapi')
|
||||||
|
self.k32 = c.WinDLL('kernel32', use_last_error=True)
|
||||||
|
k, sa = self.k32, self.setupapi
|
||||||
|
|
||||||
|
class GUID(c.Structure):
|
||||||
|
_fields_ = [('Data1', w.DWORD), ('Data2', w.WORD), ('Data3', w.WORD),
|
||||||
|
('Data4', c.c_ubyte * 8)]
|
||||||
|
|
||||||
|
class InterfaceData(c.Structure):
|
||||||
|
_fields_ = [('cbSize', w.DWORD), ('InterfaceClassGuid', GUID),
|
||||||
|
('Flags', w.DWORD), ('Reserved', c.c_void_p)]
|
||||||
|
|
||||||
|
class Attributes(c.Structure):
|
||||||
|
_fields_ = [('Size', w.ULONG), ('VendorID', w.USHORT), ('ProductID', w.USHORT),
|
||||||
|
('VersionNumber', w.USHORT)]
|
||||||
|
|
||||||
|
class Caps(c.Structure):
|
||||||
|
_fields_ = [('Usage', w.USHORT), ('UsagePage', w.USHORT),
|
||||||
|
('InputReportByteLength', w.USHORT), ('OutputReportByteLength', w.USHORT),
|
||||||
|
('FeatureReportByteLength', w.USHORT), ('Reserved', w.USHORT * 17),
|
||||||
|
('rest', w.USHORT * 10)]
|
||||||
|
|
||||||
|
class Overlapped(c.Structure):
|
||||||
|
_fields_ = [('Internal', c.c_void_p), ('InternalHigh', c.c_void_p),
|
||||||
|
('Offset', w.DWORD), ('OffsetHigh', w.DWORD), ('hEvent', w.HANDLE)]
|
||||||
|
|
||||||
|
self.GUID, self.InterfaceData, self.Attributes = GUID, InterfaceData, Attributes
|
||||||
|
self.Caps, self.Overlapped = Caps, Overlapped
|
||||||
|
|
||||||
|
k.CreateFileW.restype = w.HANDLE
|
||||||
|
k.CreateFileW.argtypes = [w.LPCWSTR, w.DWORD, w.DWORD, c.c_void_p, w.DWORD, w.DWORD, w.HANDLE]
|
||||||
|
k.CreateEventW.restype = w.HANDLE
|
||||||
|
k.CreateEventW.argtypes = [c.c_void_p, w.BOOL, w.BOOL, w.LPCWSTR]
|
||||||
|
k.ReadFile.argtypes = [w.HANDLE, c.c_void_p, w.DWORD, c.c_void_p, c.c_void_p]
|
||||||
|
k.GetOverlappedResult.argtypes = [w.HANDLE, c.c_void_p, c.POINTER(w.DWORD), w.BOOL]
|
||||||
|
k.WaitForSingleObject.argtypes = [w.HANDLE, w.DWORD]
|
||||||
|
k.WaitForSingleObject.restype = w.DWORD
|
||||||
|
k.CancelIo.argtypes = [w.HANDLE]
|
||||||
|
k.CloseHandle.argtypes = [w.HANDLE]
|
||||||
|
sa.SetupDiGetClassDevsW.restype = w.HANDLE
|
||||||
|
sa.SetupDiGetClassDevsW.argtypes = [c.c_void_p, w.LPCWSTR, w.HWND, w.DWORD]
|
||||||
|
sa.SetupDiEnumDeviceInterfaces.argtypes = [w.HANDLE, c.c_void_p, c.c_void_p, w.DWORD, c.c_void_p]
|
||||||
|
sa.SetupDiGetDeviceInterfaceDetailW.argtypes = [w.HANDLE, c.c_void_p, c.c_void_p, w.DWORD,
|
||||||
|
c.POINTER(w.DWORD), c.c_void_p]
|
||||||
|
sa.SetupDiDestroyDeviceInfoList.argtypes = [w.HANDLE]
|
||||||
|
self.hid.HidD_GetHidGuid.argtypes = [c.c_void_p]
|
||||||
|
self.hid.HidD_GetAttributes.argtypes = [w.HANDLE, c.c_void_p]
|
||||||
|
self.hid.HidD_GetPreparsedData.argtypes = [w.HANDLE, c.POINTER(c.c_void_p)]
|
||||||
|
self.hid.HidD_FreePreparsedData.argtypes = [c.c_void_p]
|
||||||
|
self.hid.HidP_GetCaps.argtypes = [c.c_void_p, c.c_void_p]
|
||||||
|
self.hid.HidD_GetProductString.argtypes = [w.HANDLE, c.c_void_p, w.ULONG]
|
||||||
|
|
||||||
|
INVALID = (2 ** 64 - 1, 2 ** 32 - 1, -1) # INVALID_HANDLE_VALUE, 64/32-bit
|
||||||
|
|
||||||
|
def paths(self):
|
||||||
|
"""Every HID interface path on the system."""
|
||||||
|
c, w = self.c, self.w
|
||||||
|
guid = self.GUID()
|
||||||
|
self.hid.HidD_GetHidGuid(c.byref(guid))
|
||||||
|
info = self.setupapi.SetupDiGetClassDevsW(c.byref(guid), None, None, 0x12) # PRESENT|INTERFACE
|
||||||
|
paths = []
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
data = self.InterfaceData()
|
||||||
|
data.cbSize = c.sizeof(data)
|
||||||
|
if not self.setupapi.SetupDiEnumDeviceInterfaces(info, None, c.byref(guid), i, c.byref(data)):
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
needed = w.DWORD()
|
||||||
|
self.setupapi.SetupDiGetDeviceInterfaceDetailW(info, c.byref(data), None, 0, c.byref(needed), None)
|
||||||
|
buf = c.create_string_buffer(needed.value)
|
||||||
|
# SP_DEVICE_INTERFACE_DETAIL_DATA_W: DWORD cbSize, then the path.
|
||||||
|
c.cast(buf, c.POINTER(w.DWORD))[0] = 8 if c.sizeof(c.c_void_p) == 8 else 6
|
||||||
|
if self.setupapi.SetupDiGetDeviceInterfaceDetailW(info, c.byref(data), buf, needed, None, None):
|
||||||
|
paths.append(c.wstring_at(c.addressof(buf) + 4))
|
||||||
|
self.setupapi.SetupDiDestroyDeviceInfoList(info)
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def open(self, path, access):
|
||||||
|
# FILE_SHARE_READ|WRITE, OPEN_EXISTING, FILE_FLAG_OVERLAPPED
|
||||||
|
h = self.k32.CreateFileW(path, access, 3, None, 3, 0x40000000, None)
|
||||||
|
return None if h is None or h in self.INVALID else h
|
||||||
|
|
||||||
|
def describe(self, h):
|
||||||
|
"""(vendor, product, usage_page, usage, input_length, name) of an open handle."""
|
||||||
|
c = self.c
|
||||||
|
attrs = self.Attributes()
|
||||||
|
attrs.Size = c.sizeof(attrs)
|
||||||
|
if not self.hid.HidD_GetAttributes(h, c.byref(attrs)):
|
||||||
|
return None
|
||||||
|
page = usage = length = 0
|
||||||
|
pre = c.c_void_p()
|
||||||
|
if self.hid.HidD_GetPreparsedData(h, c.byref(pre)):
|
||||||
|
caps = self.Caps()
|
||||||
|
self.hid.HidP_GetCaps(pre, c.byref(caps))
|
||||||
|
page, usage, length = caps.UsagePage, caps.Usage, caps.InputReportByteLength
|
||||||
|
self.hid.HidD_FreePreparsedData(pre)
|
||||||
|
name = c.create_unicode_buffer(128)
|
||||||
|
if not self.hid.HidD_GetProductString(h, name, c.sizeof(name)):
|
||||||
|
name.value = '?'
|
||||||
|
return attrs.VendorID, attrs.ProductID, page, usage, length, name.value
|
||||||
|
|
||||||
|
|
||||||
|
class WinDevice:
|
||||||
|
backend = 'windows-hid'
|
||||||
|
_hid = None
|
||||||
|
|
||||||
|
def __init__(self, path, name, vendor, product, length):
|
||||||
|
self.path, self.name, self.vendor, self.product = path, name, vendor, product
|
||||||
|
self.length = length or 64
|
||||||
|
self.handle = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def hid(cls):
|
||||||
|
if cls._hid is None:
|
||||||
|
cls._hid = WinHid()
|
||||||
|
return cls._hid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find(cls):
|
||||||
|
h = cls.hid()
|
||||||
|
found = []
|
||||||
|
for path in h.paths():
|
||||||
|
handle = h.open(path, 0) # no access: enough to read attributes
|
||||||
|
if handle is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
d = h.describe(handle)
|
||||||
|
finally:
|
||||||
|
h.k32.CloseHandle(handle)
|
||||||
|
if not d:
|
||||||
|
continue
|
||||||
|
vendor, product, page, usage, length, name = d
|
||||||
|
# The same test as QET's SpaceMouseHid::isSpaceMouse().
|
||||||
|
if vendor not in VENDORS or (page, usage) not in ((1, 8), (0, 0)):
|
||||||
|
continue
|
||||||
|
found.append(cls(path, name, '%04x' % vendor, '%04x' % product, length))
|
||||||
|
return found
|
||||||
|
|
||||||
|
def descriptor(self):
|
||||||
|
# Windows only gives out a parsed form of it.
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
h = self.hid()
|
||||||
|
self.handle = h.open(self.path, 0x80000000) # GENERIC_READ
|
||||||
|
if self.handle is None:
|
||||||
|
sys.exit('Could not open the device (error %d).' % h.c.get_last_error())
|
||||||
|
self.event = h.k32.CreateEventW(None, True, False, None)
|
||||||
|
self.buf = h.c.create_string_buffer(self.length)
|
||||||
|
self.pending = False
|
||||||
|
self.start = time.monotonic()
|
||||||
|
|
||||||
|
def _read(self, wait_ms):
|
||||||
|
"""One report if it arrives within wait_ms, else None."""
|
||||||
|
h = self.hid()
|
||||||
|
c, w = h.c, h.w
|
||||||
|
if not self.pending:
|
||||||
|
self.ov = h.Overlapped()
|
||||||
|
self.ov.hEvent = self.event
|
||||||
|
h.k32.ReadFile(self.handle, self.buf, self.length, None, c.byref(self.ov))
|
||||||
|
self.pending = True
|
||||||
|
if h.k32.WaitForSingleObject(self.event, max(0, int(wait_ms))) != 0:
|
||||||
|
return None
|
||||||
|
self.pending = False
|
||||||
|
n = w.DWORD()
|
||||||
|
if not h.k32.GetOverlappedResult(self.handle, c.byref(self.ov), c.byref(n), False):
|
||||||
|
return None
|
||||||
|
data = self.buf.raw[:n.value]
|
||||||
|
# Windows puts a report ID in front even when the device has none;
|
||||||
|
# hidapi drops that 0, so QET never sees it.
|
||||||
|
return data[1:] if data[:1] == b'\0' else data
|
||||||
|
|
||||||
|
def record(self, seconds):
|
||||||
|
reports = []
|
||||||
|
start = time.monotonic()
|
||||||
|
while True:
|
||||||
|
left = seconds - (time.monotonic() - start)
|
||||||
|
if left <= 0:
|
||||||
|
return reports
|
||||||
|
data = self._read(left * 1000)
|
||||||
|
if data:
|
||||||
|
reports.append([round((time.monotonic() - start) * 1000, 1), data.hex()])
|
||||||
|
|
||||||
|
def drain(self):
|
||||||
|
while self._read(0):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
h = self.hid()
|
||||||
|
if self.pending:
|
||||||
|
h.k32.CancelIo(self.handle)
|
||||||
|
h.k32.CloseHandle(self.event)
|
||||||
|
h.k32.CloseHandle(self.handle)
|
||||||
|
|
||||||
|
def empty_hint(self):
|
||||||
|
return 'Check the cable, push the cap firmly, and try again.'
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument('--list', action='store_true', help='only list matching devices')
|
||||||
|
ap.add_argument('--device', help='device path from --list, if more than one is found')
|
||||||
|
ap.add_argument('--descriptor', help=argparse.SUPPRESS) # testing without a device
|
||||||
|
ap.add_argument('--yes', action='store_true', help=argparse.SUPPRESS) # no Enter prompts
|
||||||
|
ap.add_argument('-o', '--output', help='output file (default: spacemouse-capture-<product>.json)')
|
||||||
|
ap.add_argument('--seconds', type=float, default=1.0,
|
||||||
|
help='multiply each step\'s recording time (default: 1.0, e.g. 3 triples it)')
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
Device = {'darwin': MacDevice, 'win32': WinDevice}.get(sys.platform, HidrawDevice)
|
||||||
|
devices = Device.find()
|
||||||
|
if args.list:
|
||||||
|
for d in devices:
|
||||||
|
print('%s %s:%s %s' % (d.path, d.vendor, d.product, d.name))
|
||||||
|
if not devices:
|
||||||
|
print('No 3Dconnexion device found.')
|
||||||
|
return 0 if devices else 1
|
||||||
|
|
||||||
|
if args.device:
|
||||||
|
dev = next((d for d in devices if d.path == args.device), None)
|
||||||
|
if dev is None:
|
||||||
|
if Device is not HidrawDevice:
|
||||||
|
sys.exit('No device %s (try --list)' % args.device)
|
||||||
|
dev = HidrawDevice(args.device)
|
||||||
|
elif len(devices) == 1:
|
||||||
|
dev = devices[0]
|
||||||
|
elif not devices:
|
||||||
|
sys.exit('No 3Dconnexion device found. Is it plugged in? (try --list)')
|
||||||
|
else:
|
||||||
|
sys.exit('Several devices found, pick one with --device:\n' +
|
||||||
|
'\n'.join(' %s %s' % (d.path, d.name) for d in devices))
|
||||||
|
|
||||||
|
if args.descriptor:
|
||||||
|
with open(args.descriptor, 'rb') as f:
|
||||||
|
descriptor = f.read().hex()
|
||||||
|
else:
|
||||||
|
descriptor = dev.descriptor()
|
||||||
|
|
||||||
|
dev.open()
|
||||||
|
print('Recording from %s (%s, %s:%s).' % (dev.path, dev.name, dev.vendor, dev.product))
|
||||||
|
readers = other_readers()
|
||||||
|
if readers:
|
||||||
|
print('Also running: %s. If nothing gets recorded, quit it and try again.' % ', '.join(readers))
|
||||||
|
print('For each step, press Enter, do the movement, and wait for the next prompt.\n')
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'tool': 'spacemouse-capture.py 2',
|
||||||
|
'date': datetime.datetime.now(datetime.timezone.utc).isoformat(timespec='seconds'),
|
||||||
|
'system': platform.platform(),
|
||||||
|
'backend': dev.backend,
|
||||||
|
'other_readers': readers,
|
||||||
|
'device': {'name': dev.name, 'vendor': dev.vendor, 'product': dev.product},
|
||||||
|
'report_descriptor': descriptor,
|
||||||
|
'steps': [],
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
for i, (key, text, seconds) in enumerate(STEPS, 1):
|
||||||
|
seconds = seconds * args.seconds
|
||||||
|
print('[%d/%d] %s' % (i, len(STEPS), text))
|
||||||
|
if not args.yes:
|
||||||
|
input(' Press Enter to start (%.0f s)... ' % seconds)
|
||||||
|
dev.drain()
|
||||||
|
reports = dev.record(seconds)
|
||||||
|
print(' %d reports recorded.\n' % len(reports))
|
||||||
|
result['steps'].append({'step': key, 'instruction': text, 'reports': reports})
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nStopped early -- saving what was recorded so far.')
|
||||||
|
finally:
|
||||||
|
dev.close()
|
||||||
|
|
||||||
|
out = args.output or 'spacemouse-capture-%s.json' % dev.product
|
||||||
|
with open(out, 'w') as f:
|
||||||
|
json.dump(result, f, indent=1)
|
||||||
|
total = sum(len(s['reports']) for s in result['steps'])
|
||||||
|
print('Saved %s (%d reports in total).' % (out, total))
|
||||||
|
if total == 0:
|
||||||
|
print('Nothing was recorded. ' + dev.empty_hint())
|
||||||
|
else:
|
||||||
|
print('Please attach this file to discussion #599. Thank you!')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
#Based on raspberry pi 5 8 Gb Trixie
|
#Based on raspberry pi 5 8 Gb Trixie
|
||||||
#sudo apt install git ssh rsync libqt5svg5-dev qt5-qmake qtbase5-dev libkf5widgetsaddons-dev libkf5coreaddons-dev pkgconf libqt5waylandclient5-dev libqt5waylandcompositor5-dev g++ make
|
#sudo apt install git ssh rsync libqt5svg5-dev qt5-qmake qtbase5-dev libkf5widgetsaddons-dev libkf5coreaddons-dev libsqlite3-dev pkgconf libqt5waylandclient5-dev libqt5waylandcompositor5-dev g++ make
|
||||||
#mkdir -p AppImage/0.100.0/aarch64
|
#mkdir -p AppImage/0.100.0/aarch64
|
||||||
# Get GIT sources
|
# Get GIT sources
|
||||||
#git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
|
#git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
|
||||||
|
|||||||
@@ -0,0 +1,940 @@
|
|||||||
|
/*
|
||||||
|
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 "elementpickerpopup.h"
|
||||||
|
|
||||||
|
#include "elementscollectionwidget.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QCloseEvent>
|
||||||
|
#include <QCursor>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListView>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QSizeGrip>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "../qetapp.h"
|
||||||
|
#include "../shortcutmanager.h"
|
||||||
|
#include "elementslocation.h"
|
||||||
|
|
||||||
|
//The palette is read from disk each time the picker opens. It is meant as
|
||||||
|
//a shortlist, and a large custom collection would otherwise make opening
|
||||||
|
//slow and the grid unusable.
|
||||||
|
static const int max_palette_entries = 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::ElementPickerPopup
|
||||||
|
@param source : the collection widget whose model the search runs against
|
||||||
|
@param parent
|
||||||
|
*/
|
||||||
|
ElementPickerPopup::ElementPickerPopup(ElementsCollectionWidget *source,
|
||||||
|
QWidget *parent) :
|
||||||
|
QFrame(parent, Qt::Popup),
|
||||||
|
m_source(source)
|
||||||
|
{
|
||||||
|
setFrameShape(QFrame::StyledPanel);
|
||||||
|
setFrameShadow(QFrame::Raised);
|
||||||
|
setMinimumWidth(340);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->setContentsMargins(6, 6, 6, 6);
|
||||||
|
layout->setSpacing(4);
|
||||||
|
|
||||||
|
//Command row, shown when the picker is opened as the shortcut bar
|
||||||
|
m_commands = new QWidget(this);
|
||||||
|
m_commands_layout = new QHBoxLayout(m_commands);
|
||||||
|
m_commands_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_commands_layout->setSpacing(2);
|
||||||
|
m_commands->hide();
|
||||||
|
//SolidWorks: right-click the bar to customise it
|
||||||
|
m_commands->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(m_commands, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
|
||||||
|
QMenu menu;
|
||||||
|
menu.addAction(tr("Personnaliser la barre…"), this, &ElementPickerPopup::startCustomising);
|
||||||
|
menu.exec(m_commands->mapToGlobal(pos));
|
||||||
|
});
|
||||||
|
|
||||||
|
//Customising, shown instead of everything else. Two lists that
|
||||||
|
//commands are dragged between: the bar, left to right, and the
|
||||||
|
//commands that are not on it.
|
||||||
|
m_editor = new QWidget(this);
|
||||||
|
auto *editor_layout = new QVBoxLayout(m_editor);
|
||||||
|
editor_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
auto *help = new QLabel(
|
||||||
|
tr("Glissez les commandes et les éléments dans la barre, hors de la "
|
||||||
|
"barre, ou d'une place à l'autre. Un double-clic fait passer une "
|
||||||
|
"commande ou un élément d'une liste à l'autre."), m_editor);
|
||||||
|
help->setWordWrap(true);
|
||||||
|
m_edit_row = new QListWidget(m_editor);
|
||||||
|
m_edit_row->setFlow(QListView::LeftToRight);
|
||||||
|
m_edit_row->setWrapping(false);
|
||||||
|
m_edit_row->setIconSize(QSize(24, 24));
|
||||||
|
m_edit_row->setFixedHeight(44);
|
||||||
|
m_edit_available = new QListWidget(m_editor);
|
||||||
|
m_edit_available->setIconSize(QSize(20, 20));
|
||||||
|
m_edit_available->setMinimumHeight(220);
|
||||||
|
for (QListWidget *list : {m_edit_row, m_edit_available}) {
|
||||||
|
list->setDragDropMode(QAbstractItemView::DragDrop);
|
||||||
|
list->setDefaultDropAction(Qt::MoveAction);
|
||||||
|
list->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
}
|
||||||
|
//A drop copies the item as it was in the other list, so a command
|
||||||
|
//dragged onto the bar would keep its text and one dragged off would
|
||||||
|
//have none. Set the text for the list it has landed in.
|
||||||
|
//Deferred: a drop inserts the row first and copies the item's data
|
||||||
|
//into it afterwards.
|
||||||
|
auto relabel = [this](QListWidget *list, bool icon_only) {
|
||||||
|
return [this, list, icon_only]() {
|
||||||
|
QTimer::singleShot(0, this, [list, icon_only]() {
|
||||||
|
for (int i = 0 ; i < list->count() ; ++i) {
|
||||||
|
QListWidgetItem *item = list->item(i);
|
||||||
|
item->setText(icon_only && !item->icon().isNull()
|
||||||
|
? QString() : item->toolTip());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
connect(m_edit_row->model(), &QAbstractItemModel::rowsInserted,
|
||||||
|
this, relabel(m_edit_row, true));
|
||||||
|
connect(m_edit_available->model(), &QAbstractItemModel::rowsInserted,
|
||||||
|
this, relabel(m_edit_available, false));
|
||||||
|
//An element dragged off the bar onto the commands is removed from
|
||||||
|
//the bar, not listed as a command
|
||||||
|
connect(m_edit_available->model(), &QAbstractItemModel::rowsInserted, this, [this]() {
|
||||||
|
QTimer::singleShot(0, this, [this]() {
|
||||||
|
for (int i = m_edit_available->count() - 1 ; i >= 0 ; --i) {
|
||||||
|
if (ShortcutBarSettings::isElement(
|
||||||
|
m_edit_available->item(i)->data(Qt::UserRole).toString())) {
|
||||||
|
delete m_edit_available->takeItem(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_edit_row, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) {
|
||||||
|
const QString id = item->data(Qt::UserRole).toString();
|
||||||
|
delete item;
|
||||||
|
if (!ShortcutBarSettings::isElement(id)) {
|
||||||
|
m_edit_available->addItem(barItem(id, false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_edit_available, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) {
|
||||||
|
const QString id = item->data(Qt::UserRole).toString();
|
||||||
|
delete item;
|
||||||
|
m_edit_row->addItem(barItem(id, true));
|
||||||
|
});
|
||||||
|
|
||||||
|
//Elements to pin: the same ranked search as the picker, dragged
|
||||||
|
//onto the bar. Copied, not moved, so a result can be pinned and
|
||||||
|
//still be seen in the list.
|
||||||
|
m_edit_symbols_box = new QWidget(m_editor);
|
||||||
|
auto *symbols_layout = new QVBoxLayout(m_edit_symbols_box);
|
||||||
|
symbols_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_edit_symbols_search = new QLineEdit(m_edit_symbols_box);
|
||||||
|
m_edit_symbols_search->setPlaceholderText(tr("Rechercher un élément…"));
|
||||||
|
m_edit_symbols_search->setClearButtonEnabled(true);
|
||||||
|
m_edit_symbols = new QListWidget(m_edit_symbols_box);
|
||||||
|
m_edit_symbols->setIconSize(QSize(32, 32));
|
||||||
|
m_edit_symbols->setMinimumHeight(220);
|
||||||
|
m_edit_symbols->setDragDropMode(QAbstractItemView::DragOnly);
|
||||||
|
m_edit_symbols->setDefaultDropAction(Qt::CopyAction);
|
||||||
|
m_edit_symbols->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
symbols_layout->addWidget(new QLabel(tr("Éléments :"), m_edit_symbols_box));
|
||||||
|
symbols_layout->addWidget(m_edit_symbols_search);
|
||||||
|
symbols_layout->addWidget(m_edit_symbols);
|
||||||
|
|
||||||
|
auto *symbols_timer = new QTimer(this);
|
||||||
|
symbols_timer->setSingleShot(true);
|
||||||
|
symbols_timer->setInterval(300);
|
||||||
|
connect(m_edit_symbols_search, &QLineEdit::textChanged, this,
|
||||||
|
[symbols_timer]() { symbols_timer->start(); });
|
||||||
|
connect(symbols_timer, &QTimer::timeout, this, &ElementPickerPopup::runSymbolSearch);
|
||||||
|
connect(m_edit_symbols, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) {
|
||||||
|
m_edit_row->addItem(barItem(item->data(Qt::UserRole).toString(), true));
|
||||||
|
});
|
||||||
|
auto *defaults = new QPushButton(tr("Valeurs par défaut"), m_editor);
|
||||||
|
auto *cancel = new QPushButton(tr("Annuler"), m_editor);
|
||||||
|
auto *done = new QPushButton(tr("Terminé"), m_editor);
|
||||||
|
done->setDefault(true);
|
||||||
|
connect(defaults, &QPushButton::clicked, this, [this]() {
|
||||||
|
fillCustomising(ShortcutBarSettings::defaultIds(m_context));
|
||||||
|
});
|
||||||
|
connect(cancel, &QPushButton::clicked, this, [this]() { finishCustomising(false); });
|
||||||
|
connect(done, &QPushButton::clicked, this, [this]() { finishCustomising(true); });
|
||||||
|
auto *editor_buttons = new QHBoxLayout();
|
||||||
|
editor_buttons->addWidget(defaults);
|
||||||
|
editor_buttons->addStretch();
|
||||||
|
editor_buttons->addWidget(cancel);
|
||||||
|
editor_buttons->addWidget(done);
|
||||||
|
editor_layout->addWidget(help);
|
||||||
|
editor_layout->addWidget(new QLabel(tr("Dans la barre :"), m_editor));
|
||||||
|
editor_layout->addWidget(m_edit_row);
|
||||||
|
auto *lists = new QHBoxLayout();
|
||||||
|
auto *commands_column = new QVBoxLayout();
|
||||||
|
commands_column->addWidget(new QLabel(tr("Autres commandes :"), m_editor));
|
||||||
|
commands_column->addWidget(m_edit_available);
|
||||||
|
lists->addLayout(commands_column);
|
||||||
|
lists->addWidget(m_edit_symbols_box);
|
||||||
|
editor_layout->addLayout(lists);
|
||||||
|
editor_layout->addLayout(editor_buttons);
|
||||||
|
m_editor->hide();
|
||||||
|
|
||||||
|
m_search = new QLineEdit(this);
|
||||||
|
m_search->setPlaceholderText(tr("Rechercher un élément…"));
|
||||||
|
m_search->setClearButtonEnabled(true);
|
||||||
|
|
||||||
|
m_model = new QStandardItemModel(this);
|
||||||
|
m_view = new QListView(this);
|
||||||
|
m_view->setModel(m_model);
|
||||||
|
m_view->setIconSize(QSize(40, 40));
|
||||||
|
m_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
m_view->setMinimumHeight(260);
|
||||||
|
|
||||||
|
m_hint = new QLabel(tr("Entrée pour insérer · Échap pour fermer"), this);
|
||||||
|
m_hint->setEnabled(false);
|
||||||
|
//Wrapped rather than cut off when the bar is made narrow
|
||||||
|
m_hint->setWordWrap(true);
|
||||||
|
|
||||||
|
//The bar's width is the user's: dragging the grip wraps the tiles
|
||||||
|
//onto more rows, and the width is kept for next time
|
||||||
|
m_grip = new QSizeGrip(this);
|
||||||
|
m_grip->setToolTip(tr("Glisser pour changer la largeur de la barre"));
|
||||||
|
m_grip->installEventFilter(this);
|
||||||
|
auto *hint_row = new QHBoxLayout();
|
||||||
|
hint_row->addWidget(m_hint, 1);
|
||||||
|
hint_row->addWidget(m_grip, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||||
|
|
||||||
|
layout->addWidget(m_commands);
|
||||||
|
layout->addWidget(m_search);
|
||||||
|
layout->addWidget(m_view);
|
||||||
|
layout->addLayout(hint_row);
|
||||||
|
layout->addWidget(m_editor);
|
||||||
|
|
||||||
|
//Search as you type, after a short idle: shorter than the dock's
|
||||||
|
//500 ms, since the popup is opened and closed in a few seconds, but
|
||||||
|
//the collection is large enough that every keystroke would show.
|
||||||
|
auto *timer = new QTimer(this);
|
||||||
|
timer->setSingleShot(true);
|
||||||
|
timer->setInterval(300);
|
||||||
|
connect(m_search, &QLineEdit::textChanged, this,
|
||||||
|
[timer]() { timer->start(); });
|
||||||
|
connect(timer, &QTimer::timeout, this, &ElementPickerPopup::runSearch);
|
||||||
|
|
||||||
|
connect(m_view, &QListView::doubleClicked, this,
|
||||||
|
[this](const QModelIndex &) { chooseCurrent(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::popUpAt
|
||||||
|
Show the picker at @a global_pos, kept on screen, with the search field
|
||||||
|
focused and any previous query cleared.
|
||||||
|
@param global_pos
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::popUpAt(const QPoint &global_pos)
|
||||||
|
{
|
||||||
|
m_bar_mode = false;
|
||||||
|
setCommands({});
|
||||||
|
show(global_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::popUpShortcutBar
|
||||||
|
Show the picker at @a global_pos with the shortcut bar's commands for
|
||||||
|
@a context above it.
|
||||||
|
@param global_pos
|
||||||
|
@param context
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::popUpShortcutBar(const QPoint &global_pos,
|
||||||
|
ShortcutBarSettings::Context context)
|
||||||
|
{
|
||||||
|
m_bar_mode = true;
|
||||||
|
m_context = context;
|
||||||
|
setCommands(ShortcutBarSettings::ids(context));
|
||||||
|
show(global_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::commandAction
|
||||||
|
@return the action registered under @a id by the window this popup
|
||||||
|
belongs to, or nullptr
|
||||||
|
*/
|
||||||
|
QAction *ElementPickerPopup::commandAction(const QString &id) const
|
||||||
|
{
|
||||||
|
return ShortcutManager::instance().action(id, parentWidget());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::show
|
||||||
|
Reset the search and show at @a global_pos, kept on screen.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::show(const QPoint &global_pos)
|
||||||
|
{
|
||||||
|
m_search->clear();
|
||||||
|
m_model->clear();
|
||||||
|
showPalette();
|
||||||
|
|
||||||
|
keepOnScreen(global_pos);
|
||||||
|
QFrame::show();
|
||||||
|
m_search->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::keepOnScreen
|
||||||
|
Fit the popup to its contents and move it to @a global_pos, kept fully on
|
||||||
|
the screen that point is on: opening at the cursor near a right or bottom
|
||||||
|
edge would otherwise push it off.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::keepOnScreen(const QPoint &global_pos)
|
||||||
|
{
|
||||||
|
adjustSize();
|
||||||
|
//The sizes the user gave the customising window and the bar
|
||||||
|
if (m_customising) {
|
||||||
|
const QSize size = ShortcutBarSettings::editorSize();
|
||||||
|
if (size.isValid()) {
|
||||||
|
resize(size.expandedTo(minimumSizeHint()));
|
||||||
|
}
|
||||||
|
} else if (m_bar_mode && ShortcutBarSettings::barWidth() > 0) {
|
||||||
|
resize(qMax(ShortcutBarSettings::barWidth(), minimumSizeHint().width()),
|
||||||
|
height());
|
||||||
|
}
|
||||||
|
QPoint pos = global_pos;
|
||||||
|
|
||||||
|
if (QScreen *screen = QGuiApplication::screenAt(global_pos)) {
|
||||||
|
const QRect avail = screen->availableGeometry();
|
||||||
|
pos.setX(qBound(avail.left(),
|
||||||
|
pos.x(), avail.right() - width()));
|
||||||
|
pos.setY(qBound(avail.top(),
|
||||||
|
pos.y(), avail.bottom() - height()));
|
||||||
|
}
|
||||||
|
|
||||||
|
move(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::setCommands
|
||||||
|
Show @a ids as a row of buttons above the search field, or hide the row
|
||||||
|
when there are none. A disabled command keeps its place, greyed out, so
|
||||||
|
the row looks the same every time for a given selection. An id no live
|
||||||
|
action carries, or a pinned element that no longer exists, is skipped.
|
||||||
|
|
||||||
|
Clicking a button closes the picker first, then triggers the action: a
|
||||||
|
command such as "add a line" starts a mode on the folio, which needs the
|
||||||
|
focus the popup holds. A pinned element is placed, as if chosen from the
|
||||||
|
picker.
|
||||||
|
@param ids : command ids and pinned elements' collection paths
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::setCommands(const QStringList &ids)
|
||||||
|
{
|
||||||
|
while (QLayoutItem *item = m_commands_layout->takeAt(0)) {
|
||||||
|
delete item->widget();
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
qDeleteAll(m_tiles);
|
||||||
|
m_tiles.clear();
|
||||||
|
m_tile_rows = new QVBoxLayout();
|
||||||
|
m_tile_rows->setSpacing(2);
|
||||||
|
m_commands_layout->addLayout(m_tile_rows);
|
||||||
|
|
||||||
|
for (const QString &id : ids)
|
||||||
|
{
|
||||||
|
if (ShortcutBarSettings::isElement(id))
|
||||||
|
{
|
||||||
|
const ElementsLocation location(id);
|
||||||
|
if (!location.exist()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto *button = new QToolButton(m_commands);
|
||||||
|
button->setAutoRaise(true);
|
||||||
|
button->setIconSize(QSize(24, 24));
|
||||||
|
button->setIcon(location.icon());
|
||||||
|
button->setToolTip(location.name());
|
||||||
|
button->setFocusPolicy(Qt::NoFocus);
|
||||||
|
connect(button, &QToolButton::clicked, this, [this, location]() {
|
||||||
|
hide();
|
||||||
|
emit elementChosen(location);
|
||||||
|
});
|
||||||
|
m_tiles << button;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *action = commandAction(id);
|
||||||
|
if (!action) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto *button = new QToolButton(m_commands);
|
||||||
|
button->setAutoRaise(true);
|
||||||
|
button->setIconSize(QSize(24, 24));
|
||||||
|
const QString text = action->text().remove(QLatin1Char('&'));
|
||||||
|
if (action->icon().isNull()) {
|
||||||
|
button->setText(text);
|
||||||
|
button->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
||||||
|
} else {
|
||||||
|
button->setIcon(action->icon());
|
||||||
|
}
|
||||||
|
const QKeySequence key = action->shortcut();
|
||||||
|
QString tip = key.isEmpty()
|
||||||
|
? text
|
||||||
|
: QStringLiteral("%1 (%2)").arg(
|
||||||
|
text, key.toString(QKeySequence::NativeText));
|
||||||
|
//The status tip says what the command does, which a short
|
||||||
|
//name such as "Orienter les textes" does not
|
||||||
|
if (!action->statusTip().isEmpty()) {
|
||||||
|
tip += QLatin1Char('\n') + action->statusTip();
|
||||||
|
}
|
||||||
|
button->setToolTip(tip);
|
||||||
|
button->setEnabled(action->isEnabled());
|
||||||
|
button->setFocusPolicy(Qt::NoFocus);
|
||||||
|
connect(button, &QToolButton::clicked, this, [this, action]() {
|
||||||
|
hide();
|
||||||
|
action->trigger();
|
||||||
|
});
|
||||||
|
m_tiles << button;
|
||||||
|
}
|
||||||
|
m_commands_layout->addStretch();
|
||||||
|
if (m_bar_mode)
|
||||||
|
{
|
||||||
|
auto *customise = new QToolButton(m_commands);
|
||||||
|
customise->setAutoRaise(true);
|
||||||
|
customise->setText(QStringLiteral("…"));
|
||||||
|
customise->setToolTip(tr("Personnaliser la barre…"));
|
||||||
|
customise->setFocusPolicy(Qt::NoFocus);
|
||||||
|
connect(customise, &QToolButton::clicked, this, &ElementPickerPopup::startCustomising);
|
||||||
|
m_commands_layout->addWidget(customise, 0, Qt::AlignTop);
|
||||||
|
}
|
||||||
|
layoutTiles(ShortcutBarSettings::barWidth());
|
||||||
|
m_grip->setVisible(m_bar_mode);
|
||||||
|
//An empty bar still shows, so it can be customised back
|
||||||
|
m_commands->setVisible(m_bar_mode || !ids.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::layoutTiles
|
||||||
|
Put the bar's tiles in rows no wider than the bar at @a bar_width, in
|
||||||
|
their order, as many per row as fit. A @a bar_width of 0 or less, the
|
||||||
|
width the user never set, keeps them on one row.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::layoutTiles(int bar_width)
|
||||||
|
{
|
||||||
|
if (!m_tile_rows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Emptying a row layout leaves its buttons alone; they go into the
|
||||||
|
//new rows below
|
||||||
|
while (QLayoutItem *item = m_tile_rows->takeAt(0)) {
|
||||||
|
delete item->layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Room left beside the tiles: the popup's margins and the "…" button
|
||||||
|
const int limit = bar_width > 0
|
||||||
|
? bar_width - 12 - 30
|
||||||
|
: std::numeric_limits<int>::max();
|
||||||
|
QHBoxLayout *row = nullptr;
|
||||||
|
int used = 0;
|
||||||
|
for (QToolButton *tile : std::as_const(m_tiles))
|
||||||
|
{
|
||||||
|
const int w = tile->sizeHint().width() + m_tile_rows->spacing();
|
||||||
|
if (!row || (used + w > limit && used > 0)) {
|
||||||
|
row = new QHBoxLayout();
|
||||||
|
row->setSpacing(2);
|
||||||
|
row->addStretch();
|
||||||
|
m_tile_rows->addLayout(row);
|
||||||
|
used = 0;
|
||||||
|
}
|
||||||
|
//Before the stretch, which stays last so the row is left-aligned
|
||||||
|
row->insertWidget(row->count() - 1, tile);
|
||||||
|
used += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::eventFilter
|
||||||
|
Drive the size grip by hand. QSizeGrip asks the window manager to do the
|
||||||
|
resize, and a popup is a window the window manager does not manage, so
|
||||||
|
on X11 nothing would happen. Only the width follows the mouse: the tiles
|
||||||
|
re-flow into rows as it changes, the height follows the rows, and the
|
||||||
|
width is saved when the grip is let go.
|
||||||
|
*/
|
||||||
|
bool ElementPickerPopup::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (watched != m_grip) {
|
||||||
|
return QFrame::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
switch (event->type())
|
||||||
|
{
|
||||||
|
case QEvent::MouseButtonPress: {
|
||||||
|
auto *me = static_cast<QMouseEvent *>(event);
|
||||||
|
if (me->button() != Qt::LeftButton) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_grip_active = true;
|
||||||
|
m_grip_press_x = me->globalPosition().toPoint().x();
|
||||||
|
m_grip_press_width = width();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case QEvent::MouseMove: {
|
||||||
|
if (!m_grip_active) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto *me = static_cast<QMouseEvent *>(event);
|
||||||
|
const int w = qMax(minimumWidth(), m_grip_press_width
|
||||||
|
+ me->globalPosition().toPoint().x() - m_grip_press_x);
|
||||||
|
layoutTiles(w);
|
||||||
|
layout()->activate();
|
||||||
|
resize(w, sizeHint().height());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case QEvent::MouseButtonRelease:
|
||||||
|
if (!m_grip_active) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_grip_active = false;
|
||||||
|
ShortcutBarSettings::setBarWidth(width());
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return QFrame::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::barItem
|
||||||
|
@return a list item for @a id, a command or a pinned element: icon only,
|
||||||
|
for the bar row, or icon and text, for the other lists. The id is in
|
||||||
|
UserRole.
|
||||||
|
*/
|
||||||
|
QListWidgetItem *ElementPickerPopup::barItem(const QString &id, bool icon_only) const
|
||||||
|
{
|
||||||
|
QString text = id;
|
||||||
|
QIcon icon;
|
||||||
|
if (ShortcutBarSettings::isElement(id)) {
|
||||||
|
const ElementsLocation location(id);
|
||||||
|
if (location.exist()) {
|
||||||
|
text = location.name();
|
||||||
|
icon = location.icon();
|
||||||
|
}
|
||||||
|
} else if (QAction *action = commandAction(id)) {
|
||||||
|
text = action->text().remove(QLatin1Char('&'));
|
||||||
|
icon = action->icon();
|
||||||
|
}
|
||||||
|
auto *item = new QListWidgetItem();
|
||||||
|
item->setData(Qt::UserRole, id);
|
||||||
|
item->setToolTip(text);
|
||||||
|
if (!icon.isNull()) {
|
||||||
|
item->setIcon(icon);
|
||||||
|
}
|
||||||
|
if (!icon_only || item->icon().isNull()) {
|
||||||
|
item->setText(text);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::startCustomising
|
||||||
|
Turn the bar into a small window holding the two lists. A Qt::Popup
|
||||||
|
closes as soon as the mouse is pressed outside it and keeps the mouse
|
||||||
|
grab during a drag, so the window is re-shown as a tool window for the
|
||||||
|
time of the edit.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::startCustomising()
|
||||||
|
{
|
||||||
|
const QPoint where = pos();
|
||||||
|
m_customising = true;
|
||||||
|
hide();
|
||||||
|
setWindowFlags(Qt::Tool);
|
||||||
|
setWindowTitle(tr("Personnaliser la barre de raccourcis : %1")
|
||||||
|
.arg(ShortcutBarSettings::title(m_context)));
|
||||||
|
|
||||||
|
fillCustomising(ShortcutBarSettings::ids(m_context));
|
||||||
|
m_commands->hide();
|
||||||
|
setPickerVisible(false);
|
||||||
|
m_grip->hide();
|
||||||
|
//Elements are pinned to the empty-folio bar only: with something
|
||||||
|
//selected, the bar is for acting on it, not for adding more
|
||||||
|
m_edit_symbols_search->clear();
|
||||||
|
m_edit_symbols->clear();
|
||||||
|
m_edit_symbols_box->setVisible(m_context == ShortcutBarSettings::Canvas);
|
||||||
|
m_editor->show();
|
||||||
|
|
||||||
|
keepOnScreen(where);
|
||||||
|
QFrame::show();
|
||||||
|
activateWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::fillCustomising
|
||||||
|
Put @a ids on the bar row, and every other available command in the
|
||||||
|
list below it.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::fillCustomising(const QStringList &ids)
|
||||||
|
{
|
||||||
|
m_edit_row->clear();
|
||||||
|
m_edit_available->clear();
|
||||||
|
for (const QString &id : ids) {
|
||||||
|
m_edit_row->addItem(barItem(id, true));
|
||||||
|
}
|
||||||
|
for (const QString &id : ShortcutBarSettings::availableIds()) {
|
||||||
|
if (!ids.contains(id)) {
|
||||||
|
m_edit_available->addItem(barItem(id, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_edit_available->sortItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::runSymbolSearch
|
||||||
|
Fill the customising window's element list with the ranked hits for its
|
||||||
|
search field. Elements embedded in a project are left out: their path
|
||||||
|
names the project as it is loaded now, so it would not find them again
|
||||||
|
in a later session.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::runSymbolSearch()
|
||||||
|
{
|
||||||
|
m_edit_symbols->clear();
|
||||||
|
if (!m_source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QVector<ElementSearchHit> hits =
|
||||||
|
m_source->rankedSearch(m_edit_symbols_search->text());
|
||||||
|
for (const ElementSearchHit &hit : hits)
|
||||||
|
{
|
||||||
|
if (ElementsLocation(hit.path).isProject()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto *item = new QListWidgetItem(hit.icon, hit.name);
|
||||||
|
item->setData(Qt::UserRole, hit.path);
|
||||||
|
item->setToolTip(QStringLiteral("%1\n%2").arg(hit.name, hit.folder));
|
||||||
|
m_edit_symbols->addItem(item);
|
||||||
|
if (m_edit_symbols->count() >= max_palette_entries) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::finishCustomising
|
||||||
|
Leave the edit, saving the bar row when @a save, and show the bar again
|
||||||
|
at the cursor so the result can be seen and used straight away. The
|
||||||
|
window's size is kept either way.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::finishCustomising(bool save)
|
||||||
|
{
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
QStringList ids;
|
||||||
|
for (int i = 0 ; i < m_edit_row->count() ; ++i) {
|
||||||
|
ids << m_edit_row->item(i)->data(Qt::UserRole).toString();
|
||||||
|
}
|
||||||
|
ShortcutBarSettings::setIds(m_context, ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortcutBarSettings::setEditorSize(size());
|
||||||
|
m_customising = false;
|
||||||
|
hide();
|
||||||
|
m_editor->hide();
|
||||||
|
setPickerVisible(true);
|
||||||
|
setWindowFlags(Qt::Popup);
|
||||||
|
setWindowTitle(QString());
|
||||||
|
//At the cursor, like any other opening of the bar
|
||||||
|
popUpShortcutBar(QCursor::pos(), m_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::closeEvent
|
||||||
|
Closing the customising window from its title bar cancels the edit,
|
||||||
|
like the Annuler button, rather than leaving the bar half-edited.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::closeEvent(QCloseEvent *event)
|
||||||
|
{
|
||||||
|
if (m_customising) {
|
||||||
|
event->ignore();
|
||||||
|
finishCustomising(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QFrame::closeEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::setPickerVisible
|
||||||
|
Show or hide the search field, the results and the hint line.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::setPickerVisible(bool visible)
|
||||||
|
{
|
||||||
|
m_search->setVisible(visible);
|
||||||
|
m_view->setVisible(visible);
|
||||||
|
m_hint->setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::runSearch
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::runSearch()
|
||||||
|
{
|
||||||
|
m_model->clear();
|
||||||
|
if (!m_source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_search->text().isEmpty()) {
|
||||||
|
const bool was_hidden = m_view->isHidden();
|
||||||
|
showPalette();
|
||||||
|
if (was_hidden != m_view->isHidden()) {
|
||||||
|
keepOnScreen(pos());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_view->isHidden()) {
|
||||||
|
m_view->show();
|
||||||
|
keepOnScreen(pos());
|
||||||
|
}
|
||||||
|
m_palette_mode = false;
|
||||||
|
m_view->setViewMode(QListView::ListMode);
|
||||||
|
m_view->setGridSize(QSize());
|
||||||
|
m_view->setIconSize(QSize(40, 40));
|
||||||
|
|
||||||
|
const QVector<ElementSearchHit> hits = m_source->rankedSearch(m_search->text());
|
||||||
|
for (const ElementSearchHit &hit : hits)
|
||||||
|
{
|
||||||
|
auto *item = new QStandardItem(hit.name);
|
||||||
|
item->setIcon(hit.icon);
|
||||||
|
item->setEditable(false);
|
||||||
|
item->setToolTip(hit.folder);
|
||||||
|
item->setData(hit.path, Qt::UserRole + 2);
|
||||||
|
m_model->appendRow(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Preselect the best hit so Enter works straight from the search field
|
||||||
|
//without arrowing down first -- that is the whole point of the popup.
|
||||||
|
if (m_model->rowCount()) {
|
||||||
|
m_view->setCurrentIndex(m_model->index(0, 0));
|
||||||
|
}
|
||||||
|
m_hint->setText(hits.isEmpty()
|
||||||
|
? tr("Aucun résultat")
|
||||||
|
: tr("Entrée pour insérer · Échap pour fermer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::chooseCurrent
|
||||||
|
Emit the highlighted element and close.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::chooseCurrent()
|
||||||
|
{
|
||||||
|
const QModelIndex index = m_view->currentIndex();
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QString path = index.data(Qt::UserRole + 2).toString();
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementsLocation location(path);
|
||||||
|
if (!location.exist()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close before emitting: placement mode wants the focus, and a popup
|
||||||
|
//still up would keep the grab.
|
||||||
|
hide();
|
||||||
|
emit elementChosen(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::keyPressEvent
|
||||||
|
Drive the list from the search field, so the hands never leave the
|
||||||
|
keyboard: Up/Down move the selection, Enter places, Esc closes.
|
||||||
|
@param event
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
switch (event->key())
|
||||||
|
{
|
||||||
|
case Qt::Key_Escape:
|
||||||
|
if (m_customising) {
|
||||||
|
finishCustomising(false);
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt::Key_Return:
|
||||||
|
case Qt::Key_Enter:
|
||||||
|
if (m_customising && m_edit_symbols_search->hasFocus()) {
|
||||||
|
//Enter in the element search pins the best hit rather
|
||||||
|
//than closing the window. Searched now, not on the
|
||||||
|
//timer, so a quick Enter after typing gets this text.
|
||||||
|
runSymbolSearch();
|
||||||
|
if (QListWidgetItem *item = m_edit_symbols->item(0)) {
|
||||||
|
m_edit_row->addItem(barItem(item->data(Qt::UserRole).toString(), true));
|
||||||
|
}
|
||||||
|
} else if (m_customising) {
|
||||||
|
finishCustomising(true);
|
||||||
|
} else {
|
||||||
|
chooseCurrent();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt::Key_Down:
|
||||||
|
case Qt::Key_Up:
|
||||||
|
case Qt::Key_PageDown:
|
||||||
|
case Qt::Key_PageUp:
|
||||||
|
if (!m_customising && m_model->rowCount()) {
|
||||||
|
//Forwarded rather than focus-switched, so typing carries
|
||||||
|
//on going to the search field.
|
||||||
|
QCoreApplication::sendEvent(m_view, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
QFrame::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::showPalette
|
||||||
|
With an empty search field, show the quick palette as an icon grid.
|
||||||
|
|
||||||
|
The palette is a folder, not a config file. QET already has a user-owned,
|
||||||
|
drag-populated, icon-rendering collection -- so a palette is just a
|
||||||
|
directory whose subfolders are categories and whose contents are entries.
|
||||||
|
That gives set-up (drag into the folder), ordering (the 01_/02_ filename
|
||||||
|
convention the shipped collection already uses), team sharing
|
||||||
|
(companyElementsDir) and version control for free, with no schema, parser,
|
||||||
|
merge rules or "reset to defaults" to build or maintain.
|
||||||
|
|
||||||
|
Only the path lives in QSettings, defaulting to the user's custom
|
||||||
|
collection.
|
||||||
|
*/
|
||||||
|
void ElementPickerPopup::showPalette()
|
||||||
|
{
|
||||||
|
m_palette_mode = true;
|
||||||
|
m_model->clear();
|
||||||
|
|
||||||
|
//Elements pinned to the bar replace the folder grid: the bar already
|
||||||
|
//shows the user's shortlist, and a second one below it is noise.
|
||||||
|
//The search still works; the list comes back as soon as one types.
|
||||||
|
if (m_bar_mode && ShortcutBarSettings::hasElements(m_context)) {
|
||||||
|
m_view->hide();
|
||||||
|
m_hint->setText(tr("Tapez pour rechercher un élément · Échap pour fermer"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_view->show();
|
||||||
|
|
||||||
|
m_view->setViewMode(QListView::IconMode);
|
||||||
|
m_view->setIconSize(QSize(48, 48));
|
||||||
|
m_view->setGridSize(QSize(92, 84));
|
||||||
|
m_view->setResizeMode(QListView::Adjust);
|
||||||
|
m_view->setWordWrap(true);
|
||||||
|
m_view->setMovement(QListView::Static);
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
const QString path = settings.value(
|
||||||
|
QStringLiteral("elementscollection/palette-path"),
|
||||||
|
QETApp::customElementsDir()).toString();
|
||||||
|
|
||||||
|
const int count = loadPaletteDir(path, QString(), 0);
|
||||||
|
|
||||||
|
if (!count) {
|
||||||
|
m_hint->setText(
|
||||||
|
tr("Palette vide — glissez des éléments dans votre collection "
|
||||||
|
"personnelle, ou tapez pour rechercher"));
|
||||||
|
} else {
|
||||||
|
m_hint->setText(tr("Entrée pour insérer · Échap pour fermer"));
|
||||||
|
m_view->setCurrentIndex(m_model->index(0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPickerPopup::loadPaletteDir
|
||||||
|
Add every .elmt under @a dir_path to the grid, recursing into subfolders.
|
||||||
|
@param dir_path
|
||||||
|
@param prefix : folder path so far, shown as the entry's tooltip
|
||||||
|
@param depth : recursion guard -- a palette is a shortlist, and the whole
|
||||||
|
shipped collection would not be usable as a grid anyway
|
||||||
|
@return how many entries were added
|
||||||
|
*/
|
||||||
|
int ElementPickerPopup::loadPaletteDir(const QString &dir_path,
|
||||||
|
const QString &prefix, int depth)
|
||||||
|
{
|
||||||
|
if (depth > 3 || m_model->rowCount() >= max_palette_entries) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDir dir(dir_path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int added = 0;
|
||||||
|
//Sorted by name, which is what makes the 01_/02_ filename convention
|
||||||
|
//work as the ordering mechanism.
|
||||||
|
const QFileInfoList entries = dir.entryInfoList(
|
||||||
|
QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
|
||||||
|
|
||||||
|
for (const QFileInfo &info : entries)
|
||||||
|
{
|
||||||
|
if (m_model->rowCount() >= max_palette_entries) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (info.isDir()) {
|
||||||
|
added += loadPaletteDir(info.absoluteFilePath(),
|
||||||
|
prefix.isEmpty()
|
||||||
|
? info.fileName()
|
||||||
|
: prefix + QStringLiteral(" / ")
|
||||||
|
+ info.fileName(),
|
||||||
|
depth + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (info.suffix().compare(QStringLiteral("elmt"),
|
||||||
|
Qt::CaseInsensitive) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementsLocation location(info.absoluteFilePath());
|
||||||
|
if (!location.exist()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *item = new QStandardItem(location.name().isEmpty()
|
||||||
|
? info.completeBaseName()
|
||||||
|
: location.name());
|
||||||
|
item->setIcon(location.icon());
|
||||||
|
item->setEditable(false);
|
||||||
|
item->setToolTip(prefix.isEmpty() ? info.completeBaseName() : prefix);
|
||||||
|
item->setData(info.absoluteFilePath(), Qt::UserRole + 2);
|
||||||
|
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignTop);
|
||||||
|
m_model->appendRow(item);
|
||||||
|
++added;
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
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 ELEMENTPICKERPOPUP_H
|
||||||
|
#define ELEMENTPICKERPOPUP_H
|
||||||
|
|
||||||
|
#include "elementslocation.h"
|
||||||
|
#include "../shortcutbarsettings.h"
|
||||||
|
|
||||||
|
#include <QFrame>
|
||||||
|
|
||||||
|
class ElementsCollectionWidget;
|
||||||
|
class QLineEdit;
|
||||||
|
class QListView;
|
||||||
|
class QStandardItemModel;
|
||||||
|
class QLabel;
|
||||||
|
class QAction;
|
||||||
|
class QHBoxLayout;
|
||||||
|
class QListWidget;
|
||||||
|
class QListWidgetItem;
|
||||||
|
class QSizeGrip;
|
||||||
|
class QToolButton;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief A cursor-anchored element picker.
|
||||||
|
|
||||||
|
Opens where the mouse is, focused on its search field: type to filter,
|
||||||
|
Enter to place, Esc to close. It is a recall tool, as opposed to the
|
||||||
|
Collections dock, which is a browsing tool.
|
||||||
|
|
||||||
|
It deliberately does not build its own ElementsCollectionModel. Loading the
|
||||||
|
collection is already the slow part of startup and a second copy would
|
||||||
|
double it, so the picker asks the dock's widget to run the query -- see
|
||||||
|
ElementsCollectionWidget::rankedSearch(). That also means the two always
|
||||||
|
agree on results and ranking.
|
||||||
|
*/
|
||||||
|
class ElementPickerPopup : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ElementPickerPopup(ElementsCollectionWidget *source,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void popUpAt(const QPoint &global_pos);
|
||||||
|
void popUpShortcutBar(const QPoint &global_pos,
|
||||||
|
ShortcutBarSettings::Context context);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// Emitted when the user picks an element; the popup has closed
|
||||||
|
void elementChosen(const ElementsLocation &location);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void runSearch();
|
||||||
|
void chooseCurrent();
|
||||||
|
void showPalette();
|
||||||
|
void show(const QPoint &global_pos);
|
||||||
|
void setCommands(const QStringList &ids);
|
||||||
|
void layoutTiles(int bar_width);
|
||||||
|
QAction *commandAction(const QString &id) const;
|
||||||
|
QListWidgetItem *barItem(const QString &id, bool icon_only) const;
|
||||||
|
void startCustomising();
|
||||||
|
void fillCustomising(const QStringList &ids);
|
||||||
|
void finishCustomising(bool save);
|
||||||
|
void runSymbolSearch();
|
||||||
|
void setPickerVisible(bool visible);
|
||||||
|
void keepOnScreen(const QPoint &global_pos);
|
||||||
|
int loadPaletteDir(const QString &dir_path, const QString &prefix,
|
||||||
|
int depth);
|
||||||
|
|
||||||
|
ElementsCollectionWidget *m_source = nullptr;
|
||||||
|
QLineEdit *m_search = nullptr;
|
||||||
|
QListView *m_view = nullptr;
|
||||||
|
QStandardItemModel *m_model = nullptr;
|
||||||
|
QLabel *m_hint = nullptr;
|
||||||
|
QWidget *m_commands = nullptr;
|
||||||
|
QHBoxLayout *m_commands_layout = nullptr;
|
||||||
|
/// The bar's tiles, commands and elements, in order
|
||||||
|
QList<QToolButton *> m_tiles;
|
||||||
|
QVBoxLayout *m_tile_rows = nullptr;
|
||||||
|
QSizeGrip *m_grip = nullptr;
|
||||||
|
/// The size grip is being dragged, from this x at this width
|
||||||
|
bool m_grip_active = false;
|
||||||
|
int m_grip_press_x = 0;
|
||||||
|
int m_grip_press_width = 0;
|
||||||
|
/// Opened as the shortcut bar, as opposed to the plain picker
|
||||||
|
bool m_bar_mode = false;
|
||||||
|
ShortcutBarSettings::Context m_context = ShortcutBarSettings::Canvas;
|
||||||
|
/// Customising the bar in place
|
||||||
|
bool m_customising = false;
|
||||||
|
QWidget *m_editor = nullptr;
|
||||||
|
QListWidget *m_edit_row = nullptr;
|
||||||
|
QListWidget *m_edit_available = nullptr;
|
||||||
|
/// Element search, for pinning elements to the bar
|
||||||
|
QWidget *m_edit_symbols_box = nullptr;
|
||||||
|
QLineEdit *m_edit_symbols_search = nullptr;
|
||||||
|
QListWidget *m_edit_symbols = nullptr;
|
||||||
|
bool m_palette_mode = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ELEMENTPICKERPOPUP_H
|
||||||
@@ -49,6 +49,14 @@ ElementsCollectionModel::ElementsCollectionModel(QObject *parent) :
|
|||||||
*/
|
*/
|
||||||
ElementsCollectionModel::~ElementsCollectionModel()
|
ElementsCollectionModel::~ElementsCollectionModel()
|
||||||
{
|
{
|
||||||
|
// Without cancel(), the wait below runs the whole queued
|
||||||
|
// QtConcurrent::map() to completion, so closing this dialog on a
|
||||||
|
// large collection blocks until every remaining item has been
|
||||||
|
// processed -- a visible hang on the button pressed precisely to
|
||||||
|
// stop the work. cancel() drops the not-yet-started items so the
|
||||||
|
// wait that follows (still needed, so an in-flight item can't
|
||||||
|
// dereference this object after it's gone) is short.
|
||||||
|
m_future.cancel();
|
||||||
m_future.waitForFinished();
|
m_future.waitForFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +439,7 @@ void ElementsCollectionModel::addLocation(const ElementsLocation& location)
|
|||||||
collection_name);
|
collection_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ANPASSUNG: Makros und Custom Collection werden hier behandelt!
|
// Macros and Custom Collection are handled here
|
||||||
else if (location.isCustomCollection() || location.isMacrosCollection()) {
|
else if (location.isCustomCollection() || location.isMacrosCollection()) {
|
||||||
QList <ElementCollectionItem *> child_list;
|
QList <ElementCollectionItem *> child_list;
|
||||||
|
|
||||||
@@ -495,6 +503,10 @@ void ElementsCollectionModel::addProject(QETProject *project, bool set_data)
|
|||||||
connect(project->embeddedElementCollection(),
|
connect(project->embeddedElementCollection(),
|
||||||
&XmlElementCollection::directoryRemoved,
|
&XmlElementCollection::directoryRemoved,
|
||||||
this, &ElementsCollectionModel::itemRemovedFromCollection);
|
this, &ElementsCollectionModel::itemRemovedFromCollection);
|
||||||
|
connect(project, &QETProject::projectTitleChanged,
|
||||||
|
this, &ElementsCollectionModel::projectNameChanged);
|
||||||
|
connect(project, &QETProject::projectFilePathChanged,
|
||||||
|
this, &ElementsCollectionModel::projectNameChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -526,6 +538,10 @@ void ElementsCollectionModel::removeProject(QETProject *project)
|
|||||||
&XmlElementCollection::directoryRemoved,
|
&XmlElementCollection::directoryRemoved,
|
||||||
this,
|
this,
|
||||||
&ElementsCollectionModel::itemRemovedFromCollection);
|
&ElementsCollectionModel::itemRemovedFromCollection);
|
||||||
|
disconnect(project, &QETProject::projectTitleChanged,
|
||||||
|
this, &ElementsCollectionModel::projectNameChanged);
|
||||||
|
disconnect(project, &QETProject::projectFilePathChanged,
|
||||||
|
this, &ElementsCollectionModel::projectNameChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,7 +663,7 @@ QModelIndex ElementsCollectionModel::indexFromLocation(
|
|||||||
if (eci->type() == FileElementCollectionItem::Type) {
|
if (eci->type() == FileElementCollectionItem::Type) {
|
||||||
if (FileElementCollectionItem *feci = static_cast<FileElementCollectionItem *>(eci)) {
|
if (FileElementCollectionItem *feci = static_cast<FileElementCollectionItem *>(eci)) {
|
||||||
|
|
||||||
// ANPASSUNG: Makro-Prüfung hinzugefügt, damit das Modell den Pfad im Baum findet!
|
// Macro check added so the model finds the path in the tree
|
||||||
if ( (location.isCommonCollection() && feci->isCommonCollection()) ||
|
if ( (location.isCommonCollection() && feci->isCommonCollection()) ||
|
||||||
(location.isCompanyCollection() && feci->isCompanyCollection()) ||
|
(location.isCompanyCollection() && feci->isCompanyCollection()) ||
|
||||||
(location.isMacrosCollection() && feci->isMacrosCollection()) ||
|
(location.isMacrosCollection() && feci->isMacrosCollection()) ||
|
||||||
@@ -766,3 +782,15 @@ void ElementsCollectionModel::updateItem(const QString& path)
|
|||||||
eci->setUpData();
|
eci->setUpData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionModel::projectNameChanged
|
||||||
|
Update the displayed name of the collection of project,
|
||||||
|
when its title or its file path changed.
|
||||||
|
@param project
|
||||||
|
*/
|
||||||
|
void ElementsCollectionModel::projectNameChanged(QETProject *project)
|
||||||
|
{
|
||||||
|
if (XmlProjectElementCollectionItem *xpeci = m_project_hash.value(project))
|
||||||
|
xpeci->updateProjectName();
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class ElementsCollectionModel : public QStandardItemModel
|
|||||||
void elementIntegratedToCollection (const QString& path);
|
void elementIntegratedToCollection (const QString& path);
|
||||||
void itemRemovedFromCollection (const QString& path);
|
void itemRemovedFromCollection (const QString& path);
|
||||||
void updateItem (const QString& path);
|
void updateItem (const QString& path);
|
||||||
|
void projectNameChanged (QETProject *project);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList <QETProject *> m_project_list;
|
QList <QETProject *> m_project_list;
|
||||||
|
|||||||
@@ -35,6 +35,12 @@
|
|||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QShortcut>
|
||||||
|
#include <QListView>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QSet>
|
||||||
|
#include <algorithm>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
@@ -49,6 +55,32 @@
|
|||||||
#include <QStatusBar>
|
#include <QStatusBar>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/**
|
||||||
|
@brief The SearchResultsView class
|
||||||
|
The flat list of search results. Rows are not backed by the collection
|
||||||
|
model, so the drag is built here from the path each row carries, with
|
||||||
|
the same content and pixmap as a drag from the tree.
|
||||||
|
*/
|
||||||
|
class SearchResultsView : public QListView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using QListView::QListView;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void startDrag(Qt::DropActions supportedActions) override
|
||||||
|
{
|
||||||
|
const QString path =
|
||||||
|
currentIndex().data(Qt::UserRole + 2).toString();
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
QListView::startDrag(supportedActions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ElementsTreeView::execElementDrag(this, ElementsLocation(path));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief ElementsCollectionWidget::ElementsCollectionWidget
|
@brief ElementsCollectionWidget::ElementsCollectionWidget
|
||||||
Default constructor.
|
Default constructor.
|
||||||
@@ -217,8 +249,26 @@ void ElementsCollectionWidget::setUpWidget()
|
|||||||
m_tab_widget->addTab(m_tree_view, tr("Collections"));
|
m_tab_widget->addTab(m_tree_view, tr("Collections"));
|
||||||
m_tab_widget->addTab(m_macros_tree_view, tr("Modèles"));
|
m_tab_widget->addTab(m_macros_tree_view, tr("Modèles"));
|
||||||
|
|
||||||
|
//Flat ranked search results.
|
||||||
|
//The tree search hides non-matching rows, so hits stay scattered
|
||||||
|
//through five levels of expanded folders -- searching "diode" leaves
|
||||||
|
//you scrolling a tree to find them. This shows the same matches as a
|
||||||
|
//ranked list instead, and takes the tab widget's place while a search
|
||||||
|
//is active.
|
||||||
|
m_search_model = new QStandardItemModel(this);
|
||||||
|
m_search_results = new SearchResultsView(this);
|
||||||
|
m_search_results->setModel(m_search_model);
|
||||||
|
m_search_results->setDragDropMode(QAbstractItemView::DragOnly);
|
||||||
|
m_search_results->setIconSize(QSize(50, 50));
|
||||||
|
m_search_results->setUniformItemSizes(false);
|
||||||
|
m_search_results->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
m_search_results->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
m_search_results->setMouseTracking(true);
|
||||||
|
m_search_results->hide();
|
||||||
|
|
||||||
m_main_vlayout->addWidget(m_search_field);
|
m_main_vlayout->addWidget(m_search_field);
|
||||||
m_main_vlayout->addWidget(m_tab_widget);
|
m_main_vlayout->addWidget(m_tab_widget);
|
||||||
|
m_main_vlayout->addWidget(m_search_results);
|
||||||
|
|
||||||
m_progress_bar = new QProgressBar(this);
|
m_progress_bar = new QProgressBar(this);
|
||||||
m_progress_bar->setFormat(QObject::tr("chargement %p% (%v sur %m)"));
|
m_progress_bar->setFormat(QObject::tr("chargement %p% (%v sur %m)"));
|
||||||
@@ -266,15 +316,7 @@ void ElementsCollectionWidget::setUpConnection()
|
|||||||
this, &ElementsCollectionWidget::dirProperties);
|
this, &ElementsCollectionWidget::dirProperties);
|
||||||
|
|
||||||
connect(m_tree_view, &QTreeView::doubleClicked,
|
connect(m_tree_view, &QTreeView::doubleClicked,
|
||||||
[this](const QModelIndex &index)
|
[this](const QModelIndex &index) { this->activateIndex(index); });
|
||||||
{
|
|
||||||
this->m_index_at_context_menu = index ;
|
|
||||||
ElementCollectionItem *eci = elementCollectionItemForIndex(index);
|
|
||||||
if (eci && eci->collectionPath().endsWith(".qetmak")) {
|
|
||||||
return; // Do nothing on double click for macros
|
|
||||||
}
|
|
||||||
this->editElement();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_tree_view, &QTreeView::entered,
|
connect(m_tree_view, &QTreeView::entered,
|
||||||
[this] (const QModelIndex &index) {
|
[this] (const QModelIndex &index) {
|
||||||
@@ -284,19 +326,58 @@ void ElementsCollectionWidget::setUpConnection()
|
|||||||
qde->statusBar()->showMessage(eci->localName());
|
qde->statusBar()->showMessage(eci->localName());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Enter on the highlighted item does the same as a double click, so a
|
||||||
|
//run of elements can be placed without leaving the keyboard. Bound as
|
||||||
|
//a shortcut on the view rather than by reimplementing keyPressEvent,
|
||||||
|
//which would mean subclassing ElementsTreeView for one key.
|
||||||
|
for (const auto key : {Qt::Key_Return, Qt::Key_Enter}) {
|
||||||
|
auto *sc = new QShortcut(QKeySequence(key), m_tree_view);
|
||||||
|
sc->setContext(Qt::WidgetShortcut);
|
||||||
|
connect(sc, &QShortcut::activated, this, [this]() {
|
||||||
|
this->activateIndex(m_tree_view->currentIndex());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//The flat results list carries the collection path directly, so it
|
||||||
|
//does not go through activateIndex() -- there is no tree index behind
|
||||||
|
//a row to look an ElementCollectionItem up from.
|
||||||
|
auto place_from_results = [this](const QModelIndex &index) {
|
||||||
|
const QString path = index.data(Qt::UserRole + 2).toString();
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ElementsLocation location(path);
|
||||||
|
if (location.exist()) {
|
||||||
|
emit insertElementRequested(location);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(m_search_results, &QListView::doubleClicked, this, place_from_results);
|
||||||
|
for (const auto key : {Qt::Key_Return, Qt::Key_Enter}) {
|
||||||
|
auto *sc = new QShortcut(QKeySequence(key), m_search_results);
|
||||||
|
sc->setContext(Qt::WidgetShortcut);
|
||||||
|
connect(sc, &QShortcut::activated, this, [this, place_from_results]() {
|
||||||
|
place_from_results(m_search_results->currentIndex());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//Down from the search field moves into the results, so the whole
|
||||||
|
//type-then-place run happens without touching the mouse.
|
||||||
|
auto *to_results = new QShortcut(QKeySequence(Qt::Key_Down), m_search_field);
|
||||||
|
to_results->setContext(Qt::WidgetShortcut);
|
||||||
|
connect(to_results, &QShortcut::activated, this, [this]() {
|
||||||
|
if (!m_search_results->isVisible() || !m_search_model->rowCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_search_results->setFocus();
|
||||||
|
if (!m_search_results->currentIndex().isValid()) {
|
||||||
|
m_search_results->setCurrentIndex(m_search_model->index(0, 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_macros_tree_view, &QTreeView::customContextMenuRequested,
|
connect(m_macros_tree_view, &QTreeView::customContextMenuRequested,
|
||||||
this, &ElementsCollectionWidget::customContextMenu);
|
this, &ElementsCollectionWidget::customContextMenu);
|
||||||
|
|
||||||
connect(m_macros_tree_view, &QTreeView::doubleClicked,
|
connect(m_macros_tree_view, &QTreeView::doubleClicked,
|
||||||
[this](const QModelIndex &index)
|
[this](const QModelIndex &index) { this->activateIndex(index); });
|
||||||
{
|
|
||||||
this->m_index_at_context_menu = index ;
|
|
||||||
ElementCollectionItem *eci = elementCollectionItemForIndex(index);
|
|
||||||
if (eci && eci->collectionPath().endsWith(".qetmak")) {
|
|
||||||
return; // Do nothing on double click for macros
|
|
||||||
}
|
|
||||||
this->editElement();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_macros_tree_view, &QTreeView::entered,
|
connect(m_macros_tree_view, &QTreeView::entered,
|
||||||
[this] (const QModelIndex &index) {
|
[this] (const QModelIndex &index) {
|
||||||
@@ -405,6 +486,60 @@ void ElementsCollectionWidget::openDir()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionWidget::activateIndex
|
||||||
|
What a double click (or Enter) on @a index does.
|
||||||
|
|
||||||
|
Historically this opened the element editor, which is the slowest action
|
||||||
|
available on a symbol you are most likely about to place. Placing is now
|
||||||
|
the default and editing has moved to the context menu, where it already
|
||||||
|
was. The old behaviour is preserved behind a preference for anyone who
|
||||||
|
relies on it.
|
||||||
|
@param index
|
||||||
|
*/
|
||||||
|
void ElementsCollectionWidget::activateIndex(const QModelIndex &index)
|
||||||
|
{
|
||||||
|
m_index_at_context_menu = index;
|
||||||
|
|
||||||
|
ElementCollectionItem *eci = elementCollectionItemForIndex(index);
|
||||||
|
if (!eci) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Macros are placed, never edited, and were already skipped here.
|
||||||
|
const bool is_macro = eci->collectionPath().endsWith(".qetmak");
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
const bool insert = settings.value(
|
||||||
|
QStringLiteral("elementscollection/double-click-inserts"), true).toBool();
|
||||||
|
|
||||||
|
if (insert) {
|
||||||
|
insertCurrentElement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!is_macro) {
|
||||||
|
editElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionWidget::insertCurrentElement
|
||||||
|
Ask for the current item to be placed on the folio.
|
||||||
|
*/
|
||||||
|
void ElementsCollectionWidget::insertCurrentElement()
|
||||||
|
{
|
||||||
|
ElementCollectionItem *eci =
|
||||||
|
elementCollectionItemForIndex(m_index_at_context_menu);
|
||||||
|
if (!(eci && eci->isElement())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementsLocation location(eci->collectionPath());
|
||||||
|
if (!location.exist()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit insertElementRequested(location);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief ElementsCollectionWidget::editElement
|
@brief ElementsCollectionWidget::editElement
|
||||||
Edit the element represented by the current selected item
|
Edit the element represented by the current selected item
|
||||||
@@ -415,10 +550,19 @@ void ElementsCollectionWidget::editElement()
|
|||||||
|
|
||||||
if ( !(eci && eci->isElement()) ) return;
|
if ( !(eci && eci->isElement()) ) return;
|
||||||
|
|
||||||
// Prevent the element editor from opening for macros
|
editLocation(ElementsLocation(eci->collectionPath()));
|
||||||
if (eci->collectionPath().endsWith(".qetmak")) return;
|
}
|
||||||
|
|
||||||
ElementsLocation location(eci->collectionPath());
|
/**
|
||||||
|
@brief ElementsCollectionWidget::editLocation
|
||||||
|
Open the element editor on @a location. Macros have no editor and are
|
||||||
|
ignored.
|
||||||
|
@param location
|
||||||
|
*/
|
||||||
|
void ElementsCollectionWidget::editLocation(const ElementsLocation &location)
|
||||||
|
{
|
||||||
|
// Prevent the element editor from opening for macros
|
||||||
|
if (!location.exist() || location.path().endsWith(".qetmak")) return;
|
||||||
|
|
||||||
QETApp *app = QETApp::instance();
|
QETApp *app = QETApp::instance();
|
||||||
app->openElementLocations(QList<ElementsLocation>() << location);
|
app->openElementLocations(QList<ElementsLocation>() << location);
|
||||||
@@ -932,6 +1076,7 @@ void ElementsCollectionWidget::search()
|
|||||||
//Reset the search
|
//Reset the search
|
||||||
if (text.isEmpty())
|
if (text.isEmpty())
|
||||||
{
|
{
|
||||||
|
clearFlatResults();
|
||||||
QModelIndex current_index = m_tree_view->currentIndex();
|
QModelIndex current_index = m_tree_view->currentIndex();
|
||||||
m_tree_view->reset();
|
m_tree_view->reset();
|
||||||
|
|
||||||
@@ -958,10 +1103,20 @@ void ElementsCollectionWidget::search()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
if (settings.value(QStringLiteral("elementscollection/search-flat-list"),
|
||||||
|
true).toBool())
|
||||||
|
{
|
||||||
|
showFlatResults(rankedSearch(text, m_showed_index));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Tree search: hide what does not match, expand what does.
|
||||||
|
clearFlatResults();
|
||||||
hideCollection(true);
|
hideCollection(true);
|
||||||
const QStringList text_list = text.split("+", Qt::SkipEmptyParts);
|
const QStringList text_list = text.split("+", Qt::SkipEmptyParts);
|
||||||
QModelIndexList match_index;
|
QModelIndexList match_index;
|
||||||
for (QString txt : text_list) {
|
for (const QString &txt : text_list) {
|
||||||
match_index << m_model->match(m_showed_index.isValid()
|
match_index << m_model->match(m_showed_index.isValid()
|
||||||
? m_model->index(0,0,m_showed_index)
|
? m_model->index(0,0,m_showed_index)
|
||||||
: m_model->index(0,0),
|
: m_model->index(0,0),
|
||||||
@@ -972,10 +1127,157 @@ void ElementsCollectionWidget::search()
|
|||||||
| Qt::MatchRecursive);
|
| Qt::MatchRecursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(QModelIndex index : match_index)
|
for (const QModelIndex &index : std::as_const(match_index))
|
||||||
showAndExpandItem(index);
|
showAndExpandItem(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionWidget::rankedSearch
|
||||||
|
Run the collection search for @a text and return the element hits, best
|
||||||
|
first. The hits carry no model index, so a list that is not backed by the
|
||||||
|
collection model can show them too.
|
||||||
|
@param text : search text, "+" separating terms
|
||||||
|
@param within : restrict the search to this item's children, or the
|
||||||
|
whole collection when invalid
|
||||||
|
@return ranked hits, best first
|
||||||
|
*/
|
||||||
|
QVector<ElementSearchHit> ElementsCollectionWidget::rankedSearch(
|
||||||
|
const QString &text,
|
||||||
|
const QModelIndex &within)
|
||||||
|
{
|
||||||
|
QVector<ElementSearchHit> hits;
|
||||||
|
if (!m_model || text.size() < 3) {
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList terms = text.split("+", Qt::SkipEmptyParts);
|
||||||
|
QModelIndexList matches;
|
||||||
|
for (const QString &term : terms) {
|
||||||
|
matches << m_model->match(within.isValid()
|
||||||
|
? m_model->index(0, 0, within)
|
||||||
|
: m_model->index(0, 0),
|
||||||
|
Qt::UserRole+1,
|
||||||
|
QVariant(term),
|
||||||
|
-1,
|
||||||
|
Qt::MatchContains
|
||||||
|
| Qt::MatchRecursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString needle = terms.value(0).toLower();
|
||||||
|
QVector<QPair<int, ElementSearchHit>> scored;
|
||||||
|
QSet<QString> seen;
|
||||||
|
|
||||||
|
for (const QModelIndex &index : std::as_const(matches))
|
||||||
|
{
|
||||||
|
ElementCollectionItem *eci = elementCollectionItemForIndex(index);
|
||||||
|
if (!(eci && eci->isElement())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QString path = eci->collectionPath();
|
||||||
|
//match() is run once per "+"-separated term, so the same element
|
||||||
|
//can arrive several times.
|
||||||
|
if (path.isEmpty() || seen.contains(path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.insert(path);
|
||||||
|
|
||||||
|
ElementSearchHit hit;
|
||||||
|
hit.path = path;
|
||||||
|
hit.name = index.data(Qt::DisplayRole).toString();
|
||||||
|
hit.icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
|
||||||
|
//Where it lives, so two similarly-named symbols are tellable apart
|
||||||
|
QStringList parts;
|
||||||
|
for (QModelIndex p = index.parent(); p.isValid(); p = p.parent()) {
|
||||||
|
parts.prepend(p.data(Qt::DisplayRole).toString());
|
||||||
|
}
|
||||||
|
hit.folder = parts.join(QStringLiteral(" / "));
|
||||||
|
|
||||||
|
const QString hay = index.data(Qt::UserRole + 1).toString().toLower();
|
||||||
|
scored.append({rankMatch(needle, hit.name, hay), hit});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stable_sort(scored.begin(), scored.end(),
|
||||||
|
[](const QPair<int, ElementSearchHit> &a,
|
||||||
|
const QPair<int, ElementSearchHit> &b) {
|
||||||
|
return a.first > b.first;
|
||||||
|
});
|
||||||
|
hits.reserve(scored.size());
|
||||||
|
for (const auto &p : std::as_const(scored)) {
|
||||||
|
hits.append(p.second);
|
||||||
|
}
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionWidget::rankMatch
|
||||||
|
Score a hit so the list can be ordered by how well it matches.
|
||||||
|
|
||||||
|
The tree search treats every hit equally, which is fine when they stay in
|
||||||
|
place but useless in a ranked list: typing "diode" should not put an
|
||||||
|
element whose *description* mentions diodes above one actually called
|
||||||
|
"Diode". Name beats element-info field, earlier beats later, shorter beats
|
||||||
|
longer.
|
||||||
|
@param needle : lower-cased search text
|
||||||
|
@param name : the element's display name
|
||||||
|
@param haystack : the full indexed string (name + every info field)
|
||||||
|
@return a score, higher is better
|
||||||
|
*/
|
||||||
|
int ElementsCollectionWidget::rankMatch(const QString &needle,
|
||||||
|
const QString &name,
|
||||||
|
const QString &haystack)
|
||||||
|
{
|
||||||
|
const QString n = name.toLower();
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
if (n == needle) {
|
||||||
|
score = 1000;
|
||||||
|
} else if (n.startsWith(needle)) {
|
||||||
|
score = 800;
|
||||||
|
} else if (n.contains(needle)) {
|
||||||
|
score = 600 - qMin(n.indexOf(needle), 99);
|
||||||
|
} else if (haystack.contains(needle)) {
|
||||||
|
//Matched only on an info field -- manufacturer, reference,
|
||||||
|
//description. Still a real hit, just a weaker one.
|
||||||
|
score = 300;
|
||||||
|
}
|
||||||
|
//Among equally-placed hits prefer the shorter name: "Diode" over
|
||||||
|
//"Diode Zener bidirectional".
|
||||||
|
return score - qMin(n.size(), 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionWidget::showFlatResults
|
||||||
|
Replace the tree with a flat list of @a hits, in the order given.
|
||||||
|
@param hits : from rankedSearch()
|
||||||
|
*/
|
||||||
|
void ElementsCollectionWidget::showFlatResults(const QVector<ElementSearchHit> &hits)
|
||||||
|
{
|
||||||
|
m_search_model->clear();
|
||||||
|
|
||||||
|
for (const ElementSearchHit &hit : hits)
|
||||||
|
{
|
||||||
|
auto *item = new QStandardItem(hit.icon, hit.name);
|
||||||
|
item->setEditable(false);
|
||||||
|
item->setToolTip(hit.folder);
|
||||||
|
item->setData(hit.path, Qt::UserRole + 2);
|
||||||
|
m_search_model->appendRow(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tab_widget->hide();
|
||||||
|
m_search_results->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsCollectionWidget::clearFlatResults
|
||||||
|
Put the tree back when the search field is emptied.
|
||||||
|
*/
|
||||||
|
void ElementsCollectionWidget::clearFlatResults()
|
||||||
|
{
|
||||||
|
m_search_model->clear();
|
||||||
|
m_search_results->hide();
|
||||||
|
m_tab_widget->show();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief ElementsCollectionWidget::hideCollection
|
@brief ElementsCollectionWidget::hideCollection
|
||||||
Hide all collection displayed in this tree
|
Hide all collection displayed in this tree
|
||||||
|
|||||||
@@ -43,6 +43,22 @@ class ElementsTreeView;
|
|||||||
and all action needed to use this widget.
|
and all action needed to use this widget.
|
||||||
This is the element collection widget used in the diagram editor.
|
This is the element collection widget used in the diagram editor.
|
||||||
*/
|
*/
|
||||||
|
class QListView;
|
||||||
|
class QStandardItemModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief One ranked hit from a collection search.
|
||||||
|
Carries everything a result row needs, so a list that is not backed by
|
||||||
|
the collection model can show it.
|
||||||
|
*/
|
||||||
|
struct ElementSearchHit
|
||||||
|
{
|
||||||
|
QString path; ///< collection path, enough to build an ElementsLocation
|
||||||
|
QString name; ///< display name
|
||||||
|
QString folder; ///< where it lives, for telling similar names apart
|
||||||
|
QIcon icon;
|
||||||
|
};
|
||||||
|
|
||||||
class ElementsCollectionWidget : public QWidget
|
class ElementsCollectionWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -56,6 +72,8 @@ class ElementsCollectionWidget : public QWidget
|
|||||||
void removeProject (QETProject *project);
|
void removeProject (QETProject *project);
|
||||||
void highlightUnusedElement();
|
void highlightUnusedElement();
|
||||||
void setCurrentLocation(const ElementsLocation &location);
|
void setCurrentLocation(const ElementsLocation &location);
|
||||||
|
QVector<ElementSearchHit> rankedSearch(const QString &text,
|
||||||
|
const QModelIndex &within = QModelIndex());
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void leaveEvent(QEvent *event) override;
|
void leaveEvent(QEvent *event) override;
|
||||||
@@ -86,9 +104,26 @@ class ElementsCollectionWidget : public QWidget
|
|||||||
public slots:
|
public slots:
|
||||||
void reload();
|
void reload();
|
||||||
void loadingFinished();
|
void loadingFinished();
|
||||||
|
void insertCurrentElement();
|
||||||
|
void editLocation(const ElementsLocation &location);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/**
|
||||||
|
Emitted when the user asks for an element to be placed on the
|
||||||
|
current folio. Whoever hosts this widget decides which view
|
||||||
|
receives it -- the dock is inside a diagram editor, but the
|
||||||
|
picker popup is not, so the widget must not reach for an
|
||||||
|
ancestor editor itself.
|
||||||
|
*/
|
||||||
|
void insertElementRequested(const ElementsLocation &location);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void locationWasSaved(const ElementsLocation& location);
|
void locationWasSaved(const ElementsLocation& location);
|
||||||
|
void activateIndex(const QModelIndex &index);
|
||||||
|
void showFlatResults(const QVector<ElementSearchHit> &hits);
|
||||||
|
void clearFlatResults();
|
||||||
|
static int rankMatch(const QString &needle, const QString &name,
|
||||||
|
const QString &haystack);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -100,6 +135,9 @@ class ElementsCollectionWidget : public QWidget
|
|||||||
ElementsTreeView *m_tree_view;
|
ElementsTreeView *m_tree_view;
|
||||||
ElementsTreeView *m_macros_tree_view = nullptr;
|
ElementsTreeView *m_macros_tree_view = nullptr;
|
||||||
QTabWidget *m_tab_widget = nullptr;
|
QTabWidget *m_tab_widget = nullptr;
|
||||||
|
/// Flat ranked results, shown in place of the tree while searching
|
||||||
|
QListView *m_search_results = nullptr;
|
||||||
|
QStandardItemModel *m_search_model = nullptr;
|
||||||
QVBoxLayout *m_main_vlayout;
|
QVBoxLayout *m_main_vlayout;
|
||||||
QMenu *m_context_menu;
|
QMenu *m_context_menu;
|
||||||
QModelIndex m_index_at_context_menu;
|
QModelIndex m_index_at_context_menu;
|
||||||
|
|||||||
@@ -653,9 +653,16 @@ QDomElement ElementsLocation::xml() const
|
|||||||
if (!m_project)
|
if (!m_project)
|
||||||
{
|
{
|
||||||
QFile file (m_file_system_path);
|
QFile file (m_file_system_path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
return QDomElement();
|
||||||
|
|
||||||
QDomDocument docu;
|
QDomDocument docu;
|
||||||
if (docu.setContent(&file))
|
if (docu.setContent(&file))
|
||||||
|
{
|
||||||
|
file.close();
|
||||||
return docu.documentElement();
|
return docu.documentElement();
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,13 +82,27 @@ void ElementsTreeView::startDrag(Qt::DropActions supportedActions)
|
|||||||
@param location : location to use for create the content of the QDrag
|
@param location : location to use for create the content of the QDrag
|
||||||
*/
|
*/
|
||||||
void ElementsTreeView::startElementDrag(const ElementsLocation &location)
|
void ElementsTreeView::startElementDrag(const ElementsLocation &location)
|
||||||
|
{
|
||||||
|
execElementDrag(this, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementsTreeView::execElementDrag
|
||||||
|
Build and run the QDrag for @a location, from @a source.
|
||||||
|
Static so that a view which is not an ElementsTreeView -- the flat list
|
||||||
|
of search results -- starts exactly the same drag as the tree.
|
||||||
|
@param source : the widget the drag starts from
|
||||||
|
@param location : location to use for create the content of the QDrag
|
||||||
|
*/
|
||||||
|
void ElementsTreeView::execElementDrag(QWidget *source,
|
||||||
|
const ElementsLocation &location)
|
||||||
{
|
{
|
||||||
if (! location.exist()) return;
|
if (! location.exist()) return;
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
|
||||||
QDrag* drag = new QDrag(this);
|
QDrag* drag = new QDrag(source);
|
||||||
#else
|
#else
|
||||||
QScopedPointer<QDrag> drag(new QDrag(this));
|
QScopedPointer<QDrag> drag(new QDrag(source));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QString location_str = location.toString();
|
QString location_str = location.toString();
|
||||||
@@ -209,7 +223,7 @@ void ElementsTreeView::startElementDrag(const ElementsLocation &location)
|
|||||||
&elmt_creation_state));
|
&elmt_creation_state));
|
||||||
if (elmt_creation_state) { return; }
|
if (elmt_creation_state) { return; }
|
||||||
|
|
||||||
QPixmap elmt_pixmap(QET::Palette::forPalette(temp_elmt->pixmap(), palette()));
|
QPixmap elmt_pixmap(QET::Palette::forPalette(temp_elmt->pixmap(), source->palette()));
|
||||||
QPoint elmt_hotspot(temp_elmt->hotspot());
|
QPoint elmt_hotspot(temp_elmt->hotspot());
|
||||||
|
|
||||||
//Adjust the size of the pixmap if he is too big
|
//Adjust the size of the pixmap if he is too big
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ class ElementsTreeView : public QTreeView
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ElementsTreeView(QWidget *parent = nullptr);
|
ElementsTreeView(QWidget *parent = nullptr);
|
||||||
|
static void execElementDrag(QWidget *source,
|
||||||
|
const ElementsLocation &location);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void startDrag(Qt::DropActions supportedActions) override;
|
void startDrag(Qt::DropActions supportedActions) override;
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ QString FileElementCollectionItem::localName()
|
|||||||
else if (m_path == QETApp::customElementsDirN())
|
else if (m_path == QETApp::customElementsDirN())
|
||||||
setText(QObject::tr("Collection utilisateur"));
|
setText(QObject::tr("Collection utilisateur"));
|
||||||
else if (m_path == macrosPath)
|
else if (m_path == macrosPath)
|
||||||
setText(QObject::tr("Makros"));
|
setText(QObject::tr("Macros"));
|
||||||
else
|
else
|
||||||
setText(QObject::tr("Collection inconnue"));
|
setText(QObject::tr("Collection inconnue"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include "../qetproject.h"
|
#include "../qetproject.h"
|
||||||
#include "xmlelementcollection.h"
|
#include "xmlelementcollection.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief XmlProjectElementCollectionItem::XmlProjectElementCollectionItem
|
@brief XmlProjectElementCollectionItem::XmlProjectElementCollectionItem
|
||||||
Constructor
|
Constructor
|
||||||
@@ -58,10 +60,7 @@ QString XmlProjectElementCollectionItem::localName()
|
|||||||
return text();
|
return text();
|
||||||
|
|
||||||
if (isCollectionRoot()) {
|
if (isCollectionRoot()) {
|
||||||
if (m_project->title().isEmpty())
|
updateProjectName();
|
||||||
setText(QObject::tr("Projet sans titre"));
|
|
||||||
else
|
|
||||||
setText(m_project->title());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ElementsLocation location (embeddedPath(), m_project);
|
ElementsLocation location (embeddedPath(), m_project);
|
||||||
@@ -71,6 +70,25 @@ QString XmlProjectElementCollectionItem::localName()
|
|||||||
return text();
|
return text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief XmlProjectElementCollectionItem::updateProjectName
|
||||||
|
Set the displayed name of the collection root from the project :
|
||||||
|
its title, or its file name when it has no title, like the project panel.
|
||||||
|
Does nothing if this item is not the root of the collection.
|
||||||
|
*/
|
||||||
|
void XmlProjectElementCollectionItem::updateProjectName()
|
||||||
|
{
|
||||||
|
if (!isCollectionRoot() || !m_project)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_project->title().isEmpty())
|
||||||
|
setText(m_project->title());
|
||||||
|
else if (!m_project->filePath().isEmpty())
|
||||||
|
setText(QFileInfo(m_project->filePath()).completeBaseName());
|
||||||
|
else
|
||||||
|
setText(QObject::tr("Projet sans titre"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief XmlProjectElementCollectionItem::name
|
@brief XmlProjectElementCollectionItem::name
|
||||||
@return The collection name of this item
|
@return The collection name of this item
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class XmlProjectElementCollectionItem : public ElementCollectionItem
|
|||||||
bool isCollectionRoot() const override;
|
bool isCollectionRoot() const override;
|
||||||
void addChildAtPath(const QString &collection_name) override;
|
void addChildAtPath(const QString &collection_name) override;
|
||||||
QETProject * project() const;
|
QETProject * project() const;
|
||||||
|
void updateProjectName();
|
||||||
|
|
||||||
void setProject (QETProject *project,
|
void setProject (QETProject *project,
|
||||||
bool set_data = true,
|
bool set_data = true,
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ FreeTerminalEditor::FreeTerminalEditor(QETProject *project, QWidget *parent) :
|
|||||||
connect(m_project, &QObject::destroyed, this, &FreeTerminalEditor::reload);
|
connect(m_project, &QObject::destroyed, this, &FreeTerminalEditor::reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Disabled the move if the table is currently edited (yellow cell)
|
//Disable the move button while cells are edited (yellow), re-evaluate on every change
|
||||||
connect(m_model, &FreeTerminalModel::dataChanged, this, [=] {
|
connect(m_model, &FreeTerminalModel::dataChanged, this, &FreeTerminalEditor::selectionChanged);
|
||||||
this->setDisabledMove();
|
|
||||||
});
|
connect(ui->m_table_view->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||||
|
this, &FreeTerminalEditor::selectionChanged);
|
||||||
|
|
||||||
connect(ui->m_table_view, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index)
|
connect(ui->m_table_view, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index)
|
||||||
{
|
{
|
||||||
@@ -102,7 +103,7 @@ void FreeTerminalEditor::reload()
|
|||||||
QString str(strip->installation() + " " + strip->location() + " " + strip->name());
|
QString str(strip->installation() + " " + strip->location() + " " + strip->name());
|
||||||
ui->m_move_in_cb->addItem(str, strip->uuid());
|
ui->m_move_in_cb->addItem(str, strip->uuid());
|
||||||
}
|
}
|
||||||
setDisabledMove(false);
|
selectionChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,10 +274,19 @@ void FreeTerminalEditor::on_m_move_pb_clicked()
|
|||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FreeTerminalEditor::selectionChanged()
|
||||||
|
{
|
||||||
|
const bool has_selection = !ui->m_table_view->selectionModel()->selectedIndexes().isEmpty();
|
||||||
|
const bool has_pending = !m_model->modifiedModelRealTerminalData().isEmpty();
|
||||||
|
setDisabledMove(!has_selection || has_pending);
|
||||||
|
}
|
||||||
|
|
||||||
void FreeTerminalEditor::setDisabledMove(bool b)
|
void FreeTerminalEditor::setDisabledMove(bool b)
|
||||||
{
|
{
|
||||||
ui->m_move_label->setDisabled(b);
|
ui->m_move_label->setDisabled(b);
|
||||||
ui->m_move_in_cb->setDisabled(b);
|
ui->m_move_in_cb->setDisabled(b);
|
||||||
ui->m_move_pb->setDisabled(b);
|
ui->m_move_pb->setDisabled(b);
|
||||||
|
ui->m_move_pb->setToolTip(b ? tr("Appliquez ou annulez les modifications en cours avant de déplacer")
|
||||||
|
: tr("Déplacer les bornes sélectionnées vers le bornier choisi"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ bool FreeTerminalModel::setData(const QModelIndex &index, const QVariant &value,
|
|||||||
modified_ = true;
|
modified_ = true;
|
||||||
modified_cell = FUNCTION_CELL;
|
modified_cell = FUNCTION_CELL;
|
||||||
}
|
}
|
||||||
else if (column_ == LED_CELL)
|
else if (column_ == LED_CELL && mrtd.led_ != value.toBool())
|
||||||
{
|
{
|
||||||
mrtd.led_ = value.toBool();
|
mrtd.led_ = value.toBool();
|
||||||
modified_ = true;
|
modified_ = true;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
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 ALIGNMENT_H
|
||||||
|
#define ALIGNMENT_H
|
||||||
|
|
||||||
|
#include "textgrid.h"
|
||||||
|
|
||||||
|
#include <QPointF>
|
||||||
|
|
||||||
|
/**
|
||||||
|
The geometry behind the align commands, kept free of any scene so it
|
||||||
|
can be tested on its own.
|
||||||
|
*/
|
||||||
|
namespace Alignment
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
@return the movement that puts p on a grid of x_grid by y_grid,
|
||||||
|
divided by divisor as TextGrid::snap() does, or a null point when
|
||||||
|
p is already on it. Unlike Diagram::snapToGrid(), this never looks
|
||||||
|
at the keyboard: a command run from a shortcut with Ctrl in it must
|
||||||
|
not quietly round to the pixel instead.
|
||||||
|
Less than a millionth of a pixel counts as on the grid: positions
|
||||||
|
that went through arithmetic carry residues of that size, and
|
||||||
|
qFuzzyIsNull() (1e-12) is too strict to absorb them.
|
||||||
|
*/
|
||||||
|
inline QPointF gridOffset(const QPointF &p, int x_grid, int y_grid, qreal divisor = 1)
|
||||||
|
{
|
||||||
|
const QPointF offset = TextGrid::snap(p, x_grid, y_grid, divisor) - p;
|
||||||
|
auto clean = [](qreal v) { return qAbs(v) < 1e-6 ? 0.0 : v; };
|
||||||
|
return QPointF(clean(offset.x()), clean(offset.y()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ALIGNMENT_H
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
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 "renumberelementscommand.h"
|
||||||
|
|
||||||
|
#include "../qetproject.h"
|
||||||
|
#include "../qetgraphicsitem/element.h"
|
||||||
|
|
||||||
|
RenumberElementsCommand::RenumberElementsCommand(
|
||||||
|
QETProject *project,
|
||||||
|
QVector<ElementChange> changes,
|
||||||
|
QHash<QString, NumerotationContext> old_ctx,
|
||||||
|
QHash<QString, NumerotationContext> new_ctx,
|
||||||
|
const QString &text)
|
||||||
|
: QUndoCommand(text)
|
||||||
|
, project_(project)
|
||||||
|
, changes_(std::move(changes))
|
||||||
|
, old_ctx_(std::move(old_ctx))
|
||||||
|
, new_ctx_(std::move(new_ctx))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenumberElementsCommand::undo()
|
||||||
|
{
|
||||||
|
apply(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenumberElementsCommand::redo()
|
||||||
|
{
|
||||||
|
// QUndoStack calls redo() once immediately after push(); keep standard behaviour.
|
||||||
|
if (first_redo_) {
|
||||||
|
first_redo_ = false;
|
||||||
|
}
|
||||||
|
apply(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenumberElementsCommand::apply(bool use_new)
|
||||||
|
{
|
||||||
|
if (!project_) return;
|
||||||
|
|
||||||
|
// Restore project contexts
|
||||||
|
const auto &ctx = use_new ? new_ctx_ : old_ctx_;
|
||||||
|
for (auto it = ctx.constBegin(); it != ctx.constEnd(); ++it) {
|
||||||
|
project_->addElementAutoNum(it.key(), it.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply per-element changes
|
||||||
|
for (const ElementChange &c : changes_) {
|
||||||
|
if (!c.element) continue;
|
||||||
|
|
||||||
|
const bool frozen = use_new ? c.new_frozen : c.old_frozen;
|
||||||
|
const auto &infos = use_new ? c.new_infos : c.old_infos;
|
||||||
|
const auto &seq = use_new ? c.new_seq : c.old_seq;
|
||||||
|
|
||||||
|
// Temporarily unfreeze so that label/infos update correctly.
|
||||||
|
const bool was_frozen = c.element->isFreezeLabel();
|
||||||
|
if (was_frozen) c.element->freezeLabel(false);
|
||||||
|
|
||||||
|
c.element->rSequenceStruct() = seq;
|
||||||
|
c.element->setElementInformations(infos);
|
||||||
|
c.element->freezeLabel(frozen);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
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 RENUMBERELEMENTSCOMMAND_H
|
||||||
|
#define RENUMBERELEMENTSCOMMAND_H
|
||||||
|
|
||||||
|
#include <QUndoCommand>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "../diagramcontext.h"
|
||||||
|
#include "assignvariables.h" // defines autonum::sequentialNumbers
|
||||||
|
|
||||||
|
class Element;
|
||||||
|
class QETProject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Undoable renumbering of element labels and sequence structs.
|
||||||
|
*/
|
||||||
|
class RenumberElementsCommand final : public QUndoCommand
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct ElementChange
|
||||||
|
{
|
||||||
|
Element *element = nullptr;
|
||||||
|
DiagramContext old_infos;
|
||||||
|
DiagramContext new_infos;
|
||||||
|
autonum::sequentialNumbers old_seq;
|
||||||
|
autonum::sequentialNumbers new_seq;
|
||||||
|
bool old_frozen = false;
|
||||||
|
bool new_frozen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
RenumberElementsCommand(
|
||||||
|
QETProject *project,
|
||||||
|
QVector<ElementChange> changes,
|
||||||
|
QHash<QString, NumerotationContext> old_ctx,
|
||||||
|
QHash<QString, NumerotationContext> new_ctx,
|
||||||
|
const QString &text);
|
||||||
|
|
||||||
|
void undo() override;
|
||||||
|
void redo() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void apply(bool use_new);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QETProject *project_ = nullptr;
|
||||||
|
QVector<ElementChange> changes_;
|
||||||
|
QHash<QString, NumerotationContext> old_ctx_;
|
||||||
|
QHash<QString, NumerotationContext> new_ctx_;
|
||||||
|
bool first_redo_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RENUMBERELEMENTSCOMMAND_H
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "formulaautonumberingw.h"
|
#include "formulaautonumberingw.h"
|
||||||
#include "numparteditorw.h"
|
#include "numparteditorw.h"
|
||||||
#include "qdebug.h"
|
#include "qdebug.h"
|
||||||
|
#include "renumberelementsdialog.h"
|
||||||
#include "ui_autonumberingmanagementw.h"
|
#include "ui_autonumberingmanagementw.h"
|
||||||
#include "ui_formulaautonumberingw.h"
|
#include "ui_formulaautonumberingw.h"
|
||||||
|
|
||||||
@@ -48,6 +49,8 @@ AutoNumberingManagementW::AutoNumberingManagementW(QETProject *project,
|
|||||||
ui->m_selected_folios_le->setDisabled(true);
|
ui->m_selected_folios_le->setDisabled(true);
|
||||||
ui->m_selected_folios_le->setReadOnly(true);
|
ui->m_selected_folios_le->setReadOnly(true);
|
||||||
ui->m_apply_project_rb->setChecked(true);
|
ui->m_apply_project_rb->setChecked(true);
|
||||||
|
// Enabled only when project is in "Under Development" status and not read-only.
|
||||||
|
ui->m_renumber_elements_pb->setEnabled(ui->m_status_cb->currentIndex() == 0 && project_ && !project_->isReadOnly());
|
||||||
setProjectContext();
|
setProjectContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +90,7 @@ void AutoNumberingManagementW::on_m_status_cb_currentIndexChanged(int index)
|
|||||||
ui->m_both_conductor_rb->setChecked(true);
|
ui->m_both_conductor_rb->setChecked(true);
|
||||||
ui->m_both_element_rb->setChecked(true);
|
ui->m_both_element_rb->setChecked(true);
|
||||||
ui->m_both_folio_rb->setChecked(true);
|
ui->m_both_folio_rb->setChecked(true);
|
||||||
|
ui->m_renumber_elements_pb->setEnabled(true);
|
||||||
}
|
}
|
||||||
//Installing
|
//Installing
|
||||||
else if (index == 1) {
|
else if (index == 1) {
|
||||||
@@ -96,15 +100,31 @@ void AutoNumberingManagementW::on_m_status_cb_currentIndexChanged(int index)
|
|||||||
ui->m_new_conductor_rb->setChecked(true);
|
ui->m_new_conductor_rb->setChecked(true);
|
||||||
ui->m_new_element_rb->setChecked(true);
|
ui->m_new_element_rb->setChecked(true);
|
||||||
ui->m_new_folio_rb->setChecked(true);
|
ui->m_new_folio_rb->setChecked(true);
|
||||||
|
ui->m_renumber_elements_pb->setEnabled(true);
|
||||||
}
|
}
|
||||||
//Built
|
//Built
|
||||||
else if (index == 2) {
|
else if (index == 2) {
|
||||||
ui->m_disable_conductor_rb->setChecked(true);
|
ui->m_disable_conductor_rb->setChecked(true);
|
||||||
ui->m_disable_element_rb->setChecked(true);
|
ui->m_disable_element_rb->setChecked(true);
|
||||||
ui->m_disable_folio_rb->setChecked(true);
|
ui->m_disable_folio_rb->setChecked(true);
|
||||||
|
ui->m_renumber_elements_pb->setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AutoNumberingManagementW::on_m_renumber_elements_pb_clicked()
|
||||||
|
{
|
||||||
|
if (!project_ || project_->isReadOnly()) return;
|
||||||
|
// Only allowed during "Under Development"
|
||||||
|
// if (ui->m_status_cb->currentIndex() != 1) return;
|
||||||
|
|
||||||
|
QStringList titles = project_->elementAutoNum().keys();
|
||||||
|
titles.sort(Qt::CaseInsensitive);
|
||||||
|
RenumberElementsDialog dlg(titles, this);
|
||||||
|
if (dlg.exec() != QDialog::Accepted) return;
|
||||||
|
|
||||||
|
project_->renumberElementsBySchemeTitle(dlg.selectedSchemeTitle());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief AutoNumberingManagementW::on_m_apply_folios_rb_clicked
|
@brief AutoNumberingManagementW::on_m_apply_folios_rb_clicked
|
||||||
Set From Folios Combobox
|
Set From Folios Combobox
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user