mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-25 19:34:13 +02:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d43c535cc9 | |||
| 59dc59cf2f | |||
| 469c8cbc70 | |||
| e7f19de2da | |||
| 4c9a307cb9 | |||
| 4381c6caa3 | |||
| 149ae961cc | |||
| ac40d110fb | |||
| 4f309770f0 | |||
| 72cf86ece0 | |||
| 8aefdcc481 | |||
| 3a45625d69 | |||
| 988357057f | |||
| e9676ef87e | |||
| ca507dc0f3 | |||
| 1932150ea6 | |||
| 23241fba69 | |||
| f2f1051914 | |||
| 313aafc95b | |||
| aaeaff55cc | |||
| 17ebbffbca | |||
| 04e2279a7f | |||
| 9987f05835 | |||
| a94f30b256 | |||
| 73075a5154 | |||
| 9875de85d7 | |||
| 48883b320e | |||
| b93d8d8baa | |||
| ae41dd7686 | |||
| 7fbbb71aac | |||
| 232724bf82 | |||
| 12f826e969 | |||
| 245aafb0d1 | |||
| 0d231ef548 | |||
| 3513f692fd | |||
| 5a27f3cf80 | |||
| d3a9adbe44 | |||
| 8ab4e24b61 | |||
| 2d2bf5ca2b | |||
| 2f6ab8f63b | |||
| 2266497b0b | |||
| dcb79b6553 | |||
| 745b4a936c | |||
| 7ac3719c37 | |||
| ddfa234943 | |||
| 9af562fcbd | |||
| c85276f61b | |||
| 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 |
@@ -28,7 +28,7 @@ jobs:
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates build-essential cmake ninja-build git pkg-config \
|
||||
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 \
|
||||
xvfb openbox xdotool x11-utils
|
||||
# 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-translations
|
||||
mingw-w64-ucrt-x86_64-qt6-pdf
|
||||
mingw-w64-ucrt-x86_64-sqlite3
|
||||
mingw-w64-ucrt-x86_64-pkg-config
|
||||
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
||||
mingw-w64-ucrt-x86_64-kcoreaddons
|
||||
@@ -56,6 +57,7 @@ jobs:
|
||||
mingw-w64-ucrt-x86_64-nsis
|
||||
mingw-w64-ucrt-x86_64-angleproject
|
||||
mingw-w64-ucrt-x86_64-qt6-declarative
|
||||
mingw-w64-ucrt-x86_64-hidapi
|
||||
|
||||
- name: Cache ccache
|
||||
uses: actions/cache@v5
|
||||
@@ -119,9 +121,12 @@ jobs:
|
||||
-DCMAKE_POLICY_DEFAULT_CMP0077=NEW \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DQET_EXPORT_PROJECT_DB=ON \
|
||||
-DQET_ENABLE_SPACEMOUSE=ON \
|
||||
-DQET_SPACEMOUSE_BACKEND=hid \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||
|
||||
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
|
||||
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
|
||||
..
|
||||
ninja -j"$NPROC"
|
||||
|
||||
@@ -144,6 +149,11 @@ jobs:
|
||||
SIZE=$(stat -c%s "$EXE")
|
||||
echo "Exe found: $EXE ($SIZE bytes)"
|
||||
[ "$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
|
||||
shell: msys2 {0}
|
||||
@@ -189,11 +199,20 @@ jobs:
|
||||
echo "=== $DLL_COUNT DLLs present after scan ==="
|
||||
ls -lh "$BIN/QElectroTech.exe" || { echo "ERROR: exe missing from bin/"; exit 1; }
|
||||
[ "$DLL_COUNT" -gt 5 ] || { echo "ERROR: too few DLLs"; exit 1; }
|
||||
# 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"
|
||||
|
||||
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
|
||||
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
|
||||
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
|
||||
SQLITE=$(find /ucrt64/bin -name "libsqlite3*.dll" | head -1)
|
||||
if [ -n "$SQLITE" ]; then
|
||||
cp "$SQLITE" "$BIN/"
|
||||
echo "SQLite3 copied: $(basename $SQLITE)"
|
||||
else
|
||||
echo "WARNING: libsqlite3 not found in /ucrt64/bin/"
|
||||
fi
|
||||
|
||||
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
|
||||
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
|
||||
|
||||
+1
-9
@@ -9,12 +9,4 @@ doc/*
|
||||
!doc/QElectroTech.qch
|
||||
QElectroTech.tag
|
||||
!doc/doc-utils
|
||||
lang/*.qm
|
||||
|
||||
# -- IDE settings/intermediate files --
|
||||
# zed
|
||||
.zed
|
||||
.cache
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
ui_*.h
|
||||
|
||||
@@ -36,6 +36,7 @@ include(cmake/git_update_submodules.cmake)
|
||||
include(cmake/git_last_commit_sha.cmake)
|
||||
include(cmake/fetch_singleapplication.cmake)
|
||||
include(cmake/fetch_pugixml.cmake)
|
||||
include(cmake/find_spacemouse.cmake)
|
||||
include(cmake/qet_compilation_vars.cmake)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
@@ -169,6 +170,13 @@ else()
|
||||
)
|
||||
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)
|
||||
# CFBundleIdentifier must not be empty. CMake's default Info.plist
|
||||
# template fills it from MACOSX_BUNDLE_GUI_IDENTIFIER; with that unset
|
||||
@@ -287,6 +295,7 @@ target_link_libraries(
|
||||
SQLite3::SQLite3
|
||||
${KF_PRIVATE_LIBRARIES}
|
||||
${QET_PRIVATE_LIBRARIES}
|
||||
${QET_SPACEMOUSE_LIBRARIES}
|
||||
)
|
||||
|
||||
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) |
|
||||
| Qt6 base + widgets | required | |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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) |
|
||||
|
||||
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)
|
||||
|
||||
@@ -100,7 +106,7 @@ the closest match.
|
||||
sudo apt install \
|
||||
build-essential cmake ninja-build git \
|
||||
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||
|
||||
libsqlite3-dev \
|
||||
libkf6coreaddons-dev libkf6widgetsaddons-dev
|
||||
```
|
||||
|
||||
@@ -132,6 +138,7 @@ sudo apt install libpugixml-dev
|
||||
sudo dnf install \
|
||||
cmake gcc-c++ git \
|
||||
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
|
||||
sqlite-devel \
|
||||
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
|
||||
```
|
||||
|
||||
@@ -148,10 +155,11 @@ Optional, for a system pugixml: `sudo dnf install pugixml-devel`.
|
||||
pkg install \
|
||||
cmake git \
|
||||
qt6-base qt6-tools \
|
||||
sqlite3 \
|
||||
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
|
||||
`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
|
||||
@@ -167,7 +175,7 @@ Optional, for a system pugixml: `pkg install pugixml` (`devel/pugixml`).
|
||||
Using [Homebrew](https://brew.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
|
||||
@@ -185,7 +193,7 @@ Optional, for a system pugixml: `brew install pugixml`.
|
||||
|
||||
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 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.
|
||||
|
||||
## 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:
|
||||
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
|
||||
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.
|
||||
|
||||
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,
|
||||
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).
|
||||
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
|
||||
mkdir build && cd build
|
||||
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
|
||||
its bundled `g++.exe` comes first on `PATH`, or pass
|
||||
`-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)
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ parts:
|
||||
- python3-lxml
|
||||
- python3-tk
|
||||
- libtk8.6
|
||||
- libspnav0
|
||||
|
||||
kde-sdk-setup:
|
||||
plugin: nil
|
||||
@@ -71,6 +72,7 @@ parts:
|
||||
source: .
|
||||
stage-packages:
|
||||
- git
|
||||
- sqlite3
|
||||
- xdg-user-dirs
|
||||
- libqt6qml6
|
||||
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
||||
@@ -81,7 +83,7 @@ parts:
|
||||
- git
|
||||
- cmake
|
||||
- ninja-build
|
||||
|
||||
- libsqlite3-dev
|
||||
- qt6-tools-dev
|
||||
- qt6-base-private-dev
|
||||
- qt6-declarative-dev
|
||||
@@ -91,6 +93,7 @@ parts:
|
||||
# runs FindCups at configure time and fails without the CUPS headers.
|
||||
# Build-time only: nothing from it is staged into the snap.
|
||||
- libcups2-dev
|
||||
- libspnav-dev
|
||||
override-build: |
|
||||
displayed_version=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
||||
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
|
||||
@@ -105,7 +108,8 @@ parts:
|
||||
-DBUILD_KF=OFF \
|
||||
-DPACKAGE_TESTS=OFF \
|
||||
-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}
|
||||
DESTDIR="$CRAFT_PART_INSTALL" ninja -C build install
|
||||
override-stage: |
|
||||
|
||||
@@ -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
|
||||
# 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)
|
||||
|
||||
# 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()
|
||||
@@ -297,6 +297,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/autoNum/numerotationcontextcommands.h
|
||||
${QET_DIR}/sources/autoNum/numerotationcontext.cpp
|
||||
${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.h
|
||||
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp
|
||||
@@ -307,6 +309,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/autoNum/ui/formulaautonumberingw.h
|
||||
${QET_DIR}/sources/autoNum/ui/numparteditorw.cpp
|
||||
${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.h
|
||||
|
||||
@@ -871,6 +875,43 @@ list(APPEND QET_SRC_FILES
|
||||
${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
|
||||
${QET_DIR}/lang/qet_ar.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>
|
||||
<target>Übersetzung</target>
|
||||
</phrase>
|
||||
<phrase>
|
||||
<source>Modifier le nom de la borne</source>
|
||||
<target>Klemmenname ändern</target>
|
||||
</phrase>
|
||||
</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>
|
||||
@@ -103,7 +103,9 @@ cmake -S . -B "$BUILD_DIR" -G Ninja \
|
||||
-DBUILD_WITH_KF=$BUILD_WITH_KF \
|
||||
-DBUILD_KF=OFF \
|
||||
-DQET_EXPORT_PROJECT_DB=ON \
|
||||
-DPACKAGE_TESTS=OFF
|
||||
-DPACKAGE_TESTS=OFF \
|
||||
-DQET_ENABLE_SPACEMOUSE=ON \
|
||||
-DQET_SPACEMOUSE_BACKEND=hid
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: cmake configure failed."
|
||||
@@ -396,10 +398,12 @@ done
|
||||
|
||||
echo "-- Signing main executable..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||
"$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
|
||||
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 "Verifying bundle signature..."
|
||||
@@ -500,8 +504,10 @@ find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||
"$MOUNT_POINT/$BUNDLE"
|
||||
|
||||
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>
|
||||
Executable
+195
@@ -0,0 +1,195 @@
|
||||
#!/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/>.
|
||||
"""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 the discussion.
|
||||
Nothing is sent anywhere.
|
||||
|
||||
sudo python3 spacemouse-capture.py # finds the device itself
|
||||
sudo python3 spacemouse-capture.py --list # just show what it finds
|
||||
|
||||
sudo is needed because /dev/hidraw* is usually readable by root only.
|
||||
spacenavd can keep running. If the recording comes out empty, stop it
|
||||
(`sudo systemctl stop spacenavd`) and try again.
|
||||
|
||||
Only the standard library is used, so it runs on any Linux with Python 3.
|
||||
"""
|
||||
import argparse
|
||||
import datetime
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import select
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
def find_devices():
|
||||
"""Return [{hidraw, name, vendor, product, sysfs}] for 3Dconnexion devices."""
|
||||
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({
|
||||
'hidraw': '/dev/' + os.path.basename(sysdir),
|
||||
'name': uevent.get('HID_NAME', '?'),
|
||||
'vendor': '%04x' % vendor,
|
||||
'product': '%04x' % product,
|
||||
'sysfs': sysdir,
|
||||
})
|
||||
return found
|
||||
|
||||
|
||||
def read_descriptor(sysdir):
|
||||
try:
|
||||
with open(os.path.join(sysdir, 'device', 'report_descriptor'), 'rb') as f:
|
||||
return f.read().hex()
|
||||
except OSError as e:
|
||||
return 'unreadable: %s' % e
|
||||
|
||||
|
||||
def record(fd, 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([fd], [], [], left)
|
||||
if not ready:
|
||||
continue
|
||||
try:
|
||||
data = os.read(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 main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument('--list', action='store_true', help='only list matching devices')
|
||||
ap.add_argument('--device', help='hidraw path, if more than one device 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)')
|
||||
args = ap.parse_args()
|
||||
|
||||
devices = find_devices()
|
||||
if args.list:
|
||||
for d in devices:
|
||||
print('%(hidraw)s %(vendor)s:%(product)s %(name)s' % d)
|
||||
if not devices:
|
||||
print('No 3Dconnexion device found under /sys/class/hidraw.')
|
||||
return 0 if devices else 1
|
||||
|
||||
if args.device:
|
||||
dev = next((d for d in devices if d['hidraw'] == args.device),
|
||||
{'hidraw': args.device, 'name': '?', 'vendor': '?', 'product': '?', 'sysfs': None})
|
||||
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(' %(hidraw)s %(name)s' % d for d in devices))
|
||||
|
||||
if args.descriptor:
|
||||
with open(args.descriptor, 'rb') as f:
|
||||
descriptor = f.read().hex()
|
||||
elif dev['sysfs']:
|
||||
descriptor = read_descriptor(dev['sysfs'])
|
||||
else:
|
||||
descriptor = 'unknown'
|
||||
|
||||
try:
|
||||
fd = os.open(dev['hidraw'], os.O_RDONLY | os.O_NONBLOCK)
|
||||
except PermissionError:
|
||||
sys.exit('Permission denied on %s -- run with sudo.' % dev['hidraw'])
|
||||
|
||||
print('Recording from %s (%s, %s:%s).' % (dev['hidraw'], dev['name'], dev['vendor'], dev['product']))
|
||||
print('For each step, press Enter, do the movement, and wait for the next prompt.\n')
|
||||
|
||||
result = {
|
||||
'tool': 'spacemouse-capture.py 1',
|
||||
'date': datetime.datetime.now(datetime.timezone.utc).isoformat(timespec='seconds'),
|
||||
'system': platform.platform(),
|
||||
'device': {k: dev[k] for k in ('name', 'vendor', 'product')},
|
||||
'report_descriptor': descriptor,
|
||||
'steps': [],
|
||||
}
|
||||
try:
|
||||
for i, (key, text, seconds) in enumerate(STEPS, 1):
|
||||
print('[%d/%d] %s' % (i, len(STEPS), text))
|
||||
if not args.yes:
|
||||
input(' Press Enter to start (%d s)... ' % seconds)
|
||||
reports = record(fd, 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:
|
||||
os.close(fd)
|
||||
|
||||
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. Try stopping spacenavd first: sudo systemctl stop spacenavd')
|
||||
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
|
||||
|
||||
#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
|
||||
# Get GIT sources
|
||||
#git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
|
||||
|
||||
@@ -49,6 +49,14 @@ ElementsCollectionModel::ElementsCollectionModel(QObject *parent) :
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -431,7 +439,7 @@ void ElementsCollectionModel::addLocation(const ElementsLocation& location)
|
||||
collection_name);
|
||||
}
|
||||
}
|
||||
// ANPASSUNG: Makros und Custom Collection werden hier behandelt!
|
||||
// Macros and Custom Collection are handled here
|
||||
else if (location.isCustomCollection() || location.isMacrosCollection()) {
|
||||
QList <ElementCollectionItem *> child_list;
|
||||
|
||||
@@ -495,6 +503,10 @@ void ElementsCollectionModel::addProject(QETProject *project, bool set_data)
|
||||
connect(project->embeddedElementCollection(),
|
||||
&XmlElementCollection::directoryRemoved,
|
||||
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,
|
||||
this,
|
||||
&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 (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()) ||
|
||||
(location.isCompanyCollection() && feci->isCompanyCollection()) ||
|
||||
(location.isMacrosCollection() && feci->isMacrosCollection()) ||
|
||||
@@ -766,3 +782,15 @@ void ElementsCollectionModel::updateItem(const QString& path)
|
||||
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 itemRemovedFromCollection (const QString& path);
|
||||
void updateItem (const QString& path);
|
||||
void projectNameChanged (QETProject *project);
|
||||
|
||||
private:
|
||||
QList <QETProject *> m_project_list;
|
||||
|
||||
@@ -653,10 +653,17 @@ QDomElement ElementsLocation::xml() const
|
||||
if (!m_project)
|
||||
{
|
||||
QFile file (m_file_system_path);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return QDomElement();
|
||||
|
||||
QDomDocument docu;
|
||||
if (docu.setContent(&file))
|
||||
{
|
||||
file.close();
|
||||
return docu.documentElement();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString str = m_collection_path;
|
||||
|
||||
@@ -162,7 +162,7 @@ QString FileElementCollectionItem::localName()
|
||||
else if (m_path == QETApp::customElementsDirN())
|
||||
setText(QObject::tr("Collection utilisateur"));
|
||||
else if (m_path == macrosPath)
|
||||
setText(QObject::tr("Makros"));
|
||||
setText(QObject::tr("Macros"));
|
||||
else
|
||||
setText(QObject::tr("Collection inconnue"));
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "../qetproject.h"
|
||||
#include "xmlelementcollection.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
/**
|
||||
@brief XmlProjectElementCollectionItem::XmlProjectElementCollectionItem
|
||||
Constructor
|
||||
@@ -58,10 +60,7 @@ QString XmlProjectElementCollectionItem::localName()
|
||||
return text();
|
||||
|
||||
if (isCollectionRoot()) {
|
||||
if (m_project->title().isEmpty())
|
||||
setText(QObject::tr("Projet sans titre"));
|
||||
else
|
||||
setText(m_project->title());
|
||||
updateProjectName();
|
||||
}
|
||||
else {
|
||||
ElementsLocation location (embeddedPath(), m_project);
|
||||
@@ -71,6 +70,25 @@ QString XmlProjectElementCollectionItem::localName()
|
||||
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
|
||||
@return The collection name of this item
|
||||
|
||||
@@ -45,6 +45,7 @@ class XmlProjectElementCollectionItem : public ElementCollectionItem
|
||||
bool isCollectionRoot() const override;
|
||||
void addChildAtPath(const QString &collection_name) override;
|
||||
QETProject * project() const;
|
||||
void updateProjectName();
|
||||
|
||||
void setProject (QETProject *project,
|
||||
bool set_data = true,
|
||||
|
||||
@@ -46,10 +46,11 @@ FreeTerminalEditor::FreeTerminalEditor(QETProject *project, QWidget *parent) :
|
||||
connect(m_project, &QObject::destroyed, this, &FreeTerminalEditor::reload);
|
||||
}
|
||||
|
||||
//Disabled the move if the table is currently edited (yellow cell)
|
||||
connect(m_model, &FreeTerminalModel::dataChanged, this, [=] {
|
||||
this->setDisabledMove();
|
||||
});
|
||||
//Disable the move button while cells are edited (yellow), re-evaluate on every change
|
||||
connect(m_model, &FreeTerminalModel::dataChanged, this, &FreeTerminalEditor::selectionChanged);
|
||||
|
||||
connect(ui->m_table_view->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||
this, &FreeTerminalEditor::selectionChanged);
|
||||
|
||||
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());
|
||||
ui->m_move_in_cb->addItem(str, strip->uuid());
|
||||
}
|
||||
setDisabledMove(false);
|
||||
selectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +274,19 @@ void FreeTerminalEditor::on_m_move_pb_clicked()
|
||||
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)
|
||||
{
|
||||
ui->m_move_label->setDisabled(b);
|
||||
ui->m_move_in_cb->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_cell = FUNCTION_CELL;
|
||||
}
|
||||
else if (column_ == LED_CELL)
|
||||
else if (column_ == LED_CELL && mrtd.led_ != value.toBool())
|
||||
{
|
||||
mrtd.led_ = value.toBool();
|
||||
modified_ = true;
|
||||
|
||||
@@ -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 "numparteditorw.h"
|
||||
#include "qdebug.h"
|
||||
#include "renumberelementsdialog.h"
|
||||
#include "ui_autonumberingmanagementw.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->setReadOnly(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();
|
||||
}
|
||||
|
||||
@@ -87,6 +90,7 @@ void AutoNumberingManagementW::on_m_status_cb_currentIndexChanged(int index)
|
||||
ui->m_both_conductor_rb->setChecked(true);
|
||||
ui->m_both_element_rb->setChecked(true);
|
||||
ui->m_both_folio_rb->setChecked(true);
|
||||
ui->m_renumber_elements_pb->setEnabled(true);
|
||||
}
|
||||
//Installing
|
||||
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_element_rb->setChecked(true);
|
||||
ui->m_new_folio_rb->setChecked(true);
|
||||
ui->m_renumber_elements_pb->setEnabled(true);
|
||||
}
|
||||
//Built
|
||||
else if (index == 2) {
|
||||
ui->m_disable_conductor_rb->setChecked(true);
|
||||
ui->m_disable_element_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
|
||||
Set From Folios Combobox
|
||||
|
||||
@@ -50,6 +50,7 @@ class AutoNumberingManagementW : public QWidget
|
||||
void on_m_from_folios_cb_currentIndexChanged(int);
|
||||
void on_m_to_folios_cb_currentIndexChanged(int);
|
||||
void on_m_status_cb_currentIndexChanged(int);
|
||||
void on_m_renumber_elements_pb_clicked();
|
||||
void on_m_apply_folios_rb_clicked();
|
||||
void on_m_apply_project_rb_clicked();
|
||||
void on_buttonBox_clicked(QAbstractButton *);
|
||||
|
||||
@@ -305,6 +305,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_renumber_elements_pb">
|
||||
<property name="text">
|
||||
<string>Renumber element(s)…</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 "renumberelementsdialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QRadioButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
RenumberElementsDialog::RenumberElementsDialog(const QStringList &scheme_titles, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_scheme_titles(scheme_titles)
|
||||
{
|
||||
setWindowTitle(tr("Renumber element(s)"));
|
||||
setModal(true);
|
||||
|
||||
auto *vl = new QVBoxLayout(this);
|
||||
|
||||
auto *scope = new QGroupBox(tr("Scope"), this);
|
||||
auto *scope_l = new QVBoxLayout(scope);
|
||||
m_all_rb = new QRadioButton(tr("All schemes"), scope);
|
||||
m_one_rb = new QRadioButton(tr("One scheme"), scope);
|
||||
scope_l->addWidget(m_all_rb);
|
||||
scope_l->addWidget(m_one_rb);
|
||||
vl->addWidget(scope);
|
||||
|
||||
auto *form = new QFormLayout();
|
||||
m_combo = new QComboBox(this);
|
||||
m_combo->addItems(m_scheme_titles);
|
||||
form->addRow(new QLabel(tr("Scheme:"), this), m_combo);
|
||||
vl->addLayout(form);
|
||||
|
||||
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
vl->addWidget(m_buttons);
|
||||
|
||||
m_all_rb->setChecked(true);
|
||||
updateUi();
|
||||
|
||||
connect(m_all_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
|
||||
connect(m_one_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
|
||||
}
|
||||
|
||||
QString RenumberElementsDialog::selectedSchemeTitle() const
|
||||
{
|
||||
if (m_all_rb && m_all_rb->isChecked()) return QString();
|
||||
return m_combo ? m_combo->currentText() : QString();
|
||||
}
|
||||
|
||||
void RenumberElementsDialog::updateUi()
|
||||
{
|
||||
const bool one = m_one_rb && m_one_rb->isChecked();
|
||||
if (m_combo) m_combo->setEnabled(one);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 RENUMBERELEMENTSDIALOG_H
|
||||
#define RENUMBERELEMENTSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QStringList>
|
||||
|
||||
class QComboBox;
|
||||
class QRadioButton;
|
||||
class QDialogButtonBox;
|
||||
|
||||
/**
|
||||
* @brief Simple dialog to pick renumbering scope for elements.
|
||||
*
|
||||
* The user can choose between:
|
||||
* - all element autonumbering schemes
|
||||
* - one selected scheme title
|
||||
*/
|
||||
class RenumberElementsDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RenumberElementsDialog(const QStringList &scheme_titles, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @return Empty string if "all" is selected, otherwise the chosen scheme title.
|
||||
*/
|
||||
QString selectedSchemeTitle() const;
|
||||
|
||||
private slots:
|
||||
void updateUi();
|
||||
|
||||
private:
|
||||
QStringList m_scheme_titles;
|
||||
QRadioButton *m_all_rb = nullptr;
|
||||
QRadioButton *m_one_rb = nullptr;
|
||||
QComboBox *m_combo = nullptr;
|
||||
QDialogButtonBox *m_buttons = nullptr;
|
||||
};
|
||||
|
||||
#endif // RENUMBERELEMENTSDIALOG_H
|
||||
@@ -227,6 +227,7 @@ int exportPdf(QETProject &project, const QString &output,
|
||||
// Rewrite the URI link annotations into native internal GoTo actions, so
|
||||
// the cross-references jump inside the document in any PDF viewer.
|
||||
PdfLinks::convertUriToGoTo(output);
|
||||
PdfLinks::removeUnusedPdfxNamespace(output);
|
||||
|
||||
out << "Exported " << diagrams.size() << " page(s) -> " << output << "\n";
|
||||
return 0;
|
||||
|
||||
@@ -319,10 +319,23 @@ void projectDataBase::addElement(Element *element)
|
||||
void projectDataBase::removeElement(Element *element)
|
||||
{
|
||||
m_content_changed = true;
|
||||
bool changed = false;
|
||||
|
||||
m_remove_element_query.bindValue(":uuid", element->uuid().toString());
|
||||
if(!m_remove_element_query.exec()) {
|
||||
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
|
||||
if (m_remove_element_query.exec()) {
|
||||
changed = true;
|
||||
} else {
|
||||
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
|
||||
}
|
||||
|
||||
m_remove_element_info_query.bindValue(":uuid", element->uuid().toString());
|
||||
if (m_remove_element_info_query.exec()) {
|
||||
changed = true;
|
||||
} else {
|
||||
qDebug() << "projectDataBase::removeElement remove element_info error : " << m_remove_element_info_query.lastError();
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
emit dataBaseUpdated();
|
||||
}
|
||||
}
|
||||
@@ -1159,6 +1172,21 @@ void projectDataBase::prepareQuery()
|
||||
m_remove_element_query = QSqlQuery(m_data_base);
|
||||
m_remove_element_query.prepare(remove_element);
|
||||
|
||||
//REMOVE ELEMENT INFO
|
||||
//element_info has no ON DELETE CASCADE (foreign keys aren't
|
||||
//enforced by this connection), so removeElement() must clear it
|
||||
//explicitly. Without this, the row is orphaned under the removed
|
||||
//element's uuid, and re-adding an element with that same uuid
|
||||
//later -- undo of this same removal, or a redo replaying it --
|
||||
//hits element_info's PRIMARY KEY constraint on element_uuid: the
|
||||
//element re-add succeeds, but its element_info insert silently
|
||||
//fails and is lost. removeDiagram()'s cascade already clears this
|
||||
//table when a whole folio goes, but that does not run for a
|
||||
//single element removed on its own.
|
||||
QString remove_element_info("DELETE FROM element_info WHERE element_uuid=:uuid");
|
||||
m_remove_element_info_query = QSqlQuery(m_data_base);
|
||||
m_remove_element_info_query.prepare(remove_element_info);
|
||||
|
||||
//UPDATE ELEMENT INFO
|
||||
QString update_str("UPDATE element_info SET ");
|
||||
for (auto string : QETInformation::elementInfoKeys()) {
|
||||
|
||||
@@ -29,7 +29,7 @@ class QETProject;
|
||||
class Diagram;
|
||||
class Conductor;
|
||||
class Terminal;
|
||||
class sqlite3;
|
||||
struct sqlite3;
|
||||
|
||||
/**
|
||||
@brief The projectDataBase class
|
||||
@@ -139,6 +139,7 @@ class projectDataBase : public QObject
|
||||
QSqlQuery m_insert_elements_query,
|
||||
m_insert_element_info_query,
|
||||
m_remove_element_query,
|
||||
m_remove_element_info_query,
|
||||
m_update_element_query,
|
||||
m_insert_diagram_query,
|
||||
m_remove_diagram_query,
|
||||
|
||||
+52
-30
@@ -42,7 +42,9 @@
|
||||
#include "qetinformation.h"
|
||||
#include "qetproject.h"
|
||||
#include "diagramsortkeys.h"
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cassert>
|
||||
#include <math.h>
|
||||
|
||||
@@ -99,6 +101,43 @@ namespace {
|
||||
QString b = terminalSortKey(cond->terminal2);
|
||||
return (a <= b) ? (a + QLatin1Char('>') + b) : (b + QLatin1Char('>') + a);
|
||||
}
|
||||
|
||||
/// Serialize @p items and append them to a new @p tag block of @p root,
|
||||
/// in stacking order (@p stack_rank). items() cannot be trusted for
|
||||
/// this: with NoIndex, the first removeItem() on the scene sorts Qt's
|
||||
/// item list by pointer address, so from then on items() hands them over
|
||||
/// in a per-run order (bugtracker #343). Stacking order is what reloading
|
||||
/// the file rebuilds, so the drawing is unchanged and a resave is stable.
|
||||
template <typename T>
|
||||
void appendInStackingOrder(QDomDocument &document, QDomElement &root,
|
||||
const QString &tag, const QVector<T *> &items,
|
||||
const QHash<const QGraphicsItem *, int> &stack_rank)
|
||||
{
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
struct Entry { int rank; QString xml_text; QDomElement xml; };
|
||||
QVector<Entry> sorted;
|
||||
for (T *item : items) {
|
||||
Entry entry{stack_rank.value(item, INT_MAX), QString(),
|
||||
item->toXml(document)};
|
||||
// Only an item the stacking query missed needs a tiebreak.
|
||||
if (entry.rank == INT_MAX) {
|
||||
QTextStream stream(&entry.xml_text);
|
||||
entry.xml.save(stream, 0);
|
||||
}
|
||||
sorted.append(entry);
|
||||
}
|
||||
std::stable_sort(sorted.begin(), sorted.end(),
|
||||
[](const Entry &a, const Entry &b) {
|
||||
return a.rank != b.rank ? a.rank < b.rank
|
||||
: a.xml_text < b.xml_text;
|
||||
});
|
||||
|
||||
auto block = document.createElement(tag);
|
||||
for (const auto &entry : sorted)
|
||||
block.appendChild(entry.xml);
|
||||
root.appendChild(block);
|
||||
}
|
||||
}
|
||||
|
||||
int Diagram::xGrid = 10;
|
||||
@@ -1223,37 +1262,20 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
|
||||
dom_root.appendChild(dom_conductors);
|
||||
}
|
||||
|
||||
if (!list_texts.isEmpty()) {
|
||||
auto dom_texts = document.createElement(QStringLiteral("inputs"));
|
||||
for (auto dti : list_texts) {
|
||||
dom_texts.appendChild(dti->toXml(document));
|
||||
}
|
||||
dom_root.appendChild(dom_texts);
|
||||
}
|
||||
|
||||
if (!list_images.isEmpty()) {
|
||||
auto dom_images = document.createElement(QStringLiteral("images"));
|
||||
for (auto dii : list_images) {
|
||||
dom_images.appendChild(dii->toXml(document));
|
||||
}
|
||||
dom_root.appendChild(dom_images);
|
||||
}
|
||||
|
||||
if (!list_shapes.isEmpty()) {
|
||||
auto dom_shapes = document.createElement(QStringLiteral("shapes"));
|
||||
for (auto dii : list_shapes) {
|
||||
dom_shapes.appendChild(dii -> toXml(document));
|
||||
}
|
||||
dom_root.appendChild(dom_shapes);
|
||||
}
|
||||
|
||||
if (table_vector.size()) {
|
||||
auto tables = document.createElement(QStringLiteral("tables"));
|
||||
for (auto table : table_vector) {
|
||||
tables.appendChild(table->toXml(document));
|
||||
}
|
||||
dom_root.appendChild(tables);
|
||||
// A rect query, unlike items(), returns true stacking order (z, then
|
||||
// insertion order) even with NoIndex.
|
||||
QHash<const QGraphicsItem *, int> stack_rank;
|
||||
{
|
||||
const QList<QGraphicsItem *> stacked = items(
|
||||
QRectF(-1e9, -1e9, 2e9, 2e9), Qt::IntersectsItemBoundingRect,
|
||||
Qt::AscendingOrder);
|
||||
for (int i = 0 ; i < stacked.size() ; ++i)
|
||||
stack_rank.insert(stacked.at(i), i);
|
||||
}
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("inputs"), list_texts, stack_rank);
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("images"), list_images, stack_rank);
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("shapes"), list_shapes, stack_rank);
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("tables"), table_vector, stack_rank);
|
||||
|
||||
if (!strip_vector.isEmpty()) {
|
||||
dom_root.appendChild(TerminalStripItemXml::toXml(strip_vector, document));
|
||||
|
||||
@@ -77,6 +77,23 @@ void PasteDiagramCommand::redo()
|
||||
{
|
||||
first_redo = false;
|
||||
|
||||
//Resolve a linked master/slave pair pasted together (bugtracker
|
||||
//#607) before anything below renews their uuids: at this exact
|
||||
//moment a pasted element's tmp_uuids_link still holds its
|
||||
//source's original partner uuid, which still equals the
|
||||
//not-yet-renewed uuid of that partner's own pasted copy if it
|
||||
//was carried along in the same batch. Scoped to this batch only
|
||||
//(not a project-wide search), so a pair pasted together links to
|
||||
//each other and not to an original element left elsewhere that
|
||||
//happens to still carry that same soon-to-be-replaced uuid. If
|
||||
//only one half of a linked group was pasted, its link entry
|
||||
//simply finds no match here and is dropped -- same "leave it
|
||||
//unlinked" outcome as always.
|
||||
const QList <Element *> elmts_list = content.m_elements;
|
||||
for (Element *e : elmts_list) {
|
||||
e->initLink(elmts_list);
|
||||
}
|
||||
|
||||
//make new uuid for every pasted conductor, because old uuid are
|
||||
//the uuid of the copied conductor
|
||||
const QList <Conductor *> all_pasted_conductors = content.conductors();
|
||||
@@ -85,7 +102,6 @@ void PasteDiagramCommand::redo()
|
||||
}
|
||||
|
||||
//this is the first paste, we do some actions for the new element
|
||||
const QList <Element *> elmts_list = content.m_elements;
|
||||
for (Element *e : elmts_list)
|
||||
{
|
||||
//make new uuid, because old uuid are the uuid of the copied element
|
||||
|
||||
@@ -1477,7 +1477,7 @@ void DiagramView::createTemplateFromSelection()
|
||||
|
||||
collection_node.appendChild(collection_elmt);
|
||||
} else {
|
||||
qDebug() << "Warnung: Konnte XML-Definition für" << old_type << "nicht laden.";
|
||||
qDebug() << "Warning: could not load XML definition for" << old_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,18 @@ void ElementView::scaleClamped(qreal factor)
|
||||
scale(factor, factor);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementView::zoom
|
||||
Zoom by an arbitrary factor, for a continuous input such as a 3D mouse.
|
||||
Same clamping as the wheel zoom.
|
||||
@param zoom_factor : > 1 zooms in, < 1 zooms out
|
||||
*/
|
||||
void ElementView::zoom(qreal zoom_factor)
|
||||
{
|
||||
adjustSceneRect();
|
||||
scaleClamped(zoom_factor);
|
||||
}
|
||||
|
||||
/**
|
||||
Agrandit le schema (+33% = inverse des -25 % de zoomMoins())
|
||||
*/
|
||||
|
||||
@@ -41,6 +41,7 @@ class ElementView : public QGraphicsView {
|
||||
ElementScene *scene() const;
|
||||
void setScene(ElementScene *);
|
||||
QRectF viewedSceneRect() const;
|
||||
void zoom(qreal zoom_factor);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#include "elementpropertieseditorwidget.h"
|
||||
|
||||
#include "../../qet.h"
|
||||
#include "../../qetapp.h"
|
||||
#include "../../qetinformation.h"
|
||||
#include "ui_elementpropertieseditorwidget.h"
|
||||
@@ -185,13 +186,13 @@ void ElementPropertiesEditorWidget::upDateInterface()
|
||||
|
||||
const DiagramContext &info = m_data.m_informations;
|
||||
ui->m_auto_num_locked_cb->setChecked(
|
||||
info.value(QStringLiteral("auto_num_locked")).toString() == QLatin1String("true"));
|
||||
QET::infoFlagIsTrue(info.value(QStringLiteral("auto_num_locked")).toString()));
|
||||
ui->m_potential_isolating_cb->setChecked(
|
||||
info.value(QStringLiteral("potential_isolating")).toString() == QLatin1String("true"));
|
||||
QET::infoFlagIsTrue(info.value(QStringLiteral("potential_isolating")).toString()));
|
||||
}
|
||||
|
||||
ui->m_exclude_from_bom_cb->setChecked(
|
||||
m_data.m_informations.value(QStringLiteral("exclude_from_bom")).toString() == QLatin1String("true"));
|
||||
QET::infoFlagIsTrue(m_data.m_informations.value(QStringLiteral("exclude_from_bom")).toString()));
|
||||
|
||||
on_m_base_type_cb_currentIndexChanged(ui->m_base_type_cb->currentIndex());
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "../../qetapp.h"
|
||||
#include "../../qetmainwindow.h"
|
||||
#include "../../recentfiles.h"
|
||||
#include "../../elementscollectioncache.h"
|
||||
#include "../../factory/elementpicturefactory.h"
|
||||
#include "../graphicspart/customelementpart.h"
|
||||
#include "../elementitemeditor.h"
|
||||
#include "../styleeditor.h"
|
||||
@@ -348,6 +350,24 @@ bool QETElementEditor::toLocation(const ElementsLocation &location)
|
||||
tr("Impossible d'enregistrer l'élément", "message box content"));
|
||||
return(false);
|
||||
}
|
||||
|
||||
//setXml() just wrote the new drawing to disk, but the preview shown
|
||||
//in the elements panel comes from two caches keyed by path+uuid that
|
||||
//nothing here has told about the change: ElementPictureFactory's
|
||||
//in-memory picture cache and ElementsCollectionCache's on-disk
|
||||
//SQLite cache (see ElementsLocation::icon()). locationWasSaved()
|
||||
//(elementscollectionwidget.cpp) re-reads the icon right after this
|
||||
//call returns, but both caches still hand back the pre-edit pixmap,
|
||||
//so the panel keeps showing the stale preview until the whole
|
||||
//collection is reloaded. Drop and rebuild them here.
|
||||
ElementPictureFactory::instance()->dropCache(location);
|
||||
if (ElementsCollectionCache *cache = QETApp::collectionCache()) {
|
||||
if (cache->fetchData(location)) {
|
||||
cache->cacheName(location.toString(), location.uuid());
|
||||
cache->cachePixmap(location.toString(), location.uuid());
|
||||
}
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -674,6 +674,27 @@ void ElementsPanelWidget::duplicateDiagram()
|
||||
bool erase_labels = settings.value(
|
||||
"diagramcommands/erase-label-on-copy", true).toBool();
|
||||
|
||||
// Resolve a linked pair duplicated together against each other
|
||||
// (bugtracker #607) before the loop below renews their uuids or
|
||||
// clears their pending links: at this exact moment a copy's
|
||||
// tmp_uuids_link still holds its source's original partner
|
||||
// uuid, which still equals the not-yet-renewed uuid of that
|
||||
// partner's own copy if both were duplicated together. Scoped
|
||||
// to this diagram's own copies, not a project-wide search, so
|
||||
// this never links back to the source elements the copies were
|
||||
// made from -- if only one half of a linked pair is here, its
|
||||
// link entry simply finds no match and is dropped, same as
|
||||
// clearPendingLinks() used to do unconditionally for every copy.
|
||||
QList<Element *> new_elements;
|
||||
for (QGraphicsItem *item : new_diagram->items()) {
|
||||
if (Element *elmt = dynamic_cast<Element *>(item)) {
|
||||
new_elements << elmt;
|
||||
}
|
||||
}
|
||||
for (Element *elmt : new_elements) {
|
||||
elmt->initLink(new_elements);
|
||||
}
|
||||
|
||||
for (QGraphicsItem *item : new_diagram->items()) {
|
||||
if (Element *elmt = dynamic_cast<Element *>(item)) {
|
||||
// The XML round-trip kept the source elements' uuids. Give the
|
||||
@@ -695,9 +716,9 @@ void ElementsPanelWidget::duplicateDiagram()
|
||||
new_diagram->restoreText(elmt);
|
||||
}
|
||||
|
||||
// Clear pending links so copies don't link back to
|
||||
// the source elements via stale UUIDs.
|
||||
elmt->clearPendingLinks();
|
||||
// initLink() above already cleared tmp_uuids_link for
|
||||
// every copy, matched or not -- nothing left here that
|
||||
// could link back to a stale source uuid.
|
||||
|
||||
// Clean up copied element data:
|
||||
// 1. Slaves always lose label/formula/comment/location
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "logging/eventloopwatchdog.h"
|
||||
#include "logging/qetlogger.h"
|
||||
#include "machine_info.h"
|
||||
#include "diagram.h"
|
||||
#include "palettegraphicsview.h"
|
||||
#include "qet.h"
|
||||
#include "qetapp.h"
|
||||
#include "qetmessagebox.h"
|
||||
@@ -31,6 +33,7 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDomImplementation>
|
||||
#include <QFont>
|
||||
|
||||
#include <QStyleFactory>
|
||||
#include <QtConcurrentRun>
|
||||
@@ -105,6 +108,19 @@ int main(int argc, char **argv)
|
||||
// from Qt 6.12 on; opt in explicitly for older Qt 5/6.
|
||||
QDomImplementation::setInvalidDataPolicy(
|
||||
QDomImplementation::ReturnNullNode);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// "MS Shell Dlg 2" is not a font but a Windows alias, and many projects
|
||||
// and settings saved on Windows carry it. Qt 5's GDI font backend let
|
||||
// Windows resolve it to Tahoma; Qt 6's DirectWrite backend does not know
|
||||
// the alias and falls back to Arial, so those texts come out heavier on
|
||||
// screen and in exported PDFs (bugtracker #340). Resolve both aliases
|
||||
// the way Windows does. Done before any application object exists so
|
||||
// that the headless export and scripting runs below get it too.
|
||||
QFont::insertSubstitution("MS Shell Dlg 2", "Tahoma");
|
||||
QFont::insertSubstitution("MS Shell Dlg", "Microsoft Sans Serif");
|
||||
#endif
|
||||
|
||||
//Creation and execution of the application
|
||||
//HighDPI
|
||||
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
|
||||
@@ -144,6 +160,22 @@ int main(int argc, char **argv)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Re-apply the sheet background last picked in the diagram editor, so
|
||||
// every project opened from here on -- existing or new, whichever one
|
||||
// it is -- draws that background instead of the built-in default that
|
||||
// would otherwise force the user to pick it again after each start.
|
||||
//
|
||||
// Done here rather than in main()'s first lines on purpose: the
|
||||
// headless export and scripting runs above return before reaching
|
||||
// this point and must keep rendering on plain white. It also has to
|
||||
// happen before QETApp is constructed below, since that constructor
|
||||
// already loads the projects given on the command line.
|
||||
{
|
||||
const QetSettings::SheetBackground sheet_background = QetSettings::sheetBackground();
|
||||
PaletteGraphicsView::setCustomBackgroundColor(sheet_background.custom);
|
||||
Diagram::background_color = sheet_background.color;
|
||||
}
|
||||
|
||||
// Resolve the logger's state (log directory, session filename, open
|
||||
// file handle) explicitly here, immediately before installing the
|
||||
// handler -- not implicitly on whichever thread happens to log
|
||||
|
||||
@@ -388,6 +388,29 @@ void convertUriToGoTo(const QString &pdfPath)
|
||||
out.close();
|
||||
}
|
||||
|
||||
void removeUnusedPdfxNamespace(const QString &pdfPath)
|
||||
{
|
||||
QFile f(pdfPath);
|
||||
if (!f.open(QIODevice::ReadOnly)) return;
|
||||
QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
// Qt only fills the pdfxid namespace in when it writes PDF/X-4.
|
||||
if (data.contains("pdfxid:GTS_PDFXVersion")) return;
|
||||
|
||||
static const QByteArray decl =
|
||||
" xmlns:pdfxid=\"http://www.npes.org/pdfx/ns/id/\"";
|
||||
const int pos = data.indexOf(decl);
|
||||
if (pos == -1) return;
|
||||
|
||||
// Same length, so the stream /Length and the xref offsets stay valid.
|
||||
data.replace(pos, decl.size(), QByteArray(decl.size(), ' '));
|
||||
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
|
||||
f.write(data);
|
||||
f.close();
|
||||
}
|
||||
|
||||
void convertComponentInfoAnnotations(const QString &pdfPath,
|
||||
const QList<ComponentInfo> &annotations)
|
||||
{
|
||||
|
||||
@@ -74,6 +74,15 @@ namespace PdfLinks {
|
||||
*/
|
||||
void convertUriToGoTo(const QString &pdfPath);
|
||||
|
||||
/**
|
||||
Post-process a Qt-generated PDF file: blank out the PDF/X namespace
|
||||
declaration Qt 6 writes into the XMP metadata of every PDF. Adobe
|
||||
Acrobat draws small text too bold when it is present (bugtracker #340).
|
||||
Replaced in place with spaces, so no offset changes. No-op for a real
|
||||
PDF/X file or when the declaration is absent.
|
||||
*/
|
||||
void removeUnusedPdfxNamespace(const QString &pdfPath);
|
||||
|
||||
struct ComponentInfo {
|
||||
QString contents;
|
||||
};
|
||||
|
||||
@@ -874,6 +874,7 @@ void ProjectPrintWindow::print()
|
||||
// Convert URI link annotations into native internal GoTo/FitR
|
||||
// actions so cross-references jump inside the document.
|
||||
PdfLinks::convertUriToGoTo(pdfFile);
|
||||
PdfLinks::removeUnusedPdfxNamespace(pdfFile);
|
||||
|
||||
this->close();
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "qeticons.h"
|
||||
#include "shortcutmanager.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <QBuffer>
|
||||
#include <QColorDialog>
|
||||
@@ -244,10 +245,33 @@ bool QET::attributeIsAReal(
|
||||
bool ok;
|
||||
qreal tmp = e.attribute(nom_attribut).toDouble(&ok);
|
||||
if (!ok) return(false);
|
||||
// QString::toDouble() sets ok=true for "nan"/"inf"/"-inf" -- these
|
||||
// parse successfully but are not usable coordinates. A non-finite
|
||||
// element/terminal position reaches Conductor::shape() during load
|
||||
// and hangs there at 100% CPU inside QPainterPathStroker::createStroke(),
|
||||
// confirmed with gdb: not a blocked wait, genuine unbounded computation.
|
||||
if (!std::isfinite(tmp)) return(false);
|
||||
if (reel != nullptr) *reel = tmp;
|
||||
return(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QET::infoFlagIsTrue
|
||||
@see the header comment for why this exists rather than a bare
|
||||
== "true" comparison.
|
||||
@param value the raw elementInformations string to test
|
||||
@return true if @p value, trimmed and case-folded, is one of the
|
||||
truthy spellings this codebase already accepts elsewhere
|
||||
*/
|
||||
bool QET::infoFlagIsTrue(const QString &value)
|
||||
{
|
||||
const QString v = value.trimmed().toLower();
|
||||
return v == QLatin1String("true")
|
||||
|| v == QLatin1String("1")
|
||||
|| v == QLatin1String("yes")
|
||||
|| v == QLatin1String("on");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QET::ElementsAndConductorsSentence
|
||||
Permet de composer rapidement la proposition "x elements et y conducteurs"
|
||||
|
||||
@@ -161,6 +161,22 @@ namespace QET {
|
||||
bool orthogonalProjection(const QPointF &, const QLineF &, QPointF * = nullptr);
|
||||
bool attributeIsAnInteger(const QDomElement &, const QString& , int * = nullptr);
|
||||
bool attributeIsAReal(const QDomElement &, const QString& , qreal * = nullptr);
|
||||
/**
|
||||
Whether an elementInformations flag (auto_num_locked,
|
||||
potential_isolating, exclude_from_bom, ...) counts as "on".
|
||||
Case-insensitive and tolerant of surrounding whitespace, and
|
||||
accepts the same set of truthy spellings ("true", "1", "yes",
|
||||
"on") that element_nomenclature_view's SQL predicate for
|
||||
exclude_from_bom already does -- see
|
||||
projectDataBase::createElementNomenclatureView(). These flags
|
||||
are only ever written by this app's own checkboxes as literal
|
||||
"true"/"false" today, but a bare == "true" comparison silently
|
||||
treats anything else -- "True", "TRUE", a trailing space from
|
||||
a hand-edited file, a value some other tool wrote -- as off,
|
||||
with no error and no visible difference from the checkbox
|
||||
being genuinely unticked (discussion #785).
|
||||
*/
|
||||
bool infoFlagIsTrue(const QString &value);
|
||||
QString ElementsAndConductorsSentence(int elements=0,
|
||||
int conductors=0,
|
||||
int indi_texts=0,
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
#include "machine_info.h"
|
||||
#include "TerminalStrip/ui/terminalstripeditorwindow.h"
|
||||
#include "qetversion.h"
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
# include "spacemouse/spacemouselistener.h"
|
||||
# include "ui/configpage/spacemouseconfigpage.h"
|
||||
#endif
|
||||
#include "logging/qetlogger.h"
|
||||
#include "logging/ui/diagnosticsreportdialog.h"
|
||||
|
||||
@@ -164,7 +168,24 @@ QETApp::QETApp() :
|
||||
m_splash_screen -> hide();
|
||||
}
|
||||
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
//Always safe to construct: it silently does nothing when spacenavd
|
||||
//isn't running or no device is attached, which is the common case
|
||||
//even in a build with this feature compiled in. See
|
||||
//SpaceMouseListener's class comment.
|
||||
m_space_mouse_listener = new SpaceMouseListener(this);
|
||||
#endif
|
||||
|
||||
//Deferred so this constructor returns before the prompts appear.
|
||||
//checkBackupFiles() opens modal dialogs, and main() still has work to
|
||||
//do once we return -- in particular connecting
|
||||
//SingleApplication::receivedMessage to receiveMessage(). While those
|
||||
//prompts were up that connection did not exist yet, so a file handed
|
||||
//to the already-running instance during start-up was accepted by the
|
||||
//socket and then dropped on the floor.
|
||||
QMetaObject::invokeMethod(this, [this]() {
|
||||
checkBackupFiles();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2192,6 +2213,9 @@ void QETApp::configureQET()
|
||||
cd.addPage(new ExportConfigPage());
|
||||
cd.addPage(new PrintConfigPage());
|
||||
cd.addPage(new ShortcutsConfigPage());
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
cd.addPage(new SpaceMouseConfigPage());
|
||||
#endif
|
||||
|
||||
// associates the dialog with a possible parent widget
|
||||
// associe le dialogue a un eventuel widget parent
|
||||
@@ -2207,6 +2231,12 @@ void QETApp::configureQET()
|
||||
// affiche le dialogue puis evite de le lier a un quelconque widget parent
|
||||
cd.exec();
|
||||
cd.setParent(nullptr, cd.windowFlags());
|
||||
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
if (m_space_mouse_listener) {
|
||||
m_space_mouse_listener->reloadSettings();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,6 +48,9 @@ class QETProject;
|
||||
class QETTitleBlockTemplateEditor;
|
||||
class QTextOrientationSpinBoxWidget;
|
||||
class RecentFiles;
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
class SpaceMouseListener;
|
||||
#endif
|
||||
|
||||
/**
|
||||
@brief The QETApp class
|
||||
@@ -229,6 +232,13 @@ class QETApp : public QObject
|
||||
static TitleBlockTemplatesFilesCollection *m_company_tbt_collection;
|
||||
static TitleBlockTemplatesFilesCollection *m_custom_tbt_collection;
|
||||
static ElementsCollectionCache *collections_cache_;
|
||||
#ifdef QET_SPACEMOUSE_SUPPORT
|
||||
/// One per application, not per window: a physical 6-DOF device
|
||||
/// is a single ambient input source, and motion is applied to
|
||||
/// whichever DiagramView is currently active. See
|
||||
/// SpaceMouseListener's class comment.
|
||||
SpaceMouseListener *m_space_mouse_listener = nullptr;
|
||||
#endif
|
||||
static QMap<uint, QETProject *> registered_projects_;
|
||||
static uint next_project_id;
|
||||
static RecentFiles *m_projects_recent_files;
|
||||
|
||||
@@ -1856,7 +1856,18 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
|
||||
diagram->undoStack().push(c);
|
||||
}
|
||||
else if (value == "rotate_selected_text")
|
||||
diagram->undoStack().push(new RotateTextsCommand(diagram));
|
||||
{
|
||||
//Ask for the angle first, then build the command: the command
|
||||
//itself no longer opens a dialog. Guarding on the selection keeps
|
||||
//the previous behaviour of showing no dialog when there is
|
||||
//nothing to rotate.
|
||||
if (RotateTextsCommand::hasSelectedTexts(diagram))
|
||||
{
|
||||
qreal rotation = 0;
|
||||
if (RotateTextsCommand::askRotation(rotation))
|
||||
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
|
||||
}
|
||||
}
|
||||
else if (value == "find_selected_element" && currentElement())
|
||||
findElementInPanel(currentElement()->location());
|
||||
else if (value == "edit_selected_element")
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QDomElement>
|
||||
#include <QtCore/qnumeric.h>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
|
||||
/**
|
||||
@brief DynamicElementTextItem::DynamicElementTextItem
|
||||
@@ -732,19 +733,6 @@ void DynamicElementTextItem::paint(QPainter *painter, const QStyleOptionGraphics
|
||||
{
|
||||
DiagramTextItem::paint(painter, option, widget);
|
||||
|
||||
//Only ever repositions already-existing sibling items here --
|
||||
//never adds or removes one. paint() runs while QGraphicsScene is
|
||||
//iterating its item list to draw it, and mutating that list mid
|
||||
//-iteration (which addResizeHandles()/removeResizeHandles() do,
|
||||
//through QGraphicsScene::addItem()/removeItem()) crashes. An
|
||||
//earlier version of this fix called them from here and crashed
|
||||
//qelectrotech reproducibly on deselecting a text (SIGABRT); see
|
||||
//refreshResizeHandlesVisibility() for where that now happens
|
||||
//instead -- itemChange(), Qt's own safe hook for exactly this,
|
||||
//already used below for this item's own selection.
|
||||
if (m_left_resize_handle || m_right_resize_handle)
|
||||
updateResizeHandlesPos();
|
||||
|
||||
if (m_frame)
|
||||
{
|
||||
painter->save();
|
||||
@@ -927,11 +915,17 @@ void DynamicElementTextItem::addResizeHandles()
|
||||
|
||||
for (QetGraphicsHandlerItem *handle : {m_left_resize_handle, m_right_resize_handle})
|
||||
{
|
||||
scene()->addItem(handle);
|
||||
//Children of this text, not free scene items: Qt then carries
|
||||
//them along when the parent element moves, rotates or is
|
||||
//zoomed, and repaints their old and new area in the same
|
||||
//update as this text. Moving free items from paint() instead
|
||||
//left green fragments behind (qelectrotech#1002).
|
||||
handle->setParentItem(this);
|
||||
handle->setColor(Qt::darkGreen);
|
||||
handle->setZValue(zValue() + 1);
|
||||
handle->installSceneEventFilter(this);
|
||||
}
|
||||
m_resize_handles_con = connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
this, &DynamicElementTextItem::updateResizeHandlesPos);
|
||||
|
||||
updateResizeHandlesPos();
|
||||
}
|
||||
@@ -941,6 +935,7 @@ void DynamicElementTextItem::addResizeHandles()
|
||||
*/
|
||||
void DynamicElementTextItem::removeResizeHandles()
|
||||
{
|
||||
disconnect(m_resize_handles_con);
|
||||
delete m_left_resize_handle;
|
||||
delete m_right_resize_handle;
|
||||
m_left_resize_handle = nullptr;
|
||||
@@ -973,8 +968,8 @@ void DynamicElementTextItem::updateResizeHandlesPos()
|
||||
return;
|
||||
|
||||
QRectF br = boundingRect();
|
||||
m_left_resize_handle->setPos(mapToScene(QPointF(br.left(), br.center().y())));
|
||||
m_right_resize_handle->setPos(mapToScene(QPointF(br.right(), br.center().y())));
|
||||
m_left_resize_handle->setPos(br.left(), br.center().y());
|
||||
m_right_resize_handle->setPos(br.right(), br.center().y());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -200,6 +200,7 @@ class DynamicElementTextItem : public DiagramTextItem
|
||||
qreal m_resize_original_width = -1;
|
||||
qreal m_resize_baseline_width = -1;
|
||||
qreal m_resize_start_local_x = 0;
|
||||
QMetaObject::Connection m_resize_handles_con;
|
||||
};
|
||||
|
||||
#endif // DYNAMICELEMENTTEXTITEM_H
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
#include "elementtextitemgroup.h"
|
||||
#include "iostream"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCollator>
|
||||
#include <QScreen>
|
||||
|
||||
static const QString plcTerminalKeys[] = {
|
||||
QETInformation::ELMT_PLC_T1,
|
||||
@@ -209,6 +211,21 @@ void Element::editProperty()
|
||||
//with the "text" tab of ElementPropertiesWidget,
|
||||
//the ui freeze, until user press escape key
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
|
||||
// A PLC master carries a 6-column IO table: without an explicit
|
||||
// size the dialog falls back to its (cramped) sizeHint, so open it
|
||||
// at three times its natural width instead. The height stays at
|
||||
// the natural one, and the width never exceeds the screen.
|
||||
const ElementData data = elementData();
|
||||
if (data.m_type == ElementData::Master
|
||||
&& data.m_master_type == ElementData::PLC) {
|
||||
const QSize natural = dialog.sizeHint();
|
||||
int width = natural.width() * 3;
|
||||
if (QScreen *screen = QApplication::primaryScreen())
|
||||
width = qMin(width, screen->availableGeometry().width());
|
||||
dialog.resize(width, natural.height());
|
||||
}
|
||||
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
@@ -1369,6 +1386,33 @@ void Element::initLink(QETProject *prj)
|
||||
tmp_uuids_link.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::initLink
|
||||
Overload resolving tmp_uuids_link against @p candidates instead of a
|
||||
project-wide ElementProvider search -- see the header comment for
|
||||
why the search has to be scoped this way right after a paste or
|
||||
folio-duplication XML round-trip, before uuids are renewed.
|
||||
@param candidates the elements to search for a link partner in
|
||||
*/
|
||||
void Element::initLink(const QList<Element *> &candidates)
|
||||
{
|
||||
// if nothing to link return now
|
||||
if (tmp_uuids_link.isEmpty()) return;
|
||||
|
||||
for (int i = 0; i < tmp_uuids_link.size(); ++i) {
|
||||
for (Element *elmt : candidates) {
|
||||
if (elmt->uuid() == tmp_uuids_link[i].uuid) {
|
||||
elmt->linkToElement(this);
|
||||
if (tmp_uuids_link[i].group_index >= 0) {
|
||||
m_group_index_map[elmt] = tmp_uuids_link[i].group_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
tmp_uuids_link.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Element::linkTypeToString
|
||||
* \deprecated use instead ElementData::typeToString
|
||||
|
||||
@@ -207,6 +207,25 @@ class Element : public QetGraphicsItem
|
||||
virtual void unlinkAllElements() {}
|
||||
virtual void unlinkElement(Element *) {}
|
||||
virtual void initLink(QETProject *);
|
||||
/**
|
||||
Resolve tmp_uuids_link against a caller-supplied candidate
|
||||
list instead of a project-wide search (bugtracker #607).
|
||||
Used right after an XML round-trip (paste, folio
|
||||
duplication) and before the pasted/duplicated elements'
|
||||
uuids are renewed: at that moment a copy's tmp_uuids_link
|
||||
still holds its source's original partner uuid, which
|
||||
still matches the not-yet-renewed uuid of that partner's
|
||||
own copy if it was carried along in the same batch.
|
||||
Resolving only within @p candidates -- not the whole
|
||||
project -- is what stops a linked pair pasted together
|
||||
from matching an original element left elsewhere that
|
||||
happens to still carry that same soon-to-be-replaced
|
||||
uuid. If only one half of a linked group is in
|
||||
@p candidates, its entry finds no match and is dropped,
|
||||
same as initLink(QETProject *) leaving an unresolvable
|
||||
link unlinked.
|
||||
*/
|
||||
void initLink(const QList<Element *> &candidates);
|
||||
QList<Element *> linkedElements ();
|
||||
|
||||
int groupIndexForElement(Element *elmt) const;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qet.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../conductorautonumerotation.h"
|
||||
#include "../diagram.h"
|
||||
@@ -986,7 +987,7 @@ QList<Terminal *> relatedPotentialTerminal (
|
||||
else if (terminal -> parentElement() -> linkType() & Element::Terminale)
|
||||
{
|
||||
// English: Check if the user activated the potential isolation checkbox for this terminal
|
||||
if (terminal->parentElement()->elementInformations().value(QStringLiteral("potential_isolating")).toString() == QLatin1String("true")) {
|
||||
if (QET::infoFlagIsTrue(terminal->parentElement()->elementInformations().value(QStringLiteral("potential_isolating")).toString())) {
|
||||
// English: Potential is isolated. Return an empty list so it does not propagate to the other side.
|
||||
return QList<Terminal *>();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
#include "autoNum/assignvariables.h"
|
||||
#include "autoNum/numerotationcontext.h"
|
||||
#include "autoNum/numerotationcontextcommands.h"
|
||||
#include "autoNum/renumberelementscommand.h"
|
||||
#include "diagram.h"
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetapp.h"
|
||||
#include "qetmessagebox.h"
|
||||
#include "qetresult.h"
|
||||
@@ -40,7 +42,30 @@
|
||||
#include <QHash>
|
||||
#include <QTimer>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief Reset numeric fields of a NumerotationContext so renumbering starts at 1.
|
||||
* Keeps non-numeric parts (string/idfolio/folio/plant/locmach/elementline/elementcolumn/elementprefix) unchanged.
|
||||
*/
|
||||
NumerotationContext resetContextForRenumber(const NumerotationContext &tmpl)
|
||||
{
|
||||
NumerotationContext out = tmpl;
|
||||
for (int i = 0; i < out.size(); ++i) {
|
||||
const QStringList parts = out.itemAt(i);
|
||||
if (parts.isEmpty()) continue;
|
||||
const QString type = parts.at(0);
|
||||
if (out.keyIsNumber(type)) {
|
||||
out.replaceValue(i, QStringLiteral("1"));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#include <QtDebug>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
static int BACKUP_INTERVAL = 1200000; //interval in ms of backup = 20min
|
||||
@@ -132,6 +157,9 @@ QETProject::QETProject(const QString &path, QObject *parent) :
|
||||
return;
|
||||
}
|
||||
|
||||
//The file just read already holds everything a crash could lose, so
|
||||
//there is nothing to back up until the project is changed.
|
||||
m_backup_needed = false;
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -258,6 +286,19 @@ void QETProject::init()
|
||||
m_undo_stack = new QUndoStack(this);
|
||||
connect(m_undo_stack, &QUndoStack::cleanChanged, this, &QETProject::undoStackChanged);
|
||||
|
||||
//What counts as a change for writeBackup(): the undo stack moving,
|
||||
//setModified(true), and the embedded collections, which can change
|
||||
//without going through either.
|
||||
const auto backup_needed = [this]() { m_backup_needed = true; };
|
||||
connect(m_undo_stack, &QUndoStack::indexChanged, this, backup_needed);
|
||||
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::changed, this, backup_needed);
|
||||
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::aboutToRemove, this, backup_needed);
|
||||
connect(m_elements_collection, &XmlElementCollection::elementAdded, this, backup_needed);
|
||||
connect(m_elements_collection, &XmlElementCollection::elementChanged, this, backup_needed);
|
||||
connect(m_elements_collection, &XmlElementCollection::elementRemoved, this, backup_needed);
|
||||
connect(m_elements_collection, &XmlElementCollection::directorieAdded, this, backup_needed);
|
||||
connect(m_elements_collection, &XmlElementCollection::directoryRemoved, this, backup_needed);
|
||||
|
||||
m_save_backup_timer.setInterval(BACKUP_INTERVAL);
|
||||
connect(&m_save_backup_timer, &QTimer::timeout, this, &QETProject::writeBackup);
|
||||
m_save_backup_timer.start();
|
||||
@@ -793,6 +834,109 @@ void QETProject::setCurrrentElementAutonum(QString autoNum) {
|
||||
m_current_element_autonum = std::move(autoNum);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::renumberElementsBySchemeTitle
|
||||
Renumber existing elements by element autonumbering scheme title.
|
||||
|
||||
Elements do not store the scheme title; they store a "formula" (elementInformations["formula"]).
|
||||
This method matches elements to schemes by comparing the stored formula with the formula derived
|
||||
from each scheme's NumerotationContext.
|
||||
|
||||
If scheme_title is empty, all schemes are renumbered. Otherwise only that scheme is renumbered.
|
||||
The operation is undoable.
|
||||
*/
|
||||
void QETProject::renumberElementsBySchemeTitle(const QString &scheme_title)
|
||||
{
|
||||
if (!m_undo_stack) return;
|
||||
if (isReadOnly()) return;
|
||||
|
||||
// Build map: scheme title -> canonical formula
|
||||
QHash<QString, QString> scheme_formula;
|
||||
for (const QString &k : m_element_autonum.keys()) {
|
||||
if (!scheme_title.isEmpty() && k != scheme_title) continue;
|
||||
scheme_formula.insert(k, autonum::numerotationContextToFormula(m_element_autonum.value(k)));
|
||||
}
|
||||
if (scheme_formula.isEmpty()) return;
|
||||
|
||||
// Collect elements per scheme by formula match
|
||||
QHash<QString, QVector<Element*>> by_key;
|
||||
for (Diagram *d : diagrams()) {
|
||||
if (!d) continue;
|
||||
const auto items = d->items();
|
||||
for (QGraphicsItem *it : items) {
|
||||
auto *el = qgraphicsitem_cast<Element*>(it);
|
||||
if (!el) continue;
|
||||
if (el->linkType() == Element::Slave || (el->linkType() & Element::AllReport))
|
||||
continue;
|
||||
|
||||
const QString el_formula = el->elementInformations().value(QStringLiteral("formula")).toString();
|
||||
if (el_formula.isEmpty()) continue;
|
||||
|
||||
QString matched_key;
|
||||
for (auto itf = scheme_formula.constBegin(); itf != scheme_formula.constEnd(); ++itf) {
|
||||
if (itf.value() == el_formula) { matched_key = itf.key(); break; }
|
||||
}
|
||||
if (matched_key.isEmpty()) continue;
|
||||
by_key[matched_key].append(el);
|
||||
}
|
||||
}
|
||||
if (by_key.isEmpty()) return;
|
||||
|
||||
QVector<RenumberElementsCommand::ElementChange> changes;
|
||||
QHash<QString, NumerotationContext> old_ctx;
|
||||
QHash<QString, NumerotationContext> new_ctx;
|
||||
|
||||
for (auto it = by_key.constBegin(); it != by_key.constEnd(); ++it) {
|
||||
old_ctx.insert(it.key(), m_element_autonum.value(it.key()));
|
||||
}
|
||||
|
||||
for (auto it = by_key.begin(); it != by_key.end(); ++it) {
|
||||
const QString key = it.key();
|
||||
auto &elements = it.value();
|
||||
std::sort(elements.begin(), elements.end(), [](Element *a, Element *b){ return comparPos(a, b); });
|
||||
|
||||
NumerotationContext base_tmpl = m_element_autonum.value(key);
|
||||
NumerotationContext nc = resetContextForRenumber(base_tmpl);
|
||||
NumerotationContextCommands ncc(nc);
|
||||
|
||||
for (Element *el : elements) {
|
||||
RenumberElementsCommand::ElementChange ch;
|
||||
ch.element = el;
|
||||
ch.old_infos = el->elementInformations();
|
||||
ch.old_seq = el->sequenceStruct();
|
||||
ch.old_frozen = el->isFreezeLabel();
|
||||
ch.new_frozen = ch.old_frozen; // preserve frozen state
|
||||
|
||||
const QString formula = ch.old_infos.value(QStringLiteral("formula")).toString();
|
||||
autonum::sequentialNumbers new_seq;
|
||||
new_seq.clear();
|
||||
autonum::setSequential(formula, new_seq, nc, el->diagram(), key);
|
||||
|
||||
DiagramContext new_infos = ch.old_infos;
|
||||
new_infos.addValue(QStringLiteral("label"), autonum::AssignVariables::formulaToLabel(formula, new_seq, el->diagram(), el, nullptr));
|
||||
ch.new_infos = new_infos;
|
||||
ch.new_seq = new_seq;
|
||||
changes.append(ch);
|
||||
|
||||
// advance
|
||||
nc = ncc.next();
|
||||
ncc = NumerotationContextCommands(nc);
|
||||
}
|
||||
|
||||
new_ctx.insert(key, nc);
|
||||
}
|
||||
|
||||
if (changes.isEmpty()) return;
|
||||
|
||||
auto *cmd = new RenumberElementsCommand(
|
||||
this,
|
||||
changes,
|
||||
old_ctx,
|
||||
new_ctx,
|
||||
scheme_title.isEmpty() ? tr("Renumber elements") : tr("Renumber elements (%1)").arg(scheme_title));
|
||||
m_undo_stack->push(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::conductorAutoNumFormula
|
||||
@param key : autonum title
|
||||
@@ -1564,6 +1708,9 @@ void QETProject::diagramOrderChanged(int old_index, int new_index) {
|
||||
Mark this project as modified and emit the projectModified() signal.
|
||||
*/
|
||||
void QETProject::setModified(bool modified) {
|
||||
if (modified) {
|
||||
m_backup_needed = true;
|
||||
}
|
||||
if (m_modified != modified) {
|
||||
m_modified = modified;
|
||||
emit projectModified(this, m_modified);
|
||||
@@ -2146,6 +2293,12 @@ void QETProject::writeBackup()
|
||||
//both would write through &m_backup_file on different threads.
|
||||
if (m_backup_future.isRunning())
|
||||
return;
|
||||
//toXml() walks the whole project on the GUI thread, which freezes
|
||||
//big projects for seconds (bugtracker #273, #329). A backup of an
|
||||
//unchanged project would be identical to the last one, so skip it.
|
||||
if (!m_backup_needed)
|
||||
return;
|
||||
m_backup_needed = false;
|
||||
//Capture the document by value (implicitly shared, so cheap): the
|
||||
//Qt5-style QtConcurrent::run(function, reference-args) call did not
|
||||
//survive the Qt6 API change, a lambda behaves identically on both.
|
||||
|
||||
@@ -169,6 +169,20 @@ class QETProject : public QObject
|
||||
QString elementCurrentAutoNum() const;
|
||||
void setCurrrentElementAutonum(QString autoNum);
|
||||
|
||||
/**
|
||||
* @brief Renumber existing elements by element autonumbering scheme.
|
||||
*
|
||||
* Elements do not store the scheme title but they store the corresponding formula.
|
||||
* This operation matches elements to schemes by comparing the stored formula
|
||||
* with the formula derived from the scheme's NumerotationContext.
|
||||
*
|
||||
* If @p scheme_title is empty, all schemes are renumbered.
|
||||
* If @p scheme_title is non-empty, only that scheme is renumbered.
|
||||
*
|
||||
* The operation is undoable.
|
||||
*/
|
||||
void renumberElementsBySchemeTitle(const QString &scheme_title = QString());
|
||||
|
||||
//Element
|
||||
void freezeExistentElementLabel(bool freeze, int from, int to);
|
||||
void freezeNewElementLabel(bool freeze, int from, int to);
|
||||
@@ -297,6 +311,8 @@ class QETProject : public QObject
|
||||
private:
|
||||
/// When false, writeBackup() is a no-op (set by the headless CLI)
|
||||
static bool m_backup_enabled;
|
||||
/// Something changed since the last backup, see writeBackup()
|
||||
bool m_backup_needed = true;
|
||||
/// File path this project is saved to
|
||||
QString m_file_path;
|
||||
/// Current state of the project
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
#include "shortcutmanager.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QAction>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QVariant>
|
||||
@@ -166,3 +168,32 @@ void ShortcutManager::resetAllToDefaults()
|
||||
resetToDefault(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ShortcutManager::trigger
|
||||
@param id
|
||||
@return see the declaration's doc comment
|
||||
*/
|
||||
bool ShortcutManager::trigger(const QString &id) const
|
||||
{
|
||||
auto it = m_entries.find(id);
|
||||
if (it == m_entries.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const QPointer<QObject> &target : qAsConst(it->targets))
|
||||
{
|
||||
if (!target) {
|
||||
continue;
|
||||
}
|
||||
if (auto *action = qobject_cast<QAction *>(target.data())) {
|
||||
action->trigger();
|
||||
return true;
|
||||
}
|
||||
if (auto *button = qobject_cast<QAbstractButton *>(target.data())) {
|
||||
button->click();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,20 @@ class ShortcutManager
|
||||
void resetToDefault(const QString &id);
|
||||
void resetAllToDefaults();
|
||||
|
||||
/// Activate the first still-alive target registered under \a id
|
||||
/// -- QAction::trigger() or QAbstractButton::click(), whichever
|
||||
/// it turns out to be -- for an input source other than the
|
||||
/// keyboard (e.g. a 3D mouse button) that wants to invoke a
|
||||
/// named action without knowing or caring which of the two it
|
||||
/// is. Deliberately not disambiguated by the currently active
|
||||
/// window when an id has several live targets: a target's
|
||||
/// owning top-level window isn't reliably discoverable from a
|
||||
/// bare QAction. Correct in the overwhelming common case of one
|
||||
/// open editor window; a known simplification in the rarer
|
||||
/// multi-window case, not a guaranteed-correct dispatch.
|
||||
/// @return whether a live target was found and triggered.
|
||||
bool trigger(const QString &id) const;
|
||||
|
||||
private:
|
||||
ShortcutManager() = default;
|
||||
ShortcutManager(const ShortcutManager &) = delete;
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
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 "connexionbackend.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
//The part of 3DconnexionClient's interface QET uses, with the
|
||||
//values Blender uses (intern/ghost/intern/GHOST_NDOFManagerCocoa.mm).
|
||||
constexpr quint32 MSG_DEVICE_STATE = 0x33645352; // '3dSR'
|
||||
constexpr quint16 CMD_HANDLE_BUTTONS = 2;
|
||||
constexpr quint16 CMD_HANDLE_AXIS = 3;
|
||||
constexpr quint16 MODE_TAKE_OVER = 1;
|
||||
constexpr quint32 MASK_ALL = 0x3fff;
|
||||
constexpr quint32 MASK_ALL_BUTTONS = 0xffffffff;
|
||||
constexpr quint32 SIGNATURE = 0x51456c54; // 'QElT'
|
||||
|
||||
//ConnexionDeviceState is packed to 2 bytes. Offsets of the fields read.
|
||||
constexpr int STATE_CLIENT = 2;
|
||||
constexpr int STATE_COMMAND = 4;
|
||||
constexpr int STATE_AXIS = 30;
|
||||
constexpr int STATE_BUTTONS = 44;
|
||||
|
||||
using MessageHandler = void (*)(quint32, quint32, void *);
|
||||
using DeviceHandler = void (*)(quint32);
|
||||
using SetConnexionHandlers = qint16 (*)(MessageHandler, DeviceHandler, DeviceHandler, bool);
|
||||
using RegisterConnexionClient = quint16 (*)(quint32, const quint8 *, quint16, quint32);
|
||||
using SetConnexionClientButtonMask = void (*)(quint16, quint32);
|
||||
using CleanupConnexionHandlers = void (*)();
|
||||
using UnregisterConnexionClient = void (*)(quint16);
|
||||
|
||||
//3DxWare's callbacks carry no context, so the one client lives here.
|
||||
//The mutex is shared with 3DxWare's thread.
|
||||
QMutex instance_mutex;
|
||||
ConnexionBackend *instance = nullptr;
|
||||
|
||||
void deviceChanged(quint32) {}
|
||||
}
|
||||
|
||||
const char ConnexionBackend::DEFAULT_LIBRARY[] =
|
||||
"/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient";
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::ConnexionBackend
|
||||
Load 3DxWare's client library and register with it. Not installed, or
|
||||
installed but not running, leaves isAvailable() false: the ordinary
|
||||
case, never reported as an error.
|
||||
@param parent
|
||||
@param library : the client library; only tests pass another
|
||||
*/
|
||||
ConnexionBackend::ConnexionBackend(QObject *parent, const QString &library) :
|
||||
SpaceMouseBackend(parent),
|
||||
m_library(library)
|
||||
{
|
||||
{
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
if (instance) {
|
||||
return; //3DxWare takes one set of handlers per process
|
||||
}
|
||||
}
|
||||
if (!m_library.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto set_handlers = reinterpret_cast<SetConnexionHandlers>(
|
||||
m_library.resolve("SetConnexionHandlers"));
|
||||
const auto register_client = reinterpret_cast<RegisterConnexionClient>(
|
||||
m_library.resolve("RegisterConnexionClient"));
|
||||
const auto button_mask = reinterpret_cast<SetConnexionClientButtonMask>(
|
||||
m_library.resolve("SetConnexionClientButtonMask"));
|
||||
m_cleanup = reinterpret_cast<CleanupConnexionHandlers>(
|
||||
m_library.resolve("CleanupConnexionHandlers"));
|
||||
m_unregister = reinterpret_cast<UnregisterConnexionClient>(
|
||||
m_library.resolve("UnregisterConnexionClient"));
|
||||
if (!set_handlers || !register_client || !m_cleanup || !m_unregister) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
instance = this;
|
||||
}
|
||||
//Fails while 3DxWare is installed but its driver is not running.
|
||||
if (set_handlers(&ConnexionBackend::onMessage, deviceChanged, deviceChanged, true) != 0) {
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
m_handlers_installed = true;
|
||||
|
||||
//3DxWare only sends a client its messages while that application
|
||||
//is in front, and recognises it by its executable's name (a Pascal
|
||||
//string). Take-over mode stops 3DxWare's own actions in QET, so the
|
||||
//view never moves twice.
|
||||
QByteArray name = QFileInfo(QCoreApplication::applicationFilePath())
|
||||
.fileName().toUtf8().left(255);
|
||||
name.prepend(char(name.size()));
|
||||
m_client = register_client(SIGNATURE,
|
||||
reinterpret_cast<const quint8 *>(name.constData()),
|
||||
MODE_TAKE_OVER, MASK_ALL);
|
||||
if (!m_client) {
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
if (button_mask) {
|
||||
button_mask(m_client, MASK_ALL_BUTTONS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::~ConnexionBackend
|
||||
*/
|
||||
ConnexionBackend::~ConnexionBackend()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::sampleFromAxes
|
||||
3DxWare reports y up and z away from the user; the raw USB reports, and
|
||||
so every other backend, have y towards the user and z down. Derived from
|
||||
Blender, whose 3DxWare and spacenavd code paths must agree.
|
||||
@param axis : TX, TY, TZ, RX, RY, RZ as 3DxWare sends them
|
||||
@return the same movement in QET's convention
|
||||
*/
|
||||
SpaceMouseSample ConnexionBackend::sampleFromAxes(const qint16 axis[6])
|
||||
{
|
||||
SpaceMouseSample sample;
|
||||
sample.x = axis[0];
|
||||
sample.y = -axis[2];
|
||||
sample.z = -axis[1];
|
||||
sample.rx = axis[3];
|
||||
sample.ry = -axis[5];
|
||||
sample.rz = -axis[4];
|
||||
return sample;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::newlyPressed
|
||||
@param before : the button bitmask of the previous message
|
||||
@param now : the button bitmask of this one
|
||||
@return the 0-based buttons pressed since \a before, as HidBackend numbers them
|
||||
*/
|
||||
QList<int> ConnexionBackend::newlyPressed(quint32 before, quint32 now)
|
||||
{
|
||||
QList<int> pressed;
|
||||
const quint32 down = now & ~before;
|
||||
for (int bit = 0; bit < 32; ++bit) {
|
||||
if (down & (quint32(1) << bit)) {
|
||||
pressed.append(bit);
|
||||
}
|
||||
}
|
||||
return pressed;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::onMessage
|
||||
Runs on 3DxWare's thread. Copies what matters out of the message and
|
||||
hands it to the main thread.
|
||||
@param type : the message type
|
||||
@param argument : a ConnexionDeviceState for MSG_DEVICE_STATE
|
||||
*/
|
||||
void ConnexionBackend::onMessage(quint32, quint32 type, void *argument)
|
||||
{
|
||||
if (type != MSG_DEVICE_STATE || !argument) {
|
||||
return;
|
||||
}
|
||||
const char *state = static_cast<const char *>(argument);
|
||||
quint16 client;
|
||||
quint16 command;
|
||||
qint16 axis[6];
|
||||
quint32 buttons;
|
||||
std::memcpy(&client, state + STATE_CLIENT, sizeof client);
|
||||
std::memcpy(&command, state + STATE_COMMAND, sizeof command);
|
||||
std::memcpy(axis, state + STATE_AXIS, sizeof axis);
|
||||
std::memcpy(&buttons, state + STATE_BUTTONS, sizeof buttons);
|
||||
|
||||
//Queued with the backend as context: if it is deleted before the
|
||||
//main thread gets to it, Qt drops the call.
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
ConnexionBackend *backend = instance;
|
||||
if (!backend) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(backend, [=]() {
|
||||
backend->handleState(client, command, axis, buttons);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::handleState
|
||||
On the main thread: turn one device state into signals.
|
||||
*/
|
||||
void ConnexionBackend::handleState(quint16 client, quint16 command,
|
||||
const qint16 axis[6], quint32 buttons)
|
||||
{
|
||||
if (client != m_client) {
|
||||
return; //3DxWare sends every state to every client
|
||||
}
|
||||
if (command == CMD_HANDLE_AXIS) {
|
||||
emit motion(sampleFromAxes(axis));
|
||||
} else if (command == CMD_HANDLE_BUTTONS) {
|
||||
//State first: a button can open a dialog whose event loop
|
||||
//delivers the next message before emit returns.
|
||||
const QList<int> pressed = newlyPressed(m_buttons, buttons);
|
||||
m_buttons = buttons;
|
||||
for (int button : pressed) {
|
||||
emit buttonPressed(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::shutdown
|
||||
Unregister and stop 3DxWare's callbacks. The library stays loaded, since
|
||||
3DxWare's thread may still be returning from one.
|
||||
*/
|
||||
void ConnexionBackend::shutdown()
|
||||
{
|
||||
if (m_client && m_unregister) {
|
||||
m_unregister(m_client);
|
||||
}
|
||||
m_client = 0;
|
||||
if (m_handlers_installed && m_cleanup) {
|
||||
m_cleanup();
|
||||
}
|
||||
m_handlers_installed = false;
|
||||
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
if (instance == this) {
|
||||
instance = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CONNEXIONBACKEND_H
|
||||
#define CONNEXIONBACKEND_H
|
||||
|
||||
#include "spacemousebackend.h"
|
||||
|
||||
#include <QLibrary>
|
||||
|
||||
/**
|
||||
@brief The ConnexionBackend class
|
||||
SpaceMouseBackend that reads the device through 3Dconnexion's own macOS
|
||||
driver, 3DxWare, the way Blender does. Only compiled in on macOS
|
||||
(cmake/find_spacemouse.cmake).
|
||||
|
||||
3DxWare installs a driver extension that takes the device over, so
|
||||
with 3DxWare installed HidBackend can open the device but receives
|
||||
nothing (discussion #599). This backend asks 3DxWare for the motion
|
||||
instead. SpaceMouseListener tries it first and falls back to HidBackend
|
||||
when 3DxWare is not installed or not running, so the device works
|
||||
either way.
|
||||
|
||||
The client library, 3DconnexionClient.framework, is loaded at run time
|
||||
from where 3DxWare installs it: nothing is linked, bundled or needed to
|
||||
build. The few declarations below are written here, as Blender does,
|
||||
rather than taken from 3Dconnexion's SDK, whose headers may not be
|
||||
redistributed.
|
||||
|
||||
3DxWare delivers its messages on a thread of its own; they are passed
|
||||
to the main thread before any signal is emitted.
|
||||
*/
|
||||
class ConnexionBackend : public SpaceMouseBackend
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static const char DEFAULT_LIBRARY[];
|
||||
|
||||
explicit ConnexionBackend(QObject *parent = nullptr,
|
||||
const QString &library = QString::fromLatin1(DEFAULT_LIBRARY));
|
||||
~ConnexionBackend() override;
|
||||
|
||||
bool isAvailable() const override { return m_client != 0; }
|
||||
|
||||
/// 3DxWare's six axes, in its own order and signs, as QET's
|
||||
/// raw USB convention (+x right, +y towards the user, +z down).
|
||||
static SpaceMouseSample sampleFromAxes(const qint16 axis[6]);
|
||||
/// The 0-based buttons set in \a now but not in \a before.
|
||||
static QList<int> newlyPressed(quint32 before, quint32 now);
|
||||
|
||||
private:
|
||||
static void onMessage(quint32 connection, quint32 type, void *argument);
|
||||
void handleState(quint16 client, quint16 command,
|
||||
const qint16 axis[6], quint32 buttons);
|
||||
void shutdown();
|
||||
|
||||
QLibrary m_library;
|
||||
quint16 m_client = 0;
|
||||
bool m_handlers_installed = false;
|
||||
quint32 m_buttons = 0;
|
||||
|
||||
void (*m_cleanup)() = nullptr;
|
||||
void (*m_unregister)(quint16) = nullptr;
|
||||
};
|
||||
|
||||
#endif // CONNEXIONBACKEND_H
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
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 "hidbackend.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include <hidapi.h>
|
||||
#if defined(Q_OS_MACOS) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
|
||||
# include <hidapi_darwin.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int SCAN_INTERVAL_MS = 3000;
|
||||
constexpr int FAST_POLL_MS = 8; // devices report at up to ~125 Hz
|
||||
constexpr int SLOW_POLL_MS = 50;
|
||||
//Fast polls in a row with nothing to read before slowing down.
|
||||
constexpr int IDLE_POLLS_BEFORE_SLOW = 125;
|
||||
constexpr int MAX_REPORT_SIZE = 64;
|
||||
constexpr int MAX_DESCRIPTOR_SIZE = 4096;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief HidBackend::HidBackend
|
||||
@param parent
|
||||
*/
|
||||
HidBackend::HidBackend(QObject *parent) :
|
||||
SpaceMouseBackend(parent)
|
||||
{
|
||||
if (hid_init() != 0) {
|
||||
return;
|
||||
}
|
||||
m_hid_initialised = true;
|
||||
#if defined(Q_OS_MACOS) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
|
||||
//hidapi opens devices exclusively on macOS by default, which fails
|
||||
//while 3DxWare has the device open (discussion #599).
|
||||
hid_darwin_set_open_exclusive(0);
|
||||
#endif
|
||||
|
||||
m_read_timer = new QTimer(this);
|
||||
connect(m_read_timer, &QTimer::timeout, this, &HidBackend::readReports);
|
||||
|
||||
m_scan_timer = new QTimer(this);
|
||||
m_scan_timer->setInterval(SCAN_INTERVAL_MS);
|
||||
connect(m_scan_timer, &QTimer::timeout, this, &HidBackend::scan);
|
||||
|
||||
scan();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief HidBackend::~HidBackend
|
||||
*/
|
||||
HidBackend::~HidBackend()
|
||||
{
|
||||
close();
|
||||
if (m_hid_initialised) {
|
||||
hid_exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief HidBackend::scan
|
||||
Open the first 3D mouse found, or keep looking every few seconds.
|
||||
*/
|
||||
void HidBackend::scan()
|
||||
{
|
||||
if (m_device) {
|
||||
return;
|
||||
}
|
||||
|
||||
hid_device_info *list = hid_enumerate(0, 0);
|
||||
for (hid_device_info *info = list; info && !m_device; info = info->next)
|
||||
{
|
||||
if (!SpaceMouseHid::isSpaceMouse(info->vendor_id, info->product_id,
|
||||
info->usage_page, info->usage)) {
|
||||
continue;
|
||||
}
|
||||
m_device = hid_open_path(info->path);
|
||||
}
|
||||
hid_free_enumeration(list);
|
||||
|
||||
if (!m_device) {
|
||||
m_scan_timer->start();
|
||||
return;
|
||||
}
|
||||
m_scan_timer->stop();
|
||||
hid_set_nonblocking(m_device, 1);
|
||||
|
||||
SpaceMouseHid::Layout layout;
|
||||
#if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0)
|
||||
unsigned char descriptor[MAX_DESCRIPTOR_SIZE];
|
||||
const int size = hid_get_report_descriptor(m_device, descriptor, sizeof descriptor);
|
||||
if (size > 0) {
|
||||
layout = SpaceMouseHid::parseDescriptor(
|
||||
QByteArray(reinterpret_cast<const char *>(descriptor), size));
|
||||
}
|
||||
#endif
|
||||
if (!layout.hasAxes()) {
|
||||
layout = SpaceMouseHid::fallbackLayout();
|
||||
}
|
||||
m_decoder = SpaceMouseHid::Decoder(layout);
|
||||
|
||||
m_idle_polls = 0;
|
||||
m_read_timer->start(FAST_POLL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief HidBackend::readReports
|
||||
Drain every report waiting, emit what they mean, and adjust the polling
|
||||
rate. A read error means the device went away (unplugged, or the
|
||||
wireless receiver lost it): go back to looking for one.
|
||||
*/
|
||||
void HidBackend::readReports()
|
||||
{
|
||||
//A button can trigger an action that opens a dialog, whose event
|
||||
//loop fires this timer again before the signal returns. That is
|
||||
//fine -- the device keeps working in the dialog -- because the
|
||||
//signals are emitted last, after every use of m_device below; this
|
||||
//guard only keeps two reads from ever overlapping.
|
||||
if (m_reading || !m_device) {
|
||||
return;
|
||||
}
|
||||
m_reading = true;
|
||||
|
||||
unsigned char buffer[MAX_REPORT_SIZE];
|
||||
bool got_any = false;
|
||||
//Everything read in one poll is one moment: a device that sends
|
||||
//translation and rotation as two reports, or two samples that
|
||||
//queued up, gives one sample here -- the latest state -- so the
|
||||
//listener's time scaling sees one sample per real interval.
|
||||
bool moved = false;
|
||||
SpaceMouseSample latest;
|
||||
QList<int> pressed;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const int size = hid_read(m_device, buffer, sizeof buffer);
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
if (size < 0) {
|
||||
close();
|
||||
m_scan_timer->start();
|
||||
break;
|
||||
}
|
||||
got_any = true;
|
||||
|
||||
const SpaceMouseHid::Decoder::Result result = m_decoder.feed(
|
||||
QByteArray(reinterpret_cast<const char *>(buffer), size));
|
||||
if (result.motion) {
|
||||
moved = true;
|
||||
latest = result.sample;
|
||||
}
|
||||
pressed += result.pressed;
|
||||
}
|
||||
|
||||
if (m_device) {
|
||||
if (got_any) {
|
||||
m_idle_polls = 0;
|
||||
if (m_read_timer->interval() != FAST_POLL_MS) {
|
||||
m_read_timer->setInterval(FAST_POLL_MS);
|
||||
}
|
||||
} else if (++m_idle_polls == IDLE_POLLS_BEFORE_SLOW) {
|
||||
m_read_timer->setInterval(SLOW_POLL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
//Signals last: whatever they trigger (even the device going away
|
||||
//during a dialog), nothing after them touches the device.
|
||||
m_reading = false;
|
||||
if (moved) {
|
||||
emit motion(latest);
|
||||
}
|
||||
for (int button : pressed) {
|
||||
emit buttonPressed(button);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief HidBackend::close
|
||||
*/
|
||||
void HidBackend::close()
|
||||
{
|
||||
if (m_read_timer) {
|
||||
m_read_timer->stop();
|
||||
}
|
||||
if (m_device) {
|
||||
hid_close(m_device);
|
||||
m_device = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 HIDBACKEND_H
|
||||
#define HIDBACKEND_H
|
||||
|
||||
#include "spacemousebackend.h"
|
||||
#include "spacemousehid.h"
|
||||
|
||||
class QTimer;
|
||||
struct hid_device_;
|
||||
|
||||
/**
|
||||
@brief The HidBackend class
|
||||
SpaceMouseBackend that reads the device directly over USB through
|
||||
hidapi, with no 3Dconnexion driver or SDK: the backend for Windows and
|
||||
macOS, and for Linux without spacenavd. Only compiled in when
|
||||
QET_SPACEMOUSE_BACKEND_HID is defined (cmake/find_spacemouse.cmake).
|
||||
|
||||
The raw reports are decoded by SpaceMouseHid, from the device's own
|
||||
report descriptor, into the same values spacenavd would give.
|
||||
|
||||
hidapi has no event to wait on, so the device is polled from the main
|
||||
thread: fast while it is moving, slowly once it has been still for a
|
||||
moment. With no device, it looks for one every few seconds, so plugging
|
||||
one in works without restarting QElectroTech. As with every backend,
|
||||
no device is the normal case and is never reported as an error.
|
||||
*/
|
||||
class HidBackend : public SpaceMouseBackend
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HidBackend(QObject *parent = nullptr);
|
||||
~HidBackend() override;
|
||||
|
||||
bool isAvailable() const override { return m_device != nullptr; }
|
||||
|
||||
private slots:
|
||||
void scan();
|
||||
void readReports();
|
||||
|
||||
private:
|
||||
void close();
|
||||
|
||||
bool m_hid_initialised = false;
|
||||
hid_device_ *m_device = nullptr;
|
||||
SpaceMouseHid::Decoder m_decoder;
|
||||
QTimer *m_scan_timer = nullptr;
|
||||
QTimer *m_read_timer = nullptr;
|
||||
int m_idle_polls = 0;
|
||||
bool m_reading = false;
|
||||
};
|
||||
|
||||
#endif // HIDBACKEND_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 SPACEMOUSEBACKEND_H
|
||||
#define SPACEMOUSEBACKEND_H
|
||||
|
||||
#include "spacemousemotion.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
/**
|
||||
@brief The SpaceMouseBackend class
|
||||
Platform seam for discussion #599's 3D mouse support. A backend owns
|
||||
one platform's connection to the actual 6-DOF device driver -- opening
|
||||
it, pumping whatever event source that platform uses, closing it -- and
|
||||
reports motion through the signals below. Everything platform-
|
||||
independent (which view to apply motion to, the pan/zoom primitives to
|
||||
call, the sample-to-motion mapping) lives in SpaceMouseListener and
|
||||
SpaceMouseMotion instead, once, so it does not have to be duplicated
|
||||
or re-verified per backend.
|
||||
|
||||
Two implementations, chosen at build time (cmake/find_spacemouse.cmake):
|
||||
SpnavBackend (Linux, through spacenavd/libspnav) and HidBackend (any
|
||||
platform, directly over USB through hidapi, with no 3Dconnexion driver
|
||||
or SDK). On macOS a third, ConnexionBackend, reads through 3DxWare and
|
||||
is tried first at run time. All report the same values for the same
|
||||
movement.
|
||||
*/
|
||||
class SpaceMouseBackend : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SpaceMouseBackend(QObject *parent = nullptr) : QObject(parent) {}
|
||||
~SpaceMouseBackend() override = default;
|
||||
|
||||
/// True once this backend actually has a live connection to a
|
||||
/// driver/daemon. False is the ordinary case -- no daemon
|
||||
/// running, no device attached -- not an error; see
|
||||
/// SpaceMouseListener's class comment for why that distinction
|
||||
/// matters.
|
||||
virtual bool isAvailable() const = 0;
|
||||
|
||||
signals:
|
||||
/// One raw device sample, all six axes. Units and range are
|
||||
/// whatever the backend's own driver reports --
|
||||
/// SpaceMouseMotion::map() and the user's settings are what
|
||||
/// turn them into pixels and a zoom factor, not this signal.
|
||||
void motion(const SpaceMouseSample &sample);
|
||||
|
||||
/// One device button was pressed. \a button is whatever index
|
||||
/// the backend's own driver numbers it as -- there is no
|
||||
/// portable numbering across device models, which is exactly
|
||||
/// why the binding from a button to a QET action
|
||||
/// (SpaceMouseButtonMap) is user-configurable rather than
|
||||
/// hardcoded. Emitted on press only; release is not reported,
|
||||
/// since nothing here has a use for it.
|
||||
void buttonPressed(int button);
|
||||
};
|
||||
|
||||
#endif // SPACEMOUSEBACKEND_H
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 "spacemousebuttonmap.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
namespace {
|
||||
const QString SETTINGS_GROUP = QStringLiteral("spacemouse/buttons/");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseButtonMap::actionId
|
||||
@param button
|
||||
@return see the declaration's doc comment
|
||||
*/
|
||||
QString SpaceMouseButtonMap::actionId(int button)
|
||||
{
|
||||
QSettings settings;
|
||||
return settings.value(SETTINGS_GROUP + QString::number(button)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseButtonMap::setActionId
|
||||
@param button
|
||||
@param action_id
|
||||
*/
|
||||
void SpaceMouseButtonMap::setActionId(int button, const QString &action_id)
|
||||
{
|
||||
QSettings settings;
|
||||
const QString key = SETTINGS_GROUP + QString::number(button);
|
||||
if (action_id.isEmpty()) {
|
||||
settings.remove(key);
|
||||
} else {
|
||||
settings.setValue(key, action_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseButtonMap::allBindings
|
||||
@return see the declaration's doc comment
|
||||
*/
|
||||
QMap<int, QString> SpaceMouseButtonMap::allBindings()
|
||||
{
|
||||
QMap<int, QString> bindings;
|
||||
QSettings settings;
|
||||
settings.beginGroup(QStringLiteral("spacemouse/buttons"));
|
||||
for (const QString &key : settings.childKeys())
|
||||
{
|
||||
bool ok = false;
|
||||
const int button = key.toInt(&ok);
|
||||
if (ok) {
|
||||
bindings.insert(button, settings.value(key).toString());
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
return bindings;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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 SPACEMOUSEBUTTONMAP_H
|
||||
#define SPACEMOUSEBUTTONMAP_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
@brief The SpaceMouseButtonMap class
|
||||
Persisted mapping from a device button's raw index (backend- and
|
||||
device-specific -- see SpaceMouseBackend::buttonPressed()) to a
|
||||
ShortcutManager action id. Deliberately just a thin QSettings wrapper,
|
||||
the same weight as ShortcutManager::savedSequence(): no caching, reads
|
||||
and writes the setting directly on every call, since this is called at
|
||||
most once per button press, never in a hot loop.
|
||||
|
||||
Unbound by default for every button on every device: nothing is bound
|
||||
until the user opens Configuration > 3D Mouse and binds something --
|
||||
see spacemouseconfigpage.h. A device compiled in and connected but
|
||||
never configured here does nothing on any button press, matching the
|
||||
rest of this feature's "silent until asked for" default.
|
||||
*/
|
||||
class SpaceMouseButtonMap
|
||||
{
|
||||
public:
|
||||
/// @return the action id bound to \a button, or an empty string
|
||||
/// if nothing is bound to it.
|
||||
static QString actionId(int button);
|
||||
|
||||
/// Bind \a button to \a action_id. An empty \a action_id
|
||||
/// removes the binding (equivalent to it never having been set).
|
||||
static void setActionId(int button, const QString &action_id);
|
||||
|
||||
/// @return every currently bound button, for the configuration
|
||||
/// page to list.
|
||||
static QMap<int, QString> allBindings();
|
||||
};
|
||||
|
||||
#endif // SPACEMOUSEBUTTONMAP_H
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
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 "spacemousehid.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
constexpr unsigned short PAGE_GENERIC_DESKTOP = 0x01;
|
||||
constexpr unsigned short PAGE_BUTTON = 0x09;
|
||||
constexpr unsigned short USAGE_MULTI_AXIS = 0x08;
|
||||
constexpr unsigned short USAGE_X = 0x30; // X, Y, Z, Rx, Ry, Rz follow
|
||||
|
||||
//Newer devices (SpaceMouse Enterprise, the wireless ones) report the
|
||||
//buttons held as a list of 16-bit button numbers in this report,
|
||||
//outside the Button usage page.
|
||||
constexpr int REPORT_BUTTON_LIST = 0x1c;
|
||||
|
||||
//spacenavd rescales absolute axes to this range, so doing the same
|
||||
//makes both backends hand QET the same numbers.
|
||||
constexpr int SCALED_MIN = -500;
|
||||
constexpr int SCALED_MAX = 500;
|
||||
|
||||
//Limits on what a descriptor can make the parser walk.
|
||||
constexpr quint32 MAX_FIELDS = 1024;
|
||||
constexpr qint64 MAX_BITS = 1 << 20;
|
||||
|
||||
//3D mice sold under Logitech's vendor id, for the ones whose
|
||||
//interface does not report a usage.
|
||||
const QSet<unsigned short> LOGITECH_3D_MICE = {
|
||||
0xc603, 0xc605, 0xc606, 0xc621, 0xc623, 0xc625,
|
||||
0xc626, 0xc627, 0xc628, 0xc629, 0xc62b
|
||||
};
|
||||
|
||||
/// Read \a size bits at \a offset, little-endian as HID packs them.
|
||||
bool extract(const QByteArray &payload, int offset, int size, bool is_signed, int *value)
|
||||
{
|
||||
if (size <= 0 || size > 32 || offset < 0
|
||||
|| (offset + size + 7) / 8 > payload.size()) {
|
||||
return false;
|
||||
}
|
||||
quint32 raw = 0;
|
||||
for (int b = 0; b < size; ++b) {
|
||||
const int bit = offset + b;
|
||||
if (static_cast<quint8>(payload.at(bit / 8)) & (1u << (bit % 8))) {
|
||||
raw |= 1u << b;
|
||||
}
|
||||
}
|
||||
if (is_signed && size < 32 && (raw & (1u << (size - 1)))) {
|
||||
raw |= ~((1u << size) - 1);
|
||||
}
|
||||
*value = static_cast<qint32>(raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
int scaled(const SpaceMouseHid::Field &field, int value)
|
||||
{
|
||||
if (field.relative || field.logical_max <= field.logical_min) {
|
||||
return value;
|
||||
}
|
||||
const qint64 range = qint64(field.logical_max) - field.logical_min;
|
||||
return static_cast<int>((qint64(value) - field.logical_min)
|
||||
* (SCALED_MAX - SCALED_MIN) / range + SCALED_MIN);
|
||||
}
|
||||
}
|
||||
|
||||
bool SpaceMouseHid::isSpaceMouse(unsigned short vendor, unsigned short product,
|
||||
unsigned short usage_page, unsigned short usage)
|
||||
{
|
||||
const bool multi_axis = usage_page == PAGE_GENERIC_DESKTOP && usage == USAGE_MULTI_AXIS;
|
||||
const bool usage_unknown = usage_page == 0 && usage == 0;
|
||||
|
||||
if (vendor == VENDOR_3DCONNEXION) {
|
||||
return multi_axis || usage_unknown;
|
||||
}
|
||||
if (vendor == VENDOR_LOGITECH) {
|
||||
return multi_axis || (usage_unknown && LOGITECH_3D_MICE.contains(product));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SpaceMouseHid::Layout::hasAxes() const
|
||||
{
|
||||
for (const Field &f : axes) {
|
||||
if (f.isValid()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseHid::parseDescriptor
|
||||
Walks the short items of a HID report descriptor (HID 1.11, 6.2.2),
|
||||
keeping only what the decoder needs: the Input fields carrying the six
|
||||
Generic Desktop axes and the Button page.
|
||||
@param descriptor
|
||||
@return see the declaration
|
||||
*/
|
||||
SpaceMouseHid::Layout SpaceMouseHid::parseDescriptor(const QByteArray &descriptor)
|
||||
{
|
||||
struct Globals {
|
||||
quint32 usage_page = 0;
|
||||
qint32 logical_min = 0;
|
||||
qint32 logical_max = 0;
|
||||
quint32 report_size = 0;
|
||||
quint32 report_count = 0;
|
||||
quint32 report_id = 0;
|
||||
};
|
||||
|
||||
Layout layout;
|
||||
Globals globals;
|
||||
QList<Globals> stack;
|
||||
QList<quint32> usages; // full 32-bit usages (page << 16 | id)
|
||||
quint32 usage_min = 0, usage_max = 0;
|
||||
bool have_range = false;
|
||||
QHash<quint32, int> next_bit; // per report id
|
||||
|
||||
auto clearLocals = [&]() {
|
||||
usages.clear();
|
||||
have_range = false;
|
||||
usage_min = usage_max = 0;
|
||||
};
|
||||
auto fullUsage = [&](quint32 value, int size) {
|
||||
return size == 4 ? value : (globals.usage_page << 16) | value;
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
const int n = descriptor.size();
|
||||
while (i < n)
|
||||
{
|
||||
const quint8 prefix = static_cast<quint8>(descriptor.at(i));
|
||||
if (prefix == 0xfe) { // long item: never used by these devices
|
||||
if (i + 1 >= n) break;
|
||||
i += 3 + static_cast<quint8>(descriptor.at(i + 1));
|
||||
continue;
|
||||
}
|
||||
const int size = (prefix & 0x03) == 3 ? 4 : (prefix & 0x03);
|
||||
const int type = (prefix >> 2) & 0x03;
|
||||
const int tag = prefix >> 4;
|
||||
if (i + 1 + size > n) break;
|
||||
|
||||
quint32 uvalue = 0;
|
||||
for (int b = 0; b < size; ++b) {
|
||||
uvalue |= quint32(static_cast<quint8>(descriptor.at(i + 1 + b))) << (8 * b);
|
||||
}
|
||||
qint32 svalue = static_cast<qint32>(uvalue);
|
||||
if (size > 0 && size < 4 && (uvalue & (1u << (8 * size - 1)))) {
|
||||
svalue = static_cast<qint32>(uvalue | ~((1u << (8 * size)) - 1));
|
||||
}
|
||||
i += 1 + size;
|
||||
|
||||
if (type == 1) // global
|
||||
{
|
||||
switch (tag) {
|
||||
case 0: globals.usage_page = uvalue; break;
|
||||
case 1: globals.logical_min = svalue; break;
|
||||
case 2: globals.logical_max = svalue; break;
|
||||
case 7: globals.report_size = uvalue; break;
|
||||
case 8: globals.report_id = uvalue; layout.numbered_reports = true; break;
|
||||
case 9: globals.report_count = uvalue; break;
|
||||
case 10: stack.append(globals); break;
|
||||
case 11: if (!stack.isEmpty()) globals = stack.takeLast(); break;
|
||||
}
|
||||
}
|
||||
else if (type == 2) // local
|
||||
{
|
||||
switch (tag) {
|
||||
case 0: usages.append(fullUsage(uvalue, size)); break;
|
||||
case 1: usage_min = fullUsage(uvalue, size); have_range = true; break;
|
||||
case 2: usage_max = fullUsage(uvalue, size); have_range = true; break;
|
||||
}
|
||||
}
|
||||
else if (type == 0) // main
|
||||
{
|
||||
if (tag == 8) // Input
|
||||
{
|
||||
const bool constant = uvalue & 0x01;
|
||||
const bool variable = uvalue & 0x02;
|
||||
const bool relative = uvalue & 0x04;
|
||||
int &bit = next_bit[globals.report_id];
|
||||
//A real report is at most a few hundred bytes; a count
|
||||
//beyond that is a broken (or hostile) descriptor, and
|
||||
//walking it field by field would stall the application.
|
||||
const quint32 count = qMin<quint32>(globals.report_count, MAX_FIELDS);
|
||||
|
||||
for (quint32 k = 0; k < count; ++k)
|
||||
{
|
||||
quint32 usage = 0;
|
||||
if (have_range && usage_max >= usage_min) {
|
||||
usage = qMin(usage_min + k, usage_max);
|
||||
} else if (!usages.isEmpty()) {
|
||||
usage = usages.at(qMin<int>(k, usages.size() - 1));
|
||||
}
|
||||
|
||||
Field field;
|
||||
field.report_id = static_cast<int>(globals.report_id);
|
||||
field.bit_offset = bit + static_cast<int>(k * globals.report_size);
|
||||
field.bit_size = static_cast<int>(globals.report_size);
|
||||
field.logical_min = globals.logical_min;
|
||||
field.logical_max = globals.logical_max;
|
||||
field.relative = relative;
|
||||
|
||||
if (!constant && variable) {
|
||||
const quint32 page = usage >> 16, id = usage & 0xffff;
|
||||
if (page == PAGE_GENERIC_DESKTOP && id >= USAGE_X && id < USAGE_X + 6) {
|
||||
layout.axes[id - USAGE_X] = field;
|
||||
} else if (page == PAGE_BUTTON && id >= 1 && id <= 64) {
|
||||
if (layout.buttons.size() < int(id)) {
|
||||
layout.buttons.resize(id);
|
||||
}
|
||||
layout.buttons[id - 1] = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
bit = static_cast<int>(qMin<qint64>(
|
||||
bit + qint64(count) * globals.report_size, MAX_BITS));
|
||||
}
|
||||
clearLocals(); // every main item (Input, Collection, ...) ends the locals
|
||||
}
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
SpaceMouseHid::Layout SpaceMouseHid::fallbackLayout()
|
||||
{
|
||||
Layout layout;
|
||||
layout.numbered_reports = true;
|
||||
for (int a = 0; a < 6; ++a) {
|
||||
Field &f = layout.axes[a];
|
||||
f.report_id = a < 3 ? 1 : 2;
|
||||
f.bit_offset = (a % 3) * 16;
|
||||
f.bit_size = 16;
|
||||
f.logical_min = -32768; // signed, and relative: passed through as sent
|
||||
f.logical_max = 32767;
|
||||
f.relative = true;
|
||||
}
|
||||
layout.buttons.resize(32);
|
||||
for (int b = 0; b < 32; ++b) {
|
||||
Field &f = layout.buttons[b];
|
||||
f.report_id = 3;
|
||||
f.bit_offset = b;
|
||||
f.bit_size = 1;
|
||||
f.logical_max = 1;
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
SpaceMouseHid::Decoder::Decoder(const Layout &layout) :
|
||||
m_layout(layout)
|
||||
{}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseHid::Decoder::feed
|
||||
@param report : one report as read from the device, report id first
|
||||
when the device numbers its reports
|
||||
@return see Result
|
||||
*/
|
||||
SpaceMouseHid::Decoder::Result SpaceMouseHid::Decoder::feed(const QByteArray &report)
|
||||
{
|
||||
Result result;
|
||||
if (report.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const int id = m_layout.numbered_reports ? static_cast<quint8>(report.at(0)) : 0;
|
||||
const QByteArray payload = m_layout.numbered_reports ? report.mid(1) : report;
|
||||
|
||||
for (int a = 0; a < 6; ++a)
|
||||
{
|
||||
const Field &f = m_layout.axes[a];
|
||||
int value = 0;
|
||||
if (f.isValid() && f.report_id == id
|
||||
&& extract(payload, f.bit_offset, f.bit_size, f.logical_min < 0, &value)) {
|
||||
m_axes[a] = scaled(f, value);
|
||||
result.motion = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Some devices send rotation after translation in one longer report
|
||||
//1, while their layout (or the fallback, which has no descriptor)
|
||||
//puts rotation in a report of its own: read it from report 1 too.
|
||||
if (id == 1 && payload.size() >= 12
|
||||
&& m_layout.axes[3].isValid() && m_layout.axes[3].report_id != 1) {
|
||||
for (int a = 3; a < 6; ++a) {
|
||||
const Field &f = m_layout.axes[a];
|
||||
int value = 0;
|
||||
if (extract(payload, 48 + f.bit_offset, f.bit_size, f.logical_min < 0, &value)) {
|
||||
m_axes[a] = scaled(f, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSet<int> down;
|
||||
bool buttons_in_report = false;
|
||||
for (int b = 0; b < m_layout.buttons.size(); ++b)
|
||||
{
|
||||
const Field &f = m_layout.buttons.at(b);
|
||||
int value = 0;
|
||||
if (f.isValid() && f.report_id == id
|
||||
&& extract(payload, f.bit_offset, f.bit_size, false, &value)) {
|
||||
buttons_in_report = true;
|
||||
if (value) {
|
||||
down.insert(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!buttons_in_report && m_layout.numbered_reports && id == REPORT_BUTTON_LIST)
|
||||
{
|
||||
buttons_in_report = true;
|
||||
for (int offset = 0; offset + 1 < payload.size(); offset += 2) {
|
||||
const int number = static_cast<quint8>(payload.at(offset))
|
||||
| static_cast<quint8>(payload.at(offset + 1)) << 8;
|
||||
//0 means no button, so the numbers start at 1: count from
|
||||
//0 like the Button page and spacenavd do.
|
||||
if (number) {
|
||||
down.insert(number - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buttons_in_report)
|
||||
{
|
||||
for (int b : down) {
|
||||
if (!m_down.contains(b)) {
|
||||
result.pressed.append(b);
|
||||
}
|
||||
}
|
||||
std::sort(result.pressed.begin(), result.pressed.end());
|
||||
m_down = down;
|
||||
}
|
||||
|
||||
if (result.motion) {
|
||||
result.sample.x = m_axes[0];
|
||||
result.sample.y = m_axes[1];
|
||||
result.sample.z = m_axes[2];
|
||||
result.sample.rx = m_axes[3];
|
||||
result.sample.ry = m_axes[4];
|
||||
result.sample.rz = m_axes[5];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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 SPACEMOUSEHID_H
|
||||
#define SPACEMOUSEHID_H
|
||||
|
||||
#include "spacemousemotion.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
/**
|
||||
Decoding of a 3D mouse's raw USB HID reports, for the backends that read
|
||||
the device directly (HidBackend) rather than through a driver that has
|
||||
already decoded them (spacenavd). No I/O here, so it is unit-tested from
|
||||
recorded bytes (tests/qttest/tst_spacemousehid).
|
||||
*/
|
||||
namespace SpaceMouseHid
|
||||
{
|
||||
/// USB vendor ids 3Dconnexion devices use: Logitech's for the older
|
||||
/// ones, 3Dconnexion's own for everything since about 2011.
|
||||
constexpr unsigned short VENDOR_LOGITECH = 0x046d;
|
||||
constexpr unsigned short VENDOR_3DCONNEXION = 0x256f;
|
||||
|
||||
/// @return whether a HID interface is a 3D mouse: a 3Dconnexion
|
||||
/// vendor id and the Generic Desktop "multi-axis controller" usage.
|
||||
/// Logitech devices that do not report a usage are matched by
|
||||
/// product id instead, from the list of their 3D mice.
|
||||
bool isSpaceMouse(unsigned short vendor, unsigned short product,
|
||||
unsigned short usage_page, unsigned short usage);
|
||||
|
||||
/// Where one value sits in the reports, per the report descriptor.
|
||||
struct Field
|
||||
{
|
||||
int report_id = 0; ///< 0 when the device does not number its reports
|
||||
int bit_offset = 0; ///< after the report id byte, if any
|
||||
int bit_size = 0;
|
||||
int logical_min = 0;
|
||||
int logical_max = 0;
|
||||
bool relative = false;
|
||||
|
||||
bool isValid() const { return bit_size > 0; }
|
||||
};
|
||||
|
||||
/// What the report descriptor says about the six axes and the buttons.
|
||||
struct Layout
|
||||
{
|
||||
Field axes[6]; ///< x, y, z, rx, ry, rz
|
||||
QVector<Field> buttons; ///< button n (0-based) is buttons[n]
|
||||
bool numbered_reports = false;
|
||||
|
||||
bool hasAxes() const;
|
||||
};
|
||||
|
||||
/// @return the layout described by \a descriptor, or one where
|
||||
/// hasAxes() is false if it describes no axes (or is empty)
|
||||
Layout parseDescriptor(const QByteArray &descriptor);
|
||||
|
||||
/// @return the fixed layout of the classic 3Dconnexion reports
|
||||
/// (1 = translation, 2 = rotation, 3 = button bitmask), used when
|
||||
/// the descriptor cannot be read
|
||||
Layout fallbackLayout();
|
||||
|
||||
/**
|
||||
@brief The Decoder class
|
||||
Turns a stream of raw reports into samples and button presses.
|
||||
A device can send translation and rotation in separate reports, so
|
||||
the decoder keeps the last value of every axis and reports all six
|
||||
each time any of them changes.
|
||||
*/
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
bool motion = false; ///< sample is new
|
||||
SpaceMouseSample sample;
|
||||
QList<int> pressed; ///< buttons pressed since the previous report
|
||||
};
|
||||
|
||||
explicit Decoder(const Layout &layout = fallbackLayout());
|
||||
Result feed(const QByteArray &report);
|
||||
|
||||
private:
|
||||
Layout m_layout;
|
||||
int m_axes[6] = {0, 0, 0, 0, 0, 0};
|
||||
QSet<int> m_down; ///< buttons currently held
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SPACEMOUSEHID_H
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
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 "spacemouselistener.h"
|
||||
|
||||
#include "spacemousebackend.h"
|
||||
#include "spacemousebuttonmap.h"
|
||||
#ifdef QET_SPACEMOUSE_BACKEND_SPNAV
|
||||
# include "spnavbackend.h"
|
||||
#endif
|
||||
#ifdef QET_SPACEMOUSE_BACKEND_HID
|
||||
# include "hidbackend.h"
|
||||
#endif
|
||||
#ifdef QET_SPACEMOUSE_BACKEND_CONNEXION
|
||||
# include "connexionbackend.h"
|
||||
#endif
|
||||
|
||||
#include "../diagramview.h"
|
||||
#include "../editor/elementview.h"
|
||||
#include "../editor/ui/qetelementeditor.h"
|
||||
#include "../projectview.h"
|
||||
#include "../qetdiagrameditor.h"
|
||||
#include "../shortcutmanager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QScrollBar>
|
||||
|
||||
/**
|
||||
@brief SpaceMouseListener::SpaceMouseListener
|
||||
Construct whichever backend is available for this platform and connect
|
||||
its motion() signal. If none is compiled in, or the one that is can't
|
||||
reach a device, isAvailable() simply stays false -- see the class
|
||||
comment.
|
||||
@param parent
|
||||
*/
|
||||
SpaceMouseListener::SpaceMouseListener(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_settings(SpaceMouseSettings::load())
|
||||
{
|
||||
#if defined(QET_SPACEMOUSE_BACKEND_CONNEXION)
|
||||
//When 3DxWare is installed and running it has the device to
|
||||
//itself, so ask it first; otherwise read the device directly.
|
||||
auto *connexion = new ConnexionBackend(this);
|
||||
if (connexion->isAvailable()) {
|
||||
m_backend = connexion;
|
||||
} else {
|
||||
delete connexion;
|
||||
}
|
||||
#endif
|
||||
#if defined(QET_SPACEMOUSE_BACKEND_SPNAV)
|
||||
if (!m_backend) {
|
||||
m_backend = new SpnavBackend(this);
|
||||
}
|
||||
#elif defined(QET_SPACEMOUSE_BACKEND_HID)
|
||||
if (!m_backend) {
|
||||
m_backend = new HidBackend(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_backend) {
|
||||
connect(m_backend, &SpaceMouseBackend::motion,
|
||||
this, &SpaceMouseListener::applyMotion);
|
||||
connect(m_backend, &SpaceMouseBackend::buttonPressed,
|
||||
this, &SpaceMouseListener::applyButton);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseListener::isAvailable
|
||||
@return whether the platform backend has a live device connection
|
||||
*/
|
||||
bool SpaceMouseListener::isAvailable() const
|
||||
{
|
||||
return m_backend && m_backend->isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseListener::reloadSettings
|
||||
*/
|
||||
void SpaceMouseListener::reloadSettings()
|
||||
{
|
||||
m_settings = SpaceMouseSettings::load();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseListener::applyMotion
|
||||
Apply one motion sample to the view of the active window: the current
|
||||
folio of a diagram editor, or the drawing of an element editor.
|
||||
Translation pans it and push/pull (or twist, per the user's settings)
|
||||
zooms it -- the same two primitives (scrollbars, zoom()) each view's
|
||||
wheelEvent() already drives from a physical wheel, so there is no new
|
||||
navigation logic here, only a new input source feeding the existing one.
|
||||
@param sample
|
||||
*/
|
||||
void SpaceMouseListener::applyMotion(const SpaceMouseSample &sample)
|
||||
{
|
||||
const qint64 elapsed_ms = m_since_last_sample.isValid()
|
||||
? m_since_last_sample.restart()
|
||||
: -1;
|
||||
if (!m_since_last_sample.isValid()) {
|
||||
m_since_last_sample.start();
|
||||
}
|
||||
if (elapsed_ms < 0 || elapsed_ms > SpaceMouseMotion::MAX_PERIOD_MS) {
|
||||
//the device was at rest: nothing left over to carry on with
|
||||
m_scroll_remainder = QPointF();
|
||||
}
|
||||
|
||||
const SpaceMouseViewMotion motion =
|
||||
SpaceMouseMotion::map(sample, elapsed_ms, m_settings);
|
||||
const bool pans = motion.scroll_x != 0 || motion.scroll_y != 0;
|
||||
const bool zooms = motion.zoom_factor != 1.0;
|
||||
QWidget *window = qApp->activeWindow();
|
||||
|
||||
if (auto *editor = qobject_cast<QETDiagramEditor *>(window))
|
||||
{
|
||||
ProjectView *project_view = editor->currentProjectView();
|
||||
if (!project_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
DiagramView *view = project_view->currentDiagram();
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pans) {
|
||||
scrollView(view, motion.scroll_x, motion.scroll_y);
|
||||
}
|
||||
if (zooms) {
|
||||
view->zoom(motion.zoom_factor);
|
||||
}
|
||||
}
|
||||
else if (auto *element_editor = qobject_cast<QETElementEditor *>(window))
|
||||
{
|
||||
ElementView *view = element_editor->elementView();
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pans)
|
||||
{
|
||||
//The element editor's scene rect only just covers what is
|
||||
//on screen, so grow it before each sample, as its own
|
||||
//middle-button pan does on release -- otherwise the
|
||||
//scrollbars have no range and the pan does nothing.
|
||||
view->adjustSceneRect();
|
||||
scrollView(view, motion.scroll_x, motion.scroll_y);
|
||||
}
|
||||
if (zooms) {
|
||||
view->zoom(motion.zoom_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseListener::scrollView
|
||||
Move a view's scrollbars by (\a dx, \a dy) pixels -- the same way both
|
||||
editors' own middle-button drag pans them -- keeping the fractional part
|
||||
for the next sample.
|
||||
@param view
|
||||
@param dx
|
||||
@param dy
|
||||
*/
|
||||
void SpaceMouseListener::scrollView(QGraphicsView *view, qreal dx, qreal dy)
|
||||
{
|
||||
m_scroll_remainder += QPointF(dx, dy);
|
||||
const int whole_x = qRound(m_scroll_remainder.x());
|
||||
const int whole_y = qRound(m_scroll_remainder.y());
|
||||
m_scroll_remainder -= QPointF(whole_x, whole_y);
|
||||
|
||||
view->horizontalScrollBar()->setValue(view->horizontalScrollBar()->value() + whole_x);
|
||||
view->verticalScrollBar()->setValue(view->verticalScrollBar()->value() + whole_y);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseListener::applyButton
|
||||
@param button
|
||||
*/
|
||||
void SpaceMouseListener::applyButton(int button)
|
||||
{
|
||||
const QString action_id = SpaceMouseButtonMap::actionId(button);
|
||||
if (!action_id.isEmpty()) {
|
||||
ShortcutManager::instance().trigger(action_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
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 SPACEMOUSELISTENER_H
|
||||
#define SPACEMOUSELISTENER_H
|
||||
|
||||
#include "spacemousemotion.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QObject>
|
||||
#include <QPointF>
|
||||
|
||||
class QGraphicsView;
|
||||
class SpaceMouseBackend;
|
||||
|
||||
/**
|
||||
@brief The SpaceMouseListener class
|
||||
https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/599 :
|
||||
bridges a 3Dconnexion SpaceMouse/SpacePilot 6-DOF device to the
|
||||
existing pan/zoom primitives of DiagramView and ElementView (the same
|
||||
horizontalScrollBar()/verticalScrollBar()/zoom() calls their
|
||||
wheelEvent() already uses for a physical wheel), and its
|
||||
buttons to named QET actions via ShortcutManager -- the same registry
|
||||
keyboard shortcuts already use, so a device button can trigger anything
|
||||
in that registry (undo, redo, rotate selection, ...) without QET having
|
||||
a second, device-specific action list.
|
||||
|
||||
Everything here is platform-independent: which view to apply
|
||||
motion to, the pan/zoom calls, the sample-to-motion mapping
|
||||
(SpaceMouseMotion, tuned by the user's SpaceMouseSettings), and button
|
||||
dispatch via SpaceMouseButtonMap + ShortcutManager. Talking to the
|
||||
actual device driver is a SpaceMouseBackend's job (see its class
|
||||
comment) -- this class owns one and applies whatever it reports,
|
||||
without knowing or caring which platform it came from.
|
||||
|
||||
Only compiled in when QET_SPACEMOUSE_SUPPORT is defined. Even then,
|
||||
constructing one is always safe: if no backend is available for this
|
||||
platform, or the one that exists can't reach a driver/daemon (no
|
||||
device attached -- the expected state for the overwhelming majority of
|
||||
users, even of a build with the option on), this silently does nothing
|
||||
rather than failing or nagging the user. There is exactly one of
|
||||
these, owned by QETApp, because a physical 6-DOF device is a single
|
||||
ambient input source for the whole application, not something tied to
|
||||
one window.
|
||||
*/
|
||||
class SpaceMouseListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SpaceMouseListener(QObject *parent = nullptr);
|
||||
~SpaceMouseListener() override = default;
|
||||
|
||||
/// True once the platform backend has a live connection to a
|
||||
/// driver/daemon. False is the common case, not an error -- see
|
||||
/// the class comment -- so callers should not warn the user
|
||||
/// when this is false.
|
||||
bool isAvailable() const;
|
||||
|
||||
/// Re-read SpaceMouseSettings, after the configuration page
|
||||
/// saved new ones.
|
||||
void reloadSettings();
|
||||
|
||||
private slots:
|
||||
/// Apply one motion sample -- from whichever backend is in use
|
||||
/// -- to the view of the active diagram or element editor.
|
||||
void applyMotion(const SpaceMouseSample &sample);
|
||||
|
||||
/// Look up which action id, if any, SpaceMouseButtonMap binds
|
||||
/// \a button to, and trigger it via ShortcutManager. Does
|
||||
/// nothing for an unbound button -- see SpaceMouseButtonMap's
|
||||
/// class comment on the "unbound by default" contract.
|
||||
void applyButton(int button);
|
||||
|
||||
private:
|
||||
void scrollView(QGraphicsView *view, qreal dx, qreal dy);
|
||||
|
||||
SpaceMouseBackend *m_backend = nullptr;
|
||||
SpaceMouseSettings m_settings;
|
||||
/// Time since the previous motion sample -- see
|
||||
/// SpaceMouseMotion::stepFor().
|
||||
QElapsedTimer m_since_last_sample;
|
||||
/// Fractions of a pixel not yet scrolled. Scrollbars only take
|
||||
/// whole pixels; without this a slow push that maps to under
|
||||
/// half a pixel per sample would never move the view at all.
|
||||
QPointF m_scroll_remainder;
|
||||
};
|
||||
|
||||
#endif // SPACEMOUSELISTENER_H
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
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 "spacemousemotion.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QtMath>
|
||||
|
||||
namespace {
|
||||
//A device deflection is an integer on roughly the same order of
|
||||
//magnitude as a QWheelEvent::angleDelta() tick (about +-120 per
|
||||
//detent, more under a hard push/twist -- exact range depends on the
|
||||
//backend and the driver's own sensitivity). DiagramView::wheelEvent()
|
||||
//turns such a tick into a zoom step of about 1 + value/1000, reused
|
||||
//here as a starting point.
|
||||
//
|
||||
//Neither constant has been calibrated against real hardware; the
|
||||
//user-facing speed settings scale both.
|
||||
constexpr qreal ZOOM_DIVISOR = 1000.0;
|
||||
constexpr qreal PAN_SCALE = 1.0;
|
||||
|
||||
const QString GROUP = QStringLiteral("spacemouse/motion/");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseSettings::load
|
||||
@return the settings saved by the configuration page, or the defaults
|
||||
*/
|
||||
SpaceMouseSettings SpaceMouseSettings::load()
|
||||
{
|
||||
QSettings settings;
|
||||
SpaceMouseSettings s;
|
||||
s.pan_speed = settings.value(GROUP + "pan_speed", s.pan_speed).toInt();
|
||||
s.zoom_speed = settings.value(GROUP + "zoom_speed", s.zoom_speed).toInt();
|
||||
s.dead_zone = settings.value(GROUP + "dead_zone", s.dead_zone).toInt();
|
||||
s.invert_pan_x = settings.value(GROUP + "invert_pan_x", s.invert_pan_x).toBool();
|
||||
s.invert_pan_y = settings.value(GROUP + "invert_pan_y", s.invert_pan_y).toBool();
|
||||
s.invert_zoom = settings.value(GROUP + "invert_zoom", s.invert_zoom).toBool();
|
||||
s.zoom_axis = settings.value(GROUP + "zoom_axis").toString() == QLatin1String("twist")
|
||||
? ZoomAxis::Twist
|
||||
: ZoomAxis::PushPull;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseSettings::save
|
||||
*/
|
||||
void SpaceMouseSettings::save() const
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue(GROUP + "pan_speed", pan_speed);
|
||||
settings.setValue(GROUP + "zoom_speed", zoom_speed);
|
||||
settings.setValue(GROUP + "dead_zone", dead_zone);
|
||||
settings.setValue(GROUP + "invert_pan_x", invert_pan_x);
|
||||
settings.setValue(GROUP + "invert_pan_y", invert_pan_y);
|
||||
settings.setValue(GROUP + "invert_zoom", invert_zoom);
|
||||
settings.setValue(GROUP + "zoom_axis",
|
||||
zoom_axis == ZoomAxis::Twist ? QStringLiteral("twist")
|
||||
: QStringLiteral("push_pull"));
|
||||
}
|
||||
|
||||
bool SpaceMouseSettings::operator==(const SpaceMouseSettings &other) const
|
||||
{
|
||||
return pan_speed == other.pan_speed
|
||||
&& zoom_speed == other.zoom_speed
|
||||
&& dead_zone == other.dead_zone
|
||||
&& invert_pan_x == other.invert_pan_x
|
||||
&& invert_pan_y == other.invert_pan_y
|
||||
&& invert_zoom == other.invert_zoom
|
||||
&& zoom_axis == other.zoom_axis;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseMotion::stepFor
|
||||
@param elapsed_ms
|
||||
@return see the declaration
|
||||
*/
|
||||
qreal SpaceMouseMotion::stepFor(qint64 elapsed_ms)
|
||||
{
|
||||
if (elapsed_ms < 0 || elapsed_ms > MAX_PERIOD_MS) {
|
||||
return 1.0;
|
||||
}
|
||||
return elapsed_ms / NOMINAL_PERIOD_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseMotion::applyDeadZone
|
||||
@param value
|
||||
@param dead_zone
|
||||
@return see the declaration
|
||||
*/
|
||||
int SpaceMouseMotion::applyDeadZone(int value, int dead_zone)
|
||||
{
|
||||
if (dead_zone <= 0) {
|
||||
return value;
|
||||
}
|
||||
if (qAbs(value) <= dead_zone) {
|
||||
return 0;
|
||||
}
|
||||
return value > 0 ? value - dead_zone : value + dead_zone;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SpaceMouseMotion::map
|
||||
Pan is a scrollbar delta: pushing the cap right moves the drawing right,
|
||||
so the scrollbar goes the other way, as in DiagramView::wheelEvent().
|
||||
Zoom is exponential in the deflection, so the factor is always positive
|
||||
and an equal push and pull cancel out exactly.
|
||||
@param sample
|
||||
@param elapsed_ms
|
||||
@param settings
|
||||
@return see the declaration
|
||||
*/
|
||||
SpaceMouseViewMotion SpaceMouseMotion::map(const SpaceMouseSample &sample,
|
||||
qint64 elapsed_ms,
|
||||
const SpaceMouseSettings &settings)
|
||||
{
|
||||
const qreal step = stepFor(elapsed_ms);
|
||||
const int x = applyDeadZone(sample.x, settings.dead_zone);
|
||||
const int y = applyDeadZone(sample.y, settings.dead_zone);
|
||||
const int zoom_value = applyDeadZone(
|
||||
settings.zoom_axis == SpaceMouseSettings::ZoomAxis::Twist ? sample.rz : sample.z,
|
||||
settings.dead_zone);
|
||||
|
||||
const qreal pan = PAN_SCALE * settings.pan_speed / 100.0 * step;
|
||||
const qreal zoom = settings.zoom_speed / 100.0 / ZOOM_DIVISOR * step;
|
||||
|
||||
SpaceMouseViewMotion motion;
|
||||
motion.scroll_x = -x * pan * (settings.invert_pan_x ? -1 : 1);
|
||||
motion.scroll_y = -y * pan * (settings.invert_pan_y ? -1 : 1);
|
||||
motion.zoom_factor = qExp(zoom_value * zoom * (settings.invert_zoom ? -1 : 1));
|
||||
return motion;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user