Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d889a384aa | |||
| ed0f800ab5 | |||
| 6d8459d647 | |||
| 28acbadaf0 | |||
| 4456d08b91 | |||
| aae7a8bde3 | |||
| 4bcf5834e4 | |||
| f6889af99c | |||
| 9e5af6440b | |||
| b97bccecd3 | |||
| 14cf3e403f | |||
| 1f0ed43006 | |||
| fc2a200ca2 | |||
| ccf96e0537 | |||
| fa213d90d9 | |||
| 84add7b3ef | |||
| 36fb048a80 | |||
| a7ae17a1b4 | |||
| a1675d7dea | |||
| 13a37e1f59 | |||
| 6fc7e4a090 | |||
| 08b83f23e3 | |||
| 3fa5e0a475 | |||
| 8334a9a27f | |||
| 4a636b14e3 | |||
| f777be05b4 | |||
| 8ee3f90047 | |||
| 41e1e8172c | |||
| 1212f48c6d | |||
| e01f46c5b0 | |||
| 3cec02b3f3 | |||
| a2e384761c | |||
| 4283ddad10 | |||
| 1046b97080 | |||
| a766f9a975 | |||
| 68503e2a7d | |||
| 3b05f5cf13 | |||
| 2906a48aab | |||
| a542b911e9 | |||
| 9cee71bde2 | |||
| 687837d7a3 | |||
| ea1f65107e | |||
| 6380e2a46b | |||
| 219273a8e5 | |||
| 8a2d31d835 | |||
| a8f883601d | |||
| cd27912605 | |||
| f087820757 | |||
| 2772b690e8 | |||
| 279b001a16 | |||
| 59cc758b7f | |||
| 0c958d3b66 | |||
| 528d33792d | |||
| fc41cf9f5d | |||
| 7f04bc1572 | |||
| 27bbf51024 | |||
| 65299e1178 | |||
| 8e2a29deaf | |||
| 5b739c3338 | |||
| a1e4e153ae | |||
| 56f60be60a | |||
| 852f581206 | |||
| 3b626a7d1a | |||
| 069a75d44b | |||
| e1f887a036 | |||
| c5edd0a54b | |||
| 3a13287ba9 | |||
| 24776bfcf6 | |||
| 3202c145e8 | |||
| 4888ae87f3 | |||
| 59344eb565 | |||
| 5d033bf1b2 | |||
| 8985babfe7 | |||
| 29d16c3337 | |||
| 61f5e5e502 | |||
| 77bc9ed8e4 | |||
| 9b26d5dc6a | |||
| b76d8ce8a1 | |||
| d7052e396b | |||
| e123c55754 | |||
| 268aba60eb | |||
| 313f9533a8 | |||
| 3d1cb671c1 | |||
| c0fc093b4e | |||
| 46c35d2297 | |||
| 2adc58c1c0 | |||
| 82262c5980 | |||
| 6dcb6a2a8f | |||
| c740cdf1ac | |||
| 78caeaec48 | |||
| aab0a18506 | |||
| ebab58e4b3 | |||
| 45ca53c7a8 | |||
| 2efce1752d | |||
| 27dea3ffab | |||
| c256e2dd1a | |||
| 8e3a7a6ab6 | |||
| 740271c993 | |||
| 1bec2700bd | |||
| 268e9c7bd9 | |||
| 264a0f677f | |||
| 9ccfb5e116 | |||
| 04463f0954 | |||
| 3245919c19 | |||
| 55ad2eccbf | |||
| 8b2548d601 | |||
| c3aeb0bf83 | |||
| 16d89f64fd | |||
| ac78c51b70 | |||
| 2e27f77b28 | |||
| 83fb525e27 | |||
| d1808c7a89 | |||
| 4e4f029d2d | |||
| 242f134e0f | |||
| 2dc88df29c | |||
| fd8135264f | |||
| 827cd2a91e | |||
| cdd25189b5 | |||
| f1313f5895 | |||
| 75fafd5acc | |||
| 3394c1246c | |||
| 3e34a6d55f | |||
| 50792ba1ad | |||
| 6404612014 | |||
| eaf15faaa3 | |||
| b8c9e670c4 | |||
| 85dc638a42 | |||
| dfe56cddbd | |||
| 3f397f5f78 | |||
| 7ec13cbc1a | |||
| ef1fbd79b4 | |||
| eb7abdfbc6 | |||
| f574d28340 | |||
| 31f90e2c46 | |||
| 45bd8256b3 | |||
| 4e43cde063 | |||
| aef56fe41d | |||
| 0395f90f75 | |||
| a4d5b7a12a | |||
| ba29dc45a0 | |||
| d06c7be606 | |||
| 56cbd6e23d | |||
| 8bbc2da5c7 | |||
| 7fa1ff35b8 | |||
| 4b8db6be0e | |||
| a6b4c3c673 | |||
| 8f0ee06a4f | |||
| 7e3420b1a5 | |||
| 183035d5ac | |||
| eb4109bb11 | |||
| 717bf57677 | |||
| 861e5de25e | |||
| bf7bff595e | |||
| 70599c4ae1 | |||
| e42ebdc861 | |||
| 3c1fb2fe72 | |||
| 95d0e523fe | |||
| 81449faffd |
@@ -1 +0,0 @@
|
|||||||
*.qch filter=lfs diff=lfs merge=lfs -text
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
name: Auto-build doxygen docs
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
doxygen:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
show-progress: ''
|
|
||||||
- name: Setup and run doxygen
|
|
||||||
run: sudo apt update && sudo apt install doxygen graphviz qhelpgenerator-qt5 -y
|
|
||||||
- name: Set up Git LFS
|
|
||||||
run: |
|
|
||||||
git lfs install
|
|
||||||
git lfs track "*.qch"
|
|
||||||
- name: Run doxygen
|
|
||||||
run: doxygen Doxyfile
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v8
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.MR_TOKEN }}
|
|
||||||
commit-message: update QCH file
|
|
||||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
|
||||||
title: Update QCH Help file
|
|
||||||
body: |
|
|
||||||
- Updating QT Help file following commit ${{ github.sha }}.
|
|
||||||
- Auto-generated by [create-pull-request][1]
|
|
||||||
|
|
||||||
[1]: https://github.com/peter-evans/create-pull-request
|
|
||||||
branch: update-qch
|
|
||||||
labels: |
|
|
||||||
qch
|
|
||||||
cicd
|
|
||||||
delete-branch: true
|
|
||||||
add-paths: doc/*.qch
|
|
||||||
- uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: ${{ github.workspace }}/doc/html/
|
|
||||||
deploy:
|
|
||||||
# Add a dependency to the build job
|
|
||||||
needs: doxygen
|
|
||||||
|
|
||||||
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
|
||||||
permissions:
|
|
||||||
pages: write # to deploy to Pages
|
|
||||||
id-token: write # to verify the deployment originates from an appropriate source
|
|
||||||
|
|
||||||
# Deploy to the github-pages environment
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
# Specify runner + deployment step
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action
|
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
ca-certificates build-essential cmake ninja-build git pkg-config \
|
ca-certificates build-essential cmake ninja-build git pkg-config \
|
||||||
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||||
libqt6svg6-dev libqt6sql6-sqlite libsqlite3-dev libcups2-dev \
|
libqt6svg6-dev libqt6sql6-sqlite libcups2-dev \
|
||||||
libxkbcommon-x11-0 \
|
libxkbcommon-x11-0 \
|
||||||
xvfb openbox xdotool x11-utils
|
xvfb openbox xdotool x11-utils
|
||||||
# extra-cmake-modules and the KF6 libraries are installed rather than
|
# extra-cmake-modules and the KF6 libraries are installed rather than
|
||||||
@@ -53,7 +53,9 @@ jobs:
|
|||||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
cmake -B build -G Ninja \
|
cmake -B build -G Ninja \
|
||||||
-DCMAKE_BUILD_TYPE=Debug \
|
-DCMAKE_BUILD_TYPE=Debug \
|
||||||
-DQT_VERSION_MAJOR=6
|
-DQT_VERSION_MAJOR=6 \
|
||||||
|
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||||
|
-DQET_EXPORT_PROJECT_DB=ON
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --parallel $(nproc)
|
run: cmake --build build --parallel $(nproc)
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ jobs:
|
|||||||
mingw-w64-ucrt-x86_64-qt6-tools
|
mingw-w64-ucrt-x86_64-qt6-tools
|
||||||
mingw-w64-ucrt-x86_64-qt6-translations
|
mingw-w64-ucrt-x86_64-qt6-translations
|
||||||
mingw-w64-ucrt-x86_64-qt6-pdf
|
mingw-w64-ucrt-x86_64-qt6-pdf
|
||||||
mingw-w64-ucrt-x86_64-sqlite3
|
|
||||||
mingw-w64-ucrt-x86_64-pkg-config
|
mingw-w64-ucrt-x86_64-pkg-config
|
||||||
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
mingw-w64-ucrt-x86_64-kwidgetsaddons
|
||||||
mingw-w64-ucrt-x86_64-kcoreaddons
|
mingw-w64-ucrt-x86_64-kcoreaddons
|
||||||
@@ -122,8 +121,7 @@ jobs:
|
|||||||
-DQET_EXPORT_PROJECT_DB=ON \
|
-DQET_EXPORT_PROJECT_DB=ON \
|
||||||
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
|
||||||
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
|
|
||||||
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
|
|
||||||
..
|
..
|
||||||
ninja -j"$NPROC"
|
ninja -j"$NPROC"
|
||||||
|
|
||||||
@@ -196,13 +194,6 @@ jobs:
|
|||||||
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
|
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
|
||||||
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
|
cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
|
||||||
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
|
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
|
||||||
SQLITE=$(find /ucrt64/bin -name "libsqlite3*.dll" | head -1)
|
|
||||||
if [ -n "$SQLITE" ]; then
|
|
||||||
cp "$SQLITE" "$BIN/"
|
|
||||||
echo "SQLite3 copied: $(basename $SQLITE)"
|
|
||||||
else
|
|
||||||
echo "WARNING: libsqlite3 not found in /ucrt64/bin/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
|
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
|
||||||
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
|
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
|
||||||
|
|||||||
@@ -59,17 +59,41 @@ jobs:
|
|||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# 2. Download the portable artifact for this flavor
|
# 2. Download the portable artifact for this flavor
|
||||||
|
#
|
||||||
|
# Wrapped in nick-fields/retry: actions/download-artifact@v8 has
|
||||||
|
# shown repeated "Artifact download failed after 5 retries"
|
||||||
|
# failures on this cross-workflow download (via run-id) — not a
|
||||||
|
# real content/digest problem, just flaky Azure blob delivery.
|
||||||
|
# Two separate occurrences observed within days of each other
|
||||||
|
# (different artifact IDs/digests, same failure signature), each
|
||||||
|
# one enough to fail build-msi outright and block the rest of the
|
||||||
|
# pipeline. Retrying the whole download 3x is cheap insurance.
|
||||||
|
# Switched to `gh run download` here because nick-fields/retry
|
||||||
|
# can only retry a shell command, not re-invoke a `uses:` step.
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
- name: Download portable artifact
|
- name: Download portable artifact
|
||||||
uses: actions/download-artifact@v8
|
uses: nick-fields/retry@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.portable_artifact }}
|
timeout_minutes: 10
|
||||||
path: artifact\files
|
max_attempts: 3
|
||||||
# workflow_run => use the triggering run's ID
|
retry_wait_seconds: 30
|
||||||
# workflow_dispatch => use input run_id if provided, otherwise current run
|
shell: pwsh
|
||||||
run-id: ${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }}
|
command: |
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
if (Test-Path "artifact\files") { Remove-Item -Recurse -Force "artifact\files" }
|
||||||
repository: ${{ github.repository }}
|
New-Item -ItemType Directory -Force -Path "artifact\files" | Out-Null
|
||||||
|
|
||||||
|
$runId = "${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }}"
|
||||||
|
gh run download $runId `
|
||||||
|
--repo "${{ github.repository }}" `
|
||||||
|
--name "${{ matrix.portable_artifact }}" `
|
||||||
|
--dir "artifact\files"
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "gh run download failed (exit $LASTEXITCODE)"
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# 3. Extract version
|
# 3. Extract version
|
||||||
@@ -336,7 +360,13 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Delete old nightly .msi asset
|
- name: Delete old nightly .msi asset
|
||||||
if: always()
|
# Only run if a new MSI actually exists in dist/ — otherwise the old
|
||||||
|
# (still working) nightly .msi would be deleted without anything to
|
||||||
|
# replace it, leaving the nightly release with no MSI at all until
|
||||||
|
# the next successful build (see windows-msi-pipeline notes,
|
||||||
|
# run 35694391567: an upstream artifact-download failure meant
|
||||||
|
# dist\*.msi never existed for that run).
|
||||||
|
if: always() && hashFiles('dist/*.msi') != ''
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
@@ -381,7 +411,20 @@ jobs:
|
|||||||
deploy-pages:
|
deploy-pages:
|
||||||
needs: build-msi
|
needs: build-msi
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always() && needs.build-msi.result == 'success'
|
# Regenerate the page whenever the MSI job actually ran (success OR
|
||||||
|
# packaging/signing failure) — not only on full success.
|
||||||
|
# generate-page.py queries the published assets on the "nightly"
|
||||||
|
# release itself (line 407: `gh release view nightly --json assets`),
|
||||||
|
# so it already handles a missing MSI natively (empty MSI_NAME -> no
|
||||||
|
# MSI button on the page). The page only needs the exe/zip already
|
||||||
|
# published by windows-build.yml, independent of the MSI's fate.
|
||||||
|
# Only "skipped"/"cancelled" are excluded: if build-msi never ran at
|
||||||
|
# all (e.g. Windows Build itself failed), there is nothing new to
|
||||||
|
# publish and regenerating the page would be pointless.
|
||||||
|
if: >
|
||||||
|
always() &&
|
||||||
|
needs.build-msi.result != 'skipped' &&
|
||||||
|
needs.build-msi.result != 'cancelled'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pages: write
|
pages: write
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
*.snap
|
*.snap
|
||||||
.flatpak-builder
|
.flatpak-builder
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
# Qt build output
|
# Qt build output
|
||||||
*.user
|
*.user
|
||||||
# doxygen Doxyfile output
|
# doxygen Doxyfile output
|
||||||
@@ -8,3 +10,11 @@ doc/*
|
|||||||
QElectroTech.tag
|
QElectroTech.tag
|
||||||
!doc/doc-utils
|
!doc/doc-utils
|
||||||
lang/*.qm
|
lang/*.qm
|
||||||
|
|
||||||
|
# -- IDE settings/intermediate files --
|
||||||
|
# zed
|
||||||
|
.zed
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ if(Backtrace_FOUND)
|
|||||||
include_directories(${Backtrace_INCLUDE_DIRS})
|
include_directories(${Backtrace_INCLUDE_DIRS})
|
||||||
add_compile_definitions(QET_CRASH_BACKTRACE)
|
add_compile_definitions(QET_CRASH_BACKTRACE)
|
||||||
else()
|
else()
|
||||||
message(STATUS "backtrace() not available: crash dumps will carry the log ring without a backtrace")
|
message(STATUS
|
||||||
|
"backtrace() not available: crash dumps will carry the log ring "
|
||||||
|
"without a backtrace")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(SQLite3 REQUIRED)
|
find_package(SQLite3 REQUIRED)
|
||||||
@@ -316,7 +318,6 @@ target_include_directories(
|
|||||||
${QET_DIR}/sources/NameList
|
${QET_DIR}/sources/NameList
|
||||||
${QET_DIR}/sources/NameList/ui
|
${QET_DIR}/sources/NameList/ui
|
||||||
${QET_DIR}/sources/utils
|
${QET_DIR}/sources/utils
|
||||||
${QET_DIR}/pugixml/src
|
|
||||||
${QET_DIR}/sources/dataBase
|
${QET_DIR}/sources/dataBase
|
||||||
${QET_DIR}/sources/dataBase/ui
|
${QET_DIR}/sources/dataBase/ui
|
||||||
${QET_DIR}/sources/factory/ui
|
${QET_DIR}/sources/factory/ui
|
||||||
|
|||||||
@@ -27,18 +27,12 @@ git submodule update --init --recursive
|
|||||||
| C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) |
|
| C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) |
|
||||||
| Qt6 base + widgets | required | |
|
| Qt6 base + widgets | required | |
|
||||||
| Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below |
|
| Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below |
|
||||||
| SQLite3 | required | used by the nomenclature/summary database |
|
|
||||||
| Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build |
|
| Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build |
|
||||||
| pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below |
|
| pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below |
|
||||||
| Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed |
|
| Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed |
|
||||||
| KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) |
|
| KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) |
|
||||||
| QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) |
|
| QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) |
|
||||||
|
|
||||||
A note on CMake versions: the project declares a minimum of 3.5 but is
|
|
||||||
routinely built with much newer releases; if your CMake is older than 4.3 it
|
|
||||||
simply won't have the newer `SQLite3::SQLite3` target name, which the build
|
|
||||||
script compensates for automatically. There is nothing you need to do either
|
|
||||||
way.
|
|
||||||
|
|
||||||
## 3. Building (out-of-source build)
|
## 3. Building (out-of-source build)
|
||||||
|
|
||||||
@@ -106,7 +100,7 @@ the closest match.
|
|||||||
sudo apt install \
|
sudo apt install \
|
||||||
build-essential cmake ninja-build git \
|
build-essential cmake ninja-build git \
|
||||||
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||||
libsqlite3-dev \
|
|
||||||
libkf6coreaddons-dev libkf6widgetsaddons-dev
|
libkf6coreaddons-dev libkf6widgetsaddons-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -138,7 +132,6 @@ sudo apt install libpugixml-dev
|
|||||||
sudo dnf install \
|
sudo dnf install \
|
||||||
cmake gcc-c++ git \
|
cmake gcc-c++ git \
|
||||||
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
|
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
|
||||||
sqlite-devel \
|
|
||||||
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
|
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -155,11 +148,10 @@ Optional, for a system pugixml: `sudo dnf install pugixml-devel`.
|
|||||||
pkg install \
|
pkg install \
|
||||||
cmake git \
|
cmake git \
|
||||||
qt6-base qt6-tools \
|
qt6-base qt6-tools \
|
||||||
sqlite3 \
|
|
||||||
kf6-kcoreaddons kf6-kwidgetsaddons
|
kf6-kcoreaddons kf6-kwidgetsaddons
|
||||||
```
|
```
|
||||||
|
|
||||||
(from ports: `devel/qt6-base`, `devel/qt6-tools`, `databases/sqlite3`,
|
(from ports: `devel/qt6-base`, `devel/qt6-tools`,
|
||||||
`devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's
|
`devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's
|
||||||
`GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate
|
`GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate
|
||||||
package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing
|
package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing
|
||||||
@@ -175,7 +167,7 @@ Optional, for a system pugixml: `pkg install pugixml` (`devel/pugixml`).
|
|||||||
Using [Homebrew](https://brew.sh):
|
Using [Homebrew](https://brew.sh):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew install cmake qt sqlite ninja
|
brew install cmake qt ninja
|
||||||
```
|
```
|
||||||
|
|
||||||
Homebrew's `qt` formula is Qt6 and includes the private headers, so no
|
Homebrew's `qt` formula is Qt6 and includes the private headers, so no
|
||||||
@@ -193,7 +185,7 @@ Optional, for a system pugixml: `brew install pugixml`.
|
|||||||
|
|
||||||
See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the
|
See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the
|
||||||
package sources differ enough from the Unix-like platforms above (no system
|
package sources differ enough from the Unix-like platforms above (no system
|
||||||
package manager, SQLite3 and Qt aren't provided the same way) that it gets
|
package manager and Qt aren't provided the same way) that it gets
|
||||||
its own section.
|
its own section.
|
||||||
|
|
||||||
## 5. pugixml
|
## 5. pugixml
|
||||||
@@ -215,7 +207,7 @@ on Debian/Ubuntu, `pugixml-devel` on Fedora).
|
|||||||
Both toolchains QET's CMake build targets on Windows are covered here:
|
Both toolchains QET's CMake build targets on Windows are covered here:
|
||||||
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
|
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
|
||||||
Unix-like platforms above, there's no single system package manager, so
|
Unix-like platforms above, there's no single system package manager, so
|
||||||
Qt, SQLite3 and (optionally) KDE Frameworks each need to be sourced
|
Qt and (optionally) KDE Frameworks each need to be sourced
|
||||||
separately per toolchain.
|
separately per toolchain.
|
||||||
|
|
||||||
One piece of good news either way: unlike Debian/Fedora, the official Qt
|
One piece of good news either way: unlike Debian/Fedora, the official Qt
|
||||||
@@ -236,11 +228,7 @@ normally use `-DBUILD_WITH_KF=OFF` (see the
|
|||||||
1. Install Visual Studio with the "Desktop development with C++" workload,
|
1. Install Visual Studio with the "Desktop development with C++" workload,
|
||||||
and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit)
|
and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit)
|
||||||
via the [Qt Online Installer](https://www.qt.io/download-qt-installer).
|
via the [Qt Online Installer](https://www.qt.io/download-qt-installer).
|
||||||
2. Get SQLite3 — the simplest route is [vcpkg](https://vcpkg.io):
|
2. Configure and build from an "x64 Native Tools Command Prompt for VS":
|
||||||
```bat
|
|
||||||
vcpkg install sqlite3:x64-windows
|
|
||||||
```
|
|
||||||
3. Configure and build from an "x64 Native Tools Command Prompt for VS":
|
|
||||||
```bat
|
```bat
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. -G "Visual Studio 17 2022" -A x64 ^
|
cmake .. -G "Visual Studio 17 2022" -A x64 ^
|
||||||
@@ -271,15 +259,29 @@ mkdir build && cd build
|
|||||||
cmake .. -G Ninja -DBUILD_WITH_KF=OFF -DCMAKE_BUILD_TYPE=Release
|
cmake .. -G Ninja -DBUILD_WITH_KF=OFF -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build .
|
cmake --build .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If `libbacktrace` is installed in your MSYS2 environment, pass the following
|
||||||
|
cached CMake variables when configuring. They are only necessary in that
|
||||||
|
case, because `FindBacktrace` needs them to locate the library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake .. -G Ninja -DBUILD_WITH_KF=OFF -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBacktrace_INCLUDE_DIR=/c/msys64/clang64/include \
|
||||||
|
-DBacktrace_LIBRARY=/c/msys64/clang64/lib/libbacktrace.a
|
||||||
|
```
|
||||||
|
|
||||||
|
`Backtrace_INCLUDE_DIR` must point to the directory containing `backtrace.h`,
|
||||||
|
and `Backtrace_LIBRARY` to the `libbacktrace.a` file. The paths above are the
|
||||||
|
usual locations for the MSYS2 `clang64` environment; adjust them if your
|
||||||
|
installation uses a different prefix.
|
||||||
|
|
||||||
(KF6 isn't packaged in MSYS2 either, hence `-DBUILD_WITH_KF=OFF` again.)
|
(KF6 isn't packaged in MSYS2 either, hence `-DBUILD_WITH_KF=OFF` again.)
|
||||||
|
|
||||||
Using the Qt Online Installer's bundled MinGW kit instead: point
|
Using the Qt Online Installer's bundled MinGW kit instead: point
|
||||||
`CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure
|
`CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure
|
||||||
its bundled `g++.exe` comes first on `PATH`, or pass
|
its bundled `g++.exe` comes first on `PATH`, or pass
|
||||||
`-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick
|
`-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick
|
||||||
up a different MinGW installation. SQLite3 still has to come from elsewhere
|
up a different MinGW installation.
|
||||||
in this path — vcpkg with a `mingw`-flavoured triplet, or MSYS2's package as
|
|
||||||
above.
|
|
||||||
|
|
||||||
## 7. Qt6 private headers (mandatory)
|
## 7. Qt6 private headers (mandatory)
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ parts:
|
|||||||
source: .
|
source: .
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- git
|
- git
|
||||||
- sqlite3
|
|
||||||
- xdg-user-dirs
|
- xdg-user-dirs
|
||||||
- libqt6qml6
|
- libqt6qml6
|
||||||
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
|
||||||
@@ -82,7 +81,7 @@ parts:
|
|||||||
- git
|
- git
|
||||||
- cmake
|
- cmake
|
||||||
- ninja-build
|
- ninja-build
|
||||||
- libsqlite3-dev
|
|
||||||
- qt6-tools-dev
|
- qt6-tools-dev
|
||||||
- qt6-base-private-dev
|
- qt6-base-private-dev
|
||||||
- qt6-declarative-dev
|
- qt6-declarative-dev
|
||||||
|
|||||||
@@ -23,8 +23,31 @@ if(BUILD_WITH_KF)
|
|||||||
|
|
||||||
if(BUILD_KF)
|
if(BUILD_KF)
|
||||||
|
|
||||||
if(NOT DEFINED KF_GIT_TAG)
|
# v6.10.0 is a more or less random version, taken as an conservative
|
||||||
# this is a more or less random version, taken as an conservative approach
|
# approach. Pinned to the commits v6.10.0 points at, not to the tags
|
||||||
|
# themselves; see the note in fetch_pugixml.cmake. Each module lives in its
|
||||||
|
# own repository, so the same v6.10.0 release is a different commit in
|
||||||
|
# each.
|
||||||
|
#
|
||||||
|
# KDE uses annotated tags, so "git ls-remote <repo> 'refs/tags/v6.10.0*'"
|
||||||
|
# prints two hashes per module: refs/tags/v6.10.0 is the tag object (the
|
||||||
|
# tagger, the date and the tag message) and refs/tags/v6.10.0^{} is the
|
||||||
|
# commit that object points at. The hashes below are the "^{}" ones, i.e.
|
||||||
|
# the commits. Lightweight tags, such as pugixml's v1.15 and
|
||||||
|
# SingleApplication's v3.2.0, have no tag object and print only the
|
||||||
|
# commit line.
|
||||||
|
set(KF_ECM_GIT_COMMIT 7dd28cc56c339c3f8fb356f7c53c0e8f61433d81) # v6.10.0
|
||||||
|
set(KF_KCOREADDONS_GIT_COMMIT c569f974dab24b4784ad186a3db4b76b2fa36612) # v6.10.0
|
||||||
|
set(KF_KWIDGETSADDONS_GIT_COMMIT 1abbed8a280d6626c59fb197f2c4667d2b1e7445) # v6.10.0
|
||||||
|
|
||||||
|
if(DEFINED KF_GIT_TAG)
|
||||||
|
# Explicit override: -DKF_GIT_TAG=<ref> selects one ref for all three
|
||||||
|
# modules, unpinned, exactly as it did before.
|
||||||
|
set(KF_ECM_GIT_COMMIT ${KF_GIT_TAG})
|
||||||
|
set(KF_KCOREADDONS_GIT_COMMIT ${KF_GIT_TAG})
|
||||||
|
set(KF_KWIDGETSADDONS_GIT_COMMIT ${KF_GIT_TAG})
|
||||||
|
else()
|
||||||
|
# Keep KF_GIT_TAG defined: define_definitions.cmake reports it.
|
||||||
set(KF_GIT_TAG v6.10.0)
|
set(KF_GIT_TAG v6.10.0)
|
||||||
endif()
|
endif()
|
||||||
# using a function in order to limit the scope of the variables
|
# using a function in order to limit the scope of the variables
|
||||||
@@ -53,19 +76,19 @@ if(BUILD_WITH_KF)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
ecm
|
ecm
|
||||||
GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git
|
GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git
|
||||||
GIT_TAG ${KF_GIT_TAG})
|
GIT_TAG ${KF_ECM_GIT_COMMIT})
|
||||||
FetchContent_MakeAvailable(ecm)
|
FetchContent_MakeAvailable(ecm)
|
||||||
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
kcoreaddons
|
kcoreaddons
|
||||||
GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git
|
GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git
|
||||||
GIT_TAG ${KF_GIT_TAG})
|
GIT_TAG ${KF_KCOREADDONS_GIT_COMMIT})
|
||||||
FetchContent_MakeAvailable(kcoreaddons)
|
FetchContent_MakeAvailable(kcoreaddons)
|
||||||
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
kwidgetsaddons
|
kwidgetsaddons
|
||||||
GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git
|
GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git
|
||||||
GIT_TAG ${KF_GIT_TAG})
|
GIT_TAG ${KF_KWIDGETSADDONS_GIT_COMMIT})
|
||||||
FetchContent_MakeAvailable(kwidgetsaddons)
|
FetchContent_MakeAvailable(kwidgetsaddons)
|
||||||
endfunction()
|
endfunction()
|
||||||
qet_make_kf_available()
|
qet_make_kf_available()
|
||||||
|
|||||||
@@ -22,10 +22,51 @@ option(BUILD_PUGIXML "Build pugixml library, use system one otherwise" YES)
|
|||||||
|
|
||||||
if(BUILD_PUGIXML)
|
if(BUILD_PUGIXML)
|
||||||
|
|
||||||
|
# Pinned to the commit v1.15 points at, not to the tag itself.
|
||||||
|
#
|
||||||
|
# A git tag is only a named pointer to a commit, and anyone with push access
|
||||||
|
# to the upstream repository can move it (git push --force) to any other
|
||||||
|
# commit. FetchContent fetches whatever the tag points at when the build
|
||||||
|
# runs, so if a maintainer account or CI token is compromised, the attacker
|
||||||
|
# can retarget a well-known release tag to malicious code: every fresh build
|
||||||
|
# of QElectroTech then compiles it, while nothing changes in this repository
|
||||||
|
# and the tag name still looks correct. A commit hash cannot be moved, because
|
||||||
|
# it is derived from the content: different code always has a different hash.
|
||||||
|
#
|
||||||
|
# This attack has been used in the wild:
|
||||||
|
# - March 2025, tj-actions/changed-files (CVE-2025-30066): tags v1 through
|
||||||
|
# v45.0.7 were retargeted to a commit that dumped CI secrets into build
|
||||||
|
# logs, affecting more than 23,000 repositories.
|
||||||
|
# - March 2026, aquasecurity/trivy-action (CVE-2026-33634): 76 of 77
|
||||||
|
# version tags were force-pushed to a credential stealer and stayed
|
||||||
|
# malicious for about 12 hours.
|
||||||
|
# Both were GitHub Actions rather than CMake dependencies, but the mechanism
|
||||||
|
# is the same one FetchContent relies on here: resolving a git tag at build
|
||||||
|
# time.
|
||||||
|
#
|
||||||
|
# To upgrade, look up the commit the new tag points at with
|
||||||
|
# git ls-remote <repo> 'refs/tags/<tag>*', check that it is the release you
|
||||||
|
# expect, and update both the hash and the trailing tag comment.
|
||||||
|
#
|
||||||
|
# How many lines that prints depends on which of the two kinds of tag
|
||||||
|
# upstream created:
|
||||||
|
# - A lightweight tag is nothing but a ref pointing straight at the commit,
|
||||||
|
# so ls-remote prints a single line, "refs/tags/<tag>", and its hash is
|
||||||
|
# the commit to pin. pugixml tags this way, which is why the v1.15 hash
|
||||||
|
# below is what "git ls-remote ... refs/tags/v1.15" reports directly;
|
||||||
|
# SingleApplication (v3.2.0) does the same.
|
||||||
|
# - An annotated tag is a git object in its own right, carrying a tagger,
|
||||||
|
# a date, a message and optionally a GPG signature, and pointing at the
|
||||||
|
# commit. ls-remote then prints two lines: "refs/tags/<tag>" is the tag
|
||||||
|
# object and "refs/tags/<tag>^{}" is that object dereferenced, i.e. the
|
||||||
|
# commit. The KDE Frameworks modules tag this way, so for them it is the
|
||||||
|
# "^{}" hash that belongs in the pin; the other hash identifies the tag
|
||||||
|
# object itself, which is not the source revision and changes whenever
|
||||||
|
# upstream re-creates the tag, even over the very same commit.
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
pugixml
|
pugixml
|
||||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||||
GIT_TAG v1.15)
|
GIT_TAG ee86beb30e4973f5feffe3ce63bfa4fbadf72f38) # v1.15
|
||||||
set(PUGIXML_INSTALL OFF CACHE INTERNAL "")
|
set(PUGIXML_INSTALL OFF CACHE INTERNAL "")
|
||||||
FetchContent_MakeAvailable(pugixml)
|
FetchContent_MakeAvailable(pugixml)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -31,9 +31,15 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/SingleApplication/CMakeLists.txt")
|
|||||||
set(FETCHCONTENT_SOURCE_DIR_SINGLEAPPLICATION "${CMAKE_SOURCE_DIR}/SingleApplication")
|
set(FETCHCONTENT_SOURCE_DIR_SINGLEAPPLICATION "${CMAKE_SOURCE_DIR}/SingleApplication")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Pinned to the commit v3.2.0 points at, not to the tag itself; see the note in
|
||||||
|
# fetch_pugixml.cmake. v3.2.0 is a lightweight tag, a ref pointing straight at
|
||||||
|
# the commit, so "git ls-remote <repo> refs/tags/v3.2.0" prints that commit and
|
||||||
|
# nothing else. An annotated tag, as KDE uses in fetch_kdeaddons.cmake, would
|
||||||
|
# print the tag object under refs/tags/v3.2.0 as well, with the commit on the
|
||||||
|
# refs/tags/v3.2.0^{} line.
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
SingleApplication
|
SingleApplication
|
||||||
GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git
|
GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git
|
||||||
GIT_TAG v3.2.0)
|
GIT_TAG aede311d28d20179216c5419b581087be2a8409f) # v3.2.0
|
||||||
set(QT_DEFAULT_MAJOR_VERSION 6)
|
set(QT_DEFAULT_MAJOR_VERSION 6)
|
||||||
FetchContent_MakeAvailable(SingleApplication)
|
FetchContent_MakeAvailable(SingleApplication)
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ if(WIN32)
|
|||||||
# puts the .qm files (see build-aux/windows/QElectroTech.wxs and the
|
# puts the .qm files (see build-aux/windows/QElectroTech.wxs and the
|
||||||
# windows-build workflow), and what the shortcuts pass as --lang-dir.
|
# windows-build workflow), and what the shortcuts pass as --lang-dir.
|
||||||
set(QET_LANG_PATH "lang/")
|
set(QET_LANG_PATH "lang/")
|
||||||
|
set(QET_EXAMPLES_PATH "examples/")
|
||||||
set(QET_LICENSE_PATH "./")
|
set(QET_LICENSE_PATH "./")
|
||||||
|
set(QET_ICONS_PATH "icons/hicolor/")
|
||||||
# Liste des ressources Windows
|
# Liste des ressources Windows
|
||||||
#RC_FILE = qelectrotech.rc
|
#RC_FILE = qelectrotech.rc
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -248,8 +248,12 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/qet.h
|
${QET_DIR}/sources/qet.h
|
||||||
${QET_DIR}/sources/qeticons.cpp
|
${QET_DIR}/sources/qeticons.cpp
|
||||||
${QET_DIR}/sources/qeticons.h
|
${QET_DIR}/sources/qeticons.h
|
||||||
|
${QET_DIR}/sources/palettegraphicsview.cpp
|
||||||
|
${QET_DIR}/sources/palettegraphicsview.h
|
||||||
${QET_DIR}/sources/qetpalette.cpp
|
${QET_DIR}/sources/qetpalette.cpp
|
||||||
${QET_DIR}/sources/qetpalette.h
|
${QET_DIR}/sources/qetpalette.h
|
||||||
|
${QET_DIR}/sources/qetstyle.cpp
|
||||||
|
${QET_DIR}/sources/qetstyle.h
|
||||||
${QET_DIR}/sources/qetinformation.cpp
|
${QET_DIR}/sources/qetinformation.cpp
|
||||||
${QET_DIR}/sources/qetinformation.h
|
${QET_DIR}/sources/qetinformation.h
|
||||||
${QET_DIR}/sources/qetmainwindow.cpp
|
${QET_DIR}/sources/qetmainwindow.cpp
|
||||||
@@ -308,6 +312,8 @@ set(QET_SRC_FILES
|
|||||||
|
|
||||||
${QET_DIR}/sources/dataBase/projectdatabase.cpp
|
${QET_DIR}/sources/dataBase/projectdatabase.cpp
|
||||||
${QET_DIR}/sources/dataBase/projectdatabase.h
|
${QET_DIR}/sources/dataBase/projectdatabase.h
|
||||||
|
${QET_DIR}/sources/dataBase/sqlreadonly.cpp
|
||||||
|
${QET_DIR}/sources/dataBase/sqlreadonly.h
|
||||||
|
|
||||||
${QET_DIR}/sources/dataBase/ui/elementquerywidget.cpp
|
${QET_DIR}/sources/dataBase/ui/elementquerywidget.cpp
|
||||||
${QET_DIR}/sources/dataBase/ui/elementquerywidget.h
|
${QET_DIR}/sources/dataBase/ui/elementquerywidget.h
|
||||||
@@ -434,6 +440,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ElementsCollection/elementcollectionhandler.h
|
${QET_DIR}/sources/ElementsCollection/elementcollectionhandler.h
|
||||||
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.cpp
|
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.cpp
|
||||||
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.h
|
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.h
|
||||||
|
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.cpp
|
||||||
|
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
|
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
|
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
|
||||||
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
|
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
|
||||||
@@ -501,10 +509,6 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.cpp
|
${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.cpp
|
||||||
${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.h
|
${QET_DIR}/sources/PropertiesEditor/propertieseditorwidget.h
|
||||||
|
|
||||||
${QET_DIR}/pugixml/src/pugiconfig.hpp
|
|
||||||
${QET_DIR}/pugixml/src/pugixml.cpp
|
|
||||||
${QET_DIR}/pugixml/src/pugixml.hpp
|
|
||||||
|
|
||||||
${QET_DIR}/sources/qetgraphicsitem/conductor.cpp
|
${QET_DIR}/sources/qetgraphicsitem/conductor.cpp
|
||||||
${QET_DIR}/sources/qetgraphicsitem/conductor.h
|
${QET_DIR}/sources/qetgraphicsitem/conductor.h
|
||||||
${QET_DIR}/sources/qetgraphicsitem/conductortextitem.cpp
|
${QET_DIR}/sources/qetgraphicsitem/conductortextitem.cpp
|
||||||
@@ -699,6 +703,10 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ui/contactgroupselectiondialog.h
|
${QET_DIR}/sources/ui/contactgroupselectiondialog.h
|
||||||
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
|
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
|
||||||
${QET_DIR}/sources/ui/conductorpropertiesdialog.h
|
${QET_DIR}/sources/ui/conductorpropertiesdialog.h
|
||||||
|
${QET_DIR}/sources/ui/conductorcolortoolbutton.cpp
|
||||||
|
${QET_DIR}/sources/ui/conductorcolortoolbutton.h
|
||||||
|
${QET_DIR}/sources/ui/diagrambgcolorbutton.cpp
|
||||||
|
${QET_DIR}/sources/ui/diagrambgcolorbutton.h
|
||||||
${QET_DIR}/sources/ui/conductorpropertieswidget.cpp
|
${QET_DIR}/sources/ui/conductorpropertieswidget.cpp
|
||||||
${QET_DIR}/sources/ui/conductorpropertieswidget.h
|
${QET_DIR}/sources/ui/conductorpropertieswidget.h
|
||||||
${QET_DIR}/sources/ui/configsaveloaderwidget.cpp
|
${QET_DIR}/sources/ui/configsaveloaderwidget.cpp
|
||||||
@@ -715,6 +723,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ui/backupdialog.h
|
${QET_DIR}/sources/ui/backupdialog.h
|
||||||
${QET_DIR}/sources/ui/dialogwaiting.cpp
|
${QET_DIR}/sources/ui/dialogwaiting.cpp
|
||||||
${QET_DIR}/sources/ui/dialogwaiting.h
|
${QET_DIR}/sources/ui/dialogwaiting.h
|
||||||
|
${QET_DIR}/sources/ui/duplicateoffsetdialog.cpp
|
||||||
|
${QET_DIR}/sources/ui/duplicateoffsetdialog.h
|
||||||
${QET_DIR}/sources/ui/dynamicelementtextitemeditor.cpp
|
${QET_DIR}/sources/ui/dynamicelementtextitemeditor.cpp
|
||||||
${QET_DIR}/sources/ui/dynamicelementtextitemeditor.h
|
${QET_DIR}/sources/ui/dynamicelementtextitemeditor.h
|
||||||
${QET_DIR}/sources/ui/dynamicelementtextmodel.cpp
|
${QET_DIR}/sources/ui/dynamicelementtextmodel.cpp
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
|
||||||
|
<!-- keyboard body with a lighter top edge -->
|
||||||
|
<rect x="6" y="30" width="116" height="68" rx="8" fill="#31363b"/>
|
||||||
|
<rect x="6" y="30" width="116" height="34" rx="8" fill="#4d4d4d"/>
|
||||||
|
<rect x="6" y="46" width="116" height="18" fill="#31363b"/>
|
||||||
|
<!-- key rows -->
|
||||||
|
<g fill="#eff0f1">
|
||||||
|
<rect x="14" y="38" width="8" height="8" rx="1.5"/><rect x="24" y="38" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="34" y="38" width="8" height="8" rx="1.5"/><rect x="44" y="38" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="54" y="38" width="8" height="8" rx="1.5"/><rect x="64" y="38" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="74" y="38" width="8" height="8" rx="1.5"/><rect x="84" y="38" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="94" y="38" width="8" height="8" rx="1.5"/><rect x="104" y="38" width="10" height="8" rx="1.5"/>
|
||||||
|
<rect x="14" y="50" width="12" height="8" rx="1.5"/><rect x="28" y="50" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="38" y="50" width="8" height="8" rx="1.5"/><rect x="48" y="50" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="58" y="50" width="8" height="8" rx="1.5"/><rect x="68" y="50" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="78" y="50" width="8" height="8" rx="1.5"/><rect x="88" y="50" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="98" y="50" width="16" height="8" rx="1.5"/>
|
||||||
|
<rect x="14" y="62" width="16" height="8" rx="1.5"/><rect x="32" y="62" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="42" y="62" width="8" height="8" rx="1.5"/><rect x="52" y="62" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="62" y="62" width="8" height="8" rx="1.5"/><rect x="72" y="62" width="8" height="8" rx="1.5"/>
|
||||||
|
<rect x="82" y="62" width="8" height="8" rx="1.5"/><rect x="92" y="62" width="22" height="8" rx="1.5"/>
|
||||||
|
<rect x="14" y="74" width="10" height="8" rx="1.5"/><rect x="26" y="74" width="10" height="8" rx="1.5"/>
|
||||||
|
<rect x="38" y="74" width="52" height="8" rx="1.5"/>
|
||||||
|
<rect x="92" y="74" width="10" height="8" rx="1.5"/><rect x="104" y="74" width="10" height="8" rx="1.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- the two keys of a shortcut, pressed -->
|
||||||
|
<rect x="14" y="62" width="16" height="8" rx="1.5" fill="#3daee9"/>
|
||||||
|
<rect x="42" y="62" width="8" height="8" rx="1.5" fill="#3daee9"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,48 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
|
||||||
|
<!-- DIN rail -->
|
||||||
|
<rect x="6" y="86" width="116" height="18" rx="3" fill="#7f8c8d"/>
|
||||||
|
<rect x="6" y="92" width="116" height="6" fill="#4d4d4d"/>
|
||||||
|
<!-- six terminal blocks: L, L, N, PE, L, spare -->
|
||||||
|
<g fill="#eff0f1">
|
||||||
|
<rect x="12" y="26" width="16" height="66" rx="2"/>
|
||||||
|
<rect x="30" y="26" width="16" height="66" rx="2"/>
|
||||||
|
<rect x="48" y="26" width="16" height="66" rx="2"/>
|
||||||
|
<rect x="66" y="26" width="16" height="66" rx="2"/>
|
||||||
|
<rect x="84" y="26" width="16" height="66" rx="2"/>
|
||||||
|
<rect x="102" y="26" width="16" height="66" rx="2"/>
|
||||||
|
</g>
|
||||||
|
<!-- right-hand shading of each block -->
|
||||||
|
<g fill="#bdc3c7">
|
||||||
|
<rect x="24" y="28" width="4" height="62"/>
|
||||||
|
<rect x="42" y="28" width="4" height="62"/>
|
||||||
|
<rect x="60" y="28" width="4" height="62"/>
|
||||||
|
<rect x="78" y="28" width="4" height="62"/>
|
||||||
|
<rect x="96" y="28" width="4" height="62"/>
|
||||||
|
<rect x="114" y="28" width="4" height="62"/>
|
||||||
|
</g>
|
||||||
|
<!-- colored marker bands -->
|
||||||
|
<rect x="12" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
|
||||||
|
<rect x="30" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
|
||||||
|
<rect x="48" y="26" width="16" height="10" rx="2" fill="#3daee9"/>
|
||||||
|
<rect x="66" y="26" width="16" height="10" rx="2" fill="#27ae60"/>
|
||||||
|
<rect x="70" y="26" width="8" height="10" fill="#fdbc4b"/>
|
||||||
|
<rect x="84" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
|
||||||
|
<rect x="102" y="26" width="16" height="10" rx="2" fill="#da4453"/>
|
||||||
|
<!-- screws -->
|
||||||
|
<g fill="#31363b">
|
||||||
|
<circle cx="20" cy="50" r="4.5"/><circle cx="20" cy="74" r="4.5"/>
|
||||||
|
<circle cx="38" cy="50" r="4.5"/><circle cx="38" cy="74" r="4.5"/>
|
||||||
|
<circle cx="56" cy="50" r="4.5"/><circle cx="56" cy="74" r="4.5"/>
|
||||||
|
<circle cx="74" cy="50" r="4.5"/><circle cx="74" cy="74" r="4.5"/>
|
||||||
|
<circle cx="92" cy="50" r="4.5"/><circle cx="92" cy="74" r="4.5"/>
|
||||||
|
<circle cx="110" cy="50" r="4.5"/><circle cx="110" cy="74" r="4.5"/>
|
||||||
|
</g>
|
||||||
|
<g fill="#eff0f1">
|
||||||
|
<rect x="16" y="49" width="8" height="2"/><rect x="16" y="73" width="8" height="2"/>
|
||||||
|
<rect x="34" y="49" width="8" height="2"/><rect x="34" y="73" width="8" height="2"/>
|
||||||
|
<rect x="52" y="49" width="8" height="2"/><rect x="52" y="73" width="8" height="2"/>
|
||||||
|
<rect x="70" y="49" width="8" height="2"/><rect x="70" y="73" width="8" height="2"/>
|
||||||
|
<rect x="88" y="49" width="8" height="2"/><rect x="88" y="73" width="8" height="2"/>
|
||||||
|
<rect x="106" y="49" width="8" height="2"/><rect x="106" y="73" width="8" height="2"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -216,6 +216,9 @@ Clarification:
|
|||||||
ico/128x128/diagram.png by the QElectroTech team (License CC BY-ND 3.0)
|
ico/128x128/diagram.png by the QElectroTech team (License CC BY-ND 3.0)
|
||||||
ico/128x128/document-export.png by the QElectroTech team (License CC BY-ND 3.0)
|
ico/128x128/document-export.png by the QElectroTech team (License CC BY-ND 3.0)
|
||||||
ico/128x128/project.png by the QElectroTech team (License CC BY-ND 3.0)
|
ico/128x128/project.png by the QElectroTech team (License CC BY-ND 3.0)
|
||||||
|
ico/128x128/terminalstrip.png and configure-shortcuts.png by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), rendered from the .svg files beside them
|
||||||
|
ico/scalable/pdf-import.svg by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), laid out like ico/22x22/insert-image.png
|
||||||
|
ico/scalable/diagram.svg, folio-new.svg, folio-delete.svg, folio-properties.svg, label.svg by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), the folio icons redrawn in the same style
|
||||||
ico/256x256/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
ico/256x256/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
||||||
ico/breeze-icons/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
ico/breeze-icons/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
||||||
ico/diagram.png by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
ico/diagram.png by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
||||||
|
|||||||
@@ -148,15 +148,8 @@
|
|||||||
<file alias="themes/qet/22x22/conductor-reset.png">22x22/conductor2.png</file>
|
<file alias="themes/qet/22x22/conductor-reset.png">22x22/conductor2.png</file>
|
||||||
<file alias="themes/qet/22x22/configure-toolbars.png">22x22/configure-toolbars.png</file>
|
<file alias="themes/qet/22x22/configure-toolbars.png">22x22/configure-toolbars.png</file>
|
||||||
<file alias="themes/qet/22x22/configure.png">22x22/configure.png</file>
|
<file alias="themes/qet/22x22/configure.png">22x22/configure.png</file>
|
||||||
<file alias="themes/qet/22x22/diagram.png">22x22/diagram.png</file>
|
|
||||||
<file alias="themes/qet/22x22/diagram_add.png">22x22/diagram_add.png</file>
|
|
||||||
<file alias="themes/qet/22x22/folio-new.png">22x22/diagram_add.png</file>
|
|
||||||
<file alias="themes/qet/22x22/diagram_bg.png">22x22/diagram_bg.png</file>
|
<file alias="themes/qet/22x22/diagram_bg.png">22x22/diagram_bg.png</file>
|
||||||
<file alias="themes/qet/22x22/diagram_del.png">22x22/diagram_del.png</file>
|
|
||||||
<file alias="themes/qet/22x22/folio-delete.png">22x22/diagram_del.png</file>
|
|
||||||
<file alias="themes/qet/22x22/dialog-cancel.png">22x22/dialog-cancel.png</file>
|
<file alias="themes/qet/22x22/dialog-cancel.png">22x22/dialog-cancel.png</file>
|
||||||
<file alias="themes/qet/22x22/dialog-information.png">22x22/dialog-information.png</file>
|
|
||||||
<file alias="themes/qet/22x22/folio-properties.png">22x22/dialog-information.png</file>
|
|
||||||
<file alias="themes/qet/22x22/dialog-ok.png">22x22/dialog-ok.png</file>
|
<file alias="themes/qet/22x22/dialog-ok.png">22x22/dialog-ok.png</file>
|
||||||
<file alias="themes/qet/22x22/document-close.png">22x22/document-close.png</file>
|
<file alias="themes/qet/22x22/document-close.png">22x22/document-close.png</file>
|
||||||
<file alias="themes/qet/22x22/document-export.png">22x22/document-export.png</file>
|
<file alias="themes/qet/22x22/document-export.png">22x22/document-export.png</file>
|
||||||
@@ -207,7 +200,6 @@
|
|||||||
<file alias="themes/qet/22x22/guides.png">22x22/guides.png</file>
|
<file alias="themes/qet/22x22/guides.png">22x22/guides.png</file>
|
||||||
<file alias="themes/qet/22x22/hotspot.png">22x22/hotspot.png</file>
|
<file alias="themes/qet/22x22/hotspot.png">22x22/hotspot.png</file>
|
||||||
<file alias="themes/qet/22x22/insert-image.png">22x22/insert-image.png</file>
|
<file alias="themes/qet/22x22/insert-image.png">22x22/insert-image.png</file>
|
||||||
<file alias="themes/qet/22x22/label.png">22x22/label.png</file>
|
|
||||||
<file alias="themes/qet/22x22/landscape.png">22x22/landscape.png</file>
|
<file alias="themes/qet/22x22/landscape.png">22x22/landscape.png</file>
|
||||||
<file alias="themes/qet/22x22/line.png">22x22/line.png</file>
|
<file alias="themes/qet/22x22/line.png">22x22/line.png</file>
|
||||||
<file alias="themes/qet/22x22/list-add.png">22x22/list-add.png</file>
|
<file alias="themes/qet/22x22/list-add.png">22x22/list-add.png</file>
|
||||||
@@ -218,7 +210,6 @@
|
|||||||
<file alias="themes/qet/22x22/object-locked.png">22x22/object-locked.png</file>
|
<file alias="themes/qet/22x22/object-locked.png">22x22/object-locked.png</file>
|
||||||
<file alias="themes/qet/22x22/object-rotate-right.png">22x22/object-rotate-right.png</file>
|
<file alias="themes/qet/22x22/object-rotate-right.png">22x22/object-rotate-right.png</file>
|
||||||
<file alias="themes/qet/22x22/object-unlocked.png">22x22/object-unlocked.png</file>
|
<file alias="themes/qet/22x22/object-unlocked.png">22x22/object-unlocked.png</file>
|
||||||
<file alias="themes/qet/22x22/pdf-import.png">22x22/pdf-import.png</file>
|
|
||||||
<file alias="themes/qet/22x22/polygon.png">22x22/polygon.png</file>
|
<file alias="themes/qet/22x22/polygon.png">22x22/polygon.png</file>
|
||||||
<file alias="themes/qet/22x22/portrait.png">22x22/portrait.png</file>
|
<file alias="themes/qet/22x22/portrait.png">22x22/portrait.png</file>
|
||||||
<file alias="themes/qet/22x22/preferences-desktop-user.png">22x22/preferences-desktop-user.png</file>
|
<file alias="themes/qet/22x22/preferences-desktop-user.png">22x22/preferences-desktop-user.png</file>
|
||||||
@@ -268,12 +259,20 @@
|
|||||||
<file alias="themes/qet/48x48/user-away-extended.png">48x48/user-away-extended.png</file>
|
<file alias="themes/qet/48x48/user-away-extended.png">48x48/user-away-extended.png</file>
|
||||||
<file alias="themes/qet/48x48/user-away.png">48x48/user-away.png</file>
|
<file alias="themes/qet/48x48/user-away.png">48x48/user-away.png</file>
|
||||||
<file alias="themes/qet/48x48/view-pim-journal.png">48x48/view-pim-journal.png</file>
|
<file alias="themes/qet/48x48/view-pim-journal.png">48x48/view-pim-journal.png</file>
|
||||||
|
<file alias="themes/qet/128x128/configure-shortcuts.png">128x128/configure-shortcuts.png</file>
|
||||||
<file alias="themes/qet/128x128/diagram.png">128x128/diagram.png</file>
|
<file alias="themes/qet/128x128/diagram.png">128x128/diagram.png</file>
|
||||||
<file alias="themes/qet/128x128/document-export.png">128x128/document-export.png</file>
|
<file alias="themes/qet/128x128/document-export.png">128x128/document-export.png</file>
|
||||||
<file alias="themes/qet/128x128/plasmagik.png">128x128/plasmagik.png</file>
|
<file alias="themes/qet/128x128/plasmagik.png">128x128/plasmagik.png</file>
|
||||||
<file alias="themes/qet/128x128/printer.png">128x128/printer.png</file>
|
<file alias="themes/qet/128x128/printer.png">128x128/printer.png</file>
|
||||||
<file alias="themes/qet/128x128/project.png">128x128/project.png</file>
|
<file alias="themes/qet/128x128/project.png">128x128/project.png</file>
|
||||||
<file alias="themes/qet/128x128/settings.png">128x128/settings.png</file>
|
<file alias="themes/qet/128x128/settings.png">128x128/settings.png</file>
|
||||||
|
<file alias="themes/qet/128x128/terminalstrip.png">128x128/terminalstrip.png</file>
|
||||||
|
<file alias="themes/qet/scalable/diagram.svg">scalable/diagram.svg</file>
|
||||||
|
<file alias="themes/qet/scalable/folio-delete.svg">scalable/folio-delete.svg</file>
|
||||||
|
<file alias="themes/qet/scalable/folio-new.svg">scalable/folio-new.svg</file>
|
||||||
|
<file alias="themes/qet/scalable/folio-properties.svg">scalable/folio-properties.svg</file>
|
||||||
|
<file alias="themes/qet/scalable/label.svg">scalable/label.svg</file>
|
||||||
|
<file alias="themes/qet/scalable/pdf-import.svg">scalable/pdf-import.svg</file>
|
||||||
<file alias="themes/qet/scalable/edit-opacity.svg">breeze-icons/scalable/apps/hidef/edit-opacity.svg</file>
|
<file alias="themes/qet/scalable/edit-opacity.svg">breeze-icons/scalable/apps/hidef/edit-opacity.svg</file>
|
||||||
<file alias="themes/qet/scalable/image-flip-horizontal.svg">breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg</file>
|
<file alias="themes/qet/scalable/image-flip-horizontal.svg">breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg</file>
|
||||||
<file alias="themes/qet/scalable/image-flip-vertical.svg">breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg</file>
|
<file alias="themes/qet/scalable/image-flip-vertical.svg">breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg</file>
|
||||||
@@ -290,7 +289,6 @@
|
|||||||
<file>themes/qet-dark/16x16/conductor-edit.png</file>
|
<file>themes/qet-dark/16x16/conductor-edit.png</file>
|
||||||
<file>themes/qet-dark/16x16/configure-toolbars.png</file>
|
<file>themes/qet-dark/16x16/configure-toolbars.png</file>
|
||||||
<file>themes/qet-dark/16x16/configure.png</file>
|
<file>themes/qet-dark/16x16/configure.png</file>
|
||||||
<file>themes/qet-dark/16x16/diagram.png</file>
|
|
||||||
<file>themes/qet-dark/16x16/dialog-cancel.png</file>
|
<file>themes/qet-dark/16x16/dialog-cancel.png</file>
|
||||||
<file>themes/qet-dark/16x16/dialog-ok.png</file>
|
<file>themes/qet-dark/16x16/dialog-ok.png</file>
|
||||||
<file>themes/qet-dark/16x16/document-export.png</file>
|
<file>themes/qet-dark/16x16/document-export.png</file>
|
||||||
@@ -331,9 +329,6 @@
|
|||||||
<file>themes/qet-dark/16x16/folder-open.png</file>
|
<file>themes/qet-dark/16x16/folder-open.png</file>
|
||||||
<file>themes/qet-dark/16x16/folder-show-all.png</file>
|
<file>themes/qet-dark/16x16/folder-show-all.png</file>
|
||||||
<file>themes/qet-dark/16x16/folder.png</file>
|
<file>themes/qet-dark/16x16/folder.png</file>
|
||||||
<file>themes/qet-dark/16x16/folio-delete.png</file>
|
|
||||||
<file>themes/qet-dark/16x16/folio-new.png</file>
|
|
||||||
<file>themes/qet-dark/16x16/folio-properties.png</file>
|
|
||||||
<file>themes/qet-dark/16x16/folio-ref-coming.png</file>
|
<file>themes/qet-dark/16x16/folio-ref-coming.png</file>
|
||||||
<file>themes/qet-dark/16x16/go-company.png</file>
|
<file>themes/qet-dark/16x16/go-company.png</file>
|
||||||
<file>themes/qet-dark/16x16/go-down-double.png</file>
|
<file>themes/qet-dark/16x16/go-down-double.png</file>
|
||||||
@@ -348,7 +343,6 @@
|
|||||||
<file>themes/qet-dark/16x16/item-copy.png</file>
|
<file>themes/qet-dark/16x16/item-copy.png</file>
|
||||||
<file>themes/qet-dark/16x16/item-move.png</file>
|
<file>themes/qet-dark/16x16/item-move.png</file>
|
||||||
<file>themes/qet-dark/16x16/kdenlive-show-video.png</file>
|
<file>themes/qet-dark/16x16/kdenlive-show-video.png</file>
|
||||||
<file>themes/qet-dark/16x16/label.png</file>
|
|
||||||
<file>themes/qet-dark/16x16/list-add.png</file>
|
<file>themes/qet-dark/16x16/list-add.png</file>
|
||||||
<file>themes/qet-dark/16x16/list-remove.png</file>
|
<file>themes/qet-dark/16x16/list-remove.png</file>
|
||||||
<file>themes/qet-dark/16x16/masquer.png</file>
|
<file>themes/qet-dark/16x16/masquer.png</file>
|
||||||
@@ -381,15 +375,7 @@
|
|||||||
<file>themes/qet-dark/22x22/arrow-right.png</file>
|
<file>themes/qet-dark/22x22/arrow-right.png</file>
|
||||||
<file>themes/qet-dark/22x22/configure-toolbars.png</file>
|
<file>themes/qet-dark/22x22/configure-toolbars.png</file>
|
||||||
<file>themes/qet-dark/22x22/configure.png</file>
|
<file>themes/qet-dark/22x22/configure.png</file>
|
||||||
<file>themes/qet-dark/22x22/diagram.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/diagram_add.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/folio-new.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/diagram_bg.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/diagram_del.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/folio-delete.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/dialog-cancel.png</file>
|
<file>themes/qet-dark/22x22/dialog-cancel.png</file>
|
||||||
<file>themes/qet-dark/22x22/dialog-information.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/folio-properties.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/dialog-ok.png</file>
|
<file>themes/qet-dark/22x22/dialog-ok.png</file>
|
||||||
<file>themes/qet-dark/22x22/document-export.png</file>
|
<file>themes/qet-dark/22x22/document-export.png</file>
|
||||||
<file>themes/qet-dark/22x22/document-import.png</file>
|
<file>themes/qet-dark/22x22/document-import.png</file>
|
||||||
@@ -425,7 +411,6 @@
|
|||||||
<file>themes/qet-dark/22x22/go-up.png</file>
|
<file>themes/qet-dark/22x22/go-up.png</file>
|
||||||
<file>themes/qet-dark/22x22/grid.png</file>
|
<file>themes/qet-dark/22x22/grid.png</file>
|
||||||
<file>themes/qet-dark/22x22/insert-image.png</file>
|
<file>themes/qet-dark/22x22/insert-image.png</file>
|
||||||
<file>themes/qet-dark/22x22/label.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/landscape.png</file>
|
<file>themes/qet-dark/22x22/landscape.png</file>
|
||||||
<file>themes/qet-dark/22x22/line.png</file>
|
<file>themes/qet-dark/22x22/line.png</file>
|
||||||
<file>themes/qet-dark/22x22/list-add.png</file>
|
<file>themes/qet-dark/22x22/list-add.png</file>
|
||||||
@@ -436,7 +421,6 @@
|
|||||||
<file>themes/qet-dark/22x22/object-locked.png</file>
|
<file>themes/qet-dark/22x22/object-locked.png</file>
|
||||||
<file>themes/qet-dark/22x22/object-rotate-right.png</file>
|
<file>themes/qet-dark/22x22/object-rotate-right.png</file>
|
||||||
<file>themes/qet-dark/22x22/object-unlocked.png</file>
|
<file>themes/qet-dark/22x22/object-unlocked.png</file>
|
||||||
<file>themes/qet-dark/22x22/pdf-import.png</file>
|
|
||||||
<file>themes/qet-dark/22x22/polygon.png</file>
|
<file>themes/qet-dark/22x22/polygon.png</file>
|
||||||
<file>themes/qet-dark/22x22/portrait.png</file>
|
<file>themes/qet-dark/22x22/portrait.png</file>
|
||||||
<file>themes/qet-dark/22x22/preferences-desktop-user.png</file>
|
<file>themes/qet-dark/22x22/preferences-desktop-user.png</file>
|
||||||
@@ -472,8 +456,13 @@
|
|||||||
<file>themes/qet-dark/48x48/user-away.png</file>
|
<file>themes/qet-dark/48x48/user-away.png</file>
|
||||||
<file>themes/qet-dark/48x48/view-pim-journal.png</file>
|
<file>themes/qet-dark/48x48/view-pim-journal.png</file>
|
||||||
<file>themes/qet-dark/128x128/plasmagik.png</file>
|
<file>themes/qet-dark/128x128/plasmagik.png</file>
|
||||||
<file>themes/qet-dark/128x128/printer.png</file>
|
|
||||||
<file>themes/qet-dark/128x128/settings.png</file>
|
<file>themes/qet-dark/128x128/settings.png</file>
|
||||||
|
<file>themes/qet-dark/scalable/diagram.svg</file>
|
||||||
|
<file>themes/qet-dark/scalable/folio-delete.svg</file>
|
||||||
|
<file>themes/qet-dark/scalable/folio-new.svg</file>
|
||||||
|
<file>themes/qet-dark/scalable/folio-properties.svg</file>
|
||||||
|
<file>themes/qet-dark/scalable/label.svg</file>
|
||||||
|
<file>themes/qet-dark/scalable/pdf-import.svg</file>
|
||||||
<file>themes/qet-dark/scalable/edit-opacity.svg</file>
|
<file>themes/qet-dark/scalable/edit-opacity.svg</file>
|
||||||
<file>themes/qet-dark/scalable/image-flip-horizontal.svg</file>
|
<file>themes/qet-dark/scalable/image-flip-horizontal.svg</file>
|
||||||
<file>themes/qet-dark/scalable/image-flip-vertical.svg</file>
|
<file>themes/qet-dark/scalable/image-flip-vertical.svg</file>
|
||||||
@@ -482,5 +471,7 @@
|
|||||||
<file>themes/qet-dark/scalable/ellipse-to-bezier.svg</file>
|
<file>themes/qet-dark/scalable/ellipse-to-bezier.svg</file>
|
||||||
<file>themes/qet-dark/scalable/rect-to-bezier.svg</file>
|
<file>themes/qet-dark/scalable/rect-to-bezier.svg</file>
|
||||||
<file>themes/qet-dark/scalable/rect-to-polyline.svg</file>
|
<file>themes/qet-dark/scalable/rect-to-polyline.svg</file>
|
||||||
|
<file alias="themes/qet-dark/128x128/document-export.png">128x128/document-export.png</file>
|
||||||
|
<file alias="themes/qet-dark/128x128/printer.png">128x128/printer.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#4d4d4d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#4d4d4d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#4d4d4d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 17 18 h 1 v 1 h -1 Z M 17 19 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#4d4d4d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 8 h 1 v 1 h -1 Z M 6 10 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 7 10 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 8 h 1 v 1 h -1 Z M 8 10 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 8 h 1 v 1 h -1 Z M 10 10 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 8 h 1 v 1 h -1 Z M 12 10 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 8 h 1 v 1 h -1 Z M 14 10 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 8 h 1 v 1 h -1 Z M 15 10 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#4d4d4d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 14 h 1 v 1 h -1 Z M 4 15 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 14 h 1 v 1 h -1 Z M 5 15 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 14 h 1 v 1 h -1 Z M 6 15 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 14 h 1 v 1 h -1 Z M 7 15 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 14 h 1 v 1 h -1 Z M 8 15 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 14 h 1 v 1 h -1 Z M 9 15 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 14 h 1 v 1 h -1 Z M 10 15 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 14 h 1 v 1 h -1 Z M 12 15 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 14 h 1 v 1 h -1 Z M 13 15 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 14 h 1 v 1 h -1 Z M 14 15 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 14 h 1 v 1 h -1 Z M 15 15 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 14 h 1 v 1 h -1 Z M 16 15 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#4d4d4d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 3 h 16 v 1 h -16 Z M 3 4 h 1 v 15 h -1 Z M 4 18 h 10 v 1 h -10 Z M 18 4 h 1 v 8 h -1 Z M 16 14 h 1 v 5 h -1 Z M 14 16 h 5 v 1 h -5 Z" class="ColorScheme-Text"/>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 5 7 h 1 v 1 h -1 Z M 6 7 h 1 v 1 h -1 Z M 7 7 h 1 v 1 h -1 Z M 5 8 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 5 9 h 1 v 1 h -1 Z M 6 9 h 1 v 1 h -1 Z M 7 9 h 1 v 1 h -1 Z M 5 10 h 1 v 1 h -1 Z M 5 11 h 1 v 1 h -1 Z M 9 7 h 1 v 1 h -1 Z M 10 7 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 9 9 h 1 v 1 h -1 Z M 11 9 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 9 11 h 1 v 1 h -1 Z M 10 11 h 1 v 1 h -1 Z M 13 7 h 1 v 1 h -1 Z M 14 7 h 1 v 1 h -1 Z M 15 7 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 9 h 1 v 1 h -1 Z M 14 9 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 11 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 163 B |
|
Before Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 702 B |
|
Before Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 667 B |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcdcdc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcdcdc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcdcdc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 17 18 h 1 v 1 h -1 Z M 17 19 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcdcdc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 8 h 1 v 1 h -1 Z M 6 10 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 7 10 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 8 h 1 v 1 h -1 Z M 8 10 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 8 h 1 v 1 h -1 Z M 10 10 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 8 h 1 v 1 h -1 Z M 12 10 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 8 h 1 v 1 h -1 Z M 14 10 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 8 h 1 v 1 h -1 Z M 15 10 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcdcdc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 14 h 1 v 1 h -1 Z M 4 15 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 14 h 1 v 1 h -1 Z M 5 15 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 14 h 1 v 1 h -1 Z M 6 15 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 14 h 1 v 1 h -1 Z M 7 15 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 14 h 1 v 1 h -1 Z M 8 15 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 14 h 1 v 1 h -1 Z M 9 15 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 14 h 1 v 1 h -1 Z M 10 15 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 14 h 1 v 1 h -1 Z M 12 15 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 14 h 1 v 1 h -1 Z M 13 15 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 14 h 1 v 1 h -1 Z M 14 15 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 14 h 1 v 1 h -1 Z M 15 15 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 14 h 1 v 1 h -1 Z M 16 15 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcdcdc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(1,1)">
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 3 h 16 v 1 h -16 Z M 3 4 h 1 v 15 h -1 Z M 4 18 h 10 v 1 h -10 Z M 18 4 h 1 v 8 h -1 Z M 16 14 h 1 v 5 h -1 Z M 14 16 h 5 v 1 h -5 Z" class="ColorScheme-Text"/>
|
||||||
|
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 5 7 h 1 v 1 h -1 Z M 6 7 h 1 v 1 h -1 Z M 7 7 h 1 v 1 h -1 Z M 5 8 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 5 9 h 1 v 1 h -1 Z M 6 9 h 1 v 1 h -1 Z M 7 9 h 1 v 1 h -1 Z M 5 10 h 1 v 1 h -1 Z M 5 11 h 1 v 1 h -1 Z M 9 7 h 1 v 1 h -1 Z M 10 7 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 9 9 h 1 v 1 h -1 Z M 11 9 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 9 11 h 1 v 1 h -1 Z M 10 11 h 1 v 1 h -1 Z M 13 7 h 1 v 1 h -1 Z M 14 7 h 1 v 1 h -1 Z M 15 7 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 9 h 1 v 1 h -1 Z M 14 9 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 11 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -34,10 +34,27 @@ expects, so QIcon::fromTheme("name") finds them. The dark theme only holds
|
|||||||
the icons that need a dark variant: black line art. Colored icons are not
|
the icons that need a dark variant: black line art. Colored icons are not
|
||||||
touched; the dark theme inherits them from the light one.
|
touched; the dark theme inherits them from the light one.
|
||||||
|
|
||||||
|
Qt inherits by name, not by size: once a name has any file in the dark
|
||||||
|
theme, the parent theme is never consulted for that name, and a size the
|
||||||
|
dark theme lacks is served by scaling the nearest dark file. An icon that
|
||||||
|
is line art at 22 pixels and colored at 128 (the printer) would then come
|
||||||
|
out as the 22 pixel copy scaled up on a dark palette. So for every name
|
||||||
|
the dark theme holds, the .qrc also aliases the light files of the sizes
|
||||||
|
the dark theme does not have, when they read on the dark window (3:1,
|
||||||
|
measured as tests/qttest/tst_qeticons.cpp does); a light file that does
|
||||||
|
not is left out, and Qt scales the nearest dark copy as before.
|
||||||
|
|
||||||
An icon counts as line art when fewer than 20% of its visible pixels are
|
An icon counts as line art when fewer than 20% of its visible pixels are
|
||||||
saturated. Its dark copy keeps hue and alpha and inverts lightness, scaled
|
saturated. Its dark copy keeps hue and alpha and inverts lightness, scaled
|
||||||
so pure black becomes (220, 220, 220), the dark palette's text color.
|
so pure black becomes (220, 220, 220), the dark palette's text color.
|
||||||
|
|
||||||
|
An icon drawn as a light object is left alone even when it is not
|
||||||
|
saturated: a page sheet, a printer body, the white half of the background
|
||||||
|
swatch. Such art already reads on a dark toolbar, and inverting it would
|
||||||
|
turn the white body black (the PDF import icon, GitHub #919). The test is
|
||||||
|
the share of visible pixels that are near white: 30% or more and the icon
|
||||||
|
inherits from the light theme untouched.
|
||||||
|
|
||||||
Requires Pillow. Idempotent: running it twice changes nothing.
|
Requires Pillow. Idempotent: running it twice changes nothing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -64,16 +81,21 @@ SIZES = ["16x16", "22x22", "32x32", "48x48", "128x128"]
|
|||||||
|
|
||||||
# Table entries in sources/qeticons.cpp that pair a 16 pixel file with a
|
# Table entries in sources/qeticons.cpp that pair a 16 pixel file with a
|
||||||
# 22 pixel file of another name. The theme needs one name per icon, so
|
# 22 pixel file of another name. The theme needs one name per icon, so
|
||||||
# the 22 pixel file is exposed under the 16 pixel name as well.
|
# the 22 pixel file is exposed under the 16 pixel name as well. The folio
|
||||||
|
# icons that used to be listed here are SVGs now (ico/scalable/).
|
||||||
ALIASES = {
|
ALIASES = {
|
||||||
"22x22/conductor2.png": "conductor-reset",
|
"22x22/conductor2.png": "conductor-reset",
|
||||||
"22x22/diagram_add.png": "folio-new",
|
|
||||||
"22x22/diagram_del.png": "folio-delete",
|
|
||||||
"22x22/dialog-information.png": "folio-properties",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# SVG icons referenced from sources/qeticons.cpp.
|
# SVG icons referenced from sources/qeticons.cpp. ico/scalable/ holds the
|
||||||
|
# ones drawn for QET as vectors; one file serves every size.
|
||||||
SVGS = [
|
SVGS = [
|
||||||
|
"scalable/diagram.svg",
|
||||||
|
"scalable/folio-delete.svg",
|
||||||
|
"scalable/folio-new.svg",
|
||||||
|
"scalable/folio-properties.svg",
|
||||||
|
"scalable/label.svg",
|
||||||
|
"scalable/pdf-import.svg",
|
||||||
"breeze-icons/scalable/apps/hidef/edit-opacity.svg",
|
"breeze-icons/scalable/apps/hidef/edit-opacity.svg",
|
||||||
"breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg",
|
"breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg",
|
||||||
"breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg",
|
"breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg",
|
||||||
@@ -85,8 +107,12 @@ SVGS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
SATURATED_FRACTION = 0.20 # at or above this an icon is "colored"
|
SATURATED_FRACTION = 0.20 # at or above this an icon is "colored"
|
||||||
|
WHITE_LIGHTNESS = 0.85 # a visible pixel this light counts as white
|
||||||
|
WHITE_FRACTION = 0.30 # at or above this an icon is "light art"
|
||||||
INK = 220 / 255.0 # lightness of pure black after inversion
|
INK = 220 / 255.0 # lightness of pure black after inversion
|
||||||
SVG_INK = "#dcdcdc"
|
SVG_INK = "#dcdcdc"
|
||||||
|
DARK_WINDOW = (53, 53, 53) # QET::Palette::fusionDark() window color
|
||||||
|
DARK_RATIO = 3.0 # what tst_qeticons requires of a dark theme file
|
||||||
|
|
||||||
|
|
||||||
def visible_pixels(image):
|
def visible_pixels(image):
|
||||||
@@ -107,6 +133,38 @@ def is_line_art(image):
|
|||||||
return saturated / len(pixels) < SATURATED_FRACTION
|
return saturated / len(pixels) < SATURATED_FRACTION
|
||||||
|
|
||||||
|
|
||||||
|
def has_light_fill(image):
|
||||||
|
"""True when the icon is mostly white: a page, a sheet, a light body."""
|
||||||
|
pixels = visible_pixels(image)
|
||||||
|
if not pixels:
|
||||||
|
return False
|
||||||
|
white = sum(1 for r, g, b, _ in pixels
|
||||||
|
if colorsys.rgb_to_hls(r / 255, g / 255, b / 255)[1] > WHITE_LIGHTNESS)
|
||||||
|
return white / len(pixels) >= WHITE_FRACTION
|
||||||
|
|
||||||
|
|
||||||
|
def relative_luminance(rgb):
|
||||||
|
def linear(c):
|
||||||
|
c /= 255.0
|
||||||
|
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
|
||||||
|
r, g, b = rgb
|
||||||
|
return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b)
|
||||||
|
|
||||||
|
|
||||||
|
def reads_on_dark(image):
|
||||||
|
"""True when the icon's mean visible color reaches DARK_RATIO against
|
||||||
|
the dark window, the test tst_qeticons applies to every dark file."""
|
||||||
|
pixels = visible_pixels(image)
|
||||||
|
if not pixels:
|
||||||
|
return False
|
||||||
|
n = len(pixels)
|
||||||
|
mean = (sum(p[0] for p in pixels) // n, sum(p[1] for p in pixels) // n,
|
||||||
|
sum(p[2] for p in pixels) // n)
|
||||||
|
lighter, darker = sorted((relative_luminance(mean), relative_luminance(DARK_WINDOW)),
|
||||||
|
reverse=True)
|
||||||
|
return (lighter + 0.05) / (darker + 0.05) >= DARK_RATIO
|
||||||
|
|
||||||
|
|
||||||
def invert_lightness(image):
|
def invert_lightness(image):
|
||||||
"""Invert lightness, keeping hue, saturation and alpha.
|
"""Invert lightness, keeping hue, saturation and alpha.
|
||||||
|
|
||||||
@@ -177,7 +235,7 @@ def main():
|
|||||||
light = [] # (alias, source) pairs, paths relative to ico/
|
light = [] # (alias, source) pairs, paths relative to ico/
|
||||||
dark = [] # paths relative to ico/, files exist on disk
|
dark = [] # paths relative to ico/, files exist on disk
|
||||||
changed = 0
|
changed = 0
|
||||||
line_art = colored = 0
|
line_art = colored = light_art = 0
|
||||||
|
|
||||||
for size in SIZES:
|
for size in SIZES:
|
||||||
folder = ICO / size
|
folder = ICO / size
|
||||||
@@ -188,7 +246,10 @@ def main():
|
|||||||
names.append(ALIASES[rel])
|
names.append(ALIASES[rel])
|
||||||
image = Image.open(png).convert("RGBA")
|
image = Image.open(png).convert("RGBA")
|
||||||
art = is_line_art(image)
|
art = is_line_art(image)
|
||||||
if art:
|
if art and has_light_fill(image):
|
||||||
|
art = False
|
||||||
|
light_art += 1
|
||||||
|
elif art:
|
||||||
line_art += 1
|
line_art += 1
|
||||||
dark_image = None
|
dark_image = None
|
||||||
else:
|
else:
|
||||||
@@ -213,6 +274,29 @@ def main():
|
|||||||
changed += write_if_changed(target, text)
|
changed += write_if_changed(target, text)
|
||||||
dark.append(f"themes/qet-dark/scalable/{name}")
|
dark.append(f"themes/qet-dark/scalable/{name}")
|
||||||
|
|
||||||
|
# Complete each dark name with the light files of its other sizes
|
||||||
|
# that read on a dark window (see the module docstring): aliases, no
|
||||||
|
# copies.
|
||||||
|
dark_sizes = {}
|
||||||
|
for path in dark:
|
||||||
|
size, name = path.split("/")[2:]
|
||||||
|
dark_sizes.setdefault(name, set()).add(size)
|
||||||
|
dark_aliases = []
|
||||||
|
for alias, source in light:
|
||||||
|
size, name = alias.split("/")[2:]
|
||||||
|
if name in dark_sizes and size not in dark_sizes[name] \
|
||||||
|
and reads_on_dark(Image.open(ICO / source).convert("RGBA")):
|
||||||
|
dark_aliases.append((f"themes/qet-dark/{size}/{name}", source))
|
||||||
|
|
||||||
|
# Drop dark files from an earlier run that are no longer generated, so
|
||||||
|
# a reclassified icon falls back to the light theme instead of keeping
|
||||||
|
# a stale copy.
|
||||||
|
for stale in sorted((THEMES / "qet-dark").glob("*/*")):
|
||||||
|
if stale.is_file() and stale.name != "index.theme" \
|
||||||
|
and str(stale.relative_to(ICO)) not in dark:
|
||||||
|
stale.unlink()
|
||||||
|
changed += 1
|
||||||
|
|
||||||
dirs = SIZES + ["scalable"]
|
dirs = SIZES + ["scalable"]
|
||||||
changed += write_if_changed(THEMES / "qet" / "index.theme",
|
changed += write_if_changed(THEMES / "qet" / "index.theme",
|
||||||
index_theme("qet", "QElectroTech icons", dirs))
|
index_theme("qet", "QElectroTech icons", dirs))
|
||||||
@@ -228,11 +312,13 @@ def main():
|
|||||||
qrc.append(f' <file alias="{alias}">{source}</file>')
|
qrc.append(f' <file alias="{alias}">{source}</file>')
|
||||||
for path in dark:
|
for path in dark:
|
||||||
qrc.append(f" <file>{path}</file>")
|
qrc.append(f" <file>{path}</file>")
|
||||||
|
for alias, source in dark_aliases:
|
||||||
|
qrc.append(f' <file alias="{alias}">{source}</file>')
|
||||||
qrc += [" </qresource>", "</RCC>", ""]
|
qrc += [" </qresource>", "</RCC>", ""]
|
||||||
changed += write_if_changed(QRC, "\n".join(qrc))
|
changed += write_if_changed(QRC, "\n".join(qrc))
|
||||||
|
|
||||||
print(f"{line_art} line-art icons, {colored} colored icons, {len(SVGS)} SVGs; "
|
print(f"{line_art} line-art icons, {light_art} light icons, {colored} colored icons, "
|
||||||
f"{changed} files written")
|
f"{len(SVGS)} SVGs; {changed} files written or removed")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,349 @@
|
|||||||
|
# qet-mcp — a Model Context Protocol server for QElectroTech projects
|
||||||
|
|
||||||
|
A small stdio MCP server that lets an AI assistant read and verify
|
||||||
|
QElectroTech projects: what is in a project, what an edit actually
|
||||||
|
changed, and what a whole corpus of projects contains.
|
||||||
|
|
||||||
|
It has **no third-party dependencies** — Python 3.9+ and the standard
|
||||||
|
library only. The MCP SDK is not required.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Verifying a change by screenshot is unreliable, and this tool exists
|
||||||
|
because that unreliability produced two wrong conclusions in one review
|
||||||
|
session:
|
||||||
|
|
||||||
|
- A drag of a multi-element selection *looked* like it had left the
|
||||||
|
symbols behind and detached their labels. Diffing the saved file showed
|
||||||
|
all four elements had moved by an identical `(0, -80)` and **no label
|
||||||
|
had moved at all**. A bug report was one step away from being filed.
|
||||||
|
- An "Apply" button *looked* like it did nothing. It was disabled,
|
||||||
|
because a required field was empty.
|
||||||
|
|
||||||
|
Both times the pixels misled and the model told the truth. So the tools
|
||||||
|
here read the model.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
| Tool | What it answers |
|
||||||
|
|---|---|
|
||||||
|
| `qet_project_info` | title, format version, folios, element and conductor counts |
|
||||||
|
| `qet_elements` | placed elements: uuid, type, position, label, information bag |
|
||||||
|
| `qet_conductors` | conductors and their documentation fields; filter by attribute |
|
||||||
|
| `qet_diff` | **what an edit actually changed** — element moves, adds, removes, relabels; conductor changes; and folio fields, texts, shapes, images, symbol text fields and terminal strips |
|
||||||
|
| `qet_scan` | sweep a directory of projects, counting nodes carrying an attribute |
|
||||||
|
| `qet_element_info` | a `.elmt`: translated names, terminals, info fields, part counts |
|
||||||
|
| `qet_export` | run a headless export (pdf, png, svg, bom, cables, wires, wiring, nets, links, info) |
|
||||||
|
| `qet_edit` | **change a project** — place, move, rotate, label, wire, number, cross-reference, add text, shapes and images, restyle a symbol's text fields, delete; then diff the result |
|
||||||
|
| `qet_element_build` | **author a `.elmt`** — draw a new symbol, with terminals to wire it by |
|
||||||
|
| `qet_project_new` | **start from nothing** — an empty project with a title and folios |
|
||||||
|
| `qet_element_search` | **find a symbol** in a collection by name (any language), type or terminal count |
|
||||||
|
| `qet_check` | **design-rule checks** — duplicate labels, unlabelled masters, unnumbered conductors, empty folios |
|
||||||
|
| `qet_query` | **ask the project database** — read-only SQL over the views and tables |
|
||||||
|
|
||||||
|
`qet_export` and `qet_edit` launch QElectroTech. Everything else parses the
|
||||||
|
file directly, which is faster, needs no display, and cannot be confused by
|
||||||
|
a dialog.
|
||||||
|
|
||||||
|
## Running it
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# list the tools and exit
|
||||||
|
misc/qet-mcp/qet_mcp.py --list
|
||||||
|
|
||||||
|
# speak MCP on stdin/stdout
|
||||||
|
misc/qet-mcp/qet_mcp.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Register it with an MCP client, for example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"qet": {
|
||||||
|
"command": "python3",
|
||||||
|
"args": ["/path/to/qelectrotech/misc/qet-mcp/qet_mcp.py"],
|
||||||
|
"env": {
|
||||||
|
"QET_MCP_WORKSPACE": "/home/you/drawings",
|
||||||
|
"QET_ENABLE_SCRIPTING": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Five tools need scripting switched on
|
||||||
|
|
||||||
|
A QElectroTech with JavaScript scripting switched off refuses `--run`, and
|
||||||
|
off is the default from
|
||||||
|
[#984](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/984)
|
||||||
|
onwards. Five tools here drive it that way and stop working until it is
|
||||||
|
turned on:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| need `QET_ENABLE_SCRIPTING=1` | `qet_query`, `qet_continuity`, `qet_check`, `qet_project_new`, `qet_edit` |
|
||||||
|
| unaffected | everything else — they read the `.qet` directly, or, in `qet_export`'s case, use a plain CLI flag |
|
||||||
|
|
||||||
|
The variable goes in the environment this server is started in, which for an
|
||||||
|
MCP client is the `env` block above; the server passes its environment
|
||||||
|
straight through to QElectroTech. It does not set the variable itself, on
|
||||||
|
purpose — a switch a program turns on for itself is not a switch. Whoever
|
||||||
|
configured this server and pointed it at a QElectroTech binary made that
|
||||||
|
choice, and their interactive QElectroTech keeps whatever its own setting
|
||||||
|
says.
|
||||||
|
|
||||||
|
Without it, those five come back `"ok": false` with a `hint` naming the
|
||||||
|
variable. Older builds, from before the setting existed, need nothing.
|
||||||
|
|
||||||
|
## What the server is allowed to touch
|
||||||
|
|
||||||
|
Every path in a tool call is chosen by the model, so without a policy this
|
||||||
|
server would be a read/write primitive for anything the operating system
|
||||||
|
lets the process reach: read any project on the disk, export one somewhere
|
||||||
|
else, overwrite an unrelated file, embed an arbitrary local image or PDF.
|
||||||
|
|
||||||
|
So **data paths are confined to a workspace**:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| `QET_MCP_WORKSPACE` | the directories tool calls may read and write, separated by `:` (`;` on Windows) |
|
||||||
|
| unset | the directory the server was started in |
|
||||||
|
| `QET_MCP_ALLOW_ANY_PATH=1` | turns the check off entirely |
|
||||||
|
|
||||||
|
Set the workspace to the folder your drawings live in. A path outside it is
|
||||||
|
refused with an error naming what was allowed; symlinks are resolved first,
|
||||||
|
so a link planted inside the workspace is judged by where it points.
|
||||||
|
|
||||||
|
Two arguments are deliberately **not** confined: `binary` (the
|
||||||
|
`qelectrotech` executable) and `elements_dir` (the element collection).
|
||||||
|
Those are configuration, chosen once by whoever runs the server, and both
|
||||||
|
normally live in `/usr` or a build tree — outside any sensible workspace.
|
||||||
|
Confining them would reject the ordinary case while stopping nothing.
|
||||||
|
|
||||||
|
`QET_MCP_ALLOW_ANY_PATH=1` is equivalent to granting the client local
|
||||||
|
filesystem access with this process's privileges. It exists so that is a
|
||||||
|
deliberate choice rather than the default.
|
||||||
|
|
||||||
|
**Nothing is overwritten unasked.** `qet_export`, `qet_edit`,
|
||||||
|
`qet_project_new` and `qet_element_build` refuse an `output` that already
|
||||||
|
exists unless the call passes `"overwrite": true`. Replacing a file is the
|
||||||
|
one step this server cannot undo, so it is the one step it will not take on
|
||||||
|
its own.
|
||||||
|
|
||||||
|
The confinement is applied where tool arguments enter the server, not inside
|
||||||
|
each tool. Importing `qet_mcp` and calling `tool_export()` from your own
|
||||||
|
Python is not confined and is not meant to be — that is your code calling a
|
||||||
|
library, and you already chose the paths.
|
||||||
|
|
||||||
|
## Worked examples
|
||||||
|
|
||||||
|
**What did that edit change?**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"name": "qet_diff", "arguments": {"before": "a.qet", "after": "b.qet"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
"elements": { "moved_count": 4,
|
||||||
|
"distinct_move_deltas": [[0.0, -80.0]],
|
||||||
|
"relabelled": [], "info_changed": [] }
|
||||||
|
```
|
||||||
|
|
||||||
|
Four elements moved by one uniform delta; nothing was relabelled. That is
|
||||||
|
the answer a screenshot gave wrongly.
|
||||||
|
|
||||||
|
**Draw something, and check it landed**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"name": "qet_edit", "arguments": {
|
||||||
|
"binary": "/path/to/qelectrotech",
|
||||||
|
"project": "in.qet", "output": "out.qet",
|
||||||
|
"elements_dir": "/path/to/qelectrotech/elements",
|
||||||
|
"operations": [
|
||||||
|
{"op": "add_folio", "id": "f"},
|
||||||
|
{"op": "set_folio_title", "folio": "$f", "title": "Starter"},
|
||||||
|
{"op": "add_element", "id": "k1", "folio": "$f", "path": "common://.../coil.elmt", "x": 100, "y": 100},
|
||||||
|
{"op": "add_element", "id": "k2", "folio": "$f", "path": "common://.../coil.elmt", "x": 320, "y": 100},
|
||||||
|
{"op": "add_conductor", "folio": "$f", "from": "$k1", "from_terminal": 0, "to": "$k2", "to_terminal": 0},
|
||||||
|
{"op": "set_conductor", "folio": "$f", "element": "$k1", "terminal": 0, "property": "num", "value": "W7"},
|
||||||
|
{"op": "set_label", "folio": "$f", "element": "$k1", "label": "KM1"}
|
||||||
|
]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
An op that creates something takes an `"id"`; later ops name it as `"$id"`.
|
||||||
|
Terminals are addressed by index — top to bottom, then left to right, **not**
|
||||||
|
the order the `.elmt` lists them. `qet_element_info` and `qet_element_search`
|
||||||
|
both report that index order. The answer carries a per-operation result
|
||||||
|
*and* a `qet_diff`, because "addConductor → true" says the call was
|
||||||
|
accepted, not that the file came out right:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"diff": {"elements": {"before": 11, "after": 13, "added": ["{0aa3…}", "{6f63…}"]},
|
||||||
|
"conductors": {"before": 47, "after": 48, "added": ["4:{0aa3…}/{2904…}--{6f63…}/{2904…}"],
|
||||||
|
"removed": []}}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Draw a symbol that does not exist yet**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"name": "qet_element_build", "arguments": {
|
||||||
|
"output": "/path/to/collection/99_custom/my_resistor.elmt",
|
||||||
|
"names": {"en": "Test resistor", "fr": "Résistance de test"},
|
||||||
|
"parts": [
|
||||||
|
{"type": "rect", "x": -10, "y": -20, "width": 20, "height": 40},
|
||||||
|
{"type": "line", "x1": 0, "y1": -30, "x2": 0, "y2": -20},
|
||||||
|
{"type": "line", "x1": 0, "y1": 20, "x2": 0, "y2": 30},
|
||||||
|
{"type": "text", "x": 14, "y": -4, "text": "R"}
|
||||||
|
],
|
||||||
|
"terminals": [{"x": 0, "y": -30, "orientation": "n", "name": "1"},
|
||||||
|
{"x": 0, "y": 30, "orientation": "s", "name": "2"}]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then place it with `qet_edit` like any catalogue element. Unlike a
|
||||||
|
project, a `.elmt` is not rewritten by QElectroTech on a round trip, so
|
||||||
|
generating one here is safe in a way that generating a `.qet` would not
|
||||||
|
be — there is no `toXml()` waiting to drop what this writer did not know
|
||||||
|
to emit.
|
||||||
|
|
||||||
|
**Ask a question the XML cannot answer**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"name": "qet_query", "arguments": {
|
||||||
|
"binary": "/path/to/qelectrotech", "project": "industrial.qet",
|
||||||
|
"sql": "SELECT label, COUNT(*) AS n FROM element_nomenclature_view WHERE label <> '' GROUP BY label HAVING n > 1 ORDER BY n DESC"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
"rows": [{"label": "V6", "n": 7}, {"label": "V5", "n": 6}, {"label": "V4", "n": 6}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Duplicate element labels in a shipped example — a design-rule question,
|
||||||
|
answered by the database that already knew it.
|
||||||
|
|
||||||
|
**How much of a corpus uses a field?**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"name": "qet_scan",
|
||||||
|
"arguments": {"directory": "examples", "tag": "conductor", "attribute": "cable"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "files": 24, "total": 3190, "non_empty": 0, "distinct_values": [] }
|
||||||
|
```
|
||||||
|
|
||||||
|
Across the shipped examples: 3190 conductors, not one with a cable value.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 test_qet_mcp.py # unit + protocol, no QElectroTech needed
|
||||||
|
QET_BINARY=/path/to/qelectrotech \
|
||||||
|
QET_ELEMENTS=/path/to/qelectrotech/elements \
|
||||||
|
QET_EXAMPLES=/path/to/qelectrotech/examples \
|
||||||
|
QET_ENABLE_SCRIPTING=1 \
|
||||||
|
python3 test_qet_mcp.py # everything
|
||||||
|
```
|
||||||
|
|
||||||
|
`QET_ENABLE_SCRIPTING=1` matters from #984 onwards: without it the
|
||||||
|
integration tests that drive QElectroTech through a script all fail, and
|
||||||
|
they fail as "the edit did nothing" rather than as "scripting is off", which
|
||||||
|
reads like a regression in the thing under test.
|
||||||
|
|
||||||
|
176 tests in three layers: unit (validation, script generation, the terminal
|
||||||
|
order rule, the diff, the part schema), the real stdio transport, and
|
||||||
|
integration against a built QElectroTech. Several exist because the
|
||||||
|
behaviour they pin was once wrong and looked right, and say so in their
|
||||||
|
docstrings. To check the suite itself rather than trust it, each of those
|
||||||
|
bugs was reintroduced in turn and the suite confirmed to fail: ten in the
|
||||||
|
Python, plus the hang guard on `addConductor` and the database refresh in
|
||||||
|
`ConductorCreator` in the C++.
|
||||||
|
|
||||||
|
## Notes and limits
|
||||||
|
|
||||||
|
- **The project database is reachable now, through `qet_query`.** It was
|
||||||
|
not when this server was written, which is why every other structural
|
||||||
|
tool here re-derives its answer from the XML. Prefer the views —
|
||||||
|
`element_nomenclature_view`, `project_summary_view`, `wiring_list_view`
|
||||||
|
— which exist to be queried; the underlying tables are how the cache is
|
||||||
|
arranged today and a column may move. Call `qet_query` with no `sql` to
|
||||||
|
list both. Only `SELECT` and `WITH` are accepted, which is the rule
|
||||||
|
QElectroTech applies to its own custom-query box, not one invented here.
|
||||||
|
An empty result and a failed query are told apart: `row_count` 0 with no
|
||||||
|
`error` means nothing matched, and a typo'd column name says so.
|
||||||
|
- **`qet_export` isolates its launch.** SingleApplication keys its socket
|
||||||
|
on `applicationFilePath()`, so a second launch of the same binary path
|
||||||
|
forwards its request to an already-running instance and returns *that*
|
||||||
|
process's answer with no error. The tool copies the binary to a unique
|
||||||
|
temporary path, gives it a private `HOME`, and runs it on the offscreen
|
||||||
|
platform. A symlink would not work: `applicationFilePath()` resolves it
|
||||||
|
back to the real path.
|
||||||
|
- **The CLI matches its flags exactly.** `--export-bom out.csv` is the
|
||||||
|
supported form; `--export-bom=out.csv` is not recognised as an export
|
||||||
|
at all, so the application starts its interface instead and a headless
|
||||||
|
run hangs. The tool uses the positional form.
|
||||||
|
- **Conductor identity is the hard part of `qet_diff`.** A conductor names
|
||||||
|
its ends with `terminal1`/`terminal2`, and the project format has two
|
||||||
|
schemes: folio-scoped integer ids in older files, terminal-definition
|
||||||
|
uuids plus `element1`/`element2` in newer ones. The integer ids are
|
||||||
|
**renumbered on every save**, so keying on them — which this tool did at
|
||||||
|
first — made all 47 conductors of an untouched folio read as removed and
|
||||||
|
re-added the moment the other side had been through QElectroTech, which
|
||||||
|
is exactly what `qet_edit` produces. They are now keyed by owning element
|
||||||
|
uuid plus terminal, which is stable across a save: measured at 0 colliding
|
||||||
|
keys over 3190 conductors in the 24 shipped examples, and 0 churn on a
|
||||||
|
no-op edit. Where an element predates persisted uuids the end cannot be
|
||||||
|
resolved and keeps a `#`-marked unstable key; the diff then reports
|
||||||
|
`unstable_keys` and says so rather than pretending to be comparable.
|
||||||
|
- **Texts, shapes and images have no uuid**, so `qet_diff` cannot say "the same
|
||||||
|
text, edited": an edited text reads as the old one removed and a new one
|
||||||
|
added, both shown. Shapes and images are keyed by position, so a restyle
|
||||||
|
or rescale *is* reported as a change to that item, but a move reads as a
|
||||||
|
removal plus an addition. The folio `version` attribute is left out of the
|
||||||
|
comparison on purpose: QElectroTech rewrites it on every save, and
|
||||||
|
including it made every folio of any re-saved project look edited.
|
||||||
|
- **Elements** written before persisted uuids fall back to a positional key,
|
||||||
|
which makes a move in such a file read as a remove plus an add.
|
||||||
|
- **`qet_edit` needs a build whose scripting API carries the drawing verbs.**
|
||||||
|
Against an older one it reports exactly which methods are missing and
|
||||||
|
changes nothing. `addElement` and the move/delete verbs shipped with the
|
||||||
|
scripting API; `addConductor`, `rotateElement`, `setElementLabel`,
|
||||||
|
`setElementInfo` and `setFolioTitle` are newer.
|
||||||
|
- **`elements_dir` is not optional for `common://` paths.** The sandboxed
|
||||||
|
run has its own empty HOME, so QElectroTech falls back to the compiled-in
|
||||||
|
collection path, which on a machine that never ran `make install` does not
|
||||||
|
exist. The only symptom is `addElement` reporting that a file plainly
|
||||||
|
present "does not resolve to an element". An absolute `.elmt` path works
|
||||||
|
without it.
|
||||||
|
- **`set_conductor` changes the whole potential, not one segment.** That is
|
||||||
|
what the application does — a wire number describes a potential — so name
|
||||||
|
a terminal carrying exactly one conductor and the change reaches every
|
||||||
|
conductor electrically joined to it. A terminal several conductors meet
|
||||||
|
at names none of them and is refused, so address a potential from one of
|
||||||
|
its leaves. Property names are the file's own, so `qet_conductors` reads
|
||||||
|
back exactly what was set.
|
||||||
|
- **`link_elements` takes a folio for each end**, because a master and its
|
||||||
|
slave are normally on different folios. Whether a pair may be linked is
|
||||||
|
decided by QElectroTech's own `isLinkable()`, so a script cannot make a
|
||||||
|
link the GUI would refuse.
|
||||||
|
- **An element must live inside a collection to be placeable.** This is
|
||||||
|
not about the path syntax: an absolute `.elmt` path works, but only if
|
||||||
|
the file sits under a directory QElectroTech knows as a collection.
|
||||||
|
Write it under the tree you pass as `elements_dir` and `qet_edit` can
|
||||||
|
place it, by absolute path or as `common://…`; write it anywhere else
|
||||||
|
and `add_element` reports only "does not resolve to an element".
|
||||||
|
- **`qet_element_build` computes the `.elmt` size header, and checks it.**
|
||||||
|
`width`/`height`/`hotspot_x`/`hotspot_y` relate to the drawing by a
|
||||||
|
containment constraint, not a formula — the declared box runs from
|
||||||
|
`(-hotspot_x, -hotspot_y)` to `(width - hotspot_x, height - hotspot_y)`
|
||||||
|
and the drawing must fit inside it. The shipped collection shows authors
|
||||||
|
picking their own margins (one element pads 2 units left and 3 right,
|
||||||
|
another 8 and 2), so there is no convention to copy, only an invariant
|
||||||
|
to satisfy. A drawing that escaped its box is the classic way a
|
||||||
|
hand-written element renders clipped in the collection panel while
|
||||||
|
looking fine in XML.
|
||||||
|
- **QElectroTech interrupts a script at 30 s** of its own accord, separately
|
||||||
|
from this tool's `timeout`. A very long operation list will hit that
|
||||||
|
first.
|
||||||
|
- **`qet_edit` never writes the input.** It saves to a separate file and
|
||||||
|
diffs the two, so the original is always the thing the diff is against.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<definition version="0.100.0" type="element" link_type="master" width="40" height="60" hotspot_x="17" hotspot_y="32">
|
||||||
|
<uuid uuid="{6d3714f6-a0e1-71b3-e4bd-5a35d4cbbec1}"/>
|
||||||
|
<names>
|
||||||
|
<name lang="ar">ملف KA بمحتفظ مغناطيسي</name>
|
||||||
|
<name lang="ca">Bobina</name>
|
||||||
|
<name lang="cs">Bistabilní remanentní relé</name>
|
||||||
|
<name lang="de">Remanenzrelais</name>
|
||||||
|
<name lang="el">Πηνίο με μανδάλωση</name>
|
||||||
|
<name lang="en">Coil</name>
|
||||||
|
<name lang="es">Bobina KA de remanencia</name>
|
||||||
|
<name lang="fr">Bobine KA à rémanence</name>
|
||||||
|
<name lang="hu">Tekercs</name>
|
||||||
|
<name lang="it">Bobina</name>
|
||||||
|
<name lang="nl">spoel remanent</name>
|
||||||
|
<name lang="nl_BE">Spoel KA remanent</name>
|
||||||
|
<name lang="pl">Cewka przekaźnika remanencyjnego</name>
|
||||||
|
<name lang="pt_BR">Bobina</name>
|
||||||
|
<name lang="ru">Обмотка</name>
|
||||||
|
<name lang="zh">剩磁保持线圈</name>
|
||||||
|
</names>
|
||||||
|
<kindInformations>
|
||||||
|
<kindInformation name="type">plc</kindInformation>
|
||||||
|
</kindInformations>
|
||||||
|
<informations>Author: The QElectroTech team
|
||||||
|
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
|
||||||
|
<description>
|
||||||
|
<rect x="14" y="-8" width="6" height="16" rx="0" ry="0" style="line-style:normal;line-weight:normal;filling:none;color:black" antialias="false"/>
|
||||||
|
<line x1="0" y1="-20" x2="0" y2="-8" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" antialias="false"/>
|
||||||
|
<dynamic_text x="2" y="11.33" z="5" text_width="-1" Halignment="AlignLeft" Valignment="AlignTop" frame="false" rotation="0" keep_visual_rotation="false" text_from="UserText" uuid="{647e33ed-520b-4d80-bbc4-7b31177b8f26}" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular">
|
||||||
|
<text>A2</text>
|
||||||
|
</dynamic_text>
|
||||||
|
<dynamic_text x="2" y="-24.67" z="6" text_width="-1" Halignment="AlignLeft" Valignment="AlignTop" frame="false" rotation="0" keep_visual_rotation="false" text_from="UserText" uuid="{fb1f8de9-70c0-4801-8dc7-12e6bf88d8f5}" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular">
|
||||||
|
<text>A1</text>
|
||||||
|
</dynamic_text>
|
||||||
|
<line x1="0" y1="8" x2="0" y2="20" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" antialias="false"/>
|
||||||
|
<dynamic_text x="30" y="-9.17" z="8" text_width="-1" Halignment="AlignLeft" Valignment="AlignTop" frame="false" rotation="0" keep_visual_rotation="false" text_from="ElementInfo" uuid="{b7fccfc3-05f1-459c-9766-f49a481bd0ff}" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular">
|
||||||
|
<text></text>
|
||||||
|
<info_name>label</info_name>
|
||||||
|
</dynamic_text>
|
||||||
|
<polygon x1="-14" y1="-8" x2="14" y2="-8" x3="20" y3="8" x4="-14" y4="8" antialias="false" style="line-style:normal;line-weight:normal;filling:none;color:black"/>
|
||||||
|
<terminal uuid="{6a87c921-6f5d-4f5c-8673-228a6d13c5c8}" name="A2" x="0" y="20" orientation="s" type="Generic"/>
|
||||||
|
<terminal uuid="{2904e5fa-6bbe-4127-acdd-f92feadd6ece}" name="A1" x="0" y="-20" orientation="n" type="Generic"/>
|
||||||
|
</description>
|
||||||
|
</definition>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<definition version="0.100.0" type="element" link_type="slave" width="20" height="60" hotspot_x="9" hotspot_y="30">
|
||||||
|
<uuid uuid="{94d56f06-6814-5561-9148-2da3a9c4f900}"/>
|
||||||
|
<names>
|
||||||
|
<name lang="en">PLC Slave Test</name>
|
||||||
|
</names>
|
||||||
|
<kindInformations>
|
||||||
|
<kindInformation name="type">plc</kindInformation>
|
||||||
|
<kindInformation name="state">NO</kindInformation>
|
||||||
|
<kindInformation name="number">1</kindInformation>
|
||||||
|
</kindInformations>
|
||||||
|
<description>
|
||||||
|
<rect x="0" y="-8" width="6" height="16" rx="0" ry="0" style="line-style:normal;line-weight:normal;filling:none;color:black" antialias="false"/>
|
||||||
|
<terminal uuid="{6a87c921-6f5d-4f5c-8673-228a6d13c500}" name="1" x="0" y="20" orientation="s" type="Generic"/>
|
||||||
|
<terminal uuid="{2904e5fa-6bbe-4127-acdd-f92feadd6e00}" name="2" x="0" y="-20" orientation="n" type="Generic"/>
|
||||||
|
</description>
|
||||||
|
</definition>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
#Based on raspberry pi 5 8 Gb Trixie
|
#Based on raspberry pi 5 8 Gb Trixie
|
||||||
#sudo apt install git ssh rsync libqt5svg5-dev qt5-qmake qtbase5-dev libkf5widgetsaddons-dev libkf5coreaddons-dev libsqlite3-dev pkgconf libqt5waylandclient5-dev libqt5waylandcompositor5-dev g++ make
|
#sudo apt install git ssh rsync libqt5svg5-dev qt5-qmake qtbase5-dev libkf5widgetsaddons-dev libkf5coreaddons-dev pkgconf libqt5waylandclient5-dev libqt5waylandcompositor5-dev g++ make
|
||||||
#mkdir -p AppImage/0.100.0/aarch64
|
#mkdir -p AppImage/0.100.0/aarch64
|
||||||
# Get GIT sources
|
# Get GIT sources
|
||||||
#git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
|
#git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
|
||||||
|
|||||||
@@ -93,10 +93,7 @@
|
|||||||
<file>ico/22x22/conductor2.png</file>
|
<file>ico/22x22/conductor2.png</file>
|
||||||
<file>ico/22x22/configure.png</file>
|
<file>ico/22x22/configure.png</file>
|
||||||
<file>ico/22x22/configure-toolbars.png</file>
|
<file>ico/22x22/configure-toolbars.png</file>
|
||||||
<file>ico/22x22/diagram_add.png</file>
|
|
||||||
<file>ico/22x22/diagram_del.png</file>
|
|
||||||
<file>ico/22x22/dialog-cancel.png</file>
|
<file>ico/22x22/dialog-cancel.png</file>
|
||||||
<file>ico/22x22/dialog-information.png</file>
|
|
||||||
<file>ico/22x22/dialog-ok.png</file>
|
<file>ico/22x22/dialog-ok.png</file>
|
||||||
<file>ico/22x22/document-close.png</file>
|
<file>ico/22x22/document-close.png</file>
|
||||||
<file>ico/22x22/document-export.png</file>
|
<file>ico/22x22/document-export.png</file>
|
||||||
@@ -140,8 +137,6 @@
|
|||||||
<file>ico/22x22/go-up.png</file>
|
<file>ico/22x22/go-up.png</file>
|
||||||
<file>ico/22x22/hotspot.png</file>
|
<file>ico/22x22/hotspot.png</file>
|
||||||
<file>ico/22x22/insert-image.png</file>
|
<file>ico/22x22/insert-image.png</file>
|
||||||
<file>ico/22x22/pdf-import.png</file>
|
|
||||||
<file>ico/22x22/label.png</file>
|
|
||||||
<file>ico/22x22/landscape.png</file>
|
<file>ico/22x22/landscape.png</file>
|
||||||
<file>ico/22x22/line.png</file>
|
<file>ico/22x22/line.png</file>
|
||||||
<file>ico/22x22/list-add.png</file>
|
<file>ico/22x22/list-add.png</file>
|
||||||
@@ -242,7 +237,6 @@
|
|||||||
<file>ico/16x16/project.png</file>
|
<file>ico/16x16/project.png</file>
|
||||||
<file>ico/16x16/qt.png</file>
|
<file>ico/16x16/qt.png</file>
|
||||||
<file>ico/16x16/terminalstrip.png</file>
|
<file>ico/16x16/terminalstrip.png</file>
|
||||||
<file>ico/22x22/diagram.png</file>
|
|
||||||
<file>ico/22x22/edit-clear-locationbar-rtl.png</file>
|
<file>ico/22x22/edit-clear-locationbar-rtl.png</file>
|
||||||
<file>ico/22x22/export-csv.png</file>
|
<file>ico/22x22/export-csv.png</file>
|
||||||
<file>ico/22x22/format-text-subscript.png</file>
|
<file>ico/22x22/format-text-subscript.png</file>
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
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 "elementpreviewdelegate.h"
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "../qetpalette.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief ElementPreviewDelegate::initStyleOption
|
||||||
|
After the base class has filled the option from the model, replace
|
||||||
|
the icon with one that reads on a dark palette. The adapted icon is
|
||||||
|
built from the pixmap the view is about to draw, at the view's
|
||||||
|
decoration size and device pixel ratio.
|
||||||
|
*/
|
||||||
|
void ElementPreviewDelegate::initStyleOption(QStyleOptionViewItem *option,
|
||||||
|
const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate::initStyleOption(option, index);
|
||||||
|
if (option->icon.isNull() || !QET::Palette::isDark(option->palette))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qint64 key = option->icon.cacheKey();
|
||||||
|
const auto it = m_dark_icons.constFind(key);
|
||||||
|
if (it != m_dark_icons.constEnd())
|
||||||
|
{
|
||||||
|
option->icon = *it;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qreal dpr = option->widget ? option->widget->devicePixelRatio() : 1.0;
|
||||||
|
const QPixmap source = option->icon.pixmap(option->decorationSize, dpr);
|
||||||
|
const QPixmap adapted = QET::Palette::forPalette(source, option->palette);
|
||||||
|
QIcon icon = option->icon;
|
||||||
|
if (adapted.cacheKey() != source.cacheKey())
|
||||||
|
icon = QIcon(adapted);
|
||||||
|
m_dark_icons.insert(key, icon);
|
||||||
|
option->icon = icon;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
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 ELEMENTPREVIEWDELEGATE_H
|
||||||
|
#define ELEMENTPREVIEWDELEGATE_H
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief The ElementPreviewDelegate class
|
||||||
|
Draws the items of an element collection tree with icons that read on
|
||||||
|
the current palette. Element previews are black line art drawn for a
|
||||||
|
white sheet; on a dark palette this delegate hands the view the same
|
||||||
|
picture with its lightness inverted (QET::Palette::forPalette), so the
|
||||||
|
ink is light on the dark row. Colored icons, such as folders, are left
|
||||||
|
alone, and nothing changes on a light palette. Adapted icons are kept
|
||||||
|
per source icon, so a repaint costs a hash lookup.
|
||||||
|
*/
|
||||||
|
class ElementPreviewDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
using QStyledItemDelegate::QStyledItemDelegate;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void initStyleOption(QStyleOptionViewItem *option,
|
||||||
|
const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable QHash<qint64, QIcon> m_dark_icons;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -725,7 +725,10 @@ void ElementsCollectionWidget::showThisDir()
|
|||||||
ElementCollectionItem *eci =
|
ElementCollectionItem *eci =
|
||||||
elementCollectionItemForIndex(m_showed_index);
|
elementCollectionItemForIndex(m_showed_index);
|
||||||
if (eci)
|
if (eci)
|
||||||
|
{
|
||||||
eci->setBackground(QBrush());
|
eci->setBackground(QBrush());
|
||||||
|
eci->setForeground(QBrush());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_showed_index = m_index_at_context_menu;
|
m_showed_index = m_index_at_context_menu;
|
||||||
@@ -736,7 +739,11 @@ void ElementsCollectionWidget::showThisDir()
|
|||||||
ElementCollectionItem *eci =
|
ElementCollectionItem *eci =
|
||||||
elementCollectionItemForIndex(m_showed_index);
|
elementCollectionItemForIndex(m_showed_index);
|
||||||
if (eci)
|
if (eci)
|
||||||
|
{
|
||||||
|
// Amber under black, whatever the palette's text color.
|
||||||
eci->setBackground(QBrush(QColor(255, 204, 0, 255)));
|
eci->setBackground(QBrush(QColor(255, 204, 0, 255)));
|
||||||
|
eci->setForeground(QBrush(Qt::black));
|
||||||
|
}
|
||||||
search();
|
search();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -755,7 +762,10 @@ void ElementsCollectionWidget::resetShowThisDir()
|
|||||||
ElementCollectionItem *eci = elementCollectionItemForIndex(
|
ElementCollectionItem *eci = elementCollectionItemForIndex(
|
||||||
m_showed_index);
|
m_showed_index);
|
||||||
if (eci)
|
if (eci)
|
||||||
|
{
|
||||||
eci->setBackground(QBrush());
|
eci->setBackground(QBrush());
|
||||||
|
eci->setForeground(QBrush());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_showed_index = QModelIndex();
|
m_showed_index = QModelIndex();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
#include "../NameList/nameslist.h"
|
#include "../NameList/nameslist.h"
|
||||||
#include "../diagramcontext.h"
|
#include "../diagramcontext.h"
|
||||||
#include "pugixml/src/pugixml.hpp"
|
#include "pugixml.hpp"
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "elementstreeview.h"
|
#include "elementstreeview.h"
|
||||||
|
#include "elementpreviewdelegate.h"
|
||||||
|
#include "../qetpalette.h"
|
||||||
|
|
||||||
#include "../factory/elementfactory.h"
|
#include "../factory/elementfactory.h"
|
||||||
#include "../qetgraphicsitem/element.h"
|
#include "../qetgraphicsitem/element.h"
|
||||||
@@ -41,24 +43,11 @@ static int MAX_DND_PIXMAP_HEIGHT = 375;
|
|||||||
ElementsTreeView::ElementsTreeView(QWidget *parent) :
|
ElementsTreeView::ElementsTreeView(QWidget *parent) :
|
||||||
QTreeView(parent)
|
QTreeView(parent)
|
||||||
{
|
{
|
||||||
// force du noir sur une alternance de blanc (comme le schema) et de gris
|
// Rows follow the application palette. Element previews are black
|
||||||
// clair, avec du blanc sur bleu pas trop fonce pour la selection
|
// line art drawn for a white sheet; ElementPreviewDelegate adapts them
|
||||||
//
|
// to a dark palette, so this view no longer has to force a light one
|
||||||
// Element icons are rendered with colors read directly from each .elmt
|
// (bugtracker #335).
|
||||||
// file (almost always black linework, matching printed-schematic
|
setItemDelegate(new ElementPreviewDelegate(this));
|
||||||
// convention) onto a transparent background -- so this view must keep
|
|
||||||
// a light background regardless of the OS/desktop theme, or the icons
|
|
||||||
// become invisible on dark themes. QAbstractItemView paints its rows
|
|
||||||
// using the viewport's palette, not the view's own, so the palette
|
|
||||||
// must be applied to both to actually take effect under every style.
|
|
||||||
QPalette qp = palette();
|
|
||||||
qp.setColor(QPalette::Text, Qt::black);
|
|
||||||
qp.setColor(QPalette::Base, Qt::white);
|
|
||||||
qp.setColor(QPalette::AlternateBase, QColor("#e8e8e8"));
|
|
||||||
qp.setColor(QPalette::Highlight, QColor("#678db2"));
|
|
||||||
qp.setColor(QPalette::HighlightedText, Qt::black);
|
|
||||||
setPalette(qp);
|
|
||||||
viewport()->setPalette(qp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,7 +209,7 @@ void ElementsTreeView::startElementDrag(const ElementsLocation &location)
|
|||||||
&elmt_creation_state));
|
&elmt_creation_state));
|
||||||
if (elmt_creation_state) { return; }
|
if (elmt_creation_state) { return; }
|
||||||
|
|
||||||
QPixmap elmt_pixmap(temp_elmt->pixmap());
|
QPixmap elmt_pixmap(QET::Palette::forPalette(temp_elmt->pixmap(), palette()));
|
||||||
QPoint elmt_hotspot(temp_elmt->hotspot());
|
QPoint elmt_hotspot(temp_elmt->hotspot());
|
||||||
|
|
||||||
//Adjust the size of the pixmap if he is too big
|
//Adjust the size of the pixmap if he is too big
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef NAMES_LIST_H
|
#ifndef NAMES_LIST_H
|
||||||
#define NAMES_LIST_H
|
#define NAMES_LIST_H
|
||||||
#include "pugixml/src/pugixml.hpp"
|
#include "pugixml.hpp"
|
||||||
|
|
||||||
#include <QtXml>
|
#include <QtXml>
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,13 +35,17 @@ AddTerminalToStripCommand::AddTerminalToStripCommand(QSharedPointer<RealTerminal
|
|||||||
const auto t_label = terminal->label();
|
const auto t_label = terminal->label();
|
||||||
const auto ts_name = strip->name();
|
const auto ts_name = strip->name();
|
||||||
|
|
||||||
const auto str_1 = t_label.isEmpty() ? QObject::tr("Ajouter une borne") :
|
QString text;
|
||||||
QObject::tr("Ajouter la borne %1").arg(t_label);
|
if (ts_name.isEmpty()) {
|
||||||
|
text = t_label.isEmpty()
|
||||||
const auto str_2 = ts_name.isEmpty() ? QObject::tr("à un groupe de bornes") :
|
? QObject::tr("Ajouter une borne à un groupe de bornes")
|
||||||
QObject::tr("au groupe de bornes %1").arg(ts_name);
|
: QObject::tr("Ajouter la borne %1 à un groupe de bornes").arg(t_label);
|
||||||
|
} else {
|
||||||
setText(str_1 % " " % str_2);
|
text = t_label.isEmpty()
|
||||||
|
? QObject::tr("Ajouter une borne au groupe de bornes %1").arg(ts_name)
|
||||||
|
: QObject::tr("Ajouter la borne %1 au groupe de bornes %2").arg(t_label, ts_name);
|
||||||
|
}
|
||||||
|
setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddTerminalToStripCommand::AddTerminalToStripCommand(QVector<QSharedPointer<RealTerminal>> terminals, TerminalStrip *strip, QUndoCommand *parent) :
|
AddTerminalToStripCommand::AddTerminalToStripCommand(QVector<QSharedPointer<RealTerminal>> terminals, TerminalStrip *strip, QUndoCommand *parent) :
|
||||||
@@ -50,14 +54,11 @@ AddTerminalToStripCommand::AddTerminalToStripCommand(QVector<QSharedPointer<Real
|
|||||||
m_new_strip{strip}
|
m_new_strip{strip}
|
||||||
{
|
{
|
||||||
const auto ts_name = strip->name();
|
const auto ts_name = strip->name();
|
||||||
|
const auto count = m_terminal.size();
|
||||||
|
|
||||||
const auto str_1 = m_terminal.size() > 1 ? QObject::tr("Ajouter %1 bornes").arg(m_terminal.size()) :
|
setText(ts_name.isEmpty()
|
||||||
QObject::tr("Ajouter une borne");
|
? QObject::tr("Ajouter %n borne(s) à un groupe de bornes", "", count)
|
||||||
|
: QObject::tr("Ajouter %n borne(s) au groupe de bornes %1", "", count).arg(ts_name));
|
||||||
const auto str_2 = ts_name.isEmpty() ? QObject::tr("à un groupe de bornes") :
|
|
||||||
QObject::tr("au groupe de bornes %1").arg(ts_name);
|
|
||||||
|
|
||||||
setText(str_1 % " " % str_2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -137,13 +138,11 @@ void RemoveTerminalFromStripCommand::redo()
|
|||||||
void RemoveTerminalFromStripCommand::setCommandTitle()
|
void RemoveTerminalFromStripCommand::setCommandTitle()
|
||||||
{
|
{
|
||||||
const auto strip_name = m_strip->name();
|
const auto strip_name = m_strip->name();
|
||||||
|
const auto count = m_terminals.size();
|
||||||
|
|
||||||
const auto str_1 = m_terminals.size()>1 ? QObject::tr("Enlever %1 bornes").arg(m_terminals.size()):
|
setText(strip_name.isEmpty()
|
||||||
QObject::tr("Enlever une borne");
|
? QObject::tr("Enlever %n borne(s) d'un groupe de bornes", "", count)
|
||||||
|
: QObject::tr("Enlever %n borne(s) du groupe de bornes %1", "", count).arg(strip_name));
|
||||||
const auto str_2 = strip_name.isEmpty() ? QObject::tr("d'un groupe de bornes") :
|
|
||||||
QObject::tr("du groupe de bornes %1").arg(strip_name);
|
|
||||||
setText(str_1 % " " % str_2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,19 +165,30 @@ MoveTerminalCommand::MoveTerminalCommand(QSharedPointer<PhysicalTerminal> termin
|
|||||||
t_label.append(", ");
|
t_label.append(", ");
|
||||||
t_label.append(real_t->label());
|
t_label.append(real_t->label());
|
||||||
}
|
}
|
||||||
|
const auto strip_name = old_strip->name();
|
||||||
|
const auto new_strip_name = new_strip->name();
|
||||||
|
|
||||||
auto strip_name = old_strip->name();
|
QString text;
|
||||||
auto new_strip_name = new_strip->name();
|
if (t_label.isEmpty()) {
|
||||||
|
if (strip_name.isEmpty() && new_strip_name.isEmpty())
|
||||||
auto str_1 = t_label.isEmpty() ? QObject::tr("Déplacer une borne") :
|
text = QObject::tr("Déplacer une borne d'un groupe de bornes vers un autre groupe de bornes");
|
||||||
QObject::tr("Déplacer la borne %1").arg(t_label);
|
else if (strip_name.isEmpty())
|
||||||
|
text = QObject::tr("Déplacer une borne d'un groupe de bornes vers le groupe de bornes %1").arg(new_strip_name);
|
||||||
auto str_2 = strip_name.isEmpty() ? QObject::tr(" d'un groupe de bornes") :
|
else if (new_strip_name.isEmpty())
|
||||||
QObject::tr(" du groupe de bornes %1").arg(strip_name);
|
text = QObject::tr("Déplacer une borne du groupe de bornes %1 vers un autre groupe de bornes").arg(strip_name);
|
||||||
|
else
|
||||||
auto str_3 = new_strip_name.isEmpty() ? QObject::tr("vers un groupe de bornes") :
|
text = QObject::tr("Déplacer une borne du groupe de bornes %1 vers le groupe de bornes %2").arg(strip_name, new_strip_name);
|
||||||
QObject::tr("vers le groupe de bornes %1").arg(new_strip_name);
|
} else {
|
||||||
setText(str_1 % " " % str_2 % " " % str_3);
|
if (strip_name.isEmpty() && new_strip_name.isEmpty())
|
||||||
|
text = QObject::tr("Déplacer la borne %1 d'un groupe de bornes vers un autre groupe de bornes").arg(t_label);
|
||||||
|
else if (strip_name.isEmpty())
|
||||||
|
text = QObject::tr("Déplacer la borne %1 d'un groupe de bornes vers le groupe de bornes %2").arg(t_label, new_strip_name);
|
||||||
|
else if (new_strip_name.isEmpty())
|
||||||
|
text = QObject::tr("Déplacer la borne %1 du groupe de bornes %2 vers un autre groupe de bornes").arg(t_label, strip_name);
|
||||||
|
else
|
||||||
|
text = QObject::tr("Déplacer la borne %1 du groupe de bornes %2 vers le groupe de bornes %3").arg(t_label, strip_name, new_strip_name);
|
||||||
|
}
|
||||||
|
setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveTerminalCommand::MoveTerminalCommand(QVector<QSharedPointer<PhysicalTerminal>> terminals, TerminalStrip *old_strip,
|
MoveTerminalCommand::MoveTerminalCommand(QVector<QSharedPointer<PhysicalTerminal>> terminals, TerminalStrip *old_strip,
|
||||||
@@ -191,17 +201,18 @@ MoveTerminalCommand::MoveTerminalCommand(QVector<QSharedPointer<PhysicalTerminal
|
|||||||
{
|
{
|
||||||
const auto strip_name = old_strip->name();
|
const auto strip_name = old_strip->name();
|
||||||
const auto new_strip_name = new_strip->name();
|
const auto new_strip_name = new_strip->name();
|
||||||
|
const auto count = m_terminal.size();
|
||||||
|
|
||||||
const auto str_1 = m_terminal.size() > 1 ? QObject::tr("Déplacer des bornes") :
|
QString text;
|
||||||
QObject::tr("Déplacer une borne");
|
if (strip_name.isEmpty() && new_strip_name.isEmpty())
|
||||||
|
text = QObject::tr("Déplacer %n borne(s) d'un groupe de bornes vers un autre groupe de bornes", "", count);
|
||||||
const auto str_2 = strip_name.isEmpty() ? QObject::tr(" d'un groupe de bornes") :
|
else if (strip_name.isEmpty())
|
||||||
QObject::tr(" du groupe de bornes %1").arg(strip_name);
|
text = QObject::tr("Déplacer %n borne(s) d'un groupe de bornes vers le groupe de bornes %1", "", count).arg(new_strip_name);
|
||||||
|
else if (new_strip_name.isEmpty())
|
||||||
const auto str_3 = new_strip_name.isEmpty() ? QObject::tr("vers un groupe de bornes") :
|
text = QObject::tr("Déplacer %n borne(s) du groupe de bornes %1 vers un autre groupe de bornes", "", count).arg(strip_name);
|
||||||
QObject::tr("vers le groupe de bornes %1").arg(new_strip_name);
|
else
|
||||||
|
text = QObject::tr("Déplacer %n borne(s) du groupe de bornes %1 vers le groupe de bornes %2", "", count).arg(strip_name, new_strip_name);
|
||||||
setText(str_1 % " " % str_2 % " " % str_3);
|
setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoveTerminalCommand::undo()
|
void MoveTerminalCommand::undo()
|
||||||
|
|||||||
@@ -275,3 +275,80 @@ QString NumerotationContext::formatValue(const QStringList &item)
|
|||||||
return QString("%1").arg(value.toInt(), 3, 10, QChar('0'));
|
return QString("%1").arg(value.toInt(), 3, 10, QChar('0'));
|
||||||
return QString::number(value.toInt());
|
return QString::number(value.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief NumerotationContext::saveToSettings
|
||||||
|
Save a hash of named NumerotationContexts to QSettings.
|
||||||
|
@param contexts : the named rules to save
|
||||||
|
@param currentRule : the name of the currently active rule
|
||||||
|
@param settings : QSettings instance
|
||||||
|
@param prefix : settings key prefix (e.g. "autonum/conductor")
|
||||||
|
*/
|
||||||
|
void NumerotationContext::saveToSettings(
|
||||||
|
const QHash<QString, NumerotationContext> &contexts,
|
||||||
|
const QString ¤tRule,
|
||||||
|
QSettings &settings,
|
||||||
|
const QString &prefix)
|
||||||
|
{
|
||||||
|
settings.setValue(prefix + "/current", currentRule);
|
||||||
|
|
||||||
|
// Clear stale array entries before writing (beginWriteArray does not
|
||||||
|
// remove entries beyond the new size).
|
||||||
|
settings.remove(prefix + "/rules");
|
||||||
|
|
||||||
|
QStringList names = contexts.keys();
|
||||||
|
settings.beginWriteArray(prefix + "/rules", names.size());
|
||||||
|
for (int i = 0; i < names.size(); ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
const QString &name = names.at(i);
|
||||||
|
const NumerotationContext &nc = contexts.value(name);
|
||||||
|
|
||||||
|
settings.setValue("name", name);
|
||||||
|
|
||||||
|
// Serialize context to XML string
|
||||||
|
QDomDocument doc;
|
||||||
|
NumerotationContext nc_copy = nc;
|
||||||
|
QDomElement root = nc_copy.toXml(doc, "context");
|
||||||
|
doc.appendChild(root);
|
||||||
|
settings.setValue("xml", doc.toString());
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief NumerotationContext::loadFromSettings
|
||||||
|
Load named NumerotationContexts from QSettings.
|
||||||
|
@param settings : QSettings instance
|
||||||
|
@param prefix : settings key prefix (e.g. "autonum/conductor")
|
||||||
|
@return pair of (hash of named rules, name of current rule)
|
||||||
|
*/
|
||||||
|
QPair<QHash<QString, NumerotationContext>, QString> NumerotationContext::loadFromSettings(
|
||||||
|
QSettings &settings,
|
||||||
|
const QString &prefix)
|
||||||
|
{
|
||||||
|
QPair<QHash<QString, NumerotationContext>, QString> result;
|
||||||
|
QHash<QString, NumerotationContext> &contexts = result.first;
|
||||||
|
QString ¤tRule = result.second;
|
||||||
|
|
||||||
|
currentRule = settings.value(prefix + "/current").toString();
|
||||||
|
|
||||||
|
int size = settings.beginReadArray(prefix + "/rules");
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
QString name = settings.value("name").toString();
|
||||||
|
QString xmlStr = settings.value("xml").toString();
|
||||||
|
|
||||||
|
if (name.isEmpty() || xmlStr.isEmpty()) continue;
|
||||||
|
|
||||||
|
QDomDocument doc;
|
||||||
|
if (!doc.setContent(xmlStr)) continue;
|
||||||
|
|
||||||
|
QDomElement root = doc.documentElement();
|
||||||
|
NumerotationContext nc;
|
||||||
|
nc.fromXml(root);
|
||||||
|
contexts.insert(name, nc);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class represents a numerotation context, i.e. the data (type, value, increase)
|
This class represents a numerotation context, i.e. the data (type, value, increase)
|
||||||
@@ -60,6 +62,14 @@ class NumerotationContext
|
|||||||
/// UI preview of a part's value matches what actually gets rendered.
|
/// UI preview of a part's value matches what actually gets rendered.
|
||||||
static QString formatValue(const QStringList &item);
|
static QString formatValue(const QStringList &item);
|
||||||
|
|
||||||
|
static void saveToSettings(const QHash<QString, NumerotationContext> &contexts,
|
||||||
|
const QString ¤tRule,
|
||||||
|
QSettings &settings,
|
||||||
|
const QString &prefix);
|
||||||
|
static QPair<QHash<QString, NumerotationContext>, QString> loadFromSettings(
|
||||||
|
QSettings &settings,
|
||||||
|
const QString &prefix);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList content_;
|
QStringList content_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -505,7 +505,10 @@ void BorderTitleBlock::draw(QPainter *painter)
|
|||||||
{
|
{
|
||||||
//Set the QPainter
|
//Set the QPainter
|
||||||
painter -> save();
|
painter -> save();
|
||||||
QPen pen(Qt::black);
|
//Use a pen color that contrasts with the background
|
||||||
|
QColor border_color = Diagram::background_color.lightness() < 128
|
||||||
|
? QColor(Qt::white) : QColor(Qt::black);
|
||||||
|
QPen pen(border_color);
|
||||||
painter -> setPen(pen);
|
painter -> setPen(pen);
|
||||||
painter -> setBrush(Qt::NoBrush);
|
painter -> setBrush(Qt::NoBrush);
|
||||||
|
|
||||||
@@ -893,10 +896,20 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
|
|||||||
// An empty page-level value means the variable was auto-added to the
|
// An empty page-level value means the variable was auto-added to the
|
||||||
// folio's Custom tab (#495) but never actually set by the user, so it
|
// folio's Custom tab (#495) but never actually set by the user, so it
|
||||||
// must not shadow a real project-level value of the same name (#531).
|
// must not shadow a real project-level value of the same name (#531).
|
||||||
|
//
|
||||||
|
// That guard has to stop short of removing the key outright, though
|
||||||
|
// (#973). TitleBlockTemplate::interpreteVariables() only replaces a
|
||||||
|
// "%name"/"%{name}" placeholder when "name" is a key in this context at
|
||||||
|
// all -- an unset variable that never makes it in is left as its own
|
||||||
|
// literal placeholder text in the rendered title block, not blank.
|
||||||
|
// So an empty page-level value is skipped only when a real project-level
|
||||||
|
// one is already there to show through; otherwise it still goes in
|
||||||
|
// empty, which is what makes the placeholder resolve to nothing.
|
||||||
DiagramContext context = initial_context;
|
DiagramContext context = initial_context;
|
||||||
foreach (QString key, additional_fields_.keys()) {
|
foreach (QString key, additional_fields_.keys()) {
|
||||||
if (!additional_fields_[key].toString().isEmpty())
|
const QVariant value = additional_fields_[key];
|
||||||
context.addValue(key, additional_fields_[key]);
|
if (!value.toString().isEmpty() || !context.contains(key))
|
||||||
|
context.addValue(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... overridden by the historical and/or dynamically generated fields
|
// ... overridden by the historical and/or dynamically generated fields
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ void ConductorProperties::applyForEqualAttributes(QList<ConductorProperties> lis
|
|||||||
horiz_rotate_text = cp.horiz_rotate_text;
|
horiz_rotate_text = cp.horiz_rotate_text;
|
||||||
m_vertical_alignment = cp.m_vertical_alignment;
|
m_vertical_alignment = cp.m_vertical_alignment;
|
||||||
m_horizontal_alignment = cp.m_horizontal_alignment;
|
m_horizontal_alignment = cp.m_horizontal_alignment;
|
||||||
|
style = cp.style;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -555,6 +556,18 @@ void ConductorProperties::applyForEqualAttributes(QList<ConductorProperties> lis
|
|||||||
m_dash_size = i_value;
|
m_dash_size = i_value;
|
||||||
equal = true;
|
equal = true;
|
||||||
|
|
||||||
|
//style
|
||||||
|
Qt::PenStyle pen_style;
|
||||||
|
pen_style = clist.first().style;
|
||||||
|
for(ConductorProperties cp : clist)
|
||||||
|
{
|
||||||
|
if (cp.style != pen_style)
|
||||||
|
equal = false;
|
||||||
|
}
|
||||||
|
if (equal)
|
||||||
|
style = pen_style;
|
||||||
|
equal = true;
|
||||||
|
|
||||||
//text
|
//text
|
||||||
s_value = clist.first().text;
|
s_value = clist.first().text;
|
||||||
for(ConductorProperties cp : clist)
|
for(ConductorProperties cp : clist)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
#include "projectdatabase.h"
|
#include "projectdatabase.h"
|
||||||
|
|
||||||
|
#include "sqlreadonly.h"
|
||||||
|
|
||||||
#include "../diagram.h"
|
#include "../diagram.h"
|
||||||
#include "../diagramposition.h"
|
#include "../diagramposition.h"
|
||||||
#include "../elementprovider.h"
|
#include "../elementprovider.h"
|
||||||
@@ -28,13 +30,15 @@
|
|||||||
#include "../qetproject.h"
|
#include "../qetproject.h"
|
||||||
|
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QFile>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlError>
|
|
||||||
|
|
||||||
#include <QSqlDriver>
|
#include <QSqlDriver>
|
||||||
|
#include <QSqlError>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief projectDataBase::projectDataBase
|
@brief projectDataBase::projectDataBase
|
||||||
Default constructor
|
Default constructor
|
||||||
@@ -208,6 +212,12 @@ bool projectDataBase::isReadOnlySelect(const QString &query, QString *error)
|
|||||||
*/
|
*/
|
||||||
QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) {
|
QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) {
|
||||||
QString reason;
|
QString reason;
|
||||||
|
|
||||||
|
// First gate: which kind of statement is acceptable here at all. A
|
||||||
|
// textual check is the right tool for that and the wrong tool for
|
||||||
|
// anything else -- see isReadOnlySelect()'s own comment. It is what
|
||||||
|
// keeps ATTACH, BEGIN and PRAGMA out, none of which SQLite itself
|
||||||
|
// considers writes.
|
||||||
if (!isReadOnlySelect(query, &reason)) {
|
if (!isReadOnlySelect(query, &reason)) {
|
||||||
qWarning().noquote() << "projectDataBase::newQuery: rejected query:" << reason << "--" << query;
|
qWarning().noquote() << "projectDataBase::newQuery: rejected query:" << reason << "--" << query;
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -215,6 +225,24 @@ QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) {
|
|||||||
}
|
}
|
||||||
return QSqlQuery(m_data_base);
|
return QSqlQuery(m_data_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Second gate, and the one that actually enforces read-only: SQLite is
|
||||||
|
// asked about the statement it compiled, instead of the text being read
|
||||||
|
// for clues. The first gate cannot see through a CTE prefix --
|
||||||
|
// "WITH x AS (SELECT 1) DELETE FROM element" starts with WITH, contains
|
||||||
|
// no semicolon, and deletes every row. That matters beyond the
|
||||||
|
// custom-query box, because this path is reachable from a file: a
|
||||||
|
// <graphics_table>'s saved <query> is read straight out of the .qet by
|
||||||
|
// ProjectDBModel::fromXml() and executed by fillValue(), so opening or
|
||||||
|
// exporting a project someone else produced would have been enough.
|
||||||
|
if (!QETSql::isSingleReadOnlyStatement(sqliteHandle(&m_data_base), query, &reason)) {
|
||||||
|
qWarning().noquote() << "projectDataBase::newQuery: rejected query:" << reason << "--" << query;
|
||||||
|
if (error) {
|
||||||
|
*error = reason;
|
||||||
|
}
|
||||||
|
return QSqlQuery(m_data_base);
|
||||||
|
}
|
||||||
|
|
||||||
return QSqlQuery(query, m_data_base);
|
return QSqlQuery(query, m_data_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1245,7 +1273,6 @@ void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QET_EXPORT_PROJECT_DB
|
|
||||||
/**
|
/**
|
||||||
@brief projectDataBase::sqliteHandle
|
@brief projectDataBase::sqliteHandle
|
||||||
@param db
|
@param db
|
||||||
@@ -1263,6 +1290,7 @@ sqlite3 *projectDataBase::sqliteHandle(QSqlDatabase *db)
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef QET_EXPORT_PROJECT_DB
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief projectDataBase::exportDb
|
* @brief projectDataBase::exportDb
|
||||||
@@ -1298,27 +1326,19 @@ void projectDataBase::exportDb(projectDataBase *db,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString connection_name("export_project_db_" % db->project()->uuid().toString());
|
// VACUUM INTO requires the destination not to exist. QFileDialog may ask
|
||||||
|
// about overwriting, but it does not remove the existing file for us.
|
||||||
if (true) //Enter in a scope only to nicely use QSqlDatabase::removeDatabase just after the end of the scope
|
if (QFile::exists(path_) && !QFile::remove(path_)) {
|
||||||
{
|
qWarning() << "Unable to replace project database export:" << path_;
|
||||||
auto file_db = QSqlDatabase::addDatabase("QSQLITE", connection_name);
|
return;
|
||||||
file_db.setDatabaseName(path_);
|
}
|
||||||
if (!file_db.open()) {
|
|
||||||
return;
|
// VACUUM INTO creates a standalone copy of the current database without
|
||||||
}
|
// requiring access to the SQLite driver's native connection handle.
|
||||||
|
const auto escaped_path = path_.replace("'", "''");
|
||||||
auto memory_db_handle = sqliteHandle(&db->m_data_base);
|
QSqlQuery query(db->m_data_base);
|
||||||
auto file_db_handle = sqliteHandle(&file_db);
|
if (!query.exec("VACUUM INTO '" % escaped_path % "'")) {
|
||||||
|
qWarning() << "Unable to export project database:" << query.lastError().text();
|
||||||
auto sqlite_backup = sqlite3_backup_init(file_db_handle, "main", memory_db_handle, "main");
|
|
||||||
if (sqlite_backup)
|
|
||||||
{
|
|
||||||
sqlite3_backup_step(sqlite_backup, -1);
|
|
||||||
sqlite3_backup_finish(sqlite_backup);
|
|
||||||
}
|
|
||||||
file_db.close();
|
|
||||||
}
|
}
|
||||||
QSqlDatabase::removeDatabase(connection_name);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -61,6 +61,26 @@ class projectDataBase : public QObject
|
|||||||
QETProject *project() const;
|
QETProject *project() const;
|
||||||
QSqlQuery newQuery(const QString &query = QString(), QString *error = nullptr);
|
QSqlQuery newQuery(const QString &query = QString(), QString *error = nullptr);
|
||||||
static bool isReadOnlySelect(const QString &query, QString *error = nullptr);
|
static bool isReadOnlySelect(const QString &query, QString *error = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
The most rows any caller reads out of one query result.
|
||||||
|
|
||||||
|
A SELECT is not bounded by how much data the project holds:
|
||||||
|
SQLite produces rows lazily, so a query that never stops
|
||||||
|
producing them makes the loop that reads them never stop
|
||||||
|
either. A recursive CTE does exactly that in one line, and
|
||||||
|
a <graphics_table>'s <query> is stored in the .qet and run
|
||||||
|
on load -- so the text can arrive from a file rather than
|
||||||
|
from the person at the keyboard, and opening that file is
|
||||||
|
the whole attack.
|
||||||
|
|
||||||
|
100000 is far above any real result: the largest table in
|
||||||
|
the shipped examples is 396 rows. It is a backstop, not a
|
||||||
|
page size -- a caller that hits it has almost certainly
|
||||||
|
been handed something it should not run to completion, and
|
||||||
|
says so rather than truncating quietly.
|
||||||
|
*/
|
||||||
|
static constexpr int MaxResultRows = 100000;
|
||||||
QSqlDatabase database() const {return m_data_base;}
|
QSqlDatabase database() const {return m_data_base;}
|
||||||
int excludedConductorCount() const;
|
int excludedConductorCount() const;
|
||||||
|
|
||||||
@@ -135,9 +155,14 @@ class projectDataBase : public QObject
|
|||||||
m_cascade_remove_conductor_query,
|
m_cascade_remove_conductor_query,
|
||||||
m_cascade_remove_element_query;
|
m_cascade_remove_element_query;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Deliberately outside the QET_EXPORT_PROJECT_DB guard below:
|
||||||
|
// newQuery() needs the raw connection to ask SQLite whether a
|
||||||
|
// query only reads, and that check runs in every build.
|
||||||
|
static sqlite3 *sqliteHandle(QSqlDatabase *db);
|
||||||
|
|
||||||
#ifdef QET_EXPORT_PROJECT_DB
|
#ifdef QET_EXPORT_PROJECT_DB
|
||||||
public:
|
public:
|
||||||
static sqlite3 *sqliteHandle(QSqlDatabase *db);
|
|
||||||
static void exportDb(projectDataBase *db,
|
static void exportDb(projectDataBase *db,
|
||||||
QWidget *parent = nullptr,
|
QWidget *parent = nullptr,
|
||||||
const QString &caption = QString(),
|
const QString &caption = QString(),
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
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 "sqlreadonly.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
namespace QETSql {
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief QETSql::isSingleReadOnlyStatement
|
||||||
|
Ask SQLite itself whether @p query is exactly one statement, and whether
|
||||||
|
that statement only reads.
|
||||||
|
|
||||||
|
Why SQLite is asked rather than the text inspected: a check on the
|
||||||
|
query's first keyword cannot see what the statement actually does.
|
||||||
|
SQLite has allowed a CTE prefix in front of a data-modifying statement
|
||||||
|
since 3.8.3, so
|
||||||
|
|
||||||
|
@code
|
||||||
|
WITH x AS (SELECT 1) DELETE FROM element
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
begins with WITH, contains no semicolon, and deletes every row.
|
||||||
|
sqlite3_stmt_readonly() reports on the statement SQLite compiled, not
|
||||||
|
on how it was spelled, so the same query is correctly refused here
|
||||||
|
while an ordinary WITH ... SELECT still passes.
|
||||||
|
|
||||||
|
The statement is compiled and immediately finalised; sqlite3_prepare_v2()
|
||||||
|
does not run it, so nothing is executed to reach this verdict.
|
||||||
|
|
||||||
|
This is a read-only test, NOT a statement-type allowlist: SQLite
|
||||||
|
considers ATTACH, BEGIN and several PRAGMAs read-only too, because none
|
||||||
|
of them change the contents of the database. Callers that need to
|
||||||
|
restrict which *kind* of statement is acceptable must say so separately
|
||||||
|
-- projectDataBase::newQuery() keeps isReadOnlySelect() in front of this
|
||||||
|
for exactly that reason.
|
||||||
|
|
||||||
|
@param handle the connection the query would run on. A null handle is
|
||||||
|
refused rather than waved through: without it there is nothing to ask,
|
||||||
|
and guessing from the text is the weakness this exists to replace.
|
||||||
|
@param query the raw SQL text
|
||||||
|
@param error set to a human-readable reason when this returns false
|
||||||
|
@return true if @p query is a single, read-only statement
|
||||||
|
*/
|
||||||
|
bool isSingleReadOnlyStatement(sqlite3 *handle, const QString &query, QString *error)
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
error->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handle) {
|
||||||
|
if (error) {
|
||||||
|
*error = QCoreApplication::translate("QETSql",
|
||||||
|
"Impossible de vérifier la requête : "
|
||||||
|
"aucune connexion SQLite disponible.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray utf8 = query.toUtf8();
|
||||||
|
sqlite3_stmt *statement = nullptr;
|
||||||
|
const char *tail = nullptr;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(handle, utf8.constData(), utf8.size(),
|
||||||
|
&statement, &tail) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
*error = QCoreApplication::translate("QETSql",
|
||||||
|
"Requête SQL invalide : %1")
|
||||||
|
.arg(QString::fromUtf8(sqlite3_errmsg(handle)));
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitespace or a bare comment compiles successfully to no statement
|
||||||
|
// at all, and sqlite3_stmt_readonly() must not be handed that.
|
||||||
|
if (!statement) {
|
||||||
|
if (error) {
|
||||||
|
*error = QCoreApplication::translate("QETSql",
|
||||||
|
"La requête ne contient aucune instruction.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool read_only = sqlite3_stmt_readonly(statement) != 0;
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
|
||||||
|
if (!read_only) {
|
||||||
|
if (error) {
|
||||||
|
*error = QCoreApplication::translate("QETSql",
|
||||||
|
"Seules les requêtes en lecture seule sont autorisées : "
|
||||||
|
"cette requête modifierait la base de données.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tail points just past the first statement, semicolon included.
|
||||||
|
// Anything left once semicolons and spacing are stripped is a second
|
||||||
|
// statement -- caught structurally here, where "SELECT ';'" is a
|
||||||
|
// perfectly ordinary query rather than a suspicious string.
|
||||||
|
if (tail) {
|
||||||
|
QString rest = QString::fromUtf8(tail);
|
||||||
|
rest.remove(QLatin1Char(';'));
|
||||||
|
if (!rest.trimmed().isEmpty()) {
|
||||||
|
if (error) {
|
||||||
|
*error = QCoreApplication::translate("QETSql",
|
||||||
|
"Une seule requête est autorisée.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QETSql
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
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 SQLREADONLY_H
|
||||||
|
#define SQLREADONLY_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
struct sqlite3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Deciding whether a piece of SQL only reads.
|
||||||
|
|
||||||
|
Deliberately its own translation unit, depending on nothing but QString
|
||||||
|
and SQLite: it is the enforcement point for every query QElectroTech
|
||||||
|
runs against a project database, including queries that arrive from
|
||||||
|
outside the application (a .qet file's saved report/table query), so it
|
||||||
|
is worth being able to test it in isolation -- see
|
||||||
|
tests/qttest/tst_sqlreadonly.cpp, which links this file and nothing
|
||||||
|
else of QElectroTech.
|
||||||
|
*/
|
||||||
|
namespace QETSql {
|
||||||
|
|
||||||
|
bool isSingleReadOnlyStatement(sqlite3 *handle,
|
||||||
|
const QString &query,
|
||||||
|
QString *error = nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SQLREADONLY_H
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "diagramposition.h"
|
#include "diagramposition.h"
|
||||||
#include "factory/elementfactory.h"
|
#include "factory/elementfactory.h"
|
||||||
#include "qetapp.h"
|
#include "qetapp.h"
|
||||||
|
#include "qetpalette.h"
|
||||||
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
|
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
|
||||||
#include "qetgraphicsitem/conductor.h"
|
#include "qetgraphicsitem/conductor.h"
|
||||||
#include "qetgraphicsitem/conductortextitem.h"
|
#include "qetgraphicsitem/conductortextitem.h"
|
||||||
@@ -283,10 +284,11 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) {
|
|||||||
* if background color is black,
|
* if background color is black,
|
||||||
* then grid spots shall be white,
|
* then grid spots shall be white,
|
||||||
* else they shall be black in color.
|
* else they shall be black in color.
|
||||||
|
* A view that shows the sheet with its lightness inverted
|
||||||
|
* gets softer dots, see QET::Palette::gridDotColor.
|
||||||
*/
|
*/
|
||||||
QPen pen;
|
QPen pen;
|
||||||
Diagram::background_color == Qt::black? pen.setColor(Qt::white)
|
pen.setColor(QET::Palette::gridDotColor(Diagram::background_color, m_inverted_lightness));
|
||||||
: pen.setColor(Qt::black);
|
|
||||||
pen.setCosmetic(true);
|
pen.setCosmetic(true);
|
||||||
p->setPen(pen);
|
p->setPen(pen);
|
||||||
|
|
||||||
@@ -1707,10 +1709,7 @@ bool Diagram::fromXml(QDomElement &document,
|
|||||||
//Get the top left corner of the rectangle that contain all added items
|
//Get the top left corner of the rectangle that contain all added items
|
||||||
QRectF items_rect;
|
QRectF items_rect;
|
||||||
for (auto item : added_items) {
|
for (auto item : added_items) {
|
||||||
items_rect = items_rect.united(
|
items_rect = items_rect.united(item->mapToScene(item->boundingRect()).boundingRect());
|
||||||
item->mapToScene(
|
|
||||||
item->boundingRect()
|
|
||||||
).boundingRect());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPointF point_ = items_rect.topLeft();
|
QPointF point_ = items_rect.topLeft();
|
||||||
@@ -1718,8 +1717,12 @@ bool Diagram::fromXml(QDomElement &document,
|
|||||||
position.y() - point_.y()));
|
position.y() - point_.y()));
|
||||||
|
|
||||||
//Translate all added items
|
//Translate all added items
|
||||||
for (auto qgi : added_items)
|
for (auto qgi : added_items) {
|
||||||
qgi->setPos(qgi->pos() += pos_);
|
qgi->setPos(qgi->pos() += pos_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load conductor
|
// Load conductor
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class Diagram : public QGraphicsScene
|
|||||||
qreal diagram_qet_version_;
|
qreal diagram_qet_version_;
|
||||||
|
|
||||||
bool draw_grid_;
|
bool draw_grid_;
|
||||||
|
bool m_inverted_lightness = false;
|
||||||
bool use_border_;
|
bool use_border_;
|
||||||
bool draw_guides_;
|
bool draw_guides_;
|
||||||
QList<Diagram::Guide> m_guides_list;
|
QList<Diagram::Guide> m_guides_list;
|
||||||
@@ -222,6 +223,7 @@ class Diagram : public QGraphicsScene
|
|||||||
ExportProperties applyProperties(const ExportProperties &);
|
ExportProperties applyProperties(const ExportProperties &);
|
||||||
void setDisplayGrid(bool);
|
void setDisplayGrid(bool);
|
||||||
bool displayGrid();
|
bool displayGrid();
|
||||||
|
void setInvertedLightness(bool);
|
||||||
void setDisplayGuides(bool);
|
void setDisplayGuides(bool);
|
||||||
bool displayGuides();
|
bool displayGuides();
|
||||||
void updateProjectGuides(const QList<GuideProperties> &guides);
|
void updateProjectGuides(const QList<GuideProperties> &guides);
|
||||||
@@ -355,6 +357,18 @@ inline void Diagram::setDisplayGrid(bool dg) {
|
|||||||
draw_grid_ = dg;
|
draw_grid_ = dg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Diagram::setInvertedLightness
|
||||||
|
Tell the diagram whether the view painting it will show the result
|
||||||
|
with its lightness inverted (PaletteGraphicsView on a dark palette).
|
||||||
|
drawBackground draws a softer grid in that case. Printing
|
||||||
|
and export never set this.
|
||||||
|
@param inverted
|
||||||
|
*/
|
||||||
|
inline void Diagram::setInvertedLightness(bool inverted) {
|
||||||
|
m_inverted_lightness = inverted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Diagram::displayGrid
|
@brief Diagram::displayGrid
|
||||||
@return draw_grid_ true if the grid is drawn, false otherwise.
|
@return draw_grid_ true if the grid is drawn, false otherwise.
|
||||||
|
|||||||
@@ -151,8 +151,8 @@ bool DiagramContext::operator!=(const DiagramContext &dc) const
|
|||||||
void DiagramContext::toXml(QDomElement &e, const QString &tag_name) const
|
void DiagramContext::toXml(QDomElement &e, const QString &tag_name) const
|
||||||
{
|
{
|
||||||
foreach (QString key, keys()) {
|
foreach (QString key, keys()) {
|
||||||
if ((tag_name == "elementInformation") &&
|
const QString raw = m_content[key].toString();
|
||||||
(m_content[key].toString().trimmed().isEmpty())) {
|
if ((tag_name == "elementInformation") && raw.trimmed().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QDomElement property = e.ownerDocument().createElement(tag_name);
|
QDomElement property = e.ownerDocument().createElement(tag_name);
|
||||||
@@ -161,7 +161,15 @@ void DiagramContext::toXml(QDomElement &e, const QString &tag_name) const
|
|||||||
property.removeAttribute("name");
|
property.removeAttribute("name");
|
||||||
property.setAttribute("show", m_content_show[key]);
|
property.setAttribute("show", m_content_show[key]);
|
||||||
property.setAttribute("name", key);
|
property.setAttribute("name", key);
|
||||||
QDomText value = e.ownerDocument().createTextNode(m_content[key].toString().trimmed());
|
// Trim stray leading/trailing whitespace around real content, but
|
||||||
|
// not a value that IS whitespace: unconditionally trimming an
|
||||||
|
// all-whitespace string collapses it to "", which is silently
|
||||||
|
// indistinguishable from a value that was never set. A title-block
|
||||||
|
// custom variable set to a single space -- a workaround for #973,
|
||||||
|
// where an unset variable renders as its own literal placeholder --
|
||||||
|
// would otherwise vanish on the very next save.
|
||||||
|
const QString stored = raw.trimmed().isEmpty() ? raw : raw.trimmed();
|
||||||
|
QDomText value = e.ownerDocument().createTextNode(stored);
|
||||||
property.appendChild(value);
|
property.appendChild(value);
|
||||||
e.appendChild(property);
|
e.appendChild(property);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef DIAGRAM_CONTEXT_H
|
#ifndef DIAGRAM_CONTEXT_H
|
||||||
#define DIAGRAM_CONTEXT_H
|
#define DIAGRAM_CONTEXT_H
|
||||||
#include "pugixml/src/pugixml.hpp"
|
#include "pugixml.hpp"
|
||||||
|
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|||||||
@@ -67,7 +67,19 @@ m_preview_item(nullptr)
|
|||||||
dummy_diagram->setDisplayGrid(false);
|
dummy_diagram->setDisplayGrid(false);
|
||||||
dummy_diagram->fromXml(diagram_node, QPointF(0, 0), false, nullptr);
|
dummy_diagram->fromXml(diagram_node, QPointF(0, 0), false, nullptr);
|
||||||
|
|
||||||
|
// Compute bounding rect of TOP-LEVEL items only (matching fromXml's added_items logic)
|
||||||
|
// Child items (DynamicElementTextItem, Terminal) are NOT included - they move with parents
|
||||||
|
QRectF top_level_rect;
|
||||||
|
for (auto *item : dummy_diagram->items()) {
|
||||||
|
if (!item->parentItem()) {
|
||||||
|
top_level_rect = top_level_rect.united(
|
||||||
|
item->mapToScene(item->boundingRect()).boundingRect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_items_top_left = top_level_rect.topLeft();
|
||||||
|
|
||||||
QRectF scene_rect = dummy_diagram->itemsBoundingRect();
|
QRectF scene_rect = dummy_diagram->itemsBoundingRect();
|
||||||
|
|
||||||
if (!scene_rect.isEmpty()) {
|
if (!scene_rect.isEmpty()) {
|
||||||
QPixmap pixmap(scene_rect.toAlignedRect().size());
|
QPixmap pixmap(scene_rect.toAlignedRect().size());
|
||||||
pixmap.fill(Qt::transparent);
|
pixmap.fill(Qt::transparent);
|
||||||
@@ -80,10 +92,11 @@ m_preview_item(nullptr)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_preview_item) {
|
if (m_preview_item) {
|
||||||
m_preview_item->setPos(Diagram::snapToGrid(pos));
|
QPointF snapped = Diagram::snapToGrid(pos);
|
||||||
m_preview_item->setOpacity(0.6);
|
m_preview_item->setPos(snapped);
|
||||||
m_diagram->addItem(m_preview_item);
|
m_preview_item->setOpacity(0.6);
|
||||||
|
m_diagram->addItem(m_preview_item);
|
||||||
m_running = true;
|
m_running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +130,7 @@ void DiagramEventAddMacro::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|||||||
{
|
{
|
||||||
if (m_preview_item) {
|
if (m_preview_item) {
|
||||||
const auto pos_{Diagram::snapToGrid(event->scenePos())};
|
const auto pos_{Diagram::snapToGrid(event->scenePos())};
|
||||||
|
|
||||||
m_preview_item->setPos(pos_);
|
m_preview_item->setPos(pos_);
|
||||||
|
|
||||||
if (m_status_bar) {
|
if (m_status_bar) {
|
||||||
@@ -141,7 +155,8 @@ void DiagramEventAddMacro::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|||||||
emit finish();
|
emit finish();
|
||||||
}
|
}
|
||||||
else if (event->button() == Qt::LeftButton) {
|
else if (event->button() == Qt::LeftButton) {
|
||||||
addMacro(Diagram::snapToGrid(event->scenePos()));
|
QPointF snapped = Diagram::snapToGrid(event->scenePos());
|
||||||
|
addMacro(snapped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event->setAccepted(true);
|
event->setAccepted(true);
|
||||||
@@ -238,10 +253,9 @@ void DiagramEventAddMacro::addMacro(QPointF final_pos)
|
|||||||
|
|
||||||
if (!diagram_node.isNull()) {
|
if (!diagram_node.isNull()) {
|
||||||
QDomElement cloned_node = diagram_node.cloneNode(true).toElement();
|
QDomElement cloned_node = diagram_node.cloneNode(true).toElement();
|
||||||
|
|
||||||
DiagramContent pasted_content;
|
DiagramContent pasted_content;
|
||||||
|
|
||||||
m_diagram->fromXml(cloned_node, final_pos, false, &pasted_content);
|
m_diagram->fromXml(cloned_node, final_pos + m_items_top_left, false, &pasted_content);
|
||||||
m_diagram->refreshContents();
|
m_diagram->refreshContents();
|
||||||
|
|
||||||
// Prevent PasteDiagramCommand from erasing labels (BMK)
|
// Prevent PasteDiagramCommand from erasing labels (BMK)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ private:
|
|||||||
QDomDocument m_macro_doc;
|
QDomDocument m_macro_doc;
|
||||||
QGraphicsPixmapItem *m_preview_item;
|
QGraphicsPixmapItem *m_preview_item;
|
||||||
QPointer<QStatusBar> m_status_bar;
|
QPointer<QStatusBar> m_status_bar;
|
||||||
|
QPointF m_items_top_left; // top-left of bounding rect of top-level items in the macro (for correct placement offset)
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DIAGRAMEVENTADDMACRO_H
|
#endif // DIAGRAMEVENTADDMACRO_H
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "../qetapp.h"
|
#include "../qetapp.h"
|
||||||
#include "../qetdiagrameditor.h"
|
#include "../qetdiagrameditor.h"
|
||||||
#include "../qetgraphicsitem/conductor.h"
|
#include "../qetgraphicsitem/conductor.h"
|
||||||
|
#include "../qetproject.h"
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos) :
|
DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos) :
|
||||||
DiagramEventInterface(diagram)
|
DiagramEventInterface(diagram)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(start_pos); // items stay at their original XML position
|
||||||
//DiagramEventInterface::init() is called by Diagram::setEventInterface
|
//DiagramEventInterface::init() is called by Diagram::setEventInterface
|
||||||
//only when it is replacing an earlier interface, so call it here as
|
//only when it is replacing an earlier interface, so call it here as
|
||||||
//DiagramEventAddMacro does.
|
//DiagramEventAddMacro does.
|
||||||
@@ -51,8 +53,25 @@
|
|||||||
QDomDocument document_xml;
|
QDomDocument document_xml;
|
||||||
if (!document_xml.setContent(clipboard_text)) return;
|
if (!document_xml.setContent(clipboard_text)) return;
|
||||||
|
|
||||||
|
//Batch the database work the same way project loading does
|
||||||
|
//(QETProject::readProjectXml): without this, every addItem()
|
||||||
|
//below emits dataBaseUpdated(), which makes each connected
|
||||||
|
//table model re-run its full SQL query -- ~77 queries for a
|
||||||
|
//typical paste, i.e. the multi-second stall on Ctrl+V.
|
||||||
|
auto *db = m_diagram->project() ? m_diagram->project()->dataBase() : nullptr;
|
||||||
|
if (db) {
|
||||||
|
db->blockSignals(true);
|
||||||
|
db->setUpdateBlocked(true);
|
||||||
|
}
|
||||||
|
|
||||||
//Load items at their original XML coordinates.
|
//Load items at their original XML coordinates.
|
||||||
m_diagram->fromXml(document_xml, QPointF(), false, &m_content);
|
m_diagram->fromXml(document_xml, QPointF(), false, &m_content);
|
||||||
|
|
||||||
|
if (db) {
|
||||||
|
db->blockSignals(false);
|
||||||
|
db->setUpdateBlocked(false);
|
||||||
|
db->updateDB();
|
||||||
|
}
|
||||||
if (!m_content.count()) return;
|
if (!m_content.count()) return;
|
||||||
|
|
||||||
const QList<QGraphicsItem *> movable = m_content.items(MovableItems);
|
const QList<QGraphicsItem *> movable = m_content.items(MovableItems);
|
||||||
@@ -85,33 +104,23 @@
|
|||||||
};
|
};
|
||||||
const QPointF grid_origin = snapGrid(top_left);
|
const QPointF grid_origin = snapGrid(top_left);
|
||||||
|
|
||||||
//Move the group to the cursor, rather than the cursor to the
|
//Store each item's original position. moveTo() applies a
|
||||||
//group. Both put the copy under the pointer, but warping the
|
|
||||||
//pointer also drags it back to the original's position, so the
|
|
||||||
//copy appears exactly on top of what was copied until the mouse
|
|
||||||
//is moved -- which is the thing pasting under the cursor was
|
|
||||||
//meant to avoid (issue #913). Taking the pointer away from
|
|
||||||
//where the user put it is also its own surprise.
|
|
||||||
m_group_origin = snapGrid(start_pos);
|
|
||||||
const QPointF offset = m_group_origin - grid_origin;
|
|
||||||
|
|
||||||
//Store each item's position after the move. moveTo() applies a
|
|
||||||
//grid-snapped delta from the baseline to these, so items
|
//grid-snapped delta from the baseline to these, so items
|
||||||
//preserve their layout and move in whole grid steps.
|
//preserve their layout and move in whole grid steps.
|
||||||
for (auto *item : movable) {
|
for (auto *item : movable) {
|
||||||
item->setPos(item->pos() + offset);
|
|
||||||
m_relative_pos.insert(item, item->pos());
|
m_relative_pos.insert(item, item->pos());
|
||||||
}
|
}
|
||||||
|
m_group_origin = grid_origin;
|
||||||
|
|
||||||
//The conductors were laid out against the old terminal
|
//The conductors were laid out against the original terminal
|
||||||
//positions, so re-route them before anything is drawn.
|
//positions, so re-route them before anything is drawn.
|
||||||
const QList<Conductor *> conductors = m_content.conductors(DiagramContent::AnyConductor);
|
const QList<Conductor *> conductors = m_content.conductors(DiagramContent::AnyConductor);
|
||||||
for (auto *conductor : conductors) {
|
for (auto *conductor : conductors) {
|
||||||
conductor->updatePath();
|
conductor->updatePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
//The baseline is known now, so moveTo() does not have to
|
//The baseline is the group's grid-snapped origin, so moveTo()
|
||||||
//capture one from the first mouse movement.
|
//does not have to capture one from the first mouse movement.
|
||||||
m_initial_cursor = m_group_origin;
|
m_initial_cursor = m_group_origin;
|
||||||
m_baseline_captured = true;
|
m_baseline_captured = true;
|
||||||
|
|
||||||
@@ -125,6 +134,14 @@
|
|||||||
if (const auto qde = QETApp::diagramEditorAncestorOf(view)) {
|
if (const auto qde = QETApp::diagramEditorAncestorOf(view)) {
|
||||||
m_status_bar = qde->statusBar();
|
m_status_bar = qde->statusBar();
|
||||||
}
|
}
|
||||||
|
//Warp the cursor to the group's grid-snapped origin so
|
||||||
|
//the actual cursor position matches m_initial_cursor.
|
||||||
|
//Without this the first mouseMoveEvent computes a large
|
||||||
|
//delta (cursor is still at the Ctrl+V press location)
|
||||||
|
//and the items jump on first touch.
|
||||||
|
const QPoint view_pos = view->mapFromScene(m_initial_cursor);
|
||||||
|
const QPoint global_pos = view->viewport()->mapToGlobal(view_pos);
|
||||||
|
QCursor::setPos(global_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showHint();
|
showHint();
|
||||||
|
|||||||
@@ -29,7 +29,14 @@
|
|||||||
*/
|
*/
|
||||||
DiagramEventAddText::DiagramEventAddText(Diagram *diagram) :
|
DiagramEventAddText::DiagramEventAddText(Diagram *diagram) :
|
||||||
DiagramEventInterface(diagram)
|
DiagramEventInterface(diagram)
|
||||||
{}
|
{
|
||||||
|
//The tool is armed from the moment it is attached: the next left
|
||||||
|
//click places a text. DiagramView::keyPressEvent() asks
|
||||||
|
//Diagram::eventInterfaceIsRunning() -- which is isRunning(), i.e.
|
||||||
|
//m_running -- before letting Escape through to the tool, so without
|
||||||
|
//this the view swallows Escape and the tool cannot be cancelled.
|
||||||
|
m_running = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief DiagramEventAddText::~DiagramEventAddText
|
@brief DiagramEventAddText::~DiagramEventAddText
|
||||||
@@ -52,6 +59,8 @@ void DiagramEventAddText::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|||||||
event->scenePos()));
|
event->scenePos()));
|
||||||
text->setTextInteractionFlags(Qt::TextEditorInteraction);
|
text->setTextInteractionFlags(Qt::TextEditorInteraction);
|
||||||
text->setFocus(Qt::MouseFocusReason);
|
text->setFocus(Qt::MouseFocusReason);
|
||||||
|
//Placed: the tool is done before it announces it.
|
||||||
|
m_running = false;
|
||||||
emit finish();
|
emit finish();
|
||||||
event->setAccepted(true);
|
event->setAccepted(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@
|
|||||||
#include "ElementsCollection/xmlelementcollection.h"
|
#include "ElementsCollection/xmlelementcollection.h"
|
||||||
#include "NameList/nameslist.h"
|
#include "NameList/nameslist.h"
|
||||||
#include "elementdialog.h"
|
#include "elementdialog.h"
|
||||||
|
#include <QApplication>
|
||||||
#include <QDropEvent>
|
#include <QDropEvent>
|
||||||
|
#include <QPainter>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +50,7 @@
|
|||||||
@param parent Le QWidget parent de cette vue de schema
|
@param parent Le QWidget parent de cette vue de schema
|
||||||
*/
|
*/
|
||||||
DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
||||||
QGraphicsView (parent),
|
PaletteGraphicsView (parent),
|
||||||
m_diagram (diagram)
|
m_diagram (diagram)
|
||||||
{
|
{
|
||||||
grabGesture(Qt::PinchGesture);
|
grabGesture(Qt::PinchGesture);
|
||||||
@@ -443,6 +445,82 @@ void DiagramView::pasteHere()
|
|||||||
paste(mapToScene(m_paste_here_pos));
|
paste(mapToScene(m_paste_here_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief DiagramView::duplicate
|
||||||
|
Copy the current selection and place the copy at @p stepOffset grid
|
||||||
|
steps from it, landing immediately rather than following the cursor
|
||||||
|
like Ctrl+V does (bugtracker #991). @p stepOffset comes from
|
||||||
|
DuplicateOffsetDialog: (1, 0) is one grid step right, (0, -1) is one
|
||||||
|
grid step up, and so on -- QET's own scene axes, X right and Y down.
|
||||||
|
|
||||||
|
No interactive placement step on purpose: the point of a duplicate
|
||||||
|
shortcut is unattended, repeatable stamping (configure the offset
|
||||||
|
once, then tap Ctrl+D to lay out a row), which following the cursor
|
||||||
|
would interrupt on every press. QET already reselects whatever a
|
||||||
|
paste just added (see PasteDiagramCommand::redo()), so the next
|
||||||
|
Ctrl+D naturally continues from the copy just placed, not the
|
||||||
|
original -- a press-and-hold row falls out of that for free, with no
|
||||||
|
special-casing needed here for "keep going from the last one".
|
||||||
|
|
||||||
|
The offset is applied by hand rather than by asking paste()/
|
||||||
|
Diagram::fromXml() to place the copy at a target position. Both of
|
||||||
|
those feed the position through Diagram::snapToGrid(), which reads
|
||||||
|
QApplication::keyboardModifiers() and rounds to the nearest PIXEL
|
||||||
|
instead of the grid whenever Ctrl is held -- and Ctrl is always held
|
||||||
|
here, this action's own shortcut being Ctrl+D. Measured the hard way
|
||||||
|
before settling on this: routing the offset through paste() first
|
||||||
|
produced copies off-grid on both axes, by an amount that tracked the
|
||||||
|
selection's own bounding-box geometry rather than being a fixed
|
||||||
|
error. fromXml() is instead called with no position at all, which
|
||||||
|
leaves every item at its source coordinates (landing the copy
|
||||||
|
exactly on top of the originals -- (0, 0) is not a position, this is
|
||||||
|
"keep the source coordinates"), and the offset is added directly
|
||||||
|
with setPos(). A plain addition cannot be off by a rounding rule
|
||||||
|
that never runs.
|
||||||
|
|
||||||
|
Conductors are not in the translated set: fromXml() itself does not
|
||||||
|
reposition them either -- they are loaded from XML after elements
|
||||||
|
are already in their final place and take their geometry from their
|
||||||
|
terminals, which have already moved with the elements that own
|
||||||
|
them. Likewise dynamic element texts are not translated separately:
|
||||||
|
they are children of their element and move with it under Qt's
|
||||||
|
normal parent-child transform.
|
||||||
|
*/
|
||||||
|
void DiagramView::duplicate(const QPoint &stepOffset)
|
||||||
|
{
|
||||||
|
if (!isInteractive() || m_diagram->isReadOnly()) return;
|
||||||
|
|
||||||
|
const QList<QGraphicsItem *> selection = m_diagram->selectedItems();
|
||||||
|
if (selection.isEmpty()) return;
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
const int x_grid = settings.value(QStringLiteral("diagrameditor/Xgrid"),
|
||||||
|
Diagram::xGrid).toInt();
|
||||||
|
const int y_grid = settings.value(QStringLiteral("diagrameditor/Ygrid"),
|
||||||
|
Diagram::yGrid).toInt();
|
||||||
|
const QPointF offset(stepOffset.x() * x_grid, stepOffset.y() * y_grid);
|
||||||
|
|
||||||
|
// Mirrors copy(), but does not touch the system clipboard: Ctrl+D
|
||||||
|
// should not clobber whatever the user last copied with Ctrl+C.
|
||||||
|
QDomDocument document = m_diagram->toXml(false, true);
|
||||||
|
|
||||||
|
DiagramContent pasted;
|
||||||
|
// No position argument -- see the function comment above for why
|
||||||
|
// the offset is not passed here.
|
||||||
|
m_diagram->fromXml(document, QPointF(), false, &pasted);
|
||||||
|
if (!pasted.count()) return;
|
||||||
|
|
||||||
|
const int movable = DiagramContent::Elements | DiagramContent::TextFields
|
||||||
|
| DiagramContent::Images | DiagramContent::Shapes
|
||||||
|
| DiagramContent::Tables | DiagramContent::TerminalStrip;
|
||||||
|
for (QGraphicsItem *item : pasted.items(movable))
|
||||||
|
item->setPos(item->pos() + offset);
|
||||||
|
|
||||||
|
m_diagram->clearSelection();
|
||||||
|
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, pasted));
|
||||||
|
adjustSceneRect();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Manage the events press click :
|
Manage the events press click :
|
||||||
* click to add an independent text field
|
* click to add an independent text field
|
||||||
@@ -1081,6 +1159,16 @@ bool DiagramView::event(QEvent *e) {
|
|||||||
return(QGraphicsView::event(e));
|
return(QGraphicsView::event(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief DiagramView::paintingInverted
|
||||||
|
Reimplemented from PaletteGraphicsView: tell the diagram it is being
|
||||||
|
drawn for an inverted display, so it softens its grid.
|
||||||
|
*/
|
||||||
|
void DiagramView::paintingInverted(bool inverted)
|
||||||
|
{
|
||||||
|
m_diagram->setInvertedLightness(inverted);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief DiagramView::paintEvent
|
@brief DiagramView::paintEvent
|
||||||
Reimplemented from QGraphicsView
|
Reimplemented from QGraphicsView
|
||||||
@@ -1088,7 +1176,7 @@ bool DiagramView::event(QEvent *e) {
|
|||||||
*/
|
*/
|
||||||
void DiagramView::paintEvent(QPaintEvent *event)
|
void DiagramView::paintEvent(QPaintEvent *event)
|
||||||
{
|
{
|
||||||
QGraphicsView::paintEvent(event);
|
PaletteGraphicsView::paintEvent(event);
|
||||||
|
|
||||||
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
|
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#include "titleblock/templatelocation.h"
|
#include "titleblock/templatelocation.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QGraphicsView>
|
#include "palettegraphicsview.h"
|
||||||
|
|
||||||
class Conductor;
|
class Conductor;
|
||||||
class Diagram;
|
class Diagram;
|
||||||
@@ -35,7 +35,7 @@ class QGestureEvent;
|
|||||||
This class provides a widget to render an electric diagram in an editable,
|
This class provides a widget to render an electric diagram in an editable,
|
||||||
interactive way.
|
interactive way.
|
||||||
*/
|
*/
|
||||||
class DiagramView : public QGraphicsView
|
class DiagramView : public PaletteGraphicsView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ class DiagramView : public QGraphicsView
|
|||||||
///Set for one call only, by the Escape handler, to let focus leave the view.
|
///Set for one call only, by the Escape handler, to let focus leave the view.
|
||||||
bool m_releasing_focus = false;
|
bool m_releasing_focus = false;
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void paintingInverted(bool inverted) override;
|
||||||
void mousePressEvent(QMouseEvent *) override;
|
void mousePressEvent(QMouseEvent *) override;
|
||||||
void mouseMoveEvent(QMouseEvent *) override;
|
void mouseMoveEvent(QMouseEvent *) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *) override;
|
void mouseReleaseEvent(QMouseEvent *) override;
|
||||||
@@ -136,6 +137,7 @@ class DiagramView : public QGraphicsView
|
|||||||
void copy();
|
void copy();
|
||||||
void paste(const QPointF & = QPointF(), QClipboard::Mode = QClipboard::Clipboard);
|
void paste(const QPointF & = QPointF(), QClipboard::Mode = QClipboard::Clipboard);
|
||||||
void pasteHere();
|
void pasteHere();
|
||||||
|
void duplicate(const QPoint &stepOffset);
|
||||||
void adjustSceneRect();
|
void adjustSceneRect();
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
void resetConductors();
|
void resetConductors();
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, Q
|
|||||||
QSqlQuery(cache_db_).exec("PRAGMA locking_mode = EXCLUSIVE");
|
QSqlQuery(cache_db_).exec("PRAGMA locking_mode = EXCLUSIVE");
|
||||||
QSqlQuery(cache_db_).exec("PRAGMA synchronous = OFF");
|
QSqlQuery(cache_db_).exec("PRAGMA synchronous = OFF");
|
||||||
|
|
||||||
|
// Previews used to be stored on an opaque white sheet; they are
|
||||||
|
// transparent now, so the collection tree can adapt them to a
|
||||||
|
// dark palette. A cache written before that is dropped once.
|
||||||
|
QSqlQuery(cache_db_).exec("CREATE TABLE IF NOT EXISTS meta"
|
||||||
|
"(key VARCHAR(32) NOT NULL PRIMARY KEY, value VARCHAR(64));");
|
||||||
|
QSqlQuery meta(cache_db_);
|
||||||
|
meta.exec("SELECT value FROM meta WHERE key = 'pixmaps'");
|
||||||
|
if (!meta.next() || meta.value(0).toString() != QLatin1String("transparent")) {
|
||||||
|
QSqlQuery(cache_db_).exec("DROP TABLE IF EXISTS pixmaps");
|
||||||
|
QSqlQuery(cache_db_).exec("DROP TABLE IF EXISTS names");
|
||||||
|
QSqlQuery(cache_db_).exec("REPLACE INTO meta (key, value) VALUES ('pixmaps', 'transparent')");
|
||||||
|
}
|
||||||
#if TODO_LIST
|
#if TODO_LIST
|
||||||
#pragma message("@TODO the tables could already exist, handle that case.")
|
#pragma message("@TODO the tables could already exist, handle that case.")
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -54,24 +54,8 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
|
|||||||
setDropIndicatorShown(true);
|
setDropIndicatorShown(true);
|
||||||
setAutoExpandDelay(1000);
|
setAutoExpandDelay(1000);
|
||||||
|
|
||||||
// force du noir sur une alternance de blanc (comme le schema) et de gris
|
// Rows follow the application palette; the icons shown here come from
|
||||||
// clair, avec du blanc sur bleu pas trop fonce pour la selection
|
// the icon theme, which has a dark variant.
|
||||||
//
|
|
||||||
// Element icons are rendered with colors read directly from each .elmt
|
|
||||||
// file (almost always black linework, matching printed-schematic
|
|
||||||
// convention) onto a transparent background -- so this view must keep
|
|
||||||
// a light background regardless of the OS/desktop theme, or the icons
|
|
||||||
// become invisible on dark themes. QAbstractItemView paints its rows
|
|
||||||
// using the viewport's palette, not the view's own, so the palette
|
|
||||||
// must be applied to both to actually take effect under every style.
|
|
||||||
QPalette qp = palette();
|
|
||||||
qp.setColor(QPalette::Text, Qt::black);
|
|
||||||
qp.setColor(QPalette::Base, Qt::white);
|
|
||||||
qp.setColor(QPalette::AlternateBase, QColor("#e8e8e8"));
|
|
||||||
qp.setColor(QPalette::Highlight, QColor("#678db2"));
|
|
||||||
qp.setColor(QPalette::HighlightedText, Qt::black);
|
|
||||||
setPalette(qp);
|
|
||||||
viewport()->setPalette(qp);
|
|
||||||
|
|
||||||
// we handle double click on items ourselves
|
// we handle double click on items ourselves
|
||||||
connect(this, &ElementsPanel::itemDoubleClicked, this, &ElementsPanel::slot_doubleClick);
|
connect(this, &ElementsPanel::itemDoubleClicked, this, &ElementsPanel::slot_doubleClick);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "QPropertyUndoCommand/qpropertyundocommand.h"
|
#include "QPropertyUndoCommand/qpropertyundocommand.h"
|
||||||
#include "diagram.h"
|
#include "diagram.h"
|
||||||
|
#include "qetapp.h"
|
||||||
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
||||||
#include "qetgraphicsitem/elementtextitemgroup.h"
|
#include "qetgraphicsitem/elementtextitemgroup.h"
|
||||||
|
|
||||||
@@ -137,25 +138,14 @@ void ElementTextsMover::endMovement()
|
|||||||
|
|
||||||
QString ElementTextsMover::undoText() const
|
QString ElementTextsMover::undoText() const
|
||||||
{
|
{
|
||||||
QString undo_text;
|
QStringList parts;
|
||||||
|
if (m_text_count)
|
||||||
if(m_text_count == 1)
|
parts << QObject::tr("%n texte(s) d'élément", "", m_text_count);
|
||||||
undo_text.append(QObject::tr("Déplacer un texte d'élément"));
|
if (m_group_count)
|
||||||
else if(m_text_count > 1)
|
parts << QObject::tr("%n groupe(s) de textes", "", m_group_count);
|
||||||
undo_text.append(QObject::tr("Déplacer %1 textes d'élément").arg(m_items_hash.size()));
|
|
||||||
|
if (parts.isEmpty())
|
||||||
if(m_group_count >= 1)
|
return QString(); // should never occur
|
||||||
{
|
|
||||||
if(undo_text.isEmpty())
|
return QObject::tr("Déplacer %1").arg(QLocale(QETApp::interfaceLanguage()).createSeparatedList(parts));
|
||||||
undo_text.append(QObject::tr("Déplacer"));
|
|
||||||
else
|
|
||||||
undo_text.append(QObject::tr(" et"));
|
|
||||||
|
|
||||||
if(m_group_count == 1)
|
|
||||||
undo_text.append(QObject::tr(" un groupe de texte"));
|
|
||||||
else
|
|
||||||
undo_text.append(QObject::tr((" %1 groupes de textes")).arg(m_group_count));
|
|
||||||
}
|
|
||||||
|
|
||||||
return undo_text;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,12 +146,13 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
|
|||||||
QPixmap pix(w, h);
|
QPixmap pix(w, h);
|
||||||
//Element definitions almost always draw with a hardcoded black
|
//Element definitions almost always draw with a hardcoded black
|
||||||
//stroke color, on the assumption of the white diagram sheet they
|
//stroke color, on the assumption of the white diagram sheet they
|
||||||
//are normally placed on. A transparent background here makes
|
//are normally placed on. The pixmap is kept as drawn, on a
|
||||||
//that stroke disappear against a dark widget/tree-view background
|
//transparent background: the places that show it (the
|
||||||
//(bugtracker #335). Give it an opaque white background instead -
|
//collection tree through ElementPreviewDelegate, the drag icon)
|
||||||
//exactly what the element already assumes visually, in every
|
//adapt it to the palette with QET::Palette::forPalette(), so a
|
||||||
//context this pixmap is used (tree icons, drag icon, previews).
|
//dark palette gets light ink instead of black on black
|
||||||
pix.fill(Qt::white);
|
//(bugtracker #335).
|
||||||
|
pix.fill(Qt::transparent);
|
||||||
|
|
||||||
QPainter painter(&pix);
|
QPainter painter(&pix);
|
||||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|||||||
@@ -32,8 +32,14 @@ class QetGraphicsTableFactory
|
|||||||
|
|
||||||
static void createAndAddNomenclature(Diagram *diagram);
|
static void createAndAddNomenclature(Diagram *diagram);
|
||||||
static void createAndAddSummary(Diagram *diagram);
|
static void createAndAddSummary(Diagram *diagram);
|
||||||
private:
|
// Public so a caller that has already built and configured an
|
||||||
|
// AddTableDialog itself (never shown or exec'd -- the two
|
||||||
|
// methods above always exec() one, which the scripting API
|
||||||
|
// cannot use headlessly) can create a table from it directly.
|
||||||
|
// create() only reads settings already on the dialog; nothing
|
||||||
|
// about it depends on the dialog having been shown.
|
||||||
static void create(Diagram *diagram, AddTableDialog *dialog);
|
static void create(Diagram *diagram, AddTableDialog *dialog);
|
||||||
|
private:
|
||||||
static QetGraphicsTableItem *newTable(
|
static QetGraphicsTableItem *newTable(
|
||||||
Diagram *diagram,
|
Diagram *diagram,
|
||||||
AddTableDialog *dialog,
|
AddTableDialog *dialog,
|
||||||
|
|||||||
@@ -72,6 +72,15 @@ bool AddTableDialog::adjustTableToFolio() const
|
|||||||
return ui->m_adjust_table_size_cb->isChecked();
|
return ui->m_adjust_table_size_cb->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief AddTableDialog::setAdjustTableToFolio
|
||||||
|
@param set
|
||||||
|
*/
|
||||||
|
void AddTableDialog::setAdjustTableToFolio(bool set)
|
||||||
|
{
|
||||||
|
ui->m_adjust_table_size_cb->setChecked(set);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief AddTableDialog::addNewTableToNewDiagram
|
@brief AddTableDialog::addNewTableToNewDiagram
|
||||||
@return
|
@return
|
||||||
@@ -81,6 +90,15 @@ bool AddTableDialog::addNewTableToNewDiagram() const
|
|||||||
return ui->m_add_table_and_folio->isChecked();
|
return ui->m_add_table_and_folio->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief AddTableDialog::setAddNewTableToNewDiagram
|
||||||
|
@param set
|
||||||
|
*/
|
||||||
|
void AddTableDialog::setAddNewTableToNewDiagram(bool set)
|
||||||
|
{
|
||||||
|
ui->m_add_table_and_folio->setChecked(set);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief AddTableDialog::tableName
|
@brief AddTableDialog::tableName
|
||||||
@return
|
@return
|
||||||
@@ -90,6 +108,18 @@ QString AddTableDialog::tableName() const
|
|||||||
return ui->m_table_name_le->text();
|
return ui->m_table_name_le->text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief AddTableDialog::setTableName
|
||||||
|
Set the name field directly, so a caller that builds this dialog to
|
||||||
|
read from (never shows or execs it -- the scripting API's addTable())
|
||||||
|
does not need a name typed by a user who was never there to type one.
|
||||||
|
@param name
|
||||||
|
*/
|
||||||
|
void AddTableDialog::setTableName(const QString &name)
|
||||||
|
{
|
||||||
|
ui->m_table_name_le->setText(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief AddTableDialog::headerMargins
|
@brief AddTableDialog::headerMargins
|
||||||
@return
|
@return
|
||||||
|
|||||||
@@ -44,9 +44,12 @@ class AddTableDialog : public QDialog
|
|||||||
|
|
||||||
void setQueryWidget(QWidget *widget);
|
void setQueryWidget(QWidget *widget);
|
||||||
bool adjustTableToFolio() const;
|
bool adjustTableToFolio() const;
|
||||||
|
void setAdjustTableToFolio(bool set);
|
||||||
bool addNewTableToNewDiagram() const;
|
bool addNewTableToNewDiagram() const;
|
||||||
|
void setAddNewTableToNewDiagram(bool set);
|
||||||
|
|
||||||
QString tableName() const;
|
QString tableName() const;
|
||||||
|
void setTableName(const QString &name);
|
||||||
|
|
||||||
QMargins headerMargins() const;
|
QMargins headerMargins() const;
|
||||||
Qt::Alignment headerAlignment() const;
|
Qt::Alignment headerAlignment() const;
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
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 "palettegraphicsview.h"
|
||||||
|
|
||||||
|
#include "qetpalette.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QStyleHintReturnMask>
|
||||||
|
#include <QStyleOptionRubberBand>
|
||||||
|
#include <QtMath>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/**
|
||||||
|
QGraphicsScene::drawItems() is protected, and QGraphicsView::drawItems()
|
||||||
|
hands the scene the viewport only when the painter is on it. The
|
||||||
|
view paints into an image, and still needs the scene to get the
|
||||||
|
viewport: that is what makes the scene record where it painted
|
||||||
|
each item, which is where the item is erased from when it moves.
|
||||||
|
Naming the member through a derived class is the standard way to
|
||||||
|
a pointer to a protected member; a call through it dispatches to
|
||||||
|
the scene's own override, if any.
|
||||||
|
*/
|
||||||
|
struct SceneAccess : QGraphicsScene
|
||||||
|
{
|
||||||
|
using DrawItems = void (QGraphicsScene::*)(QPainter *, int, QGraphicsItem *[],
|
||||||
|
const QStyleOptionGraphicsItem[], QWidget *);
|
||||||
|
static DrawItems drawItemsPointer() { return &SceneAccess::drawItems; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PaletteGraphicsView::PaletteGraphicsView(QWidget *parent) :
|
||||||
|
QGraphicsView(parent)
|
||||||
|
{
|
||||||
|
qApp->installEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
PaletteGraphicsView::PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent) :
|
||||||
|
QGraphicsView(scene, parent)
|
||||||
|
{
|
||||||
|
qApp->installEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::invertsLightness
|
||||||
|
@return true when the scene is shown with its lightness inverted,
|
||||||
|
i.e. when the application palette is dark. The application palette,
|
||||||
|
not the view's own: a style sheet on an ancestor (the folio tab
|
||||||
|
widget has one) makes QStyleSheetStyle pin the palette of every
|
||||||
|
widget under it to the application palette in force when the sheet
|
||||||
|
was applied, so after a live light/dark switch palette() is stale.
|
||||||
|
*/
|
||||||
|
bool PaletteGraphicsView::invertsLightness() const
|
||||||
|
{
|
||||||
|
return QET::Palette::isDark(QApplication::palette());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteGraphicsView::paintingInverted(bool inverted)
|
||||||
|
{
|
||||||
|
Q_UNUSED(inverted)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::eventFilter
|
||||||
|
Repaint the whole viewport when the application palette changes. Qt
|
||||||
|
sends that change to the application object and then repaints only
|
||||||
|
the widgets whose own palette changed with it, which under a style
|
||||||
|
sheet is not the case (see invertsLightness()): the scene would then
|
||||||
|
repaint only what it updates itself, and the viewport around the
|
||||||
|
sheet would keep the colors of the previous palette. The filter sits
|
||||||
|
on the application object, the one receiver Qt always notifies.
|
||||||
|
*/
|
||||||
|
bool PaletteGraphicsView::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (watched == qApp && event->type() == QEvent::ApplicationPaletteChange)
|
||||||
|
viewport()->update();
|
||||||
|
return QGraphicsView::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::paintEvent
|
||||||
|
Paints as QGraphicsView on a light palette, inverted on a dark one.
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
if (invertsLightness() && !customBackgroundColor())
|
||||||
|
{
|
||||||
|
paintInverted(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_buffer = QImage();
|
||||||
|
QGraphicsView::paintEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::paintInverted
|
||||||
|
Run QGraphicsView::paintEvent() with the drawing hooks redirected to
|
||||||
|
an off-screen image of the viewport, then invert the lightness of the
|
||||||
|
exposed part of that image between the palette's Base and Text colors
|
||||||
|
and blit it to the viewport. Inverting the finished rendering turns
|
||||||
|
the white sheet dark and the black ink light in one pass, and keeps
|
||||||
|
the hue of colored strokes. The image is in viewport coordinates, so
|
||||||
|
the hooks paint with the view's own transform and the scene records
|
||||||
|
the items' places in the viewport, as it does on a light palette.
|
||||||
|
@param event the paint event, for the exposed area
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::paintInverted(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
const QRect exposed = event->rect().intersected(viewport()->rect());
|
||||||
|
if (exposed.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qreal ratio = viewport()->devicePixelRatioF();
|
||||||
|
const QSize size(qCeil(viewport()->width() * ratio), qCeil(viewport()->height() * ratio));
|
||||||
|
if (m_buffer.size() != size || m_buffer.devicePixelRatio() != ratio)
|
||||||
|
{
|
||||||
|
m_buffer = QImage(size, QImage::Format_RGB32);
|
||||||
|
m_buffer.setDevicePixelRatio(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_buffer_painter.begin(&m_buffer);
|
||||||
|
// The hooks paint only what the scene draws; what they leave blank is
|
||||||
|
// the white sheet, which the inversion turns into the Base color.
|
||||||
|
m_buffer_painter.fillRect(exposed, Qt::white);
|
||||||
|
m_buffer_painter.setClipRect(exposed);
|
||||||
|
m_buffer_painter.setRenderHints(renderHints());
|
||||||
|
m_buffer_painter.setWorldTransform(viewportTransform());
|
||||||
|
|
||||||
|
m_inverting = true;
|
||||||
|
paintingInverted(true);
|
||||||
|
const OptimizationFlags flags = optimizationFlags();
|
||||||
|
setOptimizationFlag(QGraphicsView::IndirectPainting, true);
|
||||||
|
QGraphicsView::paintEvent(event);
|
||||||
|
setOptimizationFlags(flags);
|
||||||
|
paintingInverted(false);
|
||||||
|
m_inverting = false;
|
||||||
|
|
||||||
|
m_buffer_painter.end();
|
||||||
|
blitInverted(exposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::drawBackground
|
||||||
|
Into the off-screen image while painting inverted, else as
|
||||||
|
QGraphicsView.
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||||
|
{
|
||||||
|
QGraphicsView::drawBackground(m_inverting ? &m_buffer_painter : painter, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::drawItems
|
||||||
|
Into the off-screen image while painting inverted, with the viewport
|
||||||
|
as the scene's widget (see SceneAccess), else as QGraphicsView.
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::drawItems(QPainter *painter, int count, QGraphicsItem *items[],
|
||||||
|
const QStyleOptionGraphicsItem options[])
|
||||||
|
{
|
||||||
|
if (m_inverting && scene())
|
||||||
|
(scene()->*SceneAccess::drawItemsPointer())(&m_buffer_painter, count, items, options, viewport());
|
||||||
|
else
|
||||||
|
QGraphicsView::drawItems(painter, count, items, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::drawForeground
|
||||||
|
Into the off-screen image while painting inverted, else as
|
||||||
|
QGraphicsView.
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
|
||||||
|
{
|
||||||
|
QGraphicsView::drawForeground(m_inverting ? &m_buffer_painter : painter, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::blitInverted
|
||||||
|
Invert the lightness of \a area of the off-screen image and draw it on
|
||||||
|
the viewport, then the selection rubber band on top: the one
|
||||||
|
QGraphicsView::paintEvent() drew went under the blit.
|
||||||
|
@param area the part of the viewport to blit, in viewport coordinates
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::blitInverted(const QRect &area)
|
||||||
|
{
|
||||||
|
const qreal ratio = m_buffer.devicePixelRatio();
|
||||||
|
QImage part = m_buffer.copy(QRectF(area.topLeft() * ratio, area.size() * ratio).toAlignedRect());
|
||||||
|
part.setDevicePixelRatio(ratio);
|
||||||
|
// The application palette, for the reason given in invertsLightness().
|
||||||
|
const QPalette application_palette = QApplication::palette();
|
||||||
|
QET::Palette::invertLightness(part, application_palette.color(QPalette::Base),
|
||||||
|
application_palette.color(QPalette::Text));
|
||||||
|
|
||||||
|
QPainter painter(viewport());
|
||||||
|
painter.drawImage(area.topLeft(), part);
|
||||||
|
drawRubberBand(painter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PaletteGraphicsView::drawRubberBand
|
||||||
|
Draw the selection rubber band the way QGraphicsView::paintEvent does,
|
||||||
|
after the inversion, in the palette colors.
|
||||||
|
@param painter a painter on the viewport
|
||||||
|
*/
|
||||||
|
void PaletteGraphicsView::drawRubberBand(QPainter &painter)
|
||||||
|
{
|
||||||
|
const QRect band = rubberBandRect();
|
||||||
|
if (band.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QStyleOptionRubberBand option;
|
||||||
|
option.initFrom(viewport());
|
||||||
|
option.rect = band;
|
||||||
|
option.shape = QRubberBand::Rectangle;
|
||||||
|
|
||||||
|
QStyleHintReturnMask mask;
|
||||||
|
if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option,
|
||||||
|
viewport(), &mask))
|
||||||
|
painter.setClipRegion(mask.region, Qt::IntersectClip);
|
||||||
|
viewport()->style()->drawControl(QStyle::CE_RubberBand, &option,
|
||||||
|
&painter, viewport());
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
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 PALETTE_GRAPHICS_VIEW_H
|
||||||
|
#define PALETTE_GRAPHICS_VIEW_H
|
||||||
|
|
||||||
|
#include <QGraphicsView>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
/**
|
||||||
|
A QGraphicsView that shows its scene with inverted lightness while the
|
||||||
|
application palette is dark: white becomes the palette's Base color,
|
||||||
|
black its Text color, and colored strokes keep their hue. The scene
|
||||||
|
itself is left as drawn, so printing and exporting it still give black
|
||||||
|
on white. On a light palette the view paints exactly as QGraphicsView
|
||||||
|
does.
|
||||||
|
|
||||||
|
On a dark palette the view still runs QGraphicsView::paintEvent(), with
|
||||||
|
the IndirectPainting flag set for the duration of that call, so that
|
||||||
|
the background, the items and the foreground come through the
|
||||||
|
drawBackground(), drawItems() and drawForeground() hooks. (That flag
|
||||||
|
selects Qt's older item-painting algorithm, which first builds a list
|
||||||
|
of the exposed items and their style options; it is set only while
|
||||||
|
the view paints inverted.) The hooks
|
||||||
|
paint into an off-screen image the size of the viewport, in viewport
|
||||||
|
coordinates; paintEvent() then inverts the lightness of the exposed
|
||||||
|
part of that image and blits it. Going through the real paint event,
|
||||||
|
and handing the scene the viewport when the items are drawn, keeps the
|
||||||
|
view on QGraphicsView's default update path, which erases a moved item
|
||||||
|
where it was last painted, children included, even a child whose
|
||||||
|
geometry is set while its parent is painted (a terminal's help lines).
|
||||||
|
The alternative, rendering with QGraphicsView::render(), needed a
|
||||||
|
receiver on QGraphicsScene::changed() to keep the scene's updates
|
||||||
|
flowing, and that receiver puts the scene on its Qt 4.4 compatibility
|
||||||
|
path, which erases only the moved item's own old rectangle: a moved
|
||||||
|
element then left its terminals' help lines, which span the whole
|
||||||
|
sheet, behind at every step (#954).
|
||||||
|
|
||||||
|
The CacheBackground cache mode is not supported on the inverted path.
|
||||||
|
*/
|
||||||
|
class PaletteGraphicsView : public QGraphicsView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PaletteGraphicsView(QWidget *parent = nullptr);
|
||||||
|
explicit PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
bool invertsLightness() const;
|
||||||
|
|
||||||
|
static void setCustomBackgroundColor(bool custom) { s_custom_bg = custom; }
|
||||||
|
static bool customBackgroundColor() { return s_custom_bg; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void drawBackground(QPainter *painter, const QRectF &rect) override;
|
||||||
|
void drawItems(QPainter *painter, int count, QGraphicsItem *items[],
|
||||||
|
const QStyleOptionGraphicsItem options[]) override;
|
||||||
|
void drawForeground(QPainter *painter, const QRectF &rect) override;
|
||||||
|
/**
|
||||||
|
Called with true right before the scene is painted for an
|
||||||
|
inverted display and with false right after, so a scene can
|
||||||
|
adapt what it draws (a softer grid, for instance). Does
|
||||||
|
nothing by default.
|
||||||
|
*/
|
||||||
|
virtual void paintingInverted(bool inverted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintInverted(QPaintEvent *event);
|
||||||
|
void blitInverted(const QRect &area);
|
||||||
|
void drawRubberBand(QPainter &painter);
|
||||||
|
|
||||||
|
/// The off-screen image the hooks paint into while m_inverting:
|
||||||
|
/// the viewport's size, in its coordinates. Kept between paints,
|
||||||
|
/// dropped when the view paints on a light palette again.
|
||||||
|
QImage m_buffer;
|
||||||
|
QPainter m_buffer_painter;
|
||||||
|
/// True while paintEvent() paints for an inverted display.
|
||||||
|
bool m_inverting = false;
|
||||||
|
static inline bool s_custom_bg = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "qet.h"
|
#include "qet.h"
|
||||||
|
#include "qetapp.h"
|
||||||
#include "qeticons.h"
|
#include "qeticons.h"
|
||||||
#include "shortcutmanager.h"
|
#include "shortcutmanager.h"
|
||||||
|
|
||||||
@@ -271,84 +272,88 @@ QString QET::ElementsAndConductorsSentence(
|
|||||||
int tables_count,
|
int tables_count,
|
||||||
int terminal_strip_count)
|
int terminal_strip_count)
|
||||||
{
|
{
|
||||||
QString text;
|
QStringList parts;
|
||||||
if (elements_count) {
|
if (elements_count) {
|
||||||
text += QObject::tr(
|
parts.append(
|
||||||
"%n élément(s)",
|
QObject::tr(
|
||||||
"part of a sentence listing the content of a diagram",
|
"%n élément(s)",
|
||||||
elements_count
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
|
elements_count
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conductors_count) {
|
if (conductors_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
text += QObject::tr(
|
QObject::tr(
|
||||||
"%n conducteur(s)",
|
"%n conducteur(s)",
|
||||||
"part of a sentence listing the content of a diagram",
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
conductors_count
|
conductors_count
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texts_count) {
|
if (texts_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
text += QObject::tr(
|
QObject::tr(
|
||||||
"%n champ(s) de texte",
|
"%n champ(s) de texte",
|
||||||
"part of a sentence listing the content of a diagram",
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
texts_count
|
texts_count
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (images_count) {
|
if (images_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
// Qt's %n only selects a grammatical singular/plural form (the
|
QObject::tr(
|
||||||
// "(s)" convention used by every other count here) -- it never
|
"%n image(s)",
|
||||||
// spells the number out as a word, so getting "une image"
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
// instead of the literal "1 image" for the single-item case
|
images_count
|
||||||
// means handling that count outside %n entirely, with its own
|
)
|
||||||
// fixed string.
|
);
|
||||||
text += images_count == 1
|
|
||||||
? QObject::tr("une image", "part of a sentence listing the content of a diagram")
|
|
||||||
: QObject::tr(
|
|
||||||
"%n images",
|
|
||||||
"part of a sentence listing the content of a diagram",
|
|
||||||
images_count
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shapes_count) {
|
if (shapes_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
text += QObject::tr(
|
QObject::tr(
|
||||||
"%n forme(s)",
|
"%n forme(s)",
|
||||||
"part of a sentence listing the content of a diagram",
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
shapes_count
|
shapes_count
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element_text_count) {
|
if (element_text_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
text += QObject::tr(
|
QObject::tr(
|
||||||
"%n texte(s) d'élément",
|
"%n texte(s) d'élément",
|
||||||
"part of a sentence listing the content of a diagram",
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
element_text_count);
|
element_text_count
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tables_count) {
|
if (tables_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
text += QObject::tr(
|
QObject::tr(
|
||||||
"%n tableau(s)",
|
"%n tableau(s)",
|
||||||
"part of a sentence listing the content of diagram",
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
tables_count);
|
tables_count
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (terminal_strip_count) {
|
if (terminal_strip_count) {
|
||||||
if (!text.isEmpty()) text += ", ";
|
parts.append(
|
||||||
text += QObject::tr(
|
QObject::tr(
|
||||||
"%n plan de bornes",
|
"%n plan(s) de bornes",
|
||||||
"part of a sentence listing the content of a diagram",
|
"Sentence fragment used in an automatically generated list of different objects, e.g. objects moved at the same time, which will be combined into a sentence.",
|
||||||
terminal_strip_count);
|
terminal_strip_count
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return(text);
|
return QLocale(QETApp::interfaceLanguage()).createSeparatedList(parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "qetdiagrameditor.h"
|
#include "qetdiagrameditor.h"
|
||||||
#include "qeticons.h"
|
#include "qeticons.h"
|
||||||
#include "qetpalette.h"
|
#include "qetpalette.h"
|
||||||
|
#include "qetstyle.h"
|
||||||
#include "utils/qetutils.h"
|
#include "utils/qetutils.h"
|
||||||
#include "qetmessagebox.h"
|
#include "qetmessagebox.h"
|
||||||
#include "qetproject.h"
|
#include "qetproject.h"
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QStyleFactory>
|
||||||
#include <QStyleHints>
|
#include <QStyleHints>
|
||||||
#ifdef BUILD_WITHOUT_KF
|
#ifdef BUILD_WITHOUT_KF
|
||||||
# include "ui/nokde/kautosavefile.h"
|
# include "ui/nokde/kautosavefile.h"
|
||||||
@@ -233,6 +235,8 @@ QString QETApp::loadedQtTranslationFile()
|
|||||||
*/
|
*/
|
||||||
void QETApp::setLanguage(const QString &desired_language) {
|
void QETApp::setLanguage(const QString &desired_language) {
|
||||||
QString languages_path = languagesPath();
|
QString languages_path = languagesPath();
|
||||||
|
|
||||||
|
m_interface_language = desired_language;
|
||||||
|
|
||||||
// load Qt library translations
|
// load Qt library translations
|
||||||
QString qt_l10n_path = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
QString qt_l10n_path = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
||||||
@@ -1806,6 +1810,85 @@ void QETApp::useSystemPalette(bool use) {
|
|||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Widgets with their own style sheet keep the palette they were
|
||||||
|
// polished with; after a live light/dark switch they would stay in
|
||||||
|
// the old colors (see QET::Palette::refreshStyleSheets).
|
||||||
|
QET::Palette::refreshStyleSheets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief QETApp::useCustomPalette
|
||||||
|
Apply a user-chosen color as the application-wide palette.
|
||||||
|
Builds a full QPalette from \a color, keeping the system palette
|
||||||
|
as a fallback for roles we don't touch.
|
||||||
|
@param color the user-chosen base color
|
||||||
|
*/
|
||||||
|
void QETApp::useCustomPalette(const QColor &color) {
|
||||||
|
if (!color.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Derive readable text colors from the chosen color.
|
||||||
|
const bool dark = color.lightness() < 128;
|
||||||
|
const QColor text = dark ? QColor(220, 220, 220) : QColor(30, 30, 30);
|
||||||
|
const QColor disabled_text = dark ? QColor(175, 175, 175) : QColor(128, 128, 128);
|
||||||
|
|
||||||
|
// Slightly lighter/darker for button and window shading.
|
||||||
|
QColor button = color;
|
||||||
|
button = QColor::fromHslF(color.hslHueF(),
|
||||||
|
color.hslSaturationF(),
|
||||||
|
dark ? qMin(color.lightnessF() + 0.08, 1.0)
|
||||||
|
: qMax(color.lightnessF() - 0.08, 0.0));
|
||||||
|
QColor light = QColor::fromHslF(color.hslHueF(),
|
||||||
|
color.hslSaturationF(),
|
||||||
|
dark ? qMin(color.lightnessF() + 0.15, 1.0)
|
||||||
|
: qMax(color.lightnessF() - 0.15, 0.0));
|
||||||
|
QColor mid = QColor::fromHslF(color.hslHueF(),
|
||||||
|
color.hslSaturationF(),
|
||||||
|
dark ? qMin(color.lightnessF() + 0.04, 1.0)
|
||||||
|
: qMax(color.lightnessF() - 0.04, 0.0));
|
||||||
|
QColor dark_c = QColor::fromHslF(color.hslHueF(),
|
||||||
|
color.hslSaturationF(),
|
||||||
|
dark ? qMin(color.lightnessF() - 0.04, 1.0)
|
||||||
|
: qMax(color.lightnessF() - 0.12, 0.0));
|
||||||
|
QColor shadow = QColor::fromHslF(color.hslHueF(),
|
||||||
|
color.hslSaturationF(),
|
||||||
|
dark ? qMin(color.lightnessF() - 0.10, 1.0)
|
||||||
|
: qMax(color.lightnessF() - 0.20, 0.0));
|
||||||
|
|
||||||
|
QPalette p;
|
||||||
|
// Active and Inactive get the same colors; only Disabled differs.
|
||||||
|
for (auto group : {QPalette::Active, QPalette::Inactive}) {
|
||||||
|
p.setColor(group, QPalette::Window, color);
|
||||||
|
p.setColor(group, QPalette::WindowText, text);
|
||||||
|
p.setColor(group, QPalette::Base, color);
|
||||||
|
p.setColor(group, QPalette::AlternateBase, button);
|
||||||
|
p.setColor(group, QPalette::Text, text);
|
||||||
|
p.setColor(group, QPalette::Button, button);
|
||||||
|
p.setColor(group, QPalette::ButtonText, text);
|
||||||
|
p.setColor(group, QPalette::BrightText, dark ? QColor(255,90,90) : Qt::white);
|
||||||
|
p.setColor(group, QPalette::Highlight, QColor(30, 96, 176));
|
||||||
|
p.setColor(group, QPalette::HighlightedText, Qt::white);
|
||||||
|
p.setColor(group, QPalette::ToolTipBase, button);
|
||||||
|
p.setColor(group, QPalette::ToolTipText, text);
|
||||||
|
p.setColor(group, QPalette::Light, light);
|
||||||
|
p.setColor(group, QPalette::Midlight, mid);
|
||||||
|
p.setColor(group, QPalette::Mid, mid);
|
||||||
|
p.setColor(group, QPalette::Dark, dark_c);
|
||||||
|
p.setColor(group, QPalette::Shadow, shadow);
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
p.setColor(group, QPalette::Accent, QColor(30, 96, 176));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
p.setColor(QPalette::Disabled, QPalette::WindowText, disabled_text);
|
||||||
|
p.setColor(QPalette::Disabled, QPalette::Text, disabled_text);
|
||||||
|
p.setColor(QPalette::Disabled, QPalette::ButtonText, disabled_text);
|
||||||
|
|
||||||
|
qApp->setPalette(p);
|
||||||
|
qApp->setStyleSheet(QString());
|
||||||
|
|
||||||
|
// Switch icon theme to match light/dark.
|
||||||
|
applyIconTheme(p);
|
||||||
|
QET::Palette::refreshStyleSheets();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2368,6 +2451,12 @@ void QETApp::applyIconTheme(const QPalette &palette)
|
|||||||
*/
|
*/
|
||||||
void QETApp::initStyle()
|
void QETApp::initStyle()
|
||||||
{
|
{
|
||||||
|
// Wrap the running style so icons get a hover state (see qetstyle.h).
|
||||||
|
// The proxy keeps the base style's object name, so the Fusion checks
|
||||||
|
// below still see "fusion".
|
||||||
|
if (!qobject_cast<QETStyle *>(qApp->style()))
|
||||||
|
qApp->setStyle(new QETStyle(QStyleFactory::create(qApp->style()->objectName())));
|
||||||
|
|
||||||
initial_palette_ = qApp->palette();
|
initial_palette_ = qApp->palette();
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
@@ -2390,7 +2479,13 @@ void QETApp::initStyle()
|
|||||||
|
|
||||||
//Apply or not the system style
|
//Apply or not the system style
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
useSystemPalette(settings.value("usesystemcolors", true).toBool());
|
if (settings.value("usesystemcolors", true).toBool()) {
|
||||||
|
useSystemPalette(true);
|
||||||
|
} else if (settings.contains("customapplicationcolor")) {
|
||||||
|
useCustomPalette(QColor(settings.value("customapplicationcolor").toString()));
|
||||||
|
} else {
|
||||||
|
useSystemPalette(false);
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
#if defined(Q_OS_MACOS) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
// Setting an application palette stops Qt from following the OS
|
// Setting an application palette stops Qt from following the OS
|
||||||
@@ -3031,3 +3126,5 @@ int QETApp::projectId(const QETProject *project) {
|
|||||||
}
|
}
|
||||||
return(-1);
|
return(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QETApp::m_interface_language;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class QETApp : public QObject
|
|||||||
public:
|
public:
|
||||||
static QETApp *instance();
|
static QETApp *instance();
|
||||||
void setLanguage(const QString &);
|
void setLanguage(const QString &);
|
||||||
|
static QString interfaceLanguage() { return m_interface_language; }
|
||||||
static QString langFromSetting ();
|
static QString langFromSetting ();
|
||||||
void switchLayout(Qt::LayoutDirection);
|
void switchLayout(Qt::LayoutDirection);
|
||||||
static void printHelp();
|
static void printHelp();
|
||||||
@@ -246,6 +247,8 @@ class QETApp : public QObject
|
|||||||
static QString m_user_company_tbt_dir;
|
static QString m_user_company_tbt_dir;
|
||||||
static QString m_user_custom_tbt_dir;
|
static QString m_user_custom_tbt_dir;
|
||||||
static QString m_user_macros_dir;
|
static QString m_user_macros_dir;
|
||||||
|
|
||||||
|
static QString m_interface_language;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void systray(QSystemTrayIcon::ActivationReason);
|
void systray(QSystemTrayIcon::ActivationReason);
|
||||||
@@ -263,6 +266,7 @@ class QETApp : public QObject
|
|||||||
void setMainWindowVisible(QMainWindow *, bool);
|
void setMainWindowVisible(QMainWindow *, bool);
|
||||||
void invertMainWindowVisibility(QWidget *);
|
void invertMainWindowVisibility(QWidget *);
|
||||||
void useSystemPalette(bool);
|
void useSystemPalette(bool);
|
||||||
|
void useCustomPalette(const QColor &color);
|
||||||
void quitQET();
|
void quitQET();
|
||||||
void checkRemainingWindows();
|
void checkRemainingWindows();
|
||||||
void openFiles(const QETArguments &);
|
void openFiles(const QETArguments &);
|
||||||
|
|||||||